@miadi/stateloom-web 0.1.1 → 0.1.3
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 +2 -2
- package/server/.next/prerender-manifest.json +3 -3
- 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_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/file/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.js.nft.json +1 -1
- package/server/.next/server/app/page_client-reference-manifest.js +1 -1
- package/server/.next/server/chunks/ssr/{[root-of-the-server]__ac84e72e._.js → [root-of-the-server]__2b35b711._.js} +1 -1
- package/server/.next/server/chunks/ssr/[root-of-the-server]__edbb995c._.js +3 -3
- package/server/.next/server/pages/404.html +1 -1
- package/server/.next/server/pages/500.html +2 -2
- package/server/.next/server/server-reference-manifest.js +1 -1
- package/server/.next/server/server-reference-manifest.json +1 -1
- package/server/.next/static/chunks/50f310b5d321b604.css +2 -0
- package/server/.next/static/chunks/b8543b198154e75c.js +10 -0
- package/server/package-lock.json +36 -3
- package/server/package.json +2 -1
- package/server/src/app/globals.css +11 -73
- package/server/src/app/layout.tsx +3 -0
- package/server/src/components/Canvas.tsx +78 -1325
- package/server/src/components/PresenceChips.tsx +77 -0
- package/server/src/components/SettingsPanel.tsx +11 -6
- package/server/src/components/SocketBridgeProvider.tsx +6 -22
- package/server/src/components/Toolbar.tsx +5 -0
- package/server/src/types/definition.ts +12 -2
- package/server/stateloom-web.json +2 -2
- package/server/tsconfig.tsbuildinfo +1 -1
- package/server/.next/static/chunks/2c679fab817159f2.css +0 -1
- package/server/.next/static/chunks/7921d7703dd690a6.js +0 -10
- /package/server/.next/static/{vPEqV8SJpTDJWGy_fRuh4 → WuhCSMxaHZvNZ4hY20AEo}/_buildManifest.js +0 -0
- /package/server/.next/static/{vPEqV8SJpTDJWGy_fRuh4 → WuhCSMxaHZvNZ4hY20AEo}/_clientMiddlewareManifest.json +0 -0
- /package/server/.next/static/{vPEqV8SJpTDJWGy_fRuh4 → WuhCSMxaHZvNZ4hY20AEo}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { colorFor } from "@miadi/stateloom-protocol";
|
|
5
|
+
import { useDesignerStore } from "@/store/useDesignerStore";
|
|
6
|
+
|
|
7
|
+
/** Enough dots to read "several peers" at a glance; the count carries the rest. */
|
|
8
|
+
const MAX_DOTS = 4;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Who else is on this board. It used to float over the top-right corner as one
|
|
12
|
+
* pill per peer — which on a busy room meant a dozen chips painted across the
|
|
13
|
+
* toolbar's own controls, hiding Validate/Generate/RISE behind a colour bar
|
|
14
|
+
* nobody could dismiss. `pointer-events-none` kept the buttons clickable but
|
|
15
|
+
* you could no longer see what you were clicking.
|
|
16
|
+
*
|
|
17
|
+
* So presence stops floating: it is a toolbar control like any other, laid out
|
|
18
|
+
* in the flow beside the document identity it describes. Fixed width regardless
|
|
19
|
+
* of how many peers join — a stack of colour dots and a count — with the full
|
|
20
|
+
* roster one click away. Nothing can be covered by a room that grows.
|
|
21
|
+
*/
|
|
22
|
+
export default function PresenceChips() {
|
|
23
|
+
const presence = useDesignerStore((s) => s.presence);
|
|
24
|
+
const [open, setOpen] = useState(false);
|
|
25
|
+
|
|
26
|
+
if (presence.length === 0) return null;
|
|
27
|
+
|
|
28
|
+
const byRole = new Map<string, number>();
|
|
29
|
+
for (const p of presence) byRole.set(p.role, (byRole.get(p.role) ?? 0) + 1);
|
|
30
|
+
const summary = [...byRole]
|
|
31
|
+
.map(([role, n]) => (n > 1 ? `${role} ×${n}` : role))
|
|
32
|
+
.join(" · ");
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<span className="relative inline-flex">
|
|
36
|
+
<button
|
|
37
|
+
onClick={() => setOpen((v) => !v)}
|
|
38
|
+
className="toolbar-btn inline-flex items-center gap-1.5"
|
|
39
|
+
title={`Connected: ${summary}`}
|
|
40
|
+
aria-expanded={open}
|
|
41
|
+
aria-label={`${presence.length} connected: ${summary}`}
|
|
42
|
+
>
|
|
43
|
+
<span className="flex -space-x-1">
|
|
44
|
+
{presence.slice(0, MAX_DOTS).map((p) => (
|
|
45
|
+
<span
|
|
46
|
+
key={p.clientId}
|
|
47
|
+
className="inline-block h-2 w-2 rounded-full ring-1 ring-gray-900"
|
|
48
|
+
style={{ backgroundColor: p.color ?? colorFor(p.clientId) }}
|
|
49
|
+
/>
|
|
50
|
+
))}
|
|
51
|
+
</span>
|
|
52
|
+
<span className="tabular-nums">{presence.length}</span>
|
|
53
|
+
</button>
|
|
54
|
+
{open && (
|
|
55
|
+
<div className="absolute left-0 top-full z-50 mt-1 max-h-64 w-56 overflow-y-auto rounded border border-gray-700 bg-gray-900 p-2 shadow-lg">
|
|
56
|
+
<div className="mb-1 text-[10px] uppercase tracking-wide text-gray-500">
|
|
57
|
+
connected · {summary}
|
|
58
|
+
</div>
|
|
59
|
+
{presence.map((p) => (
|
|
60
|
+
<div
|
|
61
|
+
key={p.clientId}
|
|
62
|
+
className="flex items-center gap-1.5 rounded px-1 py-1 text-xs text-gray-300"
|
|
63
|
+
title={p.clientId}
|
|
64
|
+
>
|
|
65
|
+
<span
|
|
66
|
+
className="inline-block h-2 w-2 shrink-0 rounded-full"
|
|
67
|
+
style={{ backgroundColor: p.color ?? colorFor(p.clientId) }}
|
|
68
|
+
/>
|
|
69
|
+
<span className="shrink-0">{p.role}</span>
|
|
70
|
+
{p.name && <span className="truncate text-gray-500">{p.name}</span>}
|
|
71
|
+
</div>
|
|
72
|
+
))}
|
|
73
|
+
</div>
|
|
74
|
+
)}
|
|
75
|
+
</span>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
@@ -55,11 +55,13 @@ export default function SettingsPanel() {
|
|
|
55
55
|
<span className="text-xs text-gray-500">Context Class</span>
|
|
56
56
|
<input
|
|
57
57
|
className="w-full bg-gray-800 border border-gray-700 rounded px-2 py-1 text-xs text-gray-200"
|
|
58
|
-
value={context?.
|
|
58
|
+
value={context?.class ?? ""}
|
|
59
59
|
placeholder="e.g. MyStateMachineContext"
|
|
60
60
|
onChange={(e) =>
|
|
61
61
|
updateSettings({
|
|
62
|
-
|
|
62
|
+
// `class`, not `className`: the generator names the context from
|
|
63
|
+
// `settings.context?.class`, and nothing has ever read the other.
|
|
64
|
+
context: { ...context, class: e.target.value || undefined },
|
|
63
65
|
})
|
|
64
66
|
}
|
|
65
67
|
/>
|
|
@@ -97,15 +99,18 @@ export default function SettingsPanel() {
|
|
|
97
99
|
<h4 className="text-xs font-semibold text-gray-500">Object References</h4>
|
|
98
100
|
<textarea
|
|
99
101
|
className="w-full bg-gray-800 border border-gray-700 rounded px-2 py-1 text-xs text-gray-200 h-12 font-mono"
|
|
100
|
-
value={(settings.objects ?? []).map((o) => `${o.
|
|
101
|
-
placeholder="
|
|
102
|
+
value={(settings.objects ?? []).map((o) => `${o.instance}: ${o.class}`).join("\n")}
|
|
103
|
+
placeholder="instance: Class (one per line)"
|
|
102
104
|
onChange={(e) => {
|
|
105
|
+
// `instance: Class` — the pair the generator constructs the context
|
|
106
|
+
// with. Written as `{ name, type }` until now, which is a shape
|
|
107
|
+
// `codegen.ts` has never read.
|
|
103
108
|
const objects = e.target.value
|
|
104
109
|
.split("\n")
|
|
105
110
|
.filter((l) => l.includes(":"))
|
|
106
111
|
.map((l) => {
|
|
107
|
-
const [
|
|
108
|
-
return {
|
|
112
|
+
const [instance, cls] = l.split(":").map((s) => s.trim());
|
|
113
|
+
return { instance, class: cls };
|
|
109
114
|
});
|
|
110
115
|
updateSettings({ objects });
|
|
111
116
|
}}
|
|
@@ -7,7 +7,6 @@ import {
|
|
|
7
7
|
} from "@miadi/stateloom-react";
|
|
8
8
|
import {
|
|
9
9
|
diffDefinition,
|
|
10
|
-
colorFor,
|
|
11
10
|
type StateMachineDefinition,
|
|
12
11
|
} from "@miadi/stateloom-protocol";
|
|
13
12
|
import { useDesignerStore } from "@/store/useDesignerStore";
|
|
@@ -24,9 +23,13 @@ const OUTBOUND_DEBOUNCE_MS = 60;
|
|
|
24
23
|
* Rendered only once a bridge URL is known — see DesignBridge, which resolves
|
|
25
24
|
* it from the serving process at runtime so a published build carries nobody's
|
|
26
25
|
* URL baked in.
|
|
26
|
+
*
|
|
27
|
+
* Transport only — it paints nothing. Presence lands in the store and the
|
|
28
|
+
* toolbar draws it (PresenceChips); the floating corner dock this used to
|
|
29
|
+
* render grew one pill per peer and ended up covering the toolbar it hovered
|
|
30
|
+
* over.
|
|
27
31
|
*/
|
|
28
32
|
export default function SocketBridgeProvider({ url }: { url: string }) {
|
|
29
|
-
const presence = useDesignerStore((s) => s.presence);
|
|
30
33
|
// The requested `?doc=` (step 2): passed through to the file API, which
|
|
31
34
|
// resolves and guards it; the join below keys the room by the RESOLVED path.
|
|
32
35
|
const requested = useRequestedDoc();
|
|
@@ -126,24 +129,5 @@ export default function SocketBridgeProvider({ url }: { url: string }) {
|
|
|
126
129
|
// an arbitrary definition, so the arriving full-def repaints the canvas.
|
|
127
130
|
}, [url, requested]);
|
|
128
131
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
return (
|
|
132
|
-
// `presence-dock` replaces the raw `top-2 right-4`: with viewport-fit=cover
|
|
133
|
-
// that corner can sit under a notch or the status bar, so the offset is
|
|
134
|
-
// measured from the safe-area inset instead. Still pointer-events-none, so
|
|
135
|
-
// it never competes with the toolbar it floats over.
|
|
136
|
-
<div className="presence-dock flex gap-1 pointer-events-none">
|
|
137
|
-
{presence.map((p) => (
|
|
138
|
-
<span
|
|
139
|
-
key={p.clientId}
|
|
140
|
-
title={`${p.role}${p.name ? ` · ${p.name}` : ""}`}
|
|
141
|
-
className="text-[10px] leading-none px-2 py-1 rounded-full text-white/95 shadow font-medium"
|
|
142
|
-
style={{ backgroundColor: p.color ?? colorFor(p.clientId) }}
|
|
143
|
-
>
|
|
144
|
-
{p.role}
|
|
145
|
-
</span>
|
|
146
|
-
))}
|
|
147
|
-
</div>
|
|
148
|
-
);
|
|
132
|
+
return null;
|
|
149
133
|
}
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
} from "@/lib/exportImage";
|
|
11
11
|
import type { StateDef } from "@/types/definition";
|
|
12
12
|
import DocSwitcher from "./DocSwitcher";
|
|
13
|
+
import PresenceChips from "./PresenceChips";
|
|
13
14
|
import { docQuery, useRequestedDoc } from "@/lib/docParam";
|
|
14
15
|
|
|
15
16
|
export default function Toolbar() {
|
|
@@ -224,6 +225,10 @@ export default function Toolbar() {
|
|
|
224
225
|
<span className="text-[10px] text-gray-600" title={remoteMessage ?? "No file bound"}>○ no disk</span>
|
|
225
226
|
)}
|
|
226
227
|
|
|
228
|
+
{/* Who else is on the board. In the flow, next to the document it
|
|
229
|
+
describes — it used to float over this toolbar and cover it. */}
|
|
230
|
+
<PresenceChips />
|
|
231
|
+
|
|
227
232
|
<div className="flex-1" />
|
|
228
233
|
</div>
|
|
229
234
|
|
|
@@ -10,8 +10,11 @@ export interface ObjectRef {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
export interface ContextConfig {
|
|
13
|
+
/** The generated context's class name — what `ts/src/codegen.ts` reads. */
|
|
13
14
|
class?: string;
|
|
14
15
|
instance?: string;
|
|
16
|
+
/** The designer's own extra: a base the generated context extends. */
|
|
17
|
+
baseClass?: string;
|
|
15
18
|
}
|
|
16
19
|
|
|
17
20
|
export interface SettingsModel {
|
|
@@ -19,8 +22,15 @@ export interface SettingsModel {
|
|
|
19
22
|
name?: string;
|
|
20
23
|
description?: string;
|
|
21
24
|
asynchronous: boolean;
|
|
22
|
-
|
|
23
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Objects the generated context is constructed with. `ObjectRef` is what
|
|
27
|
+
* `ts/src/codegen.ts` actually reads (`obj.instance`, `obj.class`); the
|
|
28
|
+
* `{ name, type }` pair this field used to carry was a shape no generator
|
|
29
|
+
* could consume, so an "Object References" line typed into the designer was
|
|
30
|
+
* silently dropped on the way to code.
|
|
31
|
+
*/
|
|
32
|
+
objects?: ObjectRef[];
|
|
33
|
+
context?: ContextConfig;
|
|
24
34
|
imports?: string[];
|
|
25
35
|
using?: string[];
|
|
26
36
|
targetLanguage?: string;
|