@unofficialbox/box-open-elements 0.5.0 → 0.7.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/README.md +27 -73
- package/dist/components/collections/table.d.ts +113 -3
- package/dist/components/collections/table.js +540 -37
- package/dist/components/feedback/due-badge.d.ts +38 -0
- package/dist/components/feedback/due-badge.js +168 -0
- package/dist/components/feedback/due-types.d.ts +32 -0
- package/dist/components/feedback/due-types.js +91 -0
- package/dist/components/feedback/progress-steps.d.ts +51 -5
- package/dist/components/feedback/progress-steps.js +200 -22
- package/dist/components/feedback/stage-path.d.ts +44 -0
- package/dist/components/feedback/stage-path.js +235 -0
- package/dist/components/forms/select.d.ts +21 -0
- package/dist/components/forms/select.js +100 -3
- package/dist/components/forms/text-field.d.ts +21 -0
- package/dist/components/forms/text-field.js +100 -1
- package/dist/components/layout/nav-sidebar.d.ts +14 -0
- package/dist/components/layout/nav-sidebar.js +49 -0
- package/dist/components/layout/split-view.d.ts +14 -0
- package/dist/components/layout/split-view.js +80 -1
- package/dist/components/overlays/command-palette.d.ts +62 -0
- package/dist/components/overlays/command-palette.js +537 -0
- package/dist/components/overlays/command-types.d.ts +90 -0
- package/dist/components/overlays/command-types.js +259 -0
- package/dist/components/overlays/drawer.d.ts +18 -0
- package/dist/components/overlays/drawer.js +135 -9
- package/dist/components/overlays/shortcuts-overlay.d.ts +48 -0
- package/dist/components/overlays/shortcuts-overlay.js +385 -0
- package/dist/components/overlays/tooltip.js +1 -1
- package/dist/core/index.d.ts +4 -0
- package/dist/core/index.js +2 -0
- package/dist/core/offset-window.d.ts +70 -0
- package/dist/core/offset-window.js +134 -0
- package/dist/core/virtualize.d.ts +71 -0
- package/dist/core/virtualize.js +67 -0
- package/dist/element-maps.d.ts +292 -0
- package/dist/element-maps.js +1 -0
- package/dist/entries/activity-density.d.ts +1 -0
- package/dist/entries/activity-density.js +1 -0
- package/dist/entries/agent-chat.d.ts +1 -0
- package/dist/entries/agent-chat.js +1 -0
- package/dist/entries/audit-log.d.ts +1 -0
- package/dist/entries/audit-log.js +1 -0
- package/dist/entries/command-palette.d.ts +1 -0
- package/dist/entries/command-palette.js +1 -0
- package/dist/entries/compare-view.d.ts +1 -0
- package/dist/entries/compare-view.js +1 -0
- package/dist/entries/diff-viewer.d.ts +1 -0
- package/dist/entries/diff-viewer.js +1 -0
- package/dist/entries/due-badge.d.ts +1 -0
- package/dist/entries/due-badge.js +1 -0
- package/dist/entries/form-wizard.d.ts +1 -0
- package/dist/entries/form-wizard.js +1 -0
- package/dist/entries/lineage-graph.d.ts +1 -0
- package/dist/entries/lineage-graph.js +1 -0
- package/dist/entries/notification-bell.d.ts +1 -0
- package/dist/entries/notification-bell.js +1 -0
- package/dist/entries/notification-inbox.d.ts +1 -0
- package/dist/entries/notification-inbox.js +1 -0
- package/dist/entries/provenance-strip.d.ts +1 -0
- package/dist/entries/provenance-strip.js +1 -0
- package/dist/entries/run-trace.d.ts +1 -0
- package/dist/entries/run-trace.js +1 -0
- package/dist/entries/shortcuts-overlay.d.ts +1 -0
- package/dist/entries/shortcuts-overlay.js +1 -0
- package/dist/entries/signature-ceremony.d.ts +1 -0
- package/dist/entries/signature-ceremony.js +1 -0
- package/dist/entries/stage-path.d.ts +1 -0
- package/dist/entries/stage-path.js +1 -0
- package/dist/entries/timeline.d.ts +1 -0
- package/dist/entries/timeline.js +1 -0
- package/dist/entries/version-graph.d.ts +1 -0
- package/dist/entries/version-graph.js +1 -0
- package/dist/entries/version-list.d.ts +1 -0
- package/dist/entries/version-list.js +1 -0
- package/dist/entries/wizard-summary.d.ts +1 -0
- package/dist/entries/wizard-summary.js +1 -0
- package/dist/entries/work-queue.d.ts +1 -0
- package/dist/entries/work-queue.js +1 -0
- package/dist/entries/workload-board.d.ts +1 -0
- package/dist/entries/workload-board.js +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +21 -0
- package/dist/patterns/agent-chat/agent-chat.d.ts +75 -0
- package/dist/patterns/agent-chat/agent-chat.js +820 -0
- package/dist/patterns/agent-chat/controller.d.ts +27 -0
- package/dist/patterns/agent-chat/controller.js +182 -0
- package/dist/patterns/agent-chat/index.d.ts +3 -0
- package/dist/patterns/agent-chat/index.js +6 -0
- package/dist/patterns/agent-chat/types.d.ts +109 -0
- package/dist/patterns/agent-chat/types.js +1 -0
- package/dist/patterns/audit/activity-density.d.ts +68 -0
- package/dist/patterns/audit/activity-density.js +428 -0
- package/dist/patterns/audit/audit-log.d.ts +182 -0
- package/dist/patterns/audit/audit-log.js +1082 -0
- package/dist/patterns/audit/index.d.ts +3 -0
- package/dist/patterns/audit/index.js +7 -0
- package/dist/patterns/audit/shared.d.ts +10 -0
- package/dist/patterns/audit/shared.js +50 -0
- package/dist/patterns/audit/types.d.ts +143 -0
- package/dist/patterns/audit/types.js +327 -0
- package/dist/patterns/audit/window.d.ts +116 -0
- package/dist/patterns/audit/window.js +132 -0
- package/dist/patterns/content-explorer/actions/controller.js +12 -2
- package/dist/patterns/content-explorer/box-transport.js +117 -25
- package/dist/patterns/content-explorer/content-explorer.d.ts +13 -1
- package/dist/patterns/content-explorer/content-explorer.js +36 -3
- package/dist/patterns/content-explorer/contracts.d.ts +24 -1
- package/dist/patterns/content-explorer/contracts.js +68 -11
- package/dist/patterns/content-explorer/controller.d.ts +16 -1
- package/dist/patterns/content-explorer/controller.js +130 -4
- package/dist/patterns/content-explorer/navigation/controller.js +11 -9
- package/dist/patterns/content-explorer/types.d.ts +58 -1
- package/dist/patterns/content-picker/content-picker.d.ts +71 -0
- package/dist/patterns/content-picker/content-picker.js +747 -0
- package/dist/patterns/content-picker/controller.d.ts +38 -0
- package/dist/patterns/content-picker/controller.js +141 -0
- package/dist/patterns/content-picker/index.d.ts +3 -0
- package/dist/patterns/content-picker/index.js +6 -0
- package/dist/patterns/content-picker/types.d.ts +52 -0
- package/dist/patterns/content-picker/types.js +26 -0
- package/dist/patterns/content-sidebar/content-sidebar.d.ts +43 -0
- package/dist/patterns/content-sidebar/content-sidebar.js +293 -0
- package/dist/patterns/content-sidebar/index.d.ts +2 -0
- package/dist/patterns/content-sidebar/index.js +4 -0
- package/dist/patterns/content-sidebar/types.d.ts +18 -0
- package/dist/patterns/content-sidebar/types.js +20 -0
- package/dist/patterns/content-uploader/box-transport.d.ts +14 -0
- package/dist/patterns/content-uploader/box-transport.js +54 -0
- package/dist/patterns/content-uploader/content-uploader.d.ts +62 -0
- package/dist/patterns/content-uploader/content-uploader.js +559 -0
- package/dist/patterns/content-uploader/controller.d.ts +39 -0
- package/dist/patterns/content-uploader/controller.js +227 -0
- package/dist/patterns/content-uploader/index.d.ts +4 -0
- package/dist/patterns/content-uploader/index.js +8 -0
- package/dist/patterns/content-uploader/types.d.ts +120 -0
- package/dist/patterns/content-uploader/types.js +38 -0
- package/dist/patterns/diff/compare-view.d.ts +47 -0
- package/dist/patterns/diff/compare-view.js +276 -0
- package/dist/patterns/diff/diff-viewer.d.ts +42 -0
- package/dist/patterns/diff/diff-viewer.js +434 -0
- package/dist/patterns/diff/engine.d.ts +22 -0
- package/dist/patterns/diff/engine.js +276 -0
- package/dist/patterns/diff/index.d.ts +4 -0
- package/dist/patterns/diff/index.js +8 -0
- package/dist/patterns/diff/types.d.ts +69 -0
- package/dist/patterns/diff/types.js +24 -0
- package/dist/patterns/form-wizard/controller.d.ts +39 -0
- package/dist/patterns/form-wizard/controller.js +157 -0
- package/dist/patterns/form-wizard/form-wizard.d.ts +63 -0
- package/dist/patterns/form-wizard/form-wizard.js +468 -0
- package/dist/patterns/form-wizard/index.d.ts +4 -0
- package/dist/patterns/form-wizard/index.js +8 -0
- package/dist/patterns/form-wizard/types.d.ts +109 -0
- package/dist/patterns/form-wizard/types.js +83 -0
- package/dist/patterns/form-wizard/wizard-summary.d.ts +62 -0
- package/dist/patterns/form-wizard/wizard-summary.js +324 -0
- package/dist/patterns/internal/safe-href.d.ts +17 -0
- package/dist/patterns/internal/safe-href.js +17 -0
- package/dist/patterns/lineage/index.d.ts +3 -0
- package/dist/patterns/lineage/index.js +6 -0
- package/dist/patterns/lineage/lineage-graph.d.ts +30 -0
- package/dist/patterns/lineage/lineage-graph.js +434 -0
- package/dist/patterns/lineage/provenance-strip.d.ts +22 -0
- package/dist/patterns/lineage/provenance-strip.js +194 -0
- package/dist/patterns/lineage/types.d.ts +53 -0
- package/dist/patterns/lineage/types.js +49 -0
- package/dist/patterns/notifications/index.d.ts +3 -0
- package/dist/patterns/notifications/index.js +6 -0
- package/dist/patterns/notifications/notification-bell.d.ts +38 -0
- package/dist/patterns/notifications/notification-bell.js +198 -0
- package/dist/patterns/notifications/notification-inbox.d.ts +46 -0
- package/dist/patterns/notifications/notification-inbox.js +537 -0
- package/dist/patterns/notifications/types.d.ts +61 -0
- package/dist/patterns/notifications/types.js +108 -0
- package/dist/patterns/preview/annotation-inspector.d.ts +25 -6
- package/dist/patterns/preview/annotation-inspector.js +239 -94
- package/dist/patterns/preview/annotation-thread.d.ts +25 -5
- package/dist/patterns/preview/annotation-thread.js +233 -82
- package/dist/patterns/preview/annotation-toolbar.d.ts +20 -6
- package/dist/patterns/preview/annotation-toolbar.js +162 -96
- package/dist/patterns/preview/content-preview-adapter.d.ts +10 -1
- package/dist/patterns/preview/content-preview-adapter.js +26 -1
- package/dist/patterns/preview/preview-element.d.ts +15 -2
- package/dist/patterns/preview/preview-element.js +223 -52
- package/dist/patterns/preview/provider-adapter.d.ts +47 -0
- package/dist/patterns/preview/provider-adapter.js +17 -0
- package/dist/patterns/run/index.d.ts +2 -0
- package/dist/patterns/run/index.js +4 -0
- package/dist/patterns/run/run-trace.d.ts +40 -0
- package/dist/patterns/run/run-trace.js +441 -0
- package/dist/patterns/run/types.d.ts +66 -0
- package/dist/patterns/run/types.js +152 -0
- package/dist/patterns/signature/index.d.ts +2 -0
- package/dist/patterns/signature/index.js +4 -0
- package/dist/patterns/signature/signature-ceremony.d.ts +33 -0
- package/dist/patterns/signature/signature-ceremony.js +317 -0
- package/dist/patterns/signature/types.d.ts +68 -0
- package/dist/patterns/signature/types.js +89 -0
- package/dist/patterns/timeline/index.d.ts +2 -0
- package/dist/patterns/timeline/index.js +4 -0
- package/dist/patterns/timeline/timeline.d.ts +27 -0
- package/dist/patterns/timeline/timeline.js +430 -0
- package/dist/patterns/timeline/types.d.ts +36 -0
- package/dist/patterns/timeline/types.js +27 -0
- package/dist/patterns/versions/graph-layout.d.ts +43 -0
- package/dist/patterns/versions/graph-layout.js +159 -0
- package/dist/patterns/versions/index.d.ts +4 -0
- package/dist/patterns/versions/index.js +8 -0
- package/dist/patterns/versions/shared.d.ts +4 -0
- package/dist/patterns/versions/shared.js +13 -0
- package/dist/patterns/versions/types.d.ts +37 -0
- package/dist/patterns/versions/types.js +39 -0
- package/dist/patterns/versions/version-graph.d.ts +36 -0
- package/dist/patterns/versions/version-graph.js +438 -0
- package/dist/patterns/versions/version-list.d.ts +39 -0
- package/dist/patterns/versions/version-list.js +407 -0
- package/dist/patterns/work-queue/controller.d.ts +26 -0
- package/dist/patterns/work-queue/controller.js +150 -0
- package/dist/patterns/work-queue/index.d.ts +4 -0
- package/dist/patterns/work-queue/index.js +7 -0
- package/dist/patterns/work-queue/types.d.ts +128 -0
- package/dist/patterns/work-queue/types.js +45 -0
- package/dist/patterns/work-queue/work-queue.d.ts +48 -0
- package/dist/patterns/work-queue/work-queue.js +534 -0
- package/dist/patterns/work-queue/workload-board.d.ts +53 -0
- package/dist/patterns/work-queue/workload-board.js +608 -0
- package/package.json +89 -5
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
import { isTimelineEventRecord, resolveTimelineTone } from "./types.js";
|
|
2
|
+
import { formatItemDate } from "../content-explorer/adapters/item-summary.js";
|
|
3
|
+
import { isSafeHref } from "../internal/safe-href.js";
|
|
4
|
+
import { BaseElement } from "../../core/index.js";
|
|
5
|
+
import { boeMotionDuration, boeMotionEasing } from "../../foundations/motion/index.js";
|
|
6
|
+
import { boePanel, boeRadius } from "../../foundations/geometry/index.js";
|
|
7
|
+
const DEFAULT_TAG_NAME = "box-timeline";
|
|
8
|
+
const escapeHtml = (value) => value
|
|
9
|
+
.replaceAll("&", "&")
|
|
10
|
+
.replaceAll("<", "<")
|
|
11
|
+
.replaceAll(">", ">")
|
|
12
|
+
.replaceAll('"', """)
|
|
13
|
+
.replaceAll("'", "'");
|
|
14
|
+
const toneColor = (tone) => {
|
|
15
|
+
switch (tone) {
|
|
16
|
+
case "brand":
|
|
17
|
+
return "var(--boe-token-surface-surface-brand, #0061d5)";
|
|
18
|
+
case "success":
|
|
19
|
+
return "var(--boe-token-surface-status-surface-success, #26a27b)";
|
|
20
|
+
case "warning":
|
|
21
|
+
return "var(--boe-token-surface-status-surface-warning, #f5b31b)";
|
|
22
|
+
case "error":
|
|
23
|
+
return "var(--boe-token-surface-status-surface-error, #ed3757)";
|
|
24
|
+
default:
|
|
25
|
+
return "var(--boe-token-text-text-secondary, #6f6f6f)";
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
const elementStyles = `
|
|
29
|
+
/* Author display rules on parts would otherwise defeat the UA's
|
|
30
|
+
[hidden] rule — state toggling relies on the hidden attribute. */
|
|
31
|
+
[hidden] {
|
|
32
|
+
display: none !important;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
:host {
|
|
36
|
+
display: block;
|
|
37
|
+
color: inherit;
|
|
38
|
+
font: inherit;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
[part="panel"] {
|
|
42
|
+
display: grid;
|
|
43
|
+
gap: ${boePanel.gap};
|
|
44
|
+
padding: ${boePanel.padding};
|
|
45
|
+
border: 1px solid color-mix(in srgb, var(--boe-token-stroke-stroke, #e8e8e8) 82%, transparent);
|
|
46
|
+
border-radius: ${boePanel.radius};
|
|
47
|
+
background: ${boePanel.background};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
[part="title"] {
|
|
51
|
+
margin: 0;
|
|
52
|
+
font: inherit;
|
|
53
|
+
font-size: 1.1rem;
|
|
54
|
+
font-weight: 700;
|
|
55
|
+
color: var(--boe-token-text-text, #1f1e1b);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
[part="events"] {
|
|
59
|
+
list-style: none;
|
|
60
|
+
margin: 0;
|
|
61
|
+
padding: 0;
|
|
62
|
+
display: grid;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
[part="event"] {
|
|
66
|
+
position: relative;
|
|
67
|
+
display: grid;
|
|
68
|
+
grid-template-columns: auto 1fr;
|
|
69
|
+
gap: 0.65rem;
|
|
70
|
+
padding: 0.55rem 0 0.55rem 0.15rem;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* The connecting spine between markers. */
|
|
74
|
+
[part="event"]:not(:last-child)::after {
|
|
75
|
+
content: "";
|
|
76
|
+
position: absolute;
|
|
77
|
+
left: calc(0.15rem + 0.55rem - 1px);
|
|
78
|
+
top: 1.9rem;
|
|
79
|
+
bottom: -0.35rem;
|
|
80
|
+
width: 2px;
|
|
81
|
+
background: color-mix(in srgb, var(--boe-token-stroke-stroke, #e8e8e8) 72%, transparent);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
[part="marker"] {
|
|
85
|
+
inline-size: 1.1rem;
|
|
86
|
+
block-size: 1.1rem;
|
|
87
|
+
margin-top: 0.3rem;
|
|
88
|
+
border-radius: 999px;
|
|
89
|
+
border: 2px solid var(--timeline-tone, var(--boe-token-text-text-secondary, #6f6f6f));
|
|
90
|
+
background: color-mix(in srgb, var(--timeline-tone, var(--boe-token-text-text-secondary, #6f6f6f)) 14%, var(--boe-token-surface-surface, #ffffff) 86%);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
[part="event-body"] {
|
|
94
|
+
display: grid;
|
|
95
|
+
gap: 0.25rem;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
[part="event-topline"] {
|
|
99
|
+
display: flex;
|
|
100
|
+
flex-wrap: wrap;
|
|
101
|
+
align-items: center;
|
|
102
|
+
gap: 0.4rem;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
[part="actor"] {
|
|
106
|
+
font-weight: 700;
|
|
107
|
+
color: var(--boe-token-text-text, #1f1e1b);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
[part="action"] {
|
|
111
|
+
color: var(--boe-token-text-text, #1f1e1b);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
[part="badge"] {
|
|
115
|
+
display: inline-flex;
|
|
116
|
+
padding: 0.16rem 0.45rem;
|
|
117
|
+
border-radius: 999px;
|
|
118
|
+
background: color-mix(in srgb, var(--timeline-tone, var(--boe-token-surface-surface-brand, #0061d5)) 12%, transparent);
|
|
119
|
+
color: color-mix(in srgb, var(--timeline-tone, var(--boe-token-surface-surface-brand, #0061d5)) 74%, black 26%);
|
|
120
|
+
font-size: 0.75rem;
|
|
121
|
+
font-weight: 600;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
[part="timestamp"] {
|
|
125
|
+
margin-left: auto;
|
|
126
|
+
color: var(--boe-token-text-text-secondary, #6f6f6f);
|
|
127
|
+
font-size: 0.78rem;
|
|
128
|
+
white-space: nowrap;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
[part="summary"] {
|
|
132
|
+
color: var(--boe-token-text-text-secondary, #6f6f6f);
|
|
133
|
+
line-height: 1.5;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
[part="evidence"] {
|
|
137
|
+
display: flex;
|
|
138
|
+
flex-wrap: wrap;
|
|
139
|
+
gap: 0.4rem;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
[part="evidence-link"] {
|
|
143
|
+
appearance: none;
|
|
144
|
+
display: inline-flex;
|
|
145
|
+
align-items: center;
|
|
146
|
+
padding: 0.22rem 0.55rem;
|
|
147
|
+
border: 1px solid color-mix(in srgb, var(--boe-token-surface-surface-brand, #0061d5) 30%, transparent);
|
|
148
|
+
border-radius: 999px;
|
|
149
|
+
background: color-mix(in srgb, var(--boe-token-surface-surface-brand, #0061d5) 6%, transparent);
|
|
150
|
+
color: var(--boe-token-surface-surface-brand, #0061d5);
|
|
151
|
+
font: inherit;
|
|
152
|
+
font-size: 0.78rem;
|
|
153
|
+
font-weight: 600;
|
|
154
|
+
text-decoration: none;
|
|
155
|
+
cursor: pointer;
|
|
156
|
+
transition: background ${boeMotionDuration.interactive} ${boeMotionEasing.standard};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
[part="evidence-link"]:hover {
|
|
160
|
+
background: color-mix(in srgb, var(--boe-token-surface-surface-brand, #0061d5) 14%, transparent);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
[part="evidence-link"]:focus-visible {
|
|
164
|
+
outline: none;
|
|
165
|
+
box-shadow: 0 0 0 3px color-mix(in srgb, var(--boe-token-surface-surface-brand, #0061d5) 18%, transparent);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
[part="correlation"] {
|
|
169
|
+
font-family: ui-monospace, monospace;
|
|
170
|
+
font-size: 0.72rem;
|
|
171
|
+
color: color-mix(in srgb, var(--boe-token-text-text-secondary, #6f6f6f) 84%, transparent);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
[part="empty"] {
|
|
175
|
+
padding: ${boePanel.padding};
|
|
176
|
+
border-radius: ${boeRadius.large};
|
|
177
|
+
border: 1px dashed color-mix(in srgb, var(--boe-token-stroke-stroke, #e8e8e8) 70%, transparent);
|
|
178
|
+
color: var(--boe-token-text-text-secondary, #6f6f6f);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
[part="load-more-region"] {
|
|
182
|
+
display: flex;
|
|
183
|
+
justify-content: center;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
[part="load-more"] {
|
|
187
|
+
appearance: none;
|
|
188
|
+
font: inherit;
|
|
189
|
+
font-size: 0.875rem;
|
|
190
|
+
font-weight: 600;
|
|
191
|
+
padding: 0.4rem 0.7rem;
|
|
192
|
+
border-radius: ${boeRadius.control};
|
|
193
|
+
border: 1px solid var(--boe-token-stroke-stroke, #e8e8e8);
|
|
194
|
+
background: var(--boe-token-surface-surface, #ffffff);
|
|
195
|
+
color: var(--boe-token-text-text, #222222);
|
|
196
|
+
cursor: pointer;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
[part="load-more"]:hover {
|
|
200
|
+
background: var(--boe-token-surface-surface-hover, #f4f4f4);
|
|
201
|
+
border-color: var(--boe-token-stroke-stroke-hover, #bcbcbc);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
[part="composer"] {
|
|
205
|
+
display: grid;
|
|
206
|
+
gap: 0.5rem;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
[part="composer-label"] {
|
|
210
|
+
font-size: 0.74rem;
|
|
211
|
+
font-weight: 700;
|
|
212
|
+
letter-spacing: 0.08em;
|
|
213
|
+
text-transform: uppercase;
|
|
214
|
+
color: var(--boe-token-text-text-secondary, #6f6f6f);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
[part="composer-input"] {
|
|
218
|
+
min-height: 3.4rem;
|
|
219
|
+
padding: 0.55rem;
|
|
220
|
+
border: 1px solid color-mix(in srgb, var(--boe-token-stroke-stroke, #e8e8e8) 60%, transparent);
|
|
221
|
+
border-radius: ${boePanel.radius};
|
|
222
|
+
background: var(--boe-token-surface-surface, #ffffff);
|
|
223
|
+
color: inherit;
|
|
224
|
+
font: inherit;
|
|
225
|
+
line-height: 1.5;
|
|
226
|
+
resize: vertical;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
[part="composer-submit"] {
|
|
230
|
+
appearance: none;
|
|
231
|
+
justify-self: start;
|
|
232
|
+
min-height: 2rem;
|
|
233
|
+
padding: 0.4rem 0.7rem;
|
|
234
|
+
border: 1px solid transparent;
|
|
235
|
+
border-radius: 999px;
|
|
236
|
+
background: var(--boe-token-surface-surface-brand, #0061d5);
|
|
237
|
+
color: var(--boe-token-text-text-on-brand, #ffffff);
|
|
238
|
+
font: inherit;
|
|
239
|
+
cursor: pointer;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
[part="composer-submit"]:hover {
|
|
243
|
+
background: color-mix(in srgb, var(--boe-token-surface-surface-brand, #0061d5) 88%, black 12%);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
[part="composer-submit"]:focus-visible,
|
|
247
|
+
[part="composer-input"]:focus-visible,
|
|
248
|
+
[part="load-more"]:focus-visible {
|
|
249
|
+
outline: none;
|
|
250
|
+
box-shadow: 0 0 0 3px color-mix(in srgb, var(--boe-token-surface-surface-brand, #0061d5) 18%, transparent);
|
|
251
|
+
}
|
|
252
|
+
`;
|
|
253
|
+
export class Timeline extends BaseElement {
|
|
254
|
+
static tagName = DEFAULT_TAG_NAME;
|
|
255
|
+
static get observedAttributes() {
|
|
256
|
+
return ["composable", "events", "has-more", "heading"];
|
|
257
|
+
}
|
|
258
|
+
titleEl;
|
|
259
|
+
eventsEl;
|
|
260
|
+
emptyEl;
|
|
261
|
+
loadMoreRegionEl;
|
|
262
|
+
composerEl;
|
|
263
|
+
composerInputEl;
|
|
264
|
+
eventsSignature = "";
|
|
265
|
+
get heading() {
|
|
266
|
+
return this.getAttribute("heading") ?? "Activity";
|
|
267
|
+
}
|
|
268
|
+
set heading(value) {
|
|
269
|
+
this.setAttribute("heading", value);
|
|
270
|
+
}
|
|
271
|
+
/** Append-only event list; JSON records validated before rendering. */
|
|
272
|
+
get events() {
|
|
273
|
+
const raw = this.getAttribute("events");
|
|
274
|
+
if (!raw) {
|
|
275
|
+
return [];
|
|
276
|
+
}
|
|
277
|
+
try {
|
|
278
|
+
const parsed = JSON.parse(raw);
|
|
279
|
+
return Array.isArray(parsed) && parsed.every(isTimelineEventRecord) ? parsed : [];
|
|
280
|
+
}
|
|
281
|
+
catch {
|
|
282
|
+
return [];
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
set events(value) {
|
|
286
|
+
if (value.length) {
|
|
287
|
+
this.setAttribute("events", JSON.stringify(value));
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
this.removeAttribute("events");
|
|
291
|
+
}
|
|
292
|
+
get composable() {
|
|
293
|
+
return this.hasAttribute("composable");
|
|
294
|
+
}
|
|
295
|
+
set composable(value) {
|
|
296
|
+
this.toggleAttribute("composable", value);
|
|
297
|
+
}
|
|
298
|
+
get hasMore() {
|
|
299
|
+
return this.hasAttribute("has-more");
|
|
300
|
+
}
|
|
301
|
+
set hasMore(value) {
|
|
302
|
+
this.toggleAttribute("has-more", value);
|
|
303
|
+
}
|
|
304
|
+
emitEntrySubmitted() {
|
|
305
|
+
const body = this.composerInputEl.value.trim();
|
|
306
|
+
if (!this.composable || !body) {
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
this.dispatchEvent(new CustomEvent("entry-submitted", {
|
|
310
|
+
bubbles: true,
|
|
311
|
+
composed: true,
|
|
312
|
+
detail: { body },
|
|
313
|
+
}));
|
|
314
|
+
this.composerInputEl.value = "";
|
|
315
|
+
}
|
|
316
|
+
rebuildEvents(events) {
|
|
317
|
+
this.eventsEl.innerHTML = events
|
|
318
|
+
.map(event => {
|
|
319
|
+
const tone = resolveTimelineTone(event.tone);
|
|
320
|
+
const evidence = (event.evidence ?? [])
|
|
321
|
+
.map(entry => {
|
|
322
|
+
const attrs = `part="evidence-link" data-event-id="${escapeHtml(event.id)}" data-evidence-id="${escapeHtml(entry.id)}"`;
|
|
323
|
+
return entry.href && isSafeHref(entry.href)
|
|
324
|
+
? `<a ${attrs} href="${escapeHtml(entry.href)}">${escapeHtml(entry.label)}</a>`
|
|
325
|
+
: `<button type="button" ${attrs}>${escapeHtml(entry.label)}</button>`;
|
|
326
|
+
})
|
|
327
|
+
.join("");
|
|
328
|
+
const timestamp = event.timestamp ? formatItemDate(event.timestamp) : "";
|
|
329
|
+
return `
|
|
330
|
+
<li part="event" data-event-id="${escapeHtml(event.id)}" style="--timeline-tone:${toneColor(tone)};">
|
|
331
|
+
<span part="marker" aria-hidden="true"></span>
|
|
332
|
+
<div part="event-body">
|
|
333
|
+
<div part="event-topline">
|
|
334
|
+
${event.actor ? `<span part="actor">${escapeHtml(event.actor.name)}</span>` : ""}
|
|
335
|
+
<span part="action">${escapeHtml(event.action)}</span>
|
|
336
|
+
${event.badge ? `<span part="badge">${escapeHtml(event.badge)}</span>` : ""}
|
|
337
|
+
${timestamp ? `<time part="timestamp" datetime="${escapeHtml(event.timestamp ?? "")}">${escapeHtml(timestamp)}</time>` : ""}
|
|
338
|
+
</div>
|
|
339
|
+
${event.summary ? `<p part="summary">${escapeHtml(event.summary)}</p>` : ""}
|
|
340
|
+
${evidence ? `<div part="evidence">${evidence}</div>` : ""}
|
|
341
|
+
${event.correlationId ? `<span part="correlation">${escapeHtml(event.correlationId)}</span>` : ""}
|
|
342
|
+
</div>
|
|
343
|
+
</li>
|
|
344
|
+
`;
|
|
345
|
+
})
|
|
346
|
+
.join("");
|
|
347
|
+
}
|
|
348
|
+
renderTemplate() {
|
|
349
|
+
if (!this.shadowRoot) {
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
this.shadowRoot.innerHTML = `
|
|
353
|
+
<style>${elementStyles}</style>
|
|
354
|
+
<section part="panel">
|
|
355
|
+
<h2 part="title"></h2>
|
|
356
|
+
<ol part="events" role="list" reversed hidden></ol>
|
|
357
|
+
<div part="empty" hidden>No activity yet.</div>
|
|
358
|
+
<div part="load-more-region" hidden>
|
|
359
|
+
<button type="button" part="load-more">Load earlier activity</button>
|
|
360
|
+
</div>
|
|
361
|
+
<div part="composer" hidden>
|
|
362
|
+
<label part="composer-label" for="timeline-composer-input">Add a comment</label>
|
|
363
|
+
<textarea part="composer-input" id="timeline-composer-input" placeholder="Add a comment"></textarea>
|
|
364
|
+
<button type="button" part="composer-submit">Comment</button>
|
|
365
|
+
</div>
|
|
366
|
+
</section>
|
|
367
|
+
`;
|
|
368
|
+
this.titleEl = this.shadowRoot.querySelector('[part="title"]');
|
|
369
|
+
this.eventsEl = this.shadowRoot.querySelector('[part="events"]');
|
|
370
|
+
this.emptyEl = this.shadowRoot.querySelector('[part="empty"]');
|
|
371
|
+
this.loadMoreRegionEl = this.shadowRoot.querySelector('[part="load-more-region"]');
|
|
372
|
+
this.composerEl = this.shadowRoot.querySelector('[part="composer"]');
|
|
373
|
+
this.composerInputEl = this.shadowRoot.querySelector('[part="composer-input"]');
|
|
374
|
+
}
|
|
375
|
+
setupListeners() {
|
|
376
|
+
this.eventsEl.addEventListener("click", event => {
|
|
377
|
+
const link = event.target.closest('[part="evidence-link"]');
|
|
378
|
+
if (!link || !this.eventsEl.contains(link)) {
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
const eventId = link.getAttribute("data-event-id") ?? "";
|
|
382
|
+
const evidenceId = link.getAttribute("data-evidence-id") ?? "";
|
|
383
|
+
const source = this.events.find(entry => entry.id === eventId);
|
|
384
|
+
const evidence = source?.evidence?.find(entry => entry.id === evidenceId);
|
|
385
|
+
if (evidence) {
|
|
386
|
+
this.dispatchEvent(new CustomEvent("evidence-selected", {
|
|
387
|
+
bubbles: true,
|
|
388
|
+
composed: true,
|
|
389
|
+
detail: { event: source, evidence },
|
|
390
|
+
}));
|
|
391
|
+
}
|
|
392
|
+
});
|
|
393
|
+
this.loadMoreRegionEl.addEventListener("click", event => {
|
|
394
|
+
if (event.target.closest('[part="load-more"]')) {
|
|
395
|
+
this.dispatchEvent(new CustomEvent("load-more", { bubbles: true, composed: true }));
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
this.composerEl.addEventListener("click", event => {
|
|
399
|
+
if (event.target.closest('[part="composer-submit"]')) {
|
|
400
|
+
this.emitEntrySubmitted();
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
this.composerInputEl.addEventListener("keydown", event => {
|
|
404
|
+
if (event.key === "Enter" && (event.metaKey || event.ctrlKey)) {
|
|
405
|
+
event.preventDefault();
|
|
406
|
+
this.emitEntrySubmitted();
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
update() {
|
|
411
|
+
if (!this.eventsEl) {
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
this.titleEl.textContent = this.heading;
|
|
415
|
+
this.eventsEl.setAttribute("aria-label", this.heading);
|
|
416
|
+
const events = this.events;
|
|
417
|
+
this.eventsEl.hidden = events.length === 0;
|
|
418
|
+
this.emptyEl.hidden = events.length > 0;
|
|
419
|
+
const signature = this.getAttribute("events") ?? "";
|
|
420
|
+
if (signature !== this.eventsSignature) {
|
|
421
|
+
this.eventsSignature = signature;
|
|
422
|
+
this.rebuildEvents(events);
|
|
423
|
+
}
|
|
424
|
+
this.loadMoreRegionEl.hidden = !this.hasMore;
|
|
425
|
+
const composable = this.composable;
|
|
426
|
+
this.composerEl.hidden = !composable;
|
|
427
|
+
this.composerInputEl.disabled = !composable;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
Timeline.register();
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export type TimelineTone = "neutral" | "brand" | "success" | "warning" | "error";
|
|
2
|
+
/** A piece of supporting evidence attached to an event (document, record, citation). */
|
|
3
|
+
export interface TimelineEvidence {
|
|
4
|
+
id: string;
|
|
5
|
+
label: string;
|
|
6
|
+
/** Optional deep link; without it the shell still emits `evidence-selected`. */
|
|
7
|
+
href?: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* One append-only activity entry: who did what, when, with what supporting
|
|
11
|
+
* evidence. `correlationId` threads the event to the workflow call that
|
|
12
|
+
* produced it (audit requirement); `badge` carries a short structured
|
|
13
|
+
* outcome such as a policy-check result.
|
|
14
|
+
*/
|
|
15
|
+
export interface TimelineEvent {
|
|
16
|
+
id: string;
|
|
17
|
+
action: string;
|
|
18
|
+
actor?: {
|
|
19
|
+
name: string;
|
|
20
|
+
initials?: string;
|
|
21
|
+
};
|
|
22
|
+
/** Free-text detail under the action line. */
|
|
23
|
+
summary?: string;
|
|
24
|
+
/** ISO timestamp; rendered as-is-formatted, sorted by the host. */
|
|
25
|
+
timestamp?: string;
|
|
26
|
+
tone?: TimelineTone;
|
|
27
|
+
badge?: string;
|
|
28
|
+
correlationId?: string;
|
|
29
|
+
evidence?: TimelineEvidence[];
|
|
30
|
+
}
|
|
31
|
+
export interface TimelineEntrySubmittedDetail {
|
|
32
|
+
body: string;
|
|
33
|
+
}
|
|
34
|
+
export declare const resolveTimelineTone: (value: string | null | undefined) => TimelineTone;
|
|
35
|
+
/** Attribute payloads are author input — validate every record, not just the array. */
|
|
36
|
+
export declare const isTimelineEventRecord: (value: unknown) => value is TimelineEvent;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const TONES = new Set(["neutral", "brand", "success", "warning", "error"]);
|
|
2
|
+
export const resolveTimelineTone = (value) => value && TONES.has(value) ? value : "neutral";
|
|
3
|
+
/** Attribute payloads are author input — validate every record, not just the array. */
|
|
4
|
+
export const isTimelineEventRecord = (value) => {
|
|
5
|
+
if (typeof value !== "object" || value === null) {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
const event = value;
|
|
9
|
+
if (typeof event.id !== "string" || event.id.length === 0 || typeof event.action !== "string") {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
if (event.evidence !== undefined) {
|
|
13
|
+
if (!Array.isArray(event.evidence)) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
for (const entry of event.evidence) {
|
|
17
|
+
const evidence = entry;
|
|
18
|
+
if (typeof evidence !== "object" ||
|
|
19
|
+
evidence === null ||
|
|
20
|
+
typeof evidence.id !== "string" ||
|
|
21
|
+
typeof evidence.label !== "string") {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return true;
|
|
27
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { VersionNode } from "./types.js";
|
|
2
|
+
export interface VersionGraphPlacement {
|
|
3
|
+
id: string;
|
|
4
|
+
/** Horizontal column, 0 = trunk. */
|
|
5
|
+
lane: number;
|
|
6
|
+
/** Vertical position, 0 = newest. */
|
|
7
|
+
row: number;
|
|
8
|
+
}
|
|
9
|
+
export interface VersionGraphEdge {
|
|
10
|
+
/** Parent (older) node. */
|
|
11
|
+
fromId: string;
|
|
12
|
+
/** Child (newer) node. */
|
|
13
|
+
toId: string;
|
|
14
|
+
fromLane: number;
|
|
15
|
+
fromRow: number;
|
|
16
|
+
toLane: number;
|
|
17
|
+
toRow: number;
|
|
18
|
+
}
|
|
19
|
+
export interface VersionGraphLayout {
|
|
20
|
+
/** One placement per surviving node, in display order (newest first). */
|
|
21
|
+
placements: VersionGraphPlacement[];
|
|
22
|
+
edges: VersionGraphEdge[];
|
|
23
|
+
laneCount: number;
|
|
24
|
+
rowCount: number;
|
|
25
|
+
/** Degradations: duplicate ids, unknown parents, cycles. Never throws. */
|
|
26
|
+
warnings: string[];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Git-network layout as a pure function: topological order (host input order
|
|
30
|
+
* breaks ties), first child continues its parent's lane, later siblings
|
|
31
|
+
* branch to the lowest free lane, and a merge releases the lanes of the
|
|
32
|
+
* branches it closes so the next branch can reuse them. Malformed topology
|
|
33
|
+
* degrades instead of throwing — duplicate ids are dropped, unknown parents
|
|
34
|
+
* are ignored, and cycle members are appended in input order — with each
|
|
35
|
+
* degradation reported in `warnings`.
|
|
36
|
+
*/
|
|
37
|
+
export declare const computeVersionGraphLayout: (nodes: readonly VersionNode[]) => VersionGraphLayout;
|
|
38
|
+
/**
|
|
39
|
+
* The shared display order for both projections: topological, newest first.
|
|
40
|
+
* The list and the graph agree by construction because both derive their
|
|
41
|
+
* order from the same layout.
|
|
42
|
+
*/
|
|
43
|
+
export declare const orderVersionsForDisplay: (nodes: readonly VersionNode[]) => VersionNode[];
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git-network layout as a pure function: topological order (host input order
|
|
3
|
+
* breaks ties), first child continues its parent's lane, later siblings
|
|
4
|
+
* branch to the lowest free lane, and a merge releases the lanes of the
|
|
5
|
+
* branches it closes so the next branch can reuse them. Malformed topology
|
|
6
|
+
* degrades instead of throwing — duplicate ids are dropped, unknown parents
|
|
7
|
+
* are ignored, and cycle members are appended in input order — with each
|
|
8
|
+
* degradation reported in `warnings`.
|
|
9
|
+
*/
|
|
10
|
+
export const computeVersionGraphLayout = (nodes) => {
|
|
11
|
+
const warnings = [];
|
|
12
|
+
const byId = new Map();
|
|
13
|
+
const ordered = [];
|
|
14
|
+
for (const node of nodes) {
|
|
15
|
+
if (byId.has(node.id)) {
|
|
16
|
+
warnings.push(`Duplicate version id dropped: ${node.id}`);
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
byId.set(node.id, node);
|
|
20
|
+
ordered.push(node);
|
|
21
|
+
}
|
|
22
|
+
const parentsOf = new Map();
|
|
23
|
+
for (const node of ordered) {
|
|
24
|
+
const known = [];
|
|
25
|
+
const seen = new Set();
|
|
26
|
+
for (const parentId of node.parents ?? []) {
|
|
27
|
+
if (parentId === node.id) {
|
|
28
|
+
warnings.push(`Self parent ignored: ${node.id}`);
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (!byId.has(parentId)) {
|
|
32
|
+
warnings.push(`Unknown parent ignored: ${node.id} -> ${parentId}`);
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (seen.has(parentId)) {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
seen.add(parentId);
|
|
39
|
+
known.push(parentId);
|
|
40
|
+
}
|
|
41
|
+
parentsOf.set(node.id, known);
|
|
42
|
+
}
|
|
43
|
+
// Kahn's topological sort, oldest first; ready nodes resolve in input order.
|
|
44
|
+
const inputIndex = new Map(ordered.map((node, index) => [node.id, index]));
|
|
45
|
+
const remainingParents = new Map();
|
|
46
|
+
const childrenOf = new Map();
|
|
47
|
+
for (const node of ordered) {
|
|
48
|
+
const parents = parentsOf.get(node.id);
|
|
49
|
+
remainingParents.set(node.id, parents.length);
|
|
50
|
+
for (const parentId of parents) {
|
|
51
|
+
const children = childrenOf.get(parentId) ?? [];
|
|
52
|
+
children.push(node.id);
|
|
53
|
+
childrenOf.set(parentId, children);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const ready = ordered
|
|
57
|
+
.filter(node => remainingParents.get(node.id) === 0)
|
|
58
|
+
.map(node => node.id);
|
|
59
|
+
const topo = [];
|
|
60
|
+
while (ready.length > 0) {
|
|
61
|
+
const id = ready.shift();
|
|
62
|
+
topo.push(id);
|
|
63
|
+
for (const childId of childrenOf.get(id) ?? []) {
|
|
64
|
+
const left = remainingParents.get(childId) - 1;
|
|
65
|
+
remainingParents.set(childId, left);
|
|
66
|
+
if (left === 0) {
|
|
67
|
+
const at = ready.findIndex(other => inputIndex.get(other) > inputIndex.get(childId));
|
|
68
|
+
if (at === -1) {
|
|
69
|
+
ready.push(childId);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
ready.splice(at, 0, childId);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (topo.length < ordered.length) {
|
|
78
|
+
const placed = new Set(topo);
|
|
79
|
+
for (const node of ordered) {
|
|
80
|
+
if (!placed.has(node.id)) {
|
|
81
|
+
warnings.push(`Cycle detected; appended in input order: ${node.id}`);
|
|
82
|
+
topo.push(node.id);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// Lane allocation in topological (oldest-first) order.
|
|
87
|
+
const laneOf = new Map();
|
|
88
|
+
const laneTips = new Map();
|
|
89
|
+
const freeLanes = [];
|
|
90
|
+
let laneCount = 0;
|
|
91
|
+
const takeLane = () => {
|
|
92
|
+
if (freeLanes.length > 0) {
|
|
93
|
+
freeLanes.sort((a, b) => a - b);
|
|
94
|
+
return freeLanes.shift();
|
|
95
|
+
}
|
|
96
|
+
laneCount += 1;
|
|
97
|
+
return laneCount - 1;
|
|
98
|
+
};
|
|
99
|
+
for (const id of topo) {
|
|
100
|
+
const parents = parentsOf.get(id);
|
|
101
|
+
let lane = null;
|
|
102
|
+
for (const parentId of parents) {
|
|
103
|
+
const parentLane = laneOf.get(parentId);
|
|
104
|
+
if (parentLane !== undefined && laneTips.get(parentLane) === parentId) {
|
|
105
|
+
lane = parentLane;
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (lane === null) {
|
|
110
|
+
lane = takeLane();
|
|
111
|
+
}
|
|
112
|
+
laneOf.set(id, lane);
|
|
113
|
+
laneTips.set(lane, id);
|
|
114
|
+
// A merge closes the branches it consumes: release their lanes.
|
|
115
|
+
for (const parentId of parents) {
|
|
116
|
+
const parentLane = laneOf.get(parentId);
|
|
117
|
+
if (parentLane !== undefined && parentLane !== lane && laneTips.get(parentLane) === parentId) {
|
|
118
|
+
laneTips.delete(parentLane);
|
|
119
|
+
freeLanes.push(parentLane);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// Display rows run newest first, like a git log.
|
|
124
|
+
const rowCount = topo.length;
|
|
125
|
+
const rowOf = new Map(topo.map((id, index) => [id, rowCount - 1 - index]));
|
|
126
|
+
const placements = [...topo]
|
|
127
|
+
.reverse()
|
|
128
|
+
.map(id => ({ id, lane: laneOf.get(id), row: rowOf.get(id) }));
|
|
129
|
+
const edges = [];
|
|
130
|
+
for (const id of topo) {
|
|
131
|
+
for (const parentId of parentsOf.get(id)) {
|
|
132
|
+
edges.push({
|
|
133
|
+
fromId: parentId,
|
|
134
|
+
toId: id,
|
|
135
|
+
fromLane: laneOf.get(parentId),
|
|
136
|
+
fromRow: rowOf.get(parentId),
|
|
137
|
+
toLane: laneOf.get(id),
|
|
138
|
+
toRow: rowOf.get(id),
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return { placements, edges, laneCount: Math.max(laneCount, 1), rowCount, warnings };
|
|
143
|
+
};
|
|
144
|
+
/**
|
|
145
|
+
* The shared display order for both projections: topological, newest first.
|
|
146
|
+
* The list and the graph agree by construction because both derive their
|
|
147
|
+
* order from the same layout.
|
|
148
|
+
*/
|
|
149
|
+
export const orderVersionsForDisplay = (nodes) => {
|
|
150
|
+
// Keep the first record per id — the same resolution the layout applies —
|
|
151
|
+
// so the returned records match the topology the layout placed.
|
|
152
|
+
const byId = new Map();
|
|
153
|
+
for (const node of nodes) {
|
|
154
|
+
if (!byId.has(node.id)) {
|
|
155
|
+
byId.set(node.id, node);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return computeVersionGraphLayout(nodes).placements.map(placement => byId.get(placement.id));
|
|
159
|
+
};
|