thurview 0.16.0 → 0.17.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/dist/ui/app.js CHANGED
@@ -908,6 +908,73 @@
908
908
  // src/ui/diagrams.ts
909
909
  init_dom();
910
910
  init_dom();
911
+
912
+ // src/ui/flow-layout.ts
913
+ var boxW = 210;
914
+ var boxH = 46;
915
+ var gapX = 40;
916
+ var gapY = 58;
917
+ var pad = 14;
918
+ var minW = 700;
919
+ function layoutFlow(b) {
920
+ const layer = /* @__PURE__ */ new Map([[b.steps[0].id, 0]]);
921
+ const queue = [b.steps[0].id];
922
+ while (queue.length) {
923
+ const cur = queue.shift();
924
+ for (const e of b.edges)
925
+ if (e.from === cur && !layer.has(e.to)) {
926
+ layer.set(e.to, layer.get(cur) + 1);
927
+ queue.push(e.to);
928
+ }
929
+ }
930
+ const rows = [];
931
+ for (const s of b.steps) {
932
+ const li = layer.get(s.id);
933
+ (rows[li] ??= []).push(s.id);
934
+ }
935
+ const span = (e) => layer.get(e.from) - layer.get(e.to);
936
+ const loops = b.edges.filter((e) => span(e) >= 0).sort((p, q) => span(p) - span(q));
937
+ const lanes = loops.length > 0;
938
+ const widest = Math.max(...rows.map((r) => r.length));
939
+ const laneGap = 18;
940
+ const lane = lanes ? 34 + (loops.length - 1) * laneGap : 0;
941
+ const band = lanes ? gapY / 2 : 0;
942
+ const width = Math.max(minW, pad * 2 + widest * boxW + (widest - 1) * gapX + lane);
943
+ const height = pad * 2 + band * 2 + rows.length * boxH + (rows.length - 1) * gapY;
944
+ const at = (id) => {
945
+ const li = layer.get(id);
946
+ const row = rows[li];
947
+ const spanW = row.length * boxW + (row.length - 1) * gapX;
948
+ const x = pad + (width - lane - pad * 2 - spanW) / 2 + row.indexOf(id) * (boxW + gapX);
949
+ return { x, y: pad + band + li * (boxH + gapY), cx: x + boxW / 2, li };
950
+ };
951
+ const edges = b.edges.map((edge) => {
952
+ const from = at(edge.from);
953
+ const to = at(edge.to);
954
+ const back = to.li < from.li;
955
+ if (to.li > from.li) {
956
+ const mid = from.y + boxH + (to.y - from.y - boxH) / 2;
957
+ return {
958
+ edge,
959
+ back,
960
+ d: `M${from.cx},${from.y + boxH} V${mid} H${to.cx} V${to.y - 4}`,
961
+ label: { x: (from.cx + to.cx) / 2 + 6, y: mid - 5, at: "middle" }
962
+ };
963
+ }
964
+ const laneX = width - lane + 17 + loops.indexOf(edge) * laneGap;
965
+ const out = from.y + boxH + gapY / 2;
966
+ const into = to.y - gapY / 2;
967
+ return {
968
+ edge,
969
+ back,
970
+ d: `M${from.cx},${from.y + boxH} V${out} H${laneX} V${into} H${to.cx} V${to.y - 4}`,
971
+ label: { x: laneX - 6, y: (out + into) / 2, at: "end" }
972
+ };
973
+ });
974
+ return { width, height, at, edges };
975
+ }
976
+
977
+ // src/ui/diagrams.ts
911
978
  function openAnchor(id) {
912
979
  const a = state.data?.document?.anchors[id];
913
980
  if (a) openAnchorPeek(a);
@@ -1116,6 +1183,84 @@
1116
1183
  ops
1117
1184
  );
1118
1185
  }
1186
+ function flowDiagram(b, doc) {
1187
+ const fit = (text, room, px) => {
1188
+ const max = Math.floor(room / (px * 0.605));
1189
+ return text.length > max ? `${text.slice(0, max - 1)}\u2026` : text;
1190
+ };
1191
+ const { width, height, at, edges } = layoutFlow(b);
1192
+ const el = svg("svg", { viewBox: `0 0 ${width} ${height}`, class: "flow" });
1193
+ el.appendChild(
1194
+ svg(
1195
+ "defs",
1196
+ {},
1197
+ svg(
1198
+ "marker",
1199
+ {
1200
+ id: "flow-arr",
1201
+ viewBox: "0 0 10 10",
1202
+ refX: "9",
1203
+ refY: "5",
1204
+ markerWidth: "7",
1205
+ markerHeight: "7",
1206
+ orient: "auto"
1207
+ },
1208
+ svg("path", { d: "M0,0 L10,5 L0,10 z", fill: "currentColor" })
1209
+ )
1210
+ )
1211
+ );
1212
+ for (const { edge, d, back, label: label2 } of edges) {
1213
+ const g = svg("g", { class: back ? "edge back" : "edge" });
1214
+ g.appendChild(svg("path", { d, fill: "none", "marker-end": "url(#flow-arr)" }));
1215
+ if (edge.case)
1216
+ g.appendChild(svg("text", { x: label2.x, y: label2.y, "text-anchor": label2.at }, edge.case));
1217
+ el.appendChild(g);
1218
+ }
1219
+ for (const s of b.steps) {
1220
+ const { x, y, cx } = at(s.id);
1221
+ const g = svg("g", {
1222
+ class: `step${s.decision ? " decision" : ""}${s.anchor ? "" : " plain"}`
1223
+ });
1224
+ g.appendChild(
1225
+ s.decision ? svg("path", {
1226
+ d: `M${x + 16},${y} H${x + boxW - 16} L${x + boxW},${y + boxH / 2} L${x + boxW - 16},${y + boxH} H${x + 16} L${x},${y + boxH / 2} Z`
1227
+ }) : svg("rect", { x, y, width: boxW, height: boxH, rx: 6 })
1228
+ );
1229
+ const room = boxW - (s.decision ? 44 : 20);
1230
+ const actor = s.actor ? doc.actors[s.actor]?.label ?? s.actor : null;
1231
+ if (actor)
1232
+ g.appendChild(
1233
+ svg(
1234
+ "text",
1235
+ { class: "who", x: cx, y: y + 17, "text-anchor": "middle" },
1236
+ fit(actor, room, 10.5)
1237
+ )
1238
+ );
1239
+ g.appendChild(
1240
+ svg(
1241
+ "text",
1242
+ { x: cx, y: actor ? y + 33 : y + boxH / 2 + 4, "text-anchor": "middle" },
1243
+ fit(s.label, room, 12)
1244
+ )
1245
+ );
1246
+ g.appendChild(svg("title", {}, s.anchor ? `${s.label} \u2014 ${s.anchor}` : s.label));
1247
+ if (s.anchor) {
1248
+ const open = () => openAnchor(s.anchor);
1249
+ g.setAttribute("role", "button");
1250
+ g.setAttribute("tabindex", "0");
1251
+ g.addEventListener("click", open);
1252
+ g.addEventListener("keydown", (e) => {
1253
+ const k = e.key;
1254
+ if (k === "Enter" || k === " ") {
1255
+ e.preventDefault();
1256
+ open();
1257
+ }
1258
+ });
1259
+ }
1260
+ el.appendChild(g);
1261
+ }
1262
+ return h("div", { class: "diagram" }, h("div", { class: "dhead" }, b.label), el);
1263
+ }
1119
1264
 
1120
1265
  // src/ui/views/review.ts
1121
1266
  function renderReview(root) {
@@ -1549,6 +1694,9 @@
1549
1694
  case "database":
1550
1695
  wrap.appendChild(databaseLens(b, doc));
1551
1696
  break;
1697
+ case "flow":
1698
+ wrap.appendChild(flowDiagram(b, doc));
1699
+ break;
1552
1700
  }
1553
1701
  if (threads.length)
1554
1702
  wrap.appendChild(h("div", { class: "thread-pins" }, threads.map(threadPinRow)));