@things-factory/figure-ui 10.1.225 → 10.1.227
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/graphql/index.ts +6 -6
- package/client/modeller/consumer-capabilities.ts +15 -0
- package/client/modeller/editor-styles.ts +18 -1
- package/client/modeller/editor.ts +38 -26
- package/client/modeller/figure-capability-panel.ts +31 -10
- package/client/modeller/figure-clips.ts +2 -1
- package/client/modeller/figure-controls.ts +4 -3
- package/client/modeller/figure-data-panel.ts +24 -7
- package/client/modeller/figure-history-panel.ts +7 -7
- package/client/modeller/figure-links.ts +5 -5
- package/client/modeller/figure-material-panel.ts +2 -0
- package/client/modeller/figure-preview.ts +69 -2
- package/client/modeller/figure-recipe-catalog.ts +4 -0
- package/client/modeller/figure-slot-layout-panel.ts +25 -8
- package/client/modeller/figure-verdict.ts +4 -1
- package/client/modeller/unit-words.ts +11 -0
- package/client/pages/figure-list-page.ts +2 -2
- package/client/pages/figure-modeller-page.ts +7 -6
- package/client/types.ts +2 -2
- package/client/viewparts/figure-thumb.ts +1 -1
- package/dist-client/graphql/index.d.ts +5 -5
- package/dist-client/graphql/index.js +6 -6
- package/dist-client/graphql/index.js.map +1 -1
- package/dist-client/modeller/consumer-capabilities.d.ts +11 -0
- package/dist-client/modeller/consumer-capabilities.js +14 -0
- package/dist-client/modeller/consumer-capabilities.js.map +1 -1
- package/dist-client/modeller/editor-styles.js +18 -1
- package/dist-client/modeller/editor-styles.js.map +1 -1
- package/dist-client/modeller/editor.js +38 -26
- package/dist-client/modeller/editor.js.map +1 -1
- package/dist-client/modeller/figure-capability-panel.d.ts +3 -0
- package/dist-client/modeller/figure-capability-panel.js +29 -11
- package/dist-client/modeller/figure-capability-panel.js.map +1 -1
- package/dist-client/modeller/figure-clips.js +2 -1
- package/dist-client/modeller/figure-clips.js.map +1 -1
- package/dist-client/modeller/figure-controls.js +4 -3
- package/dist-client/modeller/figure-controls.js.map +1 -1
- package/dist-client/modeller/figure-data-panel.js +24 -7
- package/dist-client/modeller/figure-data-panel.js.map +1 -1
- package/dist-client/modeller/figure-history-panel.d.ts +5 -5
- package/dist-client/modeller/figure-history-panel.js +7 -7
- package/dist-client/modeller/figure-history-panel.js.map +1 -1
- package/dist-client/modeller/figure-links.js +5 -5
- package/dist-client/modeller/figure-links.js.map +1 -1
- package/dist-client/modeller/figure-material-panel.js +2 -0
- package/dist-client/modeller/figure-material-panel.js.map +1 -1
- package/dist-client/modeller/figure-preview.d.ts +3 -0
- package/dist-client/modeller/figure-preview.js +78 -2
- package/dist-client/modeller/figure-preview.js.map +1 -1
- package/dist-client/modeller/figure-recipe-catalog.js +4 -0
- package/dist-client/modeller/figure-recipe-catalog.js.map +1 -1
- package/dist-client/modeller/figure-slot-layout-panel.d.ts +1 -1
- package/dist-client/modeller/figure-slot-layout-panel.js +25 -8
- package/dist-client/modeller/figure-slot-layout-panel.js.map +1 -1
- package/dist-client/modeller/figure-verdict.js +4 -1
- package/dist-client/modeller/figure-verdict.js.map +1 -1
- package/dist-client/modeller/unit-words.d.ts +5 -0
- package/dist-client/modeller/unit-words.js +12 -0
- package/dist-client/modeller/unit-words.js.map +1 -0
- package/dist-client/pages/figure-list-page.d.ts +1 -1
- package/dist-client/pages/figure-list-page.js +2 -2
- package/dist-client/pages/figure-list-page.js.map +1 -1
- package/dist-client/pages/figure-modeller-page.d.ts +3 -3
- package/dist-client/pages/figure-modeller-page.js +7 -6
- package/dist-client/pages/figure-modeller-page.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/types.d.ts +2 -2
- package/dist-client/types.js.map +1 -1
- package/dist-client/viewparts/figure-thumb.js +1 -1
- package/dist-client/viewparts/figure-thumb.js.map +1 -1
- package/package.json +9 -9
- package/test/browser/detail-page.js +1 -1
- package/test/browser/editing-page.js +3 -3
- package/test/browser/editing.check.mjs +5 -5
- package/test/browser/links-page.js +1 -1
- package/test/browser/links.check.mjs +2 -3
- package/test/browser/modeller-page.js +4 -4
- package/test/browser/modeller-revision.check.mjs +5 -5
- package/test/browser/modeller.check.mjs +5 -5
- package/test/browser/parts.check.mjs +2 -2
- package/test/browser/product-page.check.mjs +6 -0
- package/test/browser/props.check.mjs +3 -4
- package/test/browser/row-preview.check.mjs +3 -3
- package/test/browser/screen.check.mjs +13 -11
- package/test/consumer-capabilities.test.ts +7 -0
- package/test/links.test.ts +1 -1
- package/test/refusals.test.ts +1 -1
- package/test/resolve-ts-siblings.mjs +3 -3
- package/translations/en.json +23 -12
- package/translations/es.json +23 -12
- package/translations/ja.json +24 -13
- package/translations/ko.json +83 -72
- package/translations/ms.json +23 -12
- package/translations/zh.json +23 -12
package/client/graphql/index.ts
CHANGED
|
@@ -45,7 +45,7 @@ function unwrap<T>(response: { data?: Record<string, unknown>; errors?: readonly
|
|
|
45
45
|
* **`thumbnail` 도 여기 없다.** base64 문자열이라 목록 응답에 실으면 카탈로그를 열 때마다 그림
|
|
46
46
|
* 전부가 다시 오고(표본 14개 227KB), data URL 은 브라우저가 캐시하지 못한다. 그림은 주소로
|
|
47
47
|
* 부른다(`figure-thumbnail` 라우트). 대신 `thumbnailUpdatedAt` 을 받는다 — 그림이 있나 없나를
|
|
48
|
-
* 그것으로 알고,
|
|
48
|
+
* 그것으로 알고, 버전을 가리는 값으로도 쓴다.
|
|
49
49
|
*/
|
|
50
50
|
const FIGURE_LIST_FIELDS = `
|
|
51
51
|
id
|
|
@@ -226,7 +226,7 @@ export async function deleteFigure(id: string): Promise<boolean> {
|
|
|
226
226
|
}
|
|
227
227
|
|
|
228
228
|
/**
|
|
229
|
-
* 발행한다 —
|
|
229
|
+
* 발행한다 — 버전 번호가 오르고, 그 순간이 버전으로 남는다.
|
|
230
230
|
*
|
|
231
231
|
* 이미 발행된 것을 다시 발행하면 서버가 거절한다. 고쳐서 다시 내려면 저장이 먼저이고, 저장은
|
|
232
232
|
* 발행을 푼다(초안으로 돌아온다).
|
|
@@ -340,7 +340,7 @@ export interface KeptDraft {
|
|
|
340
340
|
const KEPT_FIELDS = `id kind state version revertedToVersion restoredAt createdAt creator { name }`
|
|
341
341
|
|
|
342
342
|
/**
|
|
343
|
-
* 옛
|
|
343
|
+
* 옛 버전을 초안으로 되살린다. 정본·속성·그림이 돌아오고 이름·설명은 그대로다. 덮이는 초안은 같은 트랜잭션에서 서버가
|
|
344
344
|
* 보관한다. `expectedSource` 는 서버가 들고 있다고 확인된 정본 -- 그 사이 바뀌었으면 거절된다. 답을 못 받았으면
|
|
345
345
|
* (REVERT_UNREACHABLE) 같은 `requestId` 로 다시 묻는다: 서버는 같은 되돌리기로 답하고 두 번 하지 않는다.
|
|
346
346
|
*/
|
|
@@ -464,7 +464,7 @@ export async function inspectFigure(id: string): Promise<FigureInspection> {
|
|
|
464
464
|
*
|
|
465
465
|
* 발행 패널의 `inspectFigure` 는 저장된 원본을 재므로 고친 결과를 저장한 뒤에야 보여 준다. 편집하면서 막히는
|
|
466
466
|
* 것을 보려면 편집 중인 원본을 보내야 한다. 판정은 서버의 같은 문(`inspectAnySource`)이 하고 문장도 같다 —
|
|
467
|
-
* 화면이 제 판정을 따로 가지면 두 자리가 다른 말을 한다. `id` 가 있으면 서버가 그 행의 타입과 직전
|
|
467
|
+
* 화면이 제 판정을 따로 가지면 두 자리가 다른 말을 한다. `id` 가 있으면 서버가 그 행의 타입과 직전 발행 버전을 읽는다.
|
|
468
468
|
*/
|
|
469
469
|
export async function inspectFigureDraft(source: string, id?: string): Promise<FigureInspection> {
|
|
470
470
|
const response = await client.query({
|
|
@@ -490,9 +490,9 @@ export async function inspectFigureDraft(source: string, id?: string): Promise<F
|
|
|
490
490
|
return unwrap<FigureInspection>(response, 'inspectFigureDraft')
|
|
491
491
|
}
|
|
492
492
|
|
|
493
|
-
/** 발행된
|
|
493
|
+
/** 발행된 버전들 — 최근 것부터 열 개. */
|
|
494
494
|
/**
|
|
495
|
-
* 직전
|
|
495
|
+
* 직전 발행 버전의 정본. 한 번도 발행 안 했으면 `undefined`.
|
|
496
496
|
*
|
|
497
497
|
* 저작 중에 「이 고침이 이미 놓인 것을 깨나」를 그 자리에서 보여 주려면 견줄 것이 필요하다. 저장
|
|
498
498
|
* 전 초안은 서버가 모르므로 판정도 화면에서 한다 — 형식의 `contractChanges` 를 같이 쓴다.
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { isFigureConsumerCapability } from '@hatiolab/figure-model'
|
|
8
8
|
import type { FigureAsset } from '@hatiolab/figure-model'
|
|
9
9
|
import { figureCapabilityDescriptors, type FigureCapabilityDescriptor, type FigureCapabilitySetting } from '@hatiolab/things-scene'
|
|
10
|
+
import { RUNGS, type Rung } from '@things-factory/figure-viewer/dist-client/capability-rung.js'
|
|
10
11
|
|
|
11
12
|
/** The products' capabilities this modeller knows: registered descriptors with a namespaced name. */
|
|
12
13
|
export const consumerDescriptors = (): FigureCapabilityDescriptor[] => figureCapabilityDescriptors().filter(d => isFigureConsumerCapability(d.name))
|
|
@@ -24,6 +25,20 @@ export function needsMissing(descriptor: FigureCapabilityDescriptor, asset: Figu
|
|
|
24
25
|
return descriptor.needs.filter(need => (Array.isArray(need) ? !need.some(has) : !has(need as string))).map(need => (Array.isArray(need) ? need.join(' | ') : (need as string)))
|
|
25
26
|
}
|
|
26
27
|
|
|
28
|
+
/**
|
|
29
|
+
* What a product's capability needs that the figure lacks, as the tab offers it: the item tag, and the rungs that would
|
|
30
|
+
* give the names it needs (Holdable | FlowNode and Capacity | FlowNode: hold or flow). Its own names are not shown to the
|
|
31
|
+
* author (sweep 2026-10-27 U19); `names` keeps a need no rung gives.
|
|
32
|
+
*/
|
|
33
|
+
export function needsOffered(descriptor: FigureCapabilityDescriptor, asset: FigureAsset): { item: boolean; rungs: Rung[]; names: string[] } {
|
|
34
|
+
const missing = needsMissing(descriptor, asset)
|
|
35
|
+
const groups = descriptor.needs.filter(need => need !== 'item').map(need => (Array.isArray(need) ? need : [need as string]))
|
|
36
|
+
const gives = (rung: Rung) => groups.every(group => group.some(name => (RUNGS[rung] as readonly string[]).includes(name)))
|
|
37
|
+
const lacking = missing.filter(m => m !== 'item')
|
|
38
|
+
const rungs = lacking.length ? (['hold', 'flow'] as const).filter(gives) : []
|
|
39
|
+
return { item: missing.includes('item'), rungs, names: lacking.length && !rungs.length ? lacking : [] }
|
|
40
|
+
}
|
|
41
|
+
|
|
27
42
|
/** The names with a product's capability put on or taken off; the shared ones as they are. */
|
|
28
43
|
export function namesWith(asset: FigureAsset, name: string, on: boolean): string[] {
|
|
29
44
|
const names = asset.document.capabilities.names ?? []
|
|
@@ -19,7 +19,8 @@ export const EditorStyles = css`
|
|
|
19
19
|
/* The bar over the work is ox-canvas-bar (ADR-0060 decision 8): the folds at its edges, undo, redo and
|
|
20
20
|
fit in the suite's places. What the editor puts in it - the mode switch, the status, save - is styled here. */
|
|
21
21
|
ox-canvas-bar button[mode] {
|
|
22
|
-
height
|
|
22
|
+
/* The bar's height, beside its 32px icons and the save (sweep 2026-10-27 U25). */
|
|
23
|
+
height: 32px;
|
|
23
24
|
border: 1px solid transparent;
|
|
24
25
|
padding: 0 10px;
|
|
25
26
|
border-radius: 999em;
|
|
@@ -207,6 +208,22 @@ export const EditorStyles = css`
|
|
|
207
208
|
margin-top: 4px;
|
|
208
209
|
color: var(--md-sys-color-on-surface-variant);
|
|
209
210
|
}
|
|
211
|
+
div[start] button[start-more] {
|
|
212
|
+
display: block;
|
|
213
|
+
text-align: left;
|
|
214
|
+
margin: 4px 0 0;
|
|
215
|
+
padding: 0;
|
|
216
|
+
font: inherit;
|
|
217
|
+
font-size: var(--ui-t-small);
|
|
218
|
+
color: var(--md-sys-color-primary);
|
|
219
|
+
background: none;
|
|
220
|
+
border: none;
|
|
221
|
+
cursor: pointer;
|
|
222
|
+
}
|
|
223
|
+
/* No rule under the last way to start: it closed nothing (sweep 2026-10-27 U23). */
|
|
224
|
+
div[start-paths] > section:last-of-type {
|
|
225
|
+
border-bottom: none;
|
|
226
|
+
}
|
|
210
227
|
/* The side: tabs over one panel at a time, each using the whole height. */
|
|
211
228
|
/*
|
|
212
229
|
The side's words at the panel's size (side-panel-styles: titles body, the rest small). The editor's host is at body
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { LitElement, html, nothing, type PropertyValues } from 'lit'
|
|
2
2
|
import { i18next } from '@operato/i18n'
|
|
3
|
+
import { isMacOS } from '@operato/utils'
|
|
4
|
+
import { unitWord } from './unit-words.js'
|
|
3
5
|
import type { OxThreePane } from '@operato/layout'
|
|
4
6
|
import '@operato/layout'
|
|
5
7
|
import { openPopup } from '@operato/layout'
|
|
@@ -887,6 +889,14 @@ export class FigureEditor extends LitElement {
|
|
|
887
889
|
<summary><h3>${i18next.t('figure.label.group-more')}</h3></summary>
|
|
888
890
|
<div more-body>
|
|
889
891
|
${tools ? html`<div group-fields>${tools}</div>` : nothing}
|
|
892
|
+
<figure-material-panel
|
|
893
|
+
show="more"
|
|
894
|
+
.body=${{ ...part, id: this.selected }}
|
|
895
|
+
.palette=${palette}
|
|
896
|
+
?locked=${unavailable}
|
|
897
|
+
@part-edit=${(e: CustomEvent) => this.run(() => this.session!.edit(e.detail.edit))}
|
|
898
|
+
@part-live=${(e: CustomEvent) => this.hold({}, e.detail.edit)}
|
|
899
|
+
></figure-material-panel>
|
|
890
900
|
<figure-section-panel
|
|
891
901
|
embedded
|
|
892
902
|
.body=${{ ...part, id: this.selected }}
|
|
@@ -898,14 +908,6 @@ export class FigureEditor extends LitElement {
|
|
|
898
908
|
@command-live=${(e: CustomEvent) => this.holdAction(e.detail.action)}
|
|
899
909
|
@section-editor-open=${() => (this.sectionEditing = this.selected)}
|
|
900
910
|
></figure-section-panel>
|
|
901
|
-
<figure-material-panel
|
|
902
|
-
show="more"
|
|
903
|
-
.body=${{ ...part, id: this.selected }}
|
|
904
|
-
.palette=${palette}
|
|
905
|
-
?locked=${unavailable}
|
|
906
|
-
@part-edit=${(e: CustomEvent) => this.run(() => this.session!.edit(e.detail.edit))}
|
|
907
|
-
@part-live=${(e: CustomEvent) => this.hold({}, e.detail.edit)}
|
|
908
|
-
></figure-material-panel>
|
|
909
911
|
</div>
|
|
910
912
|
</details>
|
|
911
913
|
</section>`
|
|
@@ -1143,7 +1145,7 @@ export class FigureEditor extends LitElement {
|
|
|
1143
1145
|
${title('figure.text.board-size-range-explained')}
|
|
1144
1146
|
${(['x', 'y', 'z'] as const).map(
|
|
1145
1147
|
axis => html`<div inline-field>
|
|
1146
|
-
<label>${i18next.t('figure.label.resize-' + axis)} <span unit>mm</span></label>
|
|
1148
|
+
<label>${i18next.t('figure.label.resize-' + axis)} <span unit>(mm)</span></label>
|
|
1147
1149
|
<div pair range>
|
|
1148
1150
|
${(['min', 'max'] as const).map(
|
|
1149
1151
|
(bound, i) => html`${i ? html`<span dash aria-hidden="true">–</span>` : nothing}<input
|
|
@@ -1279,7 +1281,7 @@ export class FigureEditor extends LitElement {
|
|
|
1279
1281
|
<div inline-field><label>${i18next.t('figure.label.repeat-axis')}</label>${axes.length ? html`<select aria-label=${i18next.t('figure.label.repeat-axis')} ?disabled=${unavailable} .value=${live(axis)} @change=${(e: Event) => update({ axis: (e.target as HTMLSelectElement).value.toUpperCase() as 'X' | 'Y' | 'Z' })}>${axes.map(a => html`<option value=${a} ?selected=${a === axis}>${a.toUpperCase()}</option>`)}</select>` : html`<span value>${axis.toUpperCase()}</span>`}</div>
|
|
1280
1282
|
<div inline-field><label>${i18next.t('figure.label.repeat-count')}</label>${!fits && record ? html`<input type="number" min="1" step="1" aria-label=${i18next.t('figure.label.repeat-count')} .value=${live(String(repeat.count))} ?disabled=${unavailable} @change=${(e: Event) => update({ count: (e.target as HTMLInputElement).valueAsNumber })}>` : html`<span value>${repeat.count}</span>`}</div>
|
|
1281
1283
|
<div inline-field>
|
|
1282
|
-
<label>${i18next.t('figure.label.repeat-gap')} <span unit>mm</span></label>
|
|
1284
|
+
<label>${i18next.t('figure.label.repeat-gap')} <span unit>(mm)</span></label>
|
|
1283
1285
|
${fits ? html`<input
|
|
1284
1286
|
type="number"
|
|
1285
1287
|
step="any"
|
|
@@ -1371,8 +1373,8 @@ export class FigureEditor extends LitElement {
|
|
|
1371
1373
|
</label>
|
|
1372
1374
|
${open
|
|
1373
1375
|
? html`<div inline-field><label>${i18next.t('figure.label.board-name')}</label><input aria-label="figure.label.board-name" .value=${form!.label} @change=${edit('label')} /></div>
|
|
1374
|
-
<div inline-field><label>${i18next.t('figure.label.least')} <span unit>mm</span></label><input type="number" step="any" aria-label="figure.label.least" .value=${String(form!.min)} @change=${edit('min')} /></div>
|
|
1375
|
-
<div inline-field><label>${i18next.t('figure.label.most')} <span unit>mm</span></label><input type="number" step="any" aria-label="figure.label.most" .value=${String(form!.max)} @change=${edit('max')} /></div>
|
|
1376
|
+
<div inline-field><label>${i18next.t('figure.label.least')} <span unit>(mm)</span></label><input type="number" step="any" aria-label="figure.label.least" .value=${String(form!.min)} @change=${edit('min')} /></div>
|
|
1377
|
+
<div inline-field><label>${i18next.t('figure.label.most')} <span unit>(mm)</span></label><input type="number" step="any" aria-label="figure.label.most" .value=${String(form!.max)} @change=${edit('max')} /></div>
|
|
1376
1378
|
<button act publish-pitch ?disabled=${unavailable || !form!.label.trim()} @click=${() => run(steps())}>
|
|
1377
1379
|
<md-icon>${listed ? 'check' : 'ios_share'}</md-icon>${i18next.t(listed ? 'figure.button.apply' : 'figure.button.publish-input')}
|
|
1378
1380
|
</button>`
|
|
@@ -1601,9 +1603,12 @@ export class FigureEditor extends LitElement {
|
|
|
1601
1603
|
}
|
|
1602
1604
|
/** A side tab says what it is in words under its icon: the icons alone had to be learnt (designer 2026-09-27, U05). */
|
|
1603
1605
|
private renderTab(tab: SideTab, icon: string, label: string) {
|
|
1604
|
-
/*
|
|
1605
|
-
|
|
1606
|
-
|
|
1606
|
+
/*
|
|
1607
|
+
Its accessible name is the name it shows (designer 2026-09-27); the longer title stays as its tooltip. One name
|
|
1608
|
+
whatever is picked: the size tab was 「반응형」 with nothing picked and 「배치」 with a part (sweep 2026-10-27 J2).
|
|
1609
|
+
*/
|
|
1610
|
+
const name = i18next.t('figure.label.tab-short-' + tab)
|
|
1611
|
+
return html`<button tab ?on=${this.tab === tab} title=${label} aria-label=${name} @click=${() => (this.tab = tab)}>
|
|
1607
1612
|
<md-icon>${icon}</md-icon><span tab-label aria-hidden="true">${name}</span>
|
|
1608
1613
|
</button>`
|
|
1609
1614
|
}
|
|
@@ -1700,7 +1705,13 @@ export class FigureEditor extends LitElement {
|
|
|
1700
1705
|
</button>`
|
|
1701
1706
|
)}
|
|
1702
1707
|
</div>
|
|
1703
|
-
|
|
1708
|
+
${this.panes?.foldStart
|
|
1709
|
+
? /* The list it points to is folded at this width: a way to it, not words about a list not there (sweep 2026-10-27 U23). */
|
|
1710
|
+
html`<button start-more @click=${() => {
|
|
1711
|
+
this.panes!.foldStart = false
|
|
1712
|
+
this.requestUpdate()
|
|
1713
|
+
}}>${i18next.t('figure.button.start-shape-more')}</button>`
|
|
1714
|
+
: html`<small>${i18next.t('figure.text.start-shape-more')}</small>`}
|
|
1704
1715
|
</section>
|
|
1705
1716
|
${this.aiDraft
|
|
1706
1717
|
? html`<section>
|
|
@@ -1708,7 +1719,6 @@ export class FigureEditor extends LitElement {
|
|
|
1708
1719
|
<button start-button start-ai @click=${() => this.dispatchEvent(new CustomEvent('ask-ai-draft', { bubbles: true, composed: true }))}>
|
|
1709
1720
|
${i18next.t('figure.button.start-ai')}
|
|
1710
1721
|
</button>
|
|
1711
|
-
<small>${i18next.t('figure.text.start-ai-optional')}</small>
|
|
1712
1722
|
</section>`
|
|
1713
1723
|
: nothing}
|
|
1714
1724
|
</div>
|
|
@@ -1789,8 +1799,8 @@ export class FigureEditor extends LitElement {
|
|
|
1789
1799
|
return html`<ox-canvas-bar
|
|
1790
1800
|
history
|
|
1791
1801
|
fit
|
|
1792
|
-
undo-shortcut
|
|
1793
|
-
redo-shortcut
|
|
1802
|
+
undo-shortcut=${isMacOS() ? '⌘Z' : 'Ctrl+Z'}
|
|
1803
|
+
redo-shortcut=${isMacOS() ? '⇧⌘Z' : 'Ctrl+Y'}
|
|
1794
1804
|
?can-undo=${!unavailable && view.canUndo}
|
|
1795
1805
|
?can-redo=${!unavailable && view.canRedo}
|
|
1796
1806
|
.pane=${this.panes}
|
|
@@ -1822,7 +1832,7 @@ export class FigureEditor extends LitElement {
|
|
|
1822
1832
|
>
|
|
1823
1833
|
<md-icon>publish</md-icon>${i18next.t('figure.button.release')}
|
|
1824
1834
|
</button>
|
|
1825
|
-
<button slot="commit" save ?disabled=${unavailable || (!view.dirty && !!view.id)} @click=${this.save}><md-icon>save</md-icon>${i18next.t('figure.button.save')}</button>
|
|
1835
|
+
<button slot="commit" save title=${!view.id && !view.geometry.length ? i18next.t('figure.text.save-needs-a-part') : ''} ?disabled=${unavailable || (!view.dirty && !!view.id) || (!view.id && !view.geometry.length)} @click=${this.save}><md-icon>save</md-icon>${i18next.t('figure.button.save')}</button>
|
|
1826
1836
|
</ox-canvas-bar>
|
|
1827
1837
|
${this.blockersOpen && blockers.length ? this.renderReleaseBlockers(blockers, asset) : nothing}
|
|
1828
1838
|
${this.catalogOpen
|
|
@@ -1914,7 +1924,7 @@ export class FigureEditor extends LitElement {
|
|
|
1914
1924
|
${this.renderTab('motion', 'animation', i18next.t('figure.label.motion'))}
|
|
1915
1925
|
${this.renderTab('capability', 'conveyor_belt', i18next.t('figure.label.tab-capability'))}
|
|
1916
1926
|
${this.renderTab('data', 'sensors', i18next.t('figure.label.tab-data'))}
|
|
1917
|
-
${this.renderTab('verdict', 'insights', i18next.t('figure.label.
|
|
1927
|
+
${this.renderTab('verdict', 'insights', i18next.t('figure.label.tab-verdict'))}
|
|
1918
1928
|
</div>
|
|
1919
1929
|
<slot name="notice"></slot>
|
|
1920
1930
|
<div panel ?off=${tab !== 'properties'}>
|
|
@@ -2041,18 +2051,19 @@ export class FigureEditor extends LitElement {
|
|
|
2041
2051
|
return v === undefined || !Number.isFinite(v) ? '—' : String(Math.round(v * 10) / 10)
|
|
2042
2052
|
}
|
|
2043
2053
|
return html`<div vector-row>
|
|
2044
|
-
<label>${i18next.t('figure.label.group-' + group.name)}${group.name === 'rotation' ? '' : html` <span unit
|
|
2054
|
+
<label>${i18next.t('figure.label.group-' + group.name)}${group.name === 'rotation' ? '' : html` <span unit>(${unit})</span>`}</label>
|
|
2045
2055
|
<div triple>
|
|
2046
2056
|
${(['x', 'y', 'z'] as const).map(
|
|
2047
2057
|
axis => !group.cells[axis]
|
|
2048
2058
|
? html`<div cell held title=${i18next.t('figure.text.axis-held-by-rule')}>
|
|
2049
2059
|
<span axis>${axis.toUpperCase()}</span>
|
|
2050
|
-
<input type="text" readonly .value=${given(axis)} aria-label=${`${group.name}
|
|
2060
|
+
<input type="text" readonly .value=${given(axis)} data-field=${`${group.name}.${axis}`} aria-label=${`${i18next.t('figure.label.group-' + group.name)} ${axis.toUpperCase()} · ${i18next.t('figure.text.axis-held-by-rule')}`} />
|
|
2051
2061
|
</div>`
|
|
2052
2062
|
: html`<div cell>
|
|
2053
2063
|
<span axis ?scrub=${!unavailable} @pointerdown=${(e: PointerEvent) => this.scrub(group.cells[axis] as never, e, unavailable)}>${axis.toUpperCase()}</span>
|
|
2054
2064
|
<input
|
|
2055
|
-
|
|
2065
|
+
data-field=${group.cells[axis]!.id}
|
|
2066
|
+
aria-label=${`${i18next.t('figure.label.group-' + group.name)} ${axis.toUpperCase()}`}
|
|
2056
2067
|
type="number"
|
|
2057
2068
|
step="any"
|
|
2058
2069
|
.value=${String(group.cells[axis]!.value)}
|
|
@@ -2087,9 +2098,10 @@ export class FigureEditor extends LitElement {
|
|
|
2087
2098
|
}
|
|
2088
2099
|
private renderSingle(name: string, field: FieldLike, unavailable: boolean) {
|
|
2089
2100
|
const words = name.split('.').map(part => i18next.t('figure.label.field-' + part, { defaultValue: part }))
|
|
2090
|
-
return html`<span ?scrub=${!unavailable} @pointerdown=${(e: PointerEvent) => this.scrub(field as never, e, unavailable)}>${words.join(' · ')} (${field.unit})</span
|
|
2101
|
+
return html`<span ?scrub=${!unavailable} @pointerdown=${(e: PointerEvent) => this.scrub(field as never, e, unavailable)}>${words.join(' · ')} (${unitWord(field.unit)})</span
|
|
2091
2102
|
><input
|
|
2092
|
-
|
|
2103
|
+
data-field=${field.id}
|
|
2104
|
+
aria-label=${words.join(' · ')}
|
|
2093
2105
|
type="number"
|
|
2094
2106
|
step="any"
|
|
2095
2107
|
.value=${String(field.value)}
|
|
@@ -3,6 +3,7 @@ import { customElement, property } from 'lit/decorators.js'
|
|
|
3
3
|
import { live } from 'lit/directives/live.js'
|
|
4
4
|
|
|
5
5
|
import { i18next, localize } from '@operato/i18n'
|
|
6
|
+
import { unitWord } from './unit-words.js'
|
|
6
7
|
import { figureBindingsOf } from '@hatiolab/figure-model'
|
|
7
8
|
import type { FigureAsset } from '@hatiolab/figure-model'
|
|
8
9
|
import { RUNGS, rungOf, type Rung } from '@things-factory/figure-viewer/dist-client/capability-rung.js'
|
|
@@ -15,6 +16,7 @@ import {
|
|
|
15
16
|
inputTableAt,
|
|
16
17
|
namesWith,
|
|
17
18
|
needsMissing,
|
|
19
|
+
needsOffered,
|
|
18
20
|
partsAt,
|
|
19
21
|
settingsIssues,
|
|
20
22
|
settingsOf,
|
|
@@ -141,7 +143,8 @@ export class FigureCapabilityPanel extends localize(i18next)(LitElement) {
|
|
|
141
143
|
const names = asset.document.capabilities.names ?? []
|
|
142
144
|
const rung = rungOf(names)
|
|
143
145
|
const places = figureBindingsOf(asset)
|
|
144
|
-
return html
|
|
146
|
+
return html`${this.renderItem(asset)}
|
|
147
|
+
<section>
|
|
145
148
|
<h3>${i18next.t('figure.label.what-this-does')}</h3>
|
|
146
149
|
${(['none', 'hold', 'flow'] as const).map(
|
|
147
150
|
which => html`<label rung ?on=${rung === which}>
|
|
@@ -153,10 +156,18 @@ export class FigureCapabilityPanel extends localize(i18next)(LitElement) {
|
|
|
153
156
|
</label>`
|
|
154
157
|
)}
|
|
155
158
|
${rung === null ? html`<p hint>${i18next.t('figure.text.capability-hand-written')}</p>` : nothing}
|
|
156
|
-
${names.length ? html`<code>${names.join(' · ')}</code>` : nothing}
|
|
157
159
|
</section>
|
|
158
160
|
${this.renderConsumers(asset)}
|
|
159
|
-
|
|
161
|
+
${places.length || !this.selected ? this.renderPlaces(places, rung) : nothing}
|
|
162
|
+
`
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/*
|
|
166
|
+
The figure's places. None yet, with a part picked: nothing here -- the part's own place form stands above, and 「pick a
|
|
167
|
+
part and add one」 under it told the author what they were doing (sweep 2026-10-27 U20).
|
|
168
|
+
*/
|
|
169
|
+
private renderPlaces(places: ReturnType<typeof figureBindingsOf>, rung: Rung | null) {
|
|
170
|
+
return html`<section>
|
|
160
171
|
<h3>${i18next.t('figure.label.places')}</h3>
|
|
161
172
|
${places.length
|
|
162
173
|
? places.map(
|
|
@@ -170,8 +181,12 @@ export class FigureCapabilityPanel extends localize(i18next)(LitElement) {
|
|
|
170
181
|
)
|
|
171
182
|
: html`<p hint>${i18next.t('figure.text.no-places')}</p>`}
|
|
172
183
|
${places.length && rung === 'none' ? html`<p hint>${i18next.t('figure.text.place-needs-holding')}</p>` : nothing}
|
|
173
|
-
|
|
174
|
-
|
|
184
|
+
</section>`
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** Whether the figure is an item: what a product capability may need first, so it stands first (sweep 2026-10-27 J4). */
|
|
188
|
+
private renderItem(asset: FigureAsset) {
|
|
189
|
+
return html`<section>
|
|
175
190
|
<h3>${i18next.t('figure.label.item')}</h3>
|
|
176
191
|
<label item ?on=${!!asset.item}>
|
|
177
192
|
<input type="checkbox" .checked=${live(!!asset.item)} ?disabled=${this.locked} @change=${(e: Event) => this.setItem((e.target as HTMLInputElement).checked)} />
|
|
@@ -180,8 +195,7 @@ export class FigureCapabilityPanel extends localize(i18next)(LitElement) {
|
|
|
180
195
|
<em>${i18next.t('figure.text.is-item-note')}</em>
|
|
181
196
|
</span>
|
|
182
197
|
</label>
|
|
183
|
-
|
|
184
|
-
`
|
|
198
|
+
</section>`
|
|
185
199
|
}
|
|
186
200
|
|
|
187
201
|
/** The products' capabilities: those this modeller knows, each on or off with its settings, and those it does not know. */
|
|
@@ -195,15 +209,22 @@ export class FigureCapabilityPanel extends localize(i18next)(LitElement) {
|
|
|
195
209
|
${known.map(d => {
|
|
196
210
|
const on = names.includes(d.name)
|
|
197
211
|
const missing = needsMissing(d, asset)
|
|
212
|
+
const offered = needsOffered(d, asset)
|
|
213
|
+
const needs = [
|
|
214
|
+
...(offered.item ? [i18next.t('figure.label.is-item')] : []),
|
|
215
|
+
...(offered.rungs.length
|
|
216
|
+
? [i18next.t('figure.text.product-capability-needs-rungs', { rungs: offered.rungs.map(r => i18next.t(`figure.label.rung-${r}`)).join(i18next.t('figure.label.or-between')) })]
|
|
217
|
+
: []),
|
|
218
|
+
...offered.names
|
|
219
|
+
]
|
|
198
220
|
return html`<label rung ?on=${on}>
|
|
199
221
|
<input type="checkbox" .checked=${live(on)} ?disabled=${this.locked || (!on && missing.length > 0)} @change=${(e: Event) => this.setConsumer(asset, d.name, (e.target as HTMLInputElement).checked)} />
|
|
200
222
|
<span>
|
|
201
223
|
<strong>${d.label}</strong>
|
|
202
224
|
<em>${d.note}</em>
|
|
203
|
-
|
|
225
|
+
${needs.length ? html`<small needs>${i18next.t('figure.text.product-capability-needs', { needs: needs.join(', ') })}</small>` : nothing}
|
|
204
226
|
</span>
|
|
205
227
|
</label>
|
|
206
|
-
${missing.length ? html`<p hint>${i18next.t('figure.text.product-capability-needs', { needs: missing.map(m => (m === 'item' ? i18next.t('figure.label.is-item') : m)).join(', ') })}</p>` : nothing}
|
|
207
228
|
${on ? this.renderSettings(asset, d) : nothing}`
|
|
208
229
|
})}
|
|
209
230
|
${unknown.map(
|
|
@@ -241,7 +262,7 @@ export class FigureCapabilityPanel extends localize(i18next)(LitElement) {
|
|
|
241
262
|
return html`<span>${setting.label}</span>
|
|
242
263
|
<select ?disabled=${this.locked} @change=${(e: Event) => write(withInputTable(settings, setting, (e.target as HTMLSelectElement).value || undefined, table.values))}>
|
|
243
264
|
<option value="" ?selected=${!table.input}>${i18next.t('figure.label.data-none')}</option>
|
|
244
|
-
${inputs.map(i => html`<option value=${i.id} ?selected=${i.id === table.input}>${asset.stateInputs?.[i.id]?.label ?? i.id}${i.unit ? ` (${i.unit} ${i.min ?? ''}~${i.max ?? ''})` : ''}</option>`)}
|
|
265
|
+
${inputs.map(i => html`<option value=${i.id} ?selected=${i.id === table.input}>${asset.stateInputs?.[i.id]?.label ?? i.id}${i.unit ? ` (${unitWord(i.unit)} ${i.min ?? ''}~${i.max ?? ''})` : ''}</option>`)}
|
|
245
266
|
</select>
|
|
246
267
|
${table.input
|
|
247
268
|
? html`<span></span>
|
|
@@ -4,6 +4,7 @@ import { css, html, LitElement, nothing } from 'lit'
|
|
|
4
4
|
import { customElement, property, state } from 'lit/decorators.js'
|
|
5
5
|
|
|
6
6
|
import { i18next, localize } from '@operato/i18n'
|
|
7
|
+
import { unitWord } from './unit-words.js'
|
|
7
8
|
import type { FigureAsset } from '@hatiolab/figure-model'
|
|
8
9
|
|
|
9
10
|
import { clipGroupsOf, clipsOf, freeGroupIdOf, groupEdit, poseAt, trackEdit, undrivenOf, withKey, withoutKey, type Clip, type ClipGroup, type ClipTrack } from './clips.js'
|
|
@@ -156,7 +157,7 @@ export class FigureClips extends localize(i18next)(LitElement) {
|
|
|
156
157
|
const current = this.values[track.state]
|
|
157
158
|
return html`<div track>
|
|
158
159
|
<div track-head>
|
|
159
|
-
<strong>${track.label} <small>(${track.unit})</small></strong>
|
|
160
|
+
<strong>${track.label} <small>(${unitWord(track.unit)})</small></strong>
|
|
160
161
|
<select
|
|
161
162
|
aria-label=${i18next.t('figure.label.clip-loop')}
|
|
162
163
|
?disabled=${this.locked}
|
|
@@ -4,6 +4,7 @@ import { css, html, LitElement, nothing } from 'lit'
|
|
|
4
4
|
import { customElement, property, state } from 'lit/decorators.js'
|
|
5
5
|
|
|
6
6
|
import { i18next, localize } from '@operato/i18n'
|
|
7
|
+
import { unitWord } from './unit-words.js'
|
|
7
8
|
import type { FigureAsset } from '@hatiolab/figure-model'
|
|
8
9
|
|
|
9
10
|
import { controlsOf, poseAsDefault, poseDiffers, type Control } from '@things-factory/figure-viewer/dist-client/controls.js'
|
|
@@ -125,7 +126,7 @@ export class FigureControls extends localize(i18next)(LitElement) {
|
|
|
125
126
|
const c = this.live[control.id] === undefined ? control : { ...control, value: this.live[control.id]! }
|
|
126
127
|
const open = this.open === c.id
|
|
127
128
|
return html`<div control>
|
|
128
|
-
<span name title=${c.id}>${c.label} <em>${c.unit}</em></span>
|
|
129
|
+
<span name title=${c.id}>${c.label} <em>${unitWord(c.unit)}</em></span>
|
|
129
130
|
${c.unit === 'deg' ? this.renderDial(c) : html`<span></span>`}
|
|
130
131
|
<input
|
|
131
132
|
type="range"
|
|
@@ -177,7 +178,7 @@ export class FigureControls extends localize(i18next)(LitElement) {
|
|
|
177
178
|
<div form>
|
|
178
179
|
<span>${i18next.t('figure.label.control-name')}</span>
|
|
179
180
|
<input .value=${c.named ? c.label : ''} placeholder=${c.id} ?disabled=${this.locked} @change=${(e: Event) => policy({ label: (e.target as HTMLInputElement).value })} />
|
|
180
|
-
<span>${i18next.t('figure.label.control-range')} (${c.unit})</span>
|
|
181
|
+
<span>${i18next.t('figure.label.control-range')} (${unitWord(c.unit)})</span>
|
|
181
182
|
<div pair>
|
|
182
183
|
<input type="number" step="any" .value=${String(c.min)} ?disabled=${this.locked} @change=${(e: Event) => policy({ min: number(e) })} />
|
|
183
184
|
<input type="number" step="any" .value=${String(c.max)} ?disabled=${this.locked} @change=${(e: Event) => policy({ max: number(e) })} />
|
|
@@ -185,7 +186,7 @@ export class FigureControls extends localize(i18next)(LitElement) {
|
|
|
185
186
|
<span>${i18next.t('figure.label.control-sweep')}</span>
|
|
186
187
|
<input type="number" min="0" step="any" .value=${String(c.sweep)} ?disabled=${this.locked} @change=${(e: Event) => policy({ sweep: number(e) })} />
|
|
187
188
|
</div>
|
|
188
|
-
<small>${i18next.t('figure.text.control-starts-at', { value: c.start, unit: c.unit })}</small>
|
|
189
|
+
<small>${i18next.t('figure.text.control-starts-at', { value: c.start, unit: unitWord(c.unit) })}</small>
|
|
189
190
|
</div>`
|
|
190
191
|
}
|
|
191
192
|
|
|
@@ -4,6 +4,7 @@ import { css, html, LitElement, nothing } from 'lit'
|
|
|
4
4
|
import { customElement, property, state } from 'lit/decorators.js'
|
|
5
5
|
|
|
6
6
|
import { i18next, localize } from '@operato/i18n'
|
|
7
|
+
import { unitWord } from './unit-words.js'
|
|
7
8
|
import { FIGURE_UNITS, figureDataExample, inferFigureDataSchema } from '@hatiolab/figure-model'
|
|
8
9
|
import type {
|
|
9
10
|
FigureAsset,
|
|
@@ -137,6 +138,17 @@ export class FigureDataPanel extends localize(i18next)(LitElement) {
|
|
|
137
138
|
overflow-wrap: anywhere;
|
|
138
139
|
cursor: pointer;
|
|
139
140
|
}
|
|
141
|
+
/* What it reads and where on the first line, its method under it: a method wrapped alone read 「· 배율」 (sweep 2026-10-27 U11). */
|
|
142
|
+
div[head] button[line] small {
|
|
143
|
+
display: block;
|
|
144
|
+
color: var(--md-sys-color-on-surface-variant);
|
|
145
|
+
}
|
|
146
|
+
/* A binding a product capability takes over is marked where the list is read, folded or not (sweep 2026-10-27 U18). */
|
|
147
|
+
div[head] button[line] span[rests] {
|
|
148
|
+
padding: 0 6px;
|
|
149
|
+
border: 1px solid var(--md-sys-color-outline-variant);
|
|
150
|
+
border-radius: 999px;
|
|
151
|
+
}
|
|
140
152
|
div[head] button[act] {
|
|
141
153
|
width: 30px;
|
|
142
154
|
min-height: var(--figure-control-height);
|
|
@@ -307,7 +319,7 @@ export class FigureDataPanel extends localize(i18next)(LitElement) {
|
|
|
307
319
|
const kind = i18next.t('figure.label.data-kind-' + node.kind)
|
|
308
320
|
if (node.kind === 'number') {
|
|
309
321
|
const range = node.min !== undefined || node.max !== undefined ? ` ${node.min ?? ''}~${node.max ?? ''}` : ''
|
|
310
|
-
return `${kind}${node.unit ? ` ${node.unit}` : ''}${range}`
|
|
322
|
+
return `${kind}${node.unit ? ` ${unitWord(node.unit)}` : ''}${range}`
|
|
311
323
|
}
|
|
312
324
|
if (node.kind === 'text' && node.values) return `${kind} (${node.values.join(', ')})`
|
|
313
325
|
if (node.kind === 'list') return `${kind} · ${i18next.t('figure.label.data-kind-' + node.item.kind)}`
|
|
@@ -361,8 +373,8 @@ export class FigureDataPanel extends localize(i18next)(LitElement) {
|
|
|
361
373
|
${field.kind === 'number'
|
|
362
374
|
? html`<span>${i18next.t('figure.label.data-unit')}</span>
|
|
363
375
|
<select ?disabled=${this.locked} @change=${(e: Event) => set({ unit: value(e) || undefined })}>
|
|
364
|
-
<option value="" ?selected=${!field.unit}
|
|
365
|
-
${FIGURE_UNITS.map(u => html`<option value=${u} ?selected=${u === field.unit}>${u}</option>`)}
|
|
376
|
+
<option value="" ?selected=${!field.unit}>${i18next.t('figure.label.unit-none')}</option>
|
|
377
|
+
${FIGURE_UNITS.map(u => html`<option value=${u} ?selected=${u === field.unit}>${unitWord(u)}</option>`)}
|
|
366
378
|
</select>
|
|
367
379
|
<span>${i18next.t('figure.label.data-min')}</span>
|
|
368
380
|
<input type="number" step="any" .value=${field.min === undefined ? '' : String(field.min)} ?disabled=${this.locked} @change=${(e: Event) => set({ min: number(e) })} />
|
|
@@ -502,10 +514,12 @@ export class FigureDataPanel extends localize(i18next)(LitElement) {
|
|
|
502
514
|
const open = this.opened === b.id
|
|
503
515
|
const set = (next: FigureDataBinding) => this.edit({ kind: 'set-data-binding', binding: next })
|
|
504
516
|
const what = b.rule === 'table' ? this.targetsName(asset, b) : this.behaviorName(asset, choices, b.behavior)
|
|
517
|
+
const rests = resting.some(r => r.id === b.id)
|
|
505
518
|
return html`<section item>
|
|
506
519
|
<div head>
|
|
507
520
|
<button line aria-expanded=${open ? 'true' : 'false'} @click=${() => (this.opened = open ? '' : b.id)}>
|
|
508
|
-
<strong>${b.label ?? b.accessor}</strong> → ${what}
|
|
521
|
+
<strong>${b.label ?? b.accessor}</strong> → ${what}
|
|
522
|
+
<small>${i18next.t('figure.label.data-method-' + methodOf(b))}${rests ? html` <span rests>${i18next.t('figure.label.data-binding-resting')}</span>` : nothing}</small>
|
|
509
523
|
</button>
|
|
510
524
|
<button act ?disabled=${this.locked} title=${i18next.t('figure.button.remove-data-binding')} @click=${() => this.edit({ kind: 'remove-data-binding', id: b.id })}>
|
|
511
525
|
<md-icon>delete</md-icon>
|
|
@@ -871,9 +885,12 @@ export class FigureDataPanel extends localize(i18next)(LitElement) {
|
|
|
871
885
|
<input .value=${a.name} @input=${(e: Event) => set({ name: typed(e) })} />`
|
|
872
886
|
: nothing}
|
|
873
887
|
<span>${i18next.t('figure.label.data-method')}</span>
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
888
|
+
${methods.length === 1
|
|
889
|
+
? /* One way only (a text is read by a code table): said, not offered as a choice of one (sweep 2026-10-27 U10). */
|
|
890
|
+
html`<span method>${i18next.t('figure.label.data-method-' + methods[0])}</span>`
|
|
891
|
+
: html`<select ?disabled=${!methods.length} @change=${(e: Event) => set({ method: typed(e) as DataMethod })}>
|
|
892
|
+
${methods.map(m => html`<option value=${m} ?selected=${m === method}>${i18next.t('figure.label.data-method-' + m)}</option>`)}
|
|
893
|
+
</select>`}
|
|
877
894
|
${!table && fitting.length
|
|
878
895
|
? html`<span>${i18next.t('figure.label.data-behavior')}</span>
|
|
879
896
|
<select @change=${(e: Event) => set({ behavior: typed(e) })}>
|
|
@@ -20,13 +20,13 @@ import type { FigureGateFinding, FigureInspection, FigureVersion } from '../type
|
|
|
20
20
|
import { OxPrompt } from '@operato/popup'
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
|
-
* 이력 — **발행하는 자리이자, 발행된
|
|
23
|
+
* 이력 — **발행하는 자리이자, 발행된 버전들을 보는 자리.**
|
|
24
24
|
*
|
|
25
25
|
* ## 왜 저장 옆이 아니라 여기인가
|
|
26
26
|
*
|
|
27
27
|
* 저장은 하루에 수십 번이고 발행은 드물다. 두 단추를 나란히 두면 저장하려다 발행을 누른다.
|
|
28
|
-
* 그리고 발행은 **소비처에게 하는 약속**이라, 무엇을 약속하는지(지금 몇
|
|
29
|
-
* 있나 · 지난
|
|
28
|
+
* 그리고 발행은 **소비처에게 하는 약속**이라, 무엇을 약속하는지(지금 몇 버전인가 · 저장 안 한 것이
|
|
29
|
+
* 있나 · 지난 버전이 무엇이었나)를 함께 보여 주는 자리에 있어야 한다.
|
|
30
30
|
*
|
|
31
31
|
* ## 저장하지 않은 것이 있으면 발행하지 않는다
|
|
32
32
|
*
|
|
@@ -35,11 +35,11 @@ import { OxPrompt } from '@operato/popup'
|
|
|
35
35
|
*
|
|
36
36
|
* ## 되돌리기는 초안으로 돌아온다
|
|
37
37
|
*
|
|
38
|
-
* 옛
|
|
38
|
+
* 옛 버전의 정본·속성·그림이 돌아오고 이름·설명은 그대로다. 되살렸다고 그 버전이 다시 발행되지는
|
|
39
39
|
* 않는다 — 다시 내보내려면 발행을 한 번 더 하고, 그때 번호는 이미 쓴 다음 번호를 받는다.
|
|
40
40
|
*
|
|
41
41
|
* 덮이는 초안은 서버가 같은 트랜잭션에서 보관하고, 「되돌리기 전 초안」에 남는다(ADR-0097 §5.6, 디자이너 2026-09-27).
|
|
42
|
-
* 발행
|
|
42
|
+
* 발행 버전과 섞지 않는다. 다시 접속해도 거기서 찾고, 가장 최근 것은 「되돌리기 취소」로 되살린다 — 되돌린 뒤 모델이
|
|
43
43
|
* 바뀌었으면 서버가 거절하고 보관본은 남는다.
|
|
44
44
|
*/
|
|
45
45
|
@customElement('figure-history-panel')
|
|
@@ -501,7 +501,7 @@ export class FigureHistoryPanel extends localize(i18next)(LitElement) {
|
|
|
501
501
|
if (this.dirty) {
|
|
502
502
|
if (!(await this.settleUnsaved('revert', version))) return
|
|
503
503
|
} else {
|
|
504
|
-
/* 지금 초안은 서버가 보관하지만, 화면의 것이 그
|
|
504
|
+
/* 지금 초안은 서버가 보관하지만, 화면의 것이 그 버전으로 바뀐다 — 이름으로 확인한다. */
|
|
505
505
|
const agreed = await OxPrompt.open({
|
|
506
506
|
type: 'warning',
|
|
507
507
|
title: i18next.t('figure.button.revert'),
|
|
@@ -542,7 +542,7 @@ export class FigureHistoryPanel extends localize(i18next)(LitElement) {
|
|
|
542
542
|
this.dispatchEvent(new CustomEvent('figure-reverted', { detail: result, bubbles: true, composed: true }))
|
|
543
543
|
await this.reload()
|
|
544
544
|
/*
|
|
545
|
-
**되돌리면 정본이 바뀌므로 판정도 바뀐다.** 이것을 안 하면 옛
|
|
545
|
+
**되돌리면 정본이 바뀌므로 판정도 바뀐다.** 이것을 안 하면 옛 버전의 사유가 새 정본 위에
|
|
546
546
|
남아 화면이 거짓말한다 — 되돌리기는 `dirty` 도 `figureVersion` 도 건드리지 않으므로
|
|
547
547
|
`updated` 의 감시로는 다시 묻지 않는다.
|
|
548
548
|
*/
|
|
@@ -7,6 +7,7 @@ import './figure-size-editor.js'
|
|
|
7
7
|
import { customElement, property, state } from 'lit/decorators.js'
|
|
8
8
|
|
|
9
9
|
import { i18next, localize } from '@operato/i18n'
|
|
10
|
+
import { unitWord } from './unit-words.js'
|
|
10
11
|
import { figureFaceOverhangOf, figureMotionsOf, motionIdOf } from '@hatiolab/figure-model'
|
|
11
12
|
import type { FigureAsset, FigureAuthoringAction, FigureBindingPlace, FigureFace } from '@hatiolab/figure-model'
|
|
12
13
|
|
|
@@ -624,7 +625,6 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
624
625
|
const axes = followableAxes(asset, part)
|
|
625
626
|
if (!axes.length) return nothing
|
|
626
627
|
const word = (axis: Axis) => `${axis.toUpperCase()}${measureFactor(asset, part, axis) === 2 ? ' · ' + i18next.t('figure.label.size-diameter') : ''}`
|
|
627
|
-
const unitWord = (unit: string) => i18next.t(`figure.label.state-unit-${unit}`)
|
|
628
628
|
const sized = axes.flatMap(axis => {
|
|
629
629
|
const f = followOf(asset, part, axis)
|
|
630
630
|
return f.of === 'state' ? [{ axis, f }] : []
|
|
@@ -687,9 +687,9 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
687
687
|
<input type="number" step="any" .value=${String(form.min)} ?disabled=${this.locked} @change=${number('min')} />
|
|
688
688
|
<input type="number" step="any" .value=${String(form.max)} ?disabled=${this.locked} @change=${number('max')} />
|
|
689
689
|
</div>
|
|
690
|
-
<span>${i18next.t('figure.label.state-size-at-min')} (mm)</span>
|
|
690
|
+
<span>${i18next.t('figure.label.state-size-at-min', { at: String(form.min) })} (mm)</span>
|
|
691
691
|
<input type="number" step="any" min="0" state-size-at-min .value=${Number.isFinite(form.atMin) ? String(form.atMin) : ''} ?disabled=${this.locked} @change=${number('atMin')} />
|
|
692
|
-
<span>${i18next.t('figure.label.state-size-at-max')} (mm)</span>
|
|
692
|
+
<span>${i18next.t('figure.label.state-size-at-max', { at: String(form.max) })} (mm)</span>
|
|
693
693
|
<input type="number" step="any" min="0" state-size-at-max .value=${Number.isFinite(atMax) ? String(atMax) : ''} ?disabled=${this.locked} @change=${number('atMax')} />
|
|
694
694
|
</div>
|
|
695
695
|
<button act add-state-size ?disabled=${this.locked || !command} @click=${() => {
|
|
@@ -768,7 +768,7 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
768
768
|
: it.follows
|
|
769
769
|
? html`<small>${i18next.t('figure.label.motion-follow')}: ${it.follows.sum.map(t => `${t.joint} × ${t.multiplier}`).join(' + ')} + ${it.follows.offset}</small>`
|
|
770
770
|
: it.control
|
|
771
|
-
? html`<small>${it.control.id} (${it.control.unit}) ${it.control.min} … ${it.control.max}</small>`
|
|
771
|
+
? html`<small>${it.control.id} (${unitWord(it.control.unit)}) ${it.control.min} … ${it.control.max}</small>`
|
|
772
772
|
: nothing}
|
|
773
773
|
</span>
|
|
774
774
|
${count > 1
|
|
@@ -792,7 +792,7 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
792
792
|
})}
|
|
793
793
|
</p>
|
|
794
794
|
${ends
|
|
795
|
-
? html`<p hint motion-ends>${i18next.t('figure.text.motion-ends-shown', { min: String(ends.min), max: String(ends.max), unit: ends.unit })}</p>`
|
|
795
|
+
? html`<p hint motion-ends>${i18next.t('figure.text.motion-ends-shown', { min: String(ends.min), max: String(ends.max), unit: unitWord(ends.unit) })}</p>`
|
|
796
796
|
: nothing}
|
|
797
797
|
${Array.from({ length: count }, (_, k) => row(k))}
|
|
798
798
|
<div pair>
|
|
@@ -219,7 +219,9 @@ export class FigureMaterialPanel extends localize(i18next)(LitElement) {
|
|
|
219
219
|
private renderMore(part: FigureAssetGeometry & { id: string }, painted: [string, string][], lit: [string, string][]) {
|
|
220
220
|
const material = part.material
|
|
221
221
|
const glow = material.emissive
|
|
222
|
+
/* Under its own title: below 「단면」 without one, transparency and glow read as the section's (sweep 2026-10-27 U21). */
|
|
222
223
|
return html`<div more>
|
|
224
|
+
<h3>${i18next.t('figure.label.part-material')}</h3>
|
|
223
225
|
<label toggle>
|
|
224
226
|
<input
|
|
225
227
|
type="checkbox"
|