This MacOS app generates static websites that allow photography clubs to showcase the portfolios of their members.
Photo Club Hub HTML has a companion app named vdhamer/Photo-Club-Hub for iOS and iPadOS. This MacOS version of the app supports users with Android phones, PCs, Macs, and many other platforms. This version is functionally comparable to the iOS version (comparison below), but it works very differently: the app generates cross-linked HTML pages (on a Mac) that can then be viewed on e.g. an Android phone.
The concept behind both apps is to provide a (central) portal to view image portfolios that are managed by individual photo clubs.
The data rendered in both apps is organized as a 4-level hierarchy of data files that each contain (JSON) lists of items:
| Level | Typical number of files | Files are lists of | Files are maintained | Input data format |
|---|---|---|---|---|
| 3 | 1000+ | Images per photographer | by the clubs | .html |
| 2 | 100+ | Members per club | by the clubs | .level2.json |
| 1 | 10+ | Clubs per region | centrally | .level1.json |
| 0 | dozens | supported Expertise tags | centrally | .level0.json |
Layers 1-3 form a tree structure: a club Member can show a portfolio containing multiple Images. A Club typically has a dozen or more Members. A photographer can be a member of one or more clubs. To simplify data management and data ownership, Clubs can also be organized into a tree structure (e.g. a node per country, district, etc).
Technically the iOS app downloads the required JSON data files at startup and uses these to populate the user interface on an iPhone or iPad. Furthermore, the iOS app uses an in-app database (currently Core Data) that persists the data downloaded during previous sessions. This allows the app to already display more or less up to date information while refreshing the information. Any changes to the information are propagated to the (SwiftUI) user interface.
The MacOS version of the app reads the same JSON data files at startup (but without persistent storage) and converts them into static HTML pages. The static HTML generation is implemented using twostraws/ignite. These generated HTML pages can be hosted on any HTTP server and then viewed and navigated using a browser on any platform. These generated HTML pages can also be integrated into an existing (e.g. WordPress, Joomla) website by simple links from the existing pages or an existing menu to the newly generated pages.
Because the HTML pages are static, the generator app should ideally be rerun whenever the club data is updated. This might be once a year or once a month, for example when the list of club members changes. This can replace similar updating to traditional (non-generated) web pages but - because the data comes from a single data source - there is no data copying with associated risk of errors.
The Build HTML menu at the top-right allows you to choose which kinds of HTML pages to generate:
- A list of available expertise tags for photographers ("L0: expertises")
- A list of clubs ("L1: clubs")
- A list of museums ("L1: museums"). The data exists, but HTML generation hasn't been implemented yet, so it is still disabled.
- A list of members for a selected club ("L2: club members"). This requires selecting a club from the sidebar.
During website generation here is no proper feedback yet to the user (but the site is generated in less than a second).
The path to the directory with the newly generated pages will resemble
/Users/peter/Library/Containers/com.vdHamer.Photo-Club-Hub-HTML/Data/Build.
In the Settings... menu you can select whether you want to generate this for a localhost:8000 web server or
for a remote host.
For a remote host, the app currently requires you to separately copy the generated directory content to that host.
For this you can use an FTP client like Filezilla. The FTP client will require you to know the remote address
and the ftp credentials for that site. Once the directory content is copied to your remote server, you can view it in a
browser and optionally link to it from an existing website.
This website generator serves as an alternative for the Photo Club Hub iOS app:
it allows users to view the images on devices running Android, Windows, MacOS, etc.
| Photo Club Hub | Photo Club Hub HTML | |
|---|---|---|
| Runs on | iOS, iPadOS, (MacOS, VisionOS) | all major browsers |
| Available via | App Store | URLs |
| Mobile friendly | yes | yes |
| Lists clubs | yes | yes |
| Lists photo museums | yes | -✲ |
| Lists current club members | yes | yes |
| Lists former club members | optionally | optionally |
| Displays member portfolios | yes | yes |
| Linkable member portfolios | partially✲ | yes |
| Autoplay of portfolios | yes | yes |
| Content updated | whenever club updates its data | whenever the data is regenerated |
| Maps showing clubs | yes | - |
| Languages | English, Dutch✲ | English, Dutch✲ |
| Internal database | yes | - |
| Data caching | partially✲ | by browser |
| Concurrent data fetching | yes | yes |
| Open source | yes | yes |
✲ = might be improved or supported in the future
| Technology | Description | Source |
|---|---|---|
| twostraws/Ignite | static website generator | Github (Paul Hudson) |
| SwiftUI | UI framework | Apple |
| Core Data | data storage framework | Apple |
| SwiftyJSON/SwifyJSON | JSON parsing | Github |
This app runs on MacOS and generates a local directory with a few files and subdirectories (CSS, Javascript, image assets). These are then copied over to a club's existing server via e.g. FTP. Technically the files simply need to be hosted on an HTTP server such as a club's existing WordPress site.
The data being displayed on the individual HTML sites can get updated say 10 times per year. Because the update frequency is relatively low, and because the owners of the data are assumed to have limited "computer" expertise, it is best to generate static websites. This limits the hasstle to uploading a file to a directory and associated username/password. This should be easier and more robust than having custom server software that generates web pages on request.
Ignite allows us to create a tool in pure Swift
that generates the content of the static website without having to do HTML/CSS/Javascript coding.
Ignite is essentially a declarative higher-level description (Result Builder) that resembles data more than it resembles code.
From a technical perspective, Photo Club Hub and Photo Club HTML could have been implemented as a single repository with two (very) different targets that run on different platforms.
We chose to split the code into multiple repos to lower the barrier to contribute to either app. That gives us two respos. But common code is being factored out into a package in order to eliminate duplication of large amounts of code. So there will ultimately be three repositories in GitHub:
- Photo Club Hub (for iOS, interactive browing),
- Photo Club Hub HTML (for macOS, to generate static websites)
- Photo Club Hub Data (used by both to load and update JSON data into the Core Data database)
Initially there are only a handful of pilot clubs involved.
Data for a hundred clubs at <1 kB each can be contained in a single Level 1 file,
especially when loaded in the background and cached using Core Data.
To split up the level1.json file we allow the root.level1.json file to contain URL links to additional level1.json files.
This allows the root file to support a path like root/Netherlands or root/Japan/Tokio.
As a side benefit, this approach could allow a user to choose which banches of the level1 tree to load.
This hasn't been implemented yet.
Extra Level 1 sublevels should match the way the data and responsibilities are organized:
essentially the tree structure forms a chain of trust.
A "rogue" or just non-club site will only be reachable if there is a chain of valid links between the default root and that site.
Thus a site with questionable content (say my cat photos) can thus be isolated by removing a link.
But it would still be reachable using its URL (path like cats_and_more_cats/Berlin).
This is not a problem as long as the hierarchy has a single root node and a single path to any other node.
Conceivably both apps could someday allow an alternative root node to be selected for non-photo-club usages.
For now this is only possible by changing a constant in the source code.
- Fix the Ignite code (accepted PR for twostraws/Ignite) so that Ignite can be imported as a regular Swift package.
- Load the membership list from a .level2.json file. Currently the app contains a hardcoded partial copy of this data.
- localize the generated website to support multiple languages (initially English and Dutch).
- localize the app's UI to support English and Dutch (for now there isn't too much of a UI).
- support for the (new,
Level 0) data that allows photographers to be associated with keywords. - factor out common code between both apps into a Swift Package Manager package (almost done)
- allow the user to select the club for which to generate the local site (currently hardcoded constant, almost done).
- generate a static site that can serve as index of supported clubs (Level 1 data).
- generate index pages listing photographers associated with a particiular expertise
- generate all clubs in bulk instead of one club at a time
It would be nice to have an app for data enty/editing (rather than editing JSON files), but that would require adding another repo.



