Skip to content

Commit a8d97d8

Browse files
committed
Apps page reads from data/app-downloads.json: shows live install counts and versions per card, surfaces totals in the lede, sorts stable apps with most installs first; the curated PRESENTATION map filters out scaffold and library repos so the public surface stays focused
1 parent f4be1a8 commit a8d97d8

2 files changed

Lines changed: 206 additions & 103 deletions

File tree

sites/www/src/data/apps-catalog.js

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
/**
2+
* apps-catalog.js
3+
* ---------------
4+
* The single source of truth for what /apps and the home-page
5+
* AppsPreview render. Joins live download / version / store data
6+
* from `data/app-downloads.json` (refreshed weekday mornings by
7+
* `.github/workflows/app-downloads.yml`) with hand-curated display
8+
* metadata (taglines, icons, website categories) keyed by the app's
9+
* GitHub repo slug.
10+
*
11+
* Structure:
12+
*
13+
* PRESENTATION = id → {tagline, icon, categories, href, hidden?, sortKey?}
14+
* downloadsJson = generated, see scripts/app_downloads.py
15+
* getApps() = merged list for AppsGrid / AppsPreview, sorted by
16+
* install count descending; entries that aren't in
17+
* the store and have no downloads are filtered out
18+
* so scaffold repos (app-template, app_versions)
19+
* don't surface on the public site.
20+
*
21+
* Adding a new app: drop a row into PRESENTATION below. The next
22+
* GitHub-Actions refresh will fill in the live numbers automatically.
23+
*/
24+
25+
import React from 'react';
26+
import downloadsJson from '../../../../data/app-downloads.json';
27+
28+
/* Hand-curated display metadata. Keys are the GitHub repo slug
29+
(matches `apps[].id` in app-downloads.json). */
30+
const PRESENTATION = {
31+
opencatalogi: {
32+
name: 'OpenCatalogi',
33+
tagline: 'Public software catalog. Every app, dataset, API in your organisation, searchable in one place.',
34+
href: '/apps/opencatalogi',
35+
categories: ['Data'],
36+
icon: <svg viewBox="0 0 24 24"><path d="M3 7l9-4 9 4-9 4-9-4z"/><path d="M3 12l9 4 9-4"/><path d="M3 17l9 4 9-4"/></svg>,
37+
},
38+
openregister: {
39+
name: 'OpenRegister',
40+
tagline: 'Schemas, registers, structured data objects, the typed-data backbone for every app.',
41+
href: '/apps/openregister',
42+
categories: ['Data'],
43+
icon: <svg viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M3 9h18M9 4v16"/></svg>,
44+
},
45+
openconnector: {
46+
name: 'OpenConnector',
47+
tagline: <>Connect <span className="next-blue">Nextcloud</span> to anything, REST, SOAP, GraphQL, file drops, message queues.</>,
48+
href: '/apps/openconnector',
49+
categories: ['Connectors'],
50+
icon: <svg viewBox="0 0 24 24"><circle cx="6" cy="12" r="3"/><circle cx="18" cy="6" r="3"/><circle cx="18" cy="18" r="3"/><path d="M9 12h9M9 12l9-6M9 12l9 6"/></svg>,
51+
},
52+
docudesk: {
53+
name: 'DocuDesk',
54+
tagline: 'Auto-classify, anonymise, and route inbound documents. Drop them in a folder, get them filed.',
55+
href: '/apps/docudesk',
56+
categories: ['Documents'],
57+
icon: <svg viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6M16 13H8M16 17H8M10 9H8"/></svg>,
58+
},
59+
mydash: {
60+
name: 'MyDash',
61+
tagline: 'Personal and team dashboards built from your registers, no separate BI tool, no extra login.',
62+
href: '/apps/mydash',
63+
categories: ['Dashboards'],
64+
icon: <svg viewBox="0 0 24 24"><rect x="3" y="3" width="7" height="9"/><rect x="14" y="3" width="7" height="5"/><rect x="14" y="12" width="7" height="9"/><rect x="3" y="16" width="7" height="5"/></svg>,
65+
},
66+
openwoo: {
67+
name: 'OpenWoo',
68+
tagline: 'WOO-compliant publication flow. Active disclosure, queryable archive, citation-stable URLs.',
69+
href: '/apps/openwoo',
70+
categories: ['Processes'],
71+
icon: <svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3a14 14 0 0 1 0 18M12 3a14 14 0 0 0 0 18"/></svg>,
72+
},
73+
zaakafhandelapp: {
74+
name: 'ZaakAfhandelApp',
75+
tagline: 'Citizen-facing case-status portal. ZGW APIs, archief koppelvlakken, audit trail.',
76+
href: '/apps/zaakafhandelapp',
77+
categories: ['Processes'],
78+
icon: <svg viewBox="0 0 24 24"><rect x="4" y="4" width="16" height="16" rx="2"/><path d="M9 9h6v6H9z"/></svg>,
79+
},
80+
pipelinq: {
81+
name: 'PipelinQ',
82+
tagline: 'CRM with quotes, contacts, and deal-flow. Built on registers, no separate sales database.',
83+
href: '/apps/pipelinq',
84+
categories: ['Processes'],
85+
icon: <svg viewBox="0 0 24 24"><path d="M3 12h4l3-9 4 18 3-9h4"/></svg>,
86+
},
87+
procest: {
88+
name: 'Procest',
89+
tagline: 'Case-management for VTH and citizen processes. Workflow engine plus typed registers.',
90+
href: '/apps/procest',
91+
categories: ['Processes'],
92+
icon: <svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 3"/></svg>,
93+
},
94+
decidesk: {
95+
name: 'DeciDesk',
96+
tagline: 'Decision-support and board management. Agenda, dossiers, motions, voting, audit.',
97+
href: '/apps/decidesk',
98+
categories: ['Processes'],
99+
icon: <svg viewBox="0 0 24 24"><path d="M9 11l3 3 8-8"/><path d="M20 12v6a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h9"/></svg>,
100+
},
101+
softwarecatalog: {
102+
name: 'SoftwareCatalog',
103+
tagline: 'IT-asset management, software inventory, licenses, contracts, dependencies.',
104+
href: '/apps/softwarecatalog',
105+
categories: ['Data'],
106+
icon: <svg viewBox="0 0 24 24"><rect x="3" y="3" width="7" height="9"/><rect x="14" y="3" width="7" height="5"/><rect x="14" y="12" width="7" height="9"/><rect x="3" y="16" width="7" height="5"/></svg>,
107+
},
108+
larpingapp: {
109+
name: 'LarpingApp',
110+
tagline: 'Workflow and process orchestration for live-action role-play events. Visual designer, audit-logged.',
111+
href: '/apps/larpingapp',
112+
categories: ['Processes'],
113+
icon: <svg viewBox="0 0 24 24"><path d="M3 12h6l3-7 3 14 3-7h3"/></svg>,
114+
},
115+
nldesign: {
116+
name: 'NLDesign',
117+
tagline: 'Drop-in NLDS theme for Nextcloud, with the Conduction component variants on top.',
118+
href: '/apps/nldesign',
119+
categories: ['Documents'],
120+
icon: <svg viewBox="0 0 24 24"><path d="M4 4h16v6H4z"/><path d="M4 14h7v6H4z"/><path d="M14 14h6v6h-6z"/></svg>,
121+
},
122+
};
123+
124+
/* Categories the website filters by, in the order the chips render. */
125+
export const APP_CATEGORIES = ['All', 'Data', 'Processes', 'Connectors', 'Documents', 'AI', 'Dashboards'];
126+
127+
/* Pick a status label from the version string. The Nextcloud app
128+
store leaves the latest_version as-is, so "0.7.9-beta.8" reads as
129+
beta and "1.6.0" reads as stable. Falls back to GitHub's tag when
130+
store metadata is missing. */
131+
function statusFor(record) {
132+
const ver = (record.store && record.store.latest_version) || (record.github && record.github.latest_release) || '';
133+
const v = ver.toLowerCase();
134+
if (!ver) return 'COMING SOON';
135+
if (v.includes('beta') || v.includes('alpha') || v.includes('rc')) return 'BETA';
136+
if (v.startsWith('0.') || v.startsWith('v0.')) return 'BETA';
137+
return 'STABLE';
138+
}
139+
140+
/* Format the version into a short label "v1.6 · 5,740 installs". */
141+
function versionLabel(record) {
142+
const raw = (record.store && record.store.latest_version) || (record.github && record.github.latest_release) || '';
143+
const stripped = raw.replace(/^v/, '').replace(/-(beta|alpha|rc).*$/i, '');
144+
const short = stripped.split('.').slice(0, 2).join('.');
145+
const dl = record.github && record.github.downloads;
146+
const dlLabel = dl && dl > 0 ? `${dl.toLocaleString('en-US')} installs` : null;
147+
return [short && `v${short}`, dlLabel].filter(Boolean).join(' · ');
148+
}
149+
150+
/* Joined catalogue: hand-curated metadata + live numbers. Apps not in
151+
PRESENTATION (scaffolds, library repos, ExApp wrappers) are dropped
152+
so the public surface stays curated. */
153+
export function getApps() {
154+
const seen = new Set();
155+
const out = [];
156+
for (const record of downloadsJson.apps) {
157+
const meta = PRESENTATION[record.id];
158+
if (!meta) continue;
159+
if (seen.has(record.id)) continue;
160+
seen.add(record.id);
161+
out.push({
162+
...meta,
163+
status: statusFor(record),
164+
version: versionLabel(record),
165+
downloads: (record.github && record.github.downloads) || 0,
166+
});
167+
}
168+
/* Apps in PRESENTATION but not in the JSON yet (e.g. brand-new repos
169+
before the next workflow run) — surface them with COMING SOON. */
170+
for (const id of Object.keys(PRESENTATION)) {
171+
if (seen.has(id)) continue;
172+
out.push({
173+
...PRESENTATION[id],
174+
status: 'COMING SOON',
175+
version: '',
176+
downloads: 0,
177+
});
178+
}
179+
/* Sort: stable apps with most installs first, beta apps next, coming-
180+
soon last. Keeps the page anchored on what's actually shippable. */
181+
const statusRank = {STABLE: 0, BETA: 1, 'COMING SOON': 2};
182+
out.sort((a, b) => {
183+
const r = (statusRank[a.status] ?? 9) - (statusRank[b.status] ?? 9);
184+
if (r !== 0) return r;
185+
return (b.downloads || 0) - (a.downloads || 0);
186+
});
187+
return out;
188+
}
189+
190+
/* Convenience: the totals strip ("12 apps in the store · 22,400 installs"). */
191+
export function getCatalogTotals() {
192+
const apps = getApps();
193+
const installs = apps.reduce((n, a) => n + (a.downloads || 0), 0);
194+
return {
195+
apps: apps.filter(a => a.status !== 'COMING SOON').length,
196+
installs,
197+
generatedAt: downloadsJson.generated_at,
198+
};
199+
}

sites/www/src/pages/apps.mdx

Lines changed: 7 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -1,117 +1,21 @@
11
---
22
title: Apps, ConNext
3-
description: Twelve open-source apps that plug into Nextcloud. Catalogs, registers, integrations, document handling, dashboards.
3+
description: Open-source apps that plug into Nextcloud. Catalogs, registers, integrations, document handling, dashboards.
44
hide_table_of_contents: true
55
---
66

77
import {Section, SectionHead, AppsGrid} from '@conduction/docusaurus-preset/components';
8+
import {getApps, APP_CATEGORIES, getCatalogTotals} from '@site/src/data/apps-catalog';
9+
10+
export const apps = getApps();
11+
export const totals = getCatalogTotals();
812

913
<Section spacing="default">
1014
<SectionHead
1115
eyebrow="Apps"
1216
title={<>Pick an app.<br/>Install it. Done.</>}
13-
lede={<>Twelve open-source apps that plug into <span className="next-blue">Nextcloud</span>. Each one solves a concrete problem, no methodology, no roadmap workshop. Install from the <span className="next-blue">Nextcloud</span> app store and you're working in two minutes.</>}
17+
lede={<>Open-source apps that plug into <span className="next-blue">Nextcloud</span>. Each one solves a concrete problem, no methodology, no roadmap workshop. Install from the <span className="next-blue">Nextcloud</span> app store and you're working in two minutes. <strong>{totals.apps} apps</strong> in the store, <strong>{totals.installs.toLocaleString('en-US')}</strong> installs and counting.</>}
1418
/>
1519

16-
<AppsGrid
17-
categories={['All', 'Data', 'Processes', 'Connectors', 'Documents', 'AI', 'Dashboards']}
18-
apps={[
19-
{
20-
name: 'OpenCatalogi',
21-
tagline: 'Public software catalog. Every app, dataset, API in your organisation, searchable in one place.',
22-
status: 'STABLE', version: 'v2.4',
23-
href: '/apps/opencatalogi',
24-
categories: ['Data'],
25-
icon: <svg viewBox="0 0 24 24"><path d="M3 7l9-4 9 4-9 4-9-4z"/><path d="M3 12l9 4 9-4"/><path d="M3 17l9 4 9-4"/></svg>,
26-
},
27-
{
28-
name: 'OpenRegister',
29-
tagline: 'Schemas, registers, structured data objects, the typed-data backbone for every app.',
30-
status: 'STABLE', version: 'v3.1',
31-
href: '/apps/openregister',
32-
categories: ['Data'],
33-
icon: <svg viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M3 9h18M9 4v16"/></svg>,
34-
},
35-
{
36-
name: 'OpenConnector',
37-
tagline: <>Connect <span className="next-blue">Nextcloud</span> to anything, REST, SOAP, GraphQL, file drops, message queues.</>,
38-
status: 'BETA', version: 'v1.2',
39-
href: '/apps/openconnector',
40-
categories: ['Connectors'],
41-
icon: <svg viewBox="0 0 24 24"><circle cx="6" cy="12" r="3"/><circle cx="18" cy="6" r="3"/><circle cx="18" cy="18" r="3"/><path d="M9 12h9M9 12l9-6M9 12l9 6"/></svg>,
42-
},
43-
{
44-
name: 'DocuDesk',
45-
tagline: 'Auto-classify, anonymise, and route inbound documents. Drop them in a folder, get them filed.',
46-
status: 'STABLE', version: 'v1.8',
47-
href: '/apps/docudesk',
48-
categories: ['Documents'],
49-
icon: <svg viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6M16 13H8M16 17H8M10 9H8"/></svg>,
50-
},
51-
{
52-
name: 'MyDash',
53-
tagline: 'Personal and team dashboards built from your registers, no separate BI tool, no extra login.',
54-
status: 'BETA', version: 'v0.9',
55-
href: '/apps/mydash',
56-
categories: ['Dashboards'],
57-
icon: <svg viewBox="0 0 24 24"><rect x="3" y="3" width="7" height="9"/><rect x="14" y="3" width="7" height="5"/><rect x="14" y="12" width="7" height="9"/><rect x="3" y="16" width="7" height="5"/></svg>,
58-
},
59-
{
60-
name: 'OpenAI Bridge',
61-
tagline: 'Bring your own LLM. Local, hosted, or external. Audit log, redaction, prompt caching.',
62-
status: 'BETA', version: 'v0.3',
63-
href: '/apps/openai-bridge',
64-
categories: ['AI'],
65-
icon: <svg viewBox="0 0 24 24"><path d="M12 2a4 4 0 0 0-4 4v3"/><path d="M12 2a4 4 0 0 1 4 4v3"/><rect x="6" y="9" width="12" height="13" rx="2"/><path d="M9 14h6M9 18h4"/></svg>,
66-
},
67-
{
68-
name: 'OpenWoo',
69-
tagline: 'WOO-compliant publication flow. Active disclosure, queryable archive, citation-stable URLs.',
70-
status: 'STABLE', version: 'v1.4',
71-
href: '/apps/openwoo',
72-
categories: ['Processes'],
73-
icon: <svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3a14 14 0 0 1 0 18M12 3a14 14 0 0 0 0 18"/></svg>,
74-
},
75-
{
76-
name: 'ZaakAfhandelApp',
77-
tagline: 'Citizen-facing case-status portal. ZGW APIs, archief koppelvlakken, audit trail.',
78-
status: 'STABLE', version: 'v2.0',
79-
href: '/apps/zaakafhandelapp',
80-
categories: ['Processes'],
81-
icon: <svg viewBox="0 0 24 24"><rect x="4" y="4" width="16" height="16" rx="2"/><path d="M9 9h6v6H9z"/></svg>,
82-
},
83-
{
84-
name: 'PipelinQ',
85-
tagline: 'CRM with quotes, contacts, and deal-flow. Built on registers, no separate sales database.',
86-
status: 'BETA', version: 'v0.7',
87-
href: '/apps/pipelinq',
88-
categories: ['Processes'],
89-
icon: <svg viewBox="0 0 24 24"><path d="M3 12h4l3-9 4 18 3-9h4"/></svg>,
90-
},
91-
{
92-
name: 'Procest',
93-
tagline: 'Case-management for VTH and citizen processes. Workflow engine plus typed registers.',
94-
status: 'STABLE', version: 'v1.6',
95-
href: '/apps/procest',
96-
categories: ['Processes'],
97-
icon: <svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 3"/></svg>,
98-
},
99-
{
100-
name: 'DeciDesk',
101-
tagline: 'Decision-support and board management. Agenda, dossiers, motions, voting, audit.',
102-
status: 'BETA', version: 'v0.5',
103-
href: '/apps/decidesk',
104-
categories: ['Processes'],
105-
icon: <svg viewBox="0 0 24 24"><path d="M9 11l3 3 8-8"/><path d="M20 12v6a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h9"/></svg>,
106-
},
107-
{
108-
name: 'SoftwareCatalog',
109-
tagline: 'IT-asset management, software inventory, licenses, contracts, dependencies.',
110-
status: 'STABLE', version: 'v1.1',
111-
href: '/apps/softwarecatalog',
112-
categories: ['Data'],
113-
icon: <svg viewBox="0 0 24 24"><rect x="3" y="3" width="7" height="9"/><rect x="14" y="3" width="7" height="5"/><rect x="14" y="12" width="7" height="9"/><rect x="3" y="16" width="7" height="5"/></svg>,
114-
},
115-
]}
116-
/>
20+
<AppsGrid categories={APP_CATEGORIES} apps={apps} />
11721
</Section>

0 commit comments

Comments
 (0)