@aparte/plugin-cards 0.0.0-stage → 0.29.1
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/LICENSE +21 -0
- package/README.md +151 -2
- package/dist/carousel-DwgHvriV.js +63 -0
- package/dist/carousel-DwgHvriV.js.map +1 -0
- package/dist/controls-BYUm4aRV.js +62 -0
- package/dist/controls-BYUm4aRV.js.map +1 -0
- package/dist/format-BFq79ydr.js +728 -0
- package/dist/format-BFq79ydr.js.map +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +120 -0
- package/dist/index.js.map +1 -0
- package/dist/items/icons.d.ts +12 -0
- package/dist/items/icons.d.ts.map +1 -0
- package/dist/items/index.d.ts +100 -0
- package/dist/items/index.d.ts.map +1 -0
- package/dist/items.js +698 -0
- package/dist/items.js.map +1 -0
- package/dist/kit/carousel.d.ts +17 -0
- package/dist/kit/carousel.d.ts.map +1 -0
- package/dist/kit/controls.d.ts +51 -0
- package/dist/kit/controls.d.ts.map +1 -0
- package/dist/kit/define.d.ts +201 -0
- package/dist/kit/define.d.ts.map +1 -0
- package/dist/kit/dom.d.ts +40 -0
- package/dist/kit/dom.d.ts.map +1 -0
- package/dist/kit/fence.d.ts +39 -0
- package/dist/kit/fence.d.ts.map +1 -0
- package/dist/kit/format.d.ts +49 -0
- package/dist/kit/format.d.ts.map +1 -0
- package/dist/kit/rating.d.ts +19 -0
- package/dist/kit/rating.d.ts.map +1 -0
- package/dist/kit/sparkline.d.ts +19 -0
- package/dist/kit/sparkline.d.ts.map +1 -0
- package/dist/kit/strings.d.ts +139 -0
- package/dist/kit/strings.d.ts.map +1 -0
- package/dist/link/icons.d.ts +16 -0
- package/dist/link/icons.d.ts.map +1 -0
- package/dist/link/index.d.ts +98 -0
- package/dist/link/index.d.ts.map +1 -0
- package/dist/link.js +387 -0
- package/dist/link.js.map +1 -0
- package/dist/media/icons.d.ts +28 -0
- package/dist/media/icons.d.ts.map +1 -0
- package/dist/media/index.d.ts +107 -0
- package/dist/media/index.d.ts.map +1 -0
- package/dist/media.js +610 -0
- package/dist/media.js.map +1 -0
- package/dist/order/icons.d.ts +49 -0
- package/dist/order/icons.d.ts.map +1 -0
- package/dist/order/index.d.ts +204 -0
- package/dist/order/index.d.ts.map +1 -0
- package/dist/order.js +1843 -0
- package/dist/order.js.map +1 -0
- package/dist/place/icons.d.ts +18 -0
- package/dist/place/icons.d.ts.map +1 -0
- package/dist/place/index.d.ts +207 -0
- package/dist/place/index.d.ts.map +1 -0
- package/dist/place.js +1456 -0
- package/dist/place.js.map +1 -0
- package/dist/plan/icons.d.ts +25 -0
- package/dist/plan/icons.d.ts.map +1 -0
- package/dist/plan/index.d.ts +153 -0
- package/dist/plan/index.d.ts.map +1 -0
- package/dist/plan.js +1191 -0
- package/dist/plan.js.map +1 -0
- package/dist/product/icons.d.ts +16 -0
- package/dist/product/icons.d.ts.map +1 -0
- package/dist/product/index.d.ts +133 -0
- package/dist/product/index.d.ts.map +1 -0
- package/dist/product.js +1245 -0
- package/dist/product.js.map +1 -0
- package/dist/rating-CHVajQ2b.js +35 -0
- package/dist/rating-CHVajQ2b.js.map +1 -0
- package/dist/score/icons.d.ts +35 -0
- package/dist/score/icons.d.ts.map +1 -0
- package/dist/score/index.d.ts +185 -0
- package/dist/score/index.d.ts.map +1 -0
- package/dist/score.js +1843 -0
- package/dist/score.js.map +1 -0
- package/dist/sparkline-C-cA9obz.js +56 -0
- package/dist/sparkline-C-cA9obz.js.map +1 -0
- package/dist/stats/icons.d.ts +19 -0
- package/dist/stats/icons.d.ts.map +1 -0
- package/dist/stats/index.d.ts +137 -0
- package/dist/stats/index.d.ts.map +1 -0
- package/dist/stats.js +1085 -0
- package/dist/stats.js.map +1 -0
- package/dist/stock/chart.d.ts +135 -0
- package/dist/stock/chart.d.ts.map +1 -0
- package/dist/stock/icons.d.ts +15 -0
- package/dist/stock/icons.d.ts.map +1 -0
- package/dist/stock/index.d.ts +195 -0
- package/dist/stock/index.d.ts.map +1 -0
- package/dist/stock.js +1013 -0
- package/dist/stock.js.map +1 -0
- package/dist/weather/conditions.d.ts +25 -0
- package/dist/weather/conditions.d.ts.map +1 -0
- package/dist/weather/icons.d.ts +12 -0
- package/dist/weather/icons.d.ts.map +1 -0
- package/dist/weather/index.d.ts +146 -0
- package/dist/weather/index.d.ts.map +1 -0
- package/dist/weather.js +1162 -0
- package/dist/weather.js.map +1 -0
- package/package.json +137 -3
package/dist/plan.js
ADDED
|
@@ -0,0 +1,1191 @@
|
|
|
1
|
+
import { b as defineCard, q as str, e as el, t as toDate, r as word, h as formatNumber, m as glyph, i as formatPercent, p as num, n as intlTag } from "./format-BFq79ydr.js";
|
|
2
|
+
const svg = (body, className) => `<svg${className ? ` class="${className}"` : ""} viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg">${body}</svg>`;
|
|
3
|
+
const PENDING = svg('<circle cx="12" cy="12" r="8"/>');
|
|
4
|
+
const RUNNING = svg('<circle cx="12" cy="12" r="8" opacity="0.4"/><path d="M12 4a8 8 0 0 1 8 8"/>', "aparte-plan__spinner");
|
|
5
|
+
const DONE = svg('<circle cx="12" cy="12" r="9" fill="currentColor" stroke="none"/><path class="aparte-plan__mark" d="M8.2 12.4l2.6 2.6 5-5.4"/>');
|
|
6
|
+
const FAILED = svg('<circle cx="12" cy="12" r="9" fill="currentColor" stroke="none"/><path class="aparte-plan__mark" d="M9.3 9.3l5.4 5.4M14.7 9.3l-5.4 5.4"/>');
|
|
7
|
+
const SKIPPED = svg('<circle cx="12" cy="12" r="8" stroke-dasharray="2.4 2.6"/><path d="M8.6 12h6.8"/>');
|
|
8
|
+
const BLOCKED = svg('<rect x="6" y="10.6" width="12" height="9" rx="2.2"/><path d="M8.9 10.6V8.2a3.1 3.1 0 0 1 6.2 0v2.4"/>');
|
|
9
|
+
const STATUS_GLYPHS = {
|
|
10
|
+
pending: PENDING,
|
|
11
|
+
"in-progress": RUNNING,
|
|
12
|
+
done: DONE,
|
|
13
|
+
failed: FAILED,
|
|
14
|
+
skipped: SKIPPED,
|
|
15
|
+
blocked: BLOCKED
|
|
16
|
+
};
|
|
17
|
+
const NODE_CURRENT = svg('<circle cx="12" cy="12" r="8"/><circle cx="12" cy="12" r="4" fill="currentColor" stroke="none"/>');
|
|
18
|
+
const CLOCK = svg('<circle cx="12" cy="12" r="8.2"/><path d="M12 7.6V12l2.9 1.9"/>');
|
|
19
|
+
const PLAN = svg('<rect x="5" y="4.5" width="14" height="16.5" rx="2.2"/><path d="M9.2 4.5V3.8a1 1 0 0 1 1-1h3.6a1 1 0 0 1 1 1v.7"/><path d="M8.4 10.6l1.4 1.4 2.4-2.6"/><path d="M14.2 10.6h1.8"/><path d="M8.4 16.1l1.4 1.4 2.4-2.6"/><path d="M14.2 16.1h1.8"/>');
|
|
20
|
+
const planStyles = `/* =========================================
|
|
21
|
+
The plan card — @aparte/plugin-cards/plan
|
|
22
|
+
=========================================
|
|
23
|
+
Three variants of one card, chosen by \`data-variant\` on its root:
|
|
24
|
+
checklist "3 of 7 done" over a thin bar, then a row per step: its status glyph,
|
|
25
|
+
its title, a detail line, its duration at the end once it finished.
|
|
26
|
+
timeline a rail with a node per step, coloured by its status, its start time and
|
|
27
|
+
duration under the title; the step under way pulses gently.
|
|
28
|
+
compact one line — "Step 3 of 7: Running the tests", a ring, the percentage.
|
|
29
|
+
An agent re-sends this card while it works, and the kit draws the new one into the
|
|
30
|
+
same root, so nothing here depends on the status for its SIZE: the glyph is one size
|
|
31
|
+
for all, the title reserves its bold width (and its other wording's), a timed plan
|
|
32
|
+
keeps every time slot, as wide as the language's widest duration.
|
|
33
|
+
A status is never said by colour alone: each has its own glyph shape, and its word for
|
|
34
|
+
a screen reader. Every colour is a theme token or derived from one.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
.aparte-plan-card {
|
|
38
|
+
--aparte-plan-pad: var(--aparte-space-6);
|
|
39
|
+
--aparte-plan-glyph-size: 1.125rem;
|
|
40
|
+
--aparte-plan-node-size: 1.25rem;
|
|
41
|
+
--aparte-plan-pending: color-mix(in oklab, var(--aparte-text-muted) 75%, transparent);
|
|
42
|
+
--aparte-plan-active: var(--aparte-primary);
|
|
43
|
+
--aparte-plan-done: var(--aparte-success);
|
|
44
|
+
--aparte-plan-failed: var(--aparte-error);
|
|
45
|
+
--aparte-plan-failed-ink: var(--aparte-error-ink);
|
|
46
|
+
--aparte-plan-skipped: var(--aparte-text-muted);
|
|
47
|
+
--aparte-plan-blocked: var(--aparte-warning);
|
|
48
|
+
--aparte-plan-blocked-ink: var(--aparte-warning-ink);
|
|
49
|
+
--aparte-plan-track: color-mix(in oklab, var(--aparte-text) 9%, transparent);
|
|
50
|
+
--aparte-plan-rail: color-mix(in oklab, var(--aparte-text) 13%, transparent);
|
|
51
|
+
--aparte-plan-active-tint: color-mix(in oklab, var(--aparte-primary) 8%, transparent);
|
|
52
|
+
--aparte-plan-hover-tint: color-mix(in oklab, var(--aparte-text) 3.5%, transparent);
|
|
53
|
+
--aparte-plan-done-text: var(--aparte-text-muted);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.aparte-plan-card[data-variant="checklist"],
|
|
57
|
+
.aparte-plan-card[data-variant="timeline"] { --aparte-rich-card-max-inline-size: 26rem; }
|
|
58
|
+
.aparte-plan-card[data-variant="compact"] { --aparte-rich-card-max-inline-size: 24rem; }
|
|
59
|
+
|
|
60
|
+
/* ── Header: title, "3 of 7 done", the bar ───────────────────────────── */
|
|
61
|
+
|
|
62
|
+
.aparte-plan__head {
|
|
63
|
+
display: flex;
|
|
64
|
+
flex-direction: column;
|
|
65
|
+
gap: var(--aparte-space-3);
|
|
66
|
+
padding: var(--aparte-plan-pad) var(--aparte-plan-pad) 0;
|
|
67
|
+
min-inline-size: 0;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.aparte-plan__heading {
|
|
71
|
+
display: flex;
|
|
72
|
+
flex-direction: column;
|
|
73
|
+
gap: var(--aparte-space-1);
|
|
74
|
+
min-inline-size: 0;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.aparte-plan__title {
|
|
78
|
+
color: var(--aparte-text);
|
|
79
|
+
font-size: var(--aparte-font-size-base);
|
|
80
|
+
font-weight: var(--aparte-font-weight-semibold);
|
|
81
|
+
line-height: 1.3;
|
|
82
|
+
overflow-wrap: anywhere;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.aparte-plan__goal {
|
|
86
|
+
color: var(--aparte-text-muted);
|
|
87
|
+
font-size: var(--aparte-font-size-sm);
|
|
88
|
+
overflow-wrap: anywhere;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/* One line, always: a count that grows a digit or a note that appears does not wrap the
|
|
92
|
+
header and push the steps down while the agent works. */
|
|
93
|
+
.aparte-plan__meter {
|
|
94
|
+
display: flex;
|
|
95
|
+
align-items: center;
|
|
96
|
+
gap: var(--aparte-space-3);
|
|
97
|
+
min-inline-size: 0;
|
|
98
|
+
color: var(--aparte-text-muted);
|
|
99
|
+
font-size: var(--aparte-font-size-sm);
|
|
100
|
+
font-variant-numeric: tabular-nums;
|
|
101
|
+
white-space: nowrap;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.aparte-plan__count {
|
|
105
|
+
flex: none;
|
|
106
|
+
color: var(--aparte-text);
|
|
107
|
+
font-weight: var(--aparte-font-weight-medium);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.aparte-plan__note {
|
|
111
|
+
min-inline-size: 0;
|
|
112
|
+
overflow: hidden;
|
|
113
|
+
text-overflow: ellipsis;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.aparte-plan__note::before {
|
|
117
|
+
content: "·";
|
|
118
|
+
margin-inline-end: var(--aparte-space-3);
|
|
119
|
+
color: var(--aparte-text-muted);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.aparte-plan__note[data-kind="failed"] { color: var(--aparte-plan-failed-ink); }
|
|
123
|
+
|
|
124
|
+
.aparte-plan__elapsed {
|
|
125
|
+
display: inline-flex;
|
|
126
|
+
flex: none;
|
|
127
|
+
align-items: center;
|
|
128
|
+
gap: var(--aparte-space-1);
|
|
129
|
+
margin-inline-start: auto;
|
|
130
|
+
--aparte-rich-card-glyph-size: 0.95rem;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.aparte-plan__clock { opacity: 0.8; }
|
|
134
|
+
|
|
135
|
+
/* The bar: what is done in the accent (green once all of it is), what was skipped after
|
|
136
|
+
it in hatching — counted, but plainly not the same as done. */
|
|
137
|
+
.aparte-plan__bar {
|
|
138
|
+
display: flex;
|
|
139
|
+
block-size: 0.375rem;
|
|
140
|
+
overflow: hidden;
|
|
141
|
+
border-radius: var(--aparte-radius-full);
|
|
142
|
+
background: var(--aparte-plan-track);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.aparte-plan__bar-done,
|
|
146
|
+
.aparte-plan__bar-skipped {
|
|
147
|
+
flex: none;
|
|
148
|
+
block-size: 100%;
|
|
149
|
+
transition: inline-size var(--aparte-duration-slow, 400ms) var(--aparte-ease, ease);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.aparte-plan__bar-done {
|
|
153
|
+
inline-size: var(--aparte-plan-done-share, 0%);
|
|
154
|
+
background: var(--aparte-plan-active);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.aparte-plan__bar[data-complete] .aparte-plan__bar-done { background: var(--aparte-plan-done); }
|
|
158
|
+
|
|
159
|
+
.aparte-plan__bar-skipped {
|
|
160
|
+
inline-size: var(--aparte-plan-skipped-share, 0%);
|
|
161
|
+
background: repeating-linear-gradient(
|
|
162
|
+
-45deg,
|
|
163
|
+
color-mix(in oklab, var(--aparte-plan-skipped) 55%, transparent) 0 2px,
|
|
164
|
+
color-mix(in oklab, var(--aparte-plan-skipped) 22%, transparent) 2px 4px
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/* ── A step's parts, whatever the variant ────────────────────────────── */
|
|
169
|
+
|
|
170
|
+
/* The text and an invisible bold copy share one cell: the cell is always as wide and as
|
|
171
|
+
tall as the bold form, so the step that starts running reflows nothing. A todo's other
|
|
172
|
+
wording (\`activeForm\`) is a second invisible copy in the same cell, so the switch to
|
|
173
|
+
"Updating the reference pages" from "Update" reflows nothing either. */
|
|
174
|
+
.aparte-plan__step-title {
|
|
175
|
+
display: grid;
|
|
176
|
+
min-inline-size: 0;
|
|
177
|
+
overflow-wrap: anywhere;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.aparte-plan__step-title > .aparte-plan__step-title-text,
|
|
181
|
+
.aparte-plan__step-title[data-alt]::before,
|
|
182
|
+
.aparte-plan__step-title::after { grid-area: 1 / 1; }
|
|
183
|
+
|
|
184
|
+
.aparte-plan__step-title[data-alt]::before {
|
|
185
|
+
content: attr(data-alt);
|
|
186
|
+
content: attr(data-alt) / "";
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.aparte-plan__step-title[data-alt]::before,
|
|
190
|
+
.aparte-plan__step-title::after {
|
|
191
|
+
font-weight: var(--aparte-font-weight-semibold);
|
|
192
|
+
visibility: hidden;
|
|
193
|
+
pointer-events: none;
|
|
194
|
+
user-select: none;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.aparte-plan__step-title::after {
|
|
198
|
+
content: attr(data-text);
|
|
199
|
+
content: attr(data-text) / "";
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.aparte-plan__detail {
|
|
203
|
+
display: -webkit-box;
|
|
204
|
+
margin-block-start: 0.125rem;
|
|
205
|
+
overflow: hidden;
|
|
206
|
+
color: var(--aparte-text-muted);
|
|
207
|
+
font-size: var(--aparte-font-size-sm);
|
|
208
|
+
line-height: 1.4;
|
|
209
|
+
overflow-wrap: anywhere;
|
|
210
|
+
-webkit-box-orient: vertical;
|
|
211
|
+
-webkit-line-clamp: 2;
|
|
212
|
+
line-clamp: 2;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.aparte-plan__duration {
|
|
216
|
+
color: var(--aparte-text-muted);
|
|
217
|
+
font-size: var(--aparte-font-size-xs);
|
|
218
|
+
font-variant-numeric: tabular-nums;
|
|
219
|
+
white-space: nowrap;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/* The status colours: the glyph takes them; the text only where it must speak. */
|
|
223
|
+
[data-status] > .aparte-plan__row > .aparte-plan__glyph,
|
|
224
|
+
[data-status] > .aparte-plan__node,
|
|
225
|
+
.aparte-plan__compact > .aparte-plan__glyph { color: var(--aparte-plan-pending); }
|
|
226
|
+
|
|
227
|
+
[data-status="in-progress"] > .aparte-plan__row > .aparte-plan__glyph,
|
|
228
|
+
[data-status="in-progress"] > .aparte-plan__node,
|
|
229
|
+
.aparte-plan__compact[data-status="in-progress"] > .aparte-plan__glyph { color: var(--aparte-plan-active); }
|
|
230
|
+
|
|
231
|
+
[data-status="done"] > .aparte-plan__row > .aparte-plan__glyph,
|
|
232
|
+
[data-status="done"] > .aparte-plan__node,
|
|
233
|
+
.aparte-plan__compact[data-status="done"] > .aparte-plan__glyph { color: var(--aparte-plan-done); }
|
|
234
|
+
|
|
235
|
+
[data-status="failed"] > .aparte-plan__row > .aparte-plan__glyph,
|
|
236
|
+
[data-status="failed"] > .aparte-plan__node,
|
|
237
|
+
.aparte-plan__compact[data-status="failed"] > .aparte-plan__glyph { color: var(--aparte-plan-failed); }
|
|
238
|
+
|
|
239
|
+
[data-status="skipped"] > .aparte-plan__row > .aparte-plan__glyph,
|
|
240
|
+
[data-status="skipped"] > .aparte-plan__node { color: var(--aparte-plan-skipped); }
|
|
241
|
+
|
|
242
|
+
[data-status="blocked"] > .aparte-plan__row > .aparte-plan__glyph,
|
|
243
|
+
[data-status="blocked"] > .aparte-plan__node,
|
|
244
|
+
.aparte-plan__compact[data-status="blocked"] > .aparte-plan__glyph { color: var(--aparte-plan-blocked-ink); }
|
|
245
|
+
|
|
246
|
+
/* The check and the cross are cut out of their disc in the card's own surface. */
|
|
247
|
+
.aparte-plan__mark {
|
|
248
|
+
stroke: var(--aparte-surface-1);
|
|
249
|
+
stroke-width: 2.2;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/* The running ring turns, on core's own keyframe — its sheet comes with the chat; a copy
|
|
253
|
+
here was a second name for the same turn. Under reduced motion the kit stops it and the
|
|
254
|
+
arc stays. */
|
|
255
|
+
.aparte-plan__spinner {
|
|
256
|
+
transform-origin: 50% 50%;
|
|
257
|
+
animation: aparte-spin 0.9s linear infinite;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/* Done stays readable — muted, not struck through. Failed speaks in the error ink. */
|
|
261
|
+
[data-status="done"] > .aparte-plan__row .aparte-plan__step-title,
|
|
262
|
+
[data-status="done"] > .aparte-plan__event-body > .aparte-plan__step-title,
|
|
263
|
+
[data-status="skipped"] > .aparte-plan__row .aparte-plan__step-title,
|
|
264
|
+
[data-status="skipped"] > .aparte-plan__event-body > .aparte-plan__step-title { color: var(--aparte-plan-done-text); }
|
|
265
|
+
|
|
266
|
+
[data-status="failed"] > .aparte-plan__row .aparte-plan__step-title,
|
|
267
|
+
[data-status="failed"] > .aparte-plan__event-body > .aparte-plan__step-title { color: var(--aparte-plan-failed-ink); }
|
|
268
|
+
|
|
269
|
+
[data-status="blocked"] > .aparte-plan__row .aparte-plan__step-title,
|
|
270
|
+
[data-status="blocked"] > .aparte-plan__event-body > .aparte-plan__step-title { color: var(--aparte-text-muted); }
|
|
271
|
+
|
|
272
|
+
[data-status="in-progress"] > .aparte-plan__row .aparte-plan__step-title,
|
|
273
|
+
[data-status="in-progress"] > .aparte-plan__event-body > .aparte-plan__step-title {
|
|
274
|
+
color: var(--aparte-text);
|
|
275
|
+
font-weight: var(--aparte-font-weight-semibold);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/* ── checklist ───────────────────────────────────────────────────────── */
|
|
279
|
+
|
|
280
|
+
.aparte-plan__steps,
|
|
281
|
+
.aparte-plan__substeps,
|
|
282
|
+
.aparte-plan__timeline {
|
|
283
|
+
margin: 0;
|
|
284
|
+
padding: 0;
|
|
285
|
+
list-style: none;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.aparte-plan__steps {
|
|
289
|
+
display: flex;
|
|
290
|
+
flex-direction: column;
|
|
291
|
+
gap: 0.125rem;
|
|
292
|
+
padding: var(--aparte-space-4) calc(var(--aparte-plan-pad) - var(--aparte-space-3)) calc(var(--aparte-plan-pad) - var(--aparte-space-2));
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.aparte-plan__row {
|
|
296
|
+
display: grid;
|
|
297
|
+
grid-template-columns: var(--aparte-plan-glyph-size) minmax(0, 1fr) auto;
|
|
298
|
+
column-gap: var(--aparte-space-3);
|
|
299
|
+
align-items: baseline;
|
|
300
|
+
padding: var(--aparte-space-2) var(--aparte-space-3);
|
|
301
|
+
border-radius: var(--aparte-radius-md);
|
|
302
|
+
color: var(--aparte-text);
|
|
303
|
+
font-size: var(--aparte-font-size-md);
|
|
304
|
+
line-height: 1.4;
|
|
305
|
+
transition: background-color var(--aparte-duration-fast, 120ms) ease;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.aparte-plan__row:hover { background: var(--aparte-plan-hover-tint); }
|
|
309
|
+
|
|
310
|
+
/* The step under way: a soft tint of the accent, held on hover. */
|
|
311
|
+
.aparte-plan__step[data-status="in-progress"] > .aparte-plan__row,
|
|
312
|
+
.aparte-plan__step[data-status="in-progress"] > .aparte-plan__row:hover { background: var(--aparte-plan-active-tint); }
|
|
313
|
+
|
|
314
|
+
.aparte-plan__step > .aparte-plan__row > .aparte-plan__glyph {
|
|
315
|
+
--aparte-rich-card-glyph-size: var(--aparte-plan-glyph-size);
|
|
316
|
+
align-self: start;
|
|
317
|
+
/* Centred on the first line of the title, however the font sizes it. */
|
|
318
|
+
margin-block-start: calc((1lh - var(--aparte-plan-glyph-size)) / 2);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
.aparte-plan__text { min-inline-size: 0; }
|
|
322
|
+
|
|
323
|
+
/* A timed plan keeps the duration's column on every row, filled or not, as wide as the
|
|
324
|
+
language's widest duration: an invisible copy of it (\`data-reserve\`, written by the card)
|
|
325
|
+
lies under the text with no height, so \`1 Std., 5 Min.\` fills the slot without widening it
|
|
326
|
+
— a wider slot would narrow the title and could wrap it onto a second line. */
|
|
327
|
+
.aparte-plan__steps[data-timed] .aparte-plan__duration {
|
|
328
|
+
display: inline-flex;
|
|
329
|
+
flex-direction: column;
|
|
330
|
+
align-items: flex-end;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
.aparte-plan__steps[data-timed] .aparte-plan__duration::after {
|
|
334
|
+
content: attr(data-reserve);
|
|
335
|
+
content: attr(data-reserve) / "";
|
|
336
|
+
block-size: 0;
|
|
337
|
+
overflow: hidden;
|
|
338
|
+
visibility: hidden;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
.aparte-plan__substeps {
|
|
342
|
+
display: flex;
|
|
343
|
+
flex-direction: column;
|
|
344
|
+
gap: 0;
|
|
345
|
+
margin-inline-start: calc(var(--aparte-plan-glyph-size) + var(--aparte-space-3));
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
.aparte-plan__step--sub {
|
|
349
|
+
--aparte-plan-glyph-size: 0.9375rem;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
.aparte-plan__step--sub > .aparte-plan__row {
|
|
353
|
+
padding-block: var(--aparte-space-1);
|
|
354
|
+
font-size: var(--aparte-font-size-sm);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/* A sub-step under way is tinted by its parent's row already: it keeps only its weight. */
|
|
358
|
+
.aparte-plan__step--sub[data-status="in-progress"] > .aparte-plan__row,
|
|
359
|
+
.aparte-plan__step--sub[data-status="in-progress"] > .aparte-plan__row:hover { background: transparent; }
|
|
360
|
+
|
|
361
|
+
/* ── timeline ────────────────────────────────────────────────────────── */
|
|
362
|
+
|
|
363
|
+
.aparte-plan__timeline {
|
|
364
|
+
padding: var(--aparte-space-5) var(--aparte-plan-pad) var(--aparte-plan-pad);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
.aparte-plan__event {
|
|
368
|
+
position: relative;
|
|
369
|
+
display: grid;
|
|
370
|
+
grid-template-columns: var(--aparte-plan-node-size) minmax(0, 1fr);
|
|
371
|
+
column-gap: var(--aparte-space-4);
|
|
372
|
+
padding-block-end: var(--aparte-space-5);
|
|
373
|
+
color: var(--aparte-text);
|
|
374
|
+
font-size: var(--aparte-font-size-md);
|
|
375
|
+
line-height: 1.4;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
.aparte-plan__event:last-child { padding-block-end: 0; }
|
|
379
|
+
|
|
380
|
+
/* The rail: from under this node to the top of the next one. Past a finished step it
|
|
381
|
+
takes the done colour, so the run reads as a line filling downwards. */
|
|
382
|
+
.aparte-plan__event:not(:last-child)::before {
|
|
383
|
+
content: "";
|
|
384
|
+
position: absolute;
|
|
385
|
+
inset-inline-start: calc(var(--aparte-plan-node-size) / 2 - 1px);
|
|
386
|
+
inset-block-start: calc((1lh - var(--aparte-plan-node-size)) / 2 + var(--aparte-plan-node-size) + 3px);
|
|
387
|
+
inset-block-end: calc((1lh - var(--aparte-plan-node-size)) / -2 + 3px);
|
|
388
|
+
inline-size: 2px;
|
|
389
|
+
border-radius: var(--aparte-radius-full);
|
|
390
|
+
background: var(--aparte-plan-rail);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
.aparte-plan__event[data-status="done"]:not(:last-child)::before,
|
|
394
|
+
.aparte-plan__event[data-status="skipped"]:not(:last-child)::before {
|
|
395
|
+
background: color-mix(in oklab, var(--aparte-plan-done) 55%, transparent);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
.aparte-plan__node {
|
|
399
|
+
position: relative;
|
|
400
|
+
display: grid;
|
|
401
|
+
place-items: center;
|
|
402
|
+
inline-size: var(--aparte-plan-node-size);
|
|
403
|
+
block-size: var(--aparte-plan-node-size);
|
|
404
|
+
margin-block-start: calc((1lh - var(--aparte-plan-node-size)) / 2);
|
|
405
|
+
border-radius: var(--aparte-radius-full);
|
|
406
|
+
--aparte-rich-card-glyph-size: var(--aparte-plan-node-size);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/* The step under way breathes: a halo of the accent that widens and fades. */
|
|
410
|
+
.aparte-plan__event[data-status="in-progress"] > .aparte-plan__node::after {
|
|
411
|
+
content: "";
|
|
412
|
+
position: absolute;
|
|
413
|
+
inset: 0;
|
|
414
|
+
border-radius: inherit;
|
|
415
|
+
animation: aparte-plan-pulse 2.2s cubic-bezier(0.2, 0.6, 0.35, 1) infinite;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
@keyframes aparte-plan-pulse {
|
|
419
|
+
0% { box-shadow: 0 0 0 0 color-mix(in oklab, var(--aparte-plan-active) 45%, transparent); }
|
|
420
|
+
70%, 100% { box-shadow: 0 0 0 0.45rem color-mix(in oklab, var(--aparte-plan-active) 0%, transparent); }
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
.aparte-plan__event-body {
|
|
424
|
+
display: flex;
|
|
425
|
+
flex-direction: column;
|
|
426
|
+
min-inline-size: 0;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/* Start and duration on one line under the title. In a timed plan the line is there for
|
|
430
|
+
every step, empty until it starts: every step keeps one rhythm. */
|
|
431
|
+
.aparte-plan__meta {
|
|
432
|
+
display: flex;
|
|
433
|
+
flex-wrap: wrap;
|
|
434
|
+
align-items: baseline;
|
|
435
|
+
gap: 0 var(--aparte-space-2);
|
|
436
|
+
min-block-size: 1lh;
|
|
437
|
+
color: var(--aparte-text-muted);
|
|
438
|
+
font-size: var(--aparte-font-size-xs);
|
|
439
|
+
font-variant-numeric: tabular-nums;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
.aparte-plan__meta > .aparte-plan__start:not(:empty) + .aparte-plan__duration:not(:empty)::before {
|
|
443
|
+
content: "·";
|
|
444
|
+
margin-inline-end: var(--aparte-space-2);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
.aparte-plan__event-body > .aparte-plan__substeps {
|
|
448
|
+
margin-block-start: var(--aparte-space-2);
|
|
449
|
+
margin-inline: calc(var(--aparte-space-3) * -1) 0;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/* ── compact ─────────────────────────────────────────────────────────── */
|
|
453
|
+
|
|
454
|
+
.aparte-plan__compact {
|
|
455
|
+
display: grid;
|
|
456
|
+
grid-template-columns: auto minmax(0, 1fr) auto auto;
|
|
457
|
+
align-items: center;
|
|
458
|
+
column-gap: var(--aparte-space-3);
|
|
459
|
+
padding: var(--aparte-space-3) var(--aparte-space-5);
|
|
460
|
+
font-size: var(--aparte-font-size-md);
|
|
461
|
+
--aparte-rich-card-glyph-size: 1.125rem;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
.aparte-plan__compact-text {
|
|
465
|
+
min-inline-size: 0;
|
|
466
|
+
overflow: hidden;
|
|
467
|
+
text-overflow: ellipsis;
|
|
468
|
+
white-space: nowrap;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
.aparte-plan__compact-step {
|
|
472
|
+
margin-inline-end: 0.35em;
|
|
473
|
+
color: var(--aparte-text-muted);
|
|
474
|
+
font-variant-numeric: tabular-nums;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
.aparte-plan__compact-title {
|
|
478
|
+
color: var(--aparte-text);
|
|
479
|
+
font-weight: var(--aparte-font-weight-semibold);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/* Finished: the plan's name, then "All steps done". */
|
|
483
|
+
.aparte-plan__compact-title + .aparte-plan__compact-step { margin-inline: 0; }
|
|
484
|
+
.aparte-plan__compact-title + .aparte-plan__compact-step::before {
|
|
485
|
+
content: "·";
|
|
486
|
+
margin-inline: 0.4em;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
.aparte-plan__ring {
|
|
490
|
+
display: inline-flex;
|
|
491
|
+
inline-size: 1.375rem;
|
|
492
|
+
block-size: 1.375rem;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/* The arc starts at twelve o'clock and runs with the reading direction. */
|
|
496
|
+
.aparte-plan__ring-svg {
|
|
497
|
+
inline-size: 100%;
|
|
498
|
+
block-size: 100%;
|
|
499
|
+
transform: rotate(-90deg);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
.aparte-plan__ring-svg:dir(rtl) { transform: scaleX(-1) rotate(-90deg); }
|
|
503
|
+
|
|
504
|
+
.aparte-plan__ring-svg > circle {
|
|
505
|
+
fill: none;
|
|
506
|
+
stroke-width: 3;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
.aparte-plan__ring-track { stroke: var(--aparte-plan-track); }
|
|
510
|
+
.aparte-plan__ring-done { stroke: var(--aparte-plan-active); }
|
|
511
|
+
.aparte-plan__ring[data-complete] .aparte-plan__ring-done { stroke: var(--aparte-plan-done); }
|
|
512
|
+
.aparte-plan__ring-skipped { stroke: color-mix(in oklab, var(--aparte-plan-skipped) 45%, transparent); }
|
|
513
|
+
|
|
514
|
+
.aparte-plan__percent {
|
|
515
|
+
min-inline-size: 4ch;
|
|
516
|
+
color: var(--aparte-text);
|
|
517
|
+
font-size: var(--aparte-font-size-sm);
|
|
518
|
+
font-weight: var(--aparte-font-weight-semibold);
|
|
519
|
+
font-variant-numeric: tabular-nums;
|
|
520
|
+
text-align: end;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/* ── Loading ─────────────────────────────────────────────────────────── */
|
|
524
|
+
|
|
525
|
+
.aparte-plan-card[data-state="loading"] .aparte-skeleton.aparte-plan__sk-title { inline-size: 10rem; }
|
|
526
|
+
.aparte-plan-card[data-state="loading"] .aparte-skeleton.aparte-plan__sk-count { inline-size: 6rem; }
|
|
527
|
+
|
|
528
|
+
.aparte-plan-card[data-state="loading"] .aparte-skeleton.aparte-plan__sk-glyph {
|
|
529
|
+
inline-size: var(--aparte-plan-glyph-size);
|
|
530
|
+
block-size: var(--aparte-plan-glyph-size);
|
|
531
|
+
align-self: center;
|
|
532
|
+
margin: 0;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
.aparte-plan-card[data-state="loading"] .aparte-skeleton.aparte-plan__sk-node {
|
|
536
|
+
inline-size: var(--aparte-plan-node-size);
|
|
537
|
+
block-size: var(--aparte-plan-node-size);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
.aparte-plan-card[data-state="loading"] .aparte-skeleton.aparte-plan__sk-ring {
|
|
541
|
+
inline-size: 1.375rem;
|
|
542
|
+
block-size: 1.375rem;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
.aparte-plan-card[data-state="loading"] .aparte-plan__row { align-items: center; }
|
|
546
|
+
.aparte-plan-card[data-state="loading"] .aparte-skeleton.aparte-plan__sk-line { inline-size: 70%; }
|
|
547
|
+
.aparte-plan-card[data-state="loading"] li:nth-child(2) .aparte-skeleton.aparte-plan__sk-line { inline-size: 55%; }
|
|
548
|
+
.aparte-plan-card[data-state="loading"] li:nth-child(3) .aparte-skeleton.aparte-plan__sk-line { inline-size: 80%; }
|
|
549
|
+
.aparte-plan-card[data-state="loading"] li:nth-child(4) .aparte-skeleton.aparte-plan__sk-line { inline-size: 45%; }
|
|
550
|
+
.aparte-plan-card[data-state="loading"] .aparte-skeleton.aparte-plan__sk-meta { inline-size: 5rem; }
|
|
551
|
+
.aparte-plan-card[data-state="loading"] .aparte-plan__compact .aparte-skeleton.aparte-plan__sk-line { inline-size: 75%; }
|
|
552
|
+
|
|
553
|
+
/* ── Small screens ───────────────────────────────────────────────────── */
|
|
554
|
+
|
|
555
|
+
@media (max-width: 380px) {
|
|
556
|
+
.aparte-plan-card { --aparte-plan-pad: var(--aparte-space-5); }
|
|
557
|
+
.aparte-plan__compact { column-gap: var(--aparte-space-2); padding-inline: var(--aparte-space-4); }
|
|
558
|
+
}
|
|
559
|
+
`;
|
|
560
|
+
const W = {
|
|
561
|
+
cardsPlanTitle: { en: "Plan", fr: "Plan", de: "Plan", es: "Plan", it: "Piano", ja: "計画", ko: "계획", pt: "Plano", ru: "План", zh: "计划" },
|
|
562
|
+
cardsPlanProgress: {
|
|
563
|
+
en: "{done} of {total} done",
|
|
564
|
+
fr: "{done} sur {total} terminées",
|
|
565
|
+
de: "{done} von {total} erledigt",
|
|
566
|
+
es: "{done} de {total} completados",
|
|
567
|
+
it: "{done} di {total} completati",
|
|
568
|
+
ja: "{total}件中{done}件完了",
|
|
569
|
+
ko: "{total}개 중 {done}개 완료",
|
|
570
|
+
pt: "{done} de {total} concluídos",
|
|
571
|
+
ru: "Выполнено {done} из {total}",
|
|
572
|
+
zh: "已完成 {done}/{total}"
|
|
573
|
+
},
|
|
574
|
+
cardsPlanSkipped: {
|
|
575
|
+
en: "{n} skipped",
|
|
576
|
+
fr: "Ignorées : {n}",
|
|
577
|
+
de: "Übersprungen: {n}",
|
|
578
|
+
es: "Omitidos: {n}",
|
|
579
|
+
it: "Saltati: {n}",
|
|
580
|
+
ja: "スキップ {n}件",
|
|
581
|
+
ko: "건너뜀 {n}개",
|
|
582
|
+
pt: "Ignorados: {n}",
|
|
583
|
+
ru: "Пропущено: {n}",
|
|
584
|
+
zh: "已跳过 {n} 项"
|
|
585
|
+
},
|
|
586
|
+
cardsPlanFailed: {
|
|
587
|
+
en: "{n} failed",
|
|
588
|
+
fr: "Échecs : {n}",
|
|
589
|
+
de: "Fehlgeschlagen: {n}",
|
|
590
|
+
es: "Fallidos: {n}",
|
|
591
|
+
it: "Falliti: {n}",
|
|
592
|
+
ja: "失敗 {n}件",
|
|
593
|
+
ko: "실패 {n}개",
|
|
594
|
+
pt: "Falhas: {n}",
|
|
595
|
+
ru: "Ошибок: {n}",
|
|
596
|
+
zh: "失败 {n} 项"
|
|
597
|
+
},
|
|
598
|
+
cardsPlanElapsed: {
|
|
599
|
+
en: "{time} elapsed",
|
|
600
|
+
fr: "Durée : {time}",
|
|
601
|
+
de: "Dauer: {time}",
|
|
602
|
+
es: "Duración: {time}",
|
|
603
|
+
it: "Durata: {time}",
|
|
604
|
+
ja: "経過時間 {time}",
|
|
605
|
+
ko: "경과 {time}",
|
|
606
|
+
pt: "Duração: {time}",
|
|
607
|
+
ru: "Прошло: {time}",
|
|
608
|
+
zh: "用时 {time}"
|
|
609
|
+
},
|
|
610
|
+
cardsPlanStepNow: {
|
|
611
|
+
en: "Step {n} of {total}:",
|
|
612
|
+
fr: "Étape {n} sur {total} :",
|
|
613
|
+
de: "Schritt {n} von {total}:",
|
|
614
|
+
es: "Paso {n} de {total}:",
|
|
615
|
+
it: "Passo {n} di {total}:",
|
|
616
|
+
ja: "ステップ {n}/{total}:",
|
|
617
|
+
ko: "{total}단계 중 {n}단계:",
|
|
618
|
+
pt: "Etapa {n} de {total}:",
|
|
619
|
+
ru: "Шаг {n} из {total}:",
|
|
620
|
+
zh: "第 {n}/{total} 步:"
|
|
621
|
+
},
|
|
622
|
+
cardsPlanAllDone: {
|
|
623
|
+
en: "All steps done",
|
|
624
|
+
fr: "Toutes les étapes sont terminées",
|
|
625
|
+
de: "Alle Schritte erledigt",
|
|
626
|
+
es: "Todos los pasos completados",
|
|
627
|
+
it: "Tutti i passi completati",
|
|
628
|
+
ja: "すべてのステップが完了",
|
|
629
|
+
ko: "모든 단계 완료",
|
|
630
|
+
pt: "Todas as etapas concluídas",
|
|
631
|
+
ru: "Все шаги выполнены",
|
|
632
|
+
zh: "所有步骤已完成"
|
|
633
|
+
},
|
|
634
|
+
cardsPlanStatusPending: { en: "To do", fr: "À faire", de: "Offen", es: "Pendiente", it: "Da fare", ja: "未着手", ko: "대기", pt: "A fazer", ru: "Не начато", zh: "待办" },
|
|
635
|
+
cardsPlanStatusInProgress: { en: "In progress", fr: "En cours", de: "In Arbeit", es: "En curso", it: "In corso", ja: "進行中", ko: "진행 중", pt: "Em andamento", ru: "Выполняется", zh: "进行中" },
|
|
636
|
+
cardsPlanStatusDone: { en: "Done", fr: "Terminé", de: "Erledigt", es: "Hecho", it: "Fatto", ja: "完了", ko: "완료", pt: "Concluído", ru: "Готово", zh: "已完成" },
|
|
637
|
+
cardsPlanStatusFailed: { en: "Failed", fr: "Échec", de: "Fehlgeschlagen", es: "Fallido", it: "Non riuscito", ja: "失敗", ko: "실패", pt: "Falhou", ru: "Ошибка", zh: "失败" },
|
|
638
|
+
cardsPlanStatusSkipped: { en: "Skipped", fr: "Ignoré", de: "Übersprungen", es: "Omitido", it: "Saltato", ja: "スキップ", ko: "건너뜀", pt: "Ignorado", ru: "Пропущено", zh: "已跳过" },
|
|
639
|
+
cardsPlanStatusBlocked: { en: "Blocked", fr: "Bloqué", de: "Blockiert", es: "Bloqueado", it: "Bloccato", ja: "ブロック中", ko: "차단됨", pt: "Bloqueado", ru: "Заблокировано", zh: "受阻" },
|
|
640
|
+
cardsPlanSummary: {
|
|
641
|
+
en: "{done}/{total} done",
|
|
642
|
+
fr: "{done}/{total} terminées",
|
|
643
|
+
de: "{done}/{total} erledigt",
|
|
644
|
+
es: "{done}/{total} completados",
|
|
645
|
+
it: "{done}/{total} completati",
|
|
646
|
+
ja: "{done}/{total} 完了",
|
|
647
|
+
ko: "{done}/{total} 완료",
|
|
648
|
+
pt: "{done}/{total} concluídos",
|
|
649
|
+
ru: "выполнено {done}/{total}",
|
|
650
|
+
zh: "已完成 {done}/{total}"
|
|
651
|
+
},
|
|
652
|
+
cardsPlanNow: {
|
|
653
|
+
en: "now: {step}",
|
|
654
|
+
fr: "en cours : {step}",
|
|
655
|
+
de: "jetzt: {step}",
|
|
656
|
+
es: "ahora: {step}",
|
|
657
|
+
it: "ora: {step}",
|
|
658
|
+
ja: "実行中:{step}",
|
|
659
|
+
ko: "현재: {step}",
|
|
660
|
+
pt: "agora: {step}",
|
|
661
|
+
ru: "сейчас: {step}",
|
|
662
|
+
zh: "当前:{step}"
|
|
663
|
+
}
|
|
664
|
+
};
|
|
665
|
+
const say = (config, key, vars) => word(config, key, W[key], vars);
|
|
666
|
+
const STATUS_WORD = {
|
|
667
|
+
pending: "cardsPlanStatusPending",
|
|
668
|
+
"in-progress": "cardsPlanStatusInProgress",
|
|
669
|
+
done: "cardsPlanStatusDone",
|
|
670
|
+
failed: "cardsPlanStatusFailed",
|
|
671
|
+
skipped: "cardsPlanStatusSkipped",
|
|
672
|
+
blocked: "cardsPlanStatusBlocked"
|
|
673
|
+
};
|
|
674
|
+
const PLAN_STEPS_SHOWN = 50;
|
|
675
|
+
const isObject = (value) => !!value && typeof value === "object" && !Array.isArray(value);
|
|
676
|
+
const notNull = (value) => value !== null && value !== void 0;
|
|
677
|
+
function pick(r, keys) {
|
|
678
|
+
for (const key of keys) if (r[key] !== void 0 && r[key] !== null && r[key] !== "") return r[key];
|
|
679
|
+
return void 0;
|
|
680
|
+
}
|
|
681
|
+
const STATUS_ALIASES = {
|
|
682
|
+
pending: ["pending", "todo", "to-do", "open", "queued", "planned", "not-started", "notstarted", "new", "waiting", "scheduled", "upcoming", "ready", "idle", "backlog"],
|
|
683
|
+
"in-progress": ["in-progress", "inprogress", "progress", "doing", "running", "active", "current", "started", "working", "executing", "ongoing", "processing", "in-flight"],
|
|
684
|
+
done: ["done", "completed", "complete", "success", "succeeded", "successful", "finished", "ok", "passed", "resolved", "closed", "checked"],
|
|
685
|
+
failed: ["failed", "fail", "failure", "error", "errored", "aborted", "timeout", "timed-out", "crashed"],
|
|
686
|
+
skipped: ["skipped", "skip", "cancelled", "canceled", "omitted", "not-needed", "wont-do", "won't-do", "wontdo", "obsolete", "dropped"],
|
|
687
|
+
blocked: ["blocked", "on-hold", "onhold", "paused", "stuck", "held", "needs-input", "needs-approval", "awaiting-input", "awaiting-approval"]
|
|
688
|
+
};
|
|
689
|
+
const STATUS_OF = new Map(
|
|
690
|
+
Object.entries(STATUS_ALIASES).flatMap(([status, words]) => words.map((w) => [w, status]))
|
|
691
|
+
);
|
|
692
|
+
function readStatus(value) {
|
|
693
|
+
if (typeof value === "boolean") return value ? "done" : "pending";
|
|
694
|
+
if (typeof value !== "string") return void 0;
|
|
695
|
+
return STATUS_OF.get(value.trim().toLowerCase().replace(/[\s_]+/g, "-"));
|
|
696
|
+
}
|
|
697
|
+
const STATUS_KEYS = ["status", "state", "stage", "phase"];
|
|
698
|
+
const DONE_FLAGS = ["done", "completed", "complete", "checked", "finished", "isDone", "is_done", "isCompleted", "is_completed"];
|
|
699
|
+
const ACTIVE_FLAGS = ["active", "running", "current", "inProgress", "in_progress"];
|
|
700
|
+
const TITLE_KEYS = ["title", "content", "name", "label", "text", "task", "step", "action", "description"];
|
|
701
|
+
const ACTIVE_FORM_KEYS = ["activeForm", "active_form", "activeTitle", "active_title"];
|
|
702
|
+
const DETAIL_KEYS = ["detail", "details", "description", "note", "notes", "summary", "result", "output", "observation", "message", "reason"];
|
|
703
|
+
const START_KEYS = ["startedAt", "started_at", "startTime", "start_time", "startDate", "start_date", "start", "begin"];
|
|
704
|
+
const END_KEYS = ["finishedAt", "finished_at", "completedAt", "completed_at", "endedAt", "ended_at", "endTime", "end_time", "doneAt", "done_at", "end"];
|
|
705
|
+
const SECONDS_KEYS = ["duration", "durationSeconds", "duration_seconds", "durationSec", "elapsed", "seconds", "took"];
|
|
706
|
+
const MS_KEYS = ["durationMs", "duration_ms", "elapsedMs", "elapsed_ms", "tookMs", "took_ms"];
|
|
707
|
+
const SUB_KEYS = ["substeps", "subSteps", "sub_steps", "subtasks", "subTasks", "children", "steps", "todos", "items", "checklist"];
|
|
708
|
+
const LIST_KEYS = ["steps", "todos", "tasks", "items", "checklist", "stages", "phases", "subtasks", "plan"];
|
|
709
|
+
const PAST_KEYS = ["past_steps", "pastSteps", "completed_steps", "completedSteps"];
|
|
710
|
+
const PLAN_TITLE_KEYS = ["title", "name", "heading"];
|
|
711
|
+
const GOAL_KEYS = ["goal", "objective", "task", "input", "query", "request", "description", "explanation", "summary"];
|
|
712
|
+
const UPDATED_KEYS = ["updatedAt", "updated_at", "lastUpdated", "last_updated", "asOf", "as_of", "timestamp"];
|
|
713
|
+
function statusOf(r) {
|
|
714
|
+
const said = readStatus(pick(r, STATUS_KEYS));
|
|
715
|
+
if (said) return said;
|
|
716
|
+
if (r["failed"] === true || r["error"] === true || str(r["error"])) return "failed";
|
|
717
|
+
if (r["skipped"] === true || r["cancelled"] === true || r["canceled"] === true) return "skipped";
|
|
718
|
+
if (r["blocked"] === true) return "blocked";
|
|
719
|
+
const active = ACTIVE_FLAGS.some((key) => r[key] === true);
|
|
720
|
+
for (const key of DONE_FLAGS) {
|
|
721
|
+
if (typeof r[key] === "boolean") return r[key] ? "done" : active ? "in-progress" : "pending";
|
|
722
|
+
}
|
|
723
|
+
return active ? "in-progress" : void 0;
|
|
724
|
+
}
|
|
725
|
+
function fromSubsteps(subs) {
|
|
726
|
+
const has = (status) => subs.some((s) => s.status === status);
|
|
727
|
+
if (subs.every((s) => s.status === "done" || s.status === "skipped")) return has("done") ? "done" : "skipped";
|
|
728
|
+
if (has("in-progress")) return "in-progress";
|
|
729
|
+
if (has("failed")) return "failed";
|
|
730
|
+
if (has("done") || has("skipped")) return "in-progress";
|
|
731
|
+
if (has("blocked")) return "blocked";
|
|
732
|
+
return "pending";
|
|
733
|
+
}
|
|
734
|
+
const LINE = /^\s*(?:(?:[-*+•]|\d+[.)])\s+)?(?:\[([ xX✓✔>~!-])\]\s*)?(.*?)\s*$/;
|
|
735
|
+
const ITEM = /^\s*(?:(?:[-*+•]|\d+[.)])\s+|\[[ xX✓✔>~!-]\])/;
|
|
736
|
+
const BOX = { " ": "pending", x: "done", X: "done", "✓": "done", "✔": "done", ">": "in-progress", "!": "failed", "-": "skipped", "~": "blocked" };
|
|
737
|
+
function checkbox(text) {
|
|
738
|
+
const match = LINE.exec(text);
|
|
739
|
+
const title = match[2];
|
|
740
|
+
if (!title) return null;
|
|
741
|
+
return { title, ...match[1] !== void 0 ? { status: BOX[match[1]] } : {} };
|
|
742
|
+
}
|
|
743
|
+
function parseText(text) {
|
|
744
|
+
const lines = text.split(/\r?\n/).filter((line) => line.trim());
|
|
745
|
+
let title;
|
|
746
|
+
const steps = [];
|
|
747
|
+
for (const [index, line] of lines.entries()) {
|
|
748
|
+
if (!ITEM.test(line)) {
|
|
749
|
+
if (index > 0) return null;
|
|
750
|
+
title = line.trim().replace(/^#{1,6}\s+/, "").replace(/:$/, "") || void 0;
|
|
751
|
+
continue;
|
|
752
|
+
}
|
|
753
|
+
const step = readStep(line, 1);
|
|
754
|
+
if (!step) continue;
|
|
755
|
+
const parent = steps[steps.length - 1];
|
|
756
|
+
if (/^(?:\t|\s{2,})/.test(line) && parent) (parent.substeps ??= []).push(step);
|
|
757
|
+
else steps.push(step);
|
|
758
|
+
}
|
|
759
|
+
return steps.length ? { ...title ? { title } : {}, steps } : null;
|
|
760
|
+
}
|
|
761
|
+
function readStep(raw, depth, forced) {
|
|
762
|
+
if (typeof raw === "string" || typeof raw === "number") {
|
|
763
|
+
const line = checkbox(String(raw));
|
|
764
|
+
return line ? { title: line.title, status: line.status ?? forced ?? "pending" } : null;
|
|
765
|
+
}
|
|
766
|
+
if (Array.isArray(raw)) {
|
|
767
|
+
return readStep(raw[0], depth, forced);
|
|
768
|
+
}
|
|
769
|
+
if (!isObject(raw)) return null;
|
|
770
|
+
const r = raw;
|
|
771
|
+
const titleKey = TITLE_KEYS.find((key) => typeof r[key] === "string" && r[key].trim());
|
|
772
|
+
let title = titleKey ? r[titleKey].trim() : void 0;
|
|
773
|
+
if (!title) return null;
|
|
774
|
+
const subsRaw = depth === 0 ? SUB_KEYS.map((key) => r[key]).find(Array.isArray) : void 0;
|
|
775
|
+
const substeps = subsRaw ? subsRaw.map((item) => readStep(item, 1)).filter(notNull) : [];
|
|
776
|
+
const startedAt = toDate(pick(r, START_KEYS)) ?? void 0;
|
|
777
|
+
const finishedAt = toDate(pick(r, END_KEYS)) ?? void 0;
|
|
778
|
+
const status = statusOf(r) ?? forced ?? (substeps.length ? fromSubsteps(substeps) : finishedAt ? "done" : startedAt ? "in-progress" : "pending");
|
|
779
|
+
const active = str(pick(r, ACTIVE_FORM_KEYS));
|
|
780
|
+
let altTitle;
|
|
781
|
+
if (active && active !== title) {
|
|
782
|
+
if (status === "in-progress") [title, altTitle] = [active, title];
|
|
783
|
+
else altTitle = active;
|
|
784
|
+
}
|
|
785
|
+
const error = str(r["error"]);
|
|
786
|
+
const detail = (status === "failed" ? error : void 0) ?? str(pick(r, DETAIL_KEYS.filter((key) => key !== titleKey)));
|
|
787
|
+
const seconds = num(pick(r, SECONDS_KEYS));
|
|
788
|
+
const ms = num(pick(r, MS_KEYS));
|
|
789
|
+
let duration = seconds ?? (ms !== void 0 ? ms / 1e3 : void 0);
|
|
790
|
+
if (duration === void 0 && startedAt && finishedAt && finishedAt >= startedAt) duration = (finishedAt.getTime() - startedAt.getTime()) / 1e3;
|
|
791
|
+
if (duration !== void 0 && duration < 0) duration = void 0;
|
|
792
|
+
const id = str(pick(r, ["id", "key", "uuid"]));
|
|
793
|
+
return {
|
|
794
|
+
title,
|
|
795
|
+
...altTitle ? { altTitle } : {},
|
|
796
|
+
status,
|
|
797
|
+
...detail ? { detail } : {},
|
|
798
|
+
...startedAt ? { startedAt } : {},
|
|
799
|
+
...finishedAt ? { finishedAt } : {},
|
|
800
|
+
...duration !== void 0 ? { duration } : {},
|
|
801
|
+
...substeps.length ? { substeps } : {},
|
|
802
|
+
...id ? { id } : {}
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
function cap(steps) {
|
|
806
|
+
let left = PLAN_STEPS_SHOWN;
|
|
807
|
+
const kept = [];
|
|
808
|
+
for (const step of steps) {
|
|
809
|
+
if (left <= 0) break;
|
|
810
|
+
left -= 1;
|
|
811
|
+
if (!step.substeps) {
|
|
812
|
+
kept.push(step);
|
|
813
|
+
continue;
|
|
814
|
+
}
|
|
815
|
+
const substeps = step.substeps.slice(0, left);
|
|
816
|
+
left -= substeps.length;
|
|
817
|
+
const copy = { ...step, substeps };
|
|
818
|
+
if (!substeps.length) delete copy.substeps;
|
|
819
|
+
kept.push(copy);
|
|
820
|
+
}
|
|
821
|
+
return kept;
|
|
822
|
+
}
|
|
823
|
+
function readPlan(raw) {
|
|
824
|
+
if (typeof raw === "string") {
|
|
825
|
+
const parsed = parseText(raw);
|
|
826
|
+
return parsed ? { ...parsed.title ? { title: parsed.title } : {}, steps: cap(parsed.steps) } : null;
|
|
827
|
+
}
|
|
828
|
+
if (Array.isArray(raw)) {
|
|
829
|
+
const steps2 = cap(raw.map((item) => readStep(item, 0)).filter(notNull));
|
|
830
|
+
return steps2.length ? { steps: steps2 } : null;
|
|
831
|
+
}
|
|
832
|
+
if (!isObject(raw)) return null;
|
|
833
|
+
const r = raw;
|
|
834
|
+
const title = str(pick(r, PLAN_TITLE_KEYS));
|
|
835
|
+
const goal = str(pick(r, GOAL_KEYS));
|
|
836
|
+
const updatedAt = toDate(pick(r, UPDATED_KEYS)) ?? void 0;
|
|
837
|
+
const meta = { ...title ? { title } : {}, ...goal ? { goal } : {}, ...updatedAt ? { updatedAt } : {}, ...r["timed"] === true ? { timed: true } : {} };
|
|
838
|
+
if (isObject(r["plan"])) {
|
|
839
|
+
const inner = readPlan(r["plan"]);
|
|
840
|
+
return inner ? { ...meta, ...inner } : null;
|
|
841
|
+
}
|
|
842
|
+
const past = PAST_KEYS.map((key) => r[key]).find(Array.isArray) ?? [];
|
|
843
|
+
const list = LIST_KEYS.map((key) => r[key]).find((value) => Array.isArray(value) || typeof value === "string");
|
|
844
|
+
const ahead = Array.isArray(list) ? list.map((item) => readStep(item, 0)).filter(notNull) : typeof list === "string" ? parseText(list)?.steps ?? [] : [];
|
|
845
|
+
const steps = cap([...past.map((item) => readStep(item, 0, "done")).filter(notNull), ...ahead]);
|
|
846
|
+
return steps.length ? { ...meta, steps } : null;
|
|
847
|
+
}
|
|
848
|
+
const allSteps = (data) => data.steps.flatMap((step) => [step, ...step.substeps ?? []]);
|
|
849
|
+
function elapsedOf(data) {
|
|
850
|
+
const steps = allSteps(data);
|
|
851
|
+
const starts = steps.map((s) => s.startedAt?.getTime()).filter(notNull);
|
|
852
|
+
if (starts.length) {
|
|
853
|
+
const ends = steps.flatMap((s) => [s.startedAt?.getTime(), s.finishedAt?.getTime()]).filter(notNull);
|
|
854
|
+
if (data.updatedAt) ends.push(data.updatedAt.getTime());
|
|
855
|
+
const span = (Math.max(...ends) - Math.min(...starts)) / 1e3;
|
|
856
|
+
if (span > 0) return span;
|
|
857
|
+
}
|
|
858
|
+
const sum = data.steps.reduce((total, s) => total + (s.duration ?? (s.substeps ?? []).reduce((t, sub) => t + (sub.duration ?? 0), 0)), 0);
|
|
859
|
+
return sum > 0 ? sum : void 0;
|
|
860
|
+
}
|
|
861
|
+
function planProgress(data) {
|
|
862
|
+
const steps = data.steps;
|
|
863
|
+
const count = (status) => steps.filter((s) => s.status === status).length;
|
|
864
|
+
const total = steps.length;
|
|
865
|
+
const done = count("done");
|
|
866
|
+
const skipped = count("skipped");
|
|
867
|
+
const finished = done + skipped;
|
|
868
|
+
let currentIndex = steps.findIndex((s) => s.status === "in-progress");
|
|
869
|
+
if (currentIndex < 0) currentIndex = steps.findIndex((s) => s.status === "pending");
|
|
870
|
+
if (currentIndex < 0) currentIndex = steps.findIndex((s) => s.status === "blocked");
|
|
871
|
+
const stoppedIndex = currentIndex < 0 ? steps.findIndex((s) => s.status === "failed") : -1;
|
|
872
|
+
const elapsed = elapsedOf(data);
|
|
873
|
+
return {
|
|
874
|
+
total,
|
|
875
|
+
done,
|
|
876
|
+
skipped,
|
|
877
|
+
failed: count("failed"),
|
|
878
|
+
blocked: count("blocked"),
|
|
879
|
+
finished,
|
|
880
|
+
ratio: total ? finished / total : 0,
|
|
881
|
+
...currentIndex >= 0 ? { current: steps[currentIndex], currentIndex } : {},
|
|
882
|
+
...stoppedIndex >= 0 ? { stoppedIndex } : {},
|
|
883
|
+
...elapsed !== void 0 ? { elapsed } : {}
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
const isTimed = (data) => data.timed === true || allSteps(data).some((s) => s.startedAt || s.finishedAt || s.duration !== void 0);
|
|
887
|
+
function durationText(config, seconds) {
|
|
888
|
+
const tag = intlTag(config);
|
|
889
|
+
const unit = (value, name, digits = 0) => new Intl.NumberFormat(tag, { style: "unit", unit: name, unitDisplay: "narrow", maximumFractionDigits: digits }).format(value);
|
|
890
|
+
const s = Math.max(0, seconds);
|
|
891
|
+
if (s < 10 && !Number.isInteger(s)) return unit(s, "second", 1);
|
|
892
|
+
let rest = Math.round(s);
|
|
893
|
+
if (rest < 60) return unit(rest, "second");
|
|
894
|
+
const parts = [];
|
|
895
|
+
let slots = 0;
|
|
896
|
+
for (const [name, size] of [["day", 86400], ["hour", 3600], ["minute", 60], ["second", 1]]) {
|
|
897
|
+
const n2 = Math.floor(rest / size);
|
|
898
|
+
rest -= n2 * size;
|
|
899
|
+
if (n2 > 0) parts.push(unit(n2, name));
|
|
900
|
+
if (n2 > 0 || slots) slots += 1;
|
|
901
|
+
if (slots === 2) break;
|
|
902
|
+
}
|
|
903
|
+
try {
|
|
904
|
+
return new Intl.ListFormat(tag, { type: "unit", style: "narrow" }).format(parts);
|
|
905
|
+
} catch {
|
|
906
|
+
return parts.join(" ");
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
const isoDuration = (seconds) => `PT${Math.round(seconds * 1e3) / 1e3}S`;
|
|
910
|
+
function clockText(config, date, seconds) {
|
|
911
|
+
return new Intl.DateTimeFormat(intlTag(config), { hour: "numeric", minute: "2-digit", ...seconds ? { second: "2-digit" } : {} }).format(date);
|
|
912
|
+
}
|
|
913
|
+
function withinAnHour(data) {
|
|
914
|
+
const times = allSteps(data).flatMap((s) => [s.startedAt?.getTime(), s.finishedAt?.getTime()]).filter(notNull);
|
|
915
|
+
return !times.length || Math.max(...times) - Math.min(...times) < 36e5;
|
|
916
|
+
}
|
|
917
|
+
const n = (config, value) => formatNumber(config, value);
|
|
918
|
+
const percentOf = (part, total) => `${total ? part / total * 100 : 0}%`;
|
|
919
|
+
const FINISHED = /* @__PURE__ */ new Set(["done", "failed", "skipped"]);
|
|
920
|
+
function statusGlyph(status, className = "aparte-plan__glyph") {
|
|
921
|
+
return glyph(STATUS_GLYPHS[status], `aparte-rich-card__glyph ${className}`);
|
|
922
|
+
}
|
|
923
|
+
function titleOf(step) {
|
|
924
|
+
return el(
|
|
925
|
+
"span",
|
|
926
|
+
{ class: "aparte-plan__step-title", attrs: { "data-text": step.title, "data-alt": step.altTitle } },
|
|
927
|
+
el("span", { class: "aparte-plan__step-title-text", text: step.title })
|
|
928
|
+
);
|
|
929
|
+
}
|
|
930
|
+
function statusText(ctx, step) {
|
|
931
|
+
const node = el("span", { class: "aparte-sr-only aparte-plan__status" });
|
|
932
|
+
ctx.bind(node, () => ` (${say(ctx.config, STATUS_WORD[step.status])})`);
|
|
933
|
+
return node;
|
|
934
|
+
}
|
|
935
|
+
const detailOf = (step) => step.detail ? el("div", { class: "aparte-plan__detail", text: step.detail }) : null;
|
|
936
|
+
function widestDuration(config) {
|
|
937
|
+
return [3599, 86340, 604799].map((s) => durationText(config, s)).reduce((a, b) => b.length > a.length ? b : a);
|
|
938
|
+
}
|
|
939
|
+
function durationOf(ctx, step) {
|
|
940
|
+
const shown = step.duration !== void 0 && FINISHED.has(step.status) ? step.duration : void 0;
|
|
941
|
+
const node = el("time", { class: "aparte-plan__duration", attrs: { datetime: shown !== void 0 ? isoDuration(shown) : void 0 } });
|
|
942
|
+
if (shown !== void 0) ctx.bind(node, () => durationText(ctx.config, shown));
|
|
943
|
+
ctx.bind(node, () => widestDuration(ctx.config), "data-reserve");
|
|
944
|
+
return node;
|
|
945
|
+
}
|
|
946
|
+
function head(ctx, data, progress, bar) {
|
|
947
|
+
const heading = data.title ?? data.goal;
|
|
948
|
+
const count = el("span", { class: "aparte-plan__count" });
|
|
949
|
+
ctx.bind(count, () => say(ctx.config, "cardsPlanProgress", { done: n(ctx.config, progress.done), total: n(ctx.config, progress.total) }));
|
|
950
|
+
const note = (kind, key, value) => {
|
|
951
|
+
if (!value) return null;
|
|
952
|
+
const node = el("span", { class: "aparte-plan__note", attrs: { "data-kind": kind } });
|
|
953
|
+
ctx.bind(node, () => say(ctx.config, key, { n: n(ctx.config, value) }));
|
|
954
|
+
return node;
|
|
955
|
+
};
|
|
956
|
+
let elapsed = null;
|
|
957
|
+
if (progress.elapsed !== void 0) {
|
|
958
|
+
const seconds = progress.elapsed;
|
|
959
|
+
const text = el("span", { class: "aparte-plan__elapsed-text" });
|
|
960
|
+
ctx.bind(text, () => say(ctx.config, "cardsPlanElapsed", { time: durationText(ctx.config, seconds) }));
|
|
961
|
+
elapsed = el("span", { class: "aparte-plan__elapsed" }, glyph(CLOCK, "aparte-rich-card__glyph aparte-plan__clock"), text);
|
|
962
|
+
}
|
|
963
|
+
let track = null;
|
|
964
|
+
if (bar) {
|
|
965
|
+
track = el(
|
|
966
|
+
"div",
|
|
967
|
+
{ class: "aparte-plan__bar", attrs: { "aria-hidden": "true", "data-complete": progress.total > 0 && progress.finished === progress.total } },
|
|
968
|
+
el("span", { class: "aparte-plan__bar-done" }),
|
|
969
|
+
el("span", { class: "aparte-plan__bar-skipped" })
|
|
970
|
+
);
|
|
971
|
+
track.style.setProperty("--aparte-plan-done-share", percentOf(progress.done, progress.total));
|
|
972
|
+
track.style.setProperty("--aparte-plan-skipped-share", percentOf(progress.skipped, progress.total));
|
|
973
|
+
}
|
|
974
|
+
return el(
|
|
975
|
+
"div",
|
|
976
|
+
{ class: "aparte-plan__head" },
|
|
977
|
+
heading ? el(
|
|
978
|
+
"div",
|
|
979
|
+
{ class: "aparte-plan__heading" },
|
|
980
|
+
el("div", { class: "aparte-plan__title", text: heading }),
|
|
981
|
+
data.title && data.goal ? el("div", { class: "aparte-plan__goal", text: data.goal }) : null
|
|
982
|
+
) : null,
|
|
983
|
+
el(
|
|
984
|
+
"div",
|
|
985
|
+
{ class: "aparte-plan__meter" },
|
|
986
|
+
count,
|
|
987
|
+
note("skipped", "cardsPlanSkipped", progress.skipped),
|
|
988
|
+
note("failed", "cardsPlanFailed", progress.failed),
|
|
989
|
+
elapsed
|
|
990
|
+
),
|
|
991
|
+
track
|
|
992
|
+
);
|
|
993
|
+
}
|
|
994
|
+
function stepNode(ctx, step, { timed, current = false, sub = false }) {
|
|
995
|
+
const row = el(
|
|
996
|
+
"div",
|
|
997
|
+
{ class: "aparte-plan__row" },
|
|
998
|
+
statusGlyph(step.status),
|
|
999
|
+
el("div", { class: "aparte-plan__text" }, titleOf(step), statusText(ctx, step), detailOf(step)),
|
|
1000
|
+
timed ? durationOf(ctx, step) : null
|
|
1001
|
+
);
|
|
1002
|
+
const node = el("li", {
|
|
1003
|
+
class: `aparte-plan__step${sub ? " aparte-plan__step--sub" : ""}`,
|
|
1004
|
+
attrs: { "data-status": step.status, "aria-current": current ? "step" : void 0, "data-step-id": step.id }
|
|
1005
|
+
}, row);
|
|
1006
|
+
if (step.substeps?.length) {
|
|
1007
|
+
node.append(el(
|
|
1008
|
+
"ol",
|
|
1009
|
+
{ class: "aparte-plan__substeps", attrs: { role: "list" } },
|
|
1010
|
+
...step.substeps.map((s) => stepNode(ctx, s, { timed, sub: true }))
|
|
1011
|
+
));
|
|
1012
|
+
}
|
|
1013
|
+
return node;
|
|
1014
|
+
}
|
|
1015
|
+
function checklist(ctx, data, progress) {
|
|
1016
|
+
const timed = isTimed(data);
|
|
1017
|
+
const list = el(
|
|
1018
|
+
"ol",
|
|
1019
|
+
{ class: "aparte-plan__steps", attrs: { role: "list", "data-timed": timed } },
|
|
1020
|
+
...data.steps.map((step, i) => stepNode(ctx, step, { timed, current: i === progress.currentIndex }))
|
|
1021
|
+
);
|
|
1022
|
+
return [head(ctx, data, progress, true), list];
|
|
1023
|
+
}
|
|
1024
|
+
function timesOf(ctx, step, seconds) {
|
|
1025
|
+
const started = step.startedAt;
|
|
1026
|
+
const start = el("time", { class: "aparte-plan__start", attrs: { datetime: started?.toISOString() } });
|
|
1027
|
+
if (started) ctx.bind(start, () => clockText(ctx.config, started, seconds));
|
|
1028
|
+
return el("div", { class: "aparte-plan__meta" }, start, durationOf(ctx, step));
|
|
1029
|
+
}
|
|
1030
|
+
function timeline(ctx, data, progress) {
|
|
1031
|
+
const timed = isTimed(data);
|
|
1032
|
+
const seconds = withinAnHour(data);
|
|
1033
|
+
const list = el("ol", { class: "aparte-plan__timeline", attrs: { role: "list", "data-timed": timed } });
|
|
1034
|
+
data.steps.forEach((step, i) => {
|
|
1035
|
+
const node = el(
|
|
1036
|
+
"span",
|
|
1037
|
+
{ class: "aparte-plan__node" },
|
|
1038
|
+
glyph(step.status === "in-progress" ? NODE_CURRENT : STATUS_GLYPHS[step.status], "aparte-rich-card__glyph aparte-plan__node-glyph")
|
|
1039
|
+
);
|
|
1040
|
+
const body = el(
|
|
1041
|
+
"div",
|
|
1042
|
+
{ class: "aparte-plan__event-body" },
|
|
1043
|
+
titleOf(step),
|
|
1044
|
+
statusText(ctx, step),
|
|
1045
|
+
timed ? timesOf(ctx, step, seconds) : null,
|
|
1046
|
+
detailOf(step),
|
|
1047
|
+
step.substeps?.length ? el("ol", { class: "aparte-plan__substeps", attrs: { role: "list" } }, ...step.substeps.map((s) => stepNode(ctx, s, { timed: false, sub: true }))) : null
|
|
1048
|
+
);
|
|
1049
|
+
list.append(el("li", {
|
|
1050
|
+
class: "aparte-plan__event",
|
|
1051
|
+
attrs: { "data-status": step.status, "aria-current": i === progress.currentIndex ? "step" : void 0, "data-step-id": step.id }
|
|
1052
|
+
}, node, body));
|
|
1053
|
+
});
|
|
1054
|
+
return [head(ctx, data, progress, false), list];
|
|
1055
|
+
}
|
|
1056
|
+
const SVG = "http://www.w3.org/2000/svg";
|
|
1057
|
+
function ring(progress) {
|
|
1058
|
+
const svg2 = document.createElementNS(SVG, "svg");
|
|
1059
|
+
svg2.setAttribute("viewBox", "0 0 24 24");
|
|
1060
|
+
svg2.setAttribute("class", "aparte-plan__ring-svg");
|
|
1061
|
+
const arc = (cls, share, offset = 0) => {
|
|
1062
|
+
const circle = document.createElementNS(SVG, "circle");
|
|
1063
|
+
for (const [name, value] of [["cx", "12"], ["cy", "12"], ["r", "9.5"], ["pathLength", "100"], ["class", cls]]) circle.setAttribute(name, value);
|
|
1064
|
+
if (share !== void 0) {
|
|
1065
|
+
circle.setAttribute("stroke-dasharray", `${share} 100`);
|
|
1066
|
+
circle.setAttribute("stroke-dashoffset", String(-offset));
|
|
1067
|
+
}
|
|
1068
|
+
return circle;
|
|
1069
|
+
};
|
|
1070
|
+
const done = progress.total ? progress.done / progress.total * 100 : 0;
|
|
1071
|
+
const skipped = progress.total ? progress.skipped / progress.total * 100 : 0;
|
|
1072
|
+
svg2.append(arc("aparte-plan__ring-track"), arc("aparte-plan__ring-done", done), arc("aparte-plan__ring-skipped", skipped, done));
|
|
1073
|
+
return el("span", { class: "aparte-plan__ring", attrs: { "aria-hidden": "true", "data-complete": progress.total > 0 && progress.finished === progress.total } }, svg2);
|
|
1074
|
+
}
|
|
1075
|
+
function compact(ctx, data, progress) {
|
|
1076
|
+
const index = progress.currentIndex ?? progress.stoppedIndex;
|
|
1077
|
+
const focus = index !== void 0 ? data.steps[index] : void 0;
|
|
1078
|
+
const step = el("span", { class: "aparte-plan__compact-step" });
|
|
1079
|
+
const parts = [];
|
|
1080
|
+
if (focus) {
|
|
1081
|
+
ctx.bind(step, () => say(ctx.config, "cardsPlanStepNow", { n: n(ctx.config, index + 1), total: n(ctx.config, progress.total) }));
|
|
1082
|
+
parts.push(step, el("span", { class: "aparte-plan__compact-title", text: focus.title }));
|
|
1083
|
+
} else {
|
|
1084
|
+
ctx.word(step, "cardsPlanAllDone", W.cardsPlanAllDone);
|
|
1085
|
+
const name = data.title ?? data.goal;
|
|
1086
|
+
if (name) parts.push(el("span", { class: "aparte-plan__compact-title", text: name }));
|
|
1087
|
+
parts.push(step);
|
|
1088
|
+
}
|
|
1089
|
+
const percent = el("span", { class: "aparte-plan__percent" });
|
|
1090
|
+
ctx.bind(percent, () => formatPercent(ctx.config, progress.ratio));
|
|
1091
|
+
const status = focus ? focus.status : "done";
|
|
1092
|
+
return [el(
|
|
1093
|
+
"div",
|
|
1094
|
+
{ class: "aparte-plan__compact", attrs: { "data-status": status } },
|
|
1095
|
+
statusGlyph(status, "aparte-plan__glyph aparte-plan__compact-glyph"),
|
|
1096
|
+
el("span", { class: "aparte-plan__compact-text" }, ...parts),
|
|
1097
|
+
ring(progress),
|
|
1098
|
+
percent
|
|
1099
|
+
)];
|
|
1100
|
+
}
|
|
1101
|
+
function render(data, ctx) {
|
|
1102
|
+
const progress = planProgress(data);
|
|
1103
|
+
if (ctx.variant === "timeline") return timeline(ctx, data, progress);
|
|
1104
|
+
if (ctx.variant === "compact") return compact(ctx, data, progress);
|
|
1105
|
+
return checklist(ctx, data, progress);
|
|
1106
|
+
}
|
|
1107
|
+
function skeleton(partial, ctx) {
|
|
1108
|
+
if (partial?.steps?.length) return render(partial, ctx);
|
|
1109
|
+
const block = (cls, kind = "rect") => el("span", { class: `aparte-skeleton aparte-skeleton--${kind} ${cls}`, attrs: { "aria-hidden": "true" } });
|
|
1110
|
+
const heading = partial?.title ?? partial?.goal;
|
|
1111
|
+
if (ctx.variant === "compact") {
|
|
1112
|
+
return [el(
|
|
1113
|
+
"div",
|
|
1114
|
+
{ class: "aparte-plan__compact" },
|
|
1115
|
+
block("aparte-plan__sk-glyph", "circle"),
|
|
1116
|
+
el(
|
|
1117
|
+
"span",
|
|
1118
|
+
{ class: "aparte-plan__compact-text" },
|
|
1119
|
+
heading ? el("span", { class: "aparte-plan__compact-title", text: heading }) : block("aparte-plan__sk-line", "text")
|
|
1120
|
+
),
|
|
1121
|
+
block("aparte-plan__sk-ring", "circle")
|
|
1122
|
+
)];
|
|
1123
|
+
}
|
|
1124
|
+
const top = el(
|
|
1125
|
+
"div",
|
|
1126
|
+
{ class: "aparte-plan__head" },
|
|
1127
|
+
heading ? el("div", { class: "aparte-plan__heading" }, el("div", { class: "aparte-plan__title", text: heading })) : block("aparte-plan__sk-title", "text"),
|
|
1128
|
+
el("div", { class: "aparte-plan__meter" }, block("aparte-plan__sk-count", "text")),
|
|
1129
|
+
ctx.variant === "checklist" ? el("div", { class: "aparte-plan__bar", attrs: { "aria-hidden": "true" } }) : null
|
|
1130
|
+
);
|
|
1131
|
+
const rows = [0, 1, 2, 3];
|
|
1132
|
+
if (ctx.variant === "timeline") {
|
|
1133
|
+
return [top, el("ol", { class: "aparte-plan__timeline", attrs: { "aria-hidden": "true" } }, ...rows.map(() => el(
|
|
1134
|
+
"li",
|
|
1135
|
+
{ class: "aparte-plan__event", attrs: { "data-status": "pending" } },
|
|
1136
|
+
el("span", { class: "aparte-plan__node" }, block("aparte-plan__sk-node", "circle")),
|
|
1137
|
+
el("div", { class: "aparte-plan__event-body" }, block("aparte-plan__sk-line", "text"), block("aparte-plan__sk-meta", "text"))
|
|
1138
|
+
)))];
|
|
1139
|
+
}
|
|
1140
|
+
return [top, el("ol", { class: "aparte-plan__steps", attrs: { "aria-hidden": "true" } }, ...rows.map(() => el(
|
|
1141
|
+
"li",
|
|
1142
|
+
{ class: "aparte-plan__step" },
|
|
1143
|
+
el("div", { class: "aparte-plan__row" }, block("aparte-plan__sk-glyph", "circle"), block("aparte-plan__sk-line", "text"))
|
|
1144
|
+
)))];
|
|
1145
|
+
}
|
|
1146
|
+
function cardLabel(data, ctx) {
|
|
1147
|
+
return data?.title ?? data?.goal ?? say(ctx.config, "cardsPlanTitle");
|
|
1148
|
+
}
|
|
1149
|
+
const MARK = { done: "[x]", pending: "[ ]", "in-progress": "[>]", failed: "[!]", skipped: "[-]", blocked: "[~]" };
|
|
1150
|
+
const clip = (text) => text.length > 120 ? `${text.slice(0, 119).trimEnd()}…` : text;
|
|
1151
|
+
function fallback(data, ctx) {
|
|
1152
|
+
const progress = planProgress(data);
|
|
1153
|
+
const summary = say(ctx.config, "cardsPlanSummary", { done: n(ctx.config, progress.done), total: n(ctx.config, progress.total) });
|
|
1154
|
+
const now = progress.current ? ` — ${say(ctx.config, "cardsPlanNow", { step: progress.current.title })}` : "";
|
|
1155
|
+
const line = (step, indent) => {
|
|
1156
|
+
const why = (step.status === "failed" || step.status === "blocked") && step.detail ? ` — ${clip(step.detail)}` : "";
|
|
1157
|
+
return `${indent}- ${MARK[step.status]} ${step.title}${why}`;
|
|
1158
|
+
};
|
|
1159
|
+
const lines = data.steps.flatMap((step) => [line(step, ""), ...(step.substeps ?? []).map((sub) => line(sub, " "))]);
|
|
1160
|
+
return [`${cardLabel(data, ctx)}: ${summary}${now}`, ...lines].join("\n");
|
|
1161
|
+
}
|
|
1162
|
+
const planCard = defineCard({
|
|
1163
|
+
name: "plan",
|
|
1164
|
+
variants: ["checklist", "timeline", "compact"],
|
|
1165
|
+
styles: planStyles,
|
|
1166
|
+
icon: PLAN,
|
|
1167
|
+
normalize: readPlan,
|
|
1168
|
+
render,
|
|
1169
|
+
skeleton,
|
|
1170
|
+
label: cardLabel,
|
|
1171
|
+
fallback,
|
|
1172
|
+
partial: (input) => {
|
|
1173
|
+
const plan = readPlan(input);
|
|
1174
|
+
if (plan) return plan;
|
|
1175
|
+
const about = str(input["title"] ?? input["goal"] ?? input["task"] ?? input["objective"] ?? input["query"]);
|
|
1176
|
+
return about ? { title: about } : null;
|
|
1177
|
+
}
|
|
1178
|
+
});
|
|
1179
|
+
function setupPlanCard(options = {}) {
|
|
1180
|
+
return planCard.setup(options);
|
|
1181
|
+
}
|
|
1182
|
+
export {
|
|
1183
|
+
PLAN_STEPS_SHOWN,
|
|
1184
|
+
durationText,
|
|
1185
|
+
planCard,
|
|
1186
|
+
planProgress,
|
|
1187
|
+
readPlan,
|
|
1188
|
+
readStatus,
|
|
1189
|
+
setupPlanCard
|
|
1190
|
+
};
|
|
1191
|
+
//# sourceMappingURL=plan.js.map
|