ketatlas 0.1.0
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/CHANGELOG.md +13 -0
- package/LICENSE +21 -0
- package/NOTICE.md +10 -0
- package/README.md +130 -0
- package/assets/INTER-LICENSE +93 -0
- package/assets/KETJS-LICENSE +21 -0
- package/assets/LUCIDE-LICENSE +43 -0
- package/assets/design-system.lock.json +56 -0
- package/assets/inter-latin-wght-normal.woff2 +0 -0
- package/assets/inter-vietnamese-wght-normal.woff2 +0 -0
- package/bin/audit.js +116 -0
- package/bin/ketatlas.js +148 -0
- package/bin/server.js +93 -0
- package/bin/viewer.js +20 -0
- package/docs/architecture.md +57 -0
- package/docs/authoring.md +49 -0
- package/docs/cli.md +70 -0
- package/docs/configuration.md +65 -0
- package/docs/integration.md +108 -0
- package/docs/migration.md +35 -0
- package/docs/releasing.md +40 -0
- package/package.json +59 -0
- package/schema.json +187 -0
- package/src/config.js +196 -0
- package/src/icons.js +23 -0
- package/src/index.d.ts +106 -0
- package/src/index.js +679 -0
- package/src/layout.js +103 -0
- package/src/template.js +38 -0
- package/styles/design-system.css +2838 -0
- package/styles/design-system.document.css +2838 -0
- package/styles/fonts.css +17 -0
- package/styles/ketatlas.css +992 -0
- package/templates/basic/README.md +14 -0
- package/templates/basic/atlas.json +45 -0
- package/templates/basic/ketatlas.schema.json +187 -0
- package/templates/basic/screens/done.html +18 -0
- package/templates/basic/screens/screen.css +48 -0
- package/templates/basic/screens/welcome.html +21 -0
- package/templates/basic/styles/LICENSE +21 -0
- package/templates/basic/styles/design-system.css +2838 -0
- package/templates/process/README.md +14 -0
- package/templates/process/atlas.json +83 -0
- package/templates/process/ketatlas.schema.json +187 -0
- package/templates/web/README.md +14 -0
- package/templates/web/atlas.json +77 -0
- package/templates/web/ketatlas.schema.json +187 -0
- package/templates/web/screens/demo.css +255 -0
- package/templates/web/screens/portal.html +41 -0
- package/templates/web/styles/LICENSE +21 -0
- package/templates/web/styles/design-system.css +2838 -0
package/src/layout.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/** Deterministic grid layout. Each row/column fits its largest card. */
|
|
2
|
+
export function layoutAtlas(config) {
|
|
3
|
+
const screens = new Map(config.screens.map((s) => [s.id, s]));
|
|
4
|
+
const nodes = [],
|
|
5
|
+
nodeByKey = new Map(),
|
|
6
|
+
flowById = new Map();
|
|
7
|
+
let height = 0,
|
|
8
|
+
width = 0;
|
|
9
|
+
const flows = config.flows.map((flow, index) => {
|
|
10
|
+
const f = { ...flow, index, y: height };
|
|
11
|
+
f.nodes = flow.nodes.map((n) => {
|
|
12
|
+
const screen = screens.get(n.screen),
|
|
13
|
+
v = screen?.viewport;
|
|
14
|
+
const cardWidth = v ? (v.width > 600 ? 460 : 232) : 270;
|
|
15
|
+
const previewWidth = cardWidth - 2;
|
|
16
|
+
const previewHeight = v ? (previewWidth * v.height) / v.width : 0;
|
|
17
|
+
return {
|
|
18
|
+
...n,
|
|
19
|
+
key: n.id,
|
|
20
|
+
col: n.column,
|
|
21
|
+
kind: n.type,
|
|
22
|
+
screenId: n.screen,
|
|
23
|
+
screen,
|
|
24
|
+
flowId: f.id,
|
|
25
|
+
uid: `${f.id}:${n.id}`,
|
|
26
|
+
title: n.title || screen?.title,
|
|
27
|
+
url: n.url || screen?.url,
|
|
28
|
+
description: n.description || screen?.description || "",
|
|
29
|
+
width: cardWidth,
|
|
30
|
+
previewWidth,
|
|
31
|
+
previewHeight,
|
|
32
|
+
height: v ? Math.ceil(previewHeight) + 92 : 230,
|
|
33
|
+
};
|
|
34
|
+
});
|
|
35
|
+
const cols = [],
|
|
36
|
+
rows = [];
|
|
37
|
+
for (const n of f.nodes) {
|
|
38
|
+
cols[n.col] = Math.max(cols[n.col] || 0, n.width);
|
|
39
|
+
rows[n.row] = Math.max(rows[n.row] || 0, n.height);
|
|
40
|
+
}
|
|
41
|
+
const offset = (sizes, i, gap, fallback) =>
|
|
42
|
+
Array.from({ length: i }, (_, k) => (sizes[k] || fallback) + gap).reduce((a, b) => a + b, 0);
|
|
43
|
+
for (const n of f.nodes) {
|
|
44
|
+
n.x = 80 + offset(cols, n.col, 170, 232);
|
|
45
|
+
n.y = height + 240 + offset(rows, n.row, 210, 230);
|
|
46
|
+
nodes.push(n);
|
|
47
|
+
nodeByKey.set(n.uid, n);
|
|
48
|
+
}
|
|
49
|
+
f.width = Math.max(...f.nodes.map((n) => n.x + n.width)) + 80;
|
|
50
|
+
f.height = Math.max(...f.nodes.map((n) => n.y + n.height)) - f.y + 60;
|
|
51
|
+
height += f.height + 120;
|
|
52
|
+
width = Math.max(width, f.width);
|
|
53
|
+
flowById.set(f.id, f);
|
|
54
|
+
return f;
|
|
55
|
+
});
|
|
56
|
+
return { flows, nodes, nodeByKey, flowById, width, height };
|
|
57
|
+
}
|
|
58
|
+
const ports = (n, side) =>
|
|
59
|
+
({
|
|
60
|
+
left: [n.x, n.y + n.height / 2],
|
|
61
|
+
right: [n.x + n.width, n.y + n.height / 2],
|
|
62
|
+
top: [n.x + n.width / 2, n.y],
|
|
63
|
+
bottom: [n.x + n.width / 2, n.y + n.height],
|
|
64
|
+
})[side];
|
|
65
|
+
export function edgePath(a, b, index = 0) {
|
|
66
|
+
const lane = index % 3;
|
|
67
|
+
let start, end, points, label;
|
|
68
|
+
if (a.uid === b.uid) {
|
|
69
|
+
start = ports(a, "right");
|
|
70
|
+
end = ports(b, "top");
|
|
71
|
+
const x = a.x + a.width + 70,
|
|
72
|
+
y = a.y - 70;
|
|
73
|
+
points = [start, [x, start[1]], [x, y], [end[0], y], end];
|
|
74
|
+
label = [x, y];
|
|
75
|
+
} else if (a.row === b.row && b.col === a.col + 1) {
|
|
76
|
+
start = ports(a, "right");
|
|
77
|
+
end = ports(b, "left");
|
|
78
|
+
const mid = (start[0] + end[0]) / 2;
|
|
79
|
+
points = [start, [mid, start[1]], [mid, end[1]], end];
|
|
80
|
+
label = [mid, (start[1] + end[1]) / 2];
|
|
81
|
+
} else if (a.row === b.row) {
|
|
82
|
+
start = ports(a, "top");
|
|
83
|
+
end = ports(b, "top");
|
|
84
|
+
const y = Math.min(a.y, b.y) - 88 - lane * 28;
|
|
85
|
+
points = [start, [start[0], y], [end[0], y], end];
|
|
86
|
+
label = [(start[0] + end[0]) / 2, y];
|
|
87
|
+
} else if (b.row === a.row + 1) {
|
|
88
|
+
start = ports(a, "bottom");
|
|
89
|
+
end = ports(b, "top");
|
|
90
|
+
const y = Math.min(b.y - 45, a.y + a.height + 65 + lane * 24);
|
|
91
|
+
points = [start, [start[0], y], [end[0], y], end];
|
|
92
|
+
label = [(start[0] + end[0]) / 2, y];
|
|
93
|
+
} else {
|
|
94
|
+
const left = b.col < a.col;
|
|
95
|
+
start = ports(a, left ? "left" : "right");
|
|
96
|
+
end = ports(b, "top");
|
|
97
|
+
const x = left ? a.x - 85 : a.x + a.width + 85,
|
|
98
|
+
y = b.y - 88 - lane * 28;
|
|
99
|
+
points = [start, [x, start[1]], [x, y], [end[0], y], end];
|
|
100
|
+
label = [x, (start[1] + y) / 2];
|
|
101
|
+
}
|
|
102
|
+
return { d: points.map((p, i) => `${i ? "L" : "M"} ${p[0]} ${p[1]}`).join(" "), label };
|
|
103
|
+
}
|
package/src/template.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export const escapeHTML = (value) =>
|
|
2
|
+
String(value ?? "")
|
|
3
|
+
.replaceAll("&", "&")
|
|
4
|
+
.replaceAll("<", "<")
|
|
5
|
+
.replaceAll(">", ">")
|
|
6
|
+
.replaceAll('"', """);
|
|
7
|
+
export function template(config) {
|
|
8
|
+
return `<div class="flow-page" data-kv-design-system>
|
|
9
|
+
<header class="map-header">
|
|
10
|
+
<button id="map-menu" class="icon-button" aria-label="Choose a flow" aria-expanded="false" aria-controls="map-sidebar"></button>
|
|
11
|
+
<div class="map-brand"><span class="brand-mark">K</span><span><strong>${escapeHTML(config.title)}</strong><small>KetAtlas · Workflow atlas</small></span></div>
|
|
12
|
+
<span id="map-total" class="map-total"></span>
|
|
13
|
+
<div class="map-header-actions"><button id="map-help" class="icon-button" aria-label="Help"></button></div>
|
|
14
|
+
</header>
|
|
15
|
+
<div class="map-layout">
|
|
16
|
+
<aside id="map-sidebar" class="map-sidebar">
|
|
17
|
+
<div class="map-sidebar-heading"><h1>Workflows</h1><p>Choose a flow. Follow the journey.</p></div>
|
|
18
|
+
<div class="review-search"><input id="flow-search" type="search" aria-label="Search flows and screens" placeholder="Search flows, screens, IDs…"></div>
|
|
19
|
+
<nav id="flow-list" aria-label="Workflows"></nav>
|
|
20
|
+
<div class="map-sidebar-footer"><span class="map-live-dot"></span><p>Real HTML, connected.<br>Double-click a screen to try it.</p></div>
|
|
21
|
+
</aside>
|
|
22
|
+
<main id="map-viewport" class="map-viewport" tabindex="0" role="region" aria-label="Interactive workflow map" aria-describedby="map-instructions">
|
|
23
|
+
<div id="map-world" class="map-world"><svg id="map-edges" class="map-edges" aria-hidden="true"></svg><div id="map-lanes"></div><div id="map-nodes"></div></div>
|
|
24
|
+
<div class="map-context"><div class="map-context-text"><span id="flow-position"></span><h2 id="current-flow-title"></h2><p id="current-flow-description"></p></div><button id="flow-start" data-ui="action" data-variant="secondary">Back to start</button></div>
|
|
25
|
+
<div class="map-legend"><span><i class="main"></i>Primary</span><span><i class="branch"></i>Conditional</span><span><i class="recovery"></i>Recovery</span></div>
|
|
26
|
+
<div id="node-details" class="node-details" hidden></div>
|
|
27
|
+
<div class="map-bottom"><p id="map-instructions">Drag to explore · Scroll to pan · Ctrl/⌘ + scroll to zoom</p><div class="map-zoom">
|
|
28
|
+
<button id="zoom-out" class="icon-button" aria-label="Zoom out"></button><button id="zoom-reset" title="Reset to 100%">100%</button><button id="zoom-in" class="icon-button" aria-label="Zoom in"></button><span class="map-control-divider"></span><button id="zoom-fit" class="icon-button" aria-label="Fit current flow" title="Fit flow · F"></button>
|
|
29
|
+
</div></div>
|
|
30
|
+
<div class="map-minimap"><span>Current flow</span><svg id="minimap" viewBox="0 0 168 114" role="img" aria-label="Current viewport position"></svg><button id="minimap-fit">Fit entire flow</button></div>
|
|
31
|
+
</main>
|
|
32
|
+
</div>
|
|
33
|
+
<dialog id="screen-dialog" class="screen-dialog" aria-labelledby="dialog-title"><header><div><small id="dialog-screen-id"></small><h2 id="dialog-title"></h2></div><button id="close-screen" class="icon-button" aria-label="Close preview"></button></header><div id="dialog-preview"></div><footer><p>Interactive preview</p><a id="dialog-open" data-ui="action" data-variant="secondary" target="_blank" rel="noopener noreferrer">Open in new tab</a></footer></dialog>
|
|
34
|
+
<dialog id="help-dialog" class="help-dialog" aria-labelledby="help-title"><h2 id="help-title">Explore the whole journey</h2><ul>
|
|
35
|
+
<li>Drag the canvas or a screen card to move around.</li><li>Use + / −, Ctrl/⌘ + scroll, or pinch to zoom.</li><li>Choose a workflow in the sidebar. Fit the flow to see every branch.</li><li>Click a node to follow its outgoing steps. Double-click a screen to interact.</li><li>Use arrow keys to pan, F to fit, 0 to return to the start, and Escape to close.</li>
|
|
36
|
+
</ul><p>Arrows describe the authored flow. Embedded screens keep their own behavior.</p><button id="close-help" data-ui="action" data-variant="primary">Got it</button></dialog>
|
|
37
|
+
</div>`;
|
|
38
|
+
}
|