@miadi/stateloom-web 0.1.6 → 0.1.7
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/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/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]__df915e74._.js → [root-of-the-server]__46d45ef8._.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/static/chunks/486bc3bc16e91024.css +2 -0
- package/server/.next/static/chunks/b3f3eb72c25c4c6b.js +10 -0
- package/server/package-lock.json +2 -2
- package/server/package.json +1 -1
- package/server/src/components/erd/ErdWorkspace.tsx +74 -9
- package/server/stateloom-web.json +2 -2
- package/server/tsconfig.tsbuildinfo +1 -1
- package/server/.next/static/chunks/3dc2436b75e43174.css +0 -2
- package/server/.next/static/chunks/e7adb06692216348.js +0 -10
- /package/server/.next/static/{6GrzDGxArqw_jZoajUQwk → DPL_H0tteH9tpIBvvsIfq}/_buildManifest.js +0 -0
- /package/server/.next/static/{6GrzDGxArqw_jZoajUQwk → DPL_H0tteH9tpIBvvsIfq}/_clientMiddlewareManifest.json +0 -0
- /package/server/.next/static/{6GrzDGxArqw_jZoajUQwk → DPL_H0tteH9tpIBvvsIfq}/_ssgManifest.js +0 -0
|
@@ -32,9 +32,11 @@ import {
|
|
|
32
32
|
removeEntity,
|
|
33
33
|
removeRelationship,
|
|
34
34
|
renderMermaidEr,
|
|
35
|
+
updateEntity,
|
|
35
36
|
validateErd,
|
|
36
37
|
type EntityRelationshipDefinition,
|
|
37
38
|
type ErdCardinality,
|
|
39
|
+
type ErdNotation,
|
|
38
40
|
type ErdValidationError,
|
|
39
41
|
type LayoutBox,
|
|
40
42
|
type StateMachineDefinition,
|
|
@@ -47,16 +49,30 @@ import { loadRuntimeConfig } from "@/lib/runtimeConfig";
|
|
|
47
49
|
type Moved = Record<string, { x: number; y: number }>;
|
|
48
50
|
type Edit = (def: EntityRelationshipDefinition) => EntityRelationshipDefinition;
|
|
49
51
|
|
|
50
|
-
|
|
52
|
+
// The two notations give an entity different footprints, so each keeps its own
|
|
53
|
+
// dragged arrangement. Crow's foot keeps the key it has always had.
|
|
54
|
+
const layoutKey = (docPath: string, notation: ErdNotation): string =>
|
|
55
|
+
notation === "chen" ? `stateloom:erd-layout:chen:${docPath}` : `stateloom:erd-layout:${docPath}`;
|
|
51
56
|
|
|
52
|
-
|
|
57
|
+
const NOTATION_KEY = "stateloom:erd-notation";
|
|
58
|
+
|
|
59
|
+
function readMoved(docPath: string, notation: ErdNotation): Moved {
|
|
53
60
|
try {
|
|
54
|
-
return JSON.parse(localStorage.getItem(layoutKey(docPath)) ?? "{}") as Moved;
|
|
61
|
+
return JSON.parse(localStorage.getItem(layoutKey(docPath, notation)) ?? "{}") as Moved;
|
|
55
62
|
} catch {
|
|
56
63
|
return {};
|
|
57
64
|
}
|
|
58
65
|
}
|
|
59
66
|
|
|
67
|
+
/** The viewer's own preference — kept in this browser, never in the document. */
|
|
68
|
+
function readNotation(): ErdNotation {
|
|
69
|
+
try {
|
|
70
|
+
return localStorage.getItem(NOTATION_KEY) === "chen" ? "chen" : "crowsfoot";
|
|
71
|
+
} catch {
|
|
72
|
+
return "crowsfoot";
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
60
76
|
interface Machine {
|
|
61
77
|
path: string;
|
|
62
78
|
def: StateMachineDefinition;
|
|
@@ -93,6 +109,7 @@ export default function ErdWorkspace() {
|
|
|
93
109
|
const [docPath, setDocPath] = useState("");
|
|
94
110
|
const [def, setDef] = useState<EntityRelationshipDefinition | null>(null);
|
|
95
111
|
const [moved, setMoved] = useState<Moved>({});
|
|
112
|
+
const [notation, setNotation] = useState<ErdNotation>("crowsfoot");
|
|
96
113
|
const [viewport, setViewport] = useState<Viewport>({ ...IDENTITY_VIEWPORT });
|
|
97
114
|
const [fitKey, setFitKey] = useState(0);
|
|
98
115
|
const [selection, setSelection] = useState<string | null>(null);
|
|
@@ -119,9 +136,11 @@ export default function ErdWorkspace() {
|
|
|
119
136
|
}
|
|
120
137
|
if (body.exists && !loaded) return setStatus(`${path} is not a readable ERD definition`);
|
|
121
138
|
const name = (path.split("/").pop() ?? "Untitled").replace(/\.erdf\.json$/i, "");
|
|
139
|
+
const preferred = readNotation();
|
|
122
140
|
setDocPath(path);
|
|
123
141
|
setDef(loaded ?? emptyErd("Default", name));
|
|
124
|
-
|
|
142
|
+
setNotation(preferred);
|
|
143
|
+
setMoved(readMoved(path, preferred));
|
|
125
144
|
setSelection(null);
|
|
126
145
|
setLinkReport(null);
|
|
127
146
|
setFitKey((k) => k + 1);
|
|
@@ -156,12 +175,12 @@ export default function ErdWorkspace() {
|
|
|
156
175
|
|
|
157
176
|
const positions = useMemo<Record<string, LayoutBox>>(() => {
|
|
158
177
|
if (!def) return {};
|
|
159
|
-
const derived = erdAutoLayout(def);
|
|
178
|
+
const derived = erdAutoLayout(def, { notation });
|
|
160
179
|
for (const [name, at] of Object.entries(moved)) {
|
|
161
180
|
if (derived[name]) derived[name] = { ...derived[name], ...at };
|
|
162
181
|
}
|
|
163
182
|
return derived;
|
|
164
|
-
}, [def, moved]);
|
|
183
|
+
}, [def, moved, notation]);
|
|
165
184
|
|
|
166
185
|
const problems = useMemo(() => (def ? validateErd(def) : []), [def]);
|
|
167
186
|
const errorEntities = useMemo(
|
|
@@ -201,20 +220,32 @@ export default function ErdWorkspace() {
|
|
|
201
220
|
setMoved((prev) => {
|
|
202
221
|
const next = { ...prev, [name]: { x: Math.round(box.x), y: Math.round(box.y) } };
|
|
203
222
|
try {
|
|
204
|
-
localStorage.setItem(layoutKey(docPath), JSON.stringify(next));
|
|
223
|
+
localStorage.setItem(layoutKey(docPath, notation), JSON.stringify(next));
|
|
205
224
|
} catch {
|
|
206
225
|
// A full or disabled store costs the arrangement, not the document.
|
|
207
226
|
}
|
|
208
227
|
return next;
|
|
209
228
|
});
|
|
210
229
|
},
|
|
211
|
-
[docPath],
|
|
230
|
+
[docPath, notation],
|
|
212
231
|
);
|
|
213
232
|
|
|
233
|
+
const chooseNotation = (next: ErdNotation): void => {
|
|
234
|
+
if (next === notation) return;
|
|
235
|
+
try {
|
|
236
|
+
localStorage.setItem(NOTATION_KEY, next);
|
|
237
|
+
} catch {
|
|
238
|
+
// The choice then lasts for this visit only.
|
|
239
|
+
}
|
|
240
|
+
setNotation(next);
|
|
241
|
+
setMoved(readMoved(docPath, next));
|
|
242
|
+
setFitKey((k) => k + 1);
|
|
243
|
+
};
|
|
244
|
+
|
|
214
245
|
const arrange = (): void => {
|
|
215
246
|
setMoved({});
|
|
216
247
|
try {
|
|
217
|
-
localStorage.removeItem(layoutKey(docPath));
|
|
248
|
+
localStorage.removeItem(layoutKey(docPath, notation));
|
|
218
249
|
} catch {
|
|
219
250
|
/* as above */
|
|
220
251
|
}
|
|
@@ -258,6 +289,28 @@ export default function ErdWorkspace() {
|
|
|
258
289
|
{status}
|
|
259
290
|
{peers > 1 ? ` · ${peers} in the room` : ""}
|
|
260
291
|
</span>
|
|
292
|
+
{/* Same document, two drawings. The choice is this browser's; it is never written to the file. */}
|
|
293
|
+
<span className="inline-flex overflow-hidden rounded border border-gray-700" role="group" aria-label="Notation">
|
|
294
|
+
{(
|
|
295
|
+
[
|
|
296
|
+
["crowsfoot", "▤", "Crow's foot — attributes listed inside the entity box"],
|
|
297
|
+
["chen", "◇", "Chen — attributes as ovals, relationships as diamonds"],
|
|
298
|
+
] as const
|
|
299
|
+
).map(([id, glyph, title]) => (
|
|
300
|
+
<button
|
|
301
|
+
key={id}
|
|
302
|
+
title={title}
|
|
303
|
+
aria-label={title}
|
|
304
|
+
aria-pressed={notation === id}
|
|
305
|
+
onClick={() => chooseNotation(id)}
|
|
306
|
+
className={`px-2 py-1 text-sm leading-none ${
|
|
307
|
+
notation === id ? "bg-blue-900/60 text-blue-200" : "bg-gray-800 text-gray-400 hover:bg-gray-700"
|
|
308
|
+
}`}
|
|
309
|
+
>
|
|
310
|
+
{glyph}
|
|
311
|
+
</button>
|
|
312
|
+
))}
|
|
313
|
+
</span>
|
|
261
314
|
<button className={button} onClick={arrange} title="Forget the dragged positions and lay the board out again">
|
|
262
315
|
⤢ Arrange
|
|
263
316
|
</button>
|
|
@@ -280,6 +333,7 @@ export default function ErdWorkspace() {
|
|
|
280
333
|
{def && (
|
|
281
334
|
<EntityRelationshipCanvas
|
|
282
335
|
definition={def}
|
|
336
|
+
notation={notation}
|
|
283
337
|
positions={positions}
|
|
284
338
|
viewport={viewport}
|
|
285
339
|
onViewportChange={setViewport}
|
|
@@ -323,6 +377,17 @@ export default function ErdWorkspace() {
|
|
|
323
377
|
{selected && (
|
|
324
378
|
<>
|
|
325
379
|
<div className={heading}>{selected.name}</div>
|
|
380
|
+
<label
|
|
381
|
+
className="mb-1 flex items-center gap-2 text-[11px] text-gray-400"
|
|
382
|
+
title="Exists only through another entity (an order line, without its order). Chen draws it as a double rectangle."
|
|
383
|
+
>
|
|
384
|
+
<input
|
|
385
|
+
type="checkbox"
|
|
386
|
+
checked={!!selected.weak}
|
|
387
|
+
onChange={(e) => apply((d) => updateEntity(d, selected.name, { weak: e.target.checked }))}
|
|
388
|
+
/>
|
|
389
|
+
weak entity
|
|
390
|
+
</label>
|
|
326
391
|
{(selected.attributes ?? []).map((a) => (
|
|
327
392
|
<div key={a.name} className="flex items-center justify-between gap-2 py-0.5 font-mono text-[11px]">
|
|
328
393
|
<span className={a.stateOf ? "text-blue-300" : "text-gray-300"}>
|