@miadi/stateloom-web 0.3.3 → 0.3.4
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/package.json +1 -1
- package/server/.next/BUILD_ID +1 -1
- package/server/.next/build-manifest.json +5 -5
- package/server/.next/server/app/_global-error/page/build-manifest.json +3 -3
- package/server/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/server/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/server/.next/server/app/_global-error.html +2 -2
- package/server/.next/server/app/_global-error.rsc +1 -1
- package/server/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/server/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/server/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/server/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/server/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/server/.next/server/app/_not-found/page/build-manifest.json +3 -3
- package/server/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/server/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/server/.next/server/app/_not-found.html +1 -1
- package/server/.next/server/app/_not-found.rsc +2 -2
- package/server/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/server/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/server/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
- package/server/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/server/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/server/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/server/.next/server/app/api/docs/route.js.nft.json +1 -1
- package/server/.next/server/app/api/file/route.js.nft.json +1 -1
- package/server/.next/server/app/api/marks/route.js.nft.json +1 -1
- package/server/.next/server/app/api/rispec/route.js.nft.json +1 -1
- package/server/.next/server/app/api/watch/route.js.nft.json +1 -1
- package/server/.next/server/app/index.html +1 -1
- package/server/.next/server/app/index.rsc +3 -3
- package/server/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/server/.next/server/app/index.segments/_full.segment.rsc +3 -3
- package/server/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/server/.next/server/app/index.segments/_index.segment.rsc +2 -2
- package/server/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/server/.next/server/app/page/build-manifest.json +3 -3
- package/server/.next/server/app/page.js.nft.json +1 -1
- package/server/.next/server/app/page_client-reference-manifest.js +1 -1
- package/server/.next/server/chunks/ssr/{2374f_next_dist_0c464f98._.js → 2374f_next_dist_491bc9ca._.js} +2 -2
- package/server/.next/server/chunks/ssr/{[root-of-the-server]__385c85a7._.js → [root-of-the-server]__0d719a8a._.js} +2 -2
- package/server/.next/server/chunks/ssr/[root-of-the-server]__199d50b6._.js +3 -0
- package/server/.next/server/chunks/ssr/[root-of-the-server]__5c28bab6._.js +3 -0
- package/server/.next/server/chunks/ssr/[root-of-the-server]__edbb995c._.js +6 -6
- package/server/.next/server/middleware-build-manifest.js +3 -3
- package/server/.next/server/pages/404.html +1 -1
- package/server/.next/server/pages/500.html +2 -2
- package/server/.next/static/chunks/33df24ad2d3a04e7.js +1 -0
- package/server/.next/static/chunks/6052c4bfdd66ef5c.css +2 -0
- package/server/.next/static/chunks/b0af2862e9bb3e18.js +14 -0
- package/server/.next/static/chunks/b71bf89a3bbb72e1.js +5 -0
- package/server/.next/static/chunks/{turbopack-b3cae9131ae5086f.js → turbopack-3a774f5a5a8a9023.js} +1 -1
- package/server/package-lock.json +2 -2
- package/server/package.json +1 -1
- package/server/src/app/globals.css +40 -2
- package/server/src/app/page.tsx +8 -6
- package/server/src/components/ChromeIcon.tsx +106 -0
- package/server/src/components/PropertiesPanel.tsx +3 -2
- package/server/src/components/Toolbar.tsx +37 -20
- package/server/src/components/erd/ErdWorkspace.tsx +2 -1
- package/server/src/components/sequence/SequenceWorkspace.tsx +10 -1
- package/server/src/components/stage/CompanionLayer.tsx +41 -6
- package/server/src/components/stage/StageLayer.tsx +206 -23
- package/server/src/components/stage/StageProvider.tsx +146 -25
- package/server/src/components/system/SystemWorkspace.tsx +26 -12
- package/server/src/lib/markRequest.ts +24 -0
- package/server/src/lib/useNarrow.ts +30 -0
- package/server/stateloom-web.json +2 -2
- package/server/tsconfig.tsbuildinfo +1 -1
- package/server/.next/server/chunks/ssr/[root-of-the-server]__a593e8b6._.js +0 -3
- package/server/.next/server/chunks/ssr/[root-of-the-server]__f25b51be._.js +0 -3
- package/server/.next/static/chunks/59a1ec89a1dc7e34.js +0 -14
- package/server/.next/static/chunks/662d1b0ce457c16b.js +0 -1
- package/server/.next/static/chunks/8f45e56f40c207bf.css +0 -2
- package/server/.next/static/chunks/96818f5620f039a1.js +0 -5
- /package/server/.next/static/{CZ4wRF1efsHnxjkcKHITP → ZoijkpoWD861NG-cIGJrO}/_buildManifest.js +0 -0
- /package/server/.next/static/{CZ4wRF1efsHnxjkcKHITP → ZoijkpoWD861NG-cIGJrO}/_clientMiddlewareManifest.json +0 -0
- /package/server/.next/static/{CZ4wRF1efsHnxjkcKHITP → ZoijkpoWD861NG-cIGJrO}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The loom's chrome icons — the toolbar, the phone's tab bar, the context menu and the
|
|
3
|
+
* delete buttons — drawn as outlines in the current text colour (lane U, finding U6).
|
|
4
|
+
*
|
|
5
|
+
* The typed symbols they replace (📂 💾 🔄 📥 🖼 ⚡ 📜 🗑 🎛 ⚙ ✏️ ↩ ↪ ↗) are colour emoji
|
|
6
|
+
* on iOS: saturated, larger than the words beside them, and the first thing the eye lands
|
|
7
|
+
* on around a board whose stage, marks and dock are already drawn. Same style as
|
|
8
|
+
* IssueIcon and KindIcon: a 24-unit box, a 1.6 stroke, round ends.
|
|
9
|
+
*/
|
|
10
|
+
export type ChromeIconName =
|
|
11
|
+
| "undo"
|
|
12
|
+
| "redo"
|
|
13
|
+
| "arrange"
|
|
14
|
+
| "draw"
|
|
15
|
+
| "open"
|
|
16
|
+
| "save"
|
|
17
|
+
| "reload"
|
|
18
|
+
| "download"
|
|
19
|
+
| "image"
|
|
20
|
+
| "plus"
|
|
21
|
+
| "check"
|
|
22
|
+
| "bolt"
|
|
23
|
+
| "scroll"
|
|
24
|
+
| "code"
|
|
25
|
+
| "trash"
|
|
26
|
+
| "sliders"
|
|
27
|
+
| "gear";
|
|
28
|
+
|
|
29
|
+
export default function ChromeIcon({ name, size = 14 }: { name: ChromeIconName; size?: number }) {
|
|
30
|
+
return (
|
|
31
|
+
<svg
|
|
32
|
+
width={size}
|
|
33
|
+
height={size}
|
|
34
|
+
viewBox="0 0 24 24"
|
|
35
|
+
fill="none"
|
|
36
|
+
stroke="currentColor"
|
|
37
|
+
strokeWidth="1.6"
|
|
38
|
+
strokeLinecap="round"
|
|
39
|
+
strokeLinejoin="round"
|
|
40
|
+
aria-hidden="true"
|
|
41
|
+
focusable="false"
|
|
42
|
+
className="inline-block shrink-0 align-[-2px]"
|
|
43
|
+
>
|
|
44
|
+
{name === "undo" && <path d="M9 7 4.5 11.5 9 16M5 11.5h9.5a5 5 0 0 1 0 10H12" />}
|
|
45
|
+
{name === "redo" && <path d="M15 7l4.5 4.5L15 16M19 11.5H9.5a5 5 0 0 0 0 10H12" />}
|
|
46
|
+
{name === "arrange" && (
|
|
47
|
+
// Three boxes in layers.
|
|
48
|
+
<>
|
|
49
|
+
<rect x="8.5" y="3" width="7" height="5" rx="1.5" />
|
|
50
|
+
<rect x="3" y="16" width="7" height="5" rx="1.5" />
|
|
51
|
+
<rect x="14" y="16" width="7" height="5" rx="1.5" />
|
|
52
|
+
<path d="M12 8v4M6.5 16v-2a2 2 0 0 1 2-2h7a2 2 0 0 1 2 2v2" />
|
|
53
|
+
</>
|
|
54
|
+
)}
|
|
55
|
+
{name === "draw" && (
|
|
56
|
+
// An arrow drawn from one state to another.
|
|
57
|
+
<>
|
|
58
|
+
<circle cx="5.5" cy="18.5" r="2" />
|
|
59
|
+
<path d="M7 17 18.5 5.5M11.5 5.5h7v7" />
|
|
60
|
+
</>
|
|
61
|
+
)}
|
|
62
|
+
{name === "open" && <path d="M3.5 7.5V18a1.5 1.5 0 0 0 1.5 1.5h14a1.5 1.5 0 0 0 1.5-1.5V9.5A1.5 1.5 0 0 0 19 8h-7l-2-2.5H5A1.5 1.5 0 0 0 3.5 7z" />}
|
|
63
|
+
{name === "save" && (
|
|
64
|
+
<>
|
|
65
|
+
<path d="M5 4h11l3.5 3.5V19a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1z" />
|
|
66
|
+
<path d="M8 4v5h7V4M8 20v-6h8v6" />
|
|
67
|
+
</>
|
|
68
|
+
)}
|
|
69
|
+
{name === "reload" && <path d="M19.5 12a7.5 7.5 0 1 1-2.2-5.3M19.5 4v4.5H15" />}
|
|
70
|
+
{name === "download" && <path d="M12 4v11M7.5 10.5 12 15l4.5-4.5M4.5 19.5h15" />}
|
|
71
|
+
{name === "image" && (
|
|
72
|
+
<>
|
|
73
|
+
<rect x="3.5" y="5" width="17" height="14" rx="1.5" />
|
|
74
|
+
<circle cx="9" cy="10" r="1.6" />
|
|
75
|
+
<path d="m4 17.5 5-5 4 4 2.5-2.5 4.5 4.5" />
|
|
76
|
+
</>
|
|
77
|
+
)}
|
|
78
|
+
{name === "plus" && <path d="M12 5v14M5 12h14" />}
|
|
79
|
+
{name === "check" && <path d="m5 12.5 4.5 4.5L19 7.5" />}
|
|
80
|
+
{name === "bolt" && <path d="M13 3 5.5 13.5h5.5L10 21l8-11h-5.5z" />}
|
|
81
|
+
{name === "scroll" && (
|
|
82
|
+
// A page with lines: a written specification.
|
|
83
|
+
<>
|
|
84
|
+
<path d="M6.5 3.5h8l4 4v12a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1v-15a1 1 0 0 1 1-1z" />
|
|
85
|
+
<path d="M14.5 3.5v4h4M8.5 12h7M8.5 15.5h7M8.5 9h3" />
|
|
86
|
+
</>
|
|
87
|
+
)}
|
|
88
|
+
{name === "code" && <path d="m8.5 7.5-5 4.5 5 4.5M15.5 7.5l5 4.5-5 4.5M13.5 5l-3 14" />}
|
|
89
|
+
{name === "trash" && <path d="M4.5 6.5h15M9.5 6.5V4.5h5v2M6.5 6.5l.9 12.6a1.5 1.5 0 0 0 1.5 1.4h6.2a1.5 1.5 0 0 0 1.5-1.4l.9-12.6M10 10.5v6M14 10.5v6" />}
|
|
90
|
+
{name === "sliders" && (
|
|
91
|
+
<>
|
|
92
|
+
<path d="M5 4v16M12 4v16M19 4v16" />
|
|
93
|
+
<circle cx="5" cy="9" r="2" fill="currentColor" stroke="none" />
|
|
94
|
+
<circle cx="12" cy="15" r="2" fill="currentColor" stroke="none" />
|
|
95
|
+
<circle cx="19" cy="7.5" r="2" fill="currentColor" stroke="none" />
|
|
96
|
+
</>
|
|
97
|
+
)}
|
|
98
|
+
{name === "gear" && (
|
|
99
|
+
<>
|
|
100
|
+
<circle cx="12" cy="12" r="3" />
|
|
101
|
+
<path d="M12 3.5v2.2M12 18.3v2.2M3.5 12h2.2M18.3 12h2.2M6 6l1.6 1.6M16.4 16.4 18 18M6 18l1.6-1.6M16.4 7.6 18 6" />
|
|
102
|
+
</>
|
|
103
|
+
)}
|
|
104
|
+
</svg>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useDesignerStore, collectAllStates, collectEventIds } from "@/store/useDesignerStore";
|
|
4
|
+
import ChromeIcon from "@/components/ChromeIcon";
|
|
4
5
|
import type { StateDef, ActionDef } from "@/types/definition";
|
|
5
6
|
|
|
6
7
|
function ActionEditor({ label, actions, onChange }: { label: string; actions: ActionDef[]; onChange: (actions: ActionDef[]) => void }) {
|
|
@@ -175,7 +176,7 @@ export default function PropertiesPanel() {
|
|
|
175
176
|
onClick={() => removeState(selection.id!)}
|
|
176
177
|
className="text-red-500 hover:text-red-400 text-xs mt-2"
|
|
177
178
|
>
|
|
178
|
-
|
|
179
|
+
<ChromeIcon name="trash" /> Delete State
|
|
179
180
|
</button>
|
|
180
181
|
</div>
|
|
181
182
|
);
|
|
@@ -235,7 +236,7 @@ export default function PropertiesPanel() {
|
|
|
235
236
|
onClick={() => removeTransition(stateName, idx)}
|
|
236
237
|
className="text-red-500 hover:text-red-400 text-xs"
|
|
237
238
|
>
|
|
238
|
-
|
|
239
|
+
<ChromeIcon name="trash" /> Delete Transition
|
|
239
240
|
</button>
|
|
240
241
|
</div>
|
|
241
242
|
);
|
|
@@ -9,7 +9,9 @@ import {
|
|
|
9
9
|
type ExportFormat,
|
|
10
10
|
} from "@/lib/exportImage";
|
|
11
11
|
import type { StateDef } from "@/types/definition";
|
|
12
|
+
import ChromeIcon from "./ChromeIcon";
|
|
12
13
|
import DocSwitcher from "./DocSwitcher";
|
|
14
|
+
import IssueIcon from "./IssueIcon";
|
|
13
15
|
import PresenceChips from "./PresenceChips";
|
|
14
16
|
import { docQuery, useRequestedDoc } from "@/lib/docParam";
|
|
15
17
|
|
|
@@ -40,7 +42,7 @@ export default function Toolbar() {
|
|
|
40
42
|
const applyRemote = useDesignerStore((s) => s.applyRemote);
|
|
41
43
|
|
|
42
44
|
// Save and reload must follow the SAME document the page is viewing —
|
|
43
|
-
// without this,
|
|
45
|
+
// without this, Disk on a switched document would silently write the default
|
|
44
46
|
// file (the exact mismatch the allowlist exists to prevent).
|
|
45
47
|
const requested = useRequestedDoc();
|
|
46
48
|
|
|
@@ -219,7 +221,9 @@ export default function Toolbar() {
|
|
|
219
221
|
<span className="text-[10px] text-amber-400" title={remoteMessage ?? ""}>↻ remote changed</span>
|
|
220
222
|
)}
|
|
221
223
|
{remoteStatus === "error" && (
|
|
222
|
-
<span className="text-[10px] text-red-400" title={remoteMessage ?? ""}
|
|
224
|
+
<span className="inline-flex items-center gap-0.5 text-[10px] text-red-400" title={remoteMessage ?? ""}>
|
|
225
|
+
<IssueIcon size={11} /> disk error
|
|
226
|
+
</span>
|
|
223
227
|
)}
|
|
224
228
|
{remoteStatus === "idle" && (
|
|
225
229
|
<span className="text-[10px] text-gray-600" title={remoteMessage ?? "No file bound"}>○ no disk</span>
|
|
@@ -239,16 +243,18 @@ export default function Toolbar() {
|
|
|
239
243
|
disabled={undoStack.length === 0}
|
|
240
244
|
className="toolbar-btn disabled:opacity-30"
|
|
241
245
|
title="Undo (Ctrl+Z)"
|
|
246
|
+
aria-label="Undo"
|
|
242
247
|
>
|
|
243
|
-
|
|
248
|
+
<ChromeIcon name="undo" />
|
|
244
249
|
</button>
|
|
245
250
|
<button
|
|
246
251
|
onClick={redo}
|
|
247
252
|
disabled={redoStack.length === 0}
|
|
248
253
|
className="toolbar-btn disabled:opacity-30"
|
|
249
254
|
title="Redo (Ctrl+Y)"
|
|
255
|
+
aria-label="Redo"
|
|
250
256
|
>
|
|
251
|
-
|
|
257
|
+
<ChromeIcon name="redo" />
|
|
252
258
|
</button>
|
|
253
259
|
|
|
254
260
|
<div className="w-px h-4 bg-gray-700" />
|
|
@@ -259,7 +265,7 @@ export default function Toolbar() {
|
|
|
259
265
|
className="toolbar-btn"
|
|
260
266
|
title="Arrange states into layers (auto-layout)"
|
|
261
267
|
>
|
|
262
|
-
|
|
268
|
+
<ChromeIcon name="arrange" /> Arrange
|
|
263
269
|
</button>
|
|
264
270
|
|
|
265
271
|
<div className="w-px h-4 bg-gray-700" />
|
|
@@ -270,14 +276,14 @@ export default function Toolbar() {
|
|
|
270
276
|
className={`toolbar-btn ${drawMode === "transition" ? "!bg-amber-700 !text-white" : ""}`}
|
|
271
277
|
title={drawMode === "transition" ? "Exit draw mode (Esc)" : "Draw transition mode"}
|
|
272
278
|
>
|
|
273
|
-
{drawMode === "transition" ? "
|
|
279
|
+
<ChromeIcon name="draw" /> {drawMode === "transition" ? "Drawing" : "Draw"}
|
|
274
280
|
</button>
|
|
275
281
|
|
|
276
282
|
<div className="w-px h-4 bg-gray-700" />
|
|
277
283
|
|
|
278
284
|
{/* File ops */}
|
|
279
|
-
<button onClick={handleOpen} className="toolbar-btn" title="Open local .smdf.json (browser)">
|
|
280
|
-
|
|
285
|
+
<button onClick={handleOpen} className="toolbar-btn" title="Open local .smdf.json (browser)" aria-label="Open a local file">
|
|
286
|
+
<ChromeIcon name="open" />
|
|
281
287
|
</button>
|
|
282
288
|
<button
|
|
283
289
|
onClick={handleSaveDisk}
|
|
@@ -285,17 +291,18 @@ export default function Toolbar() {
|
|
|
285
291
|
className="toolbar-btn disabled:opacity-40"
|
|
286
292
|
title="Save to disk (STATELOOM_PROJECT_FILE)"
|
|
287
293
|
>
|
|
288
|
-
|
|
294
|
+
<ChromeIcon name="save" /> Disk
|
|
289
295
|
</button>
|
|
290
296
|
<button
|
|
291
297
|
onClick={handleReloadFromDisk}
|
|
292
298
|
className="toolbar-btn"
|
|
293
299
|
title="Reload from disk"
|
|
300
|
+
aria-label="Reload from disk"
|
|
294
301
|
>
|
|
295
|
-
|
|
302
|
+
<ChromeIcon name="reload" />
|
|
296
303
|
</button>
|
|
297
|
-
<button onClick={handleDownload} className="toolbar-btn" title="Download as file">
|
|
298
|
-
|
|
304
|
+
<button onClick={handleDownload} className="toolbar-btn" title="Download as file" aria-label="Download as file">
|
|
305
|
+
<ChromeIcon name="download" />
|
|
299
306
|
</button>
|
|
300
307
|
|
|
301
308
|
{/* Export the board — pictures from the canvas as it stands, mermaid and
|
|
@@ -322,7 +329,13 @@ export default function Toolbar() {
|
|
|
322
329
|
className={`toolbar-btn disabled:opacity-40 ${exportError ? "!text-red-400" : ""}`}
|
|
323
330
|
title={exportError ?? `Export the board as ${FORMAT_LABEL[imageFormat]}`}
|
|
324
331
|
>
|
|
325
|
-
{exporting ?
|
|
332
|
+
{exporting ? (
|
|
333
|
+
"…"
|
|
334
|
+
) : (
|
|
335
|
+
<>
|
|
336
|
+
<ChromeIcon name="image" /> Export
|
|
337
|
+
</>
|
|
338
|
+
)}
|
|
326
339
|
</button>
|
|
327
340
|
{exported && !exportError && (
|
|
328
341
|
<span className="text-[10px] text-emerald-500 truncate max-w-[40vw] md:max-w-[16rem]" title={exported}>
|
|
@@ -346,12 +359,16 @@ export default function Toolbar() {
|
|
|
346
359
|
if (e.key === "Escape") setAddingState(false);
|
|
347
360
|
}}
|
|
348
361
|
/>
|
|
349
|
-
<button onClick={handleAddState} className="toolbar-btn-sm"
|
|
350
|
-
|
|
362
|
+
<button onClick={handleAddState} className="toolbar-btn-sm" aria-label="Add the state">
|
|
363
|
+
<ChromeIcon name="check" />
|
|
364
|
+
</button>
|
|
365
|
+
<button onClick={() => setAddingState(false)} className="toolbar-btn-sm" aria-label="Cancel">
|
|
366
|
+
✕
|
|
367
|
+
</button>
|
|
351
368
|
</div>
|
|
352
369
|
) : (
|
|
353
370
|
<button onClick={() => setAddingState(true)} className="toolbar-btn">
|
|
354
|
-
|
|
371
|
+
<ChromeIcon name="plus" /> State
|
|
355
372
|
</button>
|
|
356
373
|
)}
|
|
357
374
|
|
|
@@ -359,7 +376,7 @@ export default function Toolbar() {
|
|
|
359
376
|
|
|
360
377
|
{/* Validate & Generate */}
|
|
361
378
|
<button onClick={() => validate()} className="toolbar-btn">
|
|
362
|
-
|
|
379
|
+
<ChromeIcon name="check" /> Validate
|
|
363
380
|
</button>
|
|
364
381
|
<select
|
|
365
382
|
value={generateLang}
|
|
@@ -370,16 +387,16 @@ export default function Toolbar() {
|
|
|
370
387
|
<option value="typescript">TypeScript</option>
|
|
371
388
|
</select>
|
|
372
389
|
<button onClick={handleGenerate} className="toolbar-btn">
|
|
373
|
-
|
|
390
|
+
<ChromeIcon name="bolt" /> Generate
|
|
374
391
|
</button>
|
|
375
392
|
<button onClick={handleGenerateRispec} className="toolbar-btn" title="Generate RISE rispec markdown">
|
|
376
|
-
|
|
393
|
+
<ChromeIcon name="scroll" /> RISE
|
|
377
394
|
</button>
|
|
378
395
|
<button
|
|
379
396
|
onClick={() => setShowCodePreview(!showCodePreview)}
|
|
380
397
|
className={`toolbar-btn ${showCodePreview ? "text-blue-400" : ""}`}
|
|
381
398
|
>
|
|
382
|
-
|
|
399
|
+
<ChromeIcon name="code" /> Code
|
|
383
400
|
</button>
|
|
384
401
|
</div>
|
|
385
402
|
</div>
|
|
@@ -55,6 +55,7 @@ import {
|
|
|
55
55
|
normalizeNotes,
|
|
56
56
|
} from "@miadi/stateloom-protocol";
|
|
57
57
|
import BoardBoundary from "@/components/BoardBoundary";
|
|
58
|
+
import ChromeIcon from "@/components/ChromeIcon";
|
|
58
59
|
import DocSwitcher from "@/components/DocSwitcher";
|
|
59
60
|
import IssueIcon from "@/components/IssueIcon";
|
|
60
61
|
import LockIcon from "@/components/LockIcon";
|
|
@@ -724,7 +725,7 @@ export default function ErdWorkspace() {
|
|
|
724
725
|
setSelection(null);
|
|
725
726
|
}}
|
|
726
727
|
>
|
|
727
|
-
|
|
728
|
+
<ChromeIcon name="trash" /> Delete {selected.name}
|
|
728
729
|
</button>
|
|
729
730
|
</>
|
|
730
731
|
)}
|
|
@@ -218,6 +218,15 @@ export default function SequenceWorkspace() {
|
|
|
218
218
|
const view = useMemo(() => (def ? drawableSequence(def) : null), [def]);
|
|
219
219
|
const layout = useMemo(() => (view ? sequenceLayout(view) : null), [view]);
|
|
220
220
|
const stageBoxes = useMemo(() => (view && layout ? safely(() => sequenceElementBoxes(view, layout), []) : []), [view, layout]);
|
|
221
|
+
// A message is named on the marks by its number and its words, as the board shows it.
|
|
222
|
+
const stageLabels = useMemo(() => {
|
|
223
|
+
const out: Record<string, string> = {};
|
|
224
|
+
for (const { ref, message } of view ? safely(() => allMessages(view), []) : []) {
|
|
225
|
+
const words = message?.label ? `${ref}. ${message.label}` : `message ${ref}`;
|
|
226
|
+
out[`message:${ref}`] = words.length > 48 ? `${words.slice(0, 47)}…` : words;
|
|
227
|
+
}
|
|
228
|
+
return out;
|
|
229
|
+
}, [view]);
|
|
221
230
|
const stageFocus = useUrlParam("focus");
|
|
222
231
|
// A malformed document is reported as one issue, not a crash.
|
|
223
232
|
const problems = useMemo<SqdValidationError[]>(() => {
|
|
@@ -586,7 +595,7 @@ export default function SequenceWorkspace() {
|
|
|
586
595
|
{/* The stage (Spec 83). In the list view nothing is drawn to light, so a
|
|
587
596
|
step that lights something here brings the diagram back. */}
|
|
588
597
|
{def && layout && mode === "diagram" && (
|
|
589
|
-
<StageLayer docPath={docPath || null} kind="sequence" viewport={viewport} setViewport={setViewport} boxes={stageBoxes} boardRef={boardRef} focus={stageFocus} />
|
|
598
|
+
<StageLayer docPath={docPath || null} kind="sequence" viewport={viewport} setViewport={setViewport} boxes={stageBoxes} boardRef={boardRef} focus={stageFocus} labels={stageLabels} />
|
|
590
599
|
)}
|
|
591
600
|
{def && layout && mode === "list" && <StageReveal docPath={docPath} onReveal={() => setMode("diagram")} />}
|
|
592
601
|
|
|
@@ -19,10 +19,17 @@
|
|
|
19
19
|
* - once the person presses ▶ or ■, the waiting sequence is over: each reply plays when
|
|
20
20
|
* they choose it.
|
|
21
21
|
*
|
|
22
|
+
* The conversation holds the person's marks on the episode's drawings too (the service
|
|
23
|
+
* reads their sidecars): each mark with its thread, and tapping one opens its drawing at
|
|
24
|
+
* that mark's thread. On a phone the closed dock is the bar at the top of the sheet at the
|
|
25
|
+
* bottom of the board, and the open conversation takes the caption's place in that sheet;
|
|
26
|
+
* it and a mark's card take turns.
|
|
27
|
+
*
|
|
22
28
|
* Everything reaches the service through `/api/companion/` (this designer's proxy to
|
|
23
29
|
* `STATELOOM_COMPANION_URL`); a reply is never posted from here.
|
|
24
30
|
*/
|
|
25
31
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
32
|
+
import { createPortal } from "react-dom";
|
|
26
33
|
import {
|
|
27
34
|
browserVoice,
|
|
28
35
|
createCompanionThread,
|
|
@@ -35,10 +42,11 @@ import {
|
|
|
35
42
|
type TakeRecorder,
|
|
36
43
|
type ThreadSnapshot,
|
|
37
44
|
} from "@miadi/stateloom-react";
|
|
38
|
-
import { CompanionDock } from "@miadi/stateloom-canvas";
|
|
45
|
+
import { CompanionDock, type DockMark } from "@miadi/stateloom-canvas";
|
|
39
46
|
import { marksWaitingOn, readMarksFile } from "@miadi/stateloom-protocol";
|
|
40
47
|
import { requestedDoc } from "@/lib/docParam";
|
|
41
|
-
import { urlParam } from "@/lib/systemParam";
|
|
48
|
+
import { openInSystem, urlParam } from "@/lib/systemParam";
|
|
49
|
+
import { requestMark } from "@/lib/markRequest";
|
|
42
50
|
import type { StageValue } from "./StageProvider";
|
|
43
51
|
|
|
44
52
|
const BASE = "/api/companion/";
|
|
@@ -316,7 +324,7 @@ export default function CompanionLayer({ stage, defaultEpisode }: { stage: Stage
|
|
|
316
324
|
const reply = threadRef.current?.getSnapshot().items.find((i): i is CompanionReply => i.kind === "reply" && i.id === id);
|
|
317
325
|
if (!reply) return;
|
|
318
326
|
if (busy(recRef.current.state)) {
|
|
319
|
-
setNotice(`Send your take first. Then press
|
|
327
|
+
setNotice(`Send your take first. Then press play to hear ${WHO}.`);
|
|
320
328
|
return;
|
|
321
329
|
}
|
|
322
330
|
queue.current = [];
|
|
@@ -350,11 +358,33 @@ export default function CompanionLayer({ stage, defaultEpisode }: { stage: Stage
|
|
|
350
358
|
|
|
351
359
|
const waiting = thread.waiting ? { take: thread.waiting.take, seconds: Math.max(0, Math.floor((now - thread.waiting.since) / 1000)) } : null;
|
|
352
360
|
|
|
353
|
-
|
|
354
|
-
|
|
361
|
+
// ── The phone's sheet: the open conversation holds it in the caption's place ──
|
|
362
|
+
const { narrow, sheet: sheetOwner, claimSheet, releaseSheet } = stage;
|
|
363
|
+
useEffect(() => {
|
|
364
|
+
if (open) claimSheet("talk");
|
|
365
|
+
else releaseSheet("talk");
|
|
366
|
+
}, [open, claimSheet, releaseSheet]);
|
|
367
|
+
useEffect(() => () => releaseSheet("talk"), [releaseSheet]);
|
|
368
|
+
// A mark's card took the sheet: the conversation folds to its bar.
|
|
369
|
+
const [seenOwner, setSeenOwner] = useState(sheetOwner);
|
|
370
|
+
if (seenOwner !== sheetOwner) {
|
|
371
|
+
setSeenOwner(sheetOwner);
|
|
372
|
+
if (narrow && sheetOwner === "card") setOpenState(false);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// A mark in the conversation: its drawing, at its thread.
|
|
376
|
+
const onOpenMark = useCallback((mark: DockMark) => {
|
|
377
|
+
if (window.matchMedia("(max-width: 767px)").matches) setOpenState(false);
|
|
378
|
+
requestMark(mark.doc, mark.id);
|
|
379
|
+
if (stageRef.current.board !== mark.doc) openInSystem({ doc: mark.doc, focus: null });
|
|
380
|
+
}, []);
|
|
381
|
+
|
|
382
|
+
const dock = (
|
|
355
383
|
<CompanionDock
|
|
356
384
|
open={open}
|
|
357
385
|
onOpenChange={setOpen}
|
|
386
|
+
variant={narrow ? "bar" : "fab"}
|
|
387
|
+
onOpenMark={onOpenMark}
|
|
358
388
|
who={WHO}
|
|
359
389
|
episode={episode}
|
|
360
390
|
episodes={episodes}
|
|
@@ -377,6 +407,11 @@ export default function CompanionLayer({ stage, defaultEpisode }: { stage: Stage
|
|
|
377
407
|
notice={notice}
|
|
378
408
|
error={problem ?? thread.error}
|
|
379
409
|
/>
|
|
380
|
-
</div>
|
|
381
410
|
);
|
|
411
|
+
if (narrow) {
|
|
412
|
+
// The bar is the sheet's handle; the open conversation is its body.
|
|
413
|
+
const slot = open ? stage.sheetBody : stage.sheetHandle;
|
|
414
|
+
return slot ? createPortal(dock, slot) : null;
|
|
415
|
+
}
|
|
416
|
+
return <div className="stage-talk-host">{dock}</div>;
|
|
382
417
|
}
|