@things-factory/figure-ui 10.1.210 → 10.1.212
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/graphql/index.ts +1 -1
- package/client/modeller/angle-dial.ts +1 -1
- package/client/modeller/canvas.ts +1 -1
- package/client/modeller/editor.ts +24 -32
- package/client/modeller/figure-history-panel.ts +1 -1
- package/client/modeller/figure-links.ts +61 -37
- package/client/modeller/figure-parts.ts +3 -3
- package/client/modeller/figure-settings.ts +1 -1
- package/client/modeller/figure-size-editor.ts +41 -4
- package/client/modeller/links.ts +63 -24
- package/client/modeller/live.ts +33 -0
- package/client/modeller/scene-budget.ts +3 -3
- package/client/modeller/thumbnail.ts +1 -1
- package/client/pages/figure-list-page.ts +2 -2
- package/client/pages/figure-modeller-page.ts +4 -4
- package/client/types.ts +2 -2
- package/client/viewparts/figure-thumb.ts +1 -1
- package/dist-client/graphql/index.d.ts +1 -1
- package/dist-client/graphql/index.js +1 -1
- package/dist-client/graphql/index.js.map +1 -1
- package/dist-client/modeller/angle-dial.d.ts +1 -1
- package/dist-client/modeller/angle-dial.js.map +1 -1
- package/dist-client/modeller/canvas.js +1 -1
- package/dist-client/modeller/canvas.js.map +1 -1
- package/dist-client/modeller/editor.d.ts +5 -0
- package/dist-client/modeller/editor.js +24 -32
- package/dist-client/modeller/editor.js.map +1 -1
- package/dist-client/modeller/figure-history-panel.d.ts +1 -1
- package/dist-client/modeller/figure-history-panel.js.map +1 -1
- package/dist-client/modeller/figure-links.d.ts +2 -0
- package/dist-client/modeller/figure-links.js +66 -39
- package/dist-client/modeller/figure-links.js.map +1 -1
- package/dist-client/modeller/figure-parts.d.ts +3 -3
- package/dist-client/modeller/figure-parts.js.map +1 -1
- package/dist-client/modeller/figure-settings.js +1 -1
- package/dist-client/modeller/figure-settings.js.map +1 -1
- package/dist-client/modeller/figure-size-editor.d.ts +3 -1
- package/dist-client/modeller/figure-size-editor.js +45 -4
- package/dist-client/modeller/figure-size-editor.js.map +1 -1
- package/dist-client/modeller/links.d.ts +15 -6
- package/dist-client/modeller/links.js +63 -25
- package/dist-client/modeller/links.js.map +1 -1
- package/dist-client/modeller/live.d.ts +14 -0
- package/dist-client/modeller/live.js +31 -0
- package/dist-client/modeller/live.js.map +1 -1
- package/dist-client/modeller/scene-budget.d.ts +3 -3
- package/dist-client/modeller/scene-budget.js +3 -3
- package/dist-client/modeller/scene-budget.js.map +1 -1
- package/dist-client/modeller/thumbnail.js +1 -1
- package/dist-client/modeller/thumbnail.js.map +1 -1
- package/dist-client/pages/figure-list-page.d.ts +1 -1
- package/dist-client/pages/figure-list-page.js +2 -2
- package/dist-client/pages/figure-list-page.js.map +1 -1
- package/dist-client/pages/figure-modeller-page.d.ts +3 -3
- package/dist-client/pages/figure-modeller-page.js +4 -4
- package/dist-client/pages/figure-modeller-page.js.map +1 -1
- package/dist-client/route.d.ts +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/types.d.ts +2 -2
- package/dist-client/types.js +1 -1
- package/dist-client/types.js.map +1 -1
- package/dist-client/viewparts/figure-thumb.js +1 -1
- package/dist-client/viewparts/figure-thumb.js.map +1 -1
- package/docs/multipart-response-review-2026-10-24.md +1 -1
- package/docs/response-authoring-2026-10-24.md +4 -4
- package/package.json +6 -6
- package/test/ai-proposal.test.ts +1 -1
- package/test/align.test.ts +1 -1
- package/test/browser/editing.check.mjs +38 -0
- package/test/browser/modeller.check.mjs +55 -0
- package/test/command-preview.test.ts +3 -3
- package/test/fields.test.ts +2 -2
- package/test/first-minute.test.ts +5 -5
- package/test/first-save.test.ts +3 -3
- package/test/gizmo.test.ts +3 -3
- package/test/keys.test.ts +2 -2
- package/test/layout.test.ts +1 -1
- package/test/links.test.ts +53 -14
- package/test/live.test.ts +4 -4
- package/test/material.test.ts +1 -1
- package/test/properties.test.ts +1 -1
- package/test/proposal-record.test.ts +1 -1
- package/test/refusals.test.ts +1 -1
- package/test/resize-with-placement.test.ts +1 -1
- package/test/scene-budget.test.ts +4 -4
- package/test/verdict.test.ts +1 -1
- package/translations/en.json +3 -0
- package/translations/es.json +3 -0
- package/translations/ja.json +3 -0
- package/translations/ko.json +109 -106
- package/translations/ms.json +3 -0
- package/translations/zh.json +3 -0
|
@@ -69,6 +69,37 @@ export function scrubbed(start, pixels, step) {
|
|
|
69
69
|
const digits = Math.max(0, -Math.floor(Math.log10(step)));
|
|
70
70
|
return Number(value.toFixed(digits));
|
|
71
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* A label dragged sideways scrubs a value, as vector tools do: each move gives the value the pointer has come to, the
|
|
74
|
+
* release gives the last one, and a drag that ends where it began -- or that the system took away -- gives it back. The
|
|
75
|
+
* label holds the pointer for the drag. Every field that scrubs goes through here: the part's own numbers and the size
|
|
76
|
+
* editor's rows.
|
|
77
|
+
*/
|
|
78
|
+
export function scrubLabel(down, from, to) {
|
|
79
|
+
if (down.button !== 0)
|
|
80
|
+
return;
|
|
81
|
+
const label = down.currentTarget;
|
|
82
|
+
down.preventDefault();
|
|
83
|
+
label.setPointerCapture(down.pointerId);
|
|
84
|
+
let value = from.value;
|
|
85
|
+
const move = (e) => {
|
|
86
|
+
value = scrubbed(from.value, e.clientX - down.clientX, scrubStep(from.unit, from.value, e.shiftKey));
|
|
87
|
+
to.move(value);
|
|
88
|
+
};
|
|
89
|
+
const up = (e) => {
|
|
90
|
+
label.removeEventListener('pointermove', move);
|
|
91
|
+
label.removeEventListener('pointerup', up);
|
|
92
|
+
label.removeEventListener('pointercancel', up);
|
|
93
|
+
/* A drag the system took away is not the author's value. */
|
|
94
|
+
if (e.type === 'pointercancel' || value === from.value)
|
|
95
|
+
to.cancel();
|
|
96
|
+
else
|
|
97
|
+
to.release(value);
|
|
98
|
+
};
|
|
99
|
+
label.addEventListener('pointermove', move);
|
|
100
|
+
label.addEventListener('pointerup', up);
|
|
101
|
+
label.addEventListener('pointercancel', up);
|
|
102
|
+
}
|
|
72
103
|
/**
|
|
73
104
|
* The bodies to draw while a value is in hand, over every input of the version as the session holds it now: the pose the
|
|
74
105
|
* author has already set with the other controls, and the design values. Drawing over the asset's saved defaults put
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"live.js","sourceRoot":"","sources":["../../client/modeller/live.ts"],"names":[],"mappings":"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","sourcesContent":["import { applyFigureAuthoring, compileFigureAsset, editFigurePart } from '@hatiolab/figure-model'\nimport type { FigureAsset, FigureAuthoringAction, FigurePartEdit } from '@hatiolab/figure-model'\n\nimport { drawableFigureParts, type FigureRenderPart } from './renderer.js'\nimport type { FigureRevision } from './revision.js'\n\n/*\n * The figure while a value is in hand: evaluated with the values being typed or dragged, drawn at once and written\n * nowhere. The panels committed on release only, so the canvas stood still while a number was typed, a spinner held,\n * a label scrubbed, a slider or the glow dragged (user, 2026-09-24: \"make the editor move naturally while the value\n * changes\"). The value in hand shows here; the release is the one step of the history.\n *\n * What is in hand is either input values -- a design value (a size, a position) or a pose -- or one part edit (the\n * glow of a material). A design value is a change of the figure's design, so it is shown by evaluating a copy with\n * that value; a pose is a state override, as the preview gives it.\n */\n\nexport interface LiveValues {\n /** Input values in hand, design or state, by input id. */\n values: Record<string, number>\n /** A part edit in hand, not yet committed. */\n edit?: FigurePartEdit\n /** An authoring command in hand (a corner radius, an inside, being typed), not yet committed (ADR-0100 §3.3). */\n action?: FigureAuthoringAction\n}\n\nexport const NOTHING_LIVE: LiveValues = { values: {} }\n\n/** Known inputs with a number, split by what they are. An id the figure does not declare is dropped. */\nexport function liveOverridesOf(asset: FigureAsset, values: Record<string, number>): { design: Record<string, number>; state: Record<string, number> } {\n const roles = new Map(asset.document.model.inputs.map(i => [i.id, i.role]))\n const design: Record<string, number> = {},\n state: Record<string, number> = {}\n for (const [id, value] of Object.entries(values)) {\n if (!Number.isFinite(value)) continue\n const role = roles.get(id)\n if (role === 'state') state[id] = value\n else if (role !== undefined) design[id] = value\n }\n return { design, state }\n}\n\nexport function isLive(live: LiveValues): boolean {\n return !!live.edit || !!live.action || Object.keys(live.values).length > 0\n}\n\n/**\n * The bodies to draw with what is in hand, or null when nothing is -- then the evaluation as it stands is drawn.\n *\n * Also null when the figure cannot take the value in hand (a size of 0 on the way to typing 0.5, a glow the material\n * refuses): the canvas keeps the figure as it stands until the release, and the release reports why through the same\n * refusal every committed edit gets. A preview never invents a figure the commit would not make.\n */\nexport function livePartsOf(asset: FigureAsset, live: LiveValues): FigureRenderPart[] | null {\n if (!isLive(live)) return null\n try {\n let next = live.edit ? editFigurePart(asset, live.edit) : asset\n if (live.action) next = applyFigureAuthoring(next, live.action)\n /* Read against the figure the edit makes: a command can take an input away (one wall becomes one per side). */\n const { design, state } = liveOverridesOf(next, live.values)\n /*\n * An input the edit or the command in hand wrote takes its value: the values as they stand hold its old one (a\n * point's coordinate before it was moved, a corner's radius before it was typed again) and would put it back.\n */\n for (const id of Object.keys(design)) if (next.designInputs[id] !== asset.designInputs[id]) delete design[id]\n /* A value the figure already holds changes nothing: a pose in hand alone is drawn from the figure as it is, not a copy. */\n for (const id of Object.keys(design)) if (design[id] === next.designInputs[id]) delete design[id]\n if (Object.keys(design).length) next = { ...next, designInputs: { ...next.designInputs, ...design } }\n return drawableFigureParts(compileFigureAsset(next).evaluate(state).geometry)\n } catch {\n return null\n }\n}\n\n/**\n * How much one pixel of a label drag moves a value: a degree for an angle, a hundredth for a ratio, and for a length a\n * step that suits its size -- a millimetre under 100, ten over 1,000 -- so a 12 m wall and a 5 mm pin both scrub at a\n * usable pace. Shift makes it ten times finer.\n */\nexport function scrubStep(unit: string, value: number, fine = false): number {\n const base =\n unit === 'deg' ? 1 : unit === 'ratio' ? 0.01 : Math.max(1, Math.pow(10, Math.floor(Math.log10(Math.max(1, Math.abs(value)))) - 2))\n return fine ? base / 10 : base\n}\n\n/** A scrubbed value on the step's grid, so a drag does not leave 1203.0000000002 behind. */\nexport function scrubbed(start: number, pixels: number, step: number): number {\n const value = start + pixels * step\n const digits = Math.max(0, -Math.floor(Math.log10(step)))\n return Number(value.toFixed(digits))\n}\n\n/**\n * The bodies to draw while a value is in hand, over every input of the version as the session holds it now: the pose the\n * author has already set with the other controls, and the design values. Drawing over the asset's saved defaults put\n * every other joint back to its saved angle while one slider moved (user, 2026-09-24: the arm jumped on release).\n */\nexport function liveOverSession(revision: FigureRevision, live: LiveValues): FigureRenderPart[] | null {\n if (!isLive(live)) return null\n return livePartsOf(revision.asset, { ...live, values: { ...revision.inputs, ...live.values } })\n}\n\n"]}
|
|
1
|
+
{"version":3,"file":"live.js","sourceRoot":"","sources":["../../client/modeller/live.ts"],"names":[],"mappings":"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","sourcesContent":["import { applyFigureAuthoring, compileFigureAsset, editFigurePart } from '@hatiolab/figure-model'\nimport type { FigureAsset, FigureAuthoringAction, FigurePartEdit } from '@hatiolab/figure-model'\n\nimport { drawableFigureParts, type FigureRenderPart } from './renderer.js'\nimport type { FigureRevision } from './revision.js'\n\n/*\n * The figure while a value is in hand: evaluated with the values being typed or dragged, drawn at once and written\n * nowhere. The panels committed on release only, so the canvas stood still while a number was typed, a spinner held,\n * a label scrubbed, a slider or the glow dragged (user, 2026-09-24: \"make the editor move naturally while the value\n * changes\"). The value in hand shows here; the release is the one step of the history.\n *\n * What is in hand is either input values -- a design value (a size, a position) or a pose -- or one part edit (the\n * glow of a material). A design value is a change of the figure's design, so it is shown by evaluating a copy with\n * that value; a pose is a state override, as the preview gives it.\n */\n\nexport interface LiveValues {\n /** Input values in hand, design or state, by input id. */\n values: Record<string, number>\n /** A part edit in hand, not yet committed. */\n edit?: FigurePartEdit\n /** An authoring command in hand (a corner radius, an inside, being typed), not yet committed (ADR-0100 §3.3). */\n action?: FigureAuthoringAction\n}\n\nexport const NOTHING_LIVE: LiveValues = { values: {} }\n\n/** Known inputs with a number, split by what they are. An id the figure does not declare is dropped. */\nexport function liveOverridesOf(asset: FigureAsset, values: Record<string, number>): { design: Record<string, number>; state: Record<string, number> } {\n const roles = new Map(asset.document.model.inputs.map(i => [i.id, i.role]))\n const design: Record<string, number> = {},\n state: Record<string, number> = {}\n for (const [id, value] of Object.entries(values)) {\n if (!Number.isFinite(value)) continue\n const role = roles.get(id)\n if (role === 'state') state[id] = value\n else if (role !== undefined) design[id] = value\n }\n return { design, state }\n}\n\nexport function isLive(live: LiveValues): boolean {\n return !!live.edit || !!live.action || Object.keys(live.values).length > 0\n}\n\n/**\n * The bodies to draw with what is in hand, or null when nothing is -- then the evaluation as it stands is drawn.\n *\n * Also null when the figure cannot take the value in hand (a size of 0 on the way to typing 0.5, a glow the material\n * refuses): the canvas keeps the figure as it stands until the release, and the release reports why through the same\n * refusal every committed edit gets. A preview never invents a figure the commit would not make.\n */\nexport function livePartsOf(asset: FigureAsset, live: LiveValues): FigureRenderPart[] | null {\n if (!isLive(live)) return null\n try {\n let next = live.edit ? editFigurePart(asset, live.edit) : asset\n if (live.action) next = applyFigureAuthoring(next, live.action)\n /* Read against the figure the edit makes: a command can take an input away (one wall becomes one per side). */\n const { design, state } = liveOverridesOf(next, live.values)\n /*\n * An input the edit or the command in hand wrote takes its value: the values as they stand hold its old one (a\n * point's coordinate before it was moved, a corner's radius before it was typed again) and would put it back.\n */\n for (const id of Object.keys(design)) if (next.designInputs[id] !== asset.designInputs[id]) delete design[id]\n /* A value the figure already holds changes nothing: a pose in hand alone is drawn from the figure as it is, not a copy. */\n for (const id of Object.keys(design)) if (design[id] === next.designInputs[id]) delete design[id]\n if (Object.keys(design).length) next = { ...next, designInputs: { ...next.designInputs, ...design } }\n return drawableFigureParts(compileFigureAsset(next).evaluate(state).geometry)\n } catch {\n return null\n }\n}\n\n/**\n * How much one pixel of a label drag moves a value: a degree for an angle, a hundredth for a ratio, and for a length a\n * step that suits its size -- a millimetre under 100, ten over 1,000 -- so a 12 m wall and a 5 mm pin both scrub at a\n * usable pace. Shift makes it ten times finer.\n */\nexport function scrubStep(unit: string, value: number, fine = false): number {\n const base =\n unit === 'deg' ? 1 : unit === 'ratio' ? 0.01 : Math.max(1, Math.pow(10, Math.floor(Math.log10(Math.max(1, Math.abs(value)))) - 2))\n return fine ? base / 10 : base\n}\n\n/** A scrubbed value on the step's grid, so a drag does not leave 1203.0000000002 behind. */\nexport function scrubbed(start: number, pixels: number, step: number): number {\n const value = start + pixels * step\n const digits = Math.max(0, -Math.floor(Math.log10(step)))\n return Number(value.toFixed(digits))\n}\n\n/**\n * A label dragged sideways scrubs a value, as vector tools do: each move gives the value the pointer has come to, the\n * release gives the last one, and a drag that ends where it began -- or that the system took away -- gives it back. The\n * label holds the pointer for the drag. Every field that scrubs goes through here: the part's own numbers and the size\n * editor's rows.\n */\nexport function scrubLabel(\n down: PointerEvent,\n from: { value: number; unit: string },\n to: { move: (value: number) => void; release: (value: number) => void; cancel: () => void }\n): void {\n if (down.button !== 0) return\n const label = down.currentTarget as HTMLElement\n down.preventDefault()\n label.setPointerCapture(down.pointerId)\n let value = from.value\n const move = (e: PointerEvent) => {\n value = scrubbed(from.value, e.clientX - down.clientX, scrubStep(from.unit, from.value, e.shiftKey))\n to.move(value)\n }\n const up = (e: PointerEvent) => {\n label.removeEventListener('pointermove', move)\n label.removeEventListener('pointerup', up)\n label.removeEventListener('pointercancel', up)\n /* A drag the system took away is not the author's value. */\n if (e.type === 'pointercancel' || value === from.value) to.cancel()\n else to.release(value)\n }\n label.addEventListener('pointermove', move)\n label.addEventListener('pointerup', up)\n label.addEventListener('pointercancel', up)\n}\n\n/**\n * The bodies to draw while a value is in hand, over every input of the version as the session holds it now: the pose the\n * author has already set with the other controls, and the design values. Drawing over the asset's saved defaults put\n * every other joint back to its saved angle while one slider moved (user, 2026-09-24: the arm jumped on release).\n */\nexport function liveOverSession(revision: FigureRevision, live: LiveValues): FigureRenderPart[] | null {\n if (!isLive(live)) return null\n return livePartsOf(revision.asset, { ...live, values: { ...revision.inputs, ...live.values } })\n}\n\n"]}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* ## 놓은 개수가 아니라 **보이는 개수**다
|
|
5
5
|
*
|
|
6
|
-
* 3D 는 요청이 있어야 다시 그린다. 가만히 서 있는
|
|
6
|
+
* 3D 는 요청이 있어야 다시 그린다. 가만히 서 있는 피규어는 프레임마다 아무 값도 치르지 않는다 —
|
|
7
7
|
* 900 대를 놓아 두어도 60fps 다. 값을 치르는 것은 **카메라 안에 들어 있고 그 프레임에 다시
|
|
8
8
|
* 그려지는** 것뿐이다. 그래서 이 파일의 분모는 「한 도면에 놓는 수」가 아니라 「한 화면에 보이는
|
|
9
9
|
* 부품 묶음 수」다.
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
* 묶음 9 짜리 6 축 로봇 600 대 보이면 59.5 fps · 700 대에서 56.7 fps
|
|
18
18
|
* 묶음 2 짜리 컨베이어 1,600 대 보이면 59.3 fps · 2,500 대에서 36.8 fps
|
|
19
19
|
*
|
|
20
|
-
* 두
|
|
21
|
-
* 쓴다.
|
|
20
|
+
* 두 피규어의 꺾이는 지점이 **인스턴스 × 묶음 = 3,600 ~ 5,400** 에 들어온다. 보수적으로 4,000 을
|
|
21
|
+
* 쓴다. 피규어가 달라도 이 곱이 비슷한 데서 꺾이므로, 분모는 묶음 수로 둔다.
|
|
22
22
|
*
|
|
23
23
|
* ⚠ 이 수는 **이 기계에서 이 씬으로** 잰 값이다. 더 낮은 GPU 나 브라우저에서는 더 일찍 꺾인다.
|
|
24
24
|
* 화면에는 잰 기계와 날짜를 함께 적는다 — 적지 않으면 어디서나 참인 수처럼 읽힌다.
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* ## 놓은 개수가 아니라 **보이는 개수**다
|
|
5
5
|
*
|
|
6
|
-
* 3D 는 요청이 있어야 다시 그린다. 가만히 서 있는
|
|
6
|
+
* 3D 는 요청이 있어야 다시 그린다. 가만히 서 있는 피규어는 프레임마다 아무 값도 치르지 않는다 —
|
|
7
7
|
* 900 대를 놓아 두어도 60fps 다. 값을 치르는 것은 **카메라 안에 들어 있고 그 프레임에 다시
|
|
8
8
|
* 그려지는** 것뿐이다. 그래서 이 파일의 분모는 「한 도면에 놓는 수」가 아니라 「한 화면에 보이는
|
|
9
9
|
* 부품 묶음 수」다.
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
* 묶음 9 짜리 6 축 로봇 600 대 보이면 59.5 fps · 700 대에서 56.7 fps
|
|
18
18
|
* 묶음 2 짜리 컨베이어 1,600 대 보이면 59.3 fps · 2,500 대에서 36.8 fps
|
|
19
19
|
*
|
|
20
|
-
* 두
|
|
21
|
-
* 쓴다.
|
|
20
|
+
* 두 피규어의 꺾이는 지점이 **인스턴스 × 묶음 = 3,600 ~ 5,400** 에 들어온다. 보수적으로 4,000 을
|
|
21
|
+
* 쓴다. 피규어가 달라도 이 곱이 비슷한 데서 꺾이므로, 분모는 묶음 수로 둔다.
|
|
22
22
|
*
|
|
23
23
|
* ⚠ 이 수는 **이 기계에서 이 씬으로** 잰 값이다. 더 낮은 GPU 나 브라우저에서는 더 일찍 꺾인다.
|
|
24
24
|
* 화면에는 잰 기계와 날짜를 함께 적는다 — 적지 않으면 어디서나 참인 수처럼 읽힌다.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scene-budget.js","sourceRoot":"","sources":["../../client/modeller/scene-budget.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,2CAA2C;IAC3C,cAAc,EAAE,IAAI;IACpB,iDAAiD;IACjD,SAAS,EAAE,OAAO;CACV,CAAA;AAEV,0DAA0D;AAC1D,MAAM,CAAC,MAAM,QAAQ,GAAG,EAAE,GAAG,EAAE,UAAU,EAAE,EAAE,EAAE,YAAY,EAAE,GAAG,EAAE,EAAE,EAAW,CAAA;AAgB/E;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,MAAc,EAAE,SAAiB;IAC5D,2CAA2C;IAC3C,IAAI,MAAM,IAAI,CAAC,IAAI,SAAS,IAAI,CAAC,EAAE,CAAC;QAClC,OAAO,SAAS,CAAA;IAClB,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,cAAc,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAA;IACzF,MAAM,WAAW,GAAG,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,SAAS,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAA;IAE7F,OAAO,QAAQ,IAAI,WAAW;QAC5B,CAAC,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,CAAC,cAAc,EAAE;QAC5F,CAAC,CAAC,EAAE,SAAS,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,CAAC,SAAS,EAAE,CAAA;AACpG,CAAC","sourcesContent":["\n/**\n * 한 화면이 감당하는 양 — 「몇 개까지 편한가」의 분모.\n *\n * ## 놓은 개수가 아니라 **보이는 개수**다\n *\n * 3D 는 요청이 있어야 다시 그린다. 가만히 서 있는
|
|
1
|
+
{"version":3,"file":"scene-budget.js","sourceRoot":"","sources":["../../client/modeller/scene-budget.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,2CAA2C;IAC3C,cAAc,EAAE,IAAI;IACpB,iDAAiD;IACjD,SAAS,EAAE,OAAO;CACV,CAAA;AAEV,0DAA0D;AAC1D,MAAM,CAAC,MAAM,QAAQ,GAAG,EAAE,GAAG,EAAE,UAAU,EAAE,EAAE,EAAE,YAAY,EAAE,GAAG,EAAE,EAAE,EAAW,CAAA;AAgB/E;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,MAAc,EAAE,SAAiB;IAC5D,2CAA2C;IAC3C,IAAI,MAAM,IAAI,CAAC,IAAI,SAAS,IAAI,CAAC,EAAE,CAAC;QAClC,OAAO,SAAS,CAAA;IAClB,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,cAAc,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAA;IACzF,MAAM,WAAW,GAAG,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,SAAS,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAA;IAE7F,OAAO,QAAQ,IAAI,WAAW;QAC5B,CAAC,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,CAAC,cAAc,EAAE;QAC5F,CAAC,CAAC,EAAE,SAAS,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,CAAC,SAAS,EAAE,CAAA;AACpG,CAAC","sourcesContent":["\n/**\n * 한 화면이 감당하는 양 — 「몇 개까지 편한가」의 분모.\n *\n * ## 놓은 개수가 아니라 **보이는 개수**다\n *\n * 3D 는 요청이 있어야 다시 그린다. 가만히 서 있는 피규어는 프레임마다 아무 값도 치르지 않는다 —\n * 900 대를 놓아 두어도 60fps 다. 값을 치르는 것은 **카메라 안에 들어 있고 그 프레임에 다시\n * 그려지는** 것뿐이다. 그래서 이 파일의 분모는 「한 도면에 놓는 수」가 아니라 「한 화면에 보이는\n * 부품 묶음 수」다.\n *\n * ## 이 수는 잰 값이다\n *\n * 전에는 설계 문서의 목표치(도면당 draw call 2,500)를 썼고, 그 문서가 스스로 「측정 없이 정한\n * 목표」라고 적어 두었다. 2026-09-18 에 실기에서 쟀다\n * (`things-scene/test/browser/measure-figure-frames.mjs`, Apple M5, 저작 화면과 같은 조명 · 그림자):\n *\n * 묶음 9 짜리 6 축 로봇 600 대 보이면 59.5 fps · 700 대에서 56.7 fps\n * 묶음 2 짜리 컨베이어 1,600 대 보이면 59.3 fps · 2,500 대에서 36.8 fps\n *\n * 두 피규어의 꺾이는 지점이 **인스턴스 × 묶음 = 3,600 ~ 5,400** 에 들어온다. 보수적으로 4,000 을\n * 쓴다. 피규어가 달라도 이 곱이 비슷한 데서 꺾이므로, 분모는 묶음 수로 둔다.\n *\n * ⚠ 이 수는 **이 기계에서 이 씬으로** 잰 값이다. 더 낮은 GPU 나 브라우저에서는 더 일찍 꺾인다.\n * 화면에는 잰 기계와 날짜를 함께 적는다 — 적지 않으면 어디서나 참인 수처럼 읽힌다.\n *\n * ## 그림자는 이미 들어 있다\n *\n * 2026-09-05 에는 draw call 을 figure-model 의 예측과 견주며 그림자 패스가 두 배로 만든다고\n * 쟀다. 지금 값은 **그림자를 켠 채 프레임 시간을 직접 잰 것**이라 그 배수가 이미 안에 있다.\n * 다시 곱하면 두 번 세는 것이다.\n */\n\nexport const SCENE_BUDGET = {\n /** 한 화면에 보이는 부품 묶음. 60fps 가 꺾이는 지점, 실측. */\n groupsOnScreen: 4000,\n /** 한 화면의 삼각형. **이 수는 아직 안 쟀다** — 설계 문서의 목표치다. */\n triangles: 500_000\n} as const\n\n/** 어디서 언제 잰 값인가. 화면이 함께 보여 준다 — 적지 않으면 어디서나 참인 수로 읽힌다. */\nexport const MEASURED = { gpu: 'Apple M5', on: '2026-09-18', fps: 60 } as const\n\n/** 무엇이 먼저 차나. */\nexport type BindingBudget = 'groups' | 'triangles'\n\nexport interface FigureCapacity {\n /** 이 형상이 **한 화면에 몇 개 보일 때까지** 60fps 로 도나. */\n instances: number\n /** 먼저 차는 예산. **여기부터 고치면 된다.** */\n bound: BindingBudget\n /** 인스턴스 하나가 무는 값. */\n per: number\n /** 그 예산의 한 도면 총량. */\n budget: number\n}\n\n/**\n * 저장된 값만으로 셈한다.\n *\n * 목록은 정본을 받아 오지 않는다 — 카드 수십 장마다 컴파일하면 목록이 무거워진다.\n * 대신 서버가 저장해 둔 `groups` · `triangles` 를 쓴다. **목록과 저작면이 같은 함수를\n * 쓰는 것**이 요점이다. 카드가 제 나름대로 셈하면 같은 형상이 두 화면에서 다른 개수로\n * 나온다.\n */\nexport function capacityFrom(groups: number, triangles: number): FigureCapacity | undefined {\n // 부품이 없으면 「무한히 놓을 수 있다」가 아니라 **답할 것이 없다**.\n if (groups <= 0 && triangles <= 0) {\n return undefined\n }\n\n const byGroups = groups > 0 ? Math.floor(SCENE_BUDGET.groupsOnScreen / groups) : Infinity\n const byTriangles = triangles > 0 ? Math.floor(SCENE_BUDGET.triangles / triangles) : Infinity\n\n return byGroups <= byTriangles\n ? { instances: byGroups, bound: 'groups', per: groups, budget: SCENE_BUDGET.groupsOnScreen }\n : { instances: byTriangles, bound: 'triangles', per: triangles, budget: SCENE_BUDGET.triangles }\n}\n\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"thumbnail.js","sourceRoot":"","sources":["../../client/modeller/thumbnail.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAA;AAIpB;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,kBAAkB;AAClB,MAAM,KAAK,GAAG,GAAG,CAAA;AACjB,MAAM,MAAM,GAAG,GAAG,CAAA;AAElB,mDAAmD;AACnD,MAAM,QAAQ,GAAG,IAAI,CAAA;AAWrB,MAAM,KAAK,GAAG,CAAC,EAAU,EAAE,EAAE,CAAC,IAAI,OAAO,CAAO,OAAO,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAA;AAEnF,yDAAyD;AACzD,SAAS,MAAM;IACb,MAAM,MAAM,GAAG,IAAI,OAAO,CAAO,OAAO,CAAC,EAAE,CACzC,qBAAqB,CAAC,GAAG,EAAE,CAAC,qBAAqB,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC,CACnF,CAAA;IACD,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;AAC3C,CAAC;AAED,SAAS,MAAM,CAAI,IAAgB,EAAE,EAAU;IAC7C,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,CAAA;AAC9D,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,KAAyB,EACzB,OAA+B;IAE/B,IAAI,CAAC,KAAK,CAAC,MAAM;QAAE,OAAO,SAAS,CAAA;IAEnC;;;MAGE;IACF,MAAM,IAAI,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAA;IAC1C,IAAI,CAAC,YAAY,CAAC,wBAAwB,EAAE,EAAE,CAAC,CAAA;IAC/C,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,mDAAmD,KAAK,eAAe,MAAM,2BAA2B,CAAA;IAC7H,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAA;IAE/B,MAAM,OAAO,GAAG,QAAQ,CAAC,aAAa,CAAC,eAAe,CAAY,CAAA;IAClE,OAAO,CAAC,KAAK,GAAG,KAAK,CAAA;IACrB,OAAO,CAAC,OAAO,GAAG,OAAO,CAAA;IACzB,OAAO,CAAC,OAAO,GAAG,WAAW,CAAA;IAC7B,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAA;IACvB,OAAO,CAAC,KAAK,CAAC,OAAO,GAAG,4CAA4C,CAAA;IAEpE,IAAI,CAAC;QACH,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,CAAA;QACzB,MAAM,IAAI,GAAG,MAAM,MAAM,CACvB,CAAC,KAAK,IAAI,EAAE;YACV,MAAM,MAAM,EAAE,CAAA;YACd,OAAO,CAAC,GAAG,EAAE,EAAE,CAAA;YACf,MAAM,MAAM,EAAE,CAAA;YACd,OAAO,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,OAAO,CAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;QAC7E,CAAC,CAAC,EAAE,EACJ,QAAQ,CACT,CAAA;QACD,IAAI,CAAC,IAAI;YAAE,OAAO,CAAC,IAAI,CAAC,eAAe,QAAQ,6BAA6B,CAAC,CAAA;QAC7E,OAAO,IAAI,CAAA;IACb,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,CAAC,IAAI,CAAC,2BAA4B,CAAW,CAAC,OAAO,EAAE,CAAC,CAAA;QAC/D,OAAO,SAAS,CAAA;IAClB,CAAC;YAAS,CAAC;QACT,IAAI,CAAC,MAAM,EAAE,CAAA;IACf,CAAC;AACH,CAAC","sourcesContent":["import './canvas.js'\n\nimport type { FigureRenderPart } from './renderer.js'\n\n/**\n * 카드에 쓸 그림을 찍는다 — **숨은 자리에 캔버스를 하나 더 세워서.**\n *\n * ## 왜 보이는 캔버스를 찍지 않나\n *\n * 저작면의 캔버스는 저작자가 돌려 둔 각도와 확대로 서 있다. 그것을 찍으면 카드마다 프레임이\n * 달라지고, 방금 확대해 보던 나사 하나가 그 자산의 얼굴이 된다. 목록은 견주는 자리이므로 그림은\n * **정해진 크기와 정해진 카메라**로 찍는다 — 새로 세운 캔버스(`viewOnly`)가 스스로 `fit()` 한 자리에서\n * `snapshot()` 으로 찍는다. 캔버스가 씬 위로 옮겨 가면서 찍는 길이 빠졌고, 그동안 저장은 그림 없이 갔다\n * (사용자 2026-09-24).\n *\n * ## 왜 서버가 아니라 브라우저인가\n *\n * 백엔드에 GPU 가 없는 배포가 많아 헤드리스로 3D 를 찍는 것이 자주 실패한다.
|
|
1
|
+
{"version":3,"file":"thumbnail.js","sourceRoot":"","sources":["../../client/modeller/thumbnail.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAA;AAIpB;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,kBAAkB;AAClB,MAAM,KAAK,GAAG,GAAG,CAAA;AACjB,MAAM,MAAM,GAAG,GAAG,CAAA;AAElB,mDAAmD;AACnD,MAAM,QAAQ,GAAG,IAAI,CAAA;AAWrB,MAAM,KAAK,GAAG,CAAC,EAAU,EAAE,EAAE,CAAC,IAAI,OAAO,CAAO,OAAO,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAA;AAEnF,yDAAyD;AACzD,SAAS,MAAM;IACb,MAAM,MAAM,GAAG,IAAI,OAAO,CAAO,OAAO,CAAC,EAAE,CACzC,qBAAqB,CAAC,GAAG,EAAE,CAAC,qBAAqB,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC,CACnF,CAAA;IACD,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;AAC3C,CAAC;AAED,SAAS,MAAM,CAAI,IAAgB,EAAE,EAAU;IAC7C,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,CAAA;AAC9D,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,KAAyB,EACzB,OAA+B;IAE/B,IAAI,CAAC,KAAK,CAAC,MAAM;QAAE,OAAO,SAAS,CAAA;IAEnC;;;MAGE;IACF,MAAM,IAAI,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAA;IAC1C,IAAI,CAAC,YAAY,CAAC,wBAAwB,EAAE,EAAE,CAAC,CAAA;IAC/C,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,mDAAmD,KAAK,eAAe,MAAM,2BAA2B,CAAA;IAC7H,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAA;IAE/B,MAAM,OAAO,GAAG,QAAQ,CAAC,aAAa,CAAC,eAAe,CAAY,CAAA;IAClE,OAAO,CAAC,KAAK,GAAG,KAAK,CAAA;IACrB,OAAO,CAAC,OAAO,GAAG,OAAO,CAAA;IACzB,OAAO,CAAC,OAAO,GAAG,WAAW,CAAA;IAC7B,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAA;IACvB,OAAO,CAAC,KAAK,CAAC,OAAO,GAAG,4CAA4C,CAAA;IAEpE,IAAI,CAAC;QACH,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,CAAA;QACzB,MAAM,IAAI,GAAG,MAAM,MAAM,CACvB,CAAC,KAAK,IAAI,EAAE;YACV,MAAM,MAAM,EAAE,CAAA;YACd,OAAO,CAAC,GAAG,EAAE,EAAE,CAAA;YACf,MAAM,MAAM,EAAE,CAAA;YACd,OAAO,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,OAAO,CAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;QAC7E,CAAC,CAAC,EAAE,EACJ,QAAQ,CACT,CAAA;QACD,IAAI,CAAC,IAAI;YAAE,OAAO,CAAC,IAAI,CAAC,eAAe,QAAQ,6BAA6B,CAAC,CAAA;QAC7E,OAAO,IAAI,CAAA;IACb,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,CAAC,IAAI,CAAC,2BAA4B,CAAW,CAAC,OAAO,EAAE,CAAC,CAAA;QAC/D,OAAO,SAAS,CAAA;IAClB,CAAC;YAAS,CAAC;QACT,IAAI,CAAC,MAAM,EAAE,CAAA;IACf,CAAC;AACH,CAAC","sourcesContent":["import './canvas.js'\n\nimport type { FigureRenderPart } from './renderer.js'\n\n/**\n * 카드에 쓸 그림을 찍는다 — **숨은 자리에 캔버스를 하나 더 세워서.**\n *\n * ## 왜 보이는 캔버스를 찍지 않나\n *\n * 저작면의 캔버스는 저작자가 돌려 둔 각도와 확대로 서 있다. 그것을 찍으면 카드마다 프레임이\n * 달라지고, 방금 확대해 보던 나사 하나가 그 자산의 얼굴이 된다. 목록은 견주는 자리이므로 그림은\n * **정해진 크기와 정해진 카메라**로 찍는다 — 새로 세운 캔버스(`viewOnly`)가 스스로 `fit()` 한 자리에서\n * `snapshot()` 으로 찍는다. 캔버스가 씬 위로 옮겨 가면서 찍는 길이 빠졌고, 그동안 저장은 그림 없이 갔다\n * (사용자 2026-09-24).\n *\n * ## 왜 서버가 아니라 브라우저인가\n *\n * 백엔드에 GPU 가 없는 배포가 많아 헤드리스로 3D 를 찍는 것이 자주 실패한다. 피규어를 그릴 GPU 는\n * 이미 보는 사람 손에 있다.\n *\n * ## 못 찍어도 저장은 간다\n *\n * 탭이 뒤로 가면 브라우저가 `requestAnimationFrame` 을 멈춘다. 그 위에 저장이 얹혀 있으면 저장이\n * 통째로 멈춘다 — 단추는 잠긴 채로, 아무 말도 없이. 그래서 시계와 경주시키고, 늦으면 그림 없이\n * 간다. 정본이 그림보다 중하고, 그림은 다음에 저작면을 열 때 다시 채운다.\n */\n\n/** 카드는 4:3 이다. */\nconst WIDTH = 480\nconst HEIGHT = 360\n\n/** 넘으면 그림 없이 저장한다. 빠르기가 아니라 저장이 멈추지 않는 것이 목적이다. */\nconst PATIENCE = 5000\n\ninterface Shooter extends HTMLElement {\n parts?: FigureRenderPart[]\n palette?: Record<string, string>\n context?: string\n viewOnly?: boolean\n snapshot?: (width?: number, height?: number) => Promise<string | undefined>\n fit?: () => void\n}\n\nconst delay = (ms: number) => new Promise<void>(resolve => setTimeout(resolve, ms))\n\n/** 씬이 서고 카메라가 자리를 잡을 틈. rAF 가 멈춘 탭에서도 풀리도록 시계와 경주시킨다. */\nfunction settle(): Promise<void> {\n const frames = new Promise<void>(resolve =>\n requestAnimationFrame(() => requestAnimationFrame(() => setTimeout(resolve, 150)))\n )\n return Promise.race([frames, delay(600)])\n}\n\nfunction within<T>(work: Promise<T>, ms: number): Promise<T | undefined> {\n return Promise.race([work, delay(ms).then(() => undefined)])\n}\n\nexport async function captureFigureThumbnail(\n parts: FigureRenderPart[],\n palette: Record<string, string>\n): Promise<string | undefined> {\n if (!parts.length) return undefined\n\n /*\n 화면 밖에 둔다. `display: none` 이면 캔버스가 크기를 못 얻어 아무것도 그리지 못한다.\n WebGL context 는 브라우저가 8~16개에서 끊으므로 찍은 뒤 반드시 버린다(아래 finally).\n */\n const host = document.createElement('div')\n host.setAttribute('figure-thumbnail-stage', '')\n host.style.cssText = `position: fixed; left: -10000px; top: 0; width: ${WIDTH}px; height: ${HEIGHT}px; pointer-events: none;`\n document.body.appendChild(host)\n\n const shooter = document.createElement('figure-canvas') as Shooter\n shooter.parts = parts\n shooter.palette = palette\n shooter.context = 'thumbnail'\n shooter.viewOnly = true\n shooter.style.cssText = 'display: block; width: 100%; height: 100%;'\n\n try {\n host.appendChild(shooter)\n const shot = await within(\n (async () => {\n await settle()\n shooter.fit?.()\n await settle()\n return shooter.snapshot ? await shooter.snapshot(WIDTH, HEIGHT) : undefined\n })(),\n PATIENCE\n )\n if (!shot) console.warn(`[thumbnail] ${PATIENCE}ms 안에 못 찍었습니다 — 그림 없이 저장합니다`)\n return shot\n } catch (e) {\n console.warn(`[thumbnail] 세우지 못했습니다 — ${(e as Error).message}`)\n return undefined\n } finally {\n host.remove()\n }\n}\n"]}
|
|
@@ -55,7 +55,7 @@ export declare class FigureListPage extends FigureListPageBase {
|
|
|
55
55
|
* 검색창에 그 말을 채워 넣는다. 격자에 조건만 걸고 창을 비워 두면 저작자는 **왜 목록이
|
|
56
56
|
* 줄었는지 모른 채** 보게 되고, 되돌리려면 무엇을 지워야 하는지도 알 수 없다.
|
|
57
57
|
*
|
|
58
|
-
* 서버가 `tags` 뿐 아니라 이름·타입·설명·분류를 함께 훑으므로, 그 말이 다른 칸에 든
|
|
58
|
+
* 서버가 `tags` 뿐 아니라 이름·타입·설명·분류를 함께 훑으므로, 그 말이 다른 칸에 든 피규어도
|
|
59
59
|
* 같이 온다. 태그만 정확히 거르는 것이 아니라 **찾기**다.
|
|
60
60
|
*/
|
|
61
61
|
private findByTag;
|
|
@@ -164,7 +164,7 @@ let FigureListPage = class FigureListPage extends FigureListPageBase {
|
|
|
164
164
|
* 검색창에 그 말을 채워 넣는다. 격자에 조건만 걸고 창을 비워 두면 저작자는 **왜 목록이
|
|
165
165
|
* 줄었는지 모른 채** 보게 되고, 되돌리려면 무엇을 지워야 하는지도 알 수 없다.
|
|
166
166
|
*
|
|
167
|
-
* 서버가 `tags` 뿐 아니라 이름·타입·설명·분류를 함께 훑으므로, 그 말이 다른 칸에 든
|
|
167
|
+
* 서버가 `tags` 뿐 아니라 이름·타입·설명·분류를 함께 훑으므로, 그 말이 다른 칸에 든 피규어도
|
|
168
168
|
* 같이 온다. 태그만 정확히 거르는 것이 아니라 **찾기**다.
|
|
169
169
|
*/
|
|
170
170
|
findByTag(tag) {
|
|
@@ -262,7 +262,7 @@ let FigureListPage = class FigureListPage extends FigureListPageBase {
|
|
|
262
262
|
정렬은 걸지 않는다. 배열이라 무엇을 기준으로 줄을 세우는지 답할 수 없다.
|
|
263
263
|
|
|
264
264
|
**카드에서는 이 컬럼이 그리지 않는다.** 본문에 한 줄로 두었더니 태그가 있는 카드만
|
|
265
|
-
높아져 격자가 어긋났다 — 개수가
|
|
265
|
+
높아져 격자가 어긋났다 — 개수가 피규어마다 다르므로 본문에 두는 한 남는 문제다.
|
|
266
266
|
카드에서는 `figure-thumb` 이 그림 위에 얹는다. 여기 남은 것은 표 모드의 칸이다.
|
|
267
267
|
*/
|
|
268
268
|
type: 'string',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"figure-list-page.js","sourceRoot":"","sources":["../../client/pages/figure-list-page.ts"],"names":[],"mappings":"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","sourcesContent":["import '@operato/data-grist'\nimport '@material/web/icon/icon.js'\nimport '../viewparts/figure-thumb.js'\n\nimport { css, html, nothing } from 'lit'\nimport { customElement, property, query } from 'lit/decorators.js'\n\nimport type { DataGrist, FetchOption } from '@operato/data-grist'\nimport { i18next, localize } from '@operato/i18n'\nimport { p13n } from '@operato/p13n'\nimport { auth } from '@things-factory/auth-base/dist-client'\nimport { navigate, PageView } from '@operato/shell'\nimport { notify } from '@operato/layout'\nimport { CardCatalogStyles, CommonGristStyles, CommonHeaderStyles, ScrollbarStyles, WorkSurfacePageStyles } from '@operato/styles'\n\nimport { capacityFrom } from '../modeller/scene-budget.js'\nimport { deleteFigure, fetchFigureList } from '../graphql/index.js'\nimport type { Figure } from '../types.js'\nimport { hasBeenReleased } from '../figure-released.js'\nimport { modellerPathOf } from '../modeller/modeller-route.js'\nimport { FIGURE_PUBLIC_CHANGED, FIGURE_RETIREMENT_CHANGED, FIGURE_THUMBNAIL_CHANGED, openFigureDetail } from '../viewparts/figure-detail-dock.js'\n\n/**\n * 이 값 아래면 점수를 색으로 눈에 띄게 한다.\n *\n * **등급 경계가 아니다.** 등급(A~D)은 `figure-model` 의 `scoreOf` 가 정하고 화면은 그것을\n * 베끼지 않는다. 이것은 「눈에 걸리게 할 선」이고, 화면의 판단이다.\n */\nconst ATTENTION_BELOW = 50\n\n/*\n * ── renderer 가 그리는 것은 이 파일의 CSS 가 닿지 않는다 ──────────────────────\n *\n * 컬럼 renderer 의 결과는 `ox-card-field` 의 shadow root 안에 그려진다. 이 페이지의 `static\n * styles` 는 그 경계를 넘지 못하므로 `code[figure-type]` 같은 선택자는 **아무 일도 하지 않는다.**\n * 넘어가는 것은 custom property 뿐이라, 색과 글꼴은 여기서 인라인으로 준다 — 값 자체는 테마\n * 토큰을 가리키므로 밝음·어둠은 따라온다. data-grist 의 image renderer 도 같은 방식이다.\n */\nconst CAPACITY = 'font-weight: 700; color: var(--md-sys-color-primary);'\nconst UNMEASURED = 'font-style: italic; color: var(--md-sys-color-outline);'\nconst RELEASED = 'font-weight: 700; color: var(--md-sys-color-primary);'\nconst DRAFT = 'color: var(--md-sys-color-tertiary);'\n/**\n * 조용히 적는 숫자. 낮은 것만 점으로 눈에 걸리게 한다.\n *\n * **`inline-flex` 를 쓰지 않는다.** 썼더니 점이 뜨는 카드에서만 이 값이 옆의 두 값과 밑줄이\n * 어긋났다 — inline-flex 상자의 밑줄은 **첫 항목**에서 오는데, 첫 항목이 글자 없는 점이라\n * 밑줄이 없어 브라우저가 상자 아래를 밑줄로 삼는다. 점이 없는 카드(50 이상)는 멀쩡했으므로\n * 한 줄에 세 값이 나란히 설 때만 눈에 띄었다.\n *\n * 그냥 인라인으로 두면 이 상자의 밑줄이 곧 숫자의 밑줄이라 옆의 둘과 맞는다. 점은 제 몫으로\n * 글자 가운데에 맞춘다.\n */\nconst SCORE = 'font-variant-numeric: tabular-nums;'\nconst LOW_DOT =\n 'display: inline-block; width: 5px; height: 5px; margin-right: 4px; vertical-align: middle; ' +\n 'border-radius: 50%; background-color: var(--md-sys-color-error);'\n\n/*\n * `as typeof PageView` 로 조이지 않는다 — 그렇게 하면 p13n 이 얹어 주는\n * `getPagePreferenceProvider` 가 타입에서 사라져 개인 설정을 붙일 수 없다.\n */\nconst FigureListPageBase = p13n(localize(i18next)(PageView))\n\n/**\n * Figure 카탈로그.\n *\n * 카드를 고르면 상세 패널에서 저장본과 발행판을 살펴본다. 크기와 움직임을 시험한 뒤\n * 「편집 화면 열기」로 저작면에 들어간다. 목록 질의는 자산 원본을 싣지 않는다.\n *\n * ## 왜 손으로 만든 카드가 아니라 격자인가\n *\n * 전에는 카드 격자와 걸러 보기 줄과 빈 화면을 이 파일이 직접 그렸다(200줄). 그 자리에서 빠져\n * 있던 것들은 하나같이 `ox-grist` 가 이미 가진 것이다 — 쪽 나누기, 정렬, 컬럼 걸러 보기,\n * 개인 설정(모드·정렬·쪽 크기를 사람마다 기억한다), 표 모드로 바꿔 보기, 내보내기.\n * 손으로 만든 카드는 그 중 아무것도 없었고, 목록이 늘어나면 전부 한 번에 그렸다.\n *\n * 카드에 실을 것은 `list` 가 정하고, 무엇을 어떻게 그릴지는 컬럼의 renderer 가 정한다. 같은\n * 설정이 표 모드에서도 그대로 쓰이므로 **두 모드가 다른 말을 하지 않는다.**\n */\n@customElement('figure-list-page')\nexport class FigureListPage extends FigureListPageBase {\n static styles = [\n /* The frame owns the page shell; the grist is the content it sizes (ADR-0060 decision 8). */\n WorkSurfacePageStyles,\n ScrollbarStyles,\n CommonGristStyles,\n CommonHeaderStyles,\n CardCatalogStyles,\n css`\n :host {\n background-color: var(--md-sys-color-surface);\n color: var(--md-sys-color-on-surface);\n }\n\n /*\n 카드 · 그림 칸 · 만들기 FAB 는 모든 카드 카탈로그와 같다(@operato/styles CardCatalogStyles,\n ADR-0117 개정 2026-10-13). 여기에는 이 화면에만 있는 것만 둔다.\n */\n ox-grist {\n /* 상태와 성숙도는 짧은 표식이다 — 두 줄로 세우지 않고 한 줄에 뉜다 */\n --data-card-detail-direction: row;\n /* 표 모드의 맨 위로 단추도 만들기 단추 위로 비킨다 */\n --data-list-fab-position-vertical: 64px;\n }\n `\n ]\n\n @property({ type: Object }) gristConfig: any\n\n @query('ox-grist') private grist!: DataGrist\n\n /**\n * The domain's own figures, or other domains' public ones (LV figure-server design G2), read only. Each is a screen\n * of its own on the rail (user 2026-10-19): the own catalogue is where figures are made and changed, the public one\n * only looked at, so a switch inside one list mixed two jobs. `figure-public-list-page` sets it.\n */\n protected readonly scope: 'own' | 'public' = 'own'\n\n private listen?: () => void\n\n get context() {\n return {\n title: i18next.t(this.scope === 'public' ? 'figure.title.public-figures' : 'figure.title.figures'),\n search: {\n handler: (search: string) => {\n this.grist.searchText = search\n },\n value: this.grist?.searchText || '',\n placeholder: i18next.t('figure.text.search-figure')\n },\n filter: {\n handler: () => {\n this.grist.toggleHeadroom()\n }\n }\n /*\n **만들기는 `actions` 가 아니라 FAB 이다.**\n\n `context.actions` 로 두면 페이지 바닥 줄에 글자 단추로 서는데, 그 줄은 「지금 화면에 대해\n 할 일」을 늘어놓는 자리다. 만들기는 이 화면의 유일한 주된 동작이므로 줄에 섞이지 않고\n 떠 있어야 한다. 카드를 가리지 않는 오른쪽 아래, 손이 가는 자리다(아래 render 의 [fab]).\n */\n }\n }\n\n render() {\n return html`\n <ox-grist\n class=\"page-fill\"\n mode=\"CARD\"\n @search-tag=${(e: CustomEvent) => this.findByTag(e.detail.tag)}\n .config=${this.gristConfig}\n .fetchHandler=${this.fetchHandler.bind(this)}\n .personalConfigProvider=${this.getPagePreferenceProvider('ox-grist')!}\n >\n <div slot=\"headroom\" class=\"header\">\n <div class=\"filters\">\n <ox-filters-form autofocus without-search></ox-filters-form>\n </div>\n </div>\n\n <ox-grist-personalizer slot=\"setting\"></ox-grist-personalizer>\n </ox-grist>\n\n ${this.scope === 'own'\n ? html`<button fab title=${i18next.t('figure.button.new-figure')} @click=${() => this.openModeller()}>\n <md-icon>add</md-icon>${i18next.t('figure.button.new-figure')}\n </button>`\n : nothing}\n `\n }\n\n /**\n * 카드의 태그 알약을 눌렀다 — **그 태그로 목록을 거른다.**\n *\n * 검색창에 그 말을 채워 넣는다. 격자에 조건만 걸고 창을 비워 두면 저작자는 **왜 목록이\n * 줄었는지 모른 채** 보게 되고, 되돌리려면 무엇을 지워야 하는지도 알 수 없다.\n *\n * 서버가 `tags` 뿐 아니라 이름·타입·설명·분류를 함께 훑으므로, 그 말이 다른 칸에 든 도형도\n * 같이 온다. 태그만 정확히 거르는 것이 아니라 **찾기**다.\n */\n private findByTag(tag: string) {\n this.grist.searchText = tag\n /* 창의 값은 `context` 에서 나온다 — 다시 알려야 헤더가 새 값을 그린다. */\n this.updateContext()\n }\n\n async pageInitialized() {\n this.gristConfig = {\n /*\n **쪽 나누기는 선택이 아니다.** 격자에 가상 스크롤이 없어 받은 것을 전부 DOM 에 그린다.\n 카드 모드는 끝까지 내려가면 다음 쪽을 이어 붙이므로, 한 쪽이 작아도 목록은 계속 읽힌다.\n */\n pagination: { pages: [24, 48, 96] },\n\n /*\n 아직 아무것도 없을 때 하는 말. **이 자리가 이 화면에서 가장 중요하다** — 처음 온 사람은\n 여기서 이 도구가 무엇을 만드는 것인지 알게 된다. 조건이 걸려 비었을 때는 격자가 스스로\n 다른 말을 한다(「찾는 것이 없다」). 그 둘을 섞지 않는 것이 격자 쪽에 이미 들어 있다.\n */\n emptyNote: {\n icon: 'deployed_code',\n title: i18next.t('figure.text.no-figure-yet'),\n description: i18next.t('figure.text.what-a-figure-is')\n },\n\n list: {\n /* 그림 칸은 그 컬럼의 renderer 가 그린다 — 그림 없을 때의 안내까지 `figure-thumb` 안이다 */\n thumbnail: 'thumbnail',\n fields: ['name', 'type'],\n /*\n ── 무게를 모른 채 고르게 두지 않는다 ────────────────────────────────\n 이름과 그림만 실으면 저작자는 **이 형상이 무거운지 모른 채** 고른다. 그것을 저작면에\n 들어가서야 아는 것은 늦다. 그래서 격자로 옮기기 전 손 카드가 실었던 셋을 그대로 싣는다 —\n 몇 개까지 놓을 수 있나(배치 한도) · 성숙도 · 삼각형 수.\n\n 한 줄에 뉜다(`--data-card-detail-direction: row`). 이름표는 달지 않는다 — 값 자체가\n 「625개까지」·「삼각형 440개」로 읽히고, 성숙도만 숫자 하나여서 손이 갔을 때 이름이 뜬다.\n\n 상태는 이 줄에 없다. 그림 위 초안 표시가 그 일을 한다(`figure-thumb`) — 발행된 것에는\n 아무 말도 하지 않으므로 빈 칸이 생기지 않는다.\n */\n details: ['capacity', 'score', 'triangles']\n },\n\n columns: [\n { type: 'gutter', gutterName: 'sequence', fixed: true },\n {\n /*\n 표 모드에서만 쓰는 삭제 칸. 카드에서는 `figure-thumb` 이 그림 위에 그리므로\n `forList: false` 로 내린다 — 카드 아래 단추 줄은 감춰도 자리가 남는다.\n */\n type: 'gutter',\n gutterName: 'button',\n icon: 'delete',\n title: i18next.t('figure.button.delete-figure'),\n danger: true,\n forList: false,\n width: 40,\n handlers: {\n click: (_columns: any, _data: any, _column: any, record: Figure) => this.removeFigure(record)\n }\n },\n {\n type: 'string',\n name: 'name',\n header: i18next.t('figure.label.name'),\n record: { editable: false },\n filter: 'search',\n sortable: true,\n width: 200\n },\n {\n /*\n 타입 이름은 **저장되는 식별자**다. 표시 이름과 다르게 보이게 두어야 도면이 무엇으로\n 이 형상을 찾는지 헷갈리지 않는다.\n */\n type: 'string',\n name: 'type',\n header: i18next.t('figure.label.figure-type'),\n record: {\n editable: false,\n /* The full name a board stores: {namespace}/{type} (ADR-0114). */\n renderer: (value: string, column: unknown, record: { namespace?: string }) =>\n html`<code>${record?.namespace ? `${record.namespace}/${value}` : value}</code>`\n },\n filter: 'search',\n sortable: true,\n width: 160\n },\n {\n /*\n 태그 — **카드에서 곁줄로 보이고 검색에 걸린다.**\n\n 서버가 이미 `type`·`name`·`description`·`category`·`tags` 를 함께 훑는다. 그런데\n 화면에는 태그가 어디에도 안 보여서, 저작자가 적은 태그가 **자기 눈에 돌아오지 않았다** —\n 적어 둔 것이 쓰이는지 알 수 없으면 아무도 적지 않는다.\n\n 정렬은 걸지 않는다. 배열이라 무엇을 기준으로 줄을 세우는지 답할 수 없다.\n\n **카드에서는 이 컬럼이 그리지 않는다.** 본문에 한 줄로 두었더니 태그가 있는 카드만\n 높아져 격자가 어긋났다 — 개수가 도형마다 다르므로 본문에 두는 한 남는 문제다.\n 카드에서는 `figure-thumb` 이 그림 위에 얹는다. 여기 남은 것은 표 모드의 칸이다.\n */\n type: 'string',\n name: 'tags',\n header: i18next.t('figure.label.tags'),\n record: {\n editable: false,\n renderer: (value: string[] | null) => (value || []).join(' · ')\n },\n filter: 'search',\n sortable: false,\n width: 180\n },\n {\n type: 'string',\n name: 'state',\n header: i18next.t('figure.label.state'),\n record: {\n editable: false,\n /*\n **발행된 것에는 아무 말도 하지 않는다.** 카탈로그에 있는 것은 대개 발행된 것이라,\n 카드마다 「발행됨」이 붙으면 그 말이 아무것도 가르지 않고 자리만 먹는다. 판 번호도\n 고를 때 쓰는 값이 아니다 — 이력 탭에 있다.\n\n 표 모드에서는 칸에 값이 있어야 하므로 그때는 발행됨도 적는다(`wide` 가 아니라 label\n 이 붙는 자리인지로 가른다 — 카드는 label 을 달지 않았다).\n */\n renderer: (value: string, _column: any, record: Figure, _rowIndex: number, field: Element) => {\n if (value !== 'released') {\n return html`<span style=${DRAFT}>${i18next.t('figure.label.draft')}</span>`\n }\n\n /* 표에서는 칸이 비면 안 된다. 카드에서만 입을 다문다 — 그리는 자리가 알려 준다. */\n return field?.tagName === 'OX-CARD-FIELD'\n ? ''\n : html`<span style=${RELEASED}\n >${i18next.t('figure.label.figure-released')}\n ${record.version ? i18next.t('figure.text.version-number', { version: record.version }) : ''}</span\n >`\n }\n },\n /*\n 전체·초안·발행됨을 단추로 고른다 — 값이 둘뿐인 조건을 펼침 목록에 넣지 않는다.\n\n 이름표는 비워 둔다. 단추에 「초안」·「발행됨」이라고 적혀 있으니 그 앞의 「상태」는\n 같은 말을 한 번 더 하는 것이고, 걸러 보기 줄에서 자리만 먹는다.\n */\n /* Another domain's public figures are released ones only: no draft to tell apart, no choice to offer. */\n filter:\n this.scope === 'own'\n ? {\n type: 'select-buttons',\n operator: 'in',\n label: '',\n options: [\n { display: i18next.t('figure.label.draft'), value: 'draft' },\n { display: i18next.t('figure.label.figure-released'), value: 'released' }\n ]\n }\n : undefined,\n width: 120\n },\n {\n /*\n **몇 개까지 놓을 수 있나.** 카드에는 싣지 않고 표 모드의 칸으로 둔다 — 고른 뒤에\n 따지는 값이다. 서버에 없는 값이라 `groups` 와 `triangles` 에서 계산하는데, 저작면\n 머리와 **같은 함수**를 쓴다. 두 화면이 같은 형상을 다른 잣대로 말하면 무엇을 고칠지\n 모른다.\n */\n type: 'string',\n name: 'capacity',\n header: i18next.t('figure.label.placement-limit'),\n sortable: false,\n record: {\n editable: false,\n renderer: (_value: unknown, _column: any, record: Figure) => {\n const capacity = capacityFrom(record.groups ?? 0, record.triangles ?? 0)\n\n return capacity\n ? html`<span style=${CAPACITY} title=${i18next.t('figure.text.reuse-score-explained')}>\n ${i18next.t('figure.text.fits-n-instances', { n: capacity.instances.toLocaleString() })}\n </span>`\n : html`<span style=${UNMEASURED}>${i18next.t('figure.text.not-measured-yet')}</span>`\n }\n },\n width: 120\n },\n {\n type: 'float',\n name: 'score',\n header: i18next.t('figure.label.maturity'),\n record: {\n editable: false,\n /*\n 카드에 남긴 하나뿐인 숫자. **이름표를 달지 않으므로** 무엇인지는 손이 갔을 때 뜨는\n 말이 알려 준다.\n\n 색칠한 배지로 두지 않는다. 옛 draw call 한도 3 으로 재던 때는 카탈로그 거의 모두가 0 점이라\n 붉은 덩어리가 카드마다 섰고 아무것도 가르지 못했다. 이 모델은 「한 화면 N 대」 목표에 견준\n 여유라(`figureScoreOf`, 2026-09-24) 퍼지지만, 숫자는 조용히 적고 낮은 것에만 **점 하나**를 붙인다.\n\n 아직 재지 않은 것을 0 으로 보여 주지 않는다 — 0 은 「가볍다」는 뜻이 되어 버린다.\n 등급 문자(A~D)도 여기서 만들지 않는다: 경계는 `figure-model` 의 `scoreOf` · `figureScoreOf` 가 정하고,\n 화면이 그것을 베끼면 두 벌이 되어 목록과 저작면이 다른 등급을 말한다.\n */\n renderer: (value: number | null) =>\n value == null\n ? html`<span style=${UNMEASURED}>${i18next.t('figure.text.not-measured-yet')}</span>`\n : html`<span style=${SCORE} title=${i18next.t('figure.label.maturity')}>\n ${value < ATTENTION_BELOW ? html`<i style=${LOW_DOT}></i>` : ''}${Math.round(value)}\n </span>`\n },\n sortable: true,\n width: 90\n },\n {\n type: 'integer',\n name: 'triangles',\n header: i18next.t('figure.label.triangles'),\n record: {\n editable: false,\n /*\n 카드에서는 이름표 없이 서므로 숫자만 두면 옆의 성숙도와 구별되지 않는다. 무엇의\n 수인지 값이 스스로 말하게 한다 — 표 모드에서는 머리글이 그 일을 하므로 겹치지만,\n 그 겹침이 카드에서 뜻을 잃는 것보다 낫다.\n */\n renderer: (value: number | null) =>\n value == null ? '' : i18next.t('figure.text.triangle-count', { count: value })\n },\n sortable: true,\n width: 110\n },\n {\n type: 'integer',\n name: 'groups',\n header: i18next.t('figure.label.budget-groups'),\n record: { editable: false },\n hidden: true,\n width: 0\n },\n {\n /*\n **카드의 그림 칸 전체를 이 renderer 가 그린다.**\n\n 기본 image renderer 는 그림만 그리고, 그 위에 얹혀야 하는 것(초안 표시·삭제)은 카드\n 아래 gutter 줄로 밀린다. 격자로 옮기기 전 손 카드가 그것을 그림 위에 두었고 그 편이\n 맞다 — 그래서 그림 칸을 `figure-thumb` 에게 넘긴다. hover 가 필요해 컴포넌트여야\n 하는 사연은 그 파일에 적혀 있다.\n\n `renderEmpty` — 그림이 아직 없는 것도 자리를 지킨다. 그 카드에도 초안 표시와 삭제가\n 있어야 하고, 「그림 없음」이라고 말하는 것도 `figure-thumb` 이 한다.\n */\n type: 'image',\n name: 'thumbnail',\n header: i18next.t('field.thumbnail'),\n record: {\n editable: false,\n align: 'center',\n wide: true,\n renderEmpty: true,\n renderer: (_value: unknown, _column: any, record: Figure) => html`\n <figure-thumb .figure=${record} ?readonly=${this.scope === 'public'} @remove=${() => this.removeFigure(record)}></figure-thumb>\n `\n },\n hidden: true,\n width: 160\n },\n {\n type: 'datetime',\n name: 'updatedAt',\n header: i18next.t('field.updated_at'),\n label: true,\n record: { editable: false },\n sortable: true,\n width: 180\n },\n {\n /*\n `resource-object` 를 쓰지 않는다 — 그 renderer 는 다른 패키지(resource-ui)가 등록하고,\n 등록되지 않은 앱에서는 조용히 글자 renderer 로 떨어져 `[object Object]` 가 된다.\n 이름 하나 꺼내는 일에 그 위험을 질 이유가 없다.\n */\n type: 'string',\n name: 'updater',\n header: i18next.t('field.updater'),\n record: {\n editable: false,\n renderer: (value: { name?: string } | null) => value?.name || ''\n },\n hidden: true,\n width: 120\n }\n ],\n\n rows: {\n selectable: false,\n appendable: false,\n handlers: {\n click: (_columns: any, _data: any, _column: any, record: Figure) => {\n if (record?.id) {\n openFigureDetail(record, this.scope === 'public')\n }\n }\n }\n },\n\n sorters: [{ name: 'updatedAt', desc: true }]\n }\n }\n\n connectedCallback() {\n super.connectedCallback()\n\n /*\n **로그인 정보가 도착하면 다시 불러온다.**\n\n 화면이 서는 것과 프로필이 오는 것은 순서가 정해져 있지 않고, 목록 질의는 로그인된 사람으로\n 나간다. 첫 질의가 프로필보다 앞서면 그 한 번이 실패하고, 그 뒤로 다시 묻는 사람이 없으면\n 화면은 비어 있는 채로 남는다. lite-menu 도 같은 신호로 같은 순서 문제를 푼다\n (`auth.on('profile')` — 그쪽 주석에 그 사연이 적혀 있다).\n */\n this.listen = () => {\n if (this.active) {\n this.grist?.fetch()\n }\n }\n auth.on('profile', this.listen)\n /* A picture chosen in the preview, or a figure retired or brought back: the cards are read again. */\n window.addEventListener(FIGURE_THUMBNAIL_CHANGED, this.cardsChanged)\n window.addEventListener(FIGURE_RETIREMENT_CHANGED, this.cardsChanged)\n window.addEventListener(FIGURE_PUBLIC_CHANGED, this.cardsChanged)\n }\n private cardsChanged = () => {\n if (this.active) this.grist?.fetch()\n }\n\n disconnectedCallback() {\n if (this.listen) {\n auth.off?.('profile', this.listen)\n this.listen = undefined\n }\n window.removeEventListener(FIGURE_THUMBNAIL_CHANGED, this.cardsChanged)\n window.removeEventListener(FIGURE_RETIREMENT_CHANGED, this.cardsChanged)\n window.removeEventListener(FIGURE_PUBLIC_CHANGED, this.cardsChanged)\n super.disconnectedCallback()\n }\n\n async pageUpdated() {\n if (this.active) {\n this.grist.fetch()\n }\n }\n\n /**\n * 걸러 보기·정렬·쪽을 **격자가 만든 그대로** 서버에 넘긴다.\n *\n * 여기서 이름을 바꿔 담으면 격자가 아는 조건과 서버에 가는 조건이 어긋난다. 실패는 격자가\n * 받아 화면에 낸다 — 목록이 비어 있는 것과 못 불러온 것은 다른 사실이다.\n */\n async fetchHandler({ page = 1, limit = 24, sortings = [], filters = [] }: FetchOption) {\n const { items, total } = await fetchFigureList({ page, limit, filters, sortings, scope: this.scope })\n\n return { total: total || 0, records: items || [] }\n }\n\n /** The modeller that can edit this figure, by its format; a new figure starts in (`modeller-route.ts`). */\n private openModeller(figure?: Figure) {\n navigate(modellerPathOf(figure))\n }\n\n /** `remove` 는 HTMLElement 의 메서드다 — 가리면 조용한 결함이 된다. */\n private async removeFigure(figure: Figure) {\n if (this.scope === 'public') return\n // 되돌릴 수 없는 조작이므로 무엇이 사라지는지 이름으로 확인한다\n if (hasBeenReleased(figure)) {\n notify({ level: 'info', message: i18next.t('figure.text.released-figure-not-deletable', { name: figure.name }) })\n return\n }\n const message = i18next.t('figure.text.delete-figure-confirm', { name: figure.name })\n if (!confirm(message)) {\n return\n }\n\n try {\n await deleteFigure(figure.id)\n } catch (e) {\n notify({ level: 'error', message: (e as Error).message })\n return\n }\n this.grist.fetch()\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"figure-list-page.js","sourceRoot":"","sources":["../../client/pages/figure-list-page.ts"],"names":[],"mappings":"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","sourcesContent":["import '@operato/data-grist'\nimport '@material/web/icon/icon.js'\nimport '../viewparts/figure-thumb.js'\n\nimport { css, html, nothing } from 'lit'\nimport { customElement, property, query } from 'lit/decorators.js'\n\nimport type { DataGrist, FetchOption } from '@operato/data-grist'\nimport { i18next, localize } from '@operato/i18n'\nimport { p13n } from '@operato/p13n'\nimport { auth } from '@things-factory/auth-base/dist-client'\nimport { navigate, PageView } from '@operato/shell'\nimport { notify } from '@operato/layout'\nimport { CardCatalogStyles, CommonGristStyles, CommonHeaderStyles, ScrollbarStyles, WorkSurfacePageStyles } from '@operato/styles'\n\nimport { capacityFrom } from '../modeller/scene-budget.js'\nimport { deleteFigure, fetchFigureList } from '../graphql/index.js'\nimport type { Figure } from '../types.js'\nimport { hasBeenReleased } from '../figure-released.js'\nimport { modellerPathOf } from '../modeller/modeller-route.js'\nimport { FIGURE_PUBLIC_CHANGED, FIGURE_RETIREMENT_CHANGED, FIGURE_THUMBNAIL_CHANGED, openFigureDetail } from '../viewparts/figure-detail-dock.js'\n\n/**\n * 이 값 아래면 점수를 색으로 눈에 띄게 한다.\n *\n * **등급 경계가 아니다.** 등급(A~D)은 `figure-model` 의 `scoreOf` 가 정하고 화면은 그것을\n * 베끼지 않는다. 이것은 「눈에 걸리게 할 선」이고, 화면의 판단이다.\n */\nconst ATTENTION_BELOW = 50\n\n/*\n * ── renderer 가 그리는 것은 이 파일의 CSS 가 닿지 않는다 ──────────────────────\n *\n * 컬럼 renderer 의 결과는 `ox-card-field` 의 shadow root 안에 그려진다. 이 페이지의 `static\n * styles` 는 그 경계를 넘지 못하므로 `code[figure-type]` 같은 선택자는 **아무 일도 하지 않는다.**\n * 넘어가는 것은 custom property 뿐이라, 색과 글꼴은 여기서 인라인으로 준다 — 값 자체는 테마\n * 토큰을 가리키므로 밝음·어둠은 따라온다. data-grist 의 image renderer 도 같은 방식이다.\n */\nconst CAPACITY = 'font-weight: 700; color: var(--md-sys-color-primary);'\nconst UNMEASURED = 'font-style: italic; color: var(--md-sys-color-outline);'\nconst RELEASED = 'font-weight: 700; color: var(--md-sys-color-primary);'\nconst DRAFT = 'color: var(--md-sys-color-tertiary);'\n/**\n * 조용히 적는 숫자. 낮은 것만 점으로 눈에 걸리게 한다.\n *\n * **`inline-flex` 를 쓰지 않는다.** 썼더니 점이 뜨는 카드에서만 이 값이 옆의 두 값과 밑줄이\n * 어긋났다 — inline-flex 상자의 밑줄은 **첫 항목**에서 오는데, 첫 항목이 글자 없는 점이라\n * 밑줄이 없어 브라우저가 상자 아래를 밑줄로 삼는다. 점이 없는 카드(50 이상)는 멀쩡했으므로\n * 한 줄에 세 값이 나란히 설 때만 눈에 띄었다.\n *\n * 그냥 인라인으로 두면 이 상자의 밑줄이 곧 숫자의 밑줄이라 옆의 둘과 맞는다. 점은 제 몫으로\n * 글자 가운데에 맞춘다.\n */\nconst SCORE = 'font-variant-numeric: tabular-nums;'\nconst LOW_DOT =\n 'display: inline-block; width: 5px; height: 5px; margin-right: 4px; vertical-align: middle; ' +\n 'border-radius: 50%; background-color: var(--md-sys-color-error);'\n\n/*\n * `as typeof PageView` 로 조이지 않는다 — 그렇게 하면 p13n 이 얹어 주는\n * `getPagePreferenceProvider` 가 타입에서 사라져 개인 설정을 붙일 수 없다.\n */\nconst FigureListPageBase = p13n(localize(i18next)(PageView))\n\n/**\n * Figure 카탈로그.\n *\n * 카드를 고르면 상세 패널에서 저장본과 발행판을 살펴본다. 크기와 움직임을 시험한 뒤\n * 「편집 화면 열기」로 저작면에 들어간다. 목록 질의는 자산 원본을 싣지 않는다.\n *\n * ## 왜 손으로 만든 카드가 아니라 격자인가\n *\n * 전에는 카드 격자와 걸러 보기 줄과 빈 화면을 이 파일이 직접 그렸다(200줄). 그 자리에서 빠져\n * 있던 것들은 하나같이 `ox-grist` 가 이미 가진 것이다 — 쪽 나누기, 정렬, 컬럼 걸러 보기,\n * 개인 설정(모드·정렬·쪽 크기를 사람마다 기억한다), 표 모드로 바꿔 보기, 내보내기.\n * 손으로 만든 카드는 그 중 아무것도 없었고, 목록이 늘어나면 전부 한 번에 그렸다.\n *\n * 카드에 실을 것은 `list` 가 정하고, 무엇을 어떻게 그릴지는 컬럼의 renderer 가 정한다. 같은\n * 설정이 표 모드에서도 그대로 쓰이므로 **두 모드가 다른 말을 하지 않는다.**\n */\n@customElement('figure-list-page')\nexport class FigureListPage extends FigureListPageBase {\n static styles = [\n /* The frame owns the page shell; the grist is the content it sizes (ADR-0060 decision 8). */\n WorkSurfacePageStyles,\n ScrollbarStyles,\n CommonGristStyles,\n CommonHeaderStyles,\n CardCatalogStyles,\n css`\n :host {\n background-color: var(--md-sys-color-surface);\n color: var(--md-sys-color-on-surface);\n }\n\n /*\n 카드 · 그림 칸 · 만들기 FAB 는 모든 카드 카탈로그와 같다(@operato/styles CardCatalogStyles,\n ADR-0117 개정 2026-10-13). 여기에는 이 화면에만 있는 것만 둔다.\n */\n ox-grist {\n /* 상태와 성숙도는 짧은 표식이다 — 두 줄로 세우지 않고 한 줄에 뉜다 */\n --data-card-detail-direction: row;\n /* 표 모드의 맨 위로 단추도 만들기 단추 위로 비킨다 */\n --data-list-fab-position-vertical: 64px;\n }\n `\n ]\n\n @property({ type: Object }) gristConfig: any\n\n @query('ox-grist') private grist!: DataGrist\n\n /**\n * The domain's own figures, or other domains' public ones (LV figure-server design G2), read only. Each is a screen\n * of its own on the rail (user 2026-10-19): the own catalogue is where figures are made and changed, the public one\n * only looked at, so a switch inside one list mixed two jobs. `figure-public-list-page` sets it.\n */\n protected readonly scope: 'own' | 'public' = 'own'\n\n private listen?: () => void\n\n get context() {\n return {\n title: i18next.t(this.scope === 'public' ? 'figure.title.public-figures' : 'figure.title.figures'),\n search: {\n handler: (search: string) => {\n this.grist.searchText = search\n },\n value: this.grist?.searchText || '',\n placeholder: i18next.t('figure.text.search-figure')\n },\n filter: {\n handler: () => {\n this.grist.toggleHeadroom()\n }\n }\n /*\n **만들기는 `actions` 가 아니라 FAB 이다.**\n\n `context.actions` 로 두면 페이지 바닥 줄에 글자 단추로 서는데, 그 줄은 「지금 화면에 대해\n 할 일」을 늘어놓는 자리다. 만들기는 이 화면의 유일한 주된 동작이므로 줄에 섞이지 않고\n 떠 있어야 한다. 카드를 가리지 않는 오른쪽 아래, 손이 가는 자리다(아래 render 의 [fab]).\n */\n }\n }\n\n render() {\n return html`\n <ox-grist\n class=\"page-fill\"\n mode=\"CARD\"\n @search-tag=${(e: CustomEvent) => this.findByTag(e.detail.tag)}\n .config=${this.gristConfig}\n .fetchHandler=${this.fetchHandler.bind(this)}\n .personalConfigProvider=${this.getPagePreferenceProvider('ox-grist')!}\n >\n <div slot=\"headroom\" class=\"header\">\n <div class=\"filters\">\n <ox-filters-form autofocus without-search></ox-filters-form>\n </div>\n </div>\n\n <ox-grist-personalizer slot=\"setting\"></ox-grist-personalizer>\n </ox-grist>\n\n ${this.scope === 'own'\n ? html`<button fab title=${i18next.t('figure.button.new-figure')} @click=${() => this.openModeller()}>\n <md-icon>add</md-icon>${i18next.t('figure.button.new-figure')}\n </button>`\n : nothing}\n `\n }\n\n /**\n * 카드의 태그 알약을 눌렀다 — **그 태그로 목록을 거른다.**\n *\n * 검색창에 그 말을 채워 넣는다. 격자에 조건만 걸고 창을 비워 두면 저작자는 **왜 목록이\n * 줄었는지 모른 채** 보게 되고, 되돌리려면 무엇을 지워야 하는지도 알 수 없다.\n *\n * 서버가 `tags` 뿐 아니라 이름·타입·설명·분류를 함께 훑으므로, 그 말이 다른 칸에 든 피규어도\n * 같이 온다. 태그만 정확히 거르는 것이 아니라 **찾기**다.\n */\n private findByTag(tag: string) {\n this.grist.searchText = tag\n /* 창의 값은 `context` 에서 나온다 — 다시 알려야 헤더가 새 값을 그린다. */\n this.updateContext()\n }\n\n async pageInitialized() {\n this.gristConfig = {\n /*\n **쪽 나누기는 선택이 아니다.** 격자에 가상 스크롤이 없어 받은 것을 전부 DOM 에 그린다.\n 카드 모드는 끝까지 내려가면 다음 쪽을 이어 붙이므로, 한 쪽이 작아도 목록은 계속 읽힌다.\n */\n pagination: { pages: [24, 48, 96] },\n\n /*\n 아직 아무것도 없을 때 하는 말. **이 자리가 이 화면에서 가장 중요하다** — 처음 온 사람은\n 여기서 이 도구가 무엇을 만드는 것인지 알게 된다. 조건이 걸려 비었을 때는 격자가 스스로\n 다른 말을 한다(「찾는 것이 없다」). 그 둘을 섞지 않는 것이 격자 쪽에 이미 들어 있다.\n */\n emptyNote: {\n icon: 'deployed_code',\n title: i18next.t('figure.text.no-figure-yet'),\n description: i18next.t('figure.text.what-a-figure-is')\n },\n\n list: {\n /* 그림 칸은 그 컬럼의 renderer 가 그린다 — 그림 없을 때의 안내까지 `figure-thumb` 안이다 */\n thumbnail: 'thumbnail',\n fields: ['name', 'type'],\n /*\n ── 무게를 모른 채 고르게 두지 않는다 ────────────────────────────────\n 이름과 그림만 실으면 저작자는 **이 형상이 무거운지 모른 채** 고른다. 그것을 저작면에\n 들어가서야 아는 것은 늦다. 그래서 격자로 옮기기 전 손 카드가 실었던 셋을 그대로 싣는다 —\n 몇 개까지 놓을 수 있나(배치 한도) · 성숙도 · 삼각형 수.\n\n 한 줄에 뉜다(`--data-card-detail-direction: row`). 이름표는 달지 않는다 — 값 자체가\n 「625개까지」·「삼각형 440개」로 읽히고, 성숙도만 숫자 하나여서 손이 갔을 때 이름이 뜬다.\n\n 상태는 이 줄에 없다. 그림 위 초안 표시가 그 일을 한다(`figure-thumb`) — 발행된 것에는\n 아무 말도 하지 않으므로 빈 칸이 생기지 않는다.\n */\n details: ['capacity', 'score', 'triangles']\n },\n\n columns: [\n { type: 'gutter', gutterName: 'sequence', fixed: true },\n {\n /*\n 표 모드에서만 쓰는 삭제 칸. 카드에서는 `figure-thumb` 이 그림 위에 그리므로\n `forList: false` 로 내린다 — 카드 아래 단추 줄은 감춰도 자리가 남는다.\n */\n type: 'gutter',\n gutterName: 'button',\n icon: 'delete',\n title: i18next.t('figure.button.delete-figure'),\n danger: true,\n forList: false,\n width: 40,\n handlers: {\n click: (_columns: any, _data: any, _column: any, record: Figure) => this.removeFigure(record)\n }\n },\n {\n type: 'string',\n name: 'name',\n header: i18next.t('figure.label.name'),\n record: { editable: false },\n filter: 'search',\n sortable: true,\n width: 200\n },\n {\n /*\n 타입 이름은 **저장되는 식별자**다. 표시 이름과 다르게 보이게 두어야 도면이 무엇으로\n 이 형상을 찾는지 헷갈리지 않는다.\n */\n type: 'string',\n name: 'type',\n header: i18next.t('figure.label.figure-type'),\n record: {\n editable: false,\n /* The full name a board stores: {namespace}/{type} (ADR-0114). */\n renderer: (value: string, column: unknown, record: { namespace?: string }) =>\n html`<code>${record?.namespace ? `${record.namespace}/${value}` : value}</code>`\n },\n filter: 'search',\n sortable: true,\n width: 160\n },\n {\n /*\n 태그 — **카드에서 곁줄로 보이고 검색에 걸린다.**\n\n 서버가 이미 `type`·`name`·`description`·`category`·`tags` 를 함께 훑는다. 그런데\n 화면에는 태그가 어디에도 안 보여서, 저작자가 적은 태그가 **자기 눈에 돌아오지 않았다** —\n 적어 둔 것이 쓰이는지 알 수 없으면 아무도 적지 않는다.\n\n 정렬은 걸지 않는다. 배열이라 무엇을 기준으로 줄을 세우는지 답할 수 없다.\n\n **카드에서는 이 컬럼이 그리지 않는다.** 본문에 한 줄로 두었더니 태그가 있는 카드만\n 높아져 격자가 어긋났다 — 개수가 피규어마다 다르므로 본문에 두는 한 남는 문제다.\n 카드에서는 `figure-thumb` 이 그림 위에 얹는다. 여기 남은 것은 표 모드의 칸이다.\n */\n type: 'string',\n name: 'tags',\n header: i18next.t('figure.label.tags'),\n record: {\n editable: false,\n renderer: (value: string[] | null) => (value || []).join(' · ')\n },\n filter: 'search',\n sortable: false,\n width: 180\n },\n {\n type: 'string',\n name: 'state',\n header: i18next.t('figure.label.state'),\n record: {\n editable: false,\n /*\n **발행된 것에는 아무 말도 하지 않는다.** 카탈로그에 있는 것은 대개 발행된 것이라,\n 카드마다 「발행됨」이 붙으면 그 말이 아무것도 가르지 않고 자리만 먹는다. 판 번호도\n 고를 때 쓰는 값이 아니다 — 이력 탭에 있다.\n\n 표 모드에서는 칸에 값이 있어야 하므로 그때는 발행됨도 적는다(`wide` 가 아니라 label\n 이 붙는 자리인지로 가른다 — 카드는 label 을 달지 않았다).\n */\n renderer: (value: string, _column: any, record: Figure, _rowIndex: number, field: Element) => {\n if (value !== 'released') {\n return html`<span style=${DRAFT}>${i18next.t('figure.label.draft')}</span>`\n }\n\n /* 표에서는 칸이 비면 안 된다. 카드에서만 입을 다문다 — 그리는 자리가 알려 준다. */\n return field?.tagName === 'OX-CARD-FIELD'\n ? ''\n : html`<span style=${RELEASED}\n >${i18next.t('figure.label.figure-released')}\n ${record.version ? i18next.t('figure.text.version-number', { version: record.version }) : ''}</span\n >`\n }\n },\n /*\n 전체·초안·발행됨을 단추로 고른다 — 값이 둘뿐인 조건을 펼침 목록에 넣지 않는다.\n\n 이름표는 비워 둔다. 단추에 「초안」·「발행됨」이라고 적혀 있으니 그 앞의 「상태」는\n 같은 말을 한 번 더 하는 것이고, 걸러 보기 줄에서 자리만 먹는다.\n */\n /* Another domain's public figures are released ones only: no draft to tell apart, no choice to offer. */\n filter:\n this.scope === 'own'\n ? {\n type: 'select-buttons',\n operator: 'in',\n label: '',\n options: [\n { display: i18next.t('figure.label.draft'), value: 'draft' },\n { display: i18next.t('figure.label.figure-released'), value: 'released' }\n ]\n }\n : undefined,\n width: 120\n },\n {\n /*\n **몇 개까지 놓을 수 있나.** 카드에는 싣지 않고 표 모드의 칸으로 둔다 — 고른 뒤에\n 따지는 값이다. 서버에 없는 값이라 `groups` 와 `triangles` 에서 계산하는데, 저작면\n 머리와 **같은 함수**를 쓴다. 두 화면이 같은 형상을 다른 잣대로 말하면 무엇을 고칠지\n 모른다.\n */\n type: 'string',\n name: 'capacity',\n header: i18next.t('figure.label.placement-limit'),\n sortable: false,\n record: {\n editable: false,\n renderer: (_value: unknown, _column: any, record: Figure) => {\n const capacity = capacityFrom(record.groups ?? 0, record.triangles ?? 0)\n\n return capacity\n ? html`<span style=${CAPACITY} title=${i18next.t('figure.text.reuse-score-explained')}>\n ${i18next.t('figure.text.fits-n-instances', { n: capacity.instances.toLocaleString() })}\n </span>`\n : html`<span style=${UNMEASURED}>${i18next.t('figure.text.not-measured-yet')}</span>`\n }\n },\n width: 120\n },\n {\n type: 'float',\n name: 'score',\n header: i18next.t('figure.label.maturity'),\n record: {\n editable: false,\n /*\n 카드에 남긴 하나뿐인 숫자. **이름표를 달지 않으므로** 무엇인지는 손이 갔을 때 뜨는\n 말이 알려 준다.\n\n 색칠한 배지로 두지 않는다. 옛 draw call 한도 3 으로 재던 때는 카탈로그 거의 모두가 0 점이라\n 붉은 덩어리가 카드마다 섰고 아무것도 가르지 못했다. 이 모델은 「한 화면 N 대」 목표에 견준\n 여유라(`figureScoreOf`, 2026-09-24) 퍼지지만, 숫자는 조용히 적고 낮은 것에만 **점 하나**를 붙인다.\n\n 아직 재지 않은 것을 0 으로 보여 주지 않는다 — 0 은 「가볍다」는 뜻이 되어 버린다.\n 등급 문자(A~D)도 여기서 만들지 않는다: 경계는 `figure-model` 의 `scoreOf` · `figureScoreOf` 가 정하고,\n 화면이 그것을 베끼면 두 벌이 되어 목록과 저작면이 다른 등급을 말한다.\n */\n renderer: (value: number | null) =>\n value == null\n ? html`<span style=${UNMEASURED}>${i18next.t('figure.text.not-measured-yet')}</span>`\n : html`<span style=${SCORE} title=${i18next.t('figure.label.maturity')}>\n ${value < ATTENTION_BELOW ? html`<i style=${LOW_DOT}></i>` : ''}${Math.round(value)}\n </span>`\n },\n sortable: true,\n width: 90\n },\n {\n type: 'integer',\n name: 'triangles',\n header: i18next.t('figure.label.triangles'),\n record: {\n editable: false,\n /*\n 카드에서는 이름표 없이 서므로 숫자만 두면 옆의 성숙도와 구별되지 않는다. 무엇의\n 수인지 값이 스스로 말하게 한다 — 표 모드에서는 머리글이 그 일을 하므로 겹치지만,\n 그 겹침이 카드에서 뜻을 잃는 것보다 낫다.\n */\n renderer: (value: number | null) =>\n value == null ? '' : i18next.t('figure.text.triangle-count', { count: value })\n },\n sortable: true,\n width: 110\n },\n {\n type: 'integer',\n name: 'groups',\n header: i18next.t('figure.label.budget-groups'),\n record: { editable: false },\n hidden: true,\n width: 0\n },\n {\n /*\n **카드의 그림 칸 전체를 이 renderer 가 그린다.**\n\n 기본 image renderer 는 그림만 그리고, 그 위에 얹혀야 하는 것(초안 표시·삭제)은 카드\n 아래 gutter 줄로 밀린다. 격자로 옮기기 전 손 카드가 그것을 그림 위에 두었고 그 편이\n 맞다 — 그래서 그림 칸을 `figure-thumb` 에게 넘긴다. hover 가 필요해 컴포넌트여야\n 하는 사연은 그 파일에 적혀 있다.\n\n `renderEmpty` — 그림이 아직 없는 것도 자리를 지킨다. 그 카드에도 초안 표시와 삭제가\n 있어야 하고, 「그림 없음」이라고 말하는 것도 `figure-thumb` 이 한다.\n */\n type: 'image',\n name: 'thumbnail',\n header: i18next.t('field.thumbnail'),\n record: {\n editable: false,\n align: 'center',\n wide: true,\n renderEmpty: true,\n renderer: (_value: unknown, _column: any, record: Figure) => html`\n <figure-thumb .figure=${record} ?readonly=${this.scope === 'public'} @remove=${() => this.removeFigure(record)}></figure-thumb>\n `\n },\n hidden: true,\n width: 160\n },\n {\n type: 'datetime',\n name: 'updatedAt',\n header: i18next.t('field.updated_at'),\n label: true,\n record: { editable: false },\n sortable: true,\n width: 180\n },\n {\n /*\n `resource-object` 를 쓰지 않는다 — 그 renderer 는 다른 패키지(resource-ui)가 등록하고,\n 등록되지 않은 앱에서는 조용히 글자 renderer 로 떨어져 `[object Object]` 가 된다.\n 이름 하나 꺼내는 일에 그 위험을 질 이유가 없다.\n */\n type: 'string',\n name: 'updater',\n header: i18next.t('field.updater'),\n record: {\n editable: false,\n renderer: (value: { name?: string } | null) => value?.name || ''\n },\n hidden: true,\n width: 120\n }\n ],\n\n rows: {\n selectable: false,\n appendable: false,\n handlers: {\n click: (_columns: any, _data: any, _column: any, record: Figure) => {\n if (record?.id) {\n openFigureDetail(record, this.scope === 'public')\n }\n }\n }\n },\n\n sorters: [{ name: 'updatedAt', desc: true }]\n }\n }\n\n connectedCallback() {\n super.connectedCallback()\n\n /*\n **로그인 정보가 도착하면 다시 불러온다.**\n\n 화면이 서는 것과 프로필이 오는 것은 순서가 정해져 있지 않고, 목록 질의는 로그인된 사람으로\n 나간다. 첫 질의가 프로필보다 앞서면 그 한 번이 실패하고, 그 뒤로 다시 묻는 사람이 없으면\n 화면은 비어 있는 채로 남는다. lite-menu 도 같은 신호로 같은 순서 문제를 푼다\n (`auth.on('profile')` — 그쪽 주석에 그 사연이 적혀 있다).\n */\n this.listen = () => {\n if (this.active) {\n this.grist?.fetch()\n }\n }\n auth.on('profile', this.listen)\n /* A picture chosen in the preview, or a figure retired or brought back: the cards are read again. */\n window.addEventListener(FIGURE_THUMBNAIL_CHANGED, this.cardsChanged)\n window.addEventListener(FIGURE_RETIREMENT_CHANGED, this.cardsChanged)\n window.addEventListener(FIGURE_PUBLIC_CHANGED, this.cardsChanged)\n }\n private cardsChanged = () => {\n if (this.active) this.grist?.fetch()\n }\n\n disconnectedCallback() {\n if (this.listen) {\n auth.off?.('profile', this.listen)\n this.listen = undefined\n }\n window.removeEventListener(FIGURE_THUMBNAIL_CHANGED, this.cardsChanged)\n window.removeEventListener(FIGURE_RETIREMENT_CHANGED, this.cardsChanged)\n window.removeEventListener(FIGURE_PUBLIC_CHANGED, this.cardsChanged)\n super.disconnectedCallback()\n }\n\n async pageUpdated() {\n if (this.active) {\n this.grist.fetch()\n }\n }\n\n /**\n * 걸러 보기·정렬·쪽을 **격자가 만든 그대로** 서버에 넘긴다.\n *\n * 여기서 이름을 바꿔 담으면 격자가 아는 조건과 서버에 가는 조건이 어긋난다. 실패는 격자가\n * 받아 화면에 낸다 — 목록이 비어 있는 것과 못 불러온 것은 다른 사실이다.\n */\n async fetchHandler({ page = 1, limit = 24, sortings = [], filters = [] }: FetchOption) {\n const { items, total } = await fetchFigureList({ page, limit, filters, sortings, scope: this.scope })\n\n return { total: total || 0, records: items || [] }\n }\n\n /** The modeller that can edit this figure, by its format; a new figure starts in (`modeller-route.ts`). */\n private openModeller(figure?: Figure) {\n navigate(modellerPathOf(figure))\n }\n\n /** `remove` 는 HTMLElement 의 메서드다 — 가리면 조용한 결함이 된다. */\n private async removeFigure(figure: Figure) {\n if (this.scope === 'public') return\n // 되돌릴 수 없는 조작이므로 무엇이 사라지는지 이름으로 확인한다\n if (hasBeenReleased(figure)) {\n notify({ level: 'info', message: i18next.t('figure.text.released-figure-not-deletable', { name: figure.name }) })\n return\n }\n const message = i18next.t('figure.text.delete-figure-confirm', { name: figure.name })\n if (!confirm(message)) {\n return\n }\n\n try {\n await deleteFigure(figure.id)\n } catch (e) {\n notify({ level: 'error', message: (e as Error).message })\n return\n }\n this.grist.fetch()\n }\n}\n"]}
|
|
@@ -28,7 +28,7 @@ export declare class FigureModellerPage extends PageView {
|
|
|
28
28
|
* 발행 패널이 읽는 행의 사실 — 상태와 발행 판 번호. 원본은 세션이 들고, 이 둘은 행에 있다.
|
|
29
29
|
*
|
|
30
30
|
* 이 화면에는 발행 패널이 없었다. 저장은 되고 발행 · 판본 · 복원 · 판정을 볼 자리가 없어서, ADR-0087 5단계에서
|
|
31
|
-
* `figure-modeller` 가 이 화면으로 바뀌면 저작자는 발행을 할 수 없었다. 새
|
|
31
|
+
* `figure-modeller` 가 이 화면으로 바뀌면 저작자는 발행을 할 수 없었다. 새 피규어는 공간을 선언하기 전에는 전부
|
|
32
32
|
* 발행 판정에 막히는데 그 선언을 할 단추도 없었다(2026-09-24 확인). 그래서 발행 패널을 건다.
|
|
33
33
|
*/
|
|
34
34
|
/** The categories the server allows, or null when a category is free text (G5). */
|
|
@@ -55,9 +55,9 @@ export declare class FigureModellerPage extends PageView {
|
|
|
55
55
|
private toldDemoted;
|
|
56
56
|
private readRow;
|
|
57
57
|
/**
|
|
58
|
-
* 타입을 정한다 — 보드가
|
|
58
|
+
* 타입을 정한다 — 보드가 피규어를 부르는 이름이고, 발행하면 굳는다(ADR-0063 결정 4).
|
|
59
59
|
*
|
|
60
|
-
* 새
|
|
60
|
+
* 새 피규어는 저장할 때 임시 타입을 받는데, 이 화면에는 그것을 바꿀 칸이 없어서 임시 타입 그대로 발행됐다.
|
|
61
61
|
* 행만 바뀐다 — 자산은 타입을 원본에 적지 않으므로(적으면 형식이 거절한다) 편집 중인 원본과 부딪히지 않는다.
|
|
62
62
|
* 서버의 거절(이미 쓰는 타입 · 발행 뒤)은 그 문장 그대로 칸 아래에 둔다.
|
|
63
63
|
*/
|
|
@@ -45,7 +45,7 @@ let FigureModellerPage = class FigureModellerPage extends PageView {
|
|
|
45
45
|
* 발행 패널이 읽는 행의 사실 — 상태와 발행 판 번호. 원본은 세션이 들고, 이 둘은 행에 있다.
|
|
46
46
|
*
|
|
47
47
|
* 이 화면에는 발행 패널이 없었다. 저장은 되고 발행 · 판본 · 복원 · 판정을 볼 자리가 없어서, ADR-0087 5단계에서
|
|
48
|
-
* `figure-modeller` 가 이 화면으로 바뀌면 저작자는 발행을 할 수 없었다. 새
|
|
48
|
+
* `figure-modeller` 가 이 화면으로 바뀌면 저작자는 발행을 할 수 없었다. 새 피규어는 공간을 선언하기 전에는 전부
|
|
49
49
|
* 발행 판정에 막히는데 그 선언을 할 단추도 없었다(2026-09-24 확인). 그래서 발행 패널을 건다.
|
|
50
50
|
*/
|
|
51
51
|
/** The categories the server allows, or null when a category is free text (G5). */
|
|
@@ -122,15 +122,15 @@ let FigureModellerPage = class FigureModellerPage extends PageView {
|
|
|
122
122
|
this.routeId = view.id;
|
|
123
123
|
navigate(modellerPathOf({ id: view.id }), true);
|
|
124
124
|
}
|
|
125
|
-
/* 저장이 끝난 순간 행을 다시 읽는다 — 발행된
|
|
125
|
+
/* 저장이 끝난 순간 행을 다시 읽는다 — 발행된 피규어를 고쳐 저장하면 서버가 초안으로 내린다. */
|
|
126
126
|
if (this.wasDirty && !view.dirty && !view.saving && view.id)
|
|
127
127
|
this.readRow(view.id);
|
|
128
128
|
this.wasDirty = !!view.dirty;
|
|
129
129
|
};
|
|
130
130
|
/**
|
|
131
|
-
* 타입을 정한다 — 보드가
|
|
131
|
+
* 타입을 정한다 — 보드가 피규어를 부르는 이름이고, 발행하면 굳는다(ADR-0063 결정 4).
|
|
132
132
|
*
|
|
133
|
-
* 새
|
|
133
|
+
* 새 피규어는 저장할 때 임시 타입을 받는데, 이 화면에는 그것을 바꿀 칸이 없어서 임시 타입 그대로 발행됐다.
|
|
134
134
|
* 행만 바뀐다 — 자산은 타입을 원본에 적지 않으므로(적으면 형식이 거절한다) 편집 중인 원본과 부딪히지 않는다.
|
|
135
135
|
* 서버의 거절(이미 쓰는 타입 · 발행 뒤)은 그 문장 그대로 칸 아래에 둔다.
|
|
136
136
|
*/
|