@things-factory/figure-ui 10.1.128 → 10.1.141
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/client/modeller/align.ts +24 -18
- package/client/modeller/editor.ts +165 -253
- package/client/modeller/fasten-geometry.ts +12 -9
- package/client/modeller/figure-facts.ts +12 -14
- package/client/modeller/figure-links.ts +16 -12
- package/client/modeller/figure-preview.ts +13 -4
- package/client/modeller/figure-properties.ts +6 -5
- package/client/modeller/figure-verdict.ts +14 -8
- package/client/modeller/joint-fan.ts +15 -17
- package/client/modeller/links.ts +17 -23
- package/client/modeller/live.ts +6 -3
- package/client/modeller/motion-ends.ts +6 -7
- package/client/modeller/occupancy.ts +2 -3
- package/client/modeller/product-session.ts +15 -0
- package/client/modeller/revision.ts +66 -0
- package/client/pages/figure-modeller-page.ts +5 -18
- package/dist-client/modeller/align.d.ts +12 -7
- package/dist-client/modeller/align.js +21 -17
- package/dist-client/modeller/align.js.map +1 -1
- package/dist-client/modeller/editor.d.ts +8 -34
- package/dist-client/modeller/editor.js +175 -250
- package/dist-client/modeller/editor.js.map +1 -1
- package/dist-client/modeller/fasten-geometry.d.ts +3 -2
- package/dist-client/modeller/fasten-geometry.js +11 -9
- package/dist-client/modeller/fasten-geometry.js.map +1 -1
- package/dist-client/modeller/figure-facts.d.ts +9 -1
- package/dist-client/modeller/figure-facts.js +11 -15
- package/dist-client/modeller/figure-facts.js.map +1 -1
- package/dist-client/modeller/figure-links.d.ts +4 -5
- package/dist-client/modeller/figure-links.js +14 -13
- package/dist-client/modeller/figure-links.js.map +1 -1
- package/dist-client/modeller/figure-preview.d.ts +5 -2
- package/dist-client/modeller/figure-preview.js +13 -4
- package/dist-client/modeller/figure-preview.js.map +1 -1
- package/dist-client/modeller/figure-properties.d.ts +3 -2
- package/dist-client/modeller/figure-properties.js +6 -6
- package/dist-client/modeller/figure-properties.js.map +1 -1
- package/dist-client/modeller/figure-verdict.d.ts +4 -2
- package/dist-client/modeller/figure-verdict.js +12 -9
- package/dist-client/modeller/figure-verdict.js.map +1 -1
- package/dist-client/modeller/joint-fan.d.ts +8 -4
- package/dist-client/modeller/joint-fan.js +15 -16
- package/dist-client/modeller/joint-fan.js.map +1 -1
- package/dist-client/modeller/links.d.ts +6 -5
- package/dist-client/modeller/links.js +17 -24
- package/dist-client/modeller/links.js.map +1 -1
- package/dist-client/modeller/live.d.ts +3 -2
- package/dist-client/modeller/live.js +7 -3
- package/dist-client/modeller/live.js.map +1 -1
- package/dist-client/modeller/motion-ends.d.ts +2 -2
- package/dist-client/modeller/motion-ends.js +5 -6
- package/dist-client/modeller/motion-ends.js.map +1 -1
- package/dist-client/modeller/occupancy.d.ts +2 -2
- package/dist-client/modeller/occupancy.js +2 -3
- package/dist-client/modeller/occupancy.js.map +1 -1
- package/dist-client/modeller/product-session.d.ts +44 -39
- package/dist-client/modeller/product-session.js +17 -0
- package/dist-client/modeller/product-session.js.map +1 -1
- package/dist-client/modeller/revision.d.ts +37 -0
- package/dist-client/modeller/revision.js +39 -0
- package/dist-client/modeller/revision.js.map +1 -0
- package/dist-client/pages/figure-modeller-page.d.ts +0 -6
- package/dist-client/pages/figure-modeller-page.js +6 -14
- package/dist-client/pages/figure-modeller-page.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -7
- package/test/align.test.ts +14 -12
- package/test/browser/modeller-page.js +52 -4
- package/test/browser/modeller-revision.check.mjs +144 -0
- package/test/command-preview.test.ts +9 -8
- package/test/fasten-drawing.test.ts +1 -1
- package/test/fasten-geometry.test.ts +10 -9
- package/test/fields.test.ts +1 -1
- package/test/first-minute.test.ts +1 -1
- package/test/gizmo.test.ts +1 -1
- package/test/joint-fan.test.ts +30 -14
- package/test/joint-frames.test.ts +9 -8
- package/test/links.test.ts +13 -12
- package/test/live.test.ts +3 -3
- package/test/parts-wiring.test.ts +1 -1
- package/test/properties.test.ts +6 -5
- package/test/release-panel.test.ts +3 -3
- package/test/resize-with-placement.test.ts +8 -6
- package/test/revision-of.ts +22 -0
- package/test/revision.test.ts +172 -0
- package/test/section-editor.test.ts +4 -3
- package/test/section-panel.test.ts +5 -4
- package/test/verdict.test.ts +1 -1
- package/client/modeller/evaluated.ts +0 -47
- package/dist-client/modeller/evaluated.d.ts +0 -12
- package/dist-client/modeller/evaluated.js +0 -42
- package/dist-client/modeller/evaluated.js.map +0 -1
- package/dist-client/tsconfig.scratch.tsbuildinfo +0 -1
- package/test/evaluated.test.ts +0 -40
- package/test/mode-switch.test.ts +0 -43
|
@@ -5,7 +5,8 @@ import { boxShape } from './drawn-shapes.js';
|
|
|
5
5
|
const THICKNESS = 6;
|
|
6
6
|
const samePose = (a, b) => [0, 1, 2].every(i => Math.abs(a.t[i] - b.t[i]) < 1e-6 && [0, 1, 2].every(k => Math.abs(a.r[i][k] - b.r[i][k]) < 1e-9));
|
|
7
7
|
/** The ends of the selected part's own motion, or nothing: a part that does not move, follows another, or turns without end. */
|
|
8
|
-
export function motionEndsOf(
|
|
8
|
+
export function motionEndsOf(revision, part) {
|
|
9
|
+
const asset = revision.asset;
|
|
9
10
|
const joint = asset.joints?.find(j => j.body1.part === part);
|
|
10
11
|
/* A turn without end is a revolute joint whose control has no range: the range check below leaves it out. */
|
|
11
12
|
if (!joint || joint.type === 'fixed' || joint.mimic)
|
|
@@ -14,13 +15,11 @@ export function motionEndsOf(asset, part, values = {}) {
|
|
|
14
15
|
const input = asset.document.model.inputs.find(i => i.id === control && i.role === 'state');
|
|
15
16
|
if (!input || typeof input.min !== 'number' || typeof input.max !== 'number' || input.min === input.max)
|
|
16
17
|
return null;
|
|
17
|
-
/* The values
|
|
18
|
-
const
|
|
19
|
-
const shown = { ...asset, designInputs: { ...asset.designInputs, ...pick(asset.designInputs) } };
|
|
20
|
-
const state = pick(asset.stateDefaults);
|
|
18
|
+
/* The version's design values are its asset's; its pose is every control as set now, this one at each end. */
|
|
19
|
+
const state = Object.fromEntries(Object.entries(revision.inputs).filter(([id]) => id in asset.stateDefaults));
|
|
21
20
|
const at = (value) => {
|
|
22
21
|
try {
|
|
23
|
-
return compileFigureAsset(
|
|
22
|
+
return compileFigureAsset(asset).evaluate({ ...state, [control]: value }).geometry;
|
|
24
23
|
}
|
|
25
24
|
catch {
|
|
26
25
|
return null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"motion-ends.js","sourceRoot":"","sources":["../../client/modeller/motion-ends.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAA;
|
|
1
|
+
{"version":3,"file":"motion-ends.js","sourceRoot":"","sources":["../../client/modeller/motion-ends.ts"],"names":[],"mappings":"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","sourcesContent":["import { compileFigureAsset } from '@hatiolab/figure-model'\n\nimport { PREVIEW, TARGET_FACE } from './command-preview.js'\nimport type { FigureRenderPart } from './renderer.js'\nimport { boxShape } from './drawn-shapes.js'\nimport type { FigureRevision } from './revision.js'\n\n/*\n * Where a part's motion takes it (ADR-0097 §5.5, designer 2026-09-27): the bodies its control moves, drawn translucent at\n * the control's lowest and highest value -- evaluated there by figure-model, not guessed -- and, for a slide, the line it\n * travels. Only the two ends are shown; nothing here says what lies between them is clear.\n */\n\ntype Pose = { r: number[][]; t: number[] }\ntype Body = FigureRenderPart & { pose: Pose }\n\n/** The line a slide travels along, thin enough not to hide the part. */\nconst THICKNESS = 6\n\nexport interface MotionEnds {\n control: string\n min: number\n max: number\n unit: string\n /** The moving bodies at the lowest value, then at the highest. */\n ghost: FigureRenderPart[]\n}\n\nconst samePose = (a: Pose, b: Pose) => [0, 1, 2].every(i => Math.abs(a.t[i]! - b.t[i]!) < 1e-6 && [0, 1, 2].every(k => Math.abs(a.r[i]![k]! - b.r[i]![k]!) < 1e-9))\n\n/** The ends of the selected part's own motion, or nothing: a part that does not move, follows another, or turns without end. */\nexport function motionEndsOf(revision: FigureRevision, part: string): MotionEnds | null {\n const asset = revision.asset\n const joint = asset.joints?.find(j => j.body1.part === part)\n /* A turn without end is a revolute joint whose control has no range: the range check below leaves it out. */\n if (!joint || joint.type === 'fixed' || joint.mimic) return null\n const control = joint.state?.id ?? joint.id\n const input = asset.document.model.inputs.find(i => i.id === control && i.role === 'state')\n if (!input || typeof input.min !== 'number' || typeof input.max !== 'number' || input.min === input.max) return null\n /* The version's design values are its asset's; its pose is every control as set now, this one at each end. */\n const state = Object.fromEntries(Object.entries(revision.inputs).filter(([id]) => id in asset.stateDefaults))\n const at = (value: number) => {\n try {\n return compileFigureAsset(asset).evaluate({ ...state, [control]: value }).geometry as unknown as Body[]\n } catch {\n return null\n }\n }\n const low = at(input.min)\n const high = at(input.max)\n if (!low || !high) return null\n const byId = new Map(high.filter(b => b.pose).map(b => [b.id, b]))\n const moving = low.filter(b => b.pose && 'shape' in b && byId.has(b.id) && !samePose(b.pose, byId.get(b.id)!.pose))\n if (!moving.length) return null\n const ghost: FigureRenderPart[] = [\n ...moving.map(b => ({ ...b, id: `${PREVIEW}:min:${b.id}`, material: { token: PREVIEW, transparent: true } })),\n ...moving.map(b => ({ ...byId.get(b.id)!, id: `${PREVIEW}:max:${b.id}`, material: { token: PREVIEW, transparent: true } }))\n ]\n /* A slide: the line its own body travels, from the lowest value to the highest. */\n const own = moving.find(b => b.id === part)\n if (joint.type === 'prismatic' && own) {\n const from = own.pose.t\n const to = byId.get(part)!.pose.t\n const v = [0, 1, 2].map(i => to[i]! - from[i]!)\n const length = Math.hypot(...v)\n if (length > 1e-6) {\n const x = v.map(c => c / length)\n /* Any two directions square to it and to each other complete its frame. */\n const helper = Math.abs(x[1]!) < 0.9 ? [0, 1, 0] : [1, 0, 0]\n const zRaw = [x[1]! * helper[2]! - x[2]! * helper[1]!, x[2]! * helper[0]! - x[0]! * helper[2]!, x[0]! * helper[1]! - x[1]! * helper[0]!]\n const zl = Math.hypot(...zRaw)\n const z = zRaw.map(c => c / zl)\n const y = [z[1]! * x[2]! - z[2]! * x[1]!, z[2]! * x[0]! - z[0]! * x[2]!, z[0]! * x[1]! - z[1]! * x[0]!]\n ghost.push({\n id: `${TARGET_FACE}:slide`,\n shape: boxShape(length, THICKNESS, THICKNESS),\n pose: { r: [0, 1, 2].map(i => [x[i]!, y[i]!, z[i]!]), t: [0, 1, 2].map(i => (from[i]! + to[i]!) / 2) },\n material: { token: TARGET_FACE }\n })\n }\n }\n return { control, min: input.min, max: input.max, unit: input.unit, ghost }\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type FigureAuthoringAction, type FigureRoomRefit } from '@hatiolab/figure-model';
|
|
1
|
+
import { type FigureAsset, type FigureAuthoringAction, type FigureRoomRefit } from '@hatiolab/figure-model';
|
|
2
2
|
/**
|
|
3
3
|
* 「부품에 맞추기」의 짝 — 공간을 지금 부품에서 다시 선언하는 한 걸음.
|
|
4
4
|
*
|
|
@@ -11,7 +11,7 @@ import { type FigureAuthoringAction, type FigureRoomRefit } from '@hatiolab/figu
|
|
|
11
11
|
* 막는다. 배치 기준은 이미 선언된 것을 지키고, 처음 선언하는 도형은 바닥에 선다고 둔다 — 새로 만든 도형의
|
|
12
12
|
* 모델러 기본이 바닥이다.
|
|
13
13
|
*/
|
|
14
|
-
export declare function occupancyFromParts(
|
|
14
|
+
export declare function occupancyFromParts(asset: FigureAsset,
|
|
15
15
|
/** A different face to place it by — the properties tab's choice. Absent keeps the declared one. */
|
|
16
16
|
placement?: 'floor' | 'ceiling' | 'center' | 'wall'): Extract<FigureAuthoringAction, {
|
|
17
17
|
kind: 'declare-occupancy';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { proposeFigureOccupancy } from '@hatiolab/figure-model';
|
|
2
2
|
/**
|
|
3
3
|
* 「부품에 맞추기」의 짝 — 공간을 지금 부품에서 다시 선언하는 한 걸음.
|
|
4
4
|
*
|
|
@@ -11,10 +11,9 @@ import { parseFigureAsset, proposeFigureOccupancy } from '@hatiolab/figure-model
|
|
|
11
11
|
* 막는다. 배치 기준은 이미 선언된 것을 지키고, 처음 선언하는 도형은 바닥에 선다고 둔다 — 새로 만든 도형의
|
|
12
12
|
* 모델러 기본이 바닥이다.
|
|
13
13
|
*/
|
|
14
|
-
export function occupancyFromParts(
|
|
14
|
+
export function occupancyFromParts(asset,
|
|
15
15
|
/** A different face to place it by — the properties tab's choice. Absent keeps the declared one. */
|
|
16
16
|
placement) {
|
|
17
|
-
const asset = parseFigureAsset(source);
|
|
18
17
|
return {
|
|
19
18
|
kind: 'declare-occupancy',
|
|
20
19
|
proposal: proposeFigureOccupancy(asset, { over: 'range' }),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"occupancy.js","sourceRoot":"","sources":["../../client/modeller/occupancy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"occupancy.js","sourceRoot":"","sources":["../../client/modeller/occupancy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAsE,MAAM,wBAAwB,CAAA;AAEnI;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,kBAAkB,CAChC,KAAkB;AAClB,oGAAoG;AACpG,SAAmD;IAEnD,OAAO;QACL,IAAI,EAAE,mBAAmB;QACzB,QAAQ,EAAE,sBAAsB,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;QAC1D,qGAAqG;QACrG,GAAG,CAAC,SAAS,IAAI,SAAS,KAAK,CAAC,KAAK,CAAC,SAAS,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAClF,CAAA;AACH,CAAC;AAcD,MAAM,UAAU,eAAe,CAAC,KAAsB;IACpD,MAAM,IAAI,GAAG,CAAC,CAA+B,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAA;IAC/F,MAAM,IAAI,GAAmB,EAAE,CAAA;IAC/B,KAAK,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,SAAS,CAAU,EAAE,CAAC;QAClD,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC,EACjC,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAA;QAC/B,IAAI,CAAC,QAAQ,IAAI,CAAC,QAAQ;YAAE,SAAQ;QACpC,KAAK,MAAM,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAU;YACzC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAA;IAChJ,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC","sourcesContent":["import { proposeFigureOccupancy, type FigureAsset, type FigureAuthoringAction, type FigureRoomRefit } from '@hatiolab/figure-model'\n\n/**\n * 「부품에 맞추기」의 짝 — 공간을 지금 부품에서 다시 선언하는 한 걸음.\n *\n * figure-model 은 이것을 둘로 나눠 두었다: 제안(`proposeFigureOccupancy`)은 무엇이 선언될지를 보여 주고, 확인\n * (`declare-occupancy`)은 그 제안이 읽은 모양 그대로일 때만 받는다. 저작자가 누른 단추가 그 확인이다. 제안과 확인\n * 사이에 모델이 바뀌면 figure-model 이 `OCCUPANCY_STALE` 로 거절하므로, 여기서는 누른 순간의 원본으로 둘을 한\n * 번에 만든다.\n *\n * 범위는 움직임 전체다(`over: 'range'`). 쉬는 자세만 담으면 움직이는 부품이 선언한 공간을 넘어 발행 판정이 다시\n * 막는다. 배치 기준은 이미 선언된 것을 지키고, 처음 선언하는 도형은 바닥에 선다고 둔다 — 새로 만든 도형의\n * 모델러 기본이 바닥이다.\n */\nexport function occupancyFromParts(\n asset: FigureAsset,\n /** A different face to place it by — the properties tab's choice. Absent keeps the declared one. */\n placement?: 'floor' | 'ceiling' | 'center' | 'wall'\n): Extract<FigureAuthoringAction, { kind: 'declare-occupancy' }> {\n return {\n kind: 'declare-occupancy',\n proposal: proposeFigureOccupancy(asset, { over: 'range' }),\n /* The placement is the figure's own now (ADR-0097): named only when a different one is asked for. */\n ...(placement && placement !== (asset.placement ?? 'floor') ? { placement } : {})\n }\n}\n\n/**\n * 공간 다시 계산의 확인 표 — 선언된 공간과 제안된 공간을 그린 크기와 가장 큰 크기에서, 축마다 한 줄로. 같은 줄은\n * 같다고 적는다. 밖으로 나간 부품은 figure-model 이 잰 것만 적는다 — 재지 않은 것을 「넘친다」고 단정하지 않는다\n * (설계자 2026-09-27).\n */\nexport interface RoomRefitRow {\n at: 'authored' | 'largest'\n axis: 'x' | 'y' | 'z'\n declared: string\n proposed: string\n same: boolean\n}\nexport function roomRefitRowsOf(refit: FigureRoomRefit): RoomRefitRow[] {\n const span = (b: { min: number; max: number }) => `${Math.round(b.min)} ~ ${Math.round(b.max)}`\n const rows: RoomRefitRow[] = []\n for (const at of ['authored', 'largest'] as const) {\n const declared = refit.declared[at],\n proposed = refit.proposed[at]\n if (!declared || !proposed) continue\n for (const axis of ['x', 'y', 'z'] as const)\n rows.push({ at, axis, declared: span(declared[axis]), proposed: span(proposed[axis]), same: span(declared[axis]) === span(proposed[axis]) })\n }\n return rows\n}\n"]}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type FigureRevision } from './revision.js';
|
|
1
2
|
import { type FigureInputChange, type FigurePrimitiveCreation, type FigurePartEdit, type FigureAuthoringAction, type FigureProposalStep, type FigureClipConsent } from '@hatiolab/figure-model';
|
|
2
3
|
/** Consent to let clips go, and the figure and version it was given on. */
|
|
3
4
|
export interface FigureClipConsentFor extends FigureClipConsent {
|
|
@@ -46,6 +47,7 @@ export declare function createFigureProductSession(store: FigureProductStore): {
|
|
|
46
47
|
canUndo?: undefined;
|
|
47
48
|
canRedo?: undefined;
|
|
48
49
|
palette?: undefined;
|
|
50
|
+
asset?: undefined;
|
|
49
51
|
} | {
|
|
50
52
|
id: string | undefined;
|
|
51
53
|
loading: boolean;
|
|
@@ -57,15 +59,16 @@ export declare function createFigureProductSession(store: FigureProductStore): {
|
|
|
57
59
|
dirty: boolean;
|
|
58
60
|
canUndo: boolean;
|
|
59
61
|
canRedo: boolean;
|
|
60
|
-
fields: Array<
|
|
61
|
-
/** Product lifecycle boundary. A late response can never change another editor's asset or ID. */
|
|
62
|
-
import("@hatiolab/figure-model").FigureAsset["document"]["model"]["inputs"][number] & {
|
|
62
|
+
fields: Array<import("@hatiolab/figure-model").FigureAsset["document"]["model"]["inputs"][number] & {
|
|
63
63
|
value: number;
|
|
64
64
|
}>;
|
|
65
|
+
asset: import("@hatiolab/figure-model").FigureAsset;
|
|
65
66
|
geometry: import("@hatiolab/figure-model").FigureAssetGeometry[];
|
|
66
67
|
bindings: import("@hatiolab/figure-model").FigureEvaluatedBinding[];
|
|
67
68
|
palette: Record<string, string>;
|
|
68
69
|
};
|
|
70
|
+
/** The figure on screen at its version, for every panel and helper to read; none while nothing is open. Readable while saving. */
|
|
71
|
+
revisionNow: () => FigureRevision | undefined;
|
|
69
72
|
/**
|
|
70
73
|
* The source the server is confirmed to hold: loaded, or answered by a save. What a revert sends as `expectedSource`
|
|
71
74
|
* -- the server writes over it only while it still holds this. Nothing for a figure not saved yet.
|
|
@@ -88,6 +91,7 @@ export declare function createFigureProductSession(store: FigureProductStore): {
|
|
|
88
91
|
canUndo?: undefined;
|
|
89
92
|
canRedo?: undefined;
|
|
90
93
|
palette?: undefined;
|
|
94
|
+
asset?: undefined;
|
|
91
95
|
} | {
|
|
92
96
|
id: string | undefined;
|
|
93
97
|
loading: boolean;
|
|
@@ -99,11 +103,10 @@ export declare function createFigureProductSession(store: FigureProductStore): {
|
|
|
99
103
|
dirty: boolean;
|
|
100
104
|
canUndo: boolean;
|
|
101
105
|
canRedo: boolean;
|
|
102
|
-
fields: Array<
|
|
103
|
-
/** Product lifecycle boundary. A late response can never change another editor's asset or ID. */
|
|
104
|
-
import("@hatiolab/figure-model").FigureAsset["document"]["model"]["inputs"][number] & {
|
|
106
|
+
fields: Array<import("@hatiolab/figure-model").FigureAsset["document"]["model"]["inputs"][number] & {
|
|
105
107
|
value: number;
|
|
106
108
|
}>;
|
|
109
|
+
asset: import("@hatiolab/figure-model").FigureAsset;
|
|
107
110
|
geometry: import("@hatiolab/figure-model").FigureAssetGeometry[];
|
|
108
111
|
bindings: import("@hatiolab/figure-model").FigureEvaluatedBinding[];
|
|
109
112
|
palette: Record<string, string>;
|
|
@@ -125,6 +128,7 @@ export declare function createFigureProductSession(store: FigureProductStore): {
|
|
|
125
128
|
canUndo?: undefined;
|
|
126
129
|
canRedo?: undefined;
|
|
127
130
|
palette?: undefined;
|
|
131
|
+
asset?: undefined;
|
|
128
132
|
} | {
|
|
129
133
|
id: string | undefined;
|
|
130
134
|
loading: boolean;
|
|
@@ -136,11 +140,10 @@ export declare function createFigureProductSession(store: FigureProductStore): {
|
|
|
136
140
|
dirty: boolean;
|
|
137
141
|
canUndo: boolean;
|
|
138
142
|
canRedo: boolean;
|
|
139
|
-
fields: Array<
|
|
140
|
-
/** Product lifecycle boundary. A late response can never change another editor's asset or ID. */
|
|
141
|
-
import("@hatiolab/figure-model").FigureAsset["document"]["model"]["inputs"][number] & {
|
|
143
|
+
fields: Array<import("@hatiolab/figure-model").FigureAsset["document"]["model"]["inputs"][number] & {
|
|
142
144
|
value: number;
|
|
143
145
|
}>;
|
|
146
|
+
asset: import("@hatiolab/figure-model").FigureAsset;
|
|
144
147
|
geometry: import("@hatiolab/figure-model").FigureAssetGeometry[];
|
|
145
148
|
bindings: import("@hatiolab/figure-model").FigureEvaluatedBinding[];
|
|
146
149
|
palette: Record<string, string>;
|
|
@@ -162,6 +165,7 @@ export declare function createFigureProductSession(store: FigureProductStore): {
|
|
|
162
165
|
canUndo?: undefined;
|
|
163
166
|
canRedo?: undefined;
|
|
164
167
|
palette?: undefined;
|
|
168
|
+
asset?: undefined;
|
|
165
169
|
} | {
|
|
166
170
|
id: string | undefined;
|
|
167
171
|
loading: boolean;
|
|
@@ -173,11 +177,10 @@ export declare function createFigureProductSession(store: FigureProductStore): {
|
|
|
173
177
|
dirty: boolean;
|
|
174
178
|
canUndo: boolean;
|
|
175
179
|
canRedo: boolean;
|
|
176
|
-
fields: Array<
|
|
177
|
-
/** Product lifecycle boundary. A late response can never change another editor's asset or ID. */
|
|
178
|
-
import("@hatiolab/figure-model").FigureAsset["document"]["model"]["inputs"][number] & {
|
|
180
|
+
fields: Array<import("@hatiolab/figure-model").FigureAsset["document"]["model"]["inputs"][number] & {
|
|
179
181
|
value: number;
|
|
180
182
|
}>;
|
|
183
|
+
asset: import("@hatiolab/figure-model").FigureAsset;
|
|
181
184
|
geometry: import("@hatiolab/figure-model").FigureAssetGeometry[];
|
|
182
185
|
bindings: import("@hatiolab/figure-model").FigureEvaluatedBinding[];
|
|
183
186
|
palette: Record<string, string>;
|
|
@@ -193,6 +196,7 @@ export declare function createFigureProductSession(store: FigureProductStore): {
|
|
|
193
196
|
canUndo?: undefined;
|
|
194
197
|
canRedo?: undefined;
|
|
195
198
|
palette?: undefined;
|
|
199
|
+
asset?: undefined;
|
|
196
200
|
} | {
|
|
197
201
|
context: string;
|
|
198
202
|
name: string;
|
|
@@ -200,11 +204,10 @@ export declare function createFigureProductSession(store: FigureProductStore): {
|
|
|
200
204
|
dirty: boolean;
|
|
201
205
|
canUndo: boolean;
|
|
202
206
|
canRedo: boolean;
|
|
203
|
-
fields: Array<
|
|
204
|
-
/** Product lifecycle boundary. A late response can never change another editor's asset or ID. */
|
|
205
|
-
import("@hatiolab/figure-model").FigureAsset["document"]["model"]["inputs"][number] & {
|
|
207
|
+
fields: Array<import("@hatiolab/figure-model").FigureAsset["document"]["model"]["inputs"][number] & {
|
|
206
208
|
value: number;
|
|
207
209
|
}>;
|
|
210
|
+
readonly asset: import("@hatiolab/figure-model").FigureAsset;
|
|
208
211
|
geometry: import("@hatiolab/figure-model").FigureAssetGeometry[];
|
|
209
212
|
bindings: import("@hatiolab/figure-model").FigureEvaluatedBinding[];
|
|
210
213
|
palette: Record<string, string>;
|
|
@@ -224,6 +227,7 @@ export declare function createFigureProductSession(store: FigureProductStore): {
|
|
|
224
227
|
canUndo?: undefined;
|
|
225
228
|
canRedo?: undefined;
|
|
226
229
|
palette?: undefined;
|
|
230
|
+
asset?: undefined;
|
|
227
231
|
} | {
|
|
228
232
|
context: string;
|
|
229
233
|
name: string;
|
|
@@ -231,11 +235,10 @@ export declare function createFigureProductSession(store: FigureProductStore): {
|
|
|
231
235
|
dirty: boolean;
|
|
232
236
|
canUndo: boolean;
|
|
233
237
|
canRedo: boolean;
|
|
234
|
-
fields: Array<
|
|
235
|
-
/** Product lifecycle boundary. A late response can never change another editor's asset or ID. */
|
|
236
|
-
import("@hatiolab/figure-model").FigureAsset["document"]["model"]["inputs"][number] & {
|
|
238
|
+
fields: Array<import("@hatiolab/figure-model").FigureAsset["document"]["model"]["inputs"][number] & {
|
|
237
239
|
value: number;
|
|
238
240
|
}>;
|
|
241
|
+
readonly asset: import("@hatiolab/figure-model").FigureAsset;
|
|
239
242
|
geometry: import("@hatiolab/figure-model").FigureAssetGeometry[];
|
|
240
243
|
bindings: import("@hatiolab/figure-model").FigureEvaluatedBinding[];
|
|
241
244
|
palette: Record<string, string>;
|
|
@@ -263,6 +266,7 @@ export declare function createFigureProductSession(store: FigureProductStore): {
|
|
|
263
266
|
canUndo?: undefined;
|
|
264
267
|
canRedo?: undefined;
|
|
265
268
|
palette?: undefined;
|
|
269
|
+
asset?: undefined;
|
|
266
270
|
} | {
|
|
267
271
|
context: string;
|
|
268
272
|
name: string;
|
|
@@ -270,11 +274,10 @@ export declare function createFigureProductSession(store: FigureProductStore): {
|
|
|
270
274
|
dirty: boolean;
|
|
271
275
|
canUndo: boolean;
|
|
272
276
|
canRedo: boolean;
|
|
273
|
-
fields: Array<
|
|
274
|
-
/** Product lifecycle boundary. A late response can never change another editor's asset or ID. */
|
|
275
|
-
import("@hatiolab/figure-model").FigureAsset["document"]["model"]["inputs"][number] & {
|
|
277
|
+
fields: Array<import("@hatiolab/figure-model").FigureAsset["document"]["model"]["inputs"][number] & {
|
|
276
278
|
value: number;
|
|
277
279
|
}>;
|
|
280
|
+
readonly asset: import("@hatiolab/figure-model").FigureAsset;
|
|
278
281
|
geometry: import("@hatiolab/figure-model").FigureAssetGeometry[];
|
|
279
282
|
bindings: import("@hatiolab/figure-model").FigureEvaluatedBinding[];
|
|
280
283
|
palette: Record<string, string>;
|
|
@@ -290,6 +293,7 @@ export declare function createFigureProductSession(store: FigureProductStore): {
|
|
|
290
293
|
canUndo?: undefined;
|
|
291
294
|
canRedo?: undefined;
|
|
292
295
|
palette?: undefined;
|
|
296
|
+
asset?: undefined;
|
|
293
297
|
} | {
|
|
294
298
|
context: string;
|
|
295
299
|
name: string;
|
|
@@ -297,11 +301,10 @@ export declare function createFigureProductSession(store: FigureProductStore): {
|
|
|
297
301
|
dirty: boolean;
|
|
298
302
|
canUndo: boolean;
|
|
299
303
|
canRedo: boolean;
|
|
300
|
-
fields: Array<
|
|
301
|
-
/** Product lifecycle boundary. A late response can never change another editor's asset or ID. */
|
|
302
|
-
import("@hatiolab/figure-model").FigureAsset["document"]["model"]["inputs"][number] & {
|
|
304
|
+
fields: Array<import("@hatiolab/figure-model").FigureAsset["document"]["model"]["inputs"][number] & {
|
|
303
305
|
value: number;
|
|
304
306
|
}>;
|
|
307
|
+
readonly asset: import("@hatiolab/figure-model").FigureAsset;
|
|
305
308
|
geometry: import("@hatiolab/figure-model").FigureAssetGeometry[];
|
|
306
309
|
bindings: import("@hatiolab/figure-model").FigureEvaluatedBinding[];
|
|
307
310
|
palette: Record<string, string>;
|
|
@@ -317,6 +320,7 @@ export declare function createFigureProductSession(store: FigureProductStore): {
|
|
|
317
320
|
canUndo?: undefined;
|
|
318
321
|
canRedo?: undefined;
|
|
319
322
|
palette?: undefined;
|
|
323
|
+
asset?: undefined;
|
|
320
324
|
} | {
|
|
321
325
|
context: string;
|
|
322
326
|
name: string;
|
|
@@ -324,11 +328,10 @@ export declare function createFigureProductSession(store: FigureProductStore): {
|
|
|
324
328
|
dirty: boolean;
|
|
325
329
|
canUndo: boolean;
|
|
326
330
|
canRedo: boolean;
|
|
327
|
-
fields: Array<
|
|
328
|
-
/** Product lifecycle boundary. A late response can never change another editor's asset or ID. */
|
|
329
|
-
import("@hatiolab/figure-model").FigureAsset["document"]["model"]["inputs"][number] & {
|
|
331
|
+
fields: Array<import("@hatiolab/figure-model").FigureAsset["document"]["model"]["inputs"][number] & {
|
|
330
332
|
value: number;
|
|
331
333
|
}>;
|
|
334
|
+
readonly asset: import("@hatiolab/figure-model").FigureAsset;
|
|
332
335
|
geometry: import("@hatiolab/figure-model").FigureAssetGeometry[];
|
|
333
336
|
bindings: import("@hatiolab/figure-model").FigureEvaluatedBinding[];
|
|
334
337
|
palette: Record<string, string>;
|
|
@@ -344,6 +347,7 @@ export declare function createFigureProductSession(store: FigureProductStore): {
|
|
|
344
347
|
canUndo?: undefined;
|
|
345
348
|
canRedo?: undefined;
|
|
346
349
|
palette?: undefined;
|
|
350
|
+
asset?: undefined;
|
|
347
351
|
} | {
|
|
348
352
|
removal: import("@hatiolab/figure-model").RemovalNotice;
|
|
349
353
|
context: string;
|
|
@@ -355,6 +359,7 @@ export declare function createFigureProductSession(store: FigureProductStore): {
|
|
|
355
359
|
fields: Array<import("@hatiolab/figure-model").FigureAsset["document"]["model"]["inputs"][number] & {
|
|
356
360
|
value: number;
|
|
357
361
|
}>;
|
|
362
|
+
asset: import("@hatiolab/figure-model").FigureAsset;
|
|
358
363
|
geometry: import("@hatiolab/figure-model").FigureAssetGeometry[];
|
|
359
364
|
bindings: import("@hatiolab/figure-model").FigureEvaluatedBinding[];
|
|
360
365
|
palette: Record<string, string>;
|
|
@@ -381,6 +386,7 @@ export declare function createFigureProductSession(store: FigureProductStore): {
|
|
|
381
386
|
canUndo?: undefined;
|
|
382
387
|
canRedo?: undefined;
|
|
383
388
|
palette?: undefined;
|
|
389
|
+
asset?: undefined;
|
|
384
390
|
} | {
|
|
385
391
|
context: string;
|
|
386
392
|
name: string;
|
|
@@ -388,11 +394,10 @@ export declare function createFigureProductSession(store: FigureProductStore): {
|
|
|
388
394
|
dirty: boolean;
|
|
389
395
|
canUndo: boolean;
|
|
390
396
|
canRedo: boolean;
|
|
391
|
-
fields: Array<
|
|
392
|
-
/** Product lifecycle boundary. A late response can never change another editor's asset or ID. */
|
|
393
|
-
import("@hatiolab/figure-model").FigureAsset["document"]["model"]["inputs"][number] & {
|
|
397
|
+
fields: Array<import("@hatiolab/figure-model").FigureAsset["document"]["model"]["inputs"][number] & {
|
|
394
398
|
value: number;
|
|
395
399
|
}>;
|
|
400
|
+
readonly asset: import("@hatiolab/figure-model").FigureAsset;
|
|
396
401
|
geometry: import("@hatiolab/figure-model").FigureAssetGeometry[];
|
|
397
402
|
bindings: import("@hatiolab/figure-model").FigureEvaluatedBinding[];
|
|
398
403
|
palette: Record<string, string>;
|
|
@@ -414,6 +419,7 @@ export declare function createFigureProductSession(store: FigureProductStore): {
|
|
|
414
419
|
canUndo?: undefined;
|
|
415
420
|
canRedo?: undefined;
|
|
416
421
|
palette?: undefined;
|
|
422
|
+
asset?: undefined;
|
|
417
423
|
} | {
|
|
418
424
|
context: string;
|
|
419
425
|
name: string;
|
|
@@ -421,11 +427,10 @@ export declare function createFigureProductSession(store: FigureProductStore): {
|
|
|
421
427
|
dirty: boolean;
|
|
422
428
|
canUndo: boolean;
|
|
423
429
|
canRedo: boolean;
|
|
424
|
-
fields: Array<
|
|
425
|
-
/** Product lifecycle boundary. A late response can never change another editor's asset or ID. */
|
|
426
|
-
import("@hatiolab/figure-model").FigureAsset["document"]["model"]["inputs"][number] & {
|
|
430
|
+
fields: Array<import("@hatiolab/figure-model").FigureAsset["document"]["model"]["inputs"][number] & {
|
|
427
431
|
value: number;
|
|
428
432
|
}>;
|
|
433
|
+
readonly asset: import("@hatiolab/figure-model").FigureAsset;
|
|
429
434
|
geometry: import("@hatiolab/figure-model").FigureAssetGeometry[];
|
|
430
435
|
bindings: import("@hatiolab/figure-model").FigureEvaluatedBinding[];
|
|
431
436
|
palette: Record<string, string>;
|
|
@@ -441,6 +446,7 @@ export declare function createFigureProductSession(store: FigureProductStore): {
|
|
|
441
446
|
canUndo?: undefined;
|
|
442
447
|
canRedo?: undefined;
|
|
443
448
|
palette?: undefined;
|
|
449
|
+
asset?: undefined;
|
|
444
450
|
} | {
|
|
445
451
|
context: string;
|
|
446
452
|
name: string;
|
|
@@ -448,11 +454,10 @@ export declare function createFigureProductSession(store: FigureProductStore): {
|
|
|
448
454
|
dirty: boolean;
|
|
449
455
|
canUndo: boolean;
|
|
450
456
|
canRedo: boolean;
|
|
451
|
-
fields: Array<
|
|
452
|
-
/** Product lifecycle boundary. A late response can never change another editor's asset or ID. */
|
|
453
|
-
import("@hatiolab/figure-model").FigureAsset["document"]["model"]["inputs"][number] & {
|
|
457
|
+
fields: Array<import("@hatiolab/figure-model").FigureAsset["document"]["model"]["inputs"][number] & {
|
|
454
458
|
value: number;
|
|
455
459
|
}>;
|
|
460
|
+
readonly asset: import("@hatiolab/figure-model").FigureAsset;
|
|
456
461
|
geometry: import("@hatiolab/figure-model").FigureAssetGeometry[];
|
|
457
462
|
bindings: import("@hatiolab/figure-model").FigureEvaluatedBinding[];
|
|
458
463
|
palette: Record<string, string>;
|
|
@@ -483,6 +488,7 @@ export declare function createFigureProductSession(store: FigureProductStore): {
|
|
|
483
488
|
canUndo?: undefined;
|
|
484
489
|
canRedo?: undefined;
|
|
485
490
|
palette?: undefined;
|
|
491
|
+
asset?: undefined;
|
|
486
492
|
} | {
|
|
487
493
|
id: string | undefined;
|
|
488
494
|
loading: boolean;
|
|
@@ -494,11 +500,10 @@ export declare function createFigureProductSession(store: FigureProductStore): {
|
|
|
494
500
|
dirty: boolean;
|
|
495
501
|
canUndo: boolean;
|
|
496
502
|
canRedo: boolean;
|
|
497
|
-
fields: Array<
|
|
498
|
-
/** Product lifecycle boundary. A late response can never change another editor's asset or ID. */
|
|
499
|
-
import("@hatiolab/figure-model").FigureAsset["document"]["model"]["inputs"][number] & {
|
|
503
|
+
fields: Array<import("@hatiolab/figure-model").FigureAsset["document"]["model"]["inputs"][number] & {
|
|
500
504
|
value: number;
|
|
501
505
|
}>;
|
|
506
|
+
asset: import("@hatiolab/figure-model").FigureAsset;
|
|
502
507
|
geometry: import("@hatiolab/figure-model").FigureAssetGeometry[];
|
|
503
508
|
bindings: import("@hatiolab/figure-model").FigureEvaluatedBinding[];
|
|
504
509
|
palette: Record<string, string>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { thumbnailHashOf } from './thumbnail-hash.js';
|
|
2
|
+
import { revisionFromView } from './revision.js';
|
|
2
3
|
import { createFigureAsset, createFigureEditorWorkspace, serializeFigureAsset, parseFigureAsset } from '@hatiolab/figure-model';
|
|
3
4
|
/** Product lifecycle boundary. A late response can never change another editor's asset or ID. */
|
|
4
5
|
export function createFigureProductSession(store) {
|
|
@@ -31,6 +32,20 @@ export function createFigureProductSession(store) {
|
|
|
31
32
|
function read() {
|
|
32
33
|
return { ...workspace.read(), id, loading, saving, error };
|
|
33
34
|
}
|
|
35
|
+
/*
|
|
36
|
+
The version on screen, one object for as long as it is the version: the workspace hands out one asset per version
|
|
37
|
+
(and another once a save is acknowledged, or the figure moves on), so that object says whether the last one holds.
|
|
38
|
+
*/
|
|
39
|
+
let shown;
|
|
40
|
+
function revisionNow() {
|
|
41
|
+
const view = workspace.read();
|
|
42
|
+
if (!view.context || view.revision === null)
|
|
43
|
+
return (shown = undefined);
|
|
44
|
+
const context = view.context;
|
|
45
|
+
if (shown?.asset !== view.asset)
|
|
46
|
+
shown = revisionFromView(view, () => workspace.export({ context }).text);
|
|
47
|
+
return shown;
|
|
48
|
+
}
|
|
34
49
|
/* Texts sent whose save was never answered: found on the server, the server is confirmed to hold what was sent. */
|
|
35
50
|
const unconfirmed = new Map();
|
|
36
51
|
/*
|
|
@@ -78,6 +93,8 @@ export function createFigureProductSession(store) {
|
|
|
78
93
|
}
|
|
79
94
|
return {
|
|
80
95
|
read,
|
|
96
|
+
/** The figure on screen at its version, for every panel and helper to read; none while nothing is open. Readable while saving. */
|
|
97
|
+
revisionNow,
|
|
81
98
|
/**
|
|
82
99
|
* The source the server is confirmed to hold: loaded, or answered by a save. What a revert sends as `expectedSource`
|
|
83
100
|
* -- the server writes over it only while it still holds this. Nothing for a figure not saved yet.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"product-session.js","sourceRoot":"","sources":["../../client/modeller/product-session.ts"],"names":[],"mappings":"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","sourcesContent":["import { thumbnailHashOf } from './thumbnail-hash.js'\nimport {\n createFigureAsset,\n createFigureEditorWorkspace,\n serializeFigureAsset,\n parseFigureAsset,\n type FigureInputChange,\n type FigurePrimitiveCreation,\n type FigurePartEdit,\n type FigureAuthoringAction,\n type FigureProposalStep,\n type FigureClipConsent\n} from '@hatiolab/figure-model'\n\n/** Consent to let clips go, and the figure and version it was given on. */\nexport interface FigureClipConsentFor extends FigureClipConsent {\n context: string\n revision: number\n}\n\nexport interface FigureProductRecord {\n id: string\n source: string\n}\n\n/** Persistence is injected; neither the model engine nor the renderer owns the product ID. */\nexport interface FigureProductStore {\n load(id: string): Promise<FigureProductRecord>\n /** Every field a save writes, as the server holds it now: to tell whether a save whose answer was lost landed. */\n readBack?(id: string): Promise<FigureProductRecord & { thumbnailHash?: string; type?: string }>\n save(request: {\n id?: string\n source: string\n expectedSource?: string\n creationId?: string\n /** The card's picture. Absent when the browser could not draw one; the source is saved either way. */\n thumbnail?: string\n /** The type name a new figure is created under (the first save asks for it). Not sent for an existing one. */\n type?: string\n /** Keys of the AI proposals whose steps the source holds: the server records them with the save. */\n proposalKeys?: string[]\n }): Promise<FigureProductRecord>\n}\n\n/** Product lifecycle boundary. A late response can never change another editor's asset or ID. */\nexport function createFigureProductSession(store: FigureProductStore) {\n const workspace = createFigureEditorWorkspace()\n let epoch = 0,\n id: string | undefined,\n baseline: string | undefined\n let loading = false,\n saving = false,\n error: string | undefined\n let creationId: string | undefined\n /*\n The history as steps, each tagged with the AI proposal it came from (or nothing): mirrored here, since the\n workspace's history does not say where a step came from. A proposal is in the model while its step is on `done` --\n undone, it is not; undone and written past, it never comes back. What a save sends as `proposalKeys`.\n */\n let done: Array<string | undefined> = [],\n undone: Array<string | undefined> = []\n function tracked<T>(kind: 'do' | 'undo' | 'redo', run: () => T, tag?: string): T {\n const before = workspace.read().revision\n const result = run()\n if (workspace.read().revision === before) return result\n if (kind === 'do') {\n done.push(tag)\n undone = []\n } else if (kind === 'undo') undone.push(done.pop())\n else done.push(undone.pop())\n return result\n }\n const inEffect = () => [...new Set(done.filter((tag): tag is string => !!tag))]\n function read() {\n return { ...workspace.read(), id, loading, saving, error }\n }\n /* Texts sent whose save was never answered: found on the server, the server is confirmed to hold what was sent. */\n const unconfirmed = new Map<string, { thumbnail?: string }>()\n /*\n Consent to let clips go: given for one edit, on the figure and the version it was asked on. The first edit made under\n it takes it; an edit on another figure or another version is refused rather than let clips go that were never named\n for it (designer 2026-09-27).\n */\n let consent: FigureClipConsentFor | undefined\n const consented = () => {\n if (!consent) return {}\n const given = consent\n consent = undefined\n const view = workspace.read()\n if (view.context !== given.context || view.revision !== given.revision) {\n const stale = Object.assign(Error('확인한 뒤 모델이 바뀌어 적용하지 않았습니다. 다시 해 주세요.'), { code: 'CONSENT_STALE' })\n throw stale\n }\n return { dropClips: given.dropClips }\n }\n function writable() {\n const view = workspace.read()\n if (loading || error || !view.context || view.revision === null) throw Error('먼저 모델을 정상적으로 열어 주세요.')\n // Serializing writes also makes create retries safe: no second create while the first is pending.\n if (saving) throw Error('저장이 끝난 뒤 편집해 주세요.')\n return { context: view.context, expectedRevision: view.revision, origin: 'ui' as const }\n }\n function clear() {\n workspace.close({ discardDesignChanges: true })\n id = undefined\n baseline = undefined\n error = undefined\n saving = false\n creationId = undefined\n done = []\n undone = []\n }\n function canLeave(discard: boolean) {\n if (saving) throw Error('저장이 끝난 뒤 모델을 전환해 주세요.')\n if (workspace.read().dirty && !discard) throw Error('저장하지 않은 변경이 있습니다.')\n }\n return {\n read,\n /**\n * The source the server is confirmed to hold: loaded, or answered by a save. What a revert sends as `expectedSource`\n * -- the server writes over it only while it still holds this. Nothing for a figure not saved yet.\n */\n confirmedSource: () => (id ? baseline : undefined),\n startNew({ discard = false } = {}) {\n canLeave(discard)\n ++epoch\n clear()\n loading = false\n workspace.open(serializeFigureAsset(createFigureAsset('새 Figure')))\n creationId = crypto.randomUUID()\n return read()\n },\n async open(nextId: string, { discard = false } = {}) {\n canLeave(discard)\n const requestEpoch = ++epoch\n clear()\n loading = true\n try {\n const record = await store.load(nextId)\n if (requestEpoch !== epoch) return read()\n if (record.id !== nextId) throw Error('요청한 Figure와 응답의 대상이 다릅니다.')\n workspace.open(record.source)\n id = nextId\n baseline = record.source\n } catch (cause) {\n if (requestEpoch === epoch) error = cause instanceof Error ? cause.message : String(cause)\n } finally {\n if (requestEpoch === epoch) loading = false\n }\n return read()\n },\n close({ discard = false } = {}) {\n canLeave(discard)\n ++epoch\n clear()\n loading = false\n return read()\n },\n apply(changes: FigureInputChange[], origin: 'ui' | 'ai' = 'ui', expectedRevision?: number, context?: string) {\n const control = writable()\n // AI must supply the context AND revision captured when it started. Never substitute current values.\n if (origin === 'ai' && (context === undefined || expectedRevision === undefined))\n throw Error('AI 편집에는 요청 당시의 모델 문맥과 리비전이 필요합니다.')\n return tracked('do', () =>\n workspace.apply({\n ...control,\n origin,\n context: context ?? control.context,\n expectedRevision: expectedRevision ?? control.expectedRevision,\n changes\n })\n )\n },\n /**\n * An AI proposal (ADR-0094): the modeller's commands, as one step of the history, on the version the model saw.\n * Never on the current one instead -- a person's edit in between makes the proposal stale, and it is refused.\n */\n applyProposal(steps: readonly FigureProposalStep[], expectedRevision: number, context: string, key?: string) {\n writable()\n /* Never with consent: only the person may let a clip go, and an AI proposal is not the person (designer 2026-09-27). */\n return tracked('do', () => workspace.applyProposal({ origin: 'ai', context, expectedRevision, steps }), key)\n },\n /**\n * A new figure made from an AI proposal is created under the proposal's key (ADR-0097 §5.6 item 5): the same\n * proposal taken twice meets the first figure on the server, never a second one. Only on a new figure not yet\n * touched -- one being made is the person's, and keeps its own.\n */\n createUnder(key: string) {\n if (id || workspace.read().dirty) throw Error('손대지 않은 새 모델에만 쓸 수 있습니다.')\n if (!/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(key)) throw Error('제안 식별자가 올바르지 않습니다.')\n creationId = key\n },\n /** The AI proposals whose steps are in the model now, each once. */\n proposalsInEffect: inEffect,\n /**\n * A recipe put in by the person (ADR-0095): its resolved commands, as one step of the history, on the version open\n * now. The same path as a proposal; the person is its origin.\n */\n applyRecipe(steps: readonly FigureProposalStep[]) {\n const at = writable()\n return tracked('do', () => workspace.applyProposal({ origin: 'ui', context: at.context, expectedRevision: at.expectedRevision, steps, ...consented() }))\n },\n add(part: FigurePrimitiveCreation) {\n return tracked('do', () => workspace.addPrimitive({ ...writable(), part }))\n },\n edit(edit: FigurePartEdit) {\n return tracked('do', () => workspace.editPart({ ...writable(), edit, ...consented() }))\n },\n /** What a command would do, on a copy (ADR-0097 §5.5): nothing is written, and the answer names its version. */\n previewCommand(action: FigureAuthoringAction) {\n const view = workspace.read()\n if (loading || error || !view.context || view.revision === null) throw Error('먼저 모델을 정상적으로 열어 주세요.')\n return workspace.previewCommand({ context: view.context, action })\n },\n /** One authoring command — what a person means, not a graph edit — as one step of the same history. */\n command(action: FigureAuthoringAction) {\n return tracked('do', () => workspace.command({ ...writable(), action, ...consented() }))\n },\n /**\n * The first edit made inside `run` may let go of exactly these clips -- the ones a CLIPS_DROPPED refusal named and\n * the person agreed to -- on the figure and version they were named on (designer 2026-09-27). Any other edit that\n * would stop a clip is refused and asked about.\n */\n withConsent<T>(given: FigureClipConsentFor, run: () => T): T {\n consent = given\n try {\n return run()\n } finally {\n consent = undefined\n }\n },\n undo() {\n return tracked('undo', () => workspace.undo(writable()))\n },\n redo() {\n return tracked('redo', () => workspace.redo(writable()))\n },\n source() {\n const { context } = writable()\n return workspace.export({ context }).text\n },\n /**\n * Saves the model, and the card's picture when the caller managed to take one.\n *\n * The picture is the caller's because taking it needs a canvas, and this session is the place that knows\n * nothing about drawing. A save with no picture is a save: a tab in the background cannot draw, and the\n * source matters more than the card (see `thumbnail.ts`).\n */\n async save({ thumbnail, type }: { thumbnail?: string; type?: string } = {}) {\n const control = writable(),\n requestEpoch = epoch\n const exported = workspace.export(control)\n /* Read with the text it goes with: an edit while saving is not what this save holds. */\n const proposalKeys = inEffect()\n saving = true\n try {\n let record: FigureProductRecord\n try {\n record = await store.save({ id, source: exported.text, expectedSource: baseline, creationId, thumbnail, proposalKeys, ...(id || !type ? {} : { type }) })\n } catch (error) {\n /*\n A save that failed may still have landed: the answer lost on the way back (SAVE_UNREACHABLE), or a second\n try refused because the first did land -- its expected source is the old one (FIGURE_SOURCE_CONFLICT). Only\n those are read back; a refusal on grounds -- permission, validation -- stays a failure whatever the row\n holds. Read back, every field the save writes must be what was sent: the source, the picture, the type\n (designer 2026-09-27: a network failure alone does not say nothing was saved; nor does a matching source\n alone say it was).\n */\n const code = (error as { code?: string })?.code\n const unknown = code === 'SAVE_UNREACHABLE' || code === 'FIGURE_SOURCE_CONFLICT'\n const landed = unknown && id !== undefined && requestEpoch === epoch && store.readBack ? await store.readBack(id).catch(() => undefined) : undefined\n const held = landed ? serializeFigureAsset(parseFigureAsset(landed.source)) : undefined\n /* The picture is compared by the name the server keeps it under: the hash of its bytes (ADR-0104). */\n const holds = async (sent?: string) => sent === undefined || (!!landed && landed.thumbnailHash !== undefined && landed.thumbnailHash === (await thumbnailHashOf(sent)))\n const same = !!landed && held === exported.text && (await holds(thumbnail)) && (type === undefined || landed.type === type)\n if (same) record = landed!\n else if (\n code === 'FIGURE_SOURCE_CONFLICT' &&\n landed &&\n held !== undefined &&\n unconfirmed.has(held) &&\n /* Every field that earlier save wrote, not the source alone: nothing not compared is written over. */\n (await holds(unconfirmed.get(held)!.thumbnail))\n ) {\n /*\n The row holds a text this session sent and never heard back about -- edited further since, the new text\n is not it. The server is known to hold what was sent: this save is sent again, once, conditional on that\n server text as the expected source (designer 2026-09-27: \"the sent content is confirmed on the server\").\n */\n baseline = landed.source\n unconfirmed.clear()\n record = await store.save({ id, source: exported.text, expectedSource: baseline, creationId, thumbnail, proposalKeys, ...(id || !type ? {} : { type }) })\n } else {\n if (code === 'SAVE_UNREACHABLE') unconfirmed.set(exported.text, { thumbnail })\n throw error\n }\n }\n if (requestEpoch !== epoch) return read()\n if (!record.id || (id !== undefined && record.id !== id)) throw Error('저장 응답의 Figure 대상이 다릅니다.')\n if (serializeFigureAsset(parseFigureAsset(record.source)) !== exported.text)\n throw Error('저장 응답이 보낸 모델과 다릅니다. 저장 상태를 다시 확인해 주세요.')\n /* The server has it: the next save expects it, whatever happened on screen meanwhile. */\n id = record.id\n baseline = record.source\n unconfirmed.clear()\n /*\n Edited while it was saving: what was sent is saved, the edits since are not -- they stay, and the model stays\n unsaved (the workspace refuses a stale acknowledgement). Never marked saved, never wiped.\n */\n if (workspace.read().revision === exported.revision) workspace.acknowledgeSaved(exported)\n } finally {\n if (requestEpoch === epoch) saving = false\n }\n return read()\n }\n }\n}\nexport type FigureProductSession = ReturnType<typeof createFigureProductSession>\n"]}
|
|
1
|
+
{"version":3,"file":"product-session.js","sourceRoot":"","sources":["../../client/modeller/product-session.ts"],"names":[],"mappings":"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","sourcesContent":["import { thumbnailHashOf } from './thumbnail-hash.js'\nimport { revisionFromView, type FigureRevision } from './revision.js'\nimport {\n createFigureAsset,\n createFigureEditorWorkspace,\n serializeFigureAsset,\n parseFigureAsset,\n type FigureInputChange,\n type FigurePrimitiveCreation,\n type FigurePartEdit,\n type FigureAuthoringAction,\n type FigureProposalStep,\n type FigureClipConsent\n} from '@hatiolab/figure-model'\n\n/** Consent to let clips go, and the figure and version it was given on. */\nexport interface FigureClipConsentFor extends FigureClipConsent {\n context: string\n revision: number\n}\n\nexport interface FigureProductRecord {\n id: string\n source: string\n}\n\n/** Persistence is injected; neither the model engine nor the renderer owns the product ID. */\nexport interface FigureProductStore {\n load(id: string): Promise<FigureProductRecord>\n /** Every field a save writes, as the server holds it now: to tell whether a save whose answer was lost landed. */\n readBack?(id: string): Promise<FigureProductRecord & { thumbnailHash?: string; type?: string }>\n save(request: {\n id?: string\n source: string\n expectedSource?: string\n creationId?: string\n /** The card's picture. Absent when the browser could not draw one; the source is saved either way. */\n thumbnail?: string\n /** The type name a new figure is created under (the first save asks for it). Not sent for an existing one. */\n type?: string\n /** Keys of the AI proposals whose steps the source holds: the server records them with the save. */\n proposalKeys?: string[]\n }): Promise<FigureProductRecord>\n}\n\n/** Product lifecycle boundary. A late response can never change another editor's asset or ID. */\nexport function createFigureProductSession(store: FigureProductStore) {\n const workspace = createFigureEditorWorkspace()\n let epoch = 0,\n id: string | undefined,\n baseline: string | undefined\n let loading = false,\n saving = false,\n error: string | undefined\n let creationId: string | undefined\n /*\n The history as steps, each tagged with the AI proposal it came from (or nothing): mirrored here, since the\n workspace's history does not say where a step came from. A proposal is in the model while its step is on `done` --\n undone, it is not; undone and written past, it never comes back. What a save sends as `proposalKeys`.\n */\n let done: Array<string | undefined> = [],\n undone: Array<string | undefined> = []\n function tracked<T>(kind: 'do' | 'undo' | 'redo', run: () => T, tag?: string): T {\n const before = workspace.read().revision\n const result = run()\n if (workspace.read().revision === before) return result\n if (kind === 'do') {\n done.push(tag)\n undone = []\n } else if (kind === 'undo') undone.push(done.pop())\n else done.push(undone.pop())\n return result\n }\n const inEffect = () => [...new Set(done.filter((tag): tag is string => !!tag))]\n function read() {\n return { ...workspace.read(), id, loading, saving, error }\n }\n /*\n The version on screen, one object for as long as it is the version: the workspace hands out one asset per version\n (and another once a save is acknowledged, or the figure moves on), so that object says whether the last one holds.\n */\n let shown: FigureRevision | undefined\n function revisionNow(): FigureRevision | undefined {\n const view = workspace.read()\n if (!view.context || view.revision === null) return (shown = undefined)\n const context = view.context\n if (shown?.asset !== view.asset) shown = revisionFromView(view, () => workspace.export({ context }).text)\n return shown\n }\n /* Texts sent whose save was never answered: found on the server, the server is confirmed to hold what was sent. */\n const unconfirmed = new Map<string, { thumbnail?: string }>()\n /*\n Consent to let clips go: given for one edit, on the figure and the version it was asked on. The first edit made under\n it takes it; an edit on another figure or another version is refused rather than let clips go that were never named\n for it (designer 2026-09-27).\n */\n let consent: FigureClipConsentFor | undefined\n const consented = () => {\n if (!consent) return {}\n const given = consent\n consent = undefined\n const view = workspace.read()\n if (view.context !== given.context || view.revision !== given.revision) {\n const stale = Object.assign(Error('확인한 뒤 모델이 바뀌어 적용하지 않았습니다. 다시 해 주세요.'), { code: 'CONSENT_STALE' })\n throw stale\n }\n return { dropClips: given.dropClips }\n }\n function writable() {\n const view = workspace.read()\n if (loading || error || !view.context || view.revision === null) throw Error('먼저 모델을 정상적으로 열어 주세요.')\n // Serializing writes also makes create retries safe: no second create while the first is pending.\n if (saving) throw Error('저장이 끝난 뒤 편집해 주세요.')\n return { context: view.context, expectedRevision: view.revision, origin: 'ui' as const }\n }\n function clear() {\n workspace.close({ discardDesignChanges: true })\n id = undefined\n baseline = undefined\n error = undefined\n saving = false\n creationId = undefined\n done = []\n undone = []\n }\n function canLeave(discard: boolean) {\n if (saving) throw Error('저장이 끝난 뒤 모델을 전환해 주세요.')\n if (workspace.read().dirty && !discard) throw Error('저장하지 않은 변경이 있습니다.')\n }\n return {\n read,\n /** The figure on screen at its version, for every panel and helper to read; none while nothing is open. Readable while saving. */\n revisionNow,\n /**\n * The source the server is confirmed to hold: loaded, or answered by a save. What a revert sends as `expectedSource`\n * -- the server writes over it only while it still holds this. Nothing for a figure not saved yet.\n */\n confirmedSource: () => (id ? baseline : undefined),\n startNew({ discard = false } = {}) {\n canLeave(discard)\n ++epoch\n clear()\n loading = false\n workspace.open(serializeFigureAsset(createFigureAsset('새 Figure')))\n creationId = crypto.randomUUID()\n return read()\n },\n async open(nextId: string, { discard = false } = {}) {\n canLeave(discard)\n const requestEpoch = ++epoch\n clear()\n loading = true\n try {\n const record = await store.load(nextId)\n if (requestEpoch !== epoch) return read()\n if (record.id !== nextId) throw Error('요청한 Figure와 응답의 대상이 다릅니다.')\n workspace.open(record.source)\n id = nextId\n baseline = record.source\n } catch (cause) {\n if (requestEpoch === epoch) error = cause instanceof Error ? cause.message : String(cause)\n } finally {\n if (requestEpoch === epoch) loading = false\n }\n return read()\n },\n close({ discard = false } = {}) {\n canLeave(discard)\n ++epoch\n clear()\n loading = false\n return read()\n },\n apply(changes: FigureInputChange[], origin: 'ui' | 'ai' = 'ui', expectedRevision?: number, context?: string) {\n const control = writable()\n // AI must supply the context AND revision captured when it started. Never substitute current values.\n if (origin === 'ai' && (context === undefined || expectedRevision === undefined))\n throw Error('AI 편집에는 요청 당시의 모델 문맥과 리비전이 필요합니다.')\n return tracked('do', () =>\n workspace.apply({\n ...control,\n origin,\n context: context ?? control.context,\n expectedRevision: expectedRevision ?? control.expectedRevision,\n changes\n })\n )\n },\n /**\n * An AI proposal (ADR-0094): the modeller's commands, as one step of the history, on the version the model saw.\n * Never on the current one instead -- a person's edit in between makes the proposal stale, and it is refused.\n */\n applyProposal(steps: readonly FigureProposalStep[], expectedRevision: number, context: string, key?: string) {\n writable()\n /* Never with consent: only the person may let a clip go, and an AI proposal is not the person (designer 2026-09-27). */\n return tracked('do', () => workspace.applyProposal({ origin: 'ai', context, expectedRevision, steps }), key)\n },\n /**\n * A new figure made from an AI proposal is created under the proposal's key (ADR-0097 §5.6 item 5): the same\n * proposal taken twice meets the first figure on the server, never a second one. Only on a new figure not yet\n * touched -- one being made is the person's, and keeps its own.\n */\n createUnder(key: string) {\n if (id || workspace.read().dirty) throw Error('손대지 않은 새 모델에만 쓸 수 있습니다.')\n if (!/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(key)) throw Error('제안 식별자가 올바르지 않습니다.')\n creationId = key\n },\n /** The AI proposals whose steps are in the model now, each once. */\n proposalsInEffect: inEffect,\n /**\n * A recipe put in by the person (ADR-0095): its resolved commands, as one step of the history, on the version open\n * now. The same path as a proposal; the person is its origin.\n */\n applyRecipe(steps: readonly FigureProposalStep[]) {\n const at = writable()\n return tracked('do', () => workspace.applyProposal({ origin: 'ui', context: at.context, expectedRevision: at.expectedRevision, steps, ...consented() }))\n },\n add(part: FigurePrimitiveCreation) {\n return tracked('do', () => workspace.addPrimitive({ ...writable(), part }))\n },\n edit(edit: FigurePartEdit) {\n return tracked('do', () => workspace.editPart({ ...writable(), edit, ...consented() }))\n },\n /** What a command would do, on a copy (ADR-0097 §5.5): nothing is written, and the answer names its version. */\n previewCommand(action: FigureAuthoringAction) {\n const view = workspace.read()\n if (loading || error || !view.context || view.revision === null) throw Error('먼저 모델을 정상적으로 열어 주세요.')\n return workspace.previewCommand({ context: view.context, action })\n },\n /** One authoring command — what a person means, not a graph edit — as one step of the same history. */\n command(action: FigureAuthoringAction) {\n return tracked('do', () => workspace.command({ ...writable(), action, ...consented() }))\n },\n /**\n * The first edit made inside `run` may let go of exactly these clips -- the ones a CLIPS_DROPPED refusal named and\n * the person agreed to -- on the figure and version they were named on (designer 2026-09-27). Any other edit that\n * would stop a clip is refused and asked about.\n */\n withConsent<T>(given: FigureClipConsentFor, run: () => T): T {\n consent = given\n try {\n return run()\n } finally {\n consent = undefined\n }\n },\n undo() {\n return tracked('undo', () => workspace.undo(writable()))\n },\n redo() {\n return tracked('redo', () => workspace.redo(writable()))\n },\n source() {\n const { context } = writable()\n return workspace.export({ context }).text\n },\n /**\n * Saves the model, and the card's picture when the caller managed to take one.\n *\n * The picture is the caller's because taking it needs a canvas, and this session is the place that knows\n * nothing about drawing. A save with no picture is a save: a tab in the background cannot draw, and the\n * source matters more than the card (see `thumbnail.ts`).\n */\n async save({ thumbnail, type }: { thumbnail?: string; type?: string } = {}) {\n const control = writable(),\n requestEpoch = epoch\n const exported = workspace.export(control)\n /* Read with the text it goes with: an edit while saving is not what this save holds. */\n const proposalKeys = inEffect()\n saving = true\n try {\n let record: FigureProductRecord\n try {\n record = await store.save({ id, source: exported.text, expectedSource: baseline, creationId, thumbnail, proposalKeys, ...(id || !type ? {} : { type }) })\n } catch (error) {\n /*\n A save that failed may still have landed: the answer lost on the way back (SAVE_UNREACHABLE), or a second\n try refused because the first did land -- its expected source is the old one (FIGURE_SOURCE_CONFLICT). Only\n those are read back; a refusal on grounds -- permission, validation -- stays a failure whatever the row\n holds. Read back, every field the save writes must be what was sent: the source, the picture, the type\n (designer 2026-09-27: a network failure alone does not say nothing was saved; nor does a matching source\n alone say it was).\n */\n const code = (error as { code?: string })?.code\n const unknown = code === 'SAVE_UNREACHABLE' || code === 'FIGURE_SOURCE_CONFLICT'\n const landed = unknown && id !== undefined && requestEpoch === epoch && store.readBack ? await store.readBack(id).catch(() => undefined) : undefined\n const held = landed ? serializeFigureAsset(parseFigureAsset(landed.source)) : undefined\n /* The picture is compared by the name the server keeps it under: the hash of its bytes (ADR-0104). */\n const holds = async (sent?: string) => sent === undefined || (!!landed && landed.thumbnailHash !== undefined && landed.thumbnailHash === (await thumbnailHashOf(sent)))\n const same = !!landed && held === exported.text && (await holds(thumbnail)) && (type === undefined || landed.type === type)\n if (same) record = landed!\n else if (\n code === 'FIGURE_SOURCE_CONFLICT' &&\n landed &&\n held !== undefined &&\n unconfirmed.has(held) &&\n /* Every field that earlier save wrote, not the source alone: nothing not compared is written over. */\n (await holds(unconfirmed.get(held)!.thumbnail))\n ) {\n /*\n The row holds a text this session sent and never heard back about -- edited further since, the new text\n is not it. The server is known to hold what was sent: this save is sent again, once, conditional on that\n server text as the expected source (designer 2026-09-27: \"the sent content is confirmed on the server\").\n */\n baseline = landed.source\n unconfirmed.clear()\n record = await store.save({ id, source: exported.text, expectedSource: baseline, creationId, thumbnail, proposalKeys, ...(id || !type ? {} : { type }) })\n } else {\n if (code === 'SAVE_UNREACHABLE') unconfirmed.set(exported.text, { thumbnail })\n throw error\n }\n }\n if (requestEpoch !== epoch) return read()\n if (!record.id || (id !== undefined && record.id !== id)) throw Error('저장 응답의 Figure 대상이 다릅니다.')\n if (serializeFigureAsset(parseFigureAsset(record.source)) !== exported.text)\n throw Error('저장 응답이 보낸 모델과 다릅니다. 저장 상태를 다시 확인해 주세요.')\n /* The server has it: the next save expects it, whatever happened on screen meanwhile. */\n id = record.id\n baseline = record.source\n unconfirmed.clear()\n /*\n Edited while it was saving: what was sent is saved, the edits since are not -- they stay, and the model stays\n unsaved (the workspace refuses a stale acknowledgement). Never marked saved, never wiped.\n */\n if (workspace.read().revision === exported.revision) workspace.acknowledgeSaved(exported)\n } finally {\n if (requestEpoch === epoch) saving = false\n }\n return read()\n }\n }\n}\nexport type FigureProductSession = ReturnType<typeof createFigureProductSession>\n"]}
|