@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.
Files changed (44) hide show
  1. package/package.json +1 -1
  2. package/server/.next/BUILD_ID +1 -1
  3. package/server/.next/build-manifest.json +2 -2
  4. package/server/.next/server/app/_global-error.html +2 -2
  5. package/server/.next/server/app/_global-error.rsc +1 -1
  6. package/server/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  7. package/server/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  8. package/server/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  9. package/server/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  10. package/server/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  11. package/server/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  12. package/server/.next/server/app/_not-found.html +1 -1
  13. package/server/.next/server/app/_not-found.rsc +2 -2
  14. package/server/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  15. package/server/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  16. package/server/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  17. package/server/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  18. package/server/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  19. package/server/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  20. package/server/.next/server/app/index.html +1 -1
  21. package/server/.next/server/app/index.rsc +3 -3
  22. package/server/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  23. package/server/.next/server/app/index.segments/_full.segment.rsc +3 -3
  24. package/server/.next/server/app/index.segments/_head.segment.rsc +1 -1
  25. package/server/.next/server/app/index.segments/_index.segment.rsc +2 -2
  26. package/server/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  27. package/server/.next/server/app/page.js.nft.json +1 -1
  28. package/server/.next/server/app/page_client-reference-manifest.js +1 -1
  29. package/server/.next/server/chunks/ssr/{[root-of-the-server]__df915e74._.js → [root-of-the-server]__46d45ef8._.js} +1 -1
  30. package/server/.next/server/chunks/ssr/[root-of-the-server]__edbb995c._.js +3 -3
  31. package/server/.next/server/pages/404.html +1 -1
  32. package/server/.next/server/pages/500.html +2 -2
  33. package/server/.next/static/chunks/486bc3bc16e91024.css +2 -0
  34. package/server/.next/static/chunks/b3f3eb72c25c4c6b.js +10 -0
  35. package/server/package-lock.json +2 -2
  36. package/server/package.json +1 -1
  37. package/server/src/components/erd/ErdWorkspace.tsx +74 -9
  38. package/server/stateloom-web.json +2 -2
  39. package/server/tsconfig.tsbuildinfo +1 -1
  40. package/server/.next/static/chunks/3dc2436b75e43174.css +0 -2
  41. package/server/.next/static/chunks/e7adb06692216348.js +0 -10
  42. /package/server/.next/static/{6GrzDGxArqw_jZoajUQwk → DPL_H0tteH9tpIBvvsIfq}/_buildManifest.js +0 -0
  43. /package/server/.next/static/{6GrzDGxArqw_jZoajUQwk → DPL_H0tteH9tpIBvvsIfq}/_clientMiddlewareManifest.json +0 -0
  44. /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
- const layoutKey = (docPath: string): string => `stateloom:erd-layout:${docPath}`;
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
- function readMoved(docPath: string): Moved {
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
- setMoved(readMoved(path));
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"}>
@@ -1,5 +1,5 @@
1
1
  {
2
- "package": "0.1.6",
3
- "designer": "0.1.6",
2
+ "package": "0.1.7",
3
+ "designer": "0.1.7",
4
4
  "next": "16.1.6"
5
5
  }