@things-factory/figure-ui 10.1.224 → 10.1.226
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/consumer-capabilities.ts +15 -0
- package/client/modeller/editor-styles.ts +21 -3
- package/client/modeller/editor.ts +41 -27
- package/client/modeller/figure-capability-panel.ts +37 -10
- package/client/modeller/figure-clips.ts +3 -2
- package/client/modeller/figure-controls.ts +4 -3
- package/client/modeller/figure-data-panel.ts +41 -21
- package/client/modeller/figure-links.ts +5 -5
- package/client/modeller/figure-material-panel.ts +2 -0
- package/client/modeller/figure-preview.ts +115 -8
- package/client/modeller/figure-recipe-catalog.ts +4 -0
- package/client/modeller/figure-slot-layout-panel.ts +25 -8
- package/client/modeller/figure-verdict.ts +4 -1
- package/client/modeller/product-session.ts +3 -2
- package/client/modeller/unit-words.ts +11 -0
- package/client/pages/figure-modeller-page.ts +5 -4
- package/dist-client/modeller/consumer-capabilities.d.ts +11 -0
- package/dist-client/modeller/consumer-capabilities.js +14 -0
- package/dist-client/modeller/consumer-capabilities.js.map +1 -1
- package/dist-client/modeller/editor-styles.js +21 -3
- package/dist-client/modeller/editor-styles.js.map +1 -1
- package/dist-client/modeller/editor.js +43 -29
- package/dist-client/modeller/editor.js.map +1 -1
- package/dist-client/modeller/figure-capability-panel.d.ts +3 -0
- package/dist-client/modeller/figure-capability-panel.js +35 -11
- package/dist-client/modeller/figure-capability-panel.js.map +1 -1
- package/dist-client/modeller/figure-clips.js +3 -2
- package/dist-client/modeller/figure-clips.js.map +1 -1
- package/dist-client/modeller/figure-controls.js +4 -3
- package/dist-client/modeller/figure-controls.js.map +1 -1
- package/dist-client/modeller/figure-data-panel.js +42 -21
- package/dist-client/modeller/figure-data-panel.js.map +1 -1
- package/dist-client/modeller/figure-links.js +5 -5
- package/dist-client/modeller/figure-links.js.map +1 -1
- package/dist-client/modeller/figure-material-panel.js +2 -0
- package/dist-client/modeller/figure-material-panel.js.map +1 -1
- package/dist-client/modeller/figure-preview.d.ts +3 -0
- package/dist-client/modeller/figure-preview.js +124 -8
- package/dist-client/modeller/figure-preview.js.map +1 -1
- package/dist-client/modeller/figure-recipe-catalog.js +4 -0
- package/dist-client/modeller/figure-recipe-catalog.js.map +1 -1
- package/dist-client/modeller/figure-slot-layout-panel.d.ts +1 -1
- package/dist-client/modeller/figure-slot-layout-panel.js +25 -8
- package/dist-client/modeller/figure-slot-layout-panel.js.map +1 -1
- package/dist-client/modeller/figure-verdict.js +4 -1
- package/dist-client/modeller/figure-verdict.js.map +1 -1
- package/dist-client/modeller/product-session.d.ts +4 -2
- package/dist-client/modeller/product-session.js +3 -2
- package/dist-client/modeller/product-session.js.map +1 -1
- package/dist-client/modeller/unit-words.d.ts +5 -0
- package/dist-client/modeller/unit-words.js +12 -0
- package/dist-client/modeller/unit-words.js.map +1 -0
- package/dist-client/pages/figure-modeller-page.js +5 -4
- package/dist-client/pages/figure-modeller-page.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/package.json +10 -10
- package/test/ai-proposal.test.ts +2 -2
- package/test/browser/editing-page.js +3 -3
- package/test/browser/editing.check.mjs +5 -5
- package/test/browser/links-page.js +1 -1
- package/test/browser/links.check.mjs +2 -3
- package/test/browser/modeller-page.js +4 -4
- package/test/browser/modeller-revision.check.mjs +5 -5
- package/test/browser/modeller.check.mjs +5 -5
- package/test/browser/parts.check.mjs +2 -2
- package/test/browser/product-page.check.mjs +31 -0
- package/test/browser/props.check.mjs +3 -4
- package/test/browser/row-preview.check.mjs +3 -3
- package/test/browser/screen.check.mjs +13 -11
- package/test/browser/side-panel-sweep.check.mjs +182 -0
- package/test/consumer-capabilities.test.ts +7 -0
- package/test/figure-assistant-surface.test.ts +1 -1
- package/test/first-save.test.ts +2 -2
- package/test/links.test.ts +1 -1
- package/test/product-session.test.ts +5 -5
- package/test/proposal-record.test.ts +3 -3
- package/test/refusals.test.ts +1 -1
- package/test/revert-keeping.test.ts +1 -1
- package/translations/en.json +24 -12
- package/translations/es.json +24 -12
- package/translations/ja.json +25 -13
- package/translations/ko.json +83 -71
- package/translations/ms.json +24 -12
- package/translations/zh.json +24 -12
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"figure-material-panel.js","sourceRoot":"","sources":["../../client/modeller/figure-material-panel.ts"],"names":[],"mappings":"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","sourcesContent":["import { css, html, LitElement, nothing, svg } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport { MATERIAL_PRESETS, SEGMENT_PRESETS, figureTrianglesOf } from '@hatiolab/figure-model'\nimport { activePalette } from '@hatiolab/things-scene'\nimport type { FigureAssetGeometry, FigureMaterial, FigurePartEdit } from '@hatiolab/figure-model'\nimport { SidePanelStyles } from '@things-factory/figure-viewer/dist-client/side-panel-styles.js'\nimport { isRoundBody } from './starting-shape.js'\nimport { glowOf, materialEditOf, offeredPaletteOf } from './material-edit.js'\n\n/** The purpose each palette role is for, by its token. */\nconst PURPOSE: Record<string, string> = {\n 'palette.primary': 'primary',\n 'palette.accent': 'accent',\n 'palette.neutral': 'neutral',\n 'palette.dark': 'dark',\n 'palette.light': 'light',\n 'palette.surface': 'surface',\n 'palette.status-danger': 'danger',\n 'palette.status-warning': 'warning',\n 'palette.status-info': 'info',\n 'palette.status-success': 'success'\n}\nexport const purposeOf = (token?: string) => i18next.t(`figure.label.palette-purpose-${PURPOSE[token ?? ''] ?? 'registered'}`)\n\n/** Bodies with a rim — the ones `set-segments` accepts. */\nconst DEFAULT_SEGMENTS = 12\n\n/**\n * 재질 — a part's material.\n *\n * An earlier editor showed the colour role as a raw token (`palette.dark`) in a select and nothing else. The material\n * holds the colour role, a preset, transparency and a glow; a round body also has how finely its rim is drawn. This\n * shows those: painted colours and lit colours as two groups of swatches, each named for what it\n * is for.\n *\n * Not here, because a material has no place for them: flat shading and casting\n * shadows. The glow's colour comes from the lit colours.\n *\n * Every change leaves as one `part-edit`; the editor runs it through the session, one step of the undo history.\n */\n@customElement('figure-material-panel')\nexport class FigureMaterialPanel extends localize(i18next)(LitElement) {\n static styles = [SidePanelStyles, css`\n :host {\n display: block;\n }\n /* The rarer half, drawn inside the 「더 보기」 fold: the fold gives the padding and the line. */\n div[more] {\n display: grid;\n gap: 8px;\n }\n div[field] {\n display: grid;\n gap: 4px;\n }\n /* A slider and its value on one line, the value at the end where the eye finishes. */\n div[slider] {\n display: grid;\n grid-template-columns: minmax(0, 1fr) 3em;\n align-items: center;\n gap: 8px;\n }\n div[slider] output {\n text-align: right;\n font-variant-numeric: tabular-nums;\n }\n div[swatches] {\n display: flex;\n flex-wrap: wrap;\n gap: 4px;\n }\n button[swatch] {\n width: 22px;\n height: 22px;\n padding: 0;\n border-radius: 50%;\n border: 1px solid var(--md-sys-color-outline-variant);\n background: var(--swatch);\n cursor: pointer;\n }\n button[swatch][on] {\n outline: 2px solid var(--md-sys-color-primary);\n outline-offset: 1px;\n }\n select,\n input[type='range'] {\n font: inherit;\n min-width: 0;\n }\n /* The segment picker: the four common counts as their silhouettes with their triangle cost, the rest folded. */\n div[segments] {\n display: grid;\n grid-template-columns: repeat(4, 1fr);\n gap: 4px;\n }\n button[seg] {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 1px;\n padding: 6px 2px 4px;\n font: inherit;\n color: var(--md-sys-color-on-surface);\n background: none;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n cursor: pointer;\n }\n button[seg]:hover:not([on]) {\n background-color: var(--md-sys-color-surface-container);\n }\n button[seg][on] {\n color: var(--md-sys-color-on-primary-container);\n background-color: var(--md-sys-color-primary-container);\n border-color: transparent;\n }\n /* Small, and 12 and 16 both read as circles: large enough for the corners to show. */\n button[seg] svg {\n display: block;\n width: 32px;\n height: 32px;\n }\n button[seg] svg polygon {\n fill: var(--md-sys-color-surface-container-highest);\n stroke: currentColor;\n stroke-width: 1.1;\n stroke-linejoin: miter;\n }\n button[seg][on] svg polygon {\n fill: var(--md-sys-color-surface-container-lowest);\n }\n button[seg] b {\n font-weight: 600;\n }\n button[seg] small {\n margin: 0;\n }\n details[free] summary {\n margin-top: 4px;\n cursor: pointer;\n color: var(--md-sys-color-on-surface-variant);\n }\n div[presets] {\n display: flex;\n gap: 4px;\n margin-top: 4px;\n }\n div[presets] button {\n flex: 1;\n padding: 3px 0;\n font: inherit;\n color: var(--md-sys-color-on-surface);\n background: none;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n cursor: pointer;\n }\n div[presets] button[on] {\n color: var(--md-sys-color-on-primary-container);\n background-color: var(--md-sys-color-primary-container);\n border-color: transparent;\n }\n `]\n\n /** The selected body as evaluated — its material, its segments and its shape. */\n @property({ attribute: false }) body?: FigureAssetGeometry & { id: string }\n /** The asset's palette, token → colour. */\n @property({ attribute: false }) palette: Record<string, string> = {}\n @property({ type: Boolean }) locked = false\n /**\n * Which half to draw. `look` is what most parts need -- the colour role and the preset; `more` is what few parts\n * need -- transparency, glow and the segments of a round part -- drawn in the properties' 「더 보기」 fold.\n */\n @property({ type: String }) show: 'look' | 'more' = 'look'\n /** The glow while its slider is dragged: shown beside it and drawn on the canvas, recorded on release. */\n @state() private glowing?: number\n\n render() {\n const part = this.body\n if (!part) return nothing\n const material = part.material\n /*\n The roles offered are the app's palette, with any the asset carries beyond it. A new asset carries\n only its primary colour; a role it lacks is sent with its colour and added in the same edit (`put`).\n */\n const entries = Object.entries(this.offered())\n const painted = entries.filter(([token]) => !token.startsWith('palette.status-'))\n const lit = entries.filter(([token]) => token.startsWith('palette.status-'))\n const glow = material.emissive\n if (this.show === 'more') return this.renderMore(part, painted, lit)\n return html`<section>\n <h3>${i18next.t('figure.label.part-material')}</h3>\n <div field>\n <label>${i18next.t('figure.label.palette-role')}</label>\n <div swatches>${this.swatches(painted, material.token, token => this.put({ token }))}</div>\n ${lit.length\n ? html`<small>${i18next.t('figure.label.status-colors')}</small>\n <div swatches>${this.swatches(lit, material.token, token => this.put({ token }))}</div>`\n : nothing}\n </div>\n <div form>\n <span>${i18next.t('figure.label.preset')}</span>\n <select\n ?disabled=${this.locked}\n @change=${(e: Event) => this.put({ preset: ((e.target as HTMLSelectElement).value || undefined) as FigureMaterial['preset'] })}\n >\n <option value=\"\" ?selected=${!material.preset}>${i18next.t('figure.label.none')}</option>\n ${MATERIAL_PRESETS.map(\n preset => html`<option value=${preset} ?selected=${preset === material.preset}>${i18next.t('figure.label.preset-' + preset)}</option>`\n )}\n </select>\n </div>\n </section>`\n }\n\n /** Transparency, glow and segments: few parts need them, so they wait in the 「더 보기」 fold. */\n private renderMore(part: FigureAssetGeometry & { id: string }, painted: [string, string][], lit: [string, string][]) {\n const material = part.material\n const glow = material.emissive\n return html`<div more>\n <label toggle>\n <input\n type=\"checkbox\"\n ?disabled=${this.locked}\n .checked=${!!material.transparent}\n @change=${(e: Event) => this.put({ transparent: (e.target as HTMLInputElement).checked || undefined })}\n />\n ${i18next.t('figure.label.transparent')}\n </label>\n <div form>\n <span>${i18next.t('figure.label.emissive')}</span>\n <div slider>\n <input\n type=\"range\"\n min=\"0\"\n max=\"1\"\n step=\"0.05\"\n ?disabled=${this.locked}\n .value=${String(glow?.intensity ?? 0)}\n @input=${(e: Event) => this.glow(Number((e.target as HTMLInputElement).value), true)}\n @change=${(e: Event) => this.glow(Number((e.target as HTMLInputElement).value))}\n />\n <output>${(this.glowing ?? glow?.intensity ?? 0).toFixed(2)}</output>\n </div>\n </div>\n ${glow\n ? html`<div field>\n <label>${i18next.t('figure.label.emissive-color')}</label>\n <div swatches>${this.swatches(lit.length ? lit : painted, glow.token, token => this.put({ emissive: { ...glow, token } }))}</div>\n </div>`\n : nothing}\n ${this.renderSegments(part)}\n </div>`\n }\n\n private renderSegments(part: FigureAssetGeometry & { id: string }) {\n if (!isRoundBody((part as { shape?: never }).shape)) return nothing\n const current = part.segments ?? DEFAULT_SEGMENTS\n return html`\n <div field>\n <label>${i18next.t('figure.label.segments')}</label>\n <div segments>\n ${SEGMENT_PRESETS.slice(0, 4).map(\n preset => html`<button\n seg\n ?on=${preset === current}\n ?disabled=${this.locked}\n @click=${() => this.edit({ kind: 'set-segments', target: part.id, segments: preset })}\n >\n ${silhouette(preset)}\n <b>${preset}</b>\n <small>${i18next.t('figure.text.n-triangles', { count: figureTrianglesOf({ ...part, segments: preset }) })}</small>\n </button>`\n )}\n </div>\n <details free ?open=${SEGMENT_PRESETS.slice(4).includes(current as never)}>\n <summary>${i18next.t('figure.text.more-segment-options')}</summary>\n <div presets>\n ${SEGMENT_PRESETS.slice(4).map(\n preset => html`<button ?on=${preset === current} ?disabled=${this.locked} @click=${() => this.edit({ kind: 'set-segments', target: part.id, segments: preset })}>\n ${preset}\n </button>`\n )}\n </div>\n </details>\n </div>\n `\n }\n\n private swatches(entries: [string, string][], chosen: string | undefined, pick: (token: string) => void) {\n return entries.map(\n ([token, colour]) => html`<button\n swatch\n ?on=${token === chosen}\n ?disabled=${this.locked}\n title=${purposeOf(token)}\n aria-label=${purposeOf(token)}\n style=\"--swatch:${colour}\"\n @click=${() => pick(token)}\n ></button>`\n )\n }\n\n private offered(): Record<string, string> {\n return offeredPaletteOf(activePalette() as Record<string, string>, this.palette)\n }\n\n private glow(intensity: number, live = false) {\n this.glowing = live ? intensity : undefined\n this.put(glowOf(this.body!.material, intensity), live)\n }\n\n /** A material change: recorded, or -- while a slider is dragged -- only drawn (`part-live`). */\n private put(patch: Partial<FigureMaterial>, live = false) {\n const edit = materialEditOf(this.body!.id, this.body!.material, patch, this.palette, this.offered())\n if (live) this.dispatchEvent(new CustomEvent('part-live', { detail: { edit }, bubbles: true, composed: true }))\n else this.edit(edit)\n }\n\n private edit(edit: FigurePartEdit) {\n this.dispatchEvent(new CustomEvent('part-edit', { detail: { edit }, bubbles: true, composed: true }))\n }\n}\n\n/** A regular polygon of this many sides -- how round a body with this rim count looks from its end. */\nfunction silhouette(segments: number) {\n const points: string[] = []\n for (let i = 0; i < segments; i++) {\n const angle = -Math.PI / 2 + (i * 2 * Math.PI) / segments\n points.push(`${(16 + 13 * Math.cos(angle)).toFixed(2)},${(16 + 13 * Math.sin(angle)).toFixed(2)}`)\n }\n return svg`<svg viewBox=\"0 0 32 32\" aria-hidden=\"true\"><polygon points=${points.join(' ')}></polygon></svg>`\n}\n"]}
|
|
1
|
+
{"version":3,"file":"figure-material-panel.js","sourceRoot":"","sources":["../../client/modeller/figure-material-panel.ts"],"names":[],"mappings":"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","sourcesContent":["import { css, html, LitElement, nothing, svg } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport { MATERIAL_PRESETS, SEGMENT_PRESETS, figureTrianglesOf } from '@hatiolab/figure-model'\nimport { activePalette } from '@hatiolab/things-scene'\nimport type { FigureAssetGeometry, FigureMaterial, FigurePartEdit } from '@hatiolab/figure-model'\nimport { SidePanelStyles } from '@things-factory/figure-viewer/dist-client/side-panel-styles.js'\nimport { isRoundBody } from './starting-shape.js'\nimport { glowOf, materialEditOf, offeredPaletteOf } from './material-edit.js'\n\n/** The purpose each palette role is for, by its token. */\nconst PURPOSE: Record<string, string> = {\n 'palette.primary': 'primary',\n 'palette.accent': 'accent',\n 'palette.neutral': 'neutral',\n 'palette.dark': 'dark',\n 'palette.light': 'light',\n 'palette.surface': 'surface',\n 'palette.status-danger': 'danger',\n 'palette.status-warning': 'warning',\n 'palette.status-info': 'info',\n 'palette.status-success': 'success'\n}\nexport const purposeOf = (token?: string) => i18next.t(`figure.label.palette-purpose-${PURPOSE[token ?? ''] ?? 'registered'}`)\n\n/** Bodies with a rim — the ones `set-segments` accepts. */\nconst DEFAULT_SEGMENTS = 12\n\n/**\n * 재질 — a part's material.\n *\n * An earlier editor showed the colour role as a raw token (`palette.dark`) in a select and nothing else. The material\n * holds the colour role, a preset, transparency and a glow; a round body also has how finely its rim is drawn. This\n * shows those: painted colours and lit colours as two groups of swatches, each named for what it\n * is for.\n *\n * Not here, because a material has no place for them: flat shading and casting\n * shadows. The glow's colour comes from the lit colours.\n *\n * Every change leaves as one `part-edit`; the editor runs it through the session, one step of the undo history.\n */\n@customElement('figure-material-panel')\nexport class FigureMaterialPanel extends localize(i18next)(LitElement) {\n static styles = [SidePanelStyles, css`\n :host {\n display: block;\n }\n /* The rarer half, drawn inside the 「더 보기」 fold: the fold gives the padding and the line. */\n div[more] {\n display: grid;\n gap: 8px;\n }\n div[field] {\n display: grid;\n gap: 4px;\n }\n /* A slider and its value on one line, the value at the end where the eye finishes. */\n div[slider] {\n display: grid;\n grid-template-columns: minmax(0, 1fr) 3em;\n align-items: center;\n gap: 8px;\n }\n div[slider] output {\n text-align: right;\n font-variant-numeric: tabular-nums;\n }\n div[swatches] {\n display: flex;\n flex-wrap: wrap;\n gap: 4px;\n }\n button[swatch] {\n width: 22px;\n height: 22px;\n padding: 0;\n border-radius: 50%;\n border: 1px solid var(--md-sys-color-outline-variant);\n background: var(--swatch);\n cursor: pointer;\n }\n button[swatch][on] {\n outline: 2px solid var(--md-sys-color-primary);\n outline-offset: 1px;\n }\n select,\n input[type='range'] {\n font: inherit;\n min-width: 0;\n }\n /* The segment picker: the four common counts as their silhouettes with their triangle cost, the rest folded. */\n div[segments] {\n display: grid;\n grid-template-columns: repeat(4, 1fr);\n gap: 4px;\n }\n button[seg] {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 1px;\n padding: 6px 2px 4px;\n font: inherit;\n color: var(--md-sys-color-on-surface);\n background: none;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n cursor: pointer;\n }\n button[seg]:hover:not([on]) {\n background-color: var(--md-sys-color-surface-container);\n }\n button[seg][on] {\n color: var(--md-sys-color-on-primary-container);\n background-color: var(--md-sys-color-primary-container);\n border-color: transparent;\n }\n /* Small, and 12 and 16 both read as circles: large enough for the corners to show. */\n button[seg] svg {\n display: block;\n width: 32px;\n height: 32px;\n }\n button[seg] svg polygon {\n fill: var(--md-sys-color-surface-container-highest);\n stroke: currentColor;\n stroke-width: 1.1;\n stroke-linejoin: miter;\n }\n button[seg][on] svg polygon {\n fill: var(--md-sys-color-surface-container-lowest);\n }\n button[seg] b {\n font-weight: 600;\n }\n button[seg] small {\n margin: 0;\n }\n details[free] summary {\n margin-top: 4px;\n cursor: pointer;\n color: var(--md-sys-color-on-surface-variant);\n }\n div[presets] {\n display: flex;\n gap: 4px;\n margin-top: 4px;\n }\n div[presets] button {\n flex: 1;\n padding: 3px 0;\n font: inherit;\n color: var(--md-sys-color-on-surface);\n background: none;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n cursor: pointer;\n }\n div[presets] button[on] {\n color: var(--md-sys-color-on-primary-container);\n background-color: var(--md-sys-color-primary-container);\n border-color: transparent;\n }\n `]\n\n /** The selected body as evaluated — its material, its segments and its shape. */\n @property({ attribute: false }) body?: FigureAssetGeometry & { id: string }\n /** The asset's palette, token → colour. */\n @property({ attribute: false }) palette: Record<string, string> = {}\n @property({ type: Boolean }) locked = false\n /**\n * Which half to draw. `look` is what most parts need -- the colour role and the preset; `more` is what few parts\n * need -- transparency, glow and the segments of a round part -- drawn in the properties' 「더 보기」 fold.\n */\n @property({ type: String }) show: 'look' | 'more' = 'look'\n /** The glow while its slider is dragged: shown beside it and drawn on the canvas, recorded on release. */\n @state() private glowing?: number\n\n render() {\n const part = this.body\n if (!part) return nothing\n const material = part.material\n /*\n The roles offered are the app's palette, with any the asset carries beyond it. A new asset carries\n only its primary colour; a role it lacks is sent with its colour and added in the same edit (`put`).\n */\n const entries = Object.entries(this.offered())\n const painted = entries.filter(([token]) => !token.startsWith('palette.status-'))\n const lit = entries.filter(([token]) => token.startsWith('palette.status-'))\n const glow = material.emissive\n if (this.show === 'more') return this.renderMore(part, painted, lit)\n return html`<section>\n <h3>${i18next.t('figure.label.part-material')}</h3>\n <div field>\n <label>${i18next.t('figure.label.palette-role')}</label>\n <div swatches>${this.swatches(painted, material.token, token => this.put({ token }))}</div>\n ${lit.length\n ? html`<small>${i18next.t('figure.label.status-colors')}</small>\n <div swatches>${this.swatches(lit, material.token, token => this.put({ token }))}</div>`\n : nothing}\n </div>\n <div form>\n <span>${i18next.t('figure.label.preset')}</span>\n <select\n ?disabled=${this.locked}\n @change=${(e: Event) => this.put({ preset: ((e.target as HTMLSelectElement).value || undefined) as FigureMaterial['preset'] })}\n >\n <option value=\"\" ?selected=${!material.preset}>${i18next.t('figure.label.none')}</option>\n ${MATERIAL_PRESETS.map(\n preset => html`<option value=${preset} ?selected=${preset === material.preset}>${i18next.t('figure.label.preset-' + preset)}</option>`\n )}\n </select>\n </div>\n </section>`\n }\n\n /** Transparency, glow and segments: few parts need them, so they wait in the 「더 보기」 fold. */\n private renderMore(part: FigureAssetGeometry & { id: string }, painted: [string, string][], lit: [string, string][]) {\n const material = part.material\n const glow = material.emissive\n /* Under its own title: below 「단면」 without one, transparency and glow read as the section's (sweep 2026-10-27 U21). */\n return html`<div more>\n <h3>${i18next.t('figure.label.part-material')}</h3>\n <label toggle>\n <input\n type=\"checkbox\"\n ?disabled=${this.locked}\n .checked=${!!material.transparent}\n @change=${(e: Event) => this.put({ transparent: (e.target as HTMLInputElement).checked || undefined })}\n />\n ${i18next.t('figure.label.transparent')}\n </label>\n <div form>\n <span>${i18next.t('figure.label.emissive')}</span>\n <div slider>\n <input\n type=\"range\"\n min=\"0\"\n max=\"1\"\n step=\"0.05\"\n ?disabled=${this.locked}\n .value=${String(glow?.intensity ?? 0)}\n @input=${(e: Event) => this.glow(Number((e.target as HTMLInputElement).value), true)}\n @change=${(e: Event) => this.glow(Number((e.target as HTMLInputElement).value))}\n />\n <output>${(this.glowing ?? glow?.intensity ?? 0).toFixed(2)}</output>\n </div>\n </div>\n ${glow\n ? html`<div field>\n <label>${i18next.t('figure.label.emissive-color')}</label>\n <div swatches>${this.swatches(lit.length ? lit : painted, glow.token, token => this.put({ emissive: { ...glow, token } }))}</div>\n </div>`\n : nothing}\n ${this.renderSegments(part)}\n </div>`\n }\n\n private renderSegments(part: FigureAssetGeometry & { id: string }) {\n if (!isRoundBody((part as { shape?: never }).shape)) return nothing\n const current = part.segments ?? DEFAULT_SEGMENTS\n return html`\n <div field>\n <label>${i18next.t('figure.label.segments')}</label>\n <div segments>\n ${SEGMENT_PRESETS.slice(0, 4).map(\n preset => html`<button\n seg\n ?on=${preset === current}\n ?disabled=${this.locked}\n @click=${() => this.edit({ kind: 'set-segments', target: part.id, segments: preset })}\n >\n ${silhouette(preset)}\n <b>${preset}</b>\n <small>${i18next.t('figure.text.n-triangles', { count: figureTrianglesOf({ ...part, segments: preset }) })}</small>\n </button>`\n )}\n </div>\n <details free ?open=${SEGMENT_PRESETS.slice(4).includes(current as never)}>\n <summary>${i18next.t('figure.text.more-segment-options')}</summary>\n <div presets>\n ${SEGMENT_PRESETS.slice(4).map(\n preset => html`<button ?on=${preset === current} ?disabled=${this.locked} @click=${() => this.edit({ kind: 'set-segments', target: part.id, segments: preset })}>\n ${preset}\n </button>`\n )}\n </div>\n </details>\n </div>\n `\n }\n\n private swatches(entries: [string, string][], chosen: string | undefined, pick: (token: string) => void) {\n return entries.map(\n ([token, colour]) => html`<button\n swatch\n ?on=${token === chosen}\n ?disabled=${this.locked}\n title=${purposeOf(token)}\n aria-label=${purposeOf(token)}\n style=\"--swatch:${colour}\"\n @click=${() => pick(token)}\n ></button>`\n )\n }\n\n private offered(): Record<string, string> {\n return offeredPaletteOf(activePalette() as Record<string, string>, this.palette)\n }\n\n private glow(intensity: number, live = false) {\n this.glowing = live ? intensity : undefined\n this.put(glowOf(this.body!.material, intensity), live)\n }\n\n /** A material change: recorded, or -- while a slider is dragged -- only drawn (`part-live`). */\n private put(patch: Partial<FigureMaterial>, live = false) {\n const edit = materialEditOf(this.body!.id, this.body!.material, patch, this.palette, this.offered())\n if (live) this.dispatchEvent(new CustomEvent('part-live', { detail: { edit }, bubbles: true, composed: true }))\n else this.edit(edit)\n }\n\n private edit(edit: FigurePartEdit) {\n this.dispatchEvent(new CustomEvent('part-edit', { detail: { edit }, bubbles: true, composed: true }))\n }\n}\n\n/** A regular polygon of this many sides -- how round a body with this rim count looks from its end. */\nfunction silhouette(segments: number) {\n const points: string[] = []\n for (let i = 0; i < segments; i++) {\n const angle = -Math.PI / 2 + (i * 2 * Math.PI) / segments\n points.push(`${(16 + 13 * Math.cos(angle)).toFixed(2)},${(16 + 13 * Math.sin(angle)).toFixed(2)}`)\n }\n return svg`<svg viewBox=\"0 0 32 32\" aria-hidden=\"true\"><polygon points=${points.join(' ')}></polygon></svg>`\n}\n"]}
|
|
@@ -46,12 +46,15 @@ export declare class FigurePreview extends FigurePreview_base {
|
|
|
46
46
|
controls?: Record<string, number>;
|
|
47
47
|
} | undefined;
|
|
48
48
|
private scene?;
|
|
49
|
+
private tags;
|
|
50
|
+
private tagFrame;
|
|
49
51
|
private standing?;
|
|
50
52
|
private registered?;
|
|
51
53
|
private watcher?;
|
|
52
54
|
render(): import("lit-html").TemplateResult<1>;
|
|
53
55
|
/** The one figure's size: a slider per axis the figure lets change, and what it is at the size tried. */
|
|
54
56
|
private renderSizes;
|
|
57
|
+
private followTags;
|
|
55
58
|
private chooseWay;
|
|
56
59
|
/** The one figure at another size on an axis, held to what the figure declares, its centre kept on the board's. */
|
|
57
60
|
private tryAxis;
|
|
@@ -2,6 +2,8 @@ import { __decorate, __metadata } from "tslib";
|
|
|
2
2
|
import { css, html, LitElement, nothing, unsafeCSS } from 'lit';
|
|
3
3
|
import { customElement, property, query, state } from 'lit/decorators.js';
|
|
4
4
|
import { i18next, localize } from '@operato/i18n';
|
|
5
|
+
import { unitWord } from './unit-words.js';
|
|
6
|
+
import * as THREE from 'three';
|
|
5
7
|
import { compileFigureAsset } from '@hatiolab/figure-model';
|
|
6
8
|
import { create, figureLegendSourcesChanged, registerFigure, unregisterFigure } from '@hatiolab/things-scene';
|
|
7
9
|
import { DEFAULT_VIEW, previewBoardDepth, sceneLook, WORKBENCH } from '@things-factory/figure-viewer/dist-client/figure-view.js';
|
|
@@ -41,6 +43,9 @@ let FigurePreview = class FigurePreview extends localize(i18next)(LitElement) {
|
|
|
41
43
|
/** One figure stretched by sliders, or the eight ways of growing side by side. */
|
|
42
44
|
this.way = 'one';
|
|
43
45
|
this.board = { width: 0, height: 0 };
|
|
46
|
+
/* The eight variants named over each one, where the camera shows it (sweep 2026-10-27 M4). */
|
|
47
|
+
this.tags = [];
|
|
48
|
+
this.tagFrame = 0;
|
|
44
49
|
}
|
|
45
50
|
static { this.styles = css `
|
|
46
51
|
:host {
|
|
@@ -63,6 +68,23 @@ let FigurePreview = class FigurePreview extends localize(i18next)(LitElement) {
|
|
|
63
68
|
stage (the 1024 width, both side panels open) the controls lay over the help's button. One row that wraps keeps both
|
|
64
69
|
whole: side by side when there is room, the controls above the help when there is not.
|
|
65
70
|
*/
|
|
71
|
+
/* A variant's name over its box: small, on the stage, never in the way of the pointer. */
|
|
72
|
+
div[tags] {
|
|
73
|
+
position: absolute;
|
|
74
|
+
inset: 0;
|
|
75
|
+
overflow: hidden;
|
|
76
|
+
pointer-events: none;
|
|
77
|
+
}
|
|
78
|
+
div[tags] span {
|
|
79
|
+
position: absolute;
|
|
80
|
+
transform: translate(-50%, calc(-100% - 4px));
|
|
81
|
+
padding: 1px 6px;
|
|
82
|
+
white-space: nowrap;
|
|
83
|
+
font-size: var(--ui-t-micro, 11px);
|
|
84
|
+
color: rgba(255, 255, 255, 0.92);
|
|
85
|
+
background-color: rgba(28, 28, 30, 0.72);
|
|
86
|
+
border-radius: 4px;
|
|
87
|
+
}
|
|
66
88
|
div[foot] {
|
|
67
89
|
position: absolute;
|
|
68
90
|
left: 8px;
|
|
@@ -78,7 +100,26 @@ let FigurePreview = class FigurePreview extends localize(i18next)(LitElement) {
|
|
|
78
100
|
div[foot] > * { pointer-events: auto; }
|
|
79
101
|
div[setup] { max-width: min(320px, 100%); padding: 12px; border-radius: 8px; background: rgba(28,28,30,.9); color: #fff; display: grid; gap: 8px; }
|
|
80
102
|
div[setup] p { margin: 0; line-height: 1.5; }
|
|
81
|
-
|
|
103
|
+
/* The help's choice and button in the look of the controls beside it, not the browser's grey (sweep 2026-10-27 U7). */
|
|
104
|
+
div[setup] select,
|
|
105
|
+
div[setup] button {
|
|
106
|
+
min-width: 0;
|
|
107
|
+
padding: 4px 8px;
|
|
108
|
+
font: inherit;
|
|
109
|
+
color: rgba(255, 255, 255, 0.92);
|
|
110
|
+
background-color: rgba(255, 255, 255, 0.14);
|
|
111
|
+
border: 1px solid rgba(255, 255, 255, 0.22);
|
|
112
|
+
border-radius: 5px;
|
|
113
|
+
}
|
|
114
|
+
div[setup] button {
|
|
115
|
+
cursor: pointer;
|
|
116
|
+
}
|
|
117
|
+
div[setup] button:hover {
|
|
118
|
+
background-color: rgba(255, 255, 255, 0.24);
|
|
119
|
+
}
|
|
120
|
+
div[setup] option {
|
|
121
|
+
color: initial;
|
|
122
|
+
}
|
|
82
123
|
/* Which is which: side by side means nothing without a legend. */
|
|
83
124
|
div[legend] {
|
|
84
125
|
position: absolute;
|
|
@@ -148,22 +189,29 @@ let FigurePreview = class FigurePreview extends localize(i18next)(LitElement) {
|
|
|
148
189
|
margin-left: 4px;
|
|
149
190
|
color: #ffcc80;
|
|
150
191
|
}
|
|
192
|
+
/*
|
|
193
|
+
One grid for every row, each row its subgrid: the rows' names in one column, so a value's field starts where the one
|
|
194
|
+
above it does whatever the names' lengths (a row of its own grid started 28px off, sweep 2026-10-27 U7).
|
|
195
|
+
*/
|
|
151
196
|
div[clips] {
|
|
152
197
|
margin-left: auto;
|
|
153
|
-
display:
|
|
154
|
-
|
|
155
|
-
gap: 6px;
|
|
198
|
+
display: grid;
|
|
199
|
+
grid-template-columns: auto 20px 96px 34px;
|
|
200
|
+
gap: 6px 8px;
|
|
201
|
+
align-items: center;
|
|
156
202
|
padding: 8px 10px;
|
|
157
203
|
color: rgba(255, 255, 255, 0.92);
|
|
158
204
|
background-color: rgba(28, 28, 30, 0.72);
|
|
159
205
|
border-radius: 8px;
|
|
160
206
|
font-size: var(--ui-t-micro, 11px);
|
|
161
207
|
}
|
|
208
|
+
div[clips] > * {
|
|
209
|
+
grid-column: 1 / -1;
|
|
210
|
+
}
|
|
162
211
|
div[clips] label {
|
|
163
212
|
display: grid;
|
|
164
|
-
grid-template-columns:
|
|
213
|
+
grid-template-columns: subgrid;
|
|
165
214
|
align-items: center;
|
|
166
|
-
gap: 8px;
|
|
167
215
|
}
|
|
168
216
|
/* A motion group's clip: as wide as the dial, slider and value together, so its name reads. */
|
|
169
217
|
div[clips] select {
|
|
@@ -213,6 +261,20 @@ let FigurePreview = class FigurePreview extends localize(i18next)(LitElement) {
|
|
|
213
261
|
width: 96px;
|
|
214
262
|
accent-color: var(--md-sys-color-primary);
|
|
215
263
|
}
|
|
264
|
+
/* A value typed, not slid: in the look of the group's clip choice, starting where the sliders start. */
|
|
265
|
+
div[clips] input:not([type='range']) {
|
|
266
|
+
box-sizing: border-box;
|
|
267
|
+
min-width: 0;
|
|
268
|
+
font: inherit;
|
|
269
|
+
color: inherit;
|
|
270
|
+
background-color: rgba(255, 255, 255, 0.14);
|
|
271
|
+
border: 1px solid rgba(255, 255, 255, 0.22);
|
|
272
|
+
border-radius: 5px;
|
|
273
|
+
}
|
|
274
|
+
div[clips] input:not([type='range']):not([type='number']) {
|
|
275
|
+
grid-column: 3 / -1;
|
|
276
|
+
width: auto;
|
|
277
|
+
}
|
|
216
278
|
div[clips] output {
|
|
217
279
|
font-variant-numeric: tabular-nums;
|
|
218
280
|
text-align: right;
|
|
@@ -261,6 +323,7 @@ let FigurePreview = class FigurePreview extends localize(i18next)(LitElement) {
|
|
|
261
323
|
/* The part the help speaks of: the one picked in it, else the one chosen in the editor, else the first. */
|
|
262
324
|
const setupPart = setupPartOf(this.setupPart, this.selected, asset?.appearance.map(p => p.target) ?? []);
|
|
263
325
|
return html `<div stage></div>
|
|
326
|
+
${this.tags.length ? html `<div tags>${this.tags.map(t => html `<span style=${`left:${t.x}px;top:${t.y}px`}>${t.name}</span>`)}</div>` : nothing}
|
|
264
327
|
${this.cannot
|
|
265
328
|
? html `<div cannot>${this.cannot}</div>`
|
|
266
329
|
: html `<div legend>
|
|
@@ -304,11 +367,56 @@ let FigurePreview = class FigurePreview extends localize(i18next)(LitElement) {
|
|
|
304
367
|
<p said>${previewSizeWordsOf(asset, size, say)}</p>
|
|
305
368
|
</div>`;
|
|
306
369
|
}
|
|
370
|
+
/*
|
|
371
|
+
Each variant's name over its top, followed while the eight stand: the legend alone listed 「x · y · x·y …」 with
|
|
372
|
+
nothing saying which box was which (sweep 2026-10-27 M4).
|
|
373
|
+
*/
|
|
374
|
+
followTags() {
|
|
375
|
+
cancelAnimationFrame(this.tagFrame);
|
|
376
|
+
if (this.way !== 'eight' || !this.scene) {
|
|
377
|
+
if (this.tags.length)
|
|
378
|
+
this.tags = [];
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
const names = new Map(PREVIEW_WAYS.map(way => [`at-${way.name}`, way.axes.length ? way.name : i18next.t('figure.label.preview-base')]));
|
|
382
|
+
const box = new THREE.Box3();
|
|
383
|
+
const top = new THREE.Vector3();
|
|
384
|
+
const step = () => {
|
|
385
|
+
const root = this.scene?.root;
|
|
386
|
+
const camera = root?.activeCamera3d;
|
|
387
|
+
const canvas = root?.renderer3d?.domElement;
|
|
388
|
+
if (!camera || !canvas || this.way !== 'eight')
|
|
389
|
+
return;
|
|
390
|
+
const at = canvas.getBoundingClientRect();
|
|
391
|
+
const host = this.getBoundingClientRect();
|
|
392
|
+
const tags = [];
|
|
393
|
+
for (const component of root?.components ?? []) {
|
|
394
|
+
const name = names.get(component.model?.id ?? '');
|
|
395
|
+
const object = component._realObject?.object3d;
|
|
396
|
+
if (!name || !object)
|
|
397
|
+
continue;
|
|
398
|
+
box.setFromObject(object);
|
|
399
|
+
if (box.isEmpty())
|
|
400
|
+
continue;
|
|
401
|
+
box.getCenter(top);
|
|
402
|
+
top.y = box.max.y;
|
|
403
|
+
top.project(camera);
|
|
404
|
+
if (top.z > 1)
|
|
405
|
+
continue;
|
|
406
|
+
tags.push({ name, x: Math.round((top.x * 0.5 + 0.5) * at.width + at.left - host.left), y: Math.round((-top.y * 0.5 + 0.5) * at.height + at.top - host.top) });
|
|
407
|
+
}
|
|
408
|
+
if (JSON.stringify(tags) !== JSON.stringify(this.tags))
|
|
409
|
+
this.tags = tags;
|
|
410
|
+
this.tagFrame = requestAnimationFrame(step);
|
|
411
|
+
};
|
|
412
|
+
this.tagFrame = requestAnimationFrame(step);
|
|
413
|
+
}
|
|
307
414
|
chooseWay(way) {
|
|
308
415
|
if (way === this.way)
|
|
309
416
|
return;
|
|
310
417
|
this.way = way;
|
|
311
418
|
this.standUp();
|
|
419
|
+
this.followTags();
|
|
312
420
|
/* The content is made again inside the scene standing, which keeps the camera: from one figure to eight it stood inside them. */
|
|
313
421
|
requestAnimationFrame(() => this.fitView());
|
|
314
422
|
}
|
|
@@ -344,7 +452,7 @@ let FigurePreview = class FigurePreview extends localize(i18next)(LitElement) {
|
|
|
344
452
|
const step = Math.max((control.max - control.min) / 100, 0.01);
|
|
345
453
|
const label = asset.stateInputs?.[control.id]?.label ?? control.id;
|
|
346
454
|
return html `<label>
|
|
347
|
-
<span>${label} <span drive>${control.unit}</span></span>
|
|
455
|
+
<span>${label} <span drive>${unitWord(control.unit)}</span></span>
|
|
348
456
|
${this.renderDial(control, value)}
|
|
349
457
|
<input
|
|
350
458
|
type="range"
|
|
@@ -403,7 +511,7 @@ let FigurePreview = class FigurePreview extends localize(i18next)(LitElement) {
|
|
|
403
511
|
if (node.kind === 'number') {
|
|
404
512
|
const ranged = node.min !== undefined && node.max !== undefined && node.max > node.min;
|
|
405
513
|
return html `<label>
|
|
406
|
-
<span>${label} <span drive>${node.unit
|
|
514
|
+
<span>${label} <span drive>${unitWord(node.unit)}</span></span>
|
|
407
515
|
<span></span>
|
|
408
516
|
${ranged
|
|
409
517
|
? html `<input type="range" min=${node.min} max=${node.max} step=${Math.max((node.max - node.min) / 100, 0.01)} .value=${String(value ?? node.min)} @input=${(e) => this.value(row, e.target.value)} />
|
|
@@ -590,6 +698,7 @@ let FigurePreview = class FigurePreview extends localize(i18next)(LitElement) {
|
|
|
590
698
|
this.cannot = '';
|
|
591
699
|
this.tellLegends();
|
|
592
700
|
this.tellSample();
|
|
701
|
+
this.followTags();
|
|
593
702
|
return;
|
|
594
703
|
}
|
|
595
704
|
catch {
|
|
@@ -610,12 +719,15 @@ let FigurePreview = class FigurePreview extends localize(i18next)(LitElement) {
|
|
|
610
719
|
this.scene.root?.fitCameraToModel?.();
|
|
611
720
|
this.cannot = '';
|
|
612
721
|
this.tellSample();
|
|
722
|
+
this.followTags();
|
|
613
723
|
}
|
|
614
724
|
catch (error) {
|
|
615
725
|
this.cannot = error.message;
|
|
616
726
|
}
|
|
617
727
|
}
|
|
618
728
|
teardown() {
|
|
729
|
+
cancelAnimationFrame(this.tagFrame);
|
|
730
|
+
this.tags = [];
|
|
619
731
|
this.scene?.dispose?.();
|
|
620
732
|
this.scene = undefined;
|
|
621
733
|
this.standing = undefined;
|
|
@@ -688,6 +800,10 @@ __decorate([
|
|
|
688
800
|
state(),
|
|
689
801
|
__metadata("design:type", Object)
|
|
690
802
|
], FigurePreview.prototype, "trying", void 0);
|
|
803
|
+
__decorate([
|
|
804
|
+
state(),
|
|
805
|
+
__metadata("design:type", Array)
|
|
806
|
+
], FigurePreview.prototype, "tags", void 0);
|
|
691
807
|
FigurePreview = __decorate([
|
|
692
808
|
customElement('figure-preview')
|
|
693
809
|
], FigurePreview);
|