@atolis-hq/corum 0.1.0 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/web/index.html CHANGED
@@ -1,41 +1,41 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="utf-8" />
5
- <title>Corum</title>
6
- <meta name="viewport" content="width=device-width, initial-scale=1" />
7
- <link rel="icon" type="image/svg+xml" href="favicon.svg" />
8
- <link rel="preconnect" href="https://fonts.googleapis.com" />
9
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
10
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet" />
11
- <link rel="stylesheet" href="style.css" />
12
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css" />
13
- <script>
14
- window.CorumPlugins = {};
15
- </script>
16
- </head>
17
- <body>
18
- <div id="root"></div>
19
-
20
- <script src="https://unpkg.com/react@18.3.1/umd/react.production.min.js" crossorigin="anonymous"></script>
21
- <script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.production.min.js" crossorigin="anonymous"></script>
22
- <script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" crossorigin="anonymous"></script>
23
- <script src="nav.js"></script>
24
- <script src="router.js"></script>
25
- <script type="text/babel" src="primitives.jsx"></script>
26
- <script type="text/babel">
27
- fetch('/api/plugins')
28
- .then(response => response.ok ? response.json() : [])
29
- .then(files => {
30
- for (const file of files) {
31
- const script = document.createElement('script');
32
- script.type = 'text/babel';
33
- script.src = `/plugins/${file}`;
34
- document.body.appendChild(script);
35
- }
36
- })
37
- .catch(() => {});
38
- </script>
39
- <script type="text/babel" src="app.jsx"></script>
40
- </body>
41
- </html>
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <title>Corum</title>
6
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
7
+ <link rel="icon" type="image/svg+xml" href="favicon.svg" />
8
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
10
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet" />
11
+ <link rel="stylesheet" href="style.css" />
12
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css" />
13
+ <script>
14
+ window.CorumPlugins = {};
15
+ </script>
16
+ </head>
17
+ <body>
18
+ <div id="root"></div>
19
+
20
+ <script src="https://unpkg.com/react@18.3.1/umd/react.production.min.js" crossorigin="anonymous"></script>
21
+ <script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.production.min.js" crossorigin="anonymous"></script>
22
+ <script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" crossorigin="anonymous"></script>
23
+ <script src="nav.js"></script>
24
+ <script src="router.js"></script>
25
+ <script type="text/babel" src="primitives.jsx"></script>
26
+ <script type="text/babel">
27
+ fetch('/api/plugins')
28
+ .then(response => response.ok ? response.json() : [])
29
+ .then(files => {
30
+ for (const file of files) {
31
+ const script = document.createElement('script');
32
+ script.type = 'text/babel';
33
+ script.src = `/plugins/${file}`;
34
+ document.body.appendChild(script);
35
+ }
36
+ })
37
+ .catch(() => {});
38
+ </script>
39
+ <script type="text/babel" src="app.jsx"></script>
40
+ </body>
41
+ </html>
package/web/nav.js CHANGED
@@ -1,141 +1,141 @@
1
- /* Navigation tree builder - pure function, no DOM dependency. */
2
-
3
- const OVERLAY_DIFF_STATES = new Set([
4
- 'local-modified',
5
- 'default-only',
6
- 'ghost-single',
7
- 'ghost-consensus',
8
- 'ghost-conflict',
9
- ]);
10
-
11
- function collectNestedNavigation(nodes, templates) {
12
- const nodeMap = new Map(nodes.map(node => [node.id, node]));
13
- const templateMap = new Map(templates.map(template => [template.name, template]));
14
- const nestedByParent = new Map();
15
- const nestedNodeIds = new Set();
16
-
17
- for (const node of nodes) {
18
- if (!node.parentId || !node.ownedSection) continue;
19
- const parent = nodeMap.get(node.parentId);
20
- if (!parent) continue;
21
- const parentTemplate = templateMap.get(parent.template);
22
- const rule = parentTemplate?.ui?.nav?.nestOwned?.find(item => item.section === node.ownedSection);
23
- if (!rule) continue;
24
-
25
- if (!nestedByParent.has(parent.id)) nestedByParent.set(parent.id, new Map());
26
- const groups = nestedByParent.get(parent.id);
27
- if (!groups.has(node.ownedSection)) {
28
- groups.set(node.ownedSection, {
29
- label: rule.label ?? node.ownedSection,
30
- nodes: [],
31
- });
32
- }
33
- groups.get(node.ownedSection).nodes.push(node);
34
- nestedNodeIds.add(node.id);
35
- }
36
-
37
- return { templateMap, nestedByParent, nestedNodeIds };
38
- }
39
-
40
- function buildNavTree(nodes, templates) {
41
- const { templateMap, nestedByParent, nestedNodeIds } = collectNestedNavigation(nodes, templates);
42
- const plainByComponent = new Map();
43
- const groupsByComponent = new Map();
44
-
45
- for (const node of nodes) {
46
- if (nestedNodeIds.has(node.id)) continue;
47
- const template = templateMap.get(node.template);
48
- const navGroup = template?.ui?.nav?.navGroup;
49
- const navChildren = [...(nestedByParent.get(node.id)?.values() ?? [])].map(group => ({
50
- label: group.label,
51
- nodes: group.nodes.sort((a, b) => a.id.localeCompare(b.id)),
52
- }));
53
- const nodeWithChildren = { ...node, navChildren };
54
-
55
- if (navGroup) {
56
- if (!groupsByComponent.has(node.component)) groupsByComponent.set(node.component, new Map());
57
- const componentGroups = groupsByComponent.get(node.component);
58
- if (!componentGroups.has(navGroup)) componentGroups.set(navGroup, new Map());
59
- const subtypeMap = componentGroups.get(navGroup);
60
- if (!subtypeMap.has(node.template)) subtypeMap.set(node.template, []);
61
- subtypeMap.get(node.template).push(nodeWithChildren);
62
- } else {
63
- if (!plainByComponent.has(node.component)) plainByComponent.set(node.component, new Map());
64
- const componentMap = plainByComponent.get(node.component);
65
- if (!componentMap.has(node.template)) componentMap.set(node.template, []);
66
- componentMap.get(node.template).push(nodeWithChildren);
67
- }
68
- }
69
-
70
- const allComponents = new Set([...plainByComponent.keys(), ...groupsByComponent.keys()]);
71
- const tree = new Map();
72
-
73
- for (const component of allComponents) {
74
- const allEntries = [];
75
-
76
- const plainMap = plainByComponent.get(component);
77
- if (plainMap) {
78
- for (const [templateName, nodeList] of plainMap) {
79
- nodeList.sort((a, b) => a.id.localeCompare(b.id));
80
- allEntries.push({ _sortKey: templateName, kind: 'template', templateName, nodes: nodeList });
81
- }
82
- }
83
-
84
- const groupMap = groupsByComponent.get(component);
85
- if (groupMap) {
86
- for (const [groupTemplateName, subtypeMap] of groupMap) {
87
- const groupTemplate = templateMap.get(groupTemplateName);
88
- const children = [];
89
- for (const [templateName, nodeList] of [...subtypeMap.entries()].sort(([a], [b]) => a.localeCompare(b))) {
90
- nodeList.sort((a, b) => a.id.localeCompare(b.id));
91
- const childTemplate = templateMap.get(templateName);
92
- children.push({
93
- templateName,
94
- label: childTemplate?.ui?.displayName ?? templateName,
95
- icon: childTemplate?.ui?.icon,
96
- colour: childTemplate?.ui?.colour ?? groupTemplate?.ui?.colour ?? 'var(--ink-4)',
97
- nodes: nodeList,
98
- });
99
- }
100
- allEntries.push({
101
- _sortKey: groupTemplateName,
102
- kind: 'group',
103
- groupTemplateName,
104
- label: groupTemplate?.ui?.displayName ?? groupTemplateName,
105
- icon: groupTemplate?.ui?.icon,
106
- colour: groupTemplate?.ui?.colour ?? 'var(--ink-4)',
107
- children,
108
- });
109
- }
110
- }
111
-
112
- allEntries.sort((a, b) => a._sortKey.localeCompare(b._sortKey));
113
- tree.set(component, allEntries.map(({ _sortKey, ...entry }) => entry));
114
- }
115
-
116
- return tree;
117
- }
118
-
119
- function buildOverlayIndicatorIds(nodes, templates, overlayNodes, activeOverlayRefs) {
120
- if (!Array.isArray(activeOverlayRefs) || activeOverlayRefs.length === 0) return new Set();
121
-
122
- const visibleNavNodeIds = nodes.map(node => node.id);
123
- const indicatorIds = new Set();
124
-
125
- for (const overlayNode of overlayNodes ?? []) {
126
- if (!OVERLAY_DIFF_STATES.has(overlayNode.ghostState)) continue;
127
- if (!activeOverlayRefs.some(ref => overlayNode.branches.includes(ref))) continue;
128
-
129
- let bestMatch = null;
130
- for (const nodeId of visibleNavNodeIds) {
131
- if (overlayNode.id !== nodeId && !overlayNode.id.startsWith(`${nodeId}.`)) continue;
132
- if (!bestMatch || nodeId.length > bestMatch.length) bestMatch = nodeId;
133
- }
134
-
135
- if (bestMatch) indicatorIds.add(bestMatch);
136
- }
137
-
138
- return indicatorIds;
139
- }
140
-
141
- window.CorumNav = { buildNavTree, buildOverlayIndicatorIds };
1
+ /* Navigation tree builder - pure function, no DOM dependency. */
2
+
3
+ const OVERLAY_DIFF_STATES = new Set([
4
+ 'local-modified',
5
+ 'default-only',
6
+ 'ghost-single',
7
+ 'ghost-consensus',
8
+ 'ghost-conflict',
9
+ ]);
10
+
11
+ function collectNestedNavigation(nodes, templates) {
12
+ const nodeMap = new Map(nodes.map(node => [node.id, node]));
13
+ const templateMap = new Map(templates.map(template => [template.name, template]));
14
+ const nestedByParent = new Map();
15
+ const nestedNodeIds = new Set();
16
+
17
+ for (const node of nodes) {
18
+ if (!node.parentId || !node.ownedSection) continue;
19
+ const parent = nodeMap.get(node.parentId);
20
+ if (!parent) continue;
21
+ const parentTemplate = templateMap.get(parent.template);
22
+ const rule = parentTemplate?.ui?.nav?.nestOwned?.find(item => item.section === node.ownedSection);
23
+ if (!rule) continue;
24
+
25
+ if (!nestedByParent.has(parent.id)) nestedByParent.set(parent.id, new Map());
26
+ const groups = nestedByParent.get(parent.id);
27
+ if (!groups.has(node.ownedSection)) {
28
+ groups.set(node.ownedSection, {
29
+ label: rule.label ?? node.ownedSection,
30
+ nodes: [],
31
+ });
32
+ }
33
+ groups.get(node.ownedSection).nodes.push(node);
34
+ nestedNodeIds.add(node.id);
35
+ }
36
+
37
+ return { templateMap, nestedByParent, nestedNodeIds };
38
+ }
39
+
40
+ function buildNavTree(nodes, templates) {
41
+ const { templateMap, nestedByParent, nestedNodeIds } = collectNestedNavigation(nodes, templates);
42
+ const plainByComponent = new Map();
43
+ const groupsByComponent = new Map();
44
+
45
+ for (const node of nodes) {
46
+ if (nestedNodeIds.has(node.id)) continue;
47
+ const template = templateMap.get(node.template);
48
+ const navGroup = template?.ui?.nav?.navGroup;
49
+ const navChildren = [...(nestedByParent.get(node.id)?.values() ?? [])].map(group => ({
50
+ label: group.label,
51
+ nodes: group.nodes.sort((a, b) => a.id.localeCompare(b.id)),
52
+ }));
53
+ const nodeWithChildren = { ...node, navChildren };
54
+
55
+ if (navGroup) {
56
+ if (!groupsByComponent.has(node.component)) groupsByComponent.set(node.component, new Map());
57
+ const componentGroups = groupsByComponent.get(node.component);
58
+ if (!componentGroups.has(navGroup)) componentGroups.set(navGroup, new Map());
59
+ const subtypeMap = componentGroups.get(navGroup);
60
+ if (!subtypeMap.has(node.template)) subtypeMap.set(node.template, []);
61
+ subtypeMap.get(node.template).push(nodeWithChildren);
62
+ } else {
63
+ if (!plainByComponent.has(node.component)) plainByComponent.set(node.component, new Map());
64
+ const componentMap = plainByComponent.get(node.component);
65
+ if (!componentMap.has(node.template)) componentMap.set(node.template, []);
66
+ componentMap.get(node.template).push(nodeWithChildren);
67
+ }
68
+ }
69
+
70
+ const allComponents = new Set([...plainByComponent.keys(), ...groupsByComponent.keys()]);
71
+ const tree = new Map();
72
+
73
+ for (const component of allComponents) {
74
+ const allEntries = [];
75
+
76
+ const plainMap = plainByComponent.get(component);
77
+ if (plainMap) {
78
+ for (const [templateName, nodeList] of plainMap) {
79
+ nodeList.sort((a, b) => a.id.localeCompare(b.id));
80
+ allEntries.push({ _sortKey: templateName, kind: 'template', templateName, nodes: nodeList });
81
+ }
82
+ }
83
+
84
+ const groupMap = groupsByComponent.get(component);
85
+ if (groupMap) {
86
+ for (const [groupTemplateName, subtypeMap] of groupMap) {
87
+ const groupTemplate = templateMap.get(groupTemplateName);
88
+ const children = [];
89
+ for (const [templateName, nodeList] of [...subtypeMap.entries()].sort(([a], [b]) => a.localeCompare(b))) {
90
+ nodeList.sort((a, b) => a.id.localeCompare(b.id));
91
+ const childTemplate = templateMap.get(templateName);
92
+ children.push({
93
+ templateName,
94
+ label: childTemplate?.ui?.displayName ?? templateName,
95
+ icon: childTemplate?.ui?.icon,
96
+ colour: childTemplate?.ui?.colour ?? groupTemplate?.ui?.colour ?? 'var(--ink-4)',
97
+ nodes: nodeList,
98
+ });
99
+ }
100
+ allEntries.push({
101
+ _sortKey: groupTemplateName,
102
+ kind: 'group',
103
+ groupTemplateName,
104
+ label: groupTemplate?.ui?.displayName ?? groupTemplateName,
105
+ icon: groupTemplate?.ui?.icon,
106
+ colour: groupTemplate?.ui?.colour ?? 'var(--ink-4)',
107
+ children,
108
+ });
109
+ }
110
+ }
111
+
112
+ allEntries.sort((a, b) => a._sortKey.localeCompare(b._sortKey));
113
+ tree.set(component, allEntries.map(({ _sortKey, ...entry }) => entry));
114
+ }
115
+
116
+ return tree;
117
+ }
118
+
119
+ function buildOverlayIndicatorIds(nodes, templates, overlayNodes, activeOverlayRefs) {
120
+ if (!Array.isArray(activeOverlayRefs) || activeOverlayRefs.length === 0) return new Set();
121
+
122
+ const visibleNavNodeIds = nodes.map(node => node.id);
123
+ const indicatorIds = new Set();
124
+
125
+ for (const overlayNode of overlayNodes ?? []) {
126
+ if (!OVERLAY_DIFF_STATES.has(overlayNode.ghostState)) continue;
127
+ if (!activeOverlayRefs.some(ref => overlayNode.branches.includes(ref))) continue;
128
+
129
+ let bestMatch = null;
130
+ for (const nodeId of visibleNavNodeIds) {
131
+ if (overlayNode.id !== nodeId && !overlayNode.id.startsWith(`${nodeId}.`)) continue;
132
+ if (!bestMatch || nodeId.length > bestMatch.length) bestMatch = nodeId;
133
+ }
134
+
135
+ if (bestMatch) indicatorIds.add(bestMatch);
136
+ }
137
+
138
+ return indicatorIds;
139
+ }
140
+
141
+ window.CorumNav = { buildNavTree, buildOverlayIndicatorIds };