@things-factory/figure-ui 10.1.205 → 10.1.207
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/editor-styles.ts +16 -0
- package/client/modeller/editor.ts +53 -137
- package/client/modeller/fasten-form.ts +50 -11
- package/client/modeller/figure-facts.ts +1 -1
- package/client/modeller/figure-fasten-dialog.ts +3 -2
- package/client/modeller/figure-history-panel.ts +1 -29
- package/client/modeller/figure-links.ts +2 -2
- package/client/modeller/figure-preview.ts +10 -12
- package/client/modeller/figure-verdict.ts +2 -17
- package/client/modeller/gizmo.ts +10 -0
- package/client/modeller/keys.ts +10 -1
- package/client/modeller/links.ts +32 -41
- package/client/modeller/maturity.ts +11 -27
- package/client/modeller/palette.ts +1 -1
- package/client/modeller/product-session.ts +16 -4
- package/client/modeller/refusal-teller.ts +3 -25
- package/client/modeller/refusals.ts +20 -33
- package/client/pages/figure-modeller-page.ts +7 -22
- package/dist-client/modeller/editor-styles.js +16 -0
- package/dist-client/modeller/editor-styles.js.map +1 -1
- package/dist-client/modeller/editor.d.ts +8 -11
- package/dist-client/modeller/editor.js +57 -138
- package/dist-client/modeller/editor.js.map +1 -1
- package/dist-client/modeller/fasten-form.d.ts +16 -7
- package/dist-client/modeller/fasten-form.js +45 -11
- package/dist-client/modeller/fasten-form.js.map +1 -1
- package/dist-client/modeller/figure-facts.js +1 -1
- package/dist-client/modeller/figure-facts.js.map +1 -1
- package/dist-client/modeller/figure-fasten-dialog.js +3 -3
- package/dist-client/modeller/figure-fasten-dialog.js.map +1 -1
- package/dist-client/modeller/figure-history-panel.d.ts +0 -16
- package/dist-client/modeller/figure-history-panel.js +1 -27
- package/dist-client/modeller/figure-history-panel.js.map +1 -1
- package/dist-client/modeller/figure-links.js +2 -2
- package/dist-client/modeller/figure-links.js.map +1 -1
- package/dist-client/modeller/figure-preview.js +8 -11
- package/dist-client/modeller/figure-preview.js.map +1 -1
- package/dist-client/modeller/figure-verdict.d.ts +0 -5
- package/dist-client/modeller/figure-verdict.js +2 -21
- package/dist-client/modeller/figure-verdict.js.map +1 -1
- package/dist-client/modeller/gizmo.d.ts +6 -0
- package/dist-client/modeller/gizmo.js +11 -0
- package/dist-client/modeller/gizmo.js.map +1 -1
- package/dist-client/modeller/keys.d.ts +1 -1
- package/dist-client/modeller/keys.js +6 -0
- package/dist-client/modeller/keys.js.map +1 -1
- package/dist-client/modeller/links.d.ts +16 -22
- package/dist-client/modeller/links.js +26 -39
- package/dist-client/modeller/links.js.map +1 -1
- package/dist-client/modeller/maturity.d.ts +2 -4
- package/dist-client/modeller/maturity.js +12 -26
- package/dist-client/modeller/maturity.js.map +1 -1
- package/dist-client/modeller/palette.js +1 -1
- package/dist-client/modeller/palette.js.map +1 -1
- package/dist-client/modeller/product-session.d.ts +15 -13
- package/dist-client/modeller/product-session.js +15 -5
- package/dist-client/modeller/product-session.js.map +1 -1
- package/dist-client/modeller/refusal-teller.d.ts +1 -12
- package/dist-client/modeller/refusal-teller.js +3 -22
- package/dist-client/modeller/refusal-teller.js.map +1 -1
- package/dist-client/modeller/refusals.d.ts +7 -17
- package/dist-client/modeller/refusals.js +17 -34
- package/dist-client/modeller/refusals.js.map +1 -1
- package/dist-client/pages/figure-modeller-page.d.ts +1 -3
- package/dist-client/pages/figure-modeller-page.js +7 -23
- 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/browser/acceptance-page.js +4 -4
- package/test/browser/detail-page.js +1 -3
- package/test/browser/detail-tabs-page.js +1 -2
- package/test/browser/links.check.mjs +32 -50
- package/test/browser/modeller.check.mjs +46 -19
- package/test/browser/parts-page.js +2 -2
- package/test/browser/parts.check.mjs +51 -12
- package/test/browser/product-page.check.mjs +6 -9
- package/test/browser/product-page.js +1 -2
- package/test/browser/screen-page.js +2 -6
- package/test/browser/screen.check.mjs +8 -65
- package/test/browser/section.check.mjs +27 -0
- package/test/fasten-form.test.ts +42 -2
- package/test/first-minute.test.ts +2 -3
- package/test/fixtures/joint-column.json +1 -66
- package/test/fixtures/joint-roller.json +1 -66
- package/test/fixtures/robot-arm-6dof.json +2 -67
- package/test/gizmo.test.ts +12 -1
- package/test/keys.test.ts +11 -0
- package/test/links.test.ts +39 -34
- package/test/parts-wiring.test.ts +1 -1
- package/test/properties.test.ts +9 -12
- package/test/refusals.test.ts +20 -42
- package/test/resize-with-placement.test.ts +13 -15
- package/test/revision.test.ts +1 -2
- package/test/verdict.test.ts +1 -15
- package/translations/en.json +8 -32
- package/translations/es.json +8 -32
- package/translations/ja.json +8 -32
- package/translations/ko.json +8 -32
- package/translations/ms.json +8 -32
- package/translations/zh.json +8 -32
- package/client/modeller/occupancy.ts +0 -51
- package/dist-client/modeller/occupancy.d.ts +0 -31
- package/dist-client/modeller/occupancy.js +0 -36
- package/dist-client/modeller/occupancy.js.map +0 -1
- package/test/release-panel.test.ts +0 -53
|
@@ -130,6 +130,22 @@ export const EditorStyles = css `
|
|
|
130
130
|
min-width: 0;
|
|
131
131
|
min-height: 0;
|
|
132
132
|
}
|
|
133
|
+
/* Which size handles change one diameter (ADR-0140 4.1): a quiet line over the canvas, out of the gizmo's way. */
|
|
134
|
+
p[same-measure] {
|
|
135
|
+
position: absolute;
|
|
136
|
+
bottom: 12px;
|
|
137
|
+
left: 50%;
|
|
138
|
+
z-index: 5;
|
|
139
|
+
transform: translateX(-50%);
|
|
140
|
+
margin: 0;
|
|
141
|
+
padding: 4px 10px;
|
|
142
|
+
border-radius: 999em;
|
|
143
|
+
background: var(--md-sys-color-surface-container-high);
|
|
144
|
+
color: var(--md-sys-color-on-surface-variant);
|
|
145
|
+
font-size: var(--ui-t-small, 11.5px);
|
|
146
|
+
pointer-events: none;
|
|
147
|
+
white-space: nowrap;
|
|
148
|
+
}
|
|
133
149
|
/* A new figure's first question, over the empty canvas. */
|
|
134
150
|
div[start] {
|
|
135
151
|
position: absolute;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"editor-styles.js","sourceRoot":"","sources":["../../client/modeller/editor-styles.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAA;AAEzB,gHAAgH;AAChH,MAAM,CAAC,MAAM,YAAY,GAAG,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4Z9B,CAAA","sourcesContent":["import { css } from 'lit'\n\n/** The modeller editor's own styles: the bar's tools, the panes, the start card, the side's tabs and panels. */\nexport const EditorStyles = css`\n :host {\n position: relative;\n display: flex;\n flex-direction: column;\n min-height: 0;\n height: 100%;\n /* The editor folds by the width it is given, not the window's (UX ruling, 2026-09-24). */\n container-type: inline-size;\n /* The app's one face (its typography theme's --theme-font) at the body size: a bare sans-serif here put a second\n face under everything the editor draws, beside the parts list and the shell in the app's own (2026-10-16). */\n font-family: var(--theme-font, ui-sans-serif, system-ui, sans-serif);\n font-size: var(--ui-t-body, 13px);\n color: var(--md-sys-color-on-surface);\n }\n /* The bar over the work is ox-canvas-bar (ADR-0060 decision 8): the folds at its edges, undo, redo and\n fit in the suite's places. What the editor puts in it - the mode switch, the status, save - is styled here. */\n ox-canvas-bar button[mode] {\n height: 28px;\n border: 1px solid transparent;\n padding: 0 10px;\n border-radius: 999em;\n border-color: transparent;\n background: none;\n }\n ox-canvas-bar button[mode][aria-pressed='true'] {\n border-color: var(--md-sys-color-primary);\n }\n /* The gizmo's tools, drawn as the bar's own icon buttons (undo, redo, fit). */\n ox-canvas-bar .gizmo-tools {\n display: inline-flex;\n gap: 2px;\n padding-left: 4px;\n border-left: 1px solid var(--md-sys-color-outline-variant);\n }\n ox-canvas-bar button[tool] {\n display: inline-grid;\n place-items: center;\n width: 32px;\n height: 32px;\n padding: 0;\n border: none;\n border-radius: var(--md-sys-shape-corner-small, 8px);\n background: transparent;\n color: var(--md-sys-color-on-surface-variant);\n cursor: pointer;\n }\n ox-canvas-bar button[tool]:hover:not(:disabled) {\n background: var(--md-sys-color-surface-container-high);\n color: var(--md-sys-color-on-surface);\n }\n ox-canvas-bar button[tool][aria-pressed='true'] {\n background: var(--md-sys-color-secondary-container);\n color: var(--md-sys-color-on-secondary-container);\n }\n ox-canvas-bar button[tool]:disabled {\n opacity: 0.38;\n cursor: default;\n }\n /*\n Slotted into the bar from here, the tools do not get the bar's own icon size (20px, set inside its shadow): they\n showed at md-icon's 24px, heavier than undo, redo and fit beside them. The same size, and the bar's focus ring.\n */\n ox-canvas-bar button[tool] md-icon {\n --md-icon-size: 20px;\n }\n ox-canvas-bar button[tool]:focus-visible {\n outline: 2px solid var(--md-sys-color-primary);\n outline-offset: 1px;\n }\n figure-preview {\n min-width: 0;\n min-height: 0;\n }\n /* The edit canvas stays standing while the preview is in front (a 3D scene costs a third of a second to make), out of sight. */\n div[middle][hidden] {\n display: none;\n }\n /* The preview, once made, stays standing out of sight too: its 3D scene is not made again each time it is asked for. */\n figure-preview[hidden] {\n display: none;\n }\n ox-canvas-bar .status {\n padding: 0 8px;\n color: var(--md-sys-color-on-surface-variant);\n }\n /*\n The bar's two commitments, one shape (user 2026-10-18: 「발행버튼도 저장버튼도 라운드 버튼으로 … 버튼 안에 적절한 아이콘」):\n round, an icon inside, so neither is taken for the other. Save, done many times a day, is the filled one; release,\n a promise to whoever draws the figure, is outlined.\n */\n ox-canvas-bar button[save],\n ox-canvas-bar button[publish] {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n height: 32px;\n padding: 0 14px 0 10px;\n border-radius: 999em;\n font-weight: 600;\n }\n ox-canvas-bar button[save] md-icon,\n ox-canvas-bar button[publish] md-icon {\n --md-icon-size: 18px;\n }\n ox-canvas-bar button[save] {\n border: 1px solid transparent;\n color: var(--md-sys-color-on-primary);\n background: var(--md-sys-color-primary);\n }\n /* The three panes are ox-three-pane's (ADR-0060 decision 8): widths 200 | 1fr | 340, 160 | 300 under\n 860px and one column with the middle first under 560px are the frame's, as are the dividers. */\n ox-three-pane {\n flex: 1;\n min-height: 0;\n }\n figure-parts {\n min-height: 0;\n }\n figure-canvas {\n flex: 1;\n min-width: 0;\n min-height: 0;\n }\n div[middle] {\n position: relative;\n display: flex;\n min-width: 0;\n min-height: 0;\n }\n /* A new figure's first question, over the empty canvas. */\n div[start] {\n position: absolute;\n inset: 50% auto auto 50%;\n z-index: 10;\n color: var(--md-sys-color-on-surface);\n transform: translate(-50%, -50%);\n width: min(440px, calc(100% - 32px));\n box-sizing: border-box;\n padding: 20px;\n border-radius: 12px;\n background: var(--md-sys-color-surface, #fff);\n box-shadow: 0 6px 24px rgba(0, 0, 0, 0.18);\n }\n div[start] button[start-button] {\n width: auto;\n margin: 0;\n font-size: var(--ui-t-body);\n height: 36px;\n padding: 0 16px;\n border: 1px solid var(--md-sys-color-outline);\n border-radius: 999em;\n color: var(--md-sys-color-primary);\n background: var(--md-sys-color-surface, #fff);\n cursor: pointer;\n }\n div[start] button[start-button]:hover:not(:disabled) {\n background: var(--md-sys-color-primary-container);\n }\n div[start] button[start-button][pressed] {\n color: var(--md-sys-color-on-primary);\n background: var(--md-sys-color-primary);\n border-color: var(--md-sys-color-primary);\n }\n div[start] button[start-button]:disabled {\n opacity: 0.5;\n cursor: default;\n }\n div[start] h2 {\n margin: 0 0 12px;\n font-size: var(--ui-t-title);\n }\n div[start-paths] {\n display: grid;\n gap: 14px;\n }\n div[start-paths] h3 {\n margin: 0 0 6px;\n font-size: var(--ui-t-body);\n }\n div[start-shapes] {\n display: flex;\n gap: 8px;\n flex-wrap: wrap;\n }\n div[start] small {\n display: block;\n margin-top: 4px;\n color: var(--md-sys-color-on-surface-variant);\n }\n /* The side: tabs over one panel at a time, each using the whole height. */\n /*\n The side's words at the panel's size (side-panel-styles: titles body, the rest small). The editor's host is at body\n for the bar, and the fields the editor drew itself -- the part's name -- stood a step larger than every other field.\n */\n aside {\n display: flex;\n flex-direction: column;\n min-height: 0;\n font-size: var(--ui-t-small, 11.5px);\n background: var(--md-sys-color-surface-container-lowest);\n }\n div[tabs] {\n display: flex;\n flex: none;\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n }\n button[tab] {\n flex: 1;\n position: relative;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 1px;\n min-width: 0;\n height: 48px;\n padding: 0;\n border: none;\n border-radius: 0;\n border-bottom: 2px solid transparent;\n background: none;\n color: var(--md-sys-color-on-surface-variant);\n }\n button[tab]:hover:not([on]) {\n background: var(--md-sys-color-surface-container);\n }\n button[tab][on] {\n color: var(--md-sys-color-primary);\n border-bottom-color: var(--md-sys-color-primary);\n }\n button[tab] md-icon {\n font-size: 20px;\n width: 20px;\n height: 20px;\n }\n button[tab] span[tab-label] {\n max-width: 100%;\n overflow: hidden;\n font-size: var(--ui-t-micro, 11px);\n line-height: 1.2;\n white-space: nowrap;\n text-overflow: ellipsis;\n }\n /* A range's two ends read as one: smallest – largest. */\n div[pair][range] {\n align-items: center;\n }\n div[pair][range] > span[dash] {\n flex: none;\n color: var(--md-sys-color-on-surface-variant);\n }\n /* The sections inside carry the margins (side-panel-styles); the panel only scrolls. */\n div[panel] {\n flex: 1;\n min-height: 0;\n overflow: auto;\n padding: 0;\n }\n div[panel][flush] {\n padding: 0;\n }\n /* Hidden, not removed: a panel keeps what it holds (the verdict its last answer) while another tab is open. */\n [off] {\n display: none !important;\n }\n /* The part's own numbers inside a group the links draw (size, place): one column, the panel's gap. */\n div[group-fields] {\n display: grid;\n gap: 6px;\n }\n /* The folded 「더 보기」: its title is the fold's handle, at the title's size. */\n section[more] > details > summary {\n padding: 0;\n }\n section[more] > details > summary > h3 {\n margin: 0;\n }\n div[more-body] {\n display: grid;\n gap: 16px;\n margin-top: 12px;\n }\n /*\n The view settings and the release history, opened from the bar over the right of the work: neither is about the\n part chosen, so neither takes a tab beside the part's (chief architect 2026-10-16). The fastening window stands in\n the same place, over the side panel, so the canvas it previews on stays in sight (user 2026-10-19).\n */\n :host {\n position: relative;\n }\n div[side-overlay] {\n position: absolute;\n top: var(--figure-bar-height, 44px);\n right: 8px;\n z-index: 5;\n display: flex;\n flex-direction: column;\n width: min(360px, calc(100% - 16px));\n max-height: calc(100% - var(--figure-bar-height, 44px) - 16px);\n background: var(--md-sys-color-surface-container-lowest);\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 8px;\n box-shadow: 0 6px 20px rgba(0, 0, 0, 0.16);\n }\n div[overlay-head] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 8px 8px 12px;\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n }\n div[overlay-head] h3 {\n margin: 0;\n }\n div[overlay-head] button[quiet] {\n margin-left: 0;\n }\n [overlay-body] {\n min-height: 0;\n overflow: auto;\n }\n /* Which part the panel speaks of. */\n div[subject] {\n position: sticky;\n top: 0;\n z-index: 1;\n display: grid;\n gap: 4px;\n padding: 10px 12px;\n background: var(--md-sys-color-surface-container-lowest);\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n }\n /* The panel's three sizes (side-panel-styles): the part's kind at the title's, its two lines at the hint's. */\n div[subject-summary] {\n display: grid;\n gap: 2px;\n }\n div[subject-summary] b {\n font-size: var(--ui-t-body, 13px);\n font-weight: 600;\n }\n div[subject-summary] b span {\n font-weight: 400;\n color: var(--md-sys-color-on-surface-variant);\n }\n div[subject-summary] small {\n color: var(--md-sys-color-on-surface-variant);\n }\n /*\n No rule here draws every button as a box: the side's buttons take the shared panel rules (SidePanelStyles), one\n height with the fields beside them, and only the bar's publish keeps an outline (user 2026-10-16: 「버튼 박스도\n 과하게 사용되어서」 -- a bare 「button」 rule had put a 32px box round every button the editor drew).\n */\n button {\n font: inherit;\n box-sizing: border-box;\n color: inherit;\n min-width: 0;\n cursor: pointer;\n }\n /* What blocks release, beside 「발행」: one red word button, and its list under the bar. */\n ox-canvas-bar button[release-blocked] {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n height: 32px;\n padding: 0 14px 0 10px;\n border: none;\n /* Round as save and release beside it: one shape for the bar's end. */\n border-radius: 999em;\n color: var(--md-sys-color-on-error-container);\n background: var(--md-sys-color-error-container);\n }\n ox-canvas-bar button[release-blocked] md-icon {\n --md-icon-size: 18px;\n }\n div[release-blockers] {\n position: absolute;\n top: 46px;\n right: 8px;\n z-index: 6;\n width: min(340px, calc(100% - 16px));\n padding: 8px 10px 10px;\n box-sizing: border-box;\n background: var(--md-sys-color-surface);\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 8px;\n box-shadow: var(--md-sys-elevation-2, 0 2px 8px #0003);\n }\n ox-canvas-bar button[publish] {\n border: 1px solid var(--md-sys-color-outline);\n color: var(--md-sys-color-primary);\n background: var(--md-sys-color-surface);\n }\n ox-canvas-bar button[publish][aria-pressed='true'] {\n background: var(--md-sys-color-secondary-container);\n color: var(--md-sys-color-on-secondary-container);\n }\n button:disabled {\n opacity: 0.45;\n cursor: default;\n }\n button[aria-pressed='true'] {\n background: var(--md-sys-color-secondary-container);\n color: var(--md-sys-color-on-secondary-container);\n border-color: var(--md-sys-color-primary);\n }\n .hint {\n color: var(--md-sys-color-on-surface-variant);\n line-height: 1.5;\n }\n`\n"]}
|
|
1
|
+
{"version":3,"file":"editor-styles.js","sourceRoot":"","sources":["../../client/modeller/editor-styles.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAA;AAEzB,gHAAgH;AAChH,MAAM,CAAC,MAAM,YAAY,GAAG,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4a9B,CAAA","sourcesContent":["import { css } from 'lit'\n\n/** The modeller editor's own styles: the bar's tools, the panes, the start card, the side's tabs and panels. */\nexport const EditorStyles = css`\n :host {\n position: relative;\n display: flex;\n flex-direction: column;\n min-height: 0;\n height: 100%;\n /* The editor folds by the width it is given, not the window's (UX ruling, 2026-09-24). */\n container-type: inline-size;\n /* The app's one face (its typography theme's --theme-font) at the body size: a bare sans-serif here put a second\n face under everything the editor draws, beside the parts list and the shell in the app's own (2026-10-16). */\n font-family: var(--theme-font, ui-sans-serif, system-ui, sans-serif);\n font-size: var(--ui-t-body, 13px);\n color: var(--md-sys-color-on-surface);\n }\n /* The bar over the work is ox-canvas-bar (ADR-0060 decision 8): the folds at its edges, undo, redo and\n fit in the suite's places. What the editor puts in it - the mode switch, the status, save - is styled here. */\n ox-canvas-bar button[mode] {\n height: 28px;\n border: 1px solid transparent;\n padding: 0 10px;\n border-radius: 999em;\n border-color: transparent;\n background: none;\n }\n ox-canvas-bar button[mode][aria-pressed='true'] {\n border-color: var(--md-sys-color-primary);\n }\n /* The gizmo's tools, drawn as the bar's own icon buttons (undo, redo, fit). */\n ox-canvas-bar .gizmo-tools {\n display: inline-flex;\n gap: 2px;\n padding-left: 4px;\n border-left: 1px solid var(--md-sys-color-outline-variant);\n }\n ox-canvas-bar button[tool] {\n display: inline-grid;\n place-items: center;\n width: 32px;\n height: 32px;\n padding: 0;\n border: none;\n border-radius: var(--md-sys-shape-corner-small, 8px);\n background: transparent;\n color: var(--md-sys-color-on-surface-variant);\n cursor: pointer;\n }\n ox-canvas-bar button[tool]:hover:not(:disabled) {\n background: var(--md-sys-color-surface-container-high);\n color: var(--md-sys-color-on-surface);\n }\n ox-canvas-bar button[tool][aria-pressed='true'] {\n background: var(--md-sys-color-secondary-container);\n color: var(--md-sys-color-on-secondary-container);\n }\n ox-canvas-bar button[tool]:disabled {\n opacity: 0.38;\n cursor: default;\n }\n /*\n Slotted into the bar from here, the tools do not get the bar's own icon size (20px, set inside its shadow): they\n showed at md-icon's 24px, heavier than undo, redo and fit beside them. The same size, and the bar's focus ring.\n */\n ox-canvas-bar button[tool] md-icon {\n --md-icon-size: 20px;\n }\n ox-canvas-bar button[tool]:focus-visible {\n outline: 2px solid var(--md-sys-color-primary);\n outline-offset: 1px;\n }\n figure-preview {\n min-width: 0;\n min-height: 0;\n }\n /* The edit canvas stays standing while the preview is in front (a 3D scene costs a third of a second to make), out of sight. */\n div[middle][hidden] {\n display: none;\n }\n /* The preview, once made, stays standing out of sight too: its 3D scene is not made again each time it is asked for. */\n figure-preview[hidden] {\n display: none;\n }\n ox-canvas-bar .status {\n padding: 0 8px;\n color: var(--md-sys-color-on-surface-variant);\n }\n /*\n The bar's two commitments, one shape (user 2026-10-18: 「발행버튼도 저장버튼도 라운드 버튼으로 … 버튼 안에 적절한 아이콘」):\n round, an icon inside, so neither is taken for the other. Save, done many times a day, is the filled one; release,\n a promise to whoever draws the figure, is outlined.\n */\n ox-canvas-bar button[save],\n ox-canvas-bar button[publish] {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n height: 32px;\n padding: 0 14px 0 10px;\n border-radius: 999em;\n font-weight: 600;\n }\n ox-canvas-bar button[save] md-icon,\n ox-canvas-bar button[publish] md-icon {\n --md-icon-size: 18px;\n }\n ox-canvas-bar button[save] {\n border: 1px solid transparent;\n color: var(--md-sys-color-on-primary);\n background: var(--md-sys-color-primary);\n }\n /* The three panes are ox-three-pane's (ADR-0060 decision 8): widths 200 | 1fr | 340, 160 | 300 under\n 860px and one column with the middle first under 560px are the frame's, as are the dividers. */\n ox-three-pane {\n flex: 1;\n min-height: 0;\n }\n figure-parts {\n min-height: 0;\n }\n figure-canvas {\n flex: 1;\n min-width: 0;\n min-height: 0;\n }\n div[middle] {\n position: relative;\n display: flex;\n min-width: 0;\n min-height: 0;\n }\n /* Which size handles change one diameter (ADR-0140 4.1): a quiet line over the canvas, out of the gizmo's way. */\n p[same-measure] {\n position: absolute;\n bottom: 12px;\n left: 50%;\n z-index: 5;\n transform: translateX(-50%);\n margin: 0;\n padding: 4px 10px;\n border-radius: 999em;\n background: var(--md-sys-color-surface-container-high);\n color: var(--md-sys-color-on-surface-variant);\n font-size: var(--ui-t-small, 11.5px);\n pointer-events: none;\n white-space: nowrap;\n }\n /* A new figure's first question, over the empty canvas. */\n div[start] {\n position: absolute;\n inset: 50% auto auto 50%;\n z-index: 10;\n color: var(--md-sys-color-on-surface);\n transform: translate(-50%, -50%);\n width: min(440px, calc(100% - 32px));\n box-sizing: border-box;\n padding: 20px;\n border-radius: 12px;\n background: var(--md-sys-color-surface, #fff);\n box-shadow: 0 6px 24px rgba(0, 0, 0, 0.18);\n }\n div[start] button[start-button] {\n width: auto;\n margin: 0;\n font-size: var(--ui-t-body);\n height: 36px;\n padding: 0 16px;\n border: 1px solid var(--md-sys-color-outline);\n border-radius: 999em;\n color: var(--md-sys-color-primary);\n background: var(--md-sys-color-surface, #fff);\n cursor: pointer;\n }\n div[start] button[start-button]:hover:not(:disabled) {\n background: var(--md-sys-color-primary-container);\n }\n div[start] button[start-button][pressed] {\n color: var(--md-sys-color-on-primary);\n background: var(--md-sys-color-primary);\n border-color: var(--md-sys-color-primary);\n }\n div[start] button[start-button]:disabled {\n opacity: 0.5;\n cursor: default;\n }\n div[start] h2 {\n margin: 0 0 12px;\n font-size: var(--ui-t-title);\n }\n div[start-paths] {\n display: grid;\n gap: 14px;\n }\n div[start-paths] h3 {\n margin: 0 0 6px;\n font-size: var(--ui-t-body);\n }\n div[start-shapes] {\n display: flex;\n gap: 8px;\n flex-wrap: wrap;\n }\n div[start] small {\n display: block;\n margin-top: 4px;\n color: var(--md-sys-color-on-surface-variant);\n }\n /* The side: tabs over one panel at a time, each using the whole height. */\n /*\n The side's words at the panel's size (side-panel-styles: titles body, the rest small). The editor's host is at body\n for the bar, and the fields the editor drew itself -- the part's name -- stood a step larger than every other field.\n */\n aside {\n display: flex;\n flex-direction: column;\n min-height: 0;\n font-size: var(--ui-t-small, 11.5px);\n background: var(--md-sys-color-surface-container-lowest);\n }\n div[tabs] {\n display: flex;\n flex: none;\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n }\n button[tab] {\n flex: 1;\n position: relative;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 1px;\n min-width: 0;\n height: 48px;\n padding: 0;\n border: none;\n border-radius: 0;\n border-bottom: 2px solid transparent;\n background: none;\n color: var(--md-sys-color-on-surface-variant);\n }\n button[tab]:hover:not([on]) {\n background: var(--md-sys-color-surface-container);\n }\n button[tab][on] {\n color: var(--md-sys-color-primary);\n border-bottom-color: var(--md-sys-color-primary);\n }\n button[tab] md-icon {\n font-size: 20px;\n width: 20px;\n height: 20px;\n }\n button[tab] span[tab-label] {\n max-width: 100%;\n overflow: hidden;\n font-size: var(--ui-t-micro, 11px);\n line-height: 1.2;\n white-space: nowrap;\n text-overflow: ellipsis;\n }\n /* A range's two ends read as one: smallest – largest. */\n div[pair][range] {\n align-items: center;\n }\n div[pair][range] > span[dash] {\n flex: none;\n color: var(--md-sys-color-on-surface-variant);\n }\n /* The sections inside carry the margins (side-panel-styles); the panel only scrolls. */\n div[panel] {\n flex: 1;\n min-height: 0;\n overflow: auto;\n padding: 0;\n }\n div[panel][flush] {\n padding: 0;\n }\n /* Hidden, not removed: a panel keeps what it holds (the verdict its last answer) while another tab is open. */\n [off] {\n display: none !important;\n }\n /* The part's own numbers inside a group the links draw (size, place): one column, the panel's gap. */\n div[group-fields] {\n display: grid;\n gap: 6px;\n }\n /* The folded 「더 보기」: its title is the fold's handle, at the title's size. */\n section[more] > details > summary {\n padding: 0;\n }\n section[more] > details > summary > h3 {\n margin: 0;\n }\n div[more-body] {\n display: grid;\n gap: 16px;\n margin-top: 12px;\n }\n /*\n The view settings and the release history, opened from the bar over the right of the work: neither is about the\n part chosen, so neither takes a tab beside the part's (chief architect 2026-10-16). The fastening window stands in\n the same place, over the side panel, so the canvas it previews on stays in sight (user 2026-10-19).\n */\n :host {\n position: relative;\n }\n div[side-overlay] {\n position: absolute;\n top: var(--figure-bar-height, 44px);\n right: 8px;\n z-index: 5;\n display: flex;\n flex-direction: column;\n width: min(360px, calc(100% - 16px));\n max-height: calc(100% - var(--figure-bar-height, 44px) - 16px);\n background: var(--md-sys-color-surface-container-lowest);\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 8px;\n box-shadow: 0 6px 20px rgba(0, 0, 0, 0.16);\n }\n div[overlay-head] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 8px 8px 12px;\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n }\n div[overlay-head] h3 {\n margin: 0;\n }\n div[overlay-head] button[quiet] {\n margin-left: 0;\n }\n [overlay-body] {\n min-height: 0;\n overflow: auto;\n }\n /* Which part the panel speaks of. */\n div[subject] {\n position: sticky;\n top: 0;\n z-index: 1;\n display: grid;\n gap: 4px;\n padding: 10px 12px;\n background: var(--md-sys-color-surface-container-lowest);\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n }\n /* The panel's three sizes (side-panel-styles): the part's kind at the title's, its two lines at the hint's. */\n div[subject-summary] {\n display: grid;\n gap: 2px;\n }\n div[subject-summary] b {\n font-size: var(--ui-t-body, 13px);\n font-weight: 600;\n }\n div[subject-summary] b span {\n font-weight: 400;\n color: var(--md-sys-color-on-surface-variant);\n }\n div[subject-summary] small {\n color: var(--md-sys-color-on-surface-variant);\n }\n /*\n No rule here draws every button as a box: the side's buttons take the shared panel rules (SidePanelStyles), one\n height with the fields beside them, and only the bar's publish keeps an outline (user 2026-10-16: 「버튼 박스도\n 과하게 사용되어서」 -- a bare 「button」 rule had put a 32px box round every button the editor drew).\n */\n button {\n font: inherit;\n box-sizing: border-box;\n color: inherit;\n min-width: 0;\n cursor: pointer;\n }\n /* What blocks release, beside 「발행」: one red word button, and its list under the bar. */\n ox-canvas-bar button[release-blocked] {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n height: 32px;\n padding: 0 14px 0 10px;\n border: none;\n /* Round as save and release beside it: one shape for the bar's end. */\n border-radius: 999em;\n color: var(--md-sys-color-on-error-container);\n background: var(--md-sys-color-error-container);\n }\n ox-canvas-bar button[release-blocked] md-icon {\n --md-icon-size: 18px;\n }\n div[release-blockers] {\n position: absolute;\n top: 46px;\n right: 8px;\n z-index: 6;\n width: min(340px, calc(100% - 16px));\n padding: 8px 10px 10px;\n box-sizing: border-box;\n background: var(--md-sys-color-surface);\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 8px;\n box-shadow: var(--md-sys-elevation-2, 0 2px 8px #0003);\n }\n ox-canvas-bar button[publish] {\n border: 1px solid var(--md-sys-color-outline);\n color: var(--md-sys-color-primary);\n background: var(--md-sys-color-surface);\n }\n ox-canvas-bar button[publish][aria-pressed='true'] {\n background: var(--md-sys-color-secondary-container);\n color: var(--md-sys-color-on-secondary-container);\n }\n button:disabled {\n opacity: 0.45;\n cursor: default;\n }\n button[aria-pressed='true'] {\n background: var(--md-sys-color-secondary-container);\n color: var(--md-sys-color-on-secondary-container);\n border-color: var(--md-sys-color-primary);\n }\n .hint {\n color: var(--md-sys-color-on-surface-variant);\n line-height: 1.5;\n }\n`\n"]}
|
|
@@ -59,8 +59,6 @@ export declare class FigureEditor extends LitElement {
|
|
|
59
59
|
/** How far the GLB being brought in has gone up (0‒1), while it does. */
|
|
60
60
|
private meshProgress?;
|
|
61
61
|
private faceMapping?;
|
|
62
|
-
/** The same link offered again with the size range narrowed, after a link the declared range could not hold. */
|
|
63
|
-
private retry?;
|
|
64
62
|
private tab;
|
|
65
63
|
/** The view settings or the release history, open over the work from the bar. */
|
|
66
64
|
private overlay?;
|
|
@@ -118,6 +116,10 @@ export declare class FigureEditor extends LitElement {
|
|
|
118
116
|
*/
|
|
119
117
|
private renderGizmoTools;
|
|
120
118
|
private keys;
|
|
119
|
+
/** The part Ctrl+C took: pasted as the kernel's duplicate. Not kept past the editor, nor written anywhere. */
|
|
120
|
+
private copied;
|
|
121
|
+
/** A copied part still in the figure is duplicated -- a part of its own, named by figure-model; one gone, nothing. */
|
|
122
|
+
private pasteCopied;
|
|
121
123
|
private keyAction;
|
|
122
124
|
private paneFold;
|
|
123
125
|
private refusals;
|
|
@@ -131,6 +133,8 @@ export declare class FigureEditor extends LitElement {
|
|
|
131
133
|
* gone or another figure opened has nothing to go back to: the editor closes.
|
|
132
134
|
*/
|
|
133
135
|
private leaveSectionEditor;
|
|
136
|
+
/** While the size tool is on a round part: which of its handles change the one diameter (ADR-0140 4.1). */
|
|
137
|
+
private renderSameMeasure;
|
|
134
138
|
/** The scene toggles: the mode already shown takes the gizmo away. */
|
|
135
139
|
private useGizmo;
|
|
136
140
|
/**
|
|
@@ -270,7 +274,7 @@ export declare class FigureEditor extends LitElement {
|
|
|
270
274
|
* pointer, and the release records one step. The field's number follows too, so the value in hand is always read.
|
|
271
275
|
*/
|
|
272
276
|
private scrub;
|
|
273
|
-
/** The sizes a board may give the figure, and the
|
|
277
|
+
/** The sizes a board may give the figure: the ends the author typed, and the rest as far as its parts allow. */
|
|
274
278
|
private renderInstanceRange;
|
|
275
279
|
/**
|
|
276
280
|
* How the figure grows. One ratio (`sizing: 'uniform'`) is said as it is; a figure with joints that has not declared
|
|
@@ -307,7 +311,7 @@ export declare class FigureEditor extends LitElement {
|
|
|
307
311
|
private setPitch;
|
|
308
312
|
/** Choose a part, from the list, the canvas or a place row. A figure-level tab gives way to the part's properties. */
|
|
309
313
|
private releaseWords;
|
|
310
|
-
/** The findings that block release, under the bar beside
|
|
314
|
+
/** The findings that block release, under the bar beside 「발행」. */
|
|
311
315
|
private renderReleaseBlockers;
|
|
312
316
|
private goRelease;
|
|
313
317
|
private overhangOf;
|
|
@@ -391,12 +395,5 @@ export declare class FigureEditor extends LitElement {
|
|
|
391
395
|
/** What a size typed keeps, under the size fields: the part's middle, where it is fastened, or only its size (ADR-0140 4.1). */
|
|
392
396
|
private renderSizeKeeps;
|
|
393
397
|
private renderHeldAxes;
|
|
394
|
-
/**
|
|
395
|
-
* A part's fields only the declared room still reads (ADR-0097, designer 2026-09-27): shown as what they are and not
|
|
396
|
-
* typed into -- they move nothing. Declaring the room again is offered; nothing is deleted or widened on the way.
|
|
397
|
-
*/
|
|
398
|
-
private renderRoomOnly;
|
|
399
|
-
/** The room as declared and as proposed, and what lies outside each as measured, before the author confirms. */
|
|
400
|
-
private askRoomRefit;
|
|
401
398
|
private renderSingle;
|
|
402
399
|
}
|
|
@@ -4,10 +4,9 @@ import { LitElement, html, nothing } from 'lit';
|
|
|
4
4
|
import { i18next } from '@operato/i18n';
|
|
5
5
|
import '@operato/layout';
|
|
6
6
|
import { openPopup } from '@operato/layout';
|
|
7
|
-
import { OxPrompt } from '@operato/popup';
|
|
8
7
|
import { customElement, property, state } from 'lit/decorators.js';
|
|
9
8
|
import { live } from 'lit/directives/live.js';
|
|
10
|
-
import { figurePartFaceOf, PART_LIMIT, parseFigureRecipe, resolveFigureRecipe, figurePartCountOf, figurePartIdFor, figureRecipeIdFor, figureSizeEditOf, figureSizeEditorOf, figureSectionMeasuresOf, figurePlaneAlignChanges, figureRowOf, figureFaceOverhangOf
|
|
9
|
+
import { figurePartFaceOf, PART_LIMIT, parseFigureRecipe, resolveFigureRecipe, figurePartCountOf, figurePartIdFor, figureRecipeIdFor, figureSizeEditOf, figureSizeEditorOf, figureSectionMeasuresOf, figurePlaneAlignChanges, figureRowOf, figureFaceOverhangOf } from '@hatiolab/figure-model';
|
|
11
10
|
import './figure-recipe-catalog.js';
|
|
12
11
|
import { captureFigureThumbnail } from './thumbnail.js';
|
|
13
12
|
import { drawableFigureParts } from './renderer.js';
|
|
@@ -28,7 +27,7 @@ import './figure-preview.js';
|
|
|
28
27
|
import './figure-settings.js';
|
|
29
28
|
import { DEFAULT_VIEW } from '@things-factory/figure-viewer/dist-client/figure-view.js';
|
|
30
29
|
import './figure-capability-panel.js';
|
|
31
|
-
import { gizmoChanges, gizmoReachOf, sizeKeepOf } from './gizmo.js';
|
|
30
|
+
import { gizmoChanges, gizmoReachOf, sameMeasureAxesOf, sizeKeepOf } from './gizmo.js';
|
|
32
31
|
import { keptBySizeOf, partsAtFrame, SIZE_KEEPS_WORDS, startSizeDrag, throughPlaneOf } from './size-drag.js';
|
|
33
32
|
import { ModellerKeys } from './modeller-keys.js';
|
|
34
33
|
import { PaneFold } from './pane-fold.js';
|
|
@@ -48,11 +47,10 @@ const NO_MARKS = [];
|
|
|
48
47
|
import { apexOver, drawnOf } from './section-drawing.js';
|
|
49
48
|
import { END_FACE } from './section-controls.js';
|
|
50
49
|
import { nudgeOf, placementOf } from './stage-model.js';
|
|
51
|
-
import { occupancyFromParts, roomRefitRowsOf } from './occupancy.js';
|
|
52
50
|
import { gateFindingStyles, renderGateFinding } from './gate-findings.js';
|
|
53
|
-
import { editRefusalOf,
|
|
51
|
+
import { editRefusalOf, partNameRefusalOf, sizeRangeFitsOf, partRefusalOf, placedRefusalOf, recipeRefusalOf, removalNoticeOf } from './refusals.js';
|
|
54
52
|
import { HelpToggles } from './help.js';
|
|
55
|
-
import { endPlaceKeyOf,
|
|
53
|
+
import { endPlaceKeyOf, followOf, sizeFollowersOf, measureNow, partSummaryOf, instanceRangeOf, instanceSizeToDeclare, proportionToFollow, rangeWith, turnsAboutAJoint, valueNow } from './links.js';
|
|
56
54
|
import { jointFanOf } from './joint-fan.js';
|
|
57
55
|
import { derivedOf } from './revision.js';
|
|
58
56
|
import { jointDragOf } from './joint-drag.js';
|
|
@@ -137,14 +135,14 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
137
135
|
selected: () => this.selected,
|
|
138
136
|
act: action => this.keyAction(action)
|
|
139
137
|
});
|
|
138
|
+
/** The part Ctrl+C took: pasted as the kernel's duplicate. Not kept past the editor, nor written anywhere. */
|
|
139
|
+
this.copied = '';
|
|
140
140
|
this.paneFold = new PaneFold(this, () => this.panes);
|
|
141
141
|
this.refusals = new RefusalTeller({
|
|
142
142
|
session: () => this.session,
|
|
143
143
|
error: () => this.error,
|
|
144
144
|
notice: () => this.notice,
|
|
145
|
-
|
|
146
|
-
refuse: message => (this.error = message),
|
|
147
|
-
command: action => this.linkCommand(action)
|
|
145
|
+
refuse: message => (this.error = message)
|
|
148
146
|
});
|
|
149
147
|
this.panelMemory = new PanelMemory(() => this.renderRoot);
|
|
150
148
|
/** The preview is made when first asked for, and kept (hidden) after that; it is taken down with the editor. */
|
|
@@ -231,6 +229,13 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
231
229
|
</button>`)}
|
|
232
230
|
</span>`;
|
|
233
231
|
}
|
|
232
|
+
/** A copied part still in the figure is duplicated -- a part of its own, named by figure-model; one gone, nothing. */
|
|
233
|
+
pasteCopied() {
|
|
234
|
+
const asset = this.session?.revisionNow()?.asset;
|
|
235
|
+
if (!this.copied || !asset?.document.model.nodes.some(n => n.op === 'place@1' && n.id === this.copied))
|
|
236
|
+
return;
|
|
237
|
+
this.duplicatePart(this.copied);
|
|
238
|
+
}
|
|
234
239
|
keyAction(action) {
|
|
235
240
|
const view = this.session.read();
|
|
236
241
|
const canvas = this.renderRoot.querySelector('figure-canvas');
|
|
@@ -242,6 +247,12 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
242
247
|
view.canRedo && this.run(() => this.session.redo());
|
|
243
248
|
else if (action === 'duplicate')
|
|
244
249
|
this.duplicatePart(this.selected);
|
|
250
|
+
else if (action === 'copy')
|
|
251
|
+
this.copied = this.selected;
|
|
252
|
+
else if (action === 'paste')
|
|
253
|
+
this.pasteCopied();
|
|
254
|
+
else if (action === 'hold')
|
|
255
|
+
return;
|
|
245
256
|
else if (action === 'remove')
|
|
246
257
|
this.removePart(this.selected);
|
|
247
258
|
else
|
|
@@ -295,6 +306,15 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
295
306
|
this.selected = next;
|
|
296
307
|
});
|
|
297
308
|
}
|
|
309
|
+
/** While the size tool is on a round part: which of its handles change the one diameter (ADR-0140 4.1). */
|
|
310
|
+
renderSameMeasure(unavailable) {
|
|
311
|
+
if (unavailable || this.gizmoMode !== 'scale' || !this.selected)
|
|
312
|
+
return nothing;
|
|
313
|
+
const axes = sameMeasureAxesOf(this.reachOf(this.selected));
|
|
314
|
+
return axes
|
|
315
|
+
? html `<p same-measure role="status">${i18next.t('figure.text.gizmo-same-measure', { axes: axes.map(a => a.toUpperCase()).join(' · ') })}</p>`
|
|
316
|
+
: nothing;
|
|
317
|
+
}
|
|
298
318
|
/** The scene toggles: the mode already shown takes the gizmo away. */
|
|
299
319
|
useGizmo(mode) {
|
|
300
320
|
/*
|
|
@@ -380,6 +400,10 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
380
400
|
this.live = action ? { ...rest, action } : rest;
|
|
381
401
|
}
|
|
382
402
|
changed() {
|
|
403
|
+
/* What the step did to the size ends the author typed, said with whatever else it said. */
|
|
404
|
+
const fits = this.session?.takeSizeRangeFits() ?? [];
|
|
405
|
+
if (fits.length)
|
|
406
|
+
this.notice = [this.notice, sizeRangeFitsOf(fits, (key, params) => i18next.t(key, params))].filter(Boolean).join(' ');
|
|
383
407
|
this.requestUpdate();
|
|
384
408
|
this.dispatchEvent(new CustomEvent('changed', { bubbles: true, composed: true }));
|
|
385
409
|
}
|
|
@@ -482,10 +506,9 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
482
506
|
movePart(id, to) {
|
|
483
507
|
this.run(() => this.session.edit({ kind: 'move-part', target: id, to }));
|
|
484
508
|
}
|
|
485
|
-
removePart(id
|
|
486
|
-
this.retry = undefined;
|
|
509
|
+
removePart(id) {
|
|
487
510
|
try {
|
|
488
|
-
const view = this.session.edit({ kind: 'remove-part', target: id
|
|
511
|
+
const view = this.session.edit({ kind: 'remove-part', target: id });
|
|
489
512
|
this.error = '';
|
|
490
513
|
/* What else the removal let go of, named (chief architect, 2026-09-27); undo brings it back. */
|
|
491
514
|
this.notice = removalNoticeOf(view.removal, (key, params) => i18next.t(key, params)) ?? '';
|
|
@@ -494,19 +517,8 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
494
517
|
this.changed();
|
|
495
518
|
}
|
|
496
519
|
catch (error) {
|
|
497
|
-
if (this.refusals.askClipLoss(error, () => this.removePart(id
|
|
520
|
+
if (this.refusals.askClipLoss(error, () => this.removePart(id)))
|
|
498
521
|
return;
|
|
499
|
-
/*
|
|
500
|
-
A part in the declared space goes, and the space is fitted to the parts left, in the same click: every space
|
|
501
|
-
this product declares is fitted to the parts (「부품에 맞추기」, the AI's fit-room -- no padding), so fitting it
|
|
502
|
-
again keeps what it says, and there is nothing to ask (user, 2026-09-27: 「이 확인이 꼭 필요한가 ?」).
|
|
503
|
-
*/
|
|
504
|
-
if (!refitRoom && inDeclaredRoom(error)) {
|
|
505
|
-
this.removePart(id, true);
|
|
506
|
-
if (!this.error)
|
|
507
|
-
this.notice = [this.notice, i18next.t('figure.text.removal-space-refitted')].filter(Boolean).join(' ');
|
|
508
|
-
return;
|
|
509
|
-
}
|
|
510
522
|
this.error = partRefusalOf(error, id, (key, params) => i18next.t(key, params)) ?? this.said(error);
|
|
511
523
|
}
|
|
512
524
|
}
|
|
@@ -962,10 +974,8 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
962
974
|
linkCommand(action, from = 'panel') {
|
|
963
975
|
/* Committed or refused, a command in hand is let go of: the figure as it stands is drawn again. */
|
|
964
976
|
this.holdAction(null);
|
|
965
|
-
this.retry = undefined;
|
|
966
977
|
this.notice = '';
|
|
967
978
|
const hadSize = this.hasSize();
|
|
968
|
-
const hadSpace = !!this.shown()?.asset.occupancy;
|
|
969
979
|
try {
|
|
970
980
|
this.session.command(action);
|
|
971
981
|
this.error = '';
|
|
@@ -973,9 +983,7 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
973
983
|
/* What the command did besides what was asked, in one line: the figure's size declared to follow (ADR ruling Q1). */
|
|
974
984
|
if (action.kind === 'follow-size' && !hadSize) {
|
|
975
985
|
const size = this.designValues();
|
|
976
|
-
|
|
977
|
-
const spaceToo = !hadSpace && !!this.shown()?.asset.occupancy;
|
|
978
|
-
this.notice = i18next.t(spaceToo ? 'figure.text.size-and-space-declared-to-follow' : 'figure.text.size-declared-to-follow', {
|
|
986
|
+
this.notice = i18next.t('figure.text.size-declared-to-follow', {
|
|
979
987
|
x: Math.round(size['size.x'] ?? 0).toLocaleString(),
|
|
980
988
|
y: Math.round(size['size.y'] ?? 0).toLocaleString(),
|
|
981
989
|
z: Math.round(size['size.z'] ?? 0).toLocaleString()
|
|
@@ -995,12 +1003,6 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
995
1003
|
this.error = '';
|
|
996
1004
|
return false;
|
|
997
1005
|
}
|
|
998
|
-
const narrowing = narrowingRetryOf(action, error, (key, params) => i18next.t(key, params));
|
|
999
|
-
if (narrowing) {
|
|
1000
|
-
this.error = narrowing.message;
|
|
1001
|
-
this.retry = { label: narrowing.label, action: narrowing.retry };
|
|
1002
|
-
return false;
|
|
1003
|
-
}
|
|
1004
1006
|
this.error =
|
|
1005
1007
|
code === 'DIMENSION_CYCLE'
|
|
1006
1008
|
? i18next.t('figure.text.follow-would-loop')
|
|
@@ -1096,12 +1098,12 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1096
1098
|
label.addEventListener('pointerup', up);
|
|
1097
1099
|
label.addEventListener('pointercancel', up);
|
|
1098
1100
|
}
|
|
1099
|
-
/** The sizes a board may give the figure, and the
|
|
1101
|
+
/** The sizes a board may give the figure: the ends the author typed, and the rest as far as its parts allow. */
|
|
1100
1102
|
renderInstanceRange(shown, unavailable) {
|
|
1101
1103
|
if (!shown)
|
|
1102
1104
|
return nothing;
|
|
1103
1105
|
const asset = shown.asset;
|
|
1104
|
-
/*
|
|
1106
|
+
/* Computed once per version: figure-model evaluates the figure at its edges. */
|
|
1105
1107
|
const measured = derivedOf(shown, 'instance-range', () => {
|
|
1106
1108
|
let of;
|
|
1107
1109
|
let toDeclare;
|
|
@@ -1112,7 +1114,7 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1112
1114
|
of = undefined;
|
|
1113
1115
|
}
|
|
1114
1116
|
try {
|
|
1115
|
-
toDeclare = of ? undefined :
|
|
1117
|
+
toDeclare = of ? undefined : instanceSizeToDeclare(asset);
|
|
1116
1118
|
}
|
|
1117
1119
|
catch {
|
|
1118
1120
|
toDeclare = undefined;
|
|
@@ -1133,14 +1135,18 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1133
1135
|
return html `<section board-size>
|
|
1134
1136
|
${title('figure.text.board-size-fixed-explained')}
|
|
1135
1137
|
<p fixed-size>${i18next.t('figure.text.board-size-fixed', { x: mm(toDeclare.at.x), y: mm(toDeclare.at.y), z: mm(toDeclare.at.z) })}</p>
|
|
1136
|
-
<button act ?disabled=${unavailable} @click=${() => this.linkCommand({ kind: 'declare-instance-size',
|
|
1138
|
+
<button act ?disabled=${unavailable} @click=${() => this.linkCommand({ kind: 'declare-instance-size', at: toDeclare.at })}>
|
|
1137
1139
|
<md-icon>open_in_full</md-icon>${i18next.t('figure.button.declare-instance-size')}
|
|
1138
1140
|
</button>
|
|
1139
1141
|
</section>`;
|
|
1140
1142
|
if (!range)
|
|
1141
1143
|
return nothing;
|
|
1142
|
-
const differs = rangeDiffers(range.declared, range.measured);
|
|
1143
1144
|
const followers = sizeFollowersOf(asset);
|
|
1145
|
+
/*
|
|
1146
|
+
An end the author typed shows its number; an open end is empty, with what the parts allow written faintly in it
|
|
1147
|
+
(ADR-0087 decision 6). Typing narrows that end, emptying the field opens it again.
|
|
1148
|
+
*/
|
|
1149
|
+
const shownEnd = (r, bound) => (bound === 'max' && !Number.isFinite(r.max) ? '∞' : mm(r[bound]));
|
|
1144
1150
|
return html `<section board-size>
|
|
1145
1151
|
${title('figure.text.board-size-range-explained')}
|
|
1146
1152
|
${['x', 'y', 'z'].map(axis => html `<div inline-field>
|
|
@@ -1150,12 +1156,12 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1150
1156
|
aria-label=${`size.${axis}.${bound}`}
|
|
1151
1157
|
type="number"
|
|
1152
1158
|
step="any"
|
|
1153
|
-
.value=${live(String(Math.round(range
|
|
1159
|
+
.value=${live(range[axis].narrowed[bound] ? String(Math.round(range[axis][bound])) : '')}
|
|
1160
|
+
placeholder=${shownEnd(range[axis], bound)}
|
|
1154
1161
|
?disabled=${unavailable}
|
|
1155
1162
|
@change=${(e) => {
|
|
1156
|
-
const
|
|
1157
|
-
|
|
1158
|
-
this.linkCommand({ kind: 'declare-instance-size', range: next, replace: true });
|
|
1163
|
+
const typed = e.target.valueAsNumber;
|
|
1164
|
+
this.linkCommand({ kind: 'declare-instance-size', range: rangeWith(range, axis, bound, typed), replace: true });
|
|
1159
1165
|
}}
|
|
1160
1166
|
/>`)}
|
|
1161
1167
|
</div>
|
|
@@ -1167,19 +1173,6 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1167
1173
|
})
|
|
1168
1174
|
: i18next.t('figure.text.board-size-no-followers')}
|
|
1169
1175
|
</p>
|
|
1170
|
-
${differs
|
|
1171
|
-
? html `<p hint>
|
|
1172
|
-
${i18next.t('figure.text.instance-range-can-widen', {
|
|
1173
|
-
ranges: ['x', 'y', 'z']
|
|
1174
|
-
.filter(axis => rangeDiffers(range.declared, { ...range.declared, [axis]: range.measured[axis] }))
|
|
1175
|
-
.map(axis => `${axis.toUpperCase()} ${mm(range.measured[axis].min)} – ${mm(range.measured[axis].max)}`)
|
|
1176
|
-
.join(' · ')
|
|
1177
|
-
})}
|
|
1178
|
-
</p>
|
|
1179
|
-
<button act ?disabled=${unavailable} @click=${() => this.linkCommand({ kind: 'declare-instance-size', range: range.measured, replace: true })}>
|
|
1180
|
-
<md-icon>straighten</md-icon>${i18next.t('figure.button.remeasure-range')}
|
|
1181
|
-
</button>`
|
|
1182
|
-
: nothing}
|
|
1183
1176
|
${this.renderSizingRule(asset, unavailable)}
|
|
1184
1177
|
</section>`;
|
|
1185
1178
|
}
|
|
@@ -1334,7 +1327,7 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1334
1327
|
const form = this.publishing?.part === part
|
|
1335
1328
|
? this.publishing
|
|
1336
1329
|
: listed && input
|
|
1337
|
-
? { part, label: listed.label, min: input.min, max: input.max }
|
|
1330
|
+
? /* A published input has both ends (figure-model checkPublicInput). */ { part, label: listed.label, min: input.min, max: input.max }
|
|
1338
1331
|
: undefined;
|
|
1339
1332
|
const open = !!form;
|
|
1340
1333
|
const edit = (key) => (e) => {
|
|
@@ -1431,18 +1424,13 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1431
1424
|
return i18next.t('figure.text.release-state-released', { version: String(version) });
|
|
1432
1425
|
return version > 0 ? i18next.t('figure.text.release-state-draft-over', { version: String(version) }) : i18next.t('figure.text.release-state-draft-new');
|
|
1433
1426
|
}
|
|
1434
|
-
/** The findings that block release, under the bar beside
|
|
1435
|
-
renderReleaseBlockers(blockers, asset
|
|
1427
|
+
/** The findings that block release, under the bar beside 「발행」. */
|
|
1428
|
+
renderReleaseBlockers(blockers, asset) {
|
|
1436
1429
|
const parts = (asset?.appearance ?? []).map(look => look.target);
|
|
1437
|
-
const way = (code) => (code === 'occupancy-undeclared' || code === 'part-outside-occupancy') && asset && parts.length
|
|
1438
|
-
? html `<button act ?disabled=${unavailable} @click=${() => this.linkCommand(occupancyFromParts(asset))}>
|
|
1439
|
-
<md-icon>fit_screen</md-icon>${i18next.t('figure.button.fit-base-to-parts')}
|
|
1440
|
-
</button>`
|
|
1441
|
-
: nothing;
|
|
1442
1430
|
return html `<div release-blockers role="dialog" aria-label=${i18next.t('figure.text.must-fix-to-release')} @keydown=${(e) => e.key === 'Escape' && (this.blockersOpen = false)}>
|
|
1443
1431
|
<ul gate>
|
|
1444
1432
|
<li head>${i18next.t('figure.text.must-fix-to-release')}</li>
|
|
1445
|
-
${blockers.map(finding => renderGateFinding(finding, true,
|
|
1433
|
+
${blockers.map(finding => renderGateFinding(finding, true, nothing, parts))}
|
|
1446
1434
|
</ul>
|
|
1447
1435
|
</div>`;
|
|
1448
1436
|
}
|
|
@@ -1777,20 +1765,9 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1777
1765
|
/* A repeat's own row values (its pitch) have their own section, not the shape's. */
|
|
1778
1766
|
const own = design.filter(i => this.selected && i.id.startsWith(this.selected + '.') && !i.id.startsWith(this.selected + '.repeat.'));
|
|
1779
1767
|
const common = design.filter(i => !view.geometry.some(g => i.id.startsWith(ownerOf(g.id) + '.')));
|
|
1780
|
-
/*
|
|
1781
|
-
A part's field only the declared room still reads -- its old height position once the floor holds it -- moves
|
|
1782
|
-
nothing; it is shown apart as what it is, not as a position (ADR-0097, designer 2026-09-27).
|
|
1783
|
-
*/
|
|
1784
1768
|
const shown = this.shown();
|
|
1785
1769
|
const asset = shown?.asset;
|
|
1786
|
-
|
|
1787
|
-
try {
|
|
1788
|
-
roomOnly = this.selected && asset ? figureRoomOnlyInputsOf(asset, this.selected) : [];
|
|
1789
|
-
}
|
|
1790
|
-
catch {
|
|
1791
|
-
roomOnly = [];
|
|
1792
|
-
}
|
|
1793
|
-
const partFields = own.filter(i => !roomOnly.includes(i.id));
|
|
1770
|
+
const partFields = own;
|
|
1794
1771
|
/* The numbers a rule gives the axes that have no field of their own -- shown read-only, never as a blank. */
|
|
1795
1772
|
let held;
|
|
1796
1773
|
try {
|
|
@@ -1808,7 +1785,6 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1808
1785
|
catch {
|
|
1809
1786
|
held = undefined;
|
|
1810
1787
|
}
|
|
1811
|
-
const roomFields = own.filter(i => roomOnly.includes(i.id));
|
|
1812
1788
|
/*
|
|
1813
1789
|
Only what is under way is said. Saved or not is the save button's: filled and pressable when there is something
|
|
1814
1790
|
to save, dimmed when not -- the saved and unsaved words beside it said the same thing again in the primary
|
|
@@ -1859,7 +1835,7 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1859
1835
|
</button>
|
|
1860
1836
|
<button slot="commit" save ?disabled=${unavailable || (!view.dirty && !!view.id)} @click=${this.save}><md-icon>save</md-icon>${i18next.t('figure.button.save')}</button>
|
|
1861
1837
|
</ox-canvas-bar>
|
|
1862
|
-
${this.blockersOpen && blockers.length ? this.renderReleaseBlockers(blockers, asset
|
|
1838
|
+
${this.blockersOpen && blockers.length ? this.renderReleaseBlockers(blockers, asset) : nothing}
|
|
1863
1839
|
${this.catalogOpen
|
|
1864
1840
|
? html `<figure-recipe-catalog
|
|
1865
1841
|
.templates=${this.templates}
|
|
@@ -1938,7 +1914,7 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1938
1914
|
@render-error=${(e) => {
|
|
1939
1915
|
this.error = e.detail.message;
|
|
1940
1916
|
}}
|
|
1941
|
-
></figure-canvas
|
|
1917
|
+
></figure-canvas>${this.renderSameMeasure(unavailable)}</div>
|
|
1942
1918
|
<aside slot="end">
|
|
1943
1919
|
<div tabs>
|
|
1944
1920
|
${this.renderTab('properties', 'tune', i18next.t('figure.label.properties'))}
|
|
@@ -1967,7 +1943,6 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1967
1943
|
${this.renderRepeatNote(view.geometry)}
|
|
1968
1944
|
</section>
|
|
1969
1945
|
${this.renderPartGroups(shown, view.geometry, partFields, unavailable, held)}
|
|
1970
|
-
${roomFields.length ? this.renderRoomOnly(roomFields, unavailable) : nothing}
|
|
1971
1946
|
<figure-material-panel
|
|
1972
1947
|
.body=${{ ...part, id: this.selected }}
|
|
1973
1948
|
.palette=${view.palette ?? {}}
|
|
@@ -2109,58 +2084,6 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
2109
2084
|
return html `<p hint held-axes>${i18next.t('figure.text.held-by-fastening')}</p>`;
|
|
2110
2085
|
return html `<p hint held-axes>${i18next.t('figure.text.held-worked-out', { group: held.map(g => i18next.t('figure.label.group-' + g.name)).join(' · ') })}</p>`;
|
|
2111
2086
|
}
|
|
2112
|
-
/**
|
|
2113
|
-
* A part's fields only the declared room still reads (ADR-0097, designer 2026-09-27): shown as what they are and not
|
|
2114
|
-
* typed into -- they move nothing. Declaring the room again is offered; nothing is deleted or widened on the way.
|
|
2115
|
-
*/
|
|
2116
|
-
renderRoomOnly(fields, unavailable) {
|
|
2117
|
-
return html `<section room-only>
|
|
2118
|
-
<h3>${i18next.t('figure.label.room-only-inputs')}</h3>
|
|
2119
|
-
<p hint>${i18next.t('figure.text.room-only-inputs')}</p>
|
|
2120
|
-
<div form>
|
|
2121
|
-
${fields.map(f => html `<span>${fieldWordsOf(f.id, key => i18next.t(key))} (${f.unit})</span><input aria-label=${f.id} type="number" .value=${String(f.value)} disabled />`)}
|
|
2122
|
-
</div>
|
|
2123
|
-
<button act room-refit ?disabled=${unavailable} @click=${() => this.askRoomRefit()}><md-icon>fit_screen</md-icon>${i18next.t('figure.button.room-refit')}</button>
|
|
2124
|
-
</section>`;
|
|
2125
|
-
}
|
|
2126
|
-
/** The room as declared and as proposed, and what lies outside each as measured, before the author confirms. */
|
|
2127
|
-
async askRoomRefit() {
|
|
2128
|
-
let refit;
|
|
2129
|
-
try {
|
|
2130
|
-
refit = figureRoomRefitOf(this.shown().asset);
|
|
2131
|
-
}
|
|
2132
|
-
catch (error) {
|
|
2133
|
-
this.error = this.said(error);
|
|
2134
|
-
return;
|
|
2135
|
-
}
|
|
2136
|
-
if (!refit)
|
|
2137
|
-
return;
|
|
2138
|
-
const rows = roomRefitRowsOf(refit);
|
|
2139
|
-
const outside = (list) => list.length
|
|
2140
|
-
? list.map(o => i18next.t('figure.text.room-part-outside', { part: partLabelOf(o.part), axis: o.axis.toUpperCase(), mm: String(o.mm) })).join(' · ')
|
|
2141
|
-
: i18next.t('figure.text.room-nothing-outside');
|
|
2142
|
-
const agreed = await OxPrompt.open({
|
|
2143
|
-
type: 'question',
|
|
2144
|
-
title: i18next.t('figure.label.room-refit'),
|
|
2145
|
-
template: html `<table room-refit>
|
|
2146
|
-
<tr><th></th><th>${i18next.t('figure.label.room-declared')}</th><th>${i18next.t('figure.label.room-proposed')}</th></tr>
|
|
2147
|
-
${rows.map(r => html `<tr>
|
|
2148
|
-
<td>${i18next.t(`figure.label.room-at-${r.at}`)} ${r.axis.toUpperCase()}</td>
|
|
2149
|
-
<td>${r.declared}</td>
|
|
2150
|
-
<td>${r.same ? i18next.t('figure.text.room-same') : r.proposed}</td>
|
|
2151
|
-
</tr>`)}
|
|
2152
|
-
</table>
|
|
2153
|
-
<p>${i18next.t('figure.label.room-declared')}: ${outside(refit.outside.declared)}</p>
|
|
2154
|
-
<p>${i18next.t('figure.label.room-proposed')}: ${outside(refit.outside.proposed)}</p>
|
|
2155
|
-
${refit.drops.length
|
|
2156
|
-
? html `<p>${i18next.t('figure.text.room-drops', { fields: [...new Set(refit.drops.map(id => `${partLabelOf(id.split('.')[0])} ${fieldWordsOf(id, key => i18next.t(key))}`))].join(' · ') })}</p>`
|
|
2157
|
-
: nothing}`,
|
|
2158
|
-
confirmButton: { text: i18next.t('figure.button.room-refit-confirm') },
|
|
2159
|
-
cancelButton: { text: i18next.t('figure.button.keep-editing') }
|
|
2160
|
-
});
|
|
2161
|
-
if (agreed)
|
|
2162
|
-
this.linkCommand(refit.step);
|
|
2163
|
-
}
|
|
2164
2087
|
renderSingle(name, field, unavailable) {
|
|
2165
2088
|
const words = name.split('.').map(part => i18next.t('figure.label.field-' + part, { defaultValue: part }));
|
|
2166
2089
|
return html `<span ?scrub=${!unavailable} @pointerdown=${(e) => this.scrub(field, e, unavailable)}>${words.join(' · ')} (${field.unit})</span
|
|
@@ -2243,10 +2166,6 @@ __decorate([
|
|
|
2243
2166
|
state(),
|
|
2244
2167
|
__metadata("design:type", Object)
|
|
2245
2168
|
], FigureEditor.prototype, "faceMapping", void 0);
|
|
2246
|
-
__decorate([
|
|
2247
|
-
state(),
|
|
2248
|
-
__metadata("design:type", Object)
|
|
2249
|
-
], FigureEditor.prototype, "retry", void 0);
|
|
2250
2169
|
__decorate([
|
|
2251
2170
|
state(),
|
|
2252
2171
|
__metadata("design:type", String)
|