@things-factory/figure-ui 10.1.181 → 10.1.185
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 +28 -9
- package/client/modeller/editor.ts +105 -12
- package/client/modeller/fasten-form.ts +207 -0
- package/client/modeller/fasten-geometry.ts +5 -1
- package/client/modeller/figure-facts.ts +4 -35
- package/client/modeller/figure-fasten-dialog.ts +457 -0
- package/client/modeller/figure-fasten-drawing.ts +32 -11
- package/client/modeller/figure-links.ts +56 -300
- package/client/modeller/figure-properties.ts +23 -70
- package/client/modeller/modeller-keys.ts +4 -4
- package/dist-client/modeller/clips.d.ts +2 -2
- package/dist-client/modeller/editor-styles.js +28 -9
- package/dist-client/modeller/editor-styles.js.map +1 -1
- package/dist-client/modeller/editor.d.ts +23 -2
- package/dist-client/modeller/editor.js +121 -12
- package/dist-client/modeller/editor.js.map +1 -1
- package/dist-client/modeller/fasten-form.d.ts +69 -0
- package/dist-client/modeller/fasten-form.js +131 -0
- package/dist-client/modeller/fasten-form.js.map +1 -0
- package/dist-client/modeller/fasten-geometry.d.ts +3 -1
- package/dist-client/modeller/fasten-geometry.js +6 -1
- package/dist-client/modeller/fasten-geometry.js.map +1 -1
- package/dist-client/modeller/figure-facts.d.ts +2 -13
- package/dist-client/modeller/figure-facts.js +2 -29
- package/dist-client/modeller/figure-facts.js.map +1 -1
- package/dist-client/modeller/figure-fasten-dialog.d.ts +69 -0
- package/dist-client/modeller/figure-fasten-dialog.js +433 -0
- package/dist-client/modeller/figure-fasten-dialog.js.map +1 -0
- package/dist-client/modeller/figure-fasten-drawing.d.ts +6 -3
- package/dist-client/modeller/figure-fasten-drawing.js +36 -10
- package/dist-client/modeller/figure-fasten-drawing.js.map +1 -1
- package/dist-client/modeller/figure-links.d.ts +14 -38
- package/dist-client/modeller/figure-links.js +58 -301
- package/dist-client/modeller/figure-links.js.map +1 -1
- package/dist-client/modeller/figure-properties.d.ts +12 -12
- package/dist-client/modeller/figure-properties.js +23 -72
- package/dist-client/modeller/figure-properties.js.map +1 -1
- package/dist-client/modeller/modeller-keys.js +4 -4
- package/dist-client/modeller/modeller-keys.js.map +1 -1
- package/dist-client/route.d.ts +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/package.json +8 -8
- package/test/browser/links-page.js +4 -4
- package/test/browser/links.check.mjs +196 -46
- package/test/browser/modeller-page.js +23 -0
- package/test/browser/modeller.check.mjs +19 -20
- package/test/browser/namespace-page.js +2 -29
- package/test/browser/popup-stand-in.js +48 -0
- package/test/browser/product-page.check.mjs +60 -9
- package/test/browser/product-page.js +30 -33
- package/test/browser/property-section.check.mjs +3 -3
- package/test/browser/props.check.mjs +28 -7
- package/test/browser/screen-page.js +7 -2
- package/test/browser/screen.check.mjs +28 -23
- package/test/browser/section-page.js +13 -41
- package/test/browser/section.check.mjs +39 -30
- package/test/fasten-form.test.ts +77 -0
- package/test/fasten-geometry.test.ts +2 -0
- package/test/first-minute.test.ts +7 -4
- package/test/properties.test.ts +11 -19
- package/translations/en.json +5 -12
- package/translations/es.json +5 -12
- package/translations/ja.json +5 -12
- package/translations/ko.json +5 -12
- package/translations/ms.json +5 -12
- package/translations/zh.json +5 -12
- package/client/modeller/mounting-part.ts +0 -15
|
@@ -1,38 +1,35 @@
|
|
|
1
1
|
import '@material/web/icon/icon.js'
|
|
2
2
|
|
|
3
3
|
import { css, html, LitElement, nothing } from 'lit'
|
|
4
|
-
import { customElement, property
|
|
4
|
+
import { customElement, property } from 'lit/decorators.js'
|
|
5
5
|
import { live } from 'lit/directives/live.js'
|
|
6
6
|
|
|
7
7
|
import { i18next, localize } from '@operato/i18n'
|
|
8
|
-
import {
|
|
9
|
-
import type { FigureAuthoringAction, FigureFace } from '@hatiolab/figure-model'
|
|
8
|
+
import type { FigureAuthoringAction } from '@hatiolab/figure-model'
|
|
10
9
|
|
|
11
|
-
import { occupancyFromParts } from './occupancy.js'
|
|
12
10
|
import { tagsOf, figureFigureFacts, type Placement, type FigureFigureFacts } from './figure-facts.js'
|
|
13
|
-
import { offeredMountingOf } from './mounting-part.js'
|
|
14
11
|
import type { FigureRevision } from './revision.js'
|
|
15
12
|
import { figurePropertySections } from './property-sections.js'
|
|
16
13
|
import { SidePanelStyles } from '@things-factory/figure-viewer/dist-client/side-panel-styles.js'
|
|
17
14
|
import { ScrollbarStyles } from '@operato/styles'
|
|
18
15
|
|
|
19
16
|
const PLACEMENTS: Placement[] = ['floor', 'ceiling', 'center', 'wall']
|
|
20
|
-
const FACES = Object.keys(FIGURE_FACES) as FigureFace[]
|
|
21
17
|
|
|
22
18
|
/**
|
|
23
19
|
* 속성 — the modeller's figure properties.
|
|
24
20
|
*
|
|
25
|
-
* The tab opens with what the figure is: its name,
|
|
26
|
-
*
|
|
21
|
+
* The tab opens with what the figure is: its name, where it stands, where the catalogue files it.
|
|
22
|
+
*
|
|
23
|
+
* Nothing here is set up before work can go on (user 2026-10-18, after a walk without it). The room the figure
|
|
24
|
+
* takes up is declared where release asks for it, from the parts -- the bar's blockers and the verdict offer it;
|
|
25
|
+
* whether the figure meets its plane is said by the release check from the parts, with no face to name. A mounting
|
|
26
|
+
* face named already (by the assistant, or before) is shown with the way to take it off, and nothing more.
|
|
27
27
|
*
|
|
28
28
|
* Two kinds of change leave this tab, and it does neither itself:
|
|
29
|
-
* - `command` carries an authoring command (rename,
|
|
30
|
-
* through the session, so it is one step of the same undo history as every other edit, and is saved with it.
|
|
29
|
+
* - `command` carries an authoring command (rename, where it stands, taking the mounting face off). The page runs
|
|
30
|
+
* it through the session, so it is one step of the same undo history as every other edit, and is saved with it.
|
|
31
31
|
* - `catalog` carries a change to the row (category, tags). Those are not in the asset; the page writes them
|
|
32
32
|
* to the saved row at once, so they need a saved figure.
|
|
33
|
-
*
|
|
34
|
-
* Not here yet, because has no place for them: editing the declared room by hand (declares it from the
|
|
35
|
-
* parts, as graph expressions), and the detail level. Both wait on a ruling.
|
|
36
33
|
*/
|
|
37
34
|
@customElement('figure-properties')
|
|
38
35
|
export class FigureProperties extends localize(i18next)(LitElement) {
|
|
@@ -48,16 +45,12 @@ export class FigureProperties extends localize(i18next)(LitElement) {
|
|
|
48
45
|
gap: 6px;
|
|
49
46
|
align-items: center;
|
|
50
47
|
}
|
|
51
|
-
|
|
48
|
+
/* What is named, then the way to take it off at the line's end, as wide as its words. */
|
|
49
|
+
div[row] > span {
|
|
52
50
|
flex: 1;
|
|
53
51
|
}
|
|
54
|
-
|
|
55
|
-
div[row] > button[act] {
|
|
52
|
+
div[row] > button[quiet] {
|
|
56
53
|
flex: none;
|
|
57
|
-
width: auto;
|
|
58
|
-
}
|
|
59
|
-
div[row] > span {
|
|
60
|
-
flex: 1;
|
|
61
54
|
}
|
|
62
55
|
`]
|
|
63
56
|
|
|
@@ -69,10 +62,6 @@ export class FigureProperties extends localize(i18next)(LitElement) {
|
|
|
69
62
|
/** The categories the server allows (LV figure-server design G5): chosen from, and fixed once set. Null: free text. */
|
|
70
63
|
@property({ attribute: false }) figureCategories?: string[] | null
|
|
71
64
|
@property({ type: Array }) figureTags?: string[]
|
|
72
|
-
/** The part and face picked for the mounting face, before 「이 면으로」 is pressed. */
|
|
73
|
-
@state() private pickedPart?: string
|
|
74
|
-
@state() private pickedFace?: FigureFace
|
|
75
|
-
|
|
76
65
|
private facts(): FigureFigureFacts | undefined {
|
|
77
66
|
try {
|
|
78
67
|
return this.revision ? figureFigureFacts(this.revision) : undefined
|
|
@@ -121,11 +110,7 @@ export class FigureProperties extends localize(i18next)(LitElement) {
|
|
|
121
110
|
return html`<section>
|
|
122
111
|
<h3>${i18next.t('figure.label.group-figure')}</h3>
|
|
123
112
|
<slot name="type"></slot>
|
|
124
|
-
${this.renderName(facts)} ${this.renderPlacement(facts)}
|
|
125
|
-
</section>
|
|
126
|
-
<section>
|
|
127
|
-
<h3>${i18next.t('figure.label.group-standing')}</h3>
|
|
128
|
-
${this.renderSpace(facts)} ${this.renderMounting(facts)}
|
|
113
|
+
${this.renderName(facts)} ${this.renderPlacement(facts)} ${this.renderMounting(facts)}
|
|
129
114
|
</section>
|
|
130
115
|
<section>
|
|
131
116
|
<h3>${i18next.t('figure.label.group-catalog')}</h3>
|
|
@@ -182,50 +167,23 @@ export class FigureProperties extends localize(i18next)(LitElement) {
|
|
|
182
167
|
`
|
|
183
168
|
}
|
|
184
169
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
<div row>
|
|
190
|
-
<span>${i18next.t(facts.declared ? 'figure.text.declared-space-declared' : 'figure.text.declared-space-none')}</span>
|
|
191
|
-
<button act ?disabled=${!facts.parts.length} @click=${() => this.declare(facts.placement ?? 'floor')}>
|
|
192
|
-
<md-icon>fit_screen</md-icon>${i18next.t('figure.button.fit-base-to-parts')}
|
|
193
|
-
</button>
|
|
194
|
-
</div>
|
|
195
|
-
</div>
|
|
196
|
-
`
|
|
197
|
-
}
|
|
198
|
-
|
|
170
|
+
/**
|
|
171
|
+
* A mounting face named already: which, and the way to take it off. A centred figure has no plane, so the gate does
|
|
172
|
+
* not check it there; the line says so rather than show a promise that is not kept.
|
|
173
|
+
*/
|
|
199
174
|
private renderMounting(facts: FigureFigureFacts) {
|
|
200
175
|
const mounting = facts.mounting
|
|
201
|
-
|
|
202
|
-
const {
|
|
176
|
+
if (!mounting) return nothing
|
|
177
|
+
const said = `${mounting.part} · ${i18next.t('figure.label.face-' + mounting.face)}`
|
|
203
178
|
return html`
|
|
204
|
-
<div field>
|
|
179
|
+
<div field mounting-face>
|
|
205
180
|
<label>${i18next.t('figure.label.mounting-face')}</label>
|
|
206
181
|
<div row>
|
|
207
|
-
<
|
|
208
|
-
|
|
209
|
-
</select>
|
|
210
|
-
<select ?disabled=${!facts.declared} @change=${(e: Event) => (this.pickedFace = (e.target as HTMLSelectElement).value as FigureFace)}>
|
|
211
|
-
${FACES.map(one => html`<option value=${one} ?selected=${one === face}>${i18next.t('figure.label.face-' + one)}</option>`)}
|
|
212
|
-
</select>
|
|
213
|
-
</div>
|
|
214
|
-
<div row>
|
|
215
|
-
<button act ?disabled=${!facts.declared || !part} @click=${() => this.command({ kind: 'set-mounting-face', part: part!, face })}>
|
|
216
|
-
<md-icon>check</md-icon>${i18next.t('figure.button.set-mounting-face')}
|
|
217
|
-
</button>
|
|
218
|
-
<button quiet ?disabled=${!mounting} @click=${() => this.command({ kind: 'clear-mounting-face' })}>
|
|
182
|
+
<span>${facts.placement === 'center' ? `${said} · ${i18next.t('figure.text.mounting-face-unchecked-at-center')}` : said}</span>
|
|
183
|
+
<button quiet clear-mounting-face @click=${() => this.command({ kind: 'clear-mounting-face' })}>
|
|
219
184
|
<md-icon>close</md-icon>${i18next.t('figure.button.clear-mounting-face')}
|
|
220
185
|
</button>
|
|
221
186
|
</div>
|
|
222
|
-
<small>
|
|
223
|
-
${facts.declared
|
|
224
|
-
? mounting
|
|
225
|
-
? `${mounting.part} · ${i18next.t('figure.label.face-' + mounting.face)}`
|
|
226
|
-
: i18next.t('figure.label.mounting-face-none')
|
|
227
|
-
: i18next.t('figure.text.mounting-face-needs-space')}
|
|
228
|
-
</small>
|
|
229
187
|
</div>
|
|
230
188
|
`
|
|
231
189
|
}
|
|
@@ -285,11 +243,6 @@ export class FigureProperties extends localize(i18next)(LitElement) {
|
|
|
285
243
|
`
|
|
286
244
|
}
|
|
287
245
|
|
|
288
|
-
private declare(placement: Placement) {
|
|
289
|
-
if (!this.revision) return
|
|
290
|
-
this.command(occupancyFromParts(this.revision.asset, placement))
|
|
291
|
-
}
|
|
292
|
-
|
|
293
246
|
private command(action: FigureAuthoringAction) {
|
|
294
247
|
this.dispatchEvent(new CustomEvent('command', { detail: { action }, bubbles: true, composed: true }))
|
|
295
248
|
}
|
|
@@ -36,11 +36,11 @@ export class ModellerKeys implements ReactiveController {
|
|
|
36
36
|
const canvas = this.of.scene()
|
|
37
37
|
const path = e.composedPath()
|
|
38
38
|
/*
|
|
39
|
-
A key for something open over the work -- a group's 「⋯」 menu, the view or the history, a question -- is
|
|
40
|
-
thing's: heard here first, Esc deselected the part and was kept from the menu, which then could not be closed
|
|
41
|
-
the keyboard (2026-10-16).
|
|
39
|
+
A key for something open over the work -- a group's 「⋯」 menu, the view or the history, a question, a popup -- is
|
|
40
|
+
that thing's: heard here first, Esc deselected the part and was kept from the menu, which then could not be closed
|
|
41
|
+
by the keyboard (2026-10-16).
|
|
42
42
|
*/
|
|
43
|
-
if (path.some(node => node instanceof Element && (node.localName.startsWith('ox-popup') || node.getAttribute('role') === 'dialog'))) return
|
|
43
|
+
if (path.some(node => node instanceof Element && (node.localName.startsWith('ox-popup') || node.localName === 'ox-floating-overlay' || node.getAttribute('role') === 'dialog'))) return
|
|
44
44
|
const action = keyActionOf(
|
|
45
45
|
{
|
|
46
46
|
key: e.key,
|
|
@@ -64,6 +64,6 @@ export declare const trackEdit: (clip: Clip, track: ClipTrack, patch?: Partial<P
|
|
|
64
64
|
state: string;
|
|
65
65
|
clip: string;
|
|
66
66
|
keys: ClipKey[];
|
|
67
|
-
loop: "
|
|
68
|
-
interpolation: "
|
|
67
|
+
loop: "hold" | "wrap";
|
|
68
|
+
interpolation: "linear" | "step";
|
|
69
69
|
};
|
|
@@ -90,11 +90,27 @@ export const EditorStyles = css `
|
|
|
90
90
|
color: var(--md-sys-color-primary);
|
|
91
91
|
font-weight: 600;
|
|
92
92
|
}
|
|
93
|
-
|
|
93
|
+
/*
|
|
94
|
+
The bar's two commitments, one shape (user 2026-10-18: 「발행버튼도 저장버튼도 라운드 버튼으로 … 버튼 안에 적절한 아이콘」):
|
|
95
|
+
round, an icon inside, so neither is taken for the other. Save, done many times a day, is the filled one; release,
|
|
96
|
+
a promise to whoever draws the figure, is outlined.
|
|
97
|
+
*/
|
|
98
|
+
ox-canvas-bar button[save],
|
|
99
|
+
ox-canvas-bar button[publish] {
|
|
100
|
+
display: inline-flex;
|
|
101
|
+
align-items: center;
|
|
102
|
+
gap: 6px;
|
|
94
103
|
height: 32px;
|
|
95
|
-
padding: 0 14px;
|
|
96
|
-
border: none;
|
|
104
|
+
padding: 0 14px 0 10px;
|
|
97
105
|
border-radius: 999em;
|
|
106
|
+
font-weight: 600;
|
|
107
|
+
}
|
|
108
|
+
ox-canvas-bar button[save] md-icon,
|
|
109
|
+
ox-canvas-bar button[publish] md-icon {
|
|
110
|
+
--md-icon-size: 18px;
|
|
111
|
+
}
|
|
112
|
+
ox-canvas-bar button[save] {
|
|
113
|
+
border: 1px solid transparent;
|
|
98
114
|
color: var(--md-sys-color-on-primary);
|
|
99
115
|
background: var(--md-sys-color-primary);
|
|
100
116
|
}
|
|
@@ -359,9 +375,10 @@ export const EditorStyles = css `
|
|
|
359
375
|
align-items: center;
|
|
360
376
|
gap: 4px;
|
|
361
377
|
height: 32px;
|
|
362
|
-
padding: 0 10px;
|
|
378
|
+
padding: 0 14px 0 10px;
|
|
363
379
|
border: none;
|
|
364
|
-
|
|
380
|
+
/* Round as save and release beside it: one shape for the bar's end. */
|
|
381
|
+
border-radius: 999em;
|
|
365
382
|
color: var(--md-sys-color-on-error-container);
|
|
366
383
|
background: var(--md-sys-color-error-container);
|
|
367
384
|
}
|
|
@@ -382,12 +399,14 @@ export const EditorStyles = css `
|
|
|
382
399
|
box-shadow: var(--md-sys-elevation-2, 0 2px 8px #0003);
|
|
383
400
|
}
|
|
384
401
|
ox-canvas-bar button[publish] {
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
border: 1px solid var(--md-sys-color-outline-variant);
|
|
388
|
-
border-radius: 6px;
|
|
402
|
+
border: 1px solid var(--md-sys-color-outline);
|
|
403
|
+
color: var(--md-sys-color-primary);
|
|
389
404
|
background: var(--md-sys-color-surface);
|
|
390
405
|
}
|
|
406
|
+
ox-canvas-bar button[publish][aria-pressed='true'] {
|
|
407
|
+
background: var(--md-sys-color-secondary-container);
|
|
408
|
+
color: var(--md-sys-color-on-secondary-container);
|
|
409
|
+
}
|
|
391
410
|
button:disabled {
|
|
392
411
|
opacity: 0.45;
|
|
393
412
|
cursor: default;
|
|
@@ -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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiZ9B,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 ox-canvas-bar .status[dirty] {\n color: var(--md-sys-color-primary);\n font-weight: 600;\n }\n ox-canvas-bar button[save] {\n height: 32px;\n padding: 0 14px;\n border: none;\n border-radius: 999em;\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 span[badge] {\n position: absolute;\n top: 3px;\n right: 2px;\n padding: 0 5px;\n font-size: var(--ui-t-micro, 11px);\n font-weight: 700;\n line-height: 1.5;\n color: var(--md-sys-color-on-primary-container);\n background: var(--md-sys-color-primary-container);\n border-radius: 999em;\n pointer-events: none;\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).\n */\n :host {\n position: relative;\n }\n div[bar-overlay-panel] {\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 div[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 10px;\n border: none;\n border-radius: 6px;\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 height: 32px;\n padding: 0 8px;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n background: var(--md-sys-color-surface);\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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoa9B,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 ox-canvas-bar .status[dirty] {\n color: var(--md-sys-color-primary);\n font-weight: 600;\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 span[badge] {\n position: absolute;\n top: 3px;\n right: 2px;\n padding: 0 5px;\n font-size: var(--ui-t-micro, 11px);\n font-weight: 700;\n line-height: 1.5;\n color: var(--md-sys-color-on-primary-container);\n background: var(--md-sys-color-primary-container);\n border-radius: 999em;\n pointer-events: none;\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).\n */\n :host {\n position: relative;\n }\n div[bar-overlay-panel] {\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 div[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"]}
|
|
@@ -9,6 +9,7 @@ import './figure-material-panel.js';
|
|
|
9
9
|
import './figure-section-panel.js';
|
|
10
10
|
import './figure-section-editor.js';
|
|
11
11
|
import './figure-links.js';
|
|
12
|
+
import './figure-fasten-dialog.js';
|
|
12
13
|
import './figure-controls.js';
|
|
13
14
|
import './figure-clips.js';
|
|
14
15
|
import './figure-data-panel.js';
|
|
@@ -63,6 +64,17 @@ export declare class FigureEditor extends LitElement {
|
|
|
63
64
|
private tab;
|
|
64
65
|
/** The view settings or the release history, open over the work from the bar. */
|
|
65
66
|
private overlay?;
|
|
67
|
+
/**
|
|
68
|
+
* The fastening window open for the selected part (user 2026-10-17), in the standard popup (2026-10-18): chosen there,
|
|
69
|
+
* applied once. It closes with nothing written when the part, the tab or the middle changes under it.
|
|
70
|
+
*/
|
|
71
|
+
private fastening;
|
|
72
|
+
private fastenPopup?;
|
|
73
|
+
private fastenWindow?;
|
|
74
|
+
/** Why the window's 「적용」 was refused, said in it. */
|
|
75
|
+
private fastenError;
|
|
76
|
+
/** The face of the part a row of the window's list is on, lit on the canvas while the pointer or the arrows are there. */
|
|
77
|
+
private fastenHover;
|
|
66
78
|
/** 「더 보기」 open, as this person left it last (chief architect 2026-10-16: kept per person). */
|
|
67
79
|
private moreOpen;
|
|
68
80
|
private static storedMoreOpen;
|
|
@@ -287,13 +299,22 @@ export declare class FigureEditor extends LitElement {
|
|
|
287
299
|
private releaseWords;
|
|
288
300
|
/** The findings that block release, under the bar beside 「발행」, each with the way out the verdict tab offers for it. */
|
|
289
301
|
private renderReleaseBlockers;
|
|
290
|
-
/** To the release that exists: the history's release section, opened from the bar, not a second one here. */
|
|
291
302
|
private goRelease;
|
|
292
303
|
private overhangOf;
|
|
293
304
|
/** The selected part's motion at both ends of its range, made once per version and part. */
|
|
294
305
|
private motionEnds;
|
|
295
|
-
/** "Fasten it first", from the motion tab: to the properties tab, the fastening
|
|
306
|
+
/** "Fasten it first", from the motion tab or a row: to the properties tab, the fastening window open on this part. */
|
|
296
307
|
private goFasten;
|
|
308
|
+
private openFastening;
|
|
309
|
+
private closeFastening;
|
|
310
|
+
private fastenClosed;
|
|
311
|
+
/** What the window shows is the editor's: the figure on screen, the part, and what the preview and a refusal said. */
|
|
312
|
+
private tellFastening;
|
|
313
|
+
disconnectedCallback(): void;
|
|
314
|
+
/** 「적용」: the command, one step; refused, the window stays open and says why. */
|
|
315
|
+
private applyFastening;
|
|
316
|
+
/** A row of the window's list under the pointer or the arrows: the face of that part it would be fastened on, lit. */
|
|
317
|
+
private hoverFastening;
|
|
297
318
|
/** What the canvas draws of it, made once per preview: a new list every render would stand the board up each time. */
|
|
298
319
|
private previewShown;
|
|
299
320
|
/**
|
|
@@ -7,7 +7,7 @@ import { openPopup } from '@operato/layout';
|
|
|
7
7
|
import { OxPrompt } from '@operato/popup';
|
|
8
8
|
import { customElement, property, state } from 'lit/decorators.js';
|
|
9
9
|
import { live } from 'lit/directives/live.js';
|
|
10
|
-
import { PART_LIMIT, parseFigureRecipe, resolveFigureRecipe, figurePartCountOf, figurePartIdFor, figureRecipeIdFor, figureSizeEditChanges, figureScaleAnchorOf, figureSectionMeasuresOf, figurePlaneAlignChanges, figureRowOf, figureFaceOverhangOf, figureRoomOnlyInputsOf, figureRoomRefitOf } from '@hatiolab/figure-model';
|
|
10
|
+
import { figurePartFaceOf, PART_LIMIT, parseFigureRecipe, resolveFigureRecipe, figurePartCountOf, figurePartIdFor, figureRecipeIdFor, figureSizeEditChanges, figureScaleAnchorOf, figureSectionMeasuresOf, figurePlaneAlignChanges, figureRowOf, figureFaceOverhangOf, figureRoomOnlyInputsOf, figureRoomRefitOf } from '@hatiolab/figure-model';
|
|
11
11
|
import './figure-recipe-catalog.js';
|
|
12
12
|
import { captureFigureThumbnail } from './thumbnail.js';
|
|
13
13
|
import { drawableFigureParts } from './renderer.js';
|
|
@@ -16,10 +16,11 @@ import './figure-parts.js';
|
|
|
16
16
|
import './figure-material-panel.js';
|
|
17
17
|
import './figure-section-panel.js';
|
|
18
18
|
import './figure-section-editor.js';
|
|
19
|
-
import { rowWordsOf } from './command-preview.js';
|
|
19
|
+
import { faceHighlightOf, rowWordsOf } from './command-preview.js';
|
|
20
20
|
import { CommandPreview, NO_WORDS } from './command-preview-controller.js';
|
|
21
21
|
import { motionEndsOf } from './motion-ends.js';
|
|
22
22
|
import './figure-links.js';
|
|
23
|
+
import './figure-fasten-dialog.js';
|
|
23
24
|
import './figure-controls.js';
|
|
24
25
|
import './figure-clips.js';
|
|
25
26
|
import './figure-data-panel.js';
|
|
@@ -78,6 +79,15 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
78
79
|
this.selected = '';
|
|
79
80
|
this.error = '';
|
|
80
81
|
this.tab = 'properties';
|
|
82
|
+
/**
|
|
83
|
+
* The fastening window open for the selected part (user 2026-10-17), in the standard popup (2026-10-18): chosen there,
|
|
84
|
+
* applied once. It closes with nothing written when the part, the tab or the middle changes under it.
|
|
85
|
+
*/
|
|
86
|
+
this.fastening = false;
|
|
87
|
+
/** Why the window's 「적용」 was refused, said in it. */
|
|
88
|
+
this.fastenError = '';
|
|
89
|
+
/** The face of the part a row of the window's list is on, lit on the canvas while the pointer or the arrows are there. */
|
|
90
|
+
this.fastenHover = null;
|
|
81
91
|
/** 「더 보기」 open, as this person left it last (chief architect 2026-10-16: kept per person). */
|
|
82
92
|
this.moreOpen = FigureEditor_1.storedMoreOpen();
|
|
83
93
|
/** What the middle shows: the parts being edited, or the figure placed on a board at eight sizes. */
|
|
@@ -136,8 +146,16 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
136
146
|
this.lastAnchors = {};
|
|
137
147
|
/** Data tried in the data tab, given to the preview's figures as their `data`; null when none is (ADR-0051 §8). */
|
|
138
148
|
this.dataSample = null;
|
|
139
|
-
|
|
149
|
+
/*
|
|
150
|
+
The bar's one way to the releases (user 2026-10-18: the history icon and 「발행」 opened the same panel): the panel
|
|
151
|
+
of releases -- what to release now, the releases before it and going back to one -- opened at its release section,
|
|
152
|
+
closed again by a second press.
|
|
153
|
+
*/
|
|
140
154
|
this.goRelease = async () => {
|
|
155
|
+
if (this.overlay === 'history') {
|
|
156
|
+
this.overlay = undefined;
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
141
159
|
this.overlay = 'history';
|
|
142
160
|
await this.updateComplete;
|
|
143
161
|
await this.querySelector('figure-history-panel')?.showRelease?.();
|
|
@@ -242,6 +260,9 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
242
260
|
this.selected = '';
|
|
243
261
|
if (_changed.has('selected'))
|
|
244
262
|
this.gizmoMode = undefined;
|
|
263
|
+
/* The window is about the part chosen on the properties tab, being edited: it does not follow anywhere else. */
|
|
264
|
+
if (this.fastening && (_changed.has('selected') || !this.selected || this.tab !== 'properties' || this.middle !== 'edit'))
|
|
265
|
+
this.closeFastening();
|
|
245
266
|
/*
|
|
246
267
|
* The section editor is of one part: when the selection moves off it by any way -- another part picked, the scene's
|
|
247
268
|
* own Esc, the part removed, another figure opened -- it closes and lets go of the preview it held.
|
|
@@ -765,10 +786,11 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
765
786
|
return html `<figure-links
|
|
766
787
|
.revision=${shown}
|
|
767
788
|
.section=${section}
|
|
768
|
-
.active=${this.tab === tab}
|
|
789
|
+
.active=${this.tab === tab && !(section === 'shape' && this.fastening)}
|
|
769
790
|
.partId=${this.selected}
|
|
770
791
|
?locked=${unavailable}
|
|
771
792
|
@command=${(e) => this.linkCommand(e.detail.action)}
|
|
793
|
+
@fasten-open=${() => this.openFastening()}
|
|
772
794
|
.previewWords=${this.commandPreview.words()}
|
|
773
795
|
.detachWords=${this.commandPreview.detachWords(shown)}
|
|
774
796
|
.overhangWords=${section === 'shape' ? this.overhangOf(shown).words : NO_WORDS}
|
|
@@ -877,7 +899,7 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
877
899
|
<div overlay-body>${body}</div>
|
|
878
900
|
</div>`;
|
|
879
901
|
return html `${panel('view', 'figure.label.tab-environment', html `<figure-settings .view=${this.view} @update-view=${(e) => this.setView(e.detail.view)}></figure-settings>`)}
|
|
880
|
-
${panel('history', 'figure.
|
|
902
|
+
${panel('history', 'figure.button.release', html `<slot name="history"></slot>`)}`;
|
|
881
903
|
}
|
|
882
904
|
/**
|
|
883
905
|
* The face a size drag keeps for the selected part, figure-model's rule (`figureScaleAnchorOf`): the gizmo shows it
|
|
@@ -1429,12 +1451,76 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1429
1451
|
}
|
|
1430
1452
|
});
|
|
1431
1453
|
}
|
|
1432
|
-
/** "Fasten it first", from the motion tab: to the properties tab, the fastening
|
|
1454
|
+
/** "Fasten it first", from the motion tab or a row: to the properties tab, the fastening window open on this part. */
|
|
1433
1455
|
async goFasten() {
|
|
1434
1456
|
this.tab = 'properties';
|
|
1435
1457
|
await this.updateComplete;
|
|
1436
|
-
|
|
1437
|
-
|
|
1458
|
+
this.openFastening();
|
|
1459
|
+
}
|
|
1460
|
+
openFastening() {
|
|
1461
|
+
if (!this.selected || this.fastenPopup)
|
|
1462
|
+
return;
|
|
1463
|
+
this.fastenError = '';
|
|
1464
|
+
this.fastenHover = null;
|
|
1465
|
+
this.fastening = true;
|
|
1466
|
+
const content = document.createElement('figure-fasten-dialog');
|
|
1467
|
+
content.addEventListener('preview-command', e => this.commandPreview.ask(e.detail));
|
|
1468
|
+
content.addEventListener('fasten-hover', e => this.hoverFastening(e.detail));
|
|
1469
|
+
content.addEventListener('fasten-apply', e => this.applyFastening(e.detail.action));
|
|
1470
|
+
content.addEventListener('fasten-cancel', () => this.closeFastening());
|
|
1471
|
+
this.fastenWindow = content;
|
|
1472
|
+
this.tellFastening();
|
|
1473
|
+
const popup = openPopup(html `${content}`, { title: i18next.t('figure.label.fastening'), size: 'fit', backdrop: true, movable: true, closable: true, escapable: true });
|
|
1474
|
+
this.fastenPopup = popup;
|
|
1475
|
+
/* Closed by its own X or Esc as well: the editor lets go of it the same way. */
|
|
1476
|
+
popup.result.then(() => this.fastenPopup === popup && this.fastenClosed());
|
|
1477
|
+
}
|
|
1478
|
+
closeFastening() {
|
|
1479
|
+
const popup = this.fastenPopup;
|
|
1480
|
+
this.fastenClosed();
|
|
1481
|
+
popup?.close();
|
|
1482
|
+
}
|
|
1483
|
+
fastenClosed() {
|
|
1484
|
+
this.fastenPopup = undefined;
|
|
1485
|
+
this.fastenWindow = undefined;
|
|
1486
|
+
this.fastening = false;
|
|
1487
|
+
this.fastenError = '';
|
|
1488
|
+
this.fastenHover = null;
|
|
1489
|
+
}
|
|
1490
|
+
/** What the window shows is the editor's: the figure on screen, the part, and what the preview and a refusal said. */
|
|
1491
|
+
tellFastening() {
|
|
1492
|
+
const content = this.fastenWindow;
|
|
1493
|
+
const view = this.session?.read();
|
|
1494
|
+
if (!content || !view)
|
|
1495
|
+
return;
|
|
1496
|
+
content.revision = this.shown();
|
|
1497
|
+
content.partId = this.selected;
|
|
1498
|
+
content.locked = view.loading || !!view.error || view.saving || !view.context;
|
|
1499
|
+
content.previewWords = this.commandPreview.words();
|
|
1500
|
+
content.error = this.fastenError;
|
|
1501
|
+
}
|
|
1502
|
+
disconnectedCallback() {
|
|
1503
|
+
super.disconnectedCallback();
|
|
1504
|
+
this.closeFastening();
|
|
1505
|
+
}
|
|
1506
|
+
/** 「적용」: the command, one step; refused, the window stays open and says why. */
|
|
1507
|
+
applyFastening(action) {
|
|
1508
|
+
if (this.linkCommand(action))
|
|
1509
|
+
this.closeFastening();
|
|
1510
|
+
else
|
|
1511
|
+
this.fastenError = this.error;
|
|
1512
|
+
}
|
|
1513
|
+
/** A row of the window's list under the pointer or the arrows: the face of that part it would be fastened on, lit. */
|
|
1514
|
+
hoverFastening(on) {
|
|
1515
|
+
const shown = this.shown();
|
|
1516
|
+
let lit = null;
|
|
1517
|
+
try {
|
|
1518
|
+
lit = on?.face && shown ? faceHighlightOf(figurePartFaceOf(shown.asset, on.part, on.face, shown.inputs)) : null;
|
|
1519
|
+
}
|
|
1520
|
+
catch {
|
|
1521
|
+
lit = null;
|
|
1522
|
+
}
|
|
1523
|
+
this.fastenHover = lit;
|
|
1438
1524
|
}
|
|
1439
1525
|
/** What the canvas draws of it, made once per preview: a new list every render would stand the board up each time. */
|
|
1440
1526
|
previewShown() {
|
|
@@ -1442,7 +1528,9 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1442
1528
|
/* On the motion tab, with nothing being tried: where the selected part's motion takes it, at both ends. */
|
|
1443
1529
|
if (!p && this.tab === 'motion' && this.middle === 'edit')
|
|
1444
1530
|
return { ghost: this.motionEnds()?.ghost ?? [], highlight: null };
|
|
1445
|
-
|
|
1531
|
+
const drawn = this.commandPreview.drawnOf(p);
|
|
1532
|
+
/* A part in the fastening window's list under the pointer: its face lit in place of the one the command meets. */
|
|
1533
|
+
return this.fastenHover ? { ghost: drawn.ghost, highlight: this.fastenHover } : drawn;
|
|
1446
1534
|
}
|
|
1447
1535
|
/**
|
|
1448
1536
|
* What the author has in view, for the AI dock to hear with a request (ADR-0097 §5.6 item 5): the part selected and
|
|
@@ -1485,6 +1573,7 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1485
1573
|
}
|
|
1486
1574
|
updated(changed) {
|
|
1487
1575
|
super.updated?.(changed);
|
|
1576
|
+
this.tellFastening();
|
|
1488
1577
|
/* Nothing blocks release any more: the list under the bar closes, and opens fresh when something does again. */
|
|
1489
1578
|
if (changed.has('releaseBlockers') && !this.releaseBlockers.length && this.blockersOpen)
|
|
1490
1579
|
this.blockersOpen = false;
|
|
@@ -1697,14 +1786,22 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1697
1786
|
</button>`)}
|
|
1698
1787
|
<span slot="status" class="status" role="status" ?dirty=${status === 'figure.text.unsaved-changes'}>${i18next.t(status)}</span>
|
|
1699
1788
|
<span slot="status" release-state>${this.releaseWords(view)}</span>
|
|
1700
|
-
${this.renderOverlayButton('history', 'history', 'figure.label.history', 'commit')}
|
|
1701
1789
|
${blockers.length
|
|
1702
1790
|
? html `<button slot="commit" release-blocked aria-expanded=${String(this.blockersOpen)} @click=${() => (this.blockersOpen = !this.blockersOpen)}>
|
|
1703
1791
|
<md-icon>error</md-icon>${i18next.t('figure.button.release-blocked', { count: blockers.length })}
|
|
1704
1792
|
</button>`
|
|
1705
1793
|
: nothing}
|
|
1706
|
-
<button
|
|
1707
|
-
|
|
1794
|
+
<button
|
|
1795
|
+
slot="commit"
|
|
1796
|
+
publish
|
|
1797
|
+
aria-pressed=${String(this.overlay === 'history')}
|
|
1798
|
+
?disabled=${!view.id}
|
|
1799
|
+
title=${view.id ? '' : i18next.t('figure.text.save-the-figure-before-releasing')}
|
|
1800
|
+
@click=${this.goRelease}
|
|
1801
|
+
>
|
|
1802
|
+
<md-icon>publish</md-icon>${i18next.t('figure.button.release')}
|
|
1803
|
+
</button>
|
|
1804
|
+
<button slot="commit" save ?disabled=${unavailable || (!view.dirty && !!view.id)} @click=${this.save}><md-icon>save</md-icon>${i18next.t('figure.button.save')}</button>
|
|
1708
1805
|
</ox-canvas-bar>
|
|
1709
1806
|
${this.blockersOpen && blockers.length ? this.renderReleaseBlockers(blockers, asset, unavailable) : nothing}
|
|
1710
1807
|
${this.catalogOpen
|
|
@@ -2098,6 +2195,18 @@ __decorate([
|
|
|
2098
2195
|
state(),
|
|
2099
2196
|
__metadata("design:type", String)
|
|
2100
2197
|
], FigureEditor.prototype, "overlay", void 0);
|
|
2198
|
+
__decorate([
|
|
2199
|
+
state(),
|
|
2200
|
+
__metadata("design:type", Object)
|
|
2201
|
+
], FigureEditor.prototype, "fastening", void 0);
|
|
2202
|
+
__decorate([
|
|
2203
|
+
state(),
|
|
2204
|
+
__metadata("design:type", Object)
|
|
2205
|
+
], FigureEditor.prototype, "fastenError", void 0);
|
|
2206
|
+
__decorate([
|
|
2207
|
+
state(),
|
|
2208
|
+
__metadata("design:type", Object)
|
|
2209
|
+
], FigureEditor.prototype, "fastenHover", void 0);
|
|
2101
2210
|
__decorate([
|
|
2102
2211
|
state(),
|
|
2103
2212
|
__metadata("design:type", Object)
|