@things-factory/figure-ui 10.1.15 → 10.1.17
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/figure-animations.ts +12 -12
- package/client/modeller/figure-canvas.ts +32 -48
- package/client/modeller/figure-capabilities.ts +10 -10
- package/client/modeller/figure-inspector.ts +39 -28
- package/client/modeller/figure-parts.ts +1 -1
- package/client/modeller/figure-preview.ts +122 -25
- package/client/modeller/figure-report.ts +24 -6
- package/client/modeller/figure-settings.ts +45 -2
- package/client/modeller/figure-side.ts +6 -6
- package/client/modeller/figure-source.ts +84 -38
- package/client/modeller/figure-thumbnail.ts +5 -5
- package/client/modeller/figure-view.ts +53 -0
- package/client/modeller/part-edits.ts +36 -36
- package/client/modeller/proposal.ts +25 -4
- package/client/pages/figure-modeller-page.ts +142 -98
- package/dist-client/modeller/figure-animations.d.ts +4 -4
- package/dist-client/modeller/figure-animations.js +10 -10
- package/dist-client/modeller/figure-animations.js.map +1 -1
- package/dist-client/modeller/figure-canvas.d.ts +7 -7
- package/dist-client/modeller/figure-canvas.js +27 -40
- package/dist-client/modeller/figure-canvas.js.map +1 -1
- package/dist-client/modeller/figure-capabilities.d.ts +2 -2
- package/dist-client/modeller/figure-capabilities.js +9 -9
- package/dist-client/modeller/figure-capabilities.js.map +1 -1
- package/dist-client/modeller/figure-inspector.d.ts +16 -5
- package/dist-client/modeller/figure-inspector.js +38 -27
- package/dist-client/modeller/figure-inspector.js.map +1 -1
- package/dist-client/modeller/figure-parts.d.ts +1 -1
- package/dist-client/modeller/figure-parts.js +1 -1
- package/dist-client/modeller/figure-parts.js.map +1 -1
- package/dist-client/modeller/figure-preview.d.ts +39 -0
- package/dist-client/modeller/figure-preview.js +112 -23
- package/dist-client/modeller/figure-preview.js.map +1 -1
- package/dist-client/modeller/figure-report.d.ts +9 -0
- package/dist-client/modeller/figure-report.js +23 -5
- package/dist-client/modeller/figure-report.js.map +1 -1
- package/dist-client/modeller/figure-settings.js +42 -2
- package/dist-client/modeller/figure-settings.js.map +1 -1
- package/dist-client/modeller/figure-side.d.ts +2 -2
- package/dist-client/modeller/figure-side.js +5 -5
- package/dist-client/modeller/figure-side.js.map +1 -1
- package/dist-client/modeller/figure-source.d.ts +38 -23
- package/dist-client/modeller/figure-source.js +40 -21
- package/dist-client/modeller/figure-source.js.map +1 -1
- package/dist-client/modeller/figure-thumbnail.d.ts +2 -2
- package/dist-client/modeller/figure-thumbnail.js +3 -3
- package/dist-client/modeller/figure-thumbnail.js.map +1 -1
- package/dist-client/modeller/figure-view.d.ts +45 -0
- package/dist-client/modeller/figure-view.js +27 -0
- package/dist-client/modeller/figure-view.js.map +1 -1
- package/dist-client/modeller/part-edits.d.ts +16 -16
- package/dist-client/modeller/part-edits.js +35 -35
- package/dist-client/modeller/part-edits.js.map +1 -1
- package/dist-client/modeller/proposal.d.ts +1 -1
- package/dist-client/modeller/proposal.js +15 -3
- package/dist-client/modeller/proposal.js.map +1 -1
- package/dist-client/pages/figure-modeller-page.d.ts +44 -9
- package/dist-client/pages/figure-modeller-page.js +134 -92
- package/dist-client/pages/figure-modeller-page.js.map +1 -1
- package/dist-client/route.d.ts +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/test/ai-proposal-contract.test.ts +393 -12
- package/test/expect.ts +55 -0
- package/test/figure-source.test.ts +562 -0
- package/test/i18n-prefix-guard.test.ts +1 -1
- package/{client/modeller → test}/part-edits.test.ts +50 -45
- package/translations/en.json +31 -28
- package/translations/ja.json +16 -21
- package/translations/ko.json +41 -38
- package/translations/ms.json +16 -21
- package/translations/zh.json +16 -21
- package/client/modeller/figure-ask.ts +0 -265
- package/client/modeller/figure-source.test.ts +0 -352
- package/dist-client/modeller/figure-ask.d.ts +0 -40
- package/dist-client/modeller/figure-ask.js +0 -282
- package/dist-client/modeller/figure-ask.js.map +0 -1
|
@@ -215,8 +215,8 @@ let FigureCapabilities = class FigureCapabilities extends localize(i18next)(LitE
|
|
|
215
215
|
`
|
|
216
216
|
]; }
|
|
217
217
|
render() {
|
|
218
|
-
const rung = rungOf(this.
|
|
219
|
-
const names = this.
|
|
218
|
+
const rung = rungOf(this.draft?.capabilities);
|
|
219
|
+
const names = this.draft?.capabilities ?? [];
|
|
220
220
|
return html `
|
|
221
221
|
<section>
|
|
222
222
|
<h3>${i18next.t('figure.label.what-this-does')}</h3>
|
|
@@ -311,10 +311,10 @@ let FigureCapabilities = class FigureCapabilities extends localize(i18next)(LitE
|
|
|
311
311
|
* 되고, 그 도형은 멀쩡해 보이면서 아무것도 못 받는다.
|
|
312
312
|
*/
|
|
313
313
|
setRung(which) {
|
|
314
|
-
const
|
|
315
|
-
if (!
|
|
314
|
+
const draft = this.draft;
|
|
315
|
+
if (!draft)
|
|
316
316
|
return;
|
|
317
|
-
this.tellBoard({ ...
|
|
317
|
+
this.tellBoard({ ...draft, capabilities: RUNGS[which].length > 0 ? [...RUNGS[which]] : undefined });
|
|
318
318
|
if (which === 'none') {
|
|
319
319
|
this.parts.forEach((part, index) => {
|
|
320
320
|
if (!part.capability)
|
|
@@ -324,15 +324,15 @@ let FigureCapabilities = class FigureCapabilities extends localize(i18next)(LitE
|
|
|
324
324
|
});
|
|
325
325
|
}
|
|
326
326
|
}
|
|
327
|
-
tellBoard(
|
|
328
|
-
this.
|
|
329
|
-
this.dispatchEvent(new CustomEvent('update-
|
|
327
|
+
tellBoard(draft) {
|
|
328
|
+
this.draft = draft;
|
|
329
|
+
this.dispatchEvent(new CustomEvent('update-draft', { detail: { draft }, bubbles: true, composed: true }));
|
|
330
330
|
}
|
|
331
331
|
};
|
|
332
332
|
__decorate([
|
|
333
333
|
property({ type: Object }),
|
|
334
334
|
__metadata("design:type", Object)
|
|
335
|
-
], FigureCapabilities.prototype, "
|
|
335
|
+
], FigureCapabilities.prototype, "draft", void 0);
|
|
336
336
|
__decorate([
|
|
337
337
|
property({ type: Array }),
|
|
338
338
|
__metadata("design:type", Array)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"figure-capabilities.js","sourceRoot":"","sources":["../../client/modeller/figure-capabilities.ts"],"names":[],"mappings":"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","sourcesContent":["import { css, html, LitElement } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\nimport { live } from 'lit/directives/live.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport { ScrollbarStyles } from '@operato/styles'\n\nimport type { BoardModel, PartModel } from './figure-source.js'\n\n/**\n * 이 도형이 **무엇을 하는가.**\n *\n * ## 왜 탭 하나가 필요한가\n *\n * 지금까지 저작한 것은 **생김새**뿐이었다. 컨베이어를 아무리 잘 그려도 보드에 놓으면\n * 상자 하나를 못 받는다 — 물건을 받고 담고 흘려보내는 것은 씬의 capability 가 하는 일이고,\n * 손으로 쓴 컴포넌트는 클래스 선언 한 줄로 그것을 졌다. 저작한 도형에는 그 줄을 쓸 자리가\n * 없어서, 여기서 고른 것이 그 줄이 된다.\n *\n * ## 사다리로 묻는다\n *\n * 능력의 이름(`Capacity` · `Holdable` · `Transferable` · `FlowNode`)을 그대로 늘어놓고\n * 고르라고 하면, 저작자는 넷의 관계를 먼저 배워야 한다. 실제로 물어야 하는 것은 하나다 —\n * **이 도형이 물건을 다루나, 다룬다면 지나가나.** 그래서 세 칸의 사다리로 묻고, 이름은\n * 고른 뒤에 보여 준다.\n *\n * 윗 칸이 아랫 칸을 포함한다(`FlowNode` 가 나머지 셋을 스스로 흡수한다). 그래서 사다리이지\n * 체크상자가 아니다.\n */\n\n/** 사다리 한 칸 — 화면이 묻는 말과, 그것이 되는 능력 이름. */\ntype Rung = 'none' | 'hold' | 'flow'\n\nconst RUNGS: Readonly<Record<Rung, readonly string[]>> = Object.freeze({\n none: [],\n hold: ['Capacity', 'Holdable'],\n flow: ['FlowNode']\n})\n\n/** 선언된 이름 집합이 사다리 어느 칸인가. 어느 칸도 아니면 `null` — 지어내지 않는다. */\nfunction rungOf(names: readonly string[] | undefined): Rung | null {\n const has = new Set(names ?? [])\n if (has.size === 0) return 'none'\n\n for (const [rung, wanted] of Object.entries(RUNGS) as [Rung, readonly string[]][]) {\n if (wanted.length === has.size && wanted.every(name => has.has(name))) return rung\n }\n return null\n}\n\n@customElement('figure-capabilities')\nexport class FigureCapabilities extends localize(i18next)(LitElement) {\n static styles = [\n ScrollbarStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n min-height: 0;\n overflow-y: auto;\n background-color: var(--md-sys-color-surface-container-lowest);\n color: var(--md-sys-color-on-surface);\n font: var(--label-font, inherit);\n font-size: 0.76rem;\n }\n\n section {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-medium, 8px);\n padding: var(--spacing-large, 12px);\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n }\n\n /* 다른 탭들과 같은 눈금이다 — 탭마다 다른 제목 규격을 쓰면 따로 논다 */\n h3 {\n margin: 0 0 2px 0;\n font-size: var(--ui-t-micro, 11px);\n font-weight: 700;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n\n p[quiet] {\n margin: 0;\n font-size: 0.72rem;\n line-height: 1.5;\n color: var(--md-sys-color-on-surface-variant);\n }\n\n /* 사다리 — 고르는 것이 하나라 라디오다 */\n label[rung] {\n display: flex;\n align-items: flex-start;\n gap: var(--spacing-medium, 8px);\n padding: 6px 8px;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 4px;\n cursor: pointer;\n }\n label[rung][on] {\n border-color: var(--md-sys-color-primary);\n background-color: var(--md-sys-color-surface-container);\n }\n label[rung]:hover:not([on]) {\n background-color: var(--md-sys-color-surface-container-low);\n }\n label[rung] span {\n display: flex;\n flex-direction: column;\n gap: 1px;\n }\n label[rung] strong {\n font-weight: 600;\n }\n label[rung] em {\n font-style: normal;\n font-size: 0.7rem;\n line-height: 1.4;\n color: var(--md-sys-color-on-surface-variant);\n }\n\n input[type='radio'] {\n flex: none;\n margin: 2px 0 0 0;\n accent-color: var(--md-sys-color-primary);\n cursor: pointer;\n }\n\n /* 실제로 씬에 붙는 이름. 고른 뒤에 보여 준다 */\n code[names] {\n font: var(--mono-font, ui-monospace, monospace);\n font-size: 0.7rem;\n color: var(--md-sys-color-on-surface-variant);\n }\n\n /* 지정된 부품 한 줄 — 읽는 것이지 고치는 것이 아니다. 눌러 그 부품으로 간다 */\n button[marked] {\n display: flex;\n align-items: baseline;\n gap: var(--spacing-medium, 8px);\n width: 100%;\n padding: 5px 8px;\n text-align: left;\n font: inherit;\n color: inherit;\n background: none;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 4px;\n cursor: pointer;\n }\n button[marked]:hover {\n background-color: var(--md-sys-color-surface-container);\n }\n button[marked] b {\n flex: none;\n font-weight: 600;\n }\n button[marked] span {\n flex: none;\n font-size: 0.72rem;\n color: var(--md-sys-color-primary);\n }\n button[marked] em {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n text-align: right;\n font-style: normal;\n font-size: 0.7rem;\n color: var(--md-sys-color-on-surface-variant);\n }\n\n /* 부품마다 한 줄. 자리를 고른 것만 아래로 펼친다 */\n div[part] {\n display: flex;\n flex-direction: column;\n gap: 4px;\n padding: 6px 0;\n border-top: 1px solid var(--md-sys-color-outline-variant);\n }\n div[part]:first-of-type {\n border-top: none;\n }\n div[head] {\n display: flex;\n align-items: center;\n gap: var(--spacing-medium, 8px);\n }\n div[head] b {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-weight: 600;\n }\n\n select,\n input[type='text'],\n input[type='number'] {\n padding: 4px 6px;\n font: var(--input-field-font, inherit);\n font-size: 0.74rem;\n color: var(--md-sys-color-on-surface);\n background-color: var(--md-sys-color-surface);\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 4px;\n }\n /* 이름이 길어질 수 있는 쪽(문)까지 안 잘리는 폭이다 */\n div[head] select {\n flex: none;\n width: 8.5em;\n }\n\n div[detail] {\n display: grid;\n grid-template-columns: 4.5em 1fr;\n align-items: center;\n gap: 4px var(--spacing-medium, 8px);\n padding-left: var(--spacing-medium, 8px);\n }\n div[detail] > span {\n font-size: 0.72rem;\n color: var(--md-sys-color-on-surface-variant);\n }\n div[detail] input {\n width: 100%;\n box-sizing: border-box;\n }\n div[detail] em {\n grid-column: 2;\n font-style: normal;\n font-size: 0.7rem;\n color: var(--md-sys-color-on-surface-variant);\n }\n `\n ]\n\n @property({ type: Object }) board?: BoardModel\n @property({ type: Array }) parts: PartModel[] = []\n\n render() {\n const rung = rungOf(this.board?.capabilities)\n const names = this.board?.capabilities ?? []\n\n return html`\n <section>\n <h3>${i18next.t('figure.label.what-this-does')}</h3>\n\n ${this.rung('none', rung)} ${this.rung('hold', rung)} ${this.rung('flow', rung)}\n ${rung === null\n ? html`<p quiet>${i18next.t('figure.text.capability-hand-written')}</p>`\n : ''}\n ${names.length > 0 ? html`<code names>${names.join(' · ')}</code>` : ''}\n </section>\n\n ${this.renderAnchors(rung)}\n `\n }\n\n /**\n * 자리와 문 — **여기서는 읽기만 한다.**\n *\n * 지정은 속성 탭에서 한다. 부품을 고치는 곳이 한 군데여야 하기 때문이다 — 목록을 여기 또\n * 두면 같은 부품을 두 화면에서 고치게 되고, 저작자는 어느 쪽이 이기는지 매번 생각해야 한다.\n *\n * 그래도 요약은 남긴다. 「어느 부품이 자리인가」는 부품을 하나씩 눌러 보며 알아낼 일이\n * 아니다 — 자리는 그려지지 않으므로 화면을 봐서는 영영 못 찾는다.\n */\n private renderAnchors(rung: Rung | null) {\n if (rung === 'none') {\n return html`\n <section>\n <h3>${i18next.t('figure.label.slots-and-ports')}</h3>\n <p quiet>${i18next.t('figure.text.slots-need-capability')}</p>\n </section>\n `\n }\n\n const marked = this.parts.filter(part => part.capability)\n\n return html`\n <section>\n <h3>${i18next.t('figure.label.slots-and-ports')}</h3>\n <p quiet>${i18next.t('figure.text.anchors-are-not-drawn')}</p>\n\n ${marked.length === 0\n ? html`<p quiet>${i18next.t('figure.text.designate-in-properties')}</p>`\n : marked.map(part => this.renderMarked(part))}\n </section>\n `\n }\n\n /** 지정된 부품 한 줄. 누르면 그 부품이 골라진다 — 고치는 자리는 속성 탭이다. */\n private renderMarked(part: PartModel) {\n const spec = part.capability!\n\n return html`\n <button marked @click=${() => this.pick(part)}>\n <b>${part.name}</b>\n <span>${spec.roles.map(role => i18next.t('figure.label.role-' + role)).join(' · ')}</span>\n ${spec.accepts?.length ? html`<em>${spec.accepts.join(' · ')}</em>` : ''}\n </button>\n `\n }\n\n /** 이 부품을 고르게 한다. 부품 목록에서 누른 것과 같은 알림이다. */\n private pick(part: PartModel) {\n const index = this.parts.indexOf(part)\n if (index < 0) return\n\n this.dispatchEvent(new CustomEvent('select-part', { detail: { index }, bubbles: true, composed: true }))\n }\n\n /**\n * 사다리 한 칸.\n *\n * `checked` 를 `live()` 로 묶는다. 그냥 두면 Lit 이 **마지막으로 그린 값**과 견주므로,\n * 브라우저가 DOM 을 바꿨는데 모델은 안 바뀐 경우 되돌리지 않는다. 실제로 그랬다 — 고르는\n * 도중 도형이 다시 실렸더니 점은 「흐른다」에 있고 강조 상자는 「그리기만 한다」에 있었다.\n * 화면이 모델과 다른 말을 하는 상태다. `live()` 는 **지금 DOM 의 값**과 견준다.\n */\n private rung(which: Rung, chosen: Rung | null) {\n return html`\n <label rung ?on=${chosen === which}>\n <input\n type=\"radio\"\n name=\"rung\"\n .checked=${live(chosen === which)}\n @change=${() => this.setRung(which)}\n />\n <span>\n <strong>${i18next.t(`figure.label.rung-${which}`)}</strong>\n <em>${i18next.t(`figure.text.rung-${which}-note`)}</em>\n </span>\n </label>\n `\n }\n\n /**\n * 사다리를 내리면 자리도 함께 거둔다.\n *\n * 능력을 「그리기만 한다」로 되돌려 놓고 부품에 자리 표시가 남아 있으면, 형식은 자리를\n * 그대로 실어 보내는데 씬은 그것을 쓸 클래스가 없다. **선언은 있고 길이 없는** 상태가\n * 되고, 그 도형은 멀쩡해 보이면서 아무것도 못 받는다.\n */\n private setRung(which: Rung) {\n const board = this.board\n if (!board) return\n\n this.tellBoard({ ...board, capabilities: RUNGS[which].length > 0 ? [...RUNGS[which]] : undefined })\n\n if (which === 'none') {\n this.parts.forEach((part, index) => {\n if (!part.capability) return\n const { capability, ...rest } = part\n this.dispatchEvent(\n new CustomEvent('update-part', { detail: { index, part: rest }, bubbles: true, composed: true })\n )\n })\n }\n }\n\n private tellBoard(board: BoardModel) {\n this.board = board\n this.dispatchEvent(new CustomEvent('update-board', { detail: { board }, bubbles: true, composed: true }))\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"figure-capabilities.js","sourceRoot":"","sources":["../../client/modeller/figure-capabilities.ts"],"names":[],"mappings":"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","sourcesContent":["import { css, html, LitElement } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\nimport { live } from 'lit/directives/live.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport { ScrollbarStyles } from '@operato/styles'\n\nimport type { FigureDraft, PartModel } from './figure-source.js'\n\n/**\n * 이 도형이 **무엇을 하는가.**\n *\n * ## 왜 탭 하나가 필요한가\n *\n * 지금까지 저작한 것은 **생김새**뿐이었다. 컨베이어를 아무리 잘 그려도 보드에 놓으면\n * 상자 하나를 못 받는다 — 물건을 받고 담고 흘려보내는 것은 씬의 capability 가 하는 일이고,\n * 손으로 쓴 컴포넌트는 클래스 선언 한 줄로 그것을 졌다. 저작한 도형에는 그 줄을 쓸 자리가\n * 없어서, 여기서 고른 것이 그 줄이 된다.\n *\n * ## 사다리로 묻는다\n *\n * 능력의 이름(`Capacity` · `Holdable` · `Transferable` · `FlowNode`)을 그대로 늘어놓고\n * 고르라고 하면, 저작자는 넷의 관계를 먼저 배워야 한다. 실제로 물어야 하는 것은 하나다 —\n * **이 도형이 물건을 다루나, 다룬다면 지나가나.** 그래서 세 칸의 사다리로 묻고, 이름은\n * 고른 뒤에 보여 준다.\n *\n * 윗 칸이 아랫 칸을 포함한다(`FlowNode` 가 나머지 셋을 스스로 흡수한다). 그래서 사다리이지\n * 체크상자가 아니다.\n */\n\n/** 사다리 한 칸 — 화면이 묻는 말과, 그것이 되는 능력 이름. */\ntype Rung = 'none' | 'hold' | 'flow'\n\nconst RUNGS: Readonly<Record<Rung, readonly string[]>> = Object.freeze({\n none: [],\n hold: ['Capacity', 'Holdable'],\n flow: ['FlowNode']\n})\n\n/** 선언된 이름 집합이 사다리 어느 칸인가. 어느 칸도 아니면 `null` — 지어내지 않는다. */\nfunction rungOf(names: readonly string[] | undefined): Rung | null {\n const has = new Set(names ?? [])\n if (has.size === 0) return 'none'\n\n for (const [rung, wanted] of Object.entries(RUNGS) as [Rung, readonly string[]][]) {\n if (wanted.length === has.size && wanted.every(name => has.has(name))) return rung\n }\n return null\n}\n\n@customElement('figure-capabilities')\nexport class FigureCapabilities extends localize(i18next)(LitElement) {\n static styles = [\n ScrollbarStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n min-height: 0;\n overflow-y: auto;\n background-color: var(--md-sys-color-surface-container-lowest);\n color: var(--md-sys-color-on-surface);\n font: var(--label-font, inherit);\n font-size: 0.76rem;\n }\n\n section {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-medium, 8px);\n padding: var(--spacing-large, 12px);\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n }\n\n /* 다른 탭들과 같은 눈금이다 — 탭마다 다른 제목 규격을 쓰면 따로 논다 */\n h3 {\n margin: 0 0 2px 0;\n font-size: var(--ui-t-micro, 11px);\n font-weight: 700;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n\n p[quiet] {\n margin: 0;\n font-size: 0.72rem;\n line-height: 1.5;\n color: var(--md-sys-color-on-surface-variant);\n }\n\n /* 사다리 — 고르는 것이 하나라 라디오다 */\n label[rung] {\n display: flex;\n align-items: flex-start;\n gap: var(--spacing-medium, 8px);\n padding: 6px 8px;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 4px;\n cursor: pointer;\n }\n label[rung][on] {\n border-color: var(--md-sys-color-primary);\n background-color: var(--md-sys-color-surface-container);\n }\n label[rung]:hover:not([on]) {\n background-color: var(--md-sys-color-surface-container-low);\n }\n label[rung] span {\n display: flex;\n flex-direction: column;\n gap: 1px;\n }\n label[rung] strong {\n font-weight: 600;\n }\n label[rung] em {\n font-style: normal;\n font-size: 0.7rem;\n line-height: 1.4;\n color: var(--md-sys-color-on-surface-variant);\n }\n\n input[type='radio'] {\n flex: none;\n margin: 2px 0 0 0;\n accent-color: var(--md-sys-color-primary);\n cursor: pointer;\n }\n\n /* 실제로 씬에 붙는 이름. 고른 뒤에 보여 준다 */\n code[names] {\n font: var(--mono-font, ui-monospace, monospace);\n font-size: 0.7rem;\n color: var(--md-sys-color-on-surface-variant);\n }\n\n /* 지정된 부품 한 줄 — 읽는 것이지 고치는 것이 아니다. 눌러 그 부품으로 간다 */\n button[marked] {\n display: flex;\n align-items: baseline;\n gap: var(--spacing-medium, 8px);\n width: 100%;\n padding: 5px 8px;\n text-align: left;\n font: inherit;\n color: inherit;\n background: none;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 4px;\n cursor: pointer;\n }\n button[marked]:hover {\n background-color: var(--md-sys-color-surface-container);\n }\n button[marked] b {\n flex: none;\n font-weight: 600;\n }\n button[marked] span {\n flex: none;\n font-size: 0.72rem;\n color: var(--md-sys-color-primary);\n }\n button[marked] em {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n text-align: right;\n font-style: normal;\n font-size: 0.7rem;\n color: var(--md-sys-color-on-surface-variant);\n }\n\n /* 부품마다 한 줄. 자리를 고른 것만 아래로 펼친다 */\n div[part] {\n display: flex;\n flex-direction: column;\n gap: 4px;\n padding: 6px 0;\n border-top: 1px solid var(--md-sys-color-outline-variant);\n }\n div[part]:first-of-type {\n border-top: none;\n }\n div[head] {\n display: flex;\n align-items: center;\n gap: var(--spacing-medium, 8px);\n }\n div[head] b {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-weight: 600;\n }\n\n select,\n input[type='text'],\n input[type='number'] {\n padding: 4px 6px;\n font: var(--input-field-font, inherit);\n font-size: 0.74rem;\n color: var(--md-sys-color-on-surface);\n background-color: var(--md-sys-color-surface);\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 4px;\n }\n /* 이름이 길어질 수 있는 쪽(문)까지 안 잘리는 폭이다 */\n div[head] select {\n flex: none;\n width: 8.5em;\n }\n\n div[detail] {\n display: grid;\n grid-template-columns: 4.5em 1fr;\n align-items: center;\n gap: 4px var(--spacing-medium, 8px);\n padding-left: var(--spacing-medium, 8px);\n }\n div[detail] > span {\n font-size: 0.72rem;\n color: var(--md-sys-color-on-surface-variant);\n }\n div[detail] input {\n width: 100%;\n box-sizing: border-box;\n }\n div[detail] em {\n grid-column: 2;\n font-style: normal;\n font-size: 0.7rem;\n color: var(--md-sys-color-on-surface-variant);\n }\n `\n ]\n\n @property({ type: Object }) draft?: FigureDraft\n @property({ type: Array }) parts: PartModel[] = []\n\n render() {\n const rung = rungOf(this.draft?.capabilities)\n const names = this.draft?.capabilities ?? []\n\n return html`\n <section>\n <h3>${i18next.t('figure.label.what-this-does')}</h3>\n\n ${this.rung('none', rung)} ${this.rung('hold', rung)} ${this.rung('flow', rung)}\n ${rung === null\n ? html`<p quiet>${i18next.t('figure.text.capability-hand-written')}</p>`\n : ''}\n ${names.length > 0 ? html`<code names>${names.join(' · ')}</code>` : ''}\n </section>\n\n ${this.renderAnchors(rung)}\n `\n }\n\n /**\n * 자리와 문 — **여기서는 읽기만 한다.**\n *\n * 지정은 속성 탭에서 한다. 부품을 고치는 곳이 한 군데여야 하기 때문이다 — 목록을 여기 또\n * 두면 같은 부품을 두 화면에서 고치게 되고, 저작자는 어느 쪽이 이기는지 매번 생각해야 한다.\n *\n * 그래도 요약은 남긴다. 「어느 부품이 자리인가」는 부품을 하나씩 눌러 보며 알아낼 일이\n * 아니다 — 자리는 그려지지 않으므로 화면을 봐서는 영영 못 찾는다.\n */\n private renderAnchors(rung: Rung | null) {\n if (rung === 'none') {\n return html`\n <section>\n <h3>${i18next.t('figure.label.slots-and-ports')}</h3>\n <p quiet>${i18next.t('figure.text.slots-need-capability')}</p>\n </section>\n `\n }\n\n const marked = this.parts.filter(part => part.capability)\n\n return html`\n <section>\n <h3>${i18next.t('figure.label.slots-and-ports')}</h3>\n <p quiet>${i18next.t('figure.text.anchors-are-not-drawn')}</p>\n\n ${marked.length === 0\n ? html`<p quiet>${i18next.t('figure.text.designate-in-properties')}</p>`\n : marked.map(part => this.renderMarked(part))}\n </section>\n `\n }\n\n /** 지정된 부품 한 줄. 누르면 그 부품이 골라진다 — 고치는 자리는 속성 탭이다. */\n private renderMarked(part: PartModel) {\n const spec = part.capability!\n\n return html`\n <button marked @click=${() => this.pick(part)}>\n <b>${part.name}</b>\n <span>${spec.roles.map(role => i18next.t('figure.label.role-' + role)).join(' · ')}</span>\n ${spec.accepts?.length ? html`<em>${spec.accepts.join(' · ')}</em>` : ''}\n </button>\n `\n }\n\n /** 이 부품을 고르게 한다. 부품 목록에서 누른 것과 같은 알림이다. */\n private pick(part: PartModel) {\n const index = this.parts.indexOf(part)\n if (index < 0) return\n\n this.dispatchEvent(new CustomEvent('select-part', { detail: { index }, bubbles: true, composed: true }))\n }\n\n /**\n * 사다리 한 칸.\n *\n * `checked` 를 `live()` 로 묶는다. 그냥 두면 Lit 이 **마지막으로 그린 값**과 견주므로,\n * 브라우저가 DOM 을 바꿨는데 모델은 안 바뀐 경우 되돌리지 않는다. 실제로 그랬다 — 고르는\n * 도중 도형이 다시 실렸더니 점은 「흐른다」에 있고 강조 상자는 「그리기만 한다」에 있었다.\n * 화면이 모델과 다른 말을 하는 상태다. `live()` 는 **지금 DOM 의 값**과 견준다.\n */\n private rung(which: Rung, chosen: Rung | null) {\n return html`\n <label rung ?on=${chosen === which}>\n <input\n type=\"radio\"\n name=\"rung\"\n .checked=${live(chosen === which)}\n @change=${() => this.setRung(which)}\n />\n <span>\n <strong>${i18next.t(`figure.label.rung-${which}`)}</strong>\n <em>${i18next.t(`figure.text.rung-${which}-note`)}</em>\n </span>\n </label>\n `\n }\n\n /**\n * 사다리를 내리면 자리도 함께 거둔다.\n *\n * 능력을 「그리기만 한다」로 되돌려 놓고 부품에 자리 표시가 남아 있으면, 형식은 자리를\n * 그대로 실어 보내는데 씬은 그것을 쓸 클래스가 없다. **선언은 있고 길이 없는** 상태가\n * 되고, 그 도형은 멀쩡해 보이면서 아무것도 못 받는다.\n */\n private setRung(which: Rung) {\n const draft = this.draft\n if (!draft) return\n\n this.tellBoard({ ...draft, capabilities: RUNGS[which].length > 0 ? [...RUNGS[which]] : undefined })\n\n if (which === 'none') {\n this.parts.forEach((part, index) => {\n if (!part.capability) return\n const { capability, ...rest } = part\n this.dispatchEvent(\n new CustomEvent('update-part', { detail: { index, part: rest }, bubbles: true, composed: true })\n )\n })\n }\n }\n\n private tellBoard(draft: FigureDraft) {\n this.draft = draft\n this.dispatchEvent(new CustomEvent('update-draft', { detail: { draft }, bubbles: true, composed: true }))\n }\n}\n"]}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import '@material/web/icon/icon.js';
|
|
2
2
|
import { LitElement } from 'lit';
|
|
3
|
-
import type {
|
|
3
|
+
import type { FigureDraft, PartModel } from './figure-source.js';
|
|
4
4
|
declare const FigureInspector_base: (new (...args: any[]) => LitElement) & typeof LitElement;
|
|
5
5
|
/**
|
|
6
6
|
* 고른 부품의 속성.
|
|
7
7
|
*
|
|
8
8
|
* ## 여기가 사용자 용어로 보여 주는 자리다
|
|
9
9
|
*
|
|
10
|
-
* 모델러 페이지가 갖고 있는 것은 **씬 모델**이다 —
|
|
10
|
+
* 모델러 페이지가 갖고 있는 것은 **씬 모델**이다 — 기준 상자 위에 부품 컴포넌트가 놓인 상태.
|
|
11
11
|
* 그런데 사용자에게 보여야 하는 것은 씬 속성 이름(`left`·`top`·`zPos`)이 아니라
|
|
12
12
|
* **X·Y·Z** 다. 다른 3D 도구가 모두 그렇게 부르고, 위치도 부품의 **중심**으로 읽는다.
|
|
13
13
|
*
|
|
@@ -54,8 +54,8 @@ export declare class FigureInspector extends FigureInspector_base {
|
|
|
54
54
|
figureTags?: string[];
|
|
55
55
|
/** 펴 둔 도움말. 부품을 바꿔도 유지된다 — 매번 다시 펴게 하지 않는다. */
|
|
56
56
|
private opened;
|
|
57
|
-
|
|
58
|
-
/**
|
|
57
|
+
draft?: FigureDraft;
|
|
58
|
+
/** 초안 위의 부품들. 씬 모델 그대로다. */
|
|
59
59
|
parts: PartModel[];
|
|
60
60
|
selected: number;
|
|
61
61
|
/** `part` 는 HTMLElement 의 속성(DOMTokenList)이다 — 가리면 형이 어긋난다. */
|
|
@@ -108,7 +108,18 @@ export declare class FigureInspector extends FigureInspector_base {
|
|
|
108
108
|
private tellTags;
|
|
109
109
|
/** 카탈로그 값이 바뀌었다고 알린다. 정본은 저작면이 든다 — 여기서는 알리기만 한다. */
|
|
110
110
|
private tellCatalog;
|
|
111
|
-
/**
|
|
111
|
+
/**
|
|
112
|
+
* 3D 배치 기준면을 바꾼다 ('floor' | 'ceiling' | 'center').
|
|
113
|
+
*
|
|
114
|
+
* **부품은 건드리지 않는다.** 이 값은 「이 종류는 어느 면에 붙는가」를 말할 뿐이고,
|
|
115
|
+
* 저작한 모양은 그것과 무관하다. 형식이 부품을 상자 중심에서 재므로, 배치가 달라도
|
|
116
|
+
* `parts` 는 한 글자도 안 바뀐다.
|
|
117
|
+
*
|
|
118
|
+
* 한동안 여기서 `zPos` 를 다시 계산했다. 편집 캔버스가 이 값을 씬의 좌표 모드로
|
|
119
|
+
* 흘려보내던 때의 일인데, 그러면 배치를 고를 때마다 저작자가 적어 둔 높이가 전부
|
|
120
|
+
* 다른 숫자로 보였다 — 저장되는 것은 그대로인데. 좌표 모드는 이제 보기 설정이고
|
|
121
|
+
* (`figure-view.ts`), 편집 캔버스는 늘 상자 기준이다.
|
|
122
|
+
*/
|
|
112
123
|
private setPlacement;
|
|
113
124
|
private renderIdentity;
|
|
114
125
|
private renderPlacement;
|
|
@@ -15,7 +15,7 @@ import { canMirror } from './part-edits.js';
|
|
|
15
15
|
*
|
|
16
16
|
* ## 여기가 사용자 용어로 보여 주는 자리다
|
|
17
17
|
*
|
|
18
|
-
* 모델러 페이지가 갖고 있는 것은 **씬 모델**이다 —
|
|
18
|
+
* 모델러 페이지가 갖고 있는 것은 **씬 모델**이다 — 기준 상자 위에 부품 컴포넌트가 놓인 상태.
|
|
19
19
|
* 그런데 사용자에게 보여야 하는 것은 씬 속성 이름(`left`·`top`·`zPos`)이 아니라
|
|
20
20
|
* **X·Y·Z** 다. 다른 3D 도구가 모두 그렇게 부르고, 위치도 부품의 **중심**으로 읽는다.
|
|
21
21
|
*
|
|
@@ -59,7 +59,7 @@ let FigureInspector = class FigureInspector extends localize(i18next)(LitElement
|
|
|
59
59
|
this.figureName = '';
|
|
60
60
|
/** 펴 둔 도움말. 부품을 바꿔도 유지된다 — 매번 다시 펴게 하지 않는다. */
|
|
61
61
|
this.opened = new Set();
|
|
62
|
-
/**
|
|
62
|
+
/** 초안 위의 부품들. 씬 모델 그대로다. */
|
|
63
63
|
this.parts = [];
|
|
64
64
|
this.selected = -1;
|
|
65
65
|
}
|
|
@@ -777,7 +777,7 @@ let FigureInspector = class FigureInspector extends localize(i18next)(LitElement
|
|
|
777
777
|
}
|
|
778
778
|
render() {
|
|
779
779
|
const part = this.chosen;
|
|
780
|
-
if (!part || !this.
|
|
780
|
+
if (!part || !this.draft) {
|
|
781
781
|
return html `
|
|
782
782
|
${this.renderFigure(false)}
|
|
783
783
|
<section>
|
|
@@ -787,7 +787,7 @@ let FigureInspector = class FigureInspector extends localize(i18next)(LitElement
|
|
|
787
787
|
`;
|
|
788
788
|
}
|
|
789
789
|
// 여기부터는 사용자 용어다 — X·Y·Z, 위치는 부품 중심, 회전은 도(degree).
|
|
790
|
-
const shown = partToFigure(part, this.
|
|
790
|
+
const shown = partToFigure(part, this.draft);
|
|
791
791
|
/*
|
|
792
792
|
역할을 먼저 묻는다.
|
|
793
793
|
|
|
@@ -812,7 +812,7 @@ let FigureInspector = class FigureInspector extends localize(i18next)(LitElement
|
|
|
812
812
|
* 컨베이어가 길어지면 실을 자리가 늘어나는 것이 그것이고, 여기 적은 값은 안 쓰인다.
|
|
813
813
|
*/
|
|
814
814
|
renderRole(part) {
|
|
815
|
-
if (!this.
|
|
815
|
+
if (!this.draft?.capabilities?.length)
|
|
816
816
|
return nothing;
|
|
817
817
|
const roles = part.capability?.roles ?? [];
|
|
818
818
|
const repeats = part.sizing === 'repeat' && !!part.repeat;
|
|
@@ -934,7 +934,7 @@ let FigureInspector = class FigureInspector extends localize(i18next)(LitElement
|
|
|
934
934
|
<label>${i18next.t('figure.label.figure-type')}${this.figureId ? this.helpButton('figure.text.type-name-cannot-change') : nothing}</label>
|
|
935
935
|
${!this.figureId
|
|
936
936
|
? html `<input
|
|
937
|
-
.value=${this.
|
|
937
|
+
.value=${this.draft?.figureType ?? ''}
|
|
938
938
|
placeholder=${i18next.t('figure.text.figure-type-placeholder', { defaultValue: '도형 식별 타입 (예: OHT)' })}
|
|
939
939
|
@change=${(e) => this.dispatchEvent(new CustomEvent('figure-type-change', {
|
|
940
940
|
detail: { type: e.target.value.trim().toUpperCase() },
|
|
@@ -943,7 +943,7 @@ let FigureInspector = class FigureInspector extends localize(i18next)(LitElement
|
|
|
943
943
|
}))}
|
|
944
944
|
/>`
|
|
945
945
|
: html `<code fixed title=${i18next.t('figure.text.type-name-cannot-change')}>
|
|
946
|
-
<md-icon>lock</md-icon>${this.
|
|
946
|
+
<md-icon>lock</md-icon>${this.draft?.figureType ?? ''}
|
|
947
947
|
</code>
|
|
948
948
|
${this.helpHint('figure.text.type-name-cannot-change')}`}
|
|
949
949
|
</div>
|
|
@@ -955,16 +955,16 @@ let FigureInspector = class FigureInspector extends localize(i18next)(LitElement
|
|
|
955
955
|
<div inline-field>
|
|
956
956
|
<label>${i18next.t('figure.label.figure-placement')}${this.helpButton('figure.text.placement-explained')}</label>
|
|
957
957
|
<select
|
|
958
|
-
.value=${this.
|
|
958
|
+
.value=${this.draft?.placement ?? 'floor'}
|
|
959
959
|
@change=${(e) => this.setPlacement(e.target.value)}
|
|
960
960
|
>
|
|
961
|
-
<option value="floor" ?selected=${(this.
|
|
961
|
+
<option value="floor" ?selected=${(this.draft?.placement ?? 'floor') === 'floor'}>
|
|
962
962
|
${i18next.t('figure.label.placement-floor')}
|
|
963
963
|
</option>
|
|
964
|
-
<option value="ceiling" ?selected=${this.
|
|
964
|
+
<option value="ceiling" ?selected=${this.draft?.placement === 'ceiling'}>
|
|
965
965
|
${i18next.t('figure.label.placement-ceiling')}
|
|
966
966
|
</option>
|
|
967
|
-
<option value="center" ?selected=${this.
|
|
967
|
+
<option value="center" ?selected=${this.draft?.placement === 'center'}>
|
|
968
968
|
${i18next.t('figure.label.placement-center')}
|
|
969
969
|
</option>
|
|
970
970
|
</select>
|
|
@@ -997,7 +997,7 @@ let FigureInspector = class FigureInspector extends localize(i18next)(LitElement
|
|
|
997
997
|
<details asset>
|
|
998
998
|
<summary>
|
|
999
999
|
<b>${i18next.t('figure.label.figure')}</b>
|
|
1000
|
-
<small>${this.figureName || this.
|
|
1000
|
+
<small>${this.figureName || this.draft?.figureType || ''}</small>
|
|
1001
1001
|
</summary>
|
|
1002
1002
|
${fields}
|
|
1003
1003
|
</details>
|
|
@@ -1031,12 +1031,23 @@ let FigureInspector = class FigureInspector extends localize(i18next)(LitElement
|
|
|
1031
1031
|
tellCatalog(patch) {
|
|
1032
1032
|
this.dispatchEvent(new CustomEvent('figure-catalog', { detail: patch, bubbles: true, composed: true }));
|
|
1033
1033
|
}
|
|
1034
|
-
/**
|
|
1034
|
+
/**
|
|
1035
|
+
* 3D 배치 기준면을 바꾼다 ('floor' | 'ceiling' | 'center').
|
|
1036
|
+
*
|
|
1037
|
+
* **부품은 건드리지 않는다.** 이 값은 「이 종류는 어느 면에 붙는가」를 말할 뿐이고,
|
|
1038
|
+
* 저작한 모양은 그것과 무관하다. 형식이 부품을 상자 중심에서 재므로, 배치가 달라도
|
|
1039
|
+
* `parts` 는 한 글자도 안 바뀐다.
|
|
1040
|
+
*
|
|
1041
|
+
* 한동안 여기서 `zPos` 를 다시 계산했다. 편집 캔버스가 이 값을 씬의 좌표 모드로
|
|
1042
|
+
* 흘려보내던 때의 일인데, 그러면 배치를 고를 때마다 저작자가 적어 둔 높이가 전부
|
|
1043
|
+
* 다른 숫자로 보였다 — 저장되는 것은 그대로인데. 좌표 모드는 이제 보기 설정이고
|
|
1044
|
+
* (`figure-view.ts`), 편집 캔버스는 늘 상자 기준이다.
|
|
1045
|
+
*/
|
|
1035
1046
|
setPlacement(placement) {
|
|
1036
|
-
if (!this.
|
|
1047
|
+
if (!this.draft)
|
|
1037
1048
|
return;
|
|
1038
|
-
const
|
|
1039
|
-
this.dispatchEvent(new CustomEvent('update-
|
|
1049
|
+
const draft = { ...this.draft, placement };
|
|
1050
|
+
this.dispatchEvent(new CustomEvent('update-draft', { detail: { draft }, bubbles: true, composed: true }));
|
|
1040
1051
|
}
|
|
1041
1052
|
renderIdentity(part) {
|
|
1042
1053
|
return html `
|
|
@@ -1117,7 +1128,7 @@ let FigureInspector = class FigureInspector extends localize(i18next)(LitElement
|
|
|
1117
1128
|
</button>
|
|
1118
1129
|
${['x', 'z'].map(axis => {
|
|
1119
1130
|
// 이미 가운데면 대칭이 제자리다 — 겹친 부품이 조용히 생기지 않게 막는다
|
|
1120
|
-
const able = this.
|
|
1131
|
+
const able = this.draft ? canMirror(this.draft, this.parts, this.selected, axis) : false;
|
|
1121
1132
|
return html `
|
|
1122
1133
|
<button
|
|
1123
1134
|
?disabled=${!able}
|
|
@@ -1505,10 +1516,10 @@ let FigureInspector = class FigureInspector extends localize(i18next)(LitElement
|
|
|
1505
1516
|
* 이 축의 anchor 가 정하는데 화면에는 그 말이 없었다. 그래서 어느 쪽인지 함께 적는다.
|
|
1506
1517
|
*/
|
|
1507
1518
|
renderRepeatCount(part) {
|
|
1508
|
-
const
|
|
1509
|
-
if (!
|
|
1519
|
+
const draft = this.draft;
|
|
1520
|
+
if (!draft)
|
|
1510
1521
|
return nothing;
|
|
1511
|
-
const base = { x:
|
|
1522
|
+
const base = { x: draft.width, y: draft.depth, z: draft.height };
|
|
1512
1523
|
const plan = repeatPlanOfPart(part, { x: 1, y: 1, z: 1 }, base);
|
|
1513
1524
|
if (!plan)
|
|
1514
1525
|
return nothing;
|
|
@@ -1589,8 +1600,8 @@ let FigureInspector = class FigureInspector extends localize(i18next)(LitElement
|
|
|
1589
1600
|
return this.opened.has(key) ? html `<p hint>${i18next.t(key)}</p>` : nothing;
|
|
1590
1601
|
}
|
|
1591
1602
|
renderAnchors(part) {
|
|
1592
|
-
const base = this.
|
|
1593
|
-
? { x: this.
|
|
1603
|
+
const base = this.draft
|
|
1604
|
+
? { x: this.draft.width, y: this.draft.height, z: this.draft.depth }
|
|
1594
1605
|
: { x: 0, y: 0, z: 0 };
|
|
1595
1606
|
const nameOf = (rule, axis) => `${i18next.t(rule === 'min' || rule === 'max' ? `figure.label.anchor-${rule}-${axis}` : `figure.label.anchor-${rule}`)} (${rule})`;
|
|
1596
1607
|
const setAnchor = (axis, rule) => {
|
|
@@ -1736,7 +1747,7 @@ let FigureInspector = class FigureInspector extends localize(i18next)(LitElement
|
|
|
1736
1747
|
*/
|
|
1737
1748
|
putEmissive(intensity) {
|
|
1738
1749
|
// `chosen` 은 씬 모델이다. 형식으로 옮긴 값은 `partToFigure` 이 만든 것이라 여기서 다시 꺼낸다.
|
|
1739
|
-
const glow = this.
|
|
1750
|
+
const glow = this.draft && this.chosen ? partToFigure(this.chosen, this.draft).material?.emissive : undefined;
|
|
1740
1751
|
this.putMaterial({ emissive: intensity > 0 ? { ...glow, intensity } : undefined });
|
|
1741
1752
|
}
|
|
1742
1753
|
putMaterial(patch) {
|
|
@@ -1754,10 +1765,10 @@ let FigureInspector = class FigureInspector extends localize(i18next)(LitElement
|
|
|
1754
1765
|
*/
|
|
1755
1766
|
change(edit) {
|
|
1756
1767
|
const part = this.chosen;
|
|
1757
|
-
const
|
|
1758
|
-
if (!part || !
|
|
1768
|
+
const draft = this.draft;
|
|
1769
|
+
if (!part || !draft)
|
|
1759
1770
|
return;
|
|
1760
|
-
this.emit(partFromFigure(edit(partToFigure(part,
|
|
1771
|
+
this.emit(partFromFigure(edit(partToFigure(part, draft)), draft));
|
|
1761
1772
|
}
|
|
1762
1773
|
/** 정밀 도구를 시킨다. 정본은 저작면이 갖는다 — 여기서는 알리기만 한다. */
|
|
1763
1774
|
ask(what, axis) {
|
|
@@ -1794,7 +1805,7 @@ __decorate([
|
|
|
1794
1805
|
__decorate([
|
|
1795
1806
|
property({ type: Object }),
|
|
1796
1807
|
__metadata("design:type", Object)
|
|
1797
|
-
], FigureInspector.prototype, "
|
|
1808
|
+
], FigureInspector.prototype, "draft", void 0);
|
|
1798
1809
|
__decorate([
|
|
1799
1810
|
property({ type: Array }),
|
|
1800
1811
|
__metadata("design:type", Array)
|