@things-factory/figure-ui 10.1.178 → 10.1.179
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/figure-released.ts +6 -0
- package/client/graphql/index.ts +101 -25
- package/client/graphql/store.ts +2 -1
- package/client/index.ts +1 -0
- package/client/modeller/editor.ts +10 -2
- package/client/modeller/figure-first-save.ts +23 -3
- package/client/modeller/figure-parts.ts +53 -23
- package/client/modeller/figure-properties.ts +41 -7
- package/client/modeller/first-save.ts +2 -0
- package/client/modeller/product-session.ts +5 -3
- package/client/modeller/property-sections.ts +45 -0
- package/client/pages/figure-list-page.ts +52 -10
- package/client/pages/figure-modeller-page.ts +15 -3
- package/client/setting-let/figure-namespace-setting-let.ts +113 -31
- package/client/types.ts +8 -0
- package/client/viewparts/figure-detail-dock.ts +124 -9
- package/client/viewparts/figure-thumb.ts +6 -3
- package/dist-client/figure-released.d.ts +3 -0
- package/dist-client/figure-released.js +5 -0
- package/dist-client/figure-released.js.map +1 -0
- package/dist-client/graphql/index.d.ts +25 -13
- package/dist-client/graphql/index.js +85 -16
- package/dist-client/graphql/index.js.map +1 -1
- package/dist-client/graphql/store.js +2 -1
- package/dist-client/graphql/store.js.map +1 -1
- package/dist-client/index.d.ts +1 -0
- package/dist-client/index.js +1 -0
- package/dist-client/index.js.map +1 -1
- package/dist-client/modeller/editor.d.ts +2 -0
- package/dist-client/modeller/editor.js +13 -2
- package/dist-client/modeller/editor.js.map +1 -1
- package/dist-client/modeller/figure-first-save.d.ts +8 -0
- package/dist-client/modeller/figure-first-save.js +28 -3
- package/dist-client/modeller/figure-first-save.js.map +1 -1
- package/dist-client/modeller/figure-parts.d.ts +5 -0
- package/dist-client/modeller/figure-parts.js +54 -22
- package/dist-client/modeller/figure-parts.js.map +1 -1
- package/dist-client/modeller/figure-properties.d.ts +4 -0
- package/dist-client/modeller/figure-properties.js +45 -7
- package/dist-client/modeller/figure-properties.js.map +1 -1
- package/dist-client/modeller/first-save.d.ts +2 -0
- package/dist-client/modeller/first-save.js.map +1 -1
- package/dist-client/modeller/product-session.d.ts +4 -1
- package/dist-client/modeller/product-session.js +3 -3
- package/dist-client/modeller/product-session.js.map +1 -1
- package/dist-client/modeller/property-sections.d.ts +26 -0
- package/dist-client/modeller/property-sections.js +17 -0
- package/dist-client/modeller/property-sections.js.map +1 -0
- package/dist-client/pages/figure-list-page.d.ts +4 -1
- package/dist-client/pages/figure-list-page.js +56 -10
- package/dist-client/pages/figure-list-page.js.map +1 -1
- package/dist-client/pages/figure-modeller-page.d.ts +2 -0
- package/dist-client/pages/figure-modeller-page.js +19 -3
- package/dist-client/pages/figure-modeller-page.js.map +1 -1
- package/dist-client/setting-let/figure-namespace-setting-let.d.ts +16 -5
- package/dist-client/setting-let/figure-namespace-setting-let.js +129 -31
- package/dist-client/setting-let/figure-namespace-setting-let.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/types.d.ts +12 -0
- package/dist-client/types.js.map +1 -1
- package/dist-client/viewparts/figure-detail-dock.d.ts +33 -2
- package/dist-client/viewparts/figure-detail-dock.js +171 -11
- package/dist-client/viewparts/figure-detail-dock.js.map +1 -1
- package/dist-client/viewparts/figure-thumb.d.ts +2 -0
- package/dist-client/viewparts/figure-thumb.js +13 -3
- package/dist-client/viewparts/figure-thumb.js.map +1 -1
- package/package.json +8 -7
- package/test/browser/category-page.js +28 -0
- package/test/browser/category.check.mjs +54 -0
- package/test/browser/mesh-select.check.mjs +1 -1
- package/test/browser/namespace-page.js +75 -0
- package/test/browser/namespace.check.mjs +104 -0
- package/test/browser/parts-page.js +8 -2
- package/test/browser/places-mesh.check.mjs +30 -2
- package/test/browser/property-section-page.js +45 -0
- package/test/browser/property-section.check.mjs +48 -0
- package/test/browser/props.check.mjs +13 -2
- package/test/browser/public-page.js +39 -0
- package/test/browser/public.check.mjs +79 -0
- package/test/browser/retire-page.js +37 -0
- package/test/browser/retire.check.mjs +96 -0
- package/test/figure-released.test.ts +54 -0
- package/test/modeller-route.test.ts +2 -1
- package/translations/en.json +20 -5
- package/translations/es.json +20 -5
- package/translations/ja.json +20 -5
- package/translations/ko.json +20 -5
- package/translations/ms.json +20 -5
- package/translations/zh.json +20 -5
|
@@ -13,6 +13,12 @@ export declare class FigureFirstSave extends LitElement {
|
|
|
13
13
|
name: string;
|
|
14
14
|
/** Type names already used in this domain. */
|
|
15
15
|
taken: string[];
|
|
16
|
+
/** The domain's active namespaces, its default first. Asked only when there are several (LV figure-server design G1). */
|
|
17
|
+
namespaces: {
|
|
18
|
+
name: string;
|
|
19
|
+
isDefault: boolean;
|
|
20
|
+
}[];
|
|
21
|
+
private namespace;
|
|
16
22
|
private refusal;
|
|
17
23
|
/** The identifier offered, while the author has not changed it. */
|
|
18
24
|
private suggested;
|
|
@@ -21,4 +27,6 @@ export declare class FigureFirstSave extends LitElement {
|
|
|
21
27
|
render(): import("lit-html").TemplateResult<1>;
|
|
22
28
|
private onKey;
|
|
23
29
|
private confirm;
|
|
30
|
+
/** The namespace chosen, or the domain's default until one is. */
|
|
31
|
+
private get chosenNamespace();
|
|
24
32
|
}
|
|
@@ -20,6 +20,9 @@ let FigureFirstSave = class FigureFirstSave extends LitElement {
|
|
|
20
20
|
this.name = '';
|
|
21
21
|
/** Type names already used in this domain. */
|
|
22
22
|
this.taken = [];
|
|
23
|
+
/** The domain's active namespaces, its default first. Asked only when there are several (LV figure-server design G1). */
|
|
24
|
+
this.namespaces = [];
|
|
25
|
+
this.namespace = '';
|
|
23
26
|
this.refusal = null;
|
|
24
27
|
/** The identifier offered, while the author has not changed it. */
|
|
25
28
|
this.suggested = '';
|
|
@@ -31,7 +34,8 @@ let FigureFirstSave = class FigureFirstSave extends LitElement {
|
|
|
31
34
|
this.refusal = firstSaveRefusalOf(this.type, this.taken);
|
|
32
35
|
if (this.refusal)
|
|
33
36
|
return;
|
|
34
|
-
|
|
37
|
+
const namespace = this.namespaces.length > 1 ? this.chosenNamespace : undefined;
|
|
38
|
+
closePopup(this, { type: this.type, name: this.name.trim() || this.type, ...(namespace ? { namespace } : {}) });
|
|
35
39
|
};
|
|
36
40
|
}
|
|
37
41
|
static { this.styles = [
|
|
@@ -50,11 +54,12 @@ let FigureFirstSave = class FigureFirstSave extends LitElement {
|
|
|
50
54
|
font-size: var(--popup-font-size-small);
|
|
51
55
|
color: var(--md-sys-color-on-surface-variant);
|
|
52
56
|
}
|
|
53
|
-
div[field] input
|
|
57
|
+
div[field] input,
|
|
58
|
+
div[field] select {
|
|
54
59
|
box-sizing: border-box;
|
|
55
60
|
width: 100%;
|
|
56
61
|
}
|
|
57
|
-
|
|
62
|
+
[type-field] {
|
|
58
63
|
font-family: var(--ui-mono, ui-monospace, monospace);
|
|
59
64
|
}
|
|
60
65
|
`
|
|
@@ -114,6 +119,14 @@ let FigureFirstSave = class FigureFirstSave extends LitElement {
|
|
|
114
119
|
? html `<div class="note">${i18next.t('figure.text.identifier-suggested')}</div>`
|
|
115
120
|
: nothing}
|
|
116
121
|
</div>
|
|
122
|
+
${this.namespaces.length > 1
|
|
123
|
+
? html `<div field>
|
|
124
|
+
<label for="namespace">${i18next.t('figure.label.namespace')}</label>
|
|
125
|
+
<select id="namespace" type-field @change=${(e) => (this.namespace = e.target.value)}>
|
|
126
|
+
${this.namespaces.map(namespace => html `<option value=${namespace.name} ?selected=${namespace.name === this.chosenNamespace}>${namespace.name}</option>`)}
|
|
127
|
+
</select>
|
|
128
|
+
</div>`
|
|
129
|
+
: nothing}
|
|
117
130
|
${this.refusal ? html `<div class="failure" role="alert">${i18next.t(this.refusal.key, { type: this.refusal.type })}</div>` : nothing}
|
|
118
131
|
<ox-popup-actions>
|
|
119
132
|
<md-text-button slot="cancel" @click=${() => closePopup(this)}>${i18next.t('figure.button.cancel')}</md-text-button>
|
|
@@ -121,6 +134,10 @@ let FigureFirstSave = class FigureFirstSave extends LitElement {
|
|
|
121
134
|
</ox-popup-actions>
|
|
122
135
|
`;
|
|
123
136
|
}
|
|
137
|
+
/** The namespace chosen, or the domain's default until one is. */
|
|
138
|
+
get chosenNamespace() {
|
|
139
|
+
return this.namespace || this.namespaces.find(namespace => namespace.isDefault)?.name || this.namespaces[0]?.name || '';
|
|
140
|
+
}
|
|
124
141
|
};
|
|
125
142
|
__decorate([
|
|
126
143
|
property(),
|
|
@@ -134,6 +151,14 @@ __decorate([
|
|
|
134
151
|
property({ attribute: false }),
|
|
135
152
|
__metadata("design:type", Array)
|
|
136
153
|
], FigureFirstSave.prototype, "taken", void 0);
|
|
154
|
+
__decorate([
|
|
155
|
+
property({ attribute: false }),
|
|
156
|
+
__metadata("design:type", Array)
|
|
157
|
+
], FigureFirstSave.prototype, "namespaces", void 0);
|
|
158
|
+
__decorate([
|
|
159
|
+
state(),
|
|
160
|
+
__metadata("design:type", Object)
|
|
161
|
+
], FigureFirstSave.prototype, "namespace", void 0);
|
|
137
162
|
__decorate([
|
|
138
163
|
state(),
|
|
139
164
|
__metadata("design:type", Object)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"figure-first-save.js","sourceRoot":"","sources":["../../client/modeller/figure-first-save.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement, html, css, nothing } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { i18next } from '@operato/i18n'\nimport { closePopup } from '@operato/popup'\nimport '@operato/popup/ox-popup-actions.js'\nimport { CommonPopupStyles, FormFieldStyles } from '@operato/styles'\nimport '@material/web/button/filled-button.js'\nimport '@material/web/button/text-button.js'\n\nimport { firstSaveRefusalOf, offeredTypeOf, releasableType, typeOf, type FirstSave } from './first-save.js'\n\n/**\n * The first save of a new figure: its type and its name, asked once, in the suite's popup look.\n * Closes with the answer, or with nothing when cancelled -- then nothing is saved.\n */\n@customElement('figure-first-save')\nexport class FigureFirstSave extends LitElement {\n static styles = [\n FormFieldStyles,\n CommonPopupStyles,\n css`\n /* The popup's height is a share of the window (small is 40%): on a low window the buttons must still be reached. */\n :host {\n overflow-y: auto;\n }\n div[field] {\n display: grid;\n gap: 4px;\n }\n div[field] label {\n font-size: var(--popup-font-size-small);\n color: var(--md-sys-color-on-surface-variant);\n }\n div[field] input {\n box-sizing: border-box;\n width: 100%;\n }\n input[type-field] {\n font-family: var(--ui-mono, ui-monospace, monospace);\n }\n `\n ]\n\n /** The type the figure has so far -- empty for a temporary one, which is not offered. */\n @property() type = ''\n @property() name = ''\n /** Type names already used in this domain. */\n @property({ attribute: false }) taken: string[] = []\n @state() private refusal: { key: string; type?: string } | null = null\n /** The identifier offered, while the author has not changed it. */\n @state() private suggested = ''\n\n /*\n Asked for a name first, with a valid identifier already there to keep or change (Q-D3). The identifier is not the\n name: renaming does not change it, and a retried first save opens on the one answered before (the page keeps it).\n Only an offered one found taken -- told by the server, or in the list -- is offered again; one the author typed is\n kept as typed, and its refusal said.\n */\n willUpdate(changed: Map<string, unknown>) {\n if (!(changed.has('type') || changed.has('taken'))) return\n const offer = offeredTypeOf(this.type, this.name, this.taken)\n this.type = offer.type\n if (offer.suggested !== undefined) this.suggested = offer.suggested\n }\n\n /*\n The name is focused here, each time the question opens. The browser honours `autofocus` once per document: the\n question asked again opened with nothing focused, and any autofocus taken earlier on the page left even the first\n opening unfocused (2026-10-11, product-page check).\n */\n firstUpdated() {\n this.renderRoot.querySelector<HTMLInputElement>('#name')?.focus()\n }\n\n render() {\n return html`\n <div class=\"what\">${i18next.t('figure.text.save-new-figure-desc')}</div>\n <div field>\n <label for=\"name\">${i18next.t('figure.label.name')}</label>\n <input\n id=\"name\"\n .value=${this.name}\n placeholder=${i18next.t('figure.text.figure-name-example')}\n @input=${(e: Event) => (this.name = (e.target as HTMLInputElement).value)}\n @keydown=${this.onKey}\n />\n </div>\n <div field>\n <label for=\"type\">${i18next.t('figure.label.figure-identifier')}</label>\n <input\n id=\"type\"\n type-field\n .value=${this.type}\n placeholder=${i18next.t('figure.text.figure-type-example')}\n @input=${(e: Event) => {\n this.type = typeOf((e.target as HTMLInputElement).value)\n ;(e.target as HTMLInputElement).value = this.type\n this.refusal = null\n }}\n @keydown=${this.onKey}\n />\n ${this.type && !releasableType(this.type)\n ? html`<div class=\"note\">${i18next.t('figure.text.type-name-will-not-release')}</div>`\n : this.suggested && this.type === this.suggested\n ? html`<div class=\"note\">${i18next.t('figure.text.identifier-suggested')}</div>`\n : nothing}\n </div>\n ${this.refusal ? html`<div class=\"failure\" role=\"alert\">${i18next.t(this.refusal.key, { type: this.refusal.type })}</div>` : nothing}\n <ox-popup-actions>\n <md-text-button slot=\"cancel\" @click=${() => closePopup(this)}>${i18next.t('figure.button.cancel')}</md-text-button>\n <md-filled-button slot=\"confirm\" ?disabled=${!this.type} @click=${this.confirm}>${i18next.t('figure.button.save')}</md-filled-button>\n </ox-popup-actions>\n `\n }\n\n private onKey = (e: KeyboardEvent) => {\n if (e.key === 'Enter' && !e.isComposing) this.confirm()\n }\n\n private confirm = () => {\n this.refusal = firstSaveRefusalOf(this.type, this.taken)\n if (this.refusal) return\n closePopup(this, { type: this.type, name: this.name.trim() || this.type } satisfies FirstSave)\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"figure-first-save.js","sourceRoot":"","sources":["../../client/modeller/figure-first-save.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement, html, css, nothing } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { i18next } from '@operato/i18n'\nimport { closePopup } from '@operato/popup'\nimport '@operato/popup/ox-popup-actions.js'\nimport { CommonPopupStyles, FormFieldStyles } from '@operato/styles'\nimport '@material/web/button/filled-button.js'\nimport '@material/web/button/text-button.js'\n\nimport { firstSaveRefusalOf, offeredTypeOf, releasableType, typeOf, type FirstSave } from './first-save.js'\n\n/**\n * The first save of a new figure: its type and its name, asked once, in the suite's popup look.\n * Closes with the answer, or with nothing when cancelled -- then nothing is saved.\n */\n@customElement('figure-first-save')\nexport class FigureFirstSave extends LitElement {\n static styles = [\n FormFieldStyles,\n CommonPopupStyles,\n css`\n /* The popup's height is a share of the window (small is 40%): on a low window the buttons must still be reached. */\n :host {\n overflow-y: auto;\n }\n div[field] {\n display: grid;\n gap: 4px;\n }\n div[field] label {\n font-size: var(--popup-font-size-small);\n color: var(--md-sys-color-on-surface-variant);\n }\n div[field] input,\n div[field] select {\n box-sizing: border-box;\n width: 100%;\n }\n [type-field] {\n font-family: var(--ui-mono, ui-monospace, monospace);\n }\n `\n ]\n\n /** The type the figure has so far -- empty for a temporary one, which is not offered. */\n @property() type = ''\n @property() name = ''\n /** Type names already used in this domain. */\n @property({ attribute: false }) taken: string[] = []\n /** The domain's active namespaces, its default first. Asked only when there are several (LV figure-server design G1). */\n @property({ attribute: false }) namespaces: { name: string; isDefault: boolean }[] = []\n @state() private namespace = ''\n @state() private refusal: { key: string; type?: string } | null = null\n /** The identifier offered, while the author has not changed it. */\n @state() private suggested = ''\n\n /*\n Asked for a name first, with a valid identifier already there to keep or change (Q-D3). The identifier is not the\n name: renaming does not change it, and a retried first save opens on the one answered before (the page keeps it).\n Only an offered one found taken -- told by the server, or in the list -- is offered again; one the author typed is\n kept as typed, and its refusal said.\n */\n willUpdate(changed: Map<string, unknown>) {\n if (!(changed.has('type') || changed.has('taken'))) return\n const offer = offeredTypeOf(this.type, this.name, this.taken)\n this.type = offer.type\n if (offer.suggested !== undefined) this.suggested = offer.suggested\n }\n\n /*\n The name is focused here, each time the question opens. The browser honours `autofocus` once per document: the\n question asked again opened with nothing focused, and any autofocus taken earlier on the page left even the first\n opening unfocused (2026-10-11, product-page check).\n */\n firstUpdated() {\n this.renderRoot.querySelector<HTMLInputElement>('#name')?.focus()\n }\n\n render() {\n return html`\n <div class=\"what\">${i18next.t('figure.text.save-new-figure-desc')}</div>\n <div field>\n <label for=\"name\">${i18next.t('figure.label.name')}</label>\n <input\n id=\"name\"\n .value=${this.name}\n placeholder=${i18next.t('figure.text.figure-name-example')}\n @input=${(e: Event) => (this.name = (e.target as HTMLInputElement).value)}\n @keydown=${this.onKey}\n />\n </div>\n <div field>\n <label for=\"type\">${i18next.t('figure.label.figure-identifier')}</label>\n <input\n id=\"type\"\n type-field\n .value=${this.type}\n placeholder=${i18next.t('figure.text.figure-type-example')}\n @input=${(e: Event) => {\n this.type = typeOf((e.target as HTMLInputElement).value)\n ;(e.target as HTMLInputElement).value = this.type\n this.refusal = null\n }}\n @keydown=${this.onKey}\n />\n ${this.type && !releasableType(this.type)\n ? html`<div class=\"note\">${i18next.t('figure.text.type-name-will-not-release')}</div>`\n : this.suggested && this.type === this.suggested\n ? html`<div class=\"note\">${i18next.t('figure.text.identifier-suggested')}</div>`\n : nothing}\n </div>\n ${this.namespaces.length > 1\n ? html`<div field>\n <label for=\"namespace\">${i18next.t('figure.label.namespace')}</label>\n <select id=\"namespace\" type-field @change=${(e: Event) => (this.namespace = (e.target as HTMLSelectElement).value)}>\n ${this.namespaces.map(\n namespace => html`<option value=${namespace.name} ?selected=${namespace.name === this.chosenNamespace}>${namespace.name}</option>`\n )}\n </select>\n </div>`\n : nothing}\n ${this.refusal ? html`<div class=\"failure\" role=\"alert\">${i18next.t(this.refusal.key, { type: this.refusal.type })}</div>` : nothing}\n <ox-popup-actions>\n <md-text-button slot=\"cancel\" @click=${() => closePopup(this)}>${i18next.t('figure.button.cancel')}</md-text-button>\n <md-filled-button slot=\"confirm\" ?disabled=${!this.type} @click=${this.confirm}>${i18next.t('figure.button.save')}</md-filled-button>\n </ox-popup-actions>\n `\n }\n\n private onKey = (e: KeyboardEvent) => {\n if (e.key === 'Enter' && !e.isComposing) this.confirm()\n }\n\n private confirm = () => {\n this.refusal = firstSaveRefusalOf(this.type, this.taken)\n if (this.refusal) return\n const namespace = this.namespaces.length > 1 ? this.chosenNamespace : undefined\n closePopup(this, { type: this.type, name: this.name.trim() || this.type, ...(namespace ? { namespace } : {}) } satisfies FirstSave)\n }\n\n /** The namespace chosen, or the domain's default until one is. */\n private get chosenNamespace(): string {\n return this.namespace || this.namespaces.find(namespace => namespace.isDefault)?.name || this.namespaces[0]?.name || ''\n }\n}\n"]}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import '@material/web/icon/icon.js';
|
|
2
|
+
import '@operato/input/ox-input-file.js';
|
|
2
3
|
import { LitElement } from 'lit';
|
|
3
4
|
import { type DetailLevel } from '@hatiolab/figure-model';
|
|
4
5
|
import type { FigurePrimitiveCreation } from '@hatiolab/figure-model';
|
|
@@ -58,6 +59,10 @@ export declare class FigureParts extends FigureParts_base {
|
|
|
58
59
|
};
|
|
59
60
|
/** How many recipe templates the catalog holds; none, and the section is not drawn. */
|
|
60
61
|
recipeCount: number;
|
|
62
|
+
/** How far the GLB being brought in has gone up (0‒1), while it does; the picker shows it. */
|
|
63
|
+
meshProgress?: number;
|
|
64
|
+
private shapes?;
|
|
65
|
+
firstUpdated(): void;
|
|
61
66
|
render(): import("lit-html").TemplateResult<1>;
|
|
62
67
|
/** 디테일 등급 — 고르면 한도가 바뀐다. 목록 머리 아래. */
|
|
63
68
|
private renderLevel;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { __decorate, __metadata } from "tslib";
|
|
2
2
|
import '@material/web/icon/icon.js';
|
|
3
|
+
import '@operato/input/ox-input-file.js';
|
|
3
4
|
import { css, html, LitElement, nothing } from 'lit';
|
|
4
|
-
import { customElement, property } from 'lit/decorators.js';
|
|
5
|
+
import { customElement, property, query } from 'lit/decorators.js';
|
|
5
6
|
import { i18next, localize } from '@operato/i18n';
|
|
6
7
|
import { ScrollbarStyles } from '@operato/styles';
|
|
7
8
|
import { DETAIL_LEVELS, PART_LIMIT } from '@hatiolab/figure-model';
|
|
@@ -233,13 +234,38 @@ let FigureParts = class FigureParts extends localize(i18next)(LitElement) {
|
|
|
233
234
|
overflow: hidden;
|
|
234
235
|
text-overflow: ellipsis;
|
|
235
236
|
}
|
|
236
|
-
|
|
237
|
+
/*
|
|
238
|
+
The one file picker (ox-input-file, quiet) on that row: a GLB refused or going up is said on the spot. Its button
|
|
239
|
+
wears the tiles' words and lit hand -- the picker's own 13/600 stood louder than the shapes' heading.
|
|
240
|
+
*/
|
|
241
|
+
ox-input-file[mesh-file] {
|
|
237
242
|
grid-column: 1 / -1;
|
|
238
|
-
flex-direction: row;
|
|
239
243
|
justify-content: center;
|
|
244
|
+
font-size: var(--ui-t-micro, 11px);
|
|
245
|
+
--ox-input-file-action-height: 26px;
|
|
246
|
+
}
|
|
247
|
+
ox-input-file[mesh-file]::part(attach) {
|
|
240
248
|
gap: 6px;
|
|
241
|
-
min-height: 26px;
|
|
242
249
|
padding: 0 8px;
|
|
250
|
+
font: var(--label-font, inherit);
|
|
251
|
+
font-size: var(--ui-t-micro, 11px);
|
|
252
|
+
font-weight: 400;
|
|
253
|
+
color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));
|
|
254
|
+
border-radius: 4px;
|
|
255
|
+
}
|
|
256
|
+
ox-input-file[mesh-file]:not([disabled])::part(attach):hover {
|
|
257
|
+
color: var(--md-sys-color-on-primary-container);
|
|
258
|
+
background-color: var(--md-sys-color-primary-container);
|
|
259
|
+
}
|
|
260
|
+
ox-input-file[mesh-file][disabled]::part(attach) {
|
|
261
|
+
opacity: 0.35;
|
|
262
|
+
}
|
|
263
|
+
ox-input-file[mesh-file] md-icon {
|
|
264
|
+
--md-icon-size: 18px;
|
|
265
|
+
}
|
|
266
|
+
section[shapes].candrop {
|
|
267
|
+
outline: 1px dashed var(--md-sys-color-primary);
|
|
268
|
+
outline-offset: -1px;
|
|
243
269
|
}
|
|
244
270
|
/* An action in the side panel's look: icon and words in the primary colour, no pill. */
|
|
245
271
|
button[recipe-catalog] {
|
|
@@ -410,11 +436,15 @@ let FigureParts = class FigureParts extends localize(i18next)(LitElement) {
|
|
|
410
436
|
}
|
|
411
437
|
`
|
|
412
438
|
]; }
|
|
439
|
+
firstUpdated() {
|
|
440
|
+
/* The drop area is drawn by the first render; the picker is told it by the next. */
|
|
441
|
+
this.requestUpdate();
|
|
442
|
+
}
|
|
413
443
|
render() {
|
|
414
444
|
const b = this.partBudget;
|
|
415
445
|
const full = !!b && b.limit !== null && b.used >= b.limit;
|
|
416
446
|
return html `
|
|
417
|
-
<section>
|
|
447
|
+
<section shapes>
|
|
418
448
|
<h3>${i18next.t('figure.label.shapes')}</h3>
|
|
419
449
|
<div palette>
|
|
420
450
|
${RECIPES.map(recipe => html `
|
|
@@ -428,29 +458,23 @@ let FigureParts = class FigureParts extends localize(i18next)(LitElement) {
|
|
|
428
458
|
<span>${i18next.t(recipe.label)}</span>
|
|
429
459
|
</button>
|
|
430
460
|
`)}
|
|
431
|
-
<
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
?disabled=${this.locked || full}
|
|
435
|
-
title=${full ? i18next.t('figure.text.part-limit-reached') : i18next.t('figure.text.import-mesh-hint')}
|
|
436
|
-
@click=${() => this.renderRoot.querySelector('input[mesh-file]')?.click()}
|
|
437
|
-
>
|
|
438
|
-
<md-icon>view_in_ar</md-icon>
|
|
439
|
-
<span>${i18next.t('figure.button.import-mesh')}</span>
|
|
440
|
-
</button>
|
|
441
|
-
<input
|
|
461
|
+
<ox-input-file
|
|
462
|
+
quiet
|
|
463
|
+
hide-filelist
|
|
442
464
|
mesh-file
|
|
443
|
-
type="file"
|
|
444
465
|
accept=".glb,model/gltf-binary"
|
|
445
|
-
|
|
466
|
+
.label=${i18next.t('figure.button.import-mesh')}
|
|
467
|
+
title=${full ? i18next.t('figure.text.part-limit-reached') : i18next.t('figure.text.import-mesh-hint')}
|
|
468
|
+
?disabled=${this.locked || full}
|
|
469
|
+
.dropTarget=${this.shapes}
|
|
470
|
+
.progress=${this.meshProgress}
|
|
446
471
|
@change=${(e) => {
|
|
447
|
-
const
|
|
448
|
-
const file = input.files?.[0];
|
|
449
|
-
input.value = '';
|
|
472
|
+
const file = e.detail?.[0];
|
|
450
473
|
if (file)
|
|
451
474
|
this.ask('import-mesh', { file });
|
|
475
|
+
e.currentTarget.reset();
|
|
452
476
|
}}
|
|
453
|
-
|
|
477
|
+
><md-icon slot="icon">view_in_ar</md-icon></ox-input-file>
|
|
454
478
|
</div>
|
|
455
479
|
${full ? html `<p full>${i18next.t('figure.text.part-limit-reached-short', { level: b.level, limit: b.limit })}</p>` : nothing}
|
|
456
480
|
</section>
|
|
@@ -667,6 +691,14 @@ __decorate([
|
|
|
667
691
|
property({ type: Number }),
|
|
668
692
|
__metadata("design:type", Object)
|
|
669
693
|
], FigureParts.prototype, "recipeCount", void 0);
|
|
694
|
+
__decorate([
|
|
695
|
+
property({ type: Number }),
|
|
696
|
+
__metadata("design:type", Number)
|
|
697
|
+
], FigureParts.prototype, "meshProgress", void 0);
|
|
698
|
+
__decorate([
|
|
699
|
+
query('section[shapes]'),
|
|
700
|
+
__metadata("design:type", HTMLElement)
|
|
701
|
+
], FigureParts.prototype, "shapes", void 0);
|
|
670
702
|
FigureParts = __decorate([
|
|
671
703
|
customElement('figure-parts')
|
|
672
704
|
], FigureParts);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"figure-parts.js","sourceRoot":"","sources":["../../client/modeller/figure-parts.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\nimport { css, html, LitElement, nothing } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\nimport { i18next, localize } from '@operato/i18n'\nimport { ScrollbarStyles } from '@operato/styles'\nimport { DETAIL_LEVELS, PART_LIMIT, type DetailLevel } from '@hatiolab/figure-model'\nimport type { FigurePrimitiveCreation } from '@hatiolab/figure-model'\nimport type { FigureBudgetLine } from './maturity.js'\nimport { partLabelOf } from './fields.js'\n\n/**\n * 도형 팔레트와 부품 목록 — 자산의 것.\n *\n * 팔레트는 목록 위에, 눈 단추는 늘 자리를 차지하고, 조작은 고른 줄에서만 보인다.\n *\n * 부품의 도형은 평가가 준 형상에서 읽은 시작 모양 이름(`kind`)으로 보인다. 「숨김」은 여기서도 **작업대의 것**이다 — 저장되지 않고, 자산의 `visibility`(상태에 따라\n * 보이고 안 보이는 것)와는 다른 것이다. 그것은 속성 패널의 몫이다.\n *\n * 이 컴포넌트는 정본을 만지지 않는다. 무엇을 하겠다는 것만 알린다.\n */\n\n/** 한 줄 — 평가에서 이 패널이 읽는 것만. */\nexport interface FigurePartRow {\n id: string\n /** 시작 모양 이름(`startingShapeKindOfBody`): 상자 · 원기둥 … 아이콘을 고른다. */\n kind: string\n /** 작업대에서 잠깐 가린 것. 저장되지 않는다. */\n hidden?: boolean\n /** 자산이 지금 상태에서 보이지 않는다고 한 것. 저장되는 것이고, 여기서는 표시만 한다. */\n invisible?: boolean\n /** 되풀이한 부품이면 지금 몇 개로 그려지나. 되풀이는 한 부품이다 — 한 줄로 보인다. */\n copies?: number\n /**\n * 바로 붙어 있는 곳: `대상 부품 · 대상 면`. 목록은 평평한 채로 두고 보조 문구로만 적는다 — 순서도 들여쓰기도\n * 붙임 때문에 바뀌지 않는다(ADR-0097 §5.5, 설계자 2026-09-27).\n */\n fastenedTo?: string\n}\n\n/** 시작 모양마다의 아이콘. Material Symbols 이름이다. */\nconst KIND_ICON: Record<string, string> = {\n cube: 'deployed_code',\n cylinder: 'database',\n frustum: 'change_history',\n sphere: 'circle',\n 'rounded-box': 'crop_square',\n 'hollow-box': 'check_box_outline_blank',\n polygon: 'pentagon',\n pyramid: 'details',\n portal: 'door_front'\n}\n\n/** 팔레트 — 저작 API 가 만들 수 있는 것만 보인다. */\n/*\n The palette: starting shapes (ADR-0100 §2.1). A plate and a wall are boxes of a shape a person reaches for, so they are\n starting sizes, not kinds of their own. A rounded box and a box open at the top are not here: they are a box with its\n corners rounded or an inside made, from the part's own 단면 section (§3.3), so the palette does not offer a second way.\n*/\n/* A mesh part is made from a file (`import-mesh`), not from a starting size. */\nexport type FigurePaletteKind = Exclude<FigurePrimitiveCreation['kind'], 'mesh'> | 'plate' | 'wall'\nconst RECIPES: Array<{ kind: FigurePaletteKind; icon: string; label: string }> = [\n { kind: 'box', icon: 'deployed_code', label: 'figure.label.primitive-cube' },\n { kind: 'cylinder', icon: 'database', label: 'figure.label.primitive-cylinder' },\n { kind: 'sphere', icon: 'circle', label: 'figure.label.primitive-sphere' },\n { kind: 'frustum', icon: 'change_history', label: 'figure.label.primitive-frustum' },\n { kind: 'polygon', icon: 'hexagon', label: 'figure.label.primitive-polygon' },\n { kind: 'pyramid', icon: 'details', label: 'figure.label.primitive-pyramid' },\n { kind: 'plate', icon: 'check_box_outline_blank', label: 'figure.label.primitive-plate' },\n { kind: 'wall', icon: 'crop_portrait', label: 'figure.label.primitive-wall' },\n { kind: 'portal', icon: 'door_front', label: 'figure.label.primitive-portal' }\n]\n\n@customElement('figure-parts')\nexport class FigureParts 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: hidden;\n background-color: var(--md-sys-color-surface-container-lowest);\n /* The divider beside it is the editor's frame's (ox-three-pane). */\n color: var(--md-sys-color-on-surface);\n font: var(--label-font, inherit);\n font-size: var(--ui-t-small, 11.5px);\n /* Folds by its own width: the editor gives this column 140 px when it has two columns (UX, 2026-09-24). */\n container-type: inline-size;\n }\n section {\n padding: var(--spacing-medium, 8px) var(--spacing-large, 12px);\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n }\n section[list] {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n }\n h3 {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: 6px;\n margin: 0 0 6px 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 /* The cost line: the grade's letter in its colour, then what the figure costs. */\n button[model-root] {\n display: flex;\n align-items: center;\n gap: 6px;\n width: 100%;\n min-height: 32px;\n padding: 4px 8px;\n border: 1px solid transparent;\n border-radius: 6px;\n background: none;\n font: inherit;\n font-weight: 600;\n text-align: start;\n color: var(--md-sys-color-on-surface);\n cursor: pointer;\n }\n button[model-root] span {\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n }\n /* Chosen: a frame and a tint, not colour alone. */\n button[model-root][on] {\n border-color: var(--md-sys-color-primary);\n background: var(--md-sys-color-primary-container);\n }\n button[model-root]:focus-visible {\n outline: 2px solid var(--md-sys-color-primary);\n outline-offset: 1px;\n }\n /* The cost line as a line, not a card; lit under the hand like the rest of the list. */\n button[budget] {\n display: flex;\n align-items: flex-start;\n gap: 6px;\n width: 100%;\n margin: 0 0 6px;\n padding: 4px 6px;\n font: inherit;\n font-size: var(--ui-t-micro, 11px);\n line-height: 1.5;\n text-align: left;\n color: var(--md-sys-color-on-surface-variant);\n background: none;\n border: 1px solid transparent;\n border-radius: 4px;\n cursor: pointer;\n }\n button[budget]:hover {\n background: var(--md-sys-color-surface-container);\n }\n button[budget] b {\n flex: none;\n min-width: 16px;\n text-align: center;\n border-radius: 4px;\n color: var(--md-sys-color-on-primary-container);\n background: var(--md-sys-color-primary-container);\n }\n button[budget][grade='C'] b {\n color: var(--md-sys-color-on-tertiary-container);\n background: var(--md-sys-color-tertiary-container);\n }\n button[budget][grade='D'] {\n border-color: var(--md-sys-color-error);\n }\n button[budget][grade='D'] b {\n color: var(--md-sys-color-on-error);\n background: var(--md-sys-color-error);\n }\n button[budget] em {\n display: block;\n font-style: normal;\n color: var(--md-sys-color-error);\n }\n span[room][full] {\n color: var(--md-sys-color-error);\n font-weight: 600;\n }\n p[full] {\n margin: 4px 0 0;\n font-size: var(--ui-t-micro, 11px);\n color: var(--md-sys-color-error);\n }\n div[level] {\n display: grid;\n grid-template-columns: auto minmax(0, 1fr);\n align-items: center;\n gap: 6px;\n margin: 0 0 6px;\n font-size: var(--ui-t-micro, 11px);\n color: var(--md-sys-color-on-surface-variant);\n }\n /* A field in the side panel's look (side-panel-styles): 26px, a quiet ground, its line only under the hand. */\n div[level] select {\n min-width: 0;\n min-height: 26px;\n padding: 2px 4px;\n font: inherit;\n font-size: var(--ui-t-small, 11.5px);\n color: var(--md-sys-color-on-surface);\n background: var(--md-sys-color-surface-container-low);\n border: 1px solid transparent;\n border-radius: 4px;\n }\n div[level] select:hover:not(:disabled) {\n border-color: var(--md-sys-color-outline-variant);\n }\n small[copies] {\n color: var(--md-sys-color-on-surface-variant);\n }\n span[room] {\n font-size: var(--ui-t-micro, 11px);\n font-weight: 400;\n letter-spacing: 0;\n text-transform: none;\n }\n /* Three to a row, small (user, 2026-09-24): the palette is reached for, not read -- the list below is what grows. */\n div[palette] {\n display: grid;\n grid-template-columns: repeat(3, minmax(0, 1fr));\n gap: 3px;\n }\n /* A tile with no outline: its icon and name, lit under the hand -- ten outlined tiles were the boxiest part of the screen (user 2026-10-17). */\n button[shape] {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 1px;\n min-width: 0;\n padding: 5px 1px 4px;\n font: var(--label-font, inherit);\n font-size: var(--ui-t-micro, 11px);\n line-height: 1.2;\n text-align: center;\n overflow-wrap: anywhere;\n /* A tile's name on one line, so the nine stand one height; a long name ends in an ellipsis, its title says it all. */\n white-space: nowrap;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n background: none;\n border: 1px solid transparent;\n border-radius: 4px;\n cursor: pointer;\n }\n /*\n A file brought in is not a starting shape: its own row under the nine, its words on one line. As a tenth tile its\n two-word name broke onto two lines and stood it taller than the rest (2026-10-16).\n */\n button[shape] > span {\n max-width: 100%;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n button[shape][import] {\n grid-column: 1 / -1;\n flex-direction: row;\n justify-content: center;\n gap: 6px;\n min-height: 26px;\n padding: 0 8px;\n }\n /* An action in the side panel's look: icon and words in the primary colour, no pill. */\n button[recipe-catalog] {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 4px 6px;\n margin-left: -6px;\n font: inherit;\n font-size: var(--ui-t-small, 11.5px);\n color: var(--md-sys-color-primary);\n background: none;\n border: 1px solid transparent;\n border-radius: 4px;\n cursor: pointer;\n }\n button[recipe-catalog]:hover:not(:disabled) {\n background: var(--md-sys-color-surface-container);\n }\n button[shape]:hover:not([disabled]) {\n color: var(--md-sys-color-on-primary-container);\n background-color: var(--md-sys-color-primary-container);\n border-color: transparent;\n }\n button[shape][disabled] {\n opacity: 0.35;\n cursor: default;\n }\n button[shape] md-icon {\n --md-icon-size: 18px;\n }\n ol {\n margin: 0;\n padding: 0;\n list-style: none;\n display: flex;\n flex-direction: column;\n gap: 2px;\n }\n li {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 4px 6px;\n border-radius: 6px;\n cursor: pointer;\n }\n li:hover {\n background-color: var(--md-sys-color-surface-container);\n }\n li[selected] {\n color: var(--md-sys-color-on-primary-container);\n background-color: var(--md-sys-color-primary-container);\n }\n /*\n Icons by role, three sizes on the whole screen (2026-10-16; there were six): 20 for the bar, the tabs and the\n model's head, 18 for a palette tile, 16 beside words and on a row.\n */\n button[model-root] md-icon {\n --md-icon-size: 20px;\n }\n /* Beside its words, as every icon on a button with words is; without a size it stood at md-icon's own 24. */\n button[recipe-catalog] md-icon {\n --md-icon-size: 16px;\n }\n li md-icon {\n --md-icon-size: 16px;\n flex: none;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n button[eye][all] {\n margin-left: auto;\n opacity: 0.75;\n }\n button[eye][all]:hover {\n opacity: 1;\n }\n button[eye] {\n display: flex;\n flex: none;\n padding: 1px;\n color: inherit;\n background: none;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n opacity: 0;\n }\n li:hover button[eye],\n li[selected] button[eye],\n button[eye][off] {\n opacity: 1;\n }\n button[eye][off] {\n color: var(--md-sys-color-tertiary);\n }\n button[eye] md-icon {\n --md-icon-size: 16px;\n color: inherit;\n }\n li[off] span[label],\n li[off] > md-icon,\n li[invisible] span[label],\n li[invisible] > md-icon {\n opacity: 0.45;\n }\n li[selected] md-icon {\n color: inherit;\n }\n span[label] {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: var(--ui-t-body, 13px);\n }\n span[label] small {\n margin-left: 4px;\n font-size: var(--ui-t-micro, 11px);\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n div[ops] {\n display: none;\n gap: 1px;\n }\n li[selected] div[ops] {\n display: flex;\n }\n div[ops] button {\n display: flex;\n padding: 2px;\n color: inherit;\n background: none;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n }\n div[ops] button:hover:not([disabled]) {\n background-color: var(--md-sys-color-surface-container-lowest);\n }\n div[ops] button[disabled] {\n opacity: 0.3;\n cursor: default;\n }\n div[ops] md-icon {\n --md-icon-size: 16px;\n color: inherit;\n }\n /*\n Under 200 px the selected row's four buttons pushed its name out and the last one past the edge (seen at 140,\n 2026-09-24). They go to a line of their own under the name.\n */\n @container (max-width: 199px) {\n li[selected] {\n flex-wrap: wrap;\n }\n li[selected] div[ops] {\n flex-basis: 100%;\n justify-content: flex-end;\n }\n }\n p[quiet] {\n margin: 0;\n font-size: var(--ui-t-small, 11.5px);\n line-height: 1.6;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n `\n ]\n\n @property({ type: Array }) parts: FigurePartRow[] = []\n /** 고른 부품의 id. 자리(index)가 아니다 — 부품은 id 로 안정하게 가리킨다. */\n @property({ type: String }) selected = ''\n /** The model's own name, the list's root: choosing it shows the model's settings (B.1). */\n @property({ type: String }) modelName = ''\n /** 편집할 수 없을 때(불러오는 중 · 저장 중) 팔레트와 조작을 잠근다. */\n @property({ type: Boolean }) locked = false\n /**\n * 이 도형이 치르는 값 — 삼각형 · 묶음 · 한 화면에 몇 대 · 등급. 부품을 더하는 바로 그 자리에서 보여야 로우폴리의\n * 기준이 저작을 이끈다. 등급이 D 면 무엇이 넘쳤는지를 적는다. 누르면 성숙도 탭이 열린다.\n */\n @property({ attribute: false }) budget?: FigureBudgetLine\n /**\n * 디테일 등급(S/M/L)의 부품 한도(사용자 2026-09-24). 부품을 넣기 **전에** 몇 개 남았는지 보이고, 다 차면 팔레트가\n * 잠긴다. 되풀이는 한 부품으로 센다. 등급을 안 정했으면 한도는 없다.\n */\n @property({ attribute: false }) partBudget?: { level: DetailLevel | null; used: number; limit: number | null }\n /** How many recipe templates the catalog holds; none, and the section is not drawn. */\n @property({ type: Number }) recipeCount = 0\n\n render() {\n const b = this.partBudget\n const full = !!b && b.limit !== null && b.used >= b.limit\n return html`\n <section>\n <h3>${i18next.t('figure.label.shapes')}</h3>\n <div palette>\n ${RECIPES.map(\n recipe => html`\n <button\n shape\n ?disabled=${this.locked || full}\n title=${full ? i18next.t('figure.text.part-limit-reached') : i18next.t(recipe.label)}\n @click=${() => this.ask('add-part', { kind: recipe.kind })}\n >\n <md-icon>${recipe.icon}</md-icon>\n <span>${i18next.t(recipe.label)}</span>\n </button>\n `\n )}\n <button\n shape\n import\n ?disabled=${this.locked || full}\n title=${full ? i18next.t('figure.text.part-limit-reached') : i18next.t('figure.text.import-mesh-hint')}\n @click=${() => this.renderRoot.querySelector<HTMLInputElement>('input[mesh-file]')?.click()}\n >\n <md-icon>view_in_ar</md-icon>\n <span>${i18next.t('figure.button.import-mesh')}</span>\n </button>\n <input\n mesh-file\n type=\"file\"\n accept=\".glb,model/gltf-binary\"\n hidden\n @change=${(e: Event) => {\n const input = e.target as HTMLInputElement\n const file = input.files?.[0]\n input.value = ''\n if (file) this.ask('import-mesh', { file })\n }}\n />\n </div>\n ${full ? html`<p full>${i18next.t('figure.text.part-limit-reached-short', { level: b!.level, limit: b!.limit })}</p>` : nothing}\n </section>\n ${this.renderRecipes(full)}\n\n <section list>\n <!--\n The list's root: the model by its name. Choosing it shows the model's settings -- not \"select all\", not \"fit the\n view\" -- the same move as the model name in the properties panel's path (designer 2026-09-27, B.1).\n -->\n <button\n model-root\n aria-pressed=${String(!this.selected)}\n ?on=${!this.selected}\n title=${i18next.t('figure.button.show-model-settings')}\n aria-label=${`${i18next.t('figure.button.show-model-settings')}: ${this.modelName || i18next.t('figure.text.new-figure-name')}`}\n @click=${() => this.dispatchEvent(new CustomEvent('show-model-settings', { bubbles: true, composed: true }))}\n >\n <md-icon>category</md-icon><span>${this.modelName || i18next.t('figure.text.new-figure-name')}</span>\n </button>\n <h3>\n <span>${i18next.t('figure.label.parts')}</span>\n <span room ?full=${full} title=${i18next.t('figure.text.limit-comes-from-level')}\n >${b && b.limit !== null ? i18next.t('figure.text.n-of-limit', { used: b.used, limit: b.limit }) : (b?.used ?? this.parts.length)}</span\n >\n ${this.renderShowAll()}\n </h3>\n ${this.renderLevel()}\n ${this.renderBudget()}\n ${this.parts.length === 0\n ? html`<p quiet>${i18next.t('figure.text.no-parts-yet')}</p>`\n : html`<ol>\n ${this.parts.map((part, index) => this.renderRow(part, index))}\n </ol>`}\n </section>\n `\n }\n\n /** 디테일 등급 — 고르면 한도가 바뀐다. 목록 머리 아래. */\n private renderLevel() {\n const b = this.partBudget\n /* Not before the first part: a level with nothing to limit was the first choice a new figure met (U09). Once set, it shows. */\n if (!b || (!this.parts.length && !b.level)) return nothing\n return html`<div level>\n <label for=\"detail-level\">${i18next.t('figure.label.detail-level')}</label>\n <select\n id=\"detail-level\"\n title=${i18next.t('figure.text.detail-level-explained')}\n .value=${b.level ?? ''}\n ?disabled=${this.locked}\n @change=${(e: Event) => {\n const value = (e.target as HTMLSelectElement).value\n this.dispatchEvent(new CustomEvent('set-detail-level', { detail: { level: value || null }, bubbles: true, composed: true }))\n }}\n >\n <option value=\"\" ?selected=${!b.level}>${i18next.t('figure.label.detail-none')}</option>\n ${DETAIL_LEVELS.map(\n one => html`<option value=${one} ?selected=${one === b.level}>\n ${one} ${i18next.t('figure.label.detail-' + one)} · ${i18next.t('figure.text.n-parts-at-most', { limit: PART_LIMIT[one] })}\n </option>`\n )}\n </select>\n </div>`\n }\n\n private renderBudget() {\n const b = this.budget\n if (!b || !this.parts.length) return nothing\n const over = b.grade === 'D'\n return html`<button\n budget\n grade=${b.grade}\n title=${i18next.t('figure.text.parts-budget-open')}\n @click=${() => this.dispatchEvent(new CustomEvent('show-maturity', { bubbles: true, composed: true }))}\n >\n <b>${b.grade}</b>\n <span>\n <strong performance-cost>${i18next.t('figure.label.performance-cost')}</strong>\n ${i18next.t('figure.text.parts-budget', {\n triangles: b.triangles.toLocaleString(),\n groups: b.groups.toLocaleString(),\n instances: b.instances.toLocaleString()\n })}\n ${over\n ? html`<em\n >${i18next.t('figure.text.parts-budget-over', {\n budget: i18next.t('figure.label.budget-' + b.tightest.key),\n used: b.tightest.used.toLocaleString(),\n limit: b.tightest.limit.toLocaleString()\n })}</em\n >`\n : nothing}\n </span>\n </button>`\n }\n\n /** 한꺼번에 감추고 한꺼번에 되돌린다 — 하나라도 감춰져 있으면 다 되돌리고, 다 보이면 다 감춘다. */\n private renderShowAll() {\n if (this.parts.length === 0) return nothing\n const anyHidden = this.parts.some(part => part.hidden)\n return html`\n <button\n eye\n all\n ?off=${!anyHidden}\n title=${i18next.t(anyHidden ? 'figure.text.show-all-parts' : 'figure.text.hide-all-parts')}\n @click=${(e: Event) => {\n e.stopPropagation()\n this.ask('set-all-parts-hidden', { hidden: !anyHidden })\n }}\n >\n <md-icon>${anyHidden ? 'visibility' : 'visibility_off'}</md-icon>\n </button>\n `\n }\n\n private renderRow(part: FigurePartRow, index: number) {\n const selected = part.id === this.selected\n const off = !!part.hidden\n return html`\n <li\n ?selected=${selected}\n ?off=${off}\n ?invisible=${!!part.invisible}\n @click=${() => this.ask('select-part', { id: part.id })}\n >\n <button\n eye\n ?off=${off}\n title=${i18next.t(off ? 'figure.text.show-this-part' : 'figure.text.hide-this-part')}\n @click=${(e: Event) => {\n e.stopPropagation()\n this.ask('toggle-part-hidden', { id: part.id })\n }}\n >\n <md-icon>${off ? 'visibility_off' : 'visibility'}</md-icon>\n </button>\n <md-icon>${KIND_ICON[part.kind] ?? 'deployed_code'}</md-icon>\n <span label title=${part.id}\n >${partLabelOf(part.id)}${part.copies ? html` <small copies>× ${part.copies}</small>` : nothing}${part.invisible ? html`<small>${i18next.t('figure.label.state-hidden')}</small>` : nothing}${part.fastenedTo\n ? html`<small fastened-to title=${i18next.t('figure.label.fastened-to')}>${part.fastenedTo}</small>`\n : nothing}</span\n >\n ${selected ? this.renderOps(part.id, index) : nothing}\n </li>\n `\n }\n\n /** 고른 줄의 조작. 못 하는 것은 빼지 않고 흐리게 둔다 — 자리가 밀리면 연달아 지우다 엉뚱한 것을 누른다. */\n private renderOps(id: string, index: number) {\n const count = this.parts.length\n const op = (name: string, icon: string, detail: object, enabled = true) => html`\n <button\n op=${name}\n ?disabled=${!enabled || this.locked}\n title=${i18next.t('figure.button.' + name)}\n @click=${(e: Event) => {\n e.stopPropagation()\n if (enabled && !this.locked) this.ask(name, detail)\n }}\n >\n <md-icon>${icon}</md-icon>\n </button>\n `\n return html`\n <div ops>\n ${op('move-part-up', 'arrow_upward', { id, to: index - 1 }, index > 0)}\n ${op('move-part-down', 'arrow_downward', { id, to: index + 1 }, index < count - 1)}\n ${op('duplicate-part', 'content_copy', { id })} ${op('remove-part', 'delete', { id })}\n </div>\n `\n }\n\n /**\n * Recipes (ADR-0095): the catalog of templates -- a door, a roller row -- opened over the modeller, put onto the chosen\n * part in one step.\n */\n private renderRecipes(full: boolean) {\n if (!this.recipeCount) return nothing\n return html`<section recipes>\n <h3>${i18next.t('figure.label.recipes')}</h3>\n <button recipe-catalog ?disabled=${this.locked || full} @click=${() => this.ask('open-recipe-catalog', {})}>\n <md-icon>widgets</md-icon>${i18next.t('figure.button.open-recipe-catalog', { count: this.recipeCount })}\n </button>\n </section>`\n }\n\n /** 정본을 고치지 않는다 — 무엇을 하겠다는 것만 알린다. */\n private ask(what: string, detail: object) {\n this.dispatchEvent(new CustomEvent(what, { detail, bubbles: true, composed: true }))\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"figure-parts.js","sourceRoot":"","sources":["../../client/modeller/figure-parts.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\nimport '@operato/input/ox-input-file.js'\nimport { css, html, LitElement, nothing } from 'lit'\nimport { customElement, property, query } from 'lit/decorators.js'\nimport { i18next, localize } from '@operato/i18n'\nimport { ScrollbarStyles } from '@operato/styles'\nimport { DETAIL_LEVELS, PART_LIMIT, type DetailLevel } from '@hatiolab/figure-model'\nimport type { FigurePrimitiveCreation } from '@hatiolab/figure-model'\nimport type { FigureBudgetLine } from './maturity.js'\nimport { partLabelOf } from './fields.js'\n\n/**\n * 도형 팔레트와 부품 목록 — 자산의 것.\n *\n * 팔레트는 목록 위에, 눈 단추는 늘 자리를 차지하고, 조작은 고른 줄에서만 보인다.\n *\n * 부품의 도형은 평가가 준 형상에서 읽은 시작 모양 이름(`kind`)으로 보인다. 「숨김」은 여기서도 **작업대의 것**이다 — 저장되지 않고, 자산의 `visibility`(상태에 따라\n * 보이고 안 보이는 것)와는 다른 것이다. 그것은 속성 패널의 몫이다.\n *\n * 이 컴포넌트는 정본을 만지지 않는다. 무엇을 하겠다는 것만 알린다.\n */\n\n/** 한 줄 — 평가에서 이 패널이 읽는 것만. */\nexport interface FigurePartRow {\n id: string\n /** 시작 모양 이름(`startingShapeKindOfBody`): 상자 · 원기둥 … 아이콘을 고른다. */\n kind: string\n /** 작업대에서 잠깐 가린 것. 저장되지 않는다. */\n hidden?: boolean\n /** 자산이 지금 상태에서 보이지 않는다고 한 것. 저장되는 것이고, 여기서는 표시만 한다. */\n invisible?: boolean\n /** 되풀이한 부품이면 지금 몇 개로 그려지나. 되풀이는 한 부품이다 — 한 줄로 보인다. */\n copies?: number\n /**\n * 바로 붙어 있는 곳: `대상 부품 · 대상 면`. 목록은 평평한 채로 두고 보조 문구로만 적는다 — 순서도 들여쓰기도\n * 붙임 때문에 바뀌지 않는다(ADR-0097 §5.5, 설계자 2026-09-27).\n */\n fastenedTo?: string\n}\n\n/** 시작 모양마다의 아이콘. Material Symbols 이름이다. */\nconst KIND_ICON: Record<string, string> = {\n cube: 'deployed_code',\n cylinder: 'database',\n frustum: 'change_history',\n sphere: 'circle',\n 'rounded-box': 'crop_square',\n 'hollow-box': 'check_box_outline_blank',\n polygon: 'pentagon',\n pyramid: 'details',\n portal: 'door_front'\n}\n\n/** 팔레트 — 저작 API 가 만들 수 있는 것만 보인다. */\n/*\n The palette: starting shapes (ADR-0100 §2.1). A plate and a wall are boxes of a shape a person reaches for, so they are\n starting sizes, not kinds of their own. A rounded box and a box open at the top are not here: they are a box with its\n corners rounded or an inside made, from the part's own 단면 section (§3.3), so the palette does not offer a second way.\n*/\n/* A mesh part is made from a file (`import-mesh`), not from a starting size. */\nexport type FigurePaletteKind = Exclude<FigurePrimitiveCreation['kind'], 'mesh'> | 'plate' | 'wall'\nconst RECIPES: Array<{ kind: FigurePaletteKind; icon: string; label: string }> = [\n { kind: 'box', icon: 'deployed_code', label: 'figure.label.primitive-cube' },\n { kind: 'cylinder', icon: 'database', label: 'figure.label.primitive-cylinder' },\n { kind: 'sphere', icon: 'circle', label: 'figure.label.primitive-sphere' },\n { kind: 'frustum', icon: 'change_history', label: 'figure.label.primitive-frustum' },\n { kind: 'polygon', icon: 'hexagon', label: 'figure.label.primitive-polygon' },\n { kind: 'pyramid', icon: 'details', label: 'figure.label.primitive-pyramid' },\n { kind: 'plate', icon: 'check_box_outline_blank', label: 'figure.label.primitive-plate' },\n { kind: 'wall', icon: 'crop_portrait', label: 'figure.label.primitive-wall' },\n { kind: 'portal', icon: 'door_front', label: 'figure.label.primitive-portal' }\n]\n\n@customElement('figure-parts')\nexport class FigureParts 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: hidden;\n background-color: var(--md-sys-color-surface-container-lowest);\n /* The divider beside it is the editor's frame's (ox-three-pane). */\n color: var(--md-sys-color-on-surface);\n font: var(--label-font, inherit);\n font-size: var(--ui-t-small, 11.5px);\n /* Folds by its own width: the editor gives this column 140 px when it has two columns (UX, 2026-09-24). */\n container-type: inline-size;\n }\n section {\n padding: var(--spacing-medium, 8px) var(--spacing-large, 12px);\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n }\n section[list] {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n }\n h3 {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: 6px;\n margin: 0 0 6px 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 /* The cost line: the grade's letter in its colour, then what the figure costs. */\n button[model-root] {\n display: flex;\n align-items: center;\n gap: 6px;\n width: 100%;\n min-height: 32px;\n padding: 4px 8px;\n border: 1px solid transparent;\n border-radius: 6px;\n background: none;\n font: inherit;\n font-weight: 600;\n text-align: start;\n color: var(--md-sys-color-on-surface);\n cursor: pointer;\n }\n button[model-root] span {\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n }\n /* Chosen: a frame and a tint, not colour alone. */\n button[model-root][on] {\n border-color: var(--md-sys-color-primary);\n background: var(--md-sys-color-primary-container);\n }\n button[model-root]:focus-visible {\n outline: 2px solid var(--md-sys-color-primary);\n outline-offset: 1px;\n }\n /* The cost line as a line, not a card; lit under the hand like the rest of the list. */\n button[budget] {\n display: flex;\n align-items: flex-start;\n gap: 6px;\n width: 100%;\n margin: 0 0 6px;\n padding: 4px 6px;\n font: inherit;\n font-size: var(--ui-t-micro, 11px);\n line-height: 1.5;\n text-align: left;\n color: var(--md-sys-color-on-surface-variant);\n background: none;\n border: 1px solid transparent;\n border-radius: 4px;\n cursor: pointer;\n }\n button[budget]:hover {\n background: var(--md-sys-color-surface-container);\n }\n button[budget] b {\n flex: none;\n min-width: 16px;\n text-align: center;\n border-radius: 4px;\n color: var(--md-sys-color-on-primary-container);\n background: var(--md-sys-color-primary-container);\n }\n button[budget][grade='C'] b {\n color: var(--md-sys-color-on-tertiary-container);\n background: var(--md-sys-color-tertiary-container);\n }\n button[budget][grade='D'] {\n border-color: var(--md-sys-color-error);\n }\n button[budget][grade='D'] b {\n color: var(--md-sys-color-on-error);\n background: var(--md-sys-color-error);\n }\n button[budget] em {\n display: block;\n font-style: normal;\n color: var(--md-sys-color-error);\n }\n span[room][full] {\n color: var(--md-sys-color-error);\n font-weight: 600;\n }\n p[full] {\n margin: 4px 0 0;\n font-size: var(--ui-t-micro, 11px);\n color: var(--md-sys-color-error);\n }\n div[level] {\n display: grid;\n grid-template-columns: auto minmax(0, 1fr);\n align-items: center;\n gap: 6px;\n margin: 0 0 6px;\n font-size: var(--ui-t-micro, 11px);\n color: var(--md-sys-color-on-surface-variant);\n }\n /* A field in the side panel's look (side-panel-styles): 26px, a quiet ground, its line only under the hand. */\n div[level] select {\n min-width: 0;\n min-height: 26px;\n padding: 2px 4px;\n font: inherit;\n font-size: var(--ui-t-small, 11.5px);\n color: var(--md-sys-color-on-surface);\n background: var(--md-sys-color-surface-container-low);\n border: 1px solid transparent;\n border-radius: 4px;\n }\n div[level] select:hover:not(:disabled) {\n border-color: var(--md-sys-color-outline-variant);\n }\n small[copies] {\n color: var(--md-sys-color-on-surface-variant);\n }\n span[room] {\n font-size: var(--ui-t-micro, 11px);\n font-weight: 400;\n letter-spacing: 0;\n text-transform: none;\n }\n /* Three to a row, small (user, 2026-09-24): the palette is reached for, not read -- the list below is what grows. */\n div[palette] {\n display: grid;\n grid-template-columns: repeat(3, minmax(0, 1fr));\n gap: 3px;\n }\n /* A tile with no outline: its icon and name, lit under the hand -- ten outlined tiles were the boxiest part of the screen (user 2026-10-17). */\n button[shape] {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 1px;\n min-width: 0;\n padding: 5px 1px 4px;\n font: var(--label-font, inherit);\n font-size: var(--ui-t-micro, 11px);\n line-height: 1.2;\n text-align: center;\n overflow-wrap: anywhere;\n /* A tile's name on one line, so the nine stand one height; a long name ends in an ellipsis, its title says it all. */\n white-space: nowrap;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n background: none;\n border: 1px solid transparent;\n border-radius: 4px;\n cursor: pointer;\n }\n /*\n A file brought in is not a starting shape: its own row under the nine, its words on one line. As a tenth tile its\n two-word name broke onto two lines and stood it taller than the rest (2026-10-16).\n */\n button[shape] > span {\n max-width: 100%;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n /*\n The one file picker (ox-input-file, quiet) on that row: a GLB refused or going up is said on the spot. Its button\n wears the tiles' words and lit hand -- the picker's own 13/600 stood louder than the shapes' heading.\n */\n ox-input-file[mesh-file] {\n grid-column: 1 / -1;\n justify-content: center;\n font-size: var(--ui-t-micro, 11px);\n --ox-input-file-action-height: 26px;\n }\n ox-input-file[mesh-file]::part(attach) {\n gap: 6px;\n padding: 0 8px;\n font: var(--label-font, inherit);\n font-size: var(--ui-t-micro, 11px);\n font-weight: 400;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n border-radius: 4px;\n }\n ox-input-file[mesh-file]:not([disabled])::part(attach):hover {\n color: var(--md-sys-color-on-primary-container);\n background-color: var(--md-sys-color-primary-container);\n }\n ox-input-file[mesh-file][disabled]::part(attach) {\n opacity: 0.35;\n }\n ox-input-file[mesh-file] md-icon {\n --md-icon-size: 18px;\n }\n section[shapes].candrop {\n outline: 1px dashed var(--md-sys-color-primary);\n outline-offset: -1px;\n }\n /* An action in the side panel's look: icon and words in the primary colour, no pill. */\n button[recipe-catalog] {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 4px 6px;\n margin-left: -6px;\n font: inherit;\n font-size: var(--ui-t-small, 11.5px);\n color: var(--md-sys-color-primary);\n background: none;\n border: 1px solid transparent;\n border-radius: 4px;\n cursor: pointer;\n }\n button[recipe-catalog]:hover:not(:disabled) {\n background: var(--md-sys-color-surface-container);\n }\n button[shape]:hover:not([disabled]) {\n color: var(--md-sys-color-on-primary-container);\n background-color: var(--md-sys-color-primary-container);\n border-color: transparent;\n }\n button[shape][disabled] {\n opacity: 0.35;\n cursor: default;\n }\n button[shape] md-icon {\n --md-icon-size: 18px;\n }\n ol {\n margin: 0;\n padding: 0;\n list-style: none;\n display: flex;\n flex-direction: column;\n gap: 2px;\n }\n li {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 4px 6px;\n border-radius: 6px;\n cursor: pointer;\n }\n li:hover {\n background-color: var(--md-sys-color-surface-container);\n }\n li[selected] {\n color: var(--md-sys-color-on-primary-container);\n background-color: var(--md-sys-color-primary-container);\n }\n /*\n Icons by role, three sizes on the whole screen (2026-10-16; there were six): 20 for the bar, the tabs and the\n model's head, 18 for a palette tile, 16 beside words and on a row.\n */\n button[model-root] md-icon {\n --md-icon-size: 20px;\n }\n /* Beside its words, as every icon on a button with words is; without a size it stood at md-icon's own 24. */\n button[recipe-catalog] md-icon {\n --md-icon-size: 16px;\n }\n li md-icon {\n --md-icon-size: 16px;\n flex: none;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n button[eye][all] {\n margin-left: auto;\n opacity: 0.75;\n }\n button[eye][all]:hover {\n opacity: 1;\n }\n button[eye] {\n display: flex;\n flex: none;\n padding: 1px;\n color: inherit;\n background: none;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n opacity: 0;\n }\n li:hover button[eye],\n li[selected] button[eye],\n button[eye][off] {\n opacity: 1;\n }\n button[eye][off] {\n color: var(--md-sys-color-tertiary);\n }\n button[eye] md-icon {\n --md-icon-size: 16px;\n color: inherit;\n }\n li[off] span[label],\n li[off] > md-icon,\n li[invisible] span[label],\n li[invisible] > md-icon {\n opacity: 0.45;\n }\n li[selected] md-icon {\n color: inherit;\n }\n span[label] {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: var(--ui-t-body, 13px);\n }\n span[label] small {\n margin-left: 4px;\n font-size: var(--ui-t-micro, 11px);\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n div[ops] {\n display: none;\n gap: 1px;\n }\n li[selected] div[ops] {\n display: flex;\n }\n div[ops] button {\n display: flex;\n padding: 2px;\n color: inherit;\n background: none;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n }\n div[ops] button:hover:not([disabled]) {\n background-color: var(--md-sys-color-surface-container-lowest);\n }\n div[ops] button[disabled] {\n opacity: 0.3;\n cursor: default;\n }\n div[ops] md-icon {\n --md-icon-size: 16px;\n color: inherit;\n }\n /*\n Under 200 px the selected row's four buttons pushed its name out and the last one past the edge (seen at 140,\n 2026-09-24). They go to a line of their own under the name.\n */\n @container (max-width: 199px) {\n li[selected] {\n flex-wrap: wrap;\n }\n li[selected] div[ops] {\n flex-basis: 100%;\n justify-content: flex-end;\n }\n }\n p[quiet] {\n margin: 0;\n font-size: var(--ui-t-small, 11.5px);\n line-height: 1.6;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n `\n ]\n\n @property({ type: Array }) parts: FigurePartRow[] = []\n /** 고른 부품의 id. 자리(index)가 아니다 — 부품은 id 로 안정하게 가리킨다. */\n @property({ type: String }) selected = ''\n /** The model's own name, the list's root: choosing it shows the model's settings (B.1). */\n @property({ type: String }) modelName = ''\n /** 편집할 수 없을 때(불러오는 중 · 저장 중) 팔레트와 조작을 잠근다. */\n @property({ type: Boolean }) locked = false\n /**\n * 이 도형이 치르는 값 — 삼각형 · 묶음 · 한 화면에 몇 대 · 등급. 부품을 더하는 바로 그 자리에서 보여야 로우폴리의\n * 기준이 저작을 이끈다. 등급이 D 면 무엇이 넘쳤는지를 적는다. 누르면 성숙도 탭이 열린다.\n */\n @property({ attribute: false }) budget?: FigureBudgetLine\n /**\n * 디테일 등급(S/M/L)의 부품 한도(사용자 2026-09-24). 부품을 넣기 **전에** 몇 개 남았는지 보이고, 다 차면 팔레트가\n * 잠긴다. 되풀이는 한 부품으로 센다. 등급을 안 정했으면 한도는 없다.\n */\n @property({ attribute: false }) partBudget?: { level: DetailLevel | null; used: number; limit: number | null }\n /** How many recipe templates the catalog holds; none, and the section is not drawn. */\n @property({ type: Number }) recipeCount = 0\n /** How far the GLB being brought in has gone up (0‒1), while it does; the picker shows it. */\n @property({ type: Number }) meshProgress?: number\n\n /* A GLB dragged over the shapes is a part brought in: the section is where it is dropped (page standard §7-1). */\n @query('section[shapes]') private shapes?: HTMLElement\n\n firstUpdated() {\n /* The drop area is drawn by the first render; the picker is told it by the next. */\n this.requestUpdate()\n }\n\n render() {\n const b = this.partBudget\n const full = !!b && b.limit !== null && b.used >= b.limit\n return html`\n <section shapes>\n <h3>${i18next.t('figure.label.shapes')}</h3>\n <div palette>\n ${RECIPES.map(\n recipe => html`\n <button\n shape\n ?disabled=${this.locked || full}\n title=${full ? i18next.t('figure.text.part-limit-reached') : i18next.t(recipe.label)}\n @click=${() => this.ask('add-part', { kind: recipe.kind })}\n >\n <md-icon>${recipe.icon}</md-icon>\n <span>${i18next.t(recipe.label)}</span>\n </button>\n `\n )}\n <ox-input-file\n quiet\n hide-filelist\n mesh-file\n accept=\".glb,model/gltf-binary\"\n .label=${i18next.t('figure.button.import-mesh')}\n title=${full ? i18next.t('figure.text.part-limit-reached') : i18next.t('figure.text.import-mesh-hint')}\n ?disabled=${this.locked || full}\n .dropTarget=${this.shapes}\n .progress=${this.meshProgress}\n @change=${(e: CustomEvent) => {\n const file = (e.detail as File[] | undefined)?.[0]\n if (file) this.ask('import-mesh', { file })\n ;(e.currentTarget as HTMLElement & { reset(): void }).reset()\n }}\n ><md-icon slot=\"icon\">view_in_ar</md-icon></ox-input-file>\n </div>\n ${full ? html`<p full>${i18next.t('figure.text.part-limit-reached-short', { level: b!.level, limit: b!.limit })}</p>` : nothing}\n </section>\n ${this.renderRecipes(full)}\n\n <section list>\n <!--\n The list's root: the model by its name. Choosing it shows the model's settings -- not \"select all\", not \"fit the\n view\" -- the same move as the model name in the properties panel's path (designer 2026-09-27, B.1).\n -->\n <button\n model-root\n aria-pressed=${String(!this.selected)}\n ?on=${!this.selected}\n title=${i18next.t('figure.button.show-model-settings')}\n aria-label=${`${i18next.t('figure.button.show-model-settings')}: ${this.modelName || i18next.t('figure.text.new-figure-name')}`}\n @click=${() => this.dispatchEvent(new CustomEvent('show-model-settings', { bubbles: true, composed: true }))}\n >\n <md-icon>category</md-icon><span>${this.modelName || i18next.t('figure.text.new-figure-name')}</span>\n </button>\n <h3>\n <span>${i18next.t('figure.label.parts')}</span>\n <span room ?full=${full} title=${i18next.t('figure.text.limit-comes-from-level')}\n >${b && b.limit !== null ? i18next.t('figure.text.n-of-limit', { used: b.used, limit: b.limit }) : (b?.used ?? this.parts.length)}</span\n >\n ${this.renderShowAll()}\n </h3>\n ${this.renderLevel()}\n ${this.renderBudget()}\n ${this.parts.length === 0\n ? html`<p quiet>${i18next.t('figure.text.no-parts-yet')}</p>`\n : html`<ol>\n ${this.parts.map((part, index) => this.renderRow(part, index))}\n </ol>`}\n </section>\n `\n }\n\n /** 디테일 등급 — 고르면 한도가 바뀐다. 목록 머리 아래. */\n private renderLevel() {\n const b = this.partBudget\n /* Not before the first part: a level with nothing to limit was the first choice a new figure met (U09). Once set, it shows. */\n if (!b || (!this.parts.length && !b.level)) return nothing\n return html`<div level>\n <label for=\"detail-level\">${i18next.t('figure.label.detail-level')}</label>\n <select\n id=\"detail-level\"\n title=${i18next.t('figure.text.detail-level-explained')}\n .value=${b.level ?? ''}\n ?disabled=${this.locked}\n @change=${(e: Event) => {\n const value = (e.target as HTMLSelectElement).value\n this.dispatchEvent(new CustomEvent('set-detail-level', { detail: { level: value || null }, bubbles: true, composed: true }))\n }}\n >\n <option value=\"\" ?selected=${!b.level}>${i18next.t('figure.label.detail-none')}</option>\n ${DETAIL_LEVELS.map(\n one => html`<option value=${one} ?selected=${one === b.level}>\n ${one} ${i18next.t('figure.label.detail-' + one)} · ${i18next.t('figure.text.n-parts-at-most', { limit: PART_LIMIT[one] })}\n </option>`\n )}\n </select>\n </div>`\n }\n\n private renderBudget() {\n const b = this.budget\n if (!b || !this.parts.length) return nothing\n const over = b.grade === 'D'\n return html`<button\n budget\n grade=${b.grade}\n title=${i18next.t('figure.text.parts-budget-open')}\n @click=${() => this.dispatchEvent(new CustomEvent('show-maturity', { bubbles: true, composed: true }))}\n >\n <b>${b.grade}</b>\n <span>\n <strong performance-cost>${i18next.t('figure.label.performance-cost')}</strong>\n ${i18next.t('figure.text.parts-budget', {\n triangles: b.triangles.toLocaleString(),\n groups: b.groups.toLocaleString(),\n instances: b.instances.toLocaleString()\n })}\n ${over\n ? html`<em\n >${i18next.t('figure.text.parts-budget-over', {\n budget: i18next.t('figure.label.budget-' + b.tightest.key),\n used: b.tightest.used.toLocaleString(),\n limit: b.tightest.limit.toLocaleString()\n })}</em\n >`\n : nothing}\n </span>\n </button>`\n }\n\n /** 한꺼번에 감추고 한꺼번에 되돌린다 — 하나라도 감춰져 있으면 다 되돌리고, 다 보이면 다 감춘다. */\n private renderShowAll() {\n if (this.parts.length === 0) return nothing\n const anyHidden = this.parts.some(part => part.hidden)\n return html`\n <button\n eye\n all\n ?off=${!anyHidden}\n title=${i18next.t(anyHidden ? 'figure.text.show-all-parts' : 'figure.text.hide-all-parts')}\n @click=${(e: Event) => {\n e.stopPropagation()\n this.ask('set-all-parts-hidden', { hidden: !anyHidden })\n }}\n >\n <md-icon>${anyHidden ? 'visibility' : 'visibility_off'}</md-icon>\n </button>\n `\n }\n\n private renderRow(part: FigurePartRow, index: number) {\n const selected = part.id === this.selected\n const off = !!part.hidden\n return html`\n <li\n ?selected=${selected}\n ?off=${off}\n ?invisible=${!!part.invisible}\n @click=${() => this.ask('select-part', { id: part.id })}\n >\n <button\n eye\n ?off=${off}\n title=${i18next.t(off ? 'figure.text.show-this-part' : 'figure.text.hide-this-part')}\n @click=${(e: Event) => {\n e.stopPropagation()\n this.ask('toggle-part-hidden', { id: part.id })\n }}\n >\n <md-icon>${off ? 'visibility_off' : 'visibility'}</md-icon>\n </button>\n <md-icon>${KIND_ICON[part.kind] ?? 'deployed_code'}</md-icon>\n <span label title=${part.id}\n >${partLabelOf(part.id)}${part.copies ? html` <small copies>× ${part.copies}</small>` : nothing}${part.invisible ? html`<small>${i18next.t('figure.label.state-hidden')}</small>` : nothing}${part.fastenedTo\n ? html`<small fastened-to title=${i18next.t('figure.label.fastened-to')}>${part.fastenedTo}</small>`\n : nothing}</span\n >\n ${selected ? this.renderOps(part.id, index) : nothing}\n </li>\n `\n }\n\n /** 고른 줄의 조작. 못 하는 것은 빼지 않고 흐리게 둔다 — 자리가 밀리면 연달아 지우다 엉뚱한 것을 누른다. */\n private renderOps(id: string, index: number) {\n const count = this.parts.length\n const op = (name: string, icon: string, detail: object, enabled = true) => html`\n <button\n op=${name}\n ?disabled=${!enabled || this.locked}\n title=${i18next.t('figure.button.' + name)}\n @click=${(e: Event) => {\n e.stopPropagation()\n if (enabled && !this.locked) this.ask(name, detail)\n }}\n >\n <md-icon>${icon}</md-icon>\n </button>\n `\n return html`\n <div ops>\n ${op('move-part-up', 'arrow_upward', { id, to: index - 1 }, index > 0)}\n ${op('move-part-down', 'arrow_downward', { id, to: index + 1 }, index < count - 1)}\n ${op('duplicate-part', 'content_copy', { id })} ${op('remove-part', 'delete', { id })}\n </div>\n `\n }\n\n /**\n * Recipes (ADR-0095): the catalog of templates -- a door, a roller row -- opened over the modeller, put onto the chosen\n * part in one step.\n */\n private renderRecipes(full: boolean) {\n if (!this.recipeCount) return nothing\n return html`<section recipes>\n <h3>${i18next.t('figure.label.recipes')}</h3>\n <button recipe-catalog ?disabled=${this.locked || full} @click=${() => this.ask('open-recipe-catalog', {})}>\n <md-icon>widgets</md-icon>${i18next.t('figure.button.open-recipe-catalog', { count: this.recipeCount })}\n </button>\n </section>`\n }\n\n /** 정본을 고치지 않는다 — 무엇을 하겠다는 것만 알린다. */\n private ask(what: string, detail: object) {\n this.dispatchEvent(new CustomEvent(what, { detail, bubbles: true, composed: true }))\n }\n}\n"]}
|
|
@@ -24,6 +24,8 @@ export declare class FigureProperties extends FigureProperties_base {
|
|
|
24
24
|
/** The saved row, if any. Category and tags live on it. */
|
|
25
25
|
figureId?: string;
|
|
26
26
|
figureCategory?: string;
|
|
27
|
+
/** The categories the server allows (LV figure-server design G5): chosen from, and fixed once set. Null: free text. */
|
|
28
|
+
figureCategories?: string[] | null;
|
|
27
29
|
figureTags?: string[];
|
|
28
30
|
/** The part and face picked for the mounting face, before 「이 면으로」 is pressed. */
|
|
29
31
|
private pickedPart?;
|
|
@@ -44,6 +46,8 @@ export declare class FigureProperties extends FigureProperties_base {
|
|
|
44
46
|
private renderPlacement;
|
|
45
47
|
private renderSpace;
|
|
46
48
|
private renderMounting;
|
|
49
|
+
/** The sections an app added (G11), after the panel's own, for the figures they are for. A section that throws says so in its place. */
|
|
50
|
+
private renderAppSections;
|
|
47
51
|
private renderCatalog;
|
|
48
52
|
private declare;
|
|
49
53
|
private command;
|
|
@@ -8,6 +8,7 @@ import { FIGURE_FACES } from '@hatiolab/figure-model';
|
|
|
8
8
|
import { occupancyFromParts } from './occupancy.js';
|
|
9
9
|
import { tagsOf, figureFigureFacts } from './figure-facts.js';
|
|
10
10
|
import { offeredMountingOf } from './mounting-part.js';
|
|
11
|
+
import { figurePropertySections } from './property-sections.js';
|
|
11
12
|
import { SidePanelStyles } from '@things-factory/figure-viewer/dist-client/side-panel-styles.js';
|
|
12
13
|
import { ScrollbarStyles } from '@operato/styles';
|
|
13
14
|
const PLACEMENTS = ['floor', 'ceiling', 'center', 'wall'];
|
|
@@ -104,7 +105,8 @@ let FigureProperties = class FigureProperties extends localize(i18next)(LitEleme
|
|
|
104
105
|
<section>
|
|
105
106
|
<h3>${i18next.t('figure.label.group-catalog')}</h3>
|
|
106
107
|
${this.renderCatalog()}
|
|
107
|
-
</section
|
|
108
|
+
</section>
|
|
109
|
+
${this.renderAppSections()}`;
|
|
108
110
|
}
|
|
109
111
|
renderName(facts) {
|
|
110
112
|
return html `
|
|
@@ -197,17 +199,49 @@ let FigureProperties = class FigureProperties extends localize(i18next)(LitEleme
|
|
|
197
199
|
</div>
|
|
198
200
|
`;
|
|
199
201
|
}
|
|
202
|
+
/** The sections an app added (G11), after the panel's own, for the figures they are for. A section that throws says so in its place. */
|
|
203
|
+
renderAppSections() {
|
|
204
|
+
const asset = this.revision?.asset;
|
|
205
|
+
if (!asset)
|
|
206
|
+
return nothing;
|
|
207
|
+
const figure = { id: this.figureId, category: this.figureCategory };
|
|
208
|
+
return figurePropertySections().map(section => {
|
|
209
|
+
try {
|
|
210
|
+
if (!section.when({ asset, figure }))
|
|
211
|
+
return nothing;
|
|
212
|
+
return html `<section data-section=${section.id}>
|
|
213
|
+
<h3>${section.title()}</h3>
|
|
214
|
+
${section.render({ asset, figure, command: action => this.command(action) })}
|
|
215
|
+
</section>`;
|
|
216
|
+
}
|
|
217
|
+
catch (error) {
|
|
218
|
+
console.error(`[figure-ui] the property section '${section.id}' failed`, error);
|
|
219
|
+
return html `<section data-section=${section.id}>
|
|
220
|
+
<p role="alert">${i18next.t('figure.text.property-section-failed', { section: section.id, reason: error?.message ?? String(error) })}</p>
|
|
221
|
+
</section>`;
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
}
|
|
200
225
|
renderCatalog() {
|
|
201
226
|
const saved = !!this.figureId;
|
|
202
227
|
return html `
|
|
203
228
|
<div field>
|
|
204
229
|
<label>${i18next.t('figure.label.category')}</label>
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
230
|
+
${this.figureCategories?.length
|
|
231
|
+
? html `<select
|
|
232
|
+
category
|
|
233
|
+
?disabled=${!saved || !!this.figureCategory}
|
|
234
|
+
@change=${(e) => this.catalog({ category: e.target.value })}
|
|
235
|
+
>
|
|
236
|
+
${this.figureCategory ? nothing : html `<option value="" selected disabled>${i18next.t('figure.text.category-choose')}</option>`}
|
|
237
|
+
${this.figureCategories.map(code => html `<option value=${code} ?selected=${code === this.figureCategory}>${code}</option>`)}
|
|
238
|
+
</select>`
|
|
239
|
+
: html `<input
|
|
240
|
+
.value=${this.figureCategory ?? ''}
|
|
241
|
+
?disabled=${!saved}
|
|
242
|
+
placeholder=${i18next.t('figure.text.category-placeholder')}
|
|
243
|
+
@change=${(e) => this.catalog({ category: e.target.value.trim() })}
|
|
244
|
+
/>`}
|
|
211
245
|
</div>
|
|
212
246
|
<div field>
|
|
213
247
|
<label>${i18next.t('figure.label.tags')}</label>
|
|
@@ -245,6 +279,10 @@ __decorate([
|
|
|
245
279
|
property({ type: String }),
|
|
246
280
|
__metadata("design:type", String)
|
|
247
281
|
], FigureProperties.prototype, "figureCategory", void 0);
|
|
282
|
+
__decorate([
|
|
283
|
+
property({ attribute: false }),
|
|
284
|
+
__metadata("design:type", Object)
|
|
285
|
+
], FigureProperties.prototype, "figureCategories", void 0);
|
|
248
286
|
__decorate([
|
|
249
287
|
property({ type: Array }),
|
|
250
288
|
__metadata("design:type", Array)
|