@things-factory/figure-ui 10.1.153 → 10.1.155
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/canvas.ts +1 -1
- package/client/modeller/editor.ts +2 -2
- package/client/modeller/figure-capability-panel.ts +6 -24
- package/client/modeller/figure-carrier.ts +1 -1
- package/client/modeller/figure-clips.ts +1 -1
- package/client/modeller/figure-controls.ts +2 -2
- package/client/modeller/figure-face-mapping.ts +1 -1
- package/client/modeller/figure-links.ts +3 -2
- package/client/modeller/figure-material-panel.ts +1 -1
- package/client/modeller/figure-preview.ts +2 -2
- package/client/modeller/figure-properties.ts +1 -1
- package/client/modeller/figure-section-editor.ts +1 -1
- package/client/modeller/figure-section-panel.ts +1 -1
- package/client/modeller/figure-settings.ts +2 -2
- package/client/modeller/figure-verdict.ts +1 -1
- package/client/pages/figure-list-page.ts +7 -1
- package/client/viewparts/figure-detail-dock.ts +90 -41
- package/client/viewparts/figure-thumb.ts +2 -22
- package/dist-client/modeller/canvas.d.ts +1 -1
- package/dist-client/modeller/canvas.js +1 -1
- package/dist-client/modeller/canvas.js.map +1 -1
- package/dist-client/modeller/editor.js +2 -2
- package/dist-client/modeller/editor.js.map +1 -1
- package/dist-client/modeller/figure-capability-panel.d.ts +0 -3
- package/dist-client/modeller/figure-capability-panel.js +5 -18
- package/dist-client/modeller/figure-capability-panel.js.map +1 -1
- package/dist-client/modeller/figure-carrier.js +1 -1
- package/dist-client/modeller/figure-carrier.js.map +1 -1
- package/dist-client/modeller/figure-clips.js +1 -1
- package/dist-client/modeller/figure-clips.js.map +1 -1
- package/dist-client/modeller/figure-controls.js +2 -2
- package/dist-client/modeller/figure-controls.js.map +1 -1
- package/dist-client/modeller/figure-face-mapping.js +1 -1
- package/dist-client/modeller/figure-face-mapping.js.map +1 -1
- package/dist-client/modeller/figure-links.js +3 -2
- package/dist-client/modeller/figure-links.js.map +1 -1
- package/dist-client/modeller/figure-material-panel.js +1 -1
- package/dist-client/modeller/figure-material-panel.js.map +1 -1
- package/dist-client/modeller/figure-preview.d.ts +1 -1
- package/dist-client/modeller/figure-preview.js +2 -2
- package/dist-client/modeller/figure-preview.js.map +1 -1
- package/dist-client/modeller/figure-properties.js +1 -1
- package/dist-client/modeller/figure-properties.js.map +1 -1
- package/dist-client/modeller/figure-section-editor.js +1 -1
- package/dist-client/modeller/figure-section-editor.js.map +1 -1
- package/dist-client/modeller/figure-section-panel.js +1 -1
- package/dist-client/modeller/figure-section-panel.js.map +1 -1
- package/dist-client/modeller/figure-settings.d.ts +1 -1
- package/dist-client/modeller/figure-settings.js +1 -1
- package/dist-client/modeller/figure-settings.js.map +1 -1
- package/dist-client/modeller/figure-verdict.js +1 -1
- package/dist-client/modeller/figure-verdict.js.map +1 -1
- package/dist-client/pages/figure-list-page.d.ts +1 -0
- package/dist-client/pages/figure-list-page.js +11 -1
- package/dist-client/pages/figure-list-page.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/viewparts/figure-detail-dock.d.ts +10 -1
- package/dist-client/viewparts/figure-detail-dock.js +93 -41
- package/dist-client/viewparts/figure-detail-dock.js.map +1 -1
- package/dist-client/viewparts/figure-thumb.d.ts +1 -0
- package/dist-client/viewparts/figure-thumb.js +2 -22
- package/dist-client/viewparts/figure-thumb.js.map +1 -1
- package/package.json +5 -4
- package/test/browser/detail-page.js +10 -2
- package/test/browser/product-page.check.mjs +1 -1
- package/test/browser/screen-page.js +1 -1
- package/test/browser/serve-detail.mjs +7 -2
- package/test/figure-controls-keys.test.ts +19 -0
- package/test/i18n-prefix-guard.test.ts +4 -1
- package/test/layout.test.ts +4 -3
- package/test/links.test.ts +5 -4
- package/test/properties.test.ts +2 -1
- package/test/refusals.test.ts +2 -1
- package/test/theme.test.ts +1 -1
- package/test/verdict.test.ts +2 -1
- package/test/words.ts +20 -0
- package/translations/en.json +2 -33
- package/translations/es.json +2 -33
- package/translations/ja.json +2 -33
- package/translations/ko.json +2 -33
- package/translations/ms.json +2 -33
- package/translations/zh.json +2 -33
- package/client/modeller/controls.ts +0 -54
- package/client/modeller/figure-view.ts +0 -151
- package/client/modeller/preview-model.ts +0 -89
- package/client/modeller/side-panel-styles.ts +0 -374
- package/client/viewparts/detail-motion.ts +0 -11
- package/client/viewparts/figure-asset-viewer.ts +0 -177
- package/dist-client/modeller/controls.d.ts +0 -28
- package/dist-client/modeller/controls.js +0 -28
- package/dist-client/modeller/controls.js.map +0 -1
- package/dist-client/modeller/figure-view.d.ts +0 -117
- package/dist-client/modeller/figure-view.js +0 -78
- package/dist-client/modeller/figure-view.js.map +0 -1
- package/dist-client/modeller/preview-model.d.ts +0 -54
- package/dist-client/modeller/preview-model.js +0 -79
- package/dist-client/modeller/preview-model.js.map +0 -1
- package/dist-client/modeller/side-panel-styles.d.ts +0 -27
- package/dist-client/modeller/side-panel-styles.js +0 -374
- package/dist-client/modeller/side-panel-styles.js.map +0 -1
- package/dist-client/viewparts/detail-motion.d.ts +0 -6
- package/dist-client/viewparts/detail-motion.js +0 -9
- package/dist-client/viewparts/detail-motion.js.map +0 -1
- package/dist-client/viewparts/figure-asset-viewer.d.ts +0 -31
- package/dist-client/viewparts/figure-asset-viewer.js +0 -198
- package/dist-client/viewparts/figure-asset-viewer.js.map +0 -1
- package/test/controls.test.ts +0 -60
- package/test/detail-motion.test.ts +0 -26
- package/test/preview.test.ts +0 -53
- package/test/view.test.ts +0 -13
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
import '@material/web/icon/icon.js';
|
|
1
2
|
import { LitElement } from 'lit';
|
|
2
3
|
import type { Figure } from '../types.js';
|
|
3
|
-
import '
|
|
4
|
+
import '@things-factory/figure-viewer/dist-client/figure-asset-viewer.js';
|
|
5
|
+
import '@things-factory/figure-viewer/dist-client/figure-facts-list.js';
|
|
6
|
+
/** Said on window when a figure's card picture was replaced from the preview: `{ detail: { id } }`. */
|
|
7
|
+
export declare const FIGURE_THUMBNAIL_CHANGED = "figure-thumbnail-changed";
|
|
4
8
|
export interface FigureDetailRecord {
|
|
5
9
|
saved: Figure;
|
|
6
10
|
released?: {
|
|
@@ -13,6 +17,11 @@ declare const FigureDetailDock_base: (new (...args: any[]) => LitElement) & type
|
|
|
13
17
|
export declare class FigureDetailDock extends FigureDetailDock_base {
|
|
14
18
|
static styles: import("lit").CSSResult[];
|
|
15
19
|
load: (id: string) => Promise<FigureDetailRecord>;
|
|
20
|
+
/**
|
|
21
|
+
* Keeps the picture taken in the preview as the figure's card picture. A patch of the picture alone does not make a
|
|
22
|
+
* released figure a draft (`updateFigure`). The catalogue is told, and reads its cards again.
|
|
23
|
+
*/
|
|
24
|
+
keepThumbnail: (id: string, dataUrl: string) => Promise<unknown>;
|
|
16
25
|
private figureId;
|
|
17
26
|
private record?;
|
|
18
27
|
private asset?;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { __decorate, __metadata } from "tslib";
|
|
2
|
+
import '@material/web/icon/icon.js';
|
|
2
3
|
import { css, html, LitElement, nothing } from 'lit';
|
|
3
4
|
import { customElement, property, state } from 'lit/decorators.js';
|
|
4
5
|
import { i18next, localize } from '@operato/i18n';
|
|
@@ -8,11 +9,15 @@ import { ScrollbarStyles } from '@operato/styles';
|
|
|
8
9
|
import { ASSISTANT_DOCK_VIEWPART } from '@things-factory/ai-assistant/dist-client/components/assistant-dock.js';
|
|
9
10
|
import { figureCostOf, parseFigureAsset } from '@hatiolab/figure-model';
|
|
10
11
|
import { capacityFrom } from '../modeller/scene-budget.js';
|
|
11
|
-
import {
|
|
12
|
+
import { SidePanelStyles } from '@things-factory/figure-viewer/dist-client/side-panel-styles.js';
|
|
13
|
+
import { fetchFigure, fetchReleasedFigure, updateFigure } from '../graphql/index.js';
|
|
12
14
|
import { modellerPathOf } from '../modeller/modeller-route.js';
|
|
13
15
|
import { leftOpeningPage } from './detail-route.js';
|
|
14
|
-
import '
|
|
16
|
+
import '@things-factory/figure-viewer/dist-client/figure-asset-viewer.js';
|
|
17
|
+
import '@things-factory/figure-viewer/dist-client/figure-facts-list.js';
|
|
15
18
|
const VIEWPART = 'figure-detail';
|
|
19
|
+
/** Said on window when a figure's card picture was replaced from the preview: `{ detail: { id } }`. */
|
|
20
|
+
export const FIGURE_THUMBNAIL_CHANGED = 'figure-thumbnail-changed';
|
|
16
21
|
/** Catalogue details stay while the catalogue is shown; choosing a size or a pose has no write path. */
|
|
17
22
|
let FigureDetailDock = class FigureDetailDock extends localize(i18next)(LitElement) {
|
|
18
23
|
constructor() {
|
|
@@ -23,6 +28,14 @@ let FigureDetailDock = class FigureDetailDock extends localize(i18next)(LitEleme
|
|
|
23
28
|
throw Error(i18next.t('figure.text.detail-no-source'));
|
|
24
29
|
return { saved, released };
|
|
25
30
|
};
|
|
31
|
+
/**
|
|
32
|
+
* Keeps the picture taken in the preview as the figure's card picture. A patch of the picture alone does not make a
|
|
33
|
+
* released figure a draft (`updateFigure`). The catalogue is told, and reads its cards again.
|
|
34
|
+
*/
|
|
35
|
+
this.keepThumbnail = async (id, dataUrl) => {
|
|
36
|
+
await updateFigure(id, { thumbnail: dataUrl });
|
|
37
|
+
window.dispatchEvent(new CustomEvent(FIGURE_THUMBNAIL_CHANGED, { detail: { id } }));
|
|
38
|
+
};
|
|
26
39
|
this.figureId = '';
|
|
27
40
|
this.sourceKind = 'saved';
|
|
28
41
|
this.loading = false;
|
|
@@ -45,23 +58,41 @@ let FigureDetailDock = class FigureDetailDock extends localize(i18next)(LitEleme
|
|
|
45
58
|
hideDetail();
|
|
46
59
|
};
|
|
47
60
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
61
|
+
/*
|
|
62
|
+
The modeller's side panel look (SidePanelStyles). What the preview is opened for sits above the model -- name, type,
|
|
63
|
+
which model is shown, how many fit -- as plain text; the controls are borderless icons and quiet text, so the model
|
|
64
|
+
and its numbers are what the eye meets.
|
|
65
|
+
*/
|
|
66
|
+
static { this.styles = [ScrollbarStyles, SidePanelStyles, css `
|
|
67
|
+
:host { display: flex; flex-direction: column; width: 400px; max-width: 100vw; height: 100%; min-height: 0;
|
|
68
|
+
background: var(--md-sys-color-surface); border-left: 1px solid var(--md-sys-color-outline-variant); }
|
|
69
|
+
header { display: flex; align-items: flex-start; gap: 2px; padding: 12px 8px 4px 16px; }
|
|
70
|
+
[title-block] { flex: 1; min-width: 0; display: grid; gap: 2px; }
|
|
71
|
+
h2 { margin: 0; font-size: var(--ui-t-title, 16px); font-weight: 600; line-height: 1.3; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
72
|
+
code { font-family: var(--mono-font, monospace); color: var(--md-sys-color-on-surface-variant); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
73
|
+
button { font: inherit; color: inherit; cursor: pointer; }
|
|
74
|
+
button:focus-visible { outline: 2px solid var(--md-sys-color-primary); outline-offset: 1px; }
|
|
75
|
+
button[icon] { display: inline-grid; place-items: center; width: 28px; height: 28px; padding: 0; border: none; border-radius: 6px;
|
|
76
|
+
background: transparent; color: var(--md-sys-color-on-surface-variant); }
|
|
77
|
+
button[icon]:hover:not(:disabled) { background: var(--md-sys-color-surface-container-high); color: var(--md-sys-color-on-surface); }
|
|
78
|
+
button[icon]:disabled { opacity: 0.38; cursor: default; }
|
|
79
|
+
button[icon] md-icon { --md-icon-size: 18px; }
|
|
54
80
|
[body] { flex: 1; overflow: auto; min-height: 0; }
|
|
55
|
-
[meta]
|
|
56
|
-
|
|
57
|
-
[
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
81
|
+
[meta] { display: grid; gap: 8px; padding: 0 16px 12px; }
|
|
82
|
+
/* Which model is shown: words that change colour, not boxes. */
|
|
83
|
+
[source] { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 12px; color: var(--md-sys-color-on-surface-variant); }
|
|
84
|
+
[source] button { padding: 0; border: none; background: none; color: var(--md-sys-color-on-surface-variant); }
|
|
85
|
+
[source] button:hover { color: var(--md-sys-color-on-surface); }
|
|
86
|
+
[source] button[aria-pressed='true'] { color: var(--md-sys-color-primary); font-weight: 600; }
|
|
87
|
+
dl { margin: 0; }
|
|
88
|
+
dt { color: var(--md-sys-color-on-surface-variant); }
|
|
89
|
+
dd { margin: 0; color: var(--md-sys-color-on-surface); }
|
|
90
|
+
dl[stats] { display: flex; flex-wrap: wrap; gap: 4px 16px; }
|
|
91
|
+
dl[stats] > div { display: flex; align-items: baseline; gap: 4px; }
|
|
92
|
+
dl[stats] dd { font-weight: 600; font-variant-numeric: tabular-nums; }
|
|
61
93
|
p { margin: 0; line-height: 1.5; }
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
footer button { width: 100%; background: var(--md-sys-color-primary-container); color: var(--md-sys-color-on-primary-container); }
|
|
94
|
+
[description] { color: var(--md-sys-color-on-surface-variant); }
|
|
95
|
+
[status] { display: grid; gap: 8px; justify-items: start; padding: 12px 16px; }
|
|
65
96
|
@media (max-width: 1439.98px) {
|
|
66
97
|
:host { position: fixed; inset: var(--figure-detail-top, 44px) 0 0 auto; height: calc(100dvh - var(--figure-detail-top, 44px)); z-index: 11; box-shadow: -4px 0 16px rgb(0 0 0 / 20%); }
|
|
67
98
|
}
|
|
@@ -133,41 +164,58 @@ let FigureDetailDock = class FigureDetailDock extends localize(i18next)(LitEleme
|
|
|
133
164
|
}
|
|
134
165
|
render() {
|
|
135
166
|
const saved = this.record?.saved;
|
|
167
|
+
const released = this.record?.released;
|
|
136
168
|
const cost = this.cost;
|
|
137
169
|
const capacity = cost ? capacityFrom(cost.groups, cost.triangles) : undefined;
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
@change=${(e) => this.choose(e.target.value)}>
|
|
147
|
-
${this.record?.released ? html `<option value="released">${i18next.t('figure.label.detail-released', { version: this.record.released.version })}</option>` : nothing}
|
|
148
|
-
<option value="saved">${i18next.t('figure.label.detail-saved')} · ${i18next.t(saved.state === 'released' ? 'figure.label.figure-released' : 'figure.label.draft')}</option>
|
|
149
|
-
</select></label>
|
|
150
|
-
${!this.record?.released ? html `<p>${i18next.t('figure.text.detail-unreleased')}</p>` : nothing}
|
|
151
|
-
</section>` : nothing}
|
|
152
|
-
${this.asset ? html `<figure-asset-viewer .asset=${this.asset}></figure-asset-viewer>
|
|
153
|
-
<section summary><h3>${i18next.t('figure.label.detail-summary')}</h3><dl>
|
|
154
|
-
<dt>${i18next.t('figure.label.budget-parts')}</dt><dd>${this.asset.appearance.length}</dd>
|
|
155
|
-
<dt>${i18next.t('figure.label.budget-groups')}</dt><dd>${cost?.groups ?? '—'}</dd>
|
|
156
|
-
<dt>${i18next.t('figure.label.triangles')}</dt><dd>${cost?.triangles ?? '—'}</dd>
|
|
157
|
-
</dl>${capacity ? html `<p>${i18next.t('figure.text.parts-budget', { triangles: cost.triangles, groups: cost.groups, instances: capacity.instances })}</p>` : nothing}
|
|
158
|
-
</section>` : nothing}
|
|
159
|
-
</div>
|
|
160
|
-
<footer><button ?disabled=${!saved} @click=${() => {
|
|
170
|
+
const savedLabel = saved
|
|
171
|
+
? `${i18next.t('figure.label.detail-saved')} · ${i18next.t(saved.state === 'released' ? 'figure.label.figure-released' : 'figure.label.draft')}`
|
|
172
|
+
: '';
|
|
173
|
+
return html `<header>
|
|
174
|
+
<div title-block><h2 title=${saved?.name || ''}>${saved?.name || i18next.t('figure.title.detail')}</h2>
|
|
175
|
+
${saved ? html `<code title=${fullName(saved)}>${fullName(saved)}</code>` : nothing}</div>
|
|
176
|
+
<button icon edit aria-label=${i18next.t('figure.button.detail-edit')} title=${i18next.t('figure.button.detail-edit')} ?disabled=${!saved}
|
|
177
|
+
@click=${() => {
|
|
161
178
|
// The modeller shows the figure itself; a preview of it beside the editor only covers the work.
|
|
162
179
|
hideDetail();
|
|
163
180
|
navigate(modellerPathOf(saved));
|
|
164
|
-
}}
|
|
181
|
+
}}><md-icon>edit</md-icon></button>
|
|
182
|
+
<button icon aria-label=${i18next.t('figure.button.close')} title=${i18next.t('figure.button.close')} @click=${() => hideDetail()}><md-icon>close</md-icon></button>
|
|
183
|
+
</header>
|
|
184
|
+
<div body>
|
|
185
|
+
${this.loading ? html `<p status role="status">${i18next.t('figure.text.detail-loading')}</p>` : nothing}
|
|
186
|
+
${this.error ? html `<div status role="alert"><p>${this.error}</p><button act @click=${() => this.show(this.figureId)}><md-icon>refresh</md-icon>${i18next.t('figure.button.detail-retry')}</button></div>` : nothing}
|
|
187
|
+
${saved ? html `<section meta>
|
|
188
|
+
${released
|
|
189
|
+
? html `<div source role="group" aria-label=${i18next.t('figure.label.detail-source')}>
|
|
190
|
+
<button aria-pressed=${String(this.sourceKind === 'released')} @click=${() => this.choose('released')}>${i18next.t('figure.label.detail-released', { version: released.version })}</button>
|
|
191
|
+
<button aria-pressed=${String(this.sourceKind === 'saved')} @click=${() => this.choose('saved')}>${savedLabel}</button>
|
|
192
|
+
</div>`
|
|
193
|
+
: html `<div source><span title=${i18next.t('figure.text.detail-unreleased')}>${savedLabel}</span></div>`}
|
|
194
|
+
${this.asset ? html `<dl stats>
|
|
195
|
+
<div><dt>${i18next.t('figure.label.placement-limit')}</dt><dd>${capacity
|
|
196
|
+
? i18next.t('figure.text.fits-n-instances', { n: capacity.instances.toLocaleString() })
|
|
197
|
+
: i18next.t('figure.text.not-measured-yet')}</dd></div>
|
|
198
|
+
<div><dt>${i18next.t('figure.label.triangles')}</dt><dd>${cost?.triangles.toLocaleString() ?? '—'}</dd></div>
|
|
199
|
+
<div><dt>${i18next.t('figure.label.budget-groups')}</dt><dd>${cost?.groups ?? '—'}</dd></div>
|
|
200
|
+
<div><dt>${i18next.t('figure.label.budget-parts')}</dt><dd>${this.asset.appearance.length}</dd></div>
|
|
201
|
+
</dl>` : nothing}
|
|
202
|
+
${saved.description ? html `<p description>${saved.description}</p>` : nothing}
|
|
203
|
+
</section>` : nothing}
|
|
204
|
+
${this.asset ? html `<figure-asset-viewer .asset=${this.asset}
|
|
205
|
+
.keepShot=${saved?.id ? (dataUrl) => this.keepThumbnail(saved.id, dataUrl) : undefined}>
|
|
206
|
+
${saved ? html `<figure-facts-list slot="facts" .asset=${this.asset} .row=${saved}></figure-facts-list>` : nothing}
|
|
207
|
+
</figure-asset-viewer>` : nothing}
|
|
208
|
+
</div>`;
|
|
165
209
|
}
|
|
166
210
|
};
|
|
167
211
|
__decorate([
|
|
168
212
|
property({ attribute: false }),
|
|
169
213
|
__metadata("design:type", Function)
|
|
170
214
|
], FigureDetailDock.prototype, "load", void 0);
|
|
215
|
+
__decorate([
|
|
216
|
+
property({ attribute: false }),
|
|
217
|
+
__metadata("design:type", Function)
|
|
218
|
+
], FigureDetailDock.prototype, "keepThumbnail", void 0);
|
|
171
219
|
__decorate([
|
|
172
220
|
state(),
|
|
173
221
|
__metadata("design:type", Object)
|
|
@@ -200,6 +248,10 @@ FigureDetailDock = __decorate([
|
|
|
200
248
|
customElement('figure-detail-dock')
|
|
201
249
|
], FigureDetailDock);
|
|
202
250
|
export { FigureDetailDock };
|
|
251
|
+
/** The name a board stores: `{namespace}/{type}` (ADR-0114). */
|
|
252
|
+
function fullName(figure) {
|
|
253
|
+
return figure.namespace ? `${figure.namespace}/${figure.type}` : figure.type;
|
|
254
|
+
}
|
|
203
255
|
function visibleBar(root) {
|
|
204
256
|
for (const element of root.querySelectorAll('*')) {
|
|
205
257
|
if (element.tagName === 'OX-CANVAS-BAR' && element.getBoundingClientRect().height > 0)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"figure-detail-dock.js","sourceRoot":"","sources":["../../client/viewparts/figure-detail-dock.ts"],"names":[],"mappings":"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","sourcesContent":["import { css, html, LitElement, nothing } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { i18next, localize } from '@operato/i18n'\nimport { appendViewpart, UPDATE_VIEWPART, VIEWPART_POSITION, VIEWPART_LEVEL } from '@operato/layout'\nimport { navigate, store } from '@operato/shell'\nimport { ScrollbarStyles } from '@operato/styles'\nimport { ASSISTANT_DOCK_VIEWPART } from '@things-factory/ai-assistant/dist-client/components/assistant-dock.js'\nimport { figureCostOf, parseFigureAsset, type FigureAsset } from '@hatiolab/figure-model'\nimport { capacityFrom } from '../modeller/scene-budget.js'\nimport { fetchFigure, fetchReleasedFigure } from '../graphql/index.js'\nimport { modellerPathOf } from '../modeller/modeller-route.js'\nimport { leftOpeningPage } from './detail-route.js'\nimport type { Figure } from '../types.js'\nimport './figure-asset-viewer.js'\n\nconst VIEWPART = 'figure-detail'\nexport interface FigureDetailRecord {\n saved: Figure\n released?: { version: number; source: string }\n}\n\n/** Catalogue details stay while the catalogue is shown; choosing a size or a pose has no write path. */\n@customElement('figure-detail-dock')\nexport class FigureDetailDock extends localize(i18next)(LitElement) {\n static styles = [ScrollbarStyles, css`\n :host { display: flex; flex-direction: column; width: 440px; max-width: 100vw; height: 100%; min-height: 0;\n color: var(--md-sys-color-on-surface); background: var(--md-sys-color-surface); border-left: 1px solid var(--md-sys-color-outline-variant); font-size: 13px; }\n header { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 12px 16px; border-bottom: 1px solid var(--md-sys-color-outline-variant); }\n h2 { margin: 0; font-size: var(--ui-t-title, 16px); }\n button, select { font: inherit; color: inherit; padding: 8px 12px; border: 1px solid var(--md-sys-color-outline); border-radius: 8px; background: var(--md-sys-color-surface); cursor: pointer; }\n [body] { flex: 1; overflow: auto; min-height: 0; }\n [meta], [status] { padding: 12px 16px; }\n [meta] { display: grid; gap: 8px; }\n [summary] { padding: 12px 16px; border-bottom: 1px solid var(--md-sys-color-outline-variant); }\n h3 { margin: 0 0 8px; font-size: var(--ui-t-body, 13px); }\n dl { display: grid; grid-template-columns: 1fr auto; gap: 6px 12px; margin: 0; }\n dd { margin: 0; font-variant-numeric: tabular-nums; }\n p { margin: 0; line-height: 1.5; }\n code { color: var(--md-sys-color-on-surface-variant); }\n footer { padding: 12px 16px; border-top: 1px solid var(--md-sys-color-outline-variant); }\n footer button { width: 100%; background: var(--md-sys-color-primary-container); color: var(--md-sys-color-on-primary-container); }\n @media (max-width: 1439.98px) {\n :host { position: fixed; inset: var(--figure-detail-top, 44px) 0 0 auto; height: calc(100dvh - var(--figure-detail-top, 44px)); z-index: 11; box-shadow: -4px 0 16px rgb(0 0 0 / 20%); }\n }\n `]\n @property({ attribute: false }) load: (id: string) => Promise<FigureDetailRecord> = async id => {\n const [saved, released] = await Promise.all([fetchFigure(id), fetchReleasedFigure(id)])\n if (!saved?.source) throw Error(i18next.t('figure.text.detail-no-source'))\n return { saved, released }\n }\n @state() private figureId = ''\n @state() private record?: FigureDetailRecord\n @state() private asset?: FigureAsset\n @state() private sourceKind: 'saved' | 'released' = 'saved'\n @state() private loading = false\n @state() private error = ''\n @state() private cost?: ReturnType<typeof figureCostOf>\n private ticket = 0\n private unsubscribe?: () => void\n private boundary?: Element\n private boundaryObserver?: ResizeObserver\n\n async show(id: string) {\n const ticket = ++this.ticket\n this.figureId = id\n this.record = undefined\n this.asset = undefined\n this.cost = undefined\n this.loading = true\n this.error = ''\n try {\n const record = await this.load(id)\n if (ticket !== this.ticket) return\n this.record = record\n this.choose(record.released ? 'released' : 'saved')\n } catch {\n if (ticket === this.ticket) this.error = i18next.t('figure.text.detail-load-failed')\n } finally {\n if (ticket === this.ticket) this.loading = false\n }\n }\n private choose(kind: 'saved' | 'released') {\n this.sourceKind = kind\n this.asset = undefined\n this.cost = undefined\n this.error = ''\n try {\n const source = kind === 'released' ? this.record?.released?.source : this.record?.saved.source\n if (!source) throw Error('missing source')\n const asset = parseFigureAsset(source)\n this.cost = figureCostOf(asset)\n this.asset = asset\n } catch {\n this.error = i18next.t('figure.text.detail-invalid-source')\n }\n }\n connectedCallback() {\n super.connectedCallback()\n window.addEventListener('resize', this.place)\n window.addEventListener('popstate', this.place)\n this.unsubscribe = store.subscribe(() => {\n const state = store.getState() as { layout?: { viewparts?: Record<string, { show?: boolean }> }; route?: { page?: string } }\n const parts = state.layout?.viewparts\n if (parts?.[VIEWPART]?.show && (parts?.[ASSISTANT_DOCK_VIEWPART]?.show || leftOpeningPage(openedOn, state.route?.page))) hideDetail()\n else requestAnimationFrame(this.place)\n })\n this.place()\n window.addEventListener('keydown', this.escape)\n }\n disconnectedCallback() {\n super.disconnectedCallback()\n window.removeEventListener('resize', this.place)\n window.removeEventListener('popstate', this.place)\n window.removeEventListener('keydown', this.escape)\n this.unsubscribe?.()\n this.boundaryObserver?.disconnect()\n this.boundary = undefined\n }\n private place = () => {\n const bar = visibleBar(document)\n if (bar !== this.boundary) {\n this.boundaryObserver?.disconnect()\n this.boundary = bar\n if (bar) {\n this.boundaryObserver = new ResizeObserver(this.place)\n this.boundaryObserver.observe(bar)\n }\n }\n this.style.setProperty('--figure-detail-top', `${bar?.getBoundingClientRect().bottom ?? 44}px`)\n }\n private escape = (e: KeyboardEvent) => {\n if (e.key === 'Escape') hideDetail()\n }\n render() {\n const saved = this.record?.saved\n const cost = this.cost\n const capacity = cost ? capacityFrom(cost.groups, cost.triangles) : undefined\n return html`<header><h2>${saved?.name || i18next.t('figure.title.detail')}</h2>\n <button aria-label=${i18next.t('figure.button.close')} @click=${() => hideDetail()}>×</button></header>\n <div body>\n ${this.loading ? html`<p status role=\"status\">${i18next.t('figure.text.detail-loading')}</p>` : nothing}\n ${this.error ? html`<div status role=\"alert\"><p>${this.error}</p><button @click=${() => this.show(this.figureId)}>${i18next.t('figure.button.detail-retry')}</button></div>` : nothing}\n ${saved ? html`<section meta><code>${saved.type}</code><p>${saved.description || ''}</p>\n <label>${i18next.t('figure.label.detail-source')}\n <select aria-label=${i18next.t('figure.label.detail-source')} .value=${this.sourceKind}\n @change=${(e: Event) => this.choose((e.target as HTMLSelectElement).value as 'saved' | 'released')}>\n ${this.record?.released ? html`<option value=\"released\">${i18next.t('figure.label.detail-released', { version: this.record.released.version })}</option>` : nothing}\n <option value=\"saved\">${i18next.t('figure.label.detail-saved')} · ${i18next.t(saved.state === 'released' ? 'figure.label.figure-released' : 'figure.label.draft')}</option>\n </select></label>\n ${!this.record?.released ? html`<p>${i18next.t('figure.text.detail-unreleased')}</p>` : nothing}\n </section>` : nothing}\n ${this.asset ? html`<figure-asset-viewer .asset=${this.asset}></figure-asset-viewer>\n <section summary><h3>${i18next.t('figure.label.detail-summary')}</h3><dl>\n <dt>${i18next.t('figure.label.budget-parts')}</dt><dd>${this.asset.appearance.length}</dd>\n <dt>${i18next.t('figure.label.budget-groups')}</dt><dd>${cost?.groups ?? '—'}</dd>\n <dt>${i18next.t('figure.label.triangles')}</dt><dd>${cost?.triangles ?? '—'}</dd>\n </dl>${capacity ? html`<p>${i18next.t('figure.text.parts-budget', { triangles: cost!.triangles, groups: cost!.groups, instances: capacity.instances })}</p>` : nothing}\n </section>` : nothing}\n </div>\n <footer><button ?disabled=${!saved} @click=${() => {\n // The modeller shows the figure itself; a preview of it beside the editor only covers the work.\n hideDetail()\n navigate(modellerPathOf(saved))\n }}>${i18next.t('figure.button.detail-edit')}</button></footer>`\n }\n}\n\nfunction visibleBar(root: Document | ShadowRoot): Element | undefined {\n for (const element of root.querySelectorAll('*')) {\n if (element.tagName === 'OX-CANVAS-BAR' && element.getBoundingClientRect().height > 0) return element\n if (element.shadowRoot) {\n const found = visibleBar(element.shadowRoot)\n if (found) return found\n }\n }\n}\n\nlet dock: FigureDetailDock | undefined\n/** The page the preview was opened on; the preview belongs to that page and closes when the shell leaves it. */\nlet openedOn: string | undefined\nfunction hideDetail() {\n store.dispatch({ type: UPDATE_VIEWPART, name: VIEWPART, override: { show: false } })\n}\nexport function openFigureDetail(figure: Figure) {\n if (!dock) {\n dock = document.createElement('figure-detail-dock') as FigureDetailDock\n }\n openedOn = (store.getState() as { route?: { page?: string } }).route?.page\n store.dispatch({ type: UPDATE_VIEWPART, name: ASSISTANT_DOCK_VIEWPART, override: { show: false } })\n appendViewpart({ name: VIEWPART, position: VIEWPART_POSITION.ASIDEBAR,\n viewpart: { show: true, persistent: true, level: VIEWPART_LEVEL.TOPMOST, template: html`${dock}` } })\n void dock.show(figure.id)\n}\n"]}
|
|
1
|
+
{"version":3,"file":"figure-detail-dock.js","sourceRoot":"","sources":["../../client/viewparts/figure-detail-dock.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\nimport { css, html, LitElement, nothing } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { i18next, localize } from '@operato/i18n'\nimport { appendViewpart, UPDATE_VIEWPART, VIEWPART_POSITION, VIEWPART_LEVEL } from '@operato/layout'\nimport { navigate, store } from '@operato/shell'\nimport { ScrollbarStyles } from '@operato/styles'\nimport { ASSISTANT_DOCK_VIEWPART } from '@things-factory/ai-assistant/dist-client/components/assistant-dock.js'\nimport { figureCostOf, parseFigureAsset, type FigureAsset } from '@hatiolab/figure-model'\nimport { capacityFrom } from '../modeller/scene-budget.js'\nimport { SidePanelStyles } from '@things-factory/figure-viewer/dist-client/side-panel-styles.js'\nimport { fetchFigure, fetchReleasedFigure, updateFigure } from '../graphql/index.js'\nimport { modellerPathOf } from '../modeller/modeller-route.js'\nimport { leftOpeningPage } from './detail-route.js'\nimport type { Figure } from '../types.js'\nimport '@things-factory/figure-viewer/dist-client/figure-asset-viewer.js'\nimport '@things-factory/figure-viewer/dist-client/figure-facts-list.js'\n\nconst VIEWPART = 'figure-detail'\n/** Said on window when a figure's card picture was replaced from the preview: `{ detail: { id } }`. */\nexport const FIGURE_THUMBNAIL_CHANGED = 'figure-thumbnail-changed'\nexport interface FigureDetailRecord {\n saved: Figure\n released?: { version: number; source: string }\n}\n\n/** Catalogue details stay while the catalogue is shown; choosing a size or a pose has no write path. */\n@customElement('figure-detail-dock')\nexport class FigureDetailDock extends localize(i18next)(LitElement) {\n /*\n The modeller's side panel look (SidePanelStyles). What the preview is opened for sits above the model -- name, type,\n which model is shown, how many fit -- as plain text; the controls are borderless icons and quiet text, so the model\n and its numbers are what the eye meets.\n */\n static styles = [ScrollbarStyles, SidePanelStyles, css`\n :host { display: flex; flex-direction: column; width: 400px; max-width: 100vw; height: 100%; min-height: 0;\n background: var(--md-sys-color-surface); border-left: 1px solid var(--md-sys-color-outline-variant); }\n header { display: flex; align-items: flex-start; gap: 2px; padding: 12px 8px 4px 16px; }\n [title-block] { flex: 1; min-width: 0; display: grid; gap: 2px; }\n h2 { margin: 0; font-size: var(--ui-t-title, 16px); font-weight: 600; line-height: 1.3; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n code { font-family: var(--mono-font, monospace); color: var(--md-sys-color-on-surface-variant); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n button { font: inherit; color: inherit; cursor: pointer; }\n button:focus-visible { outline: 2px solid var(--md-sys-color-primary); outline-offset: 1px; }\n button[icon] { display: inline-grid; place-items: center; width: 28px; height: 28px; padding: 0; border: none; border-radius: 6px;\n background: transparent; color: var(--md-sys-color-on-surface-variant); }\n button[icon]:hover:not(:disabled) { background: var(--md-sys-color-surface-container-high); color: var(--md-sys-color-on-surface); }\n button[icon]:disabled { opacity: 0.38; cursor: default; }\n button[icon] md-icon { --md-icon-size: 18px; }\n [body] { flex: 1; overflow: auto; min-height: 0; }\n [meta] { display: grid; gap: 8px; padding: 0 16px 12px; }\n /* Which model is shown: words that change colour, not boxes. */\n [source] { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 12px; color: var(--md-sys-color-on-surface-variant); }\n [source] button { padding: 0; border: none; background: none; color: var(--md-sys-color-on-surface-variant); }\n [source] button:hover { color: var(--md-sys-color-on-surface); }\n [source] button[aria-pressed='true'] { color: var(--md-sys-color-primary); font-weight: 600; }\n dl { margin: 0; }\n dt { color: var(--md-sys-color-on-surface-variant); }\n dd { margin: 0; color: var(--md-sys-color-on-surface); }\n dl[stats] { display: flex; flex-wrap: wrap; gap: 4px 16px; }\n dl[stats] > div { display: flex; align-items: baseline; gap: 4px; }\n dl[stats] dd { font-weight: 600; font-variant-numeric: tabular-nums; }\n p { margin: 0; line-height: 1.5; }\n [description] { color: var(--md-sys-color-on-surface-variant); }\n [status] { display: grid; gap: 8px; justify-items: start; padding: 12px 16px; }\n @media (max-width: 1439.98px) {\n :host { position: fixed; inset: var(--figure-detail-top, 44px) 0 0 auto; height: calc(100dvh - var(--figure-detail-top, 44px)); z-index: 11; box-shadow: -4px 0 16px rgb(0 0 0 / 20%); }\n }\n `]\n @property({ attribute: false }) load: (id: string) => Promise<FigureDetailRecord> = async id => {\n const [saved, released] = await Promise.all([fetchFigure(id), fetchReleasedFigure(id)])\n if (!saved?.source) throw Error(i18next.t('figure.text.detail-no-source'))\n return { saved, released }\n }\n /**\n * Keeps the picture taken in the preview as the figure's card picture. A patch of the picture alone does not make a\n * released figure a draft (`updateFigure`). The catalogue is told, and reads its cards again.\n */\n @property({ attribute: false }) keepThumbnail: (id: string, dataUrl: string) => Promise<unknown> = async (id, dataUrl) => {\n await updateFigure(id, { thumbnail: dataUrl })\n window.dispatchEvent(new CustomEvent(FIGURE_THUMBNAIL_CHANGED, { detail: { id } }))\n }\n @state() private figureId = ''\n @state() private record?: FigureDetailRecord\n @state() private asset?: FigureAsset\n @state() private sourceKind: 'saved' | 'released' = 'saved'\n @state() private loading = false\n @state() private error = ''\n @state() private cost?: ReturnType<typeof figureCostOf>\n private ticket = 0\n private unsubscribe?: () => void\n private boundary?: Element\n private boundaryObserver?: ResizeObserver\n\n async show(id: string) {\n const ticket = ++this.ticket\n this.figureId = id\n this.record = undefined\n this.asset = undefined\n this.cost = undefined\n this.loading = true\n this.error = ''\n try {\n const record = await this.load(id)\n if (ticket !== this.ticket) return\n this.record = record\n this.choose(record.released ? 'released' : 'saved')\n } catch {\n if (ticket === this.ticket) this.error = i18next.t('figure.text.detail-load-failed')\n } finally {\n if (ticket === this.ticket) this.loading = false\n }\n }\n private choose(kind: 'saved' | 'released') {\n this.sourceKind = kind\n this.asset = undefined\n this.cost = undefined\n this.error = ''\n try {\n const source = kind === 'released' ? this.record?.released?.source : this.record?.saved.source\n if (!source) throw Error('missing source')\n const asset = parseFigureAsset(source)\n this.cost = figureCostOf(asset)\n this.asset = asset\n } catch {\n this.error = i18next.t('figure.text.detail-invalid-source')\n }\n }\n connectedCallback() {\n super.connectedCallback()\n window.addEventListener('resize', this.place)\n window.addEventListener('popstate', this.place)\n this.unsubscribe = store.subscribe(() => {\n const state = store.getState() as { layout?: { viewparts?: Record<string, { show?: boolean }> }; route?: { page?: string } }\n const parts = state.layout?.viewparts\n if (parts?.[VIEWPART]?.show && (parts?.[ASSISTANT_DOCK_VIEWPART]?.show || leftOpeningPage(openedOn, state.route?.page))) hideDetail()\n else requestAnimationFrame(this.place)\n })\n this.place()\n window.addEventListener('keydown', this.escape)\n }\n disconnectedCallback() {\n super.disconnectedCallback()\n window.removeEventListener('resize', this.place)\n window.removeEventListener('popstate', this.place)\n window.removeEventListener('keydown', this.escape)\n this.unsubscribe?.()\n this.boundaryObserver?.disconnect()\n this.boundary = undefined\n }\n private place = () => {\n const bar = visibleBar(document)\n if (bar !== this.boundary) {\n this.boundaryObserver?.disconnect()\n this.boundary = bar\n if (bar) {\n this.boundaryObserver = new ResizeObserver(this.place)\n this.boundaryObserver.observe(bar)\n }\n }\n this.style.setProperty('--figure-detail-top', `${bar?.getBoundingClientRect().bottom ?? 44}px`)\n }\n private escape = (e: KeyboardEvent) => {\n if (e.key === 'Escape') hideDetail()\n }\n render() {\n const saved = this.record?.saved\n const released = this.record?.released\n const cost = this.cost\n const capacity = cost ? capacityFrom(cost.groups, cost.triangles) : undefined\n const savedLabel = saved\n ? `${i18next.t('figure.label.detail-saved')} · ${i18next.t(saved.state === 'released' ? 'figure.label.figure-released' : 'figure.label.draft')}`\n : ''\n return html`<header>\n <div title-block><h2 title=${saved?.name || ''}>${saved?.name || i18next.t('figure.title.detail')}</h2>\n ${saved ? html`<code title=${fullName(saved)}>${fullName(saved)}</code>` : nothing}</div>\n <button icon edit aria-label=${i18next.t('figure.button.detail-edit')} title=${i18next.t('figure.button.detail-edit')} ?disabled=${!saved}\n @click=${() => {\n // The modeller shows the figure itself; a preview of it beside the editor only covers the work.\n hideDetail()\n navigate(modellerPathOf(saved))\n }}><md-icon>edit</md-icon></button>\n <button icon aria-label=${i18next.t('figure.button.close')} title=${i18next.t('figure.button.close')} @click=${() => hideDetail()}><md-icon>close</md-icon></button>\n </header>\n <div body>\n ${this.loading ? html`<p status role=\"status\">${i18next.t('figure.text.detail-loading')}</p>` : nothing}\n ${this.error ? html`<div status role=\"alert\"><p>${this.error}</p><button act @click=${() => this.show(this.figureId)}><md-icon>refresh</md-icon>${i18next.t('figure.button.detail-retry')}</button></div>` : nothing}\n ${saved ? html`<section meta>\n ${released\n ? html`<div source role=\"group\" aria-label=${i18next.t('figure.label.detail-source')}>\n <button aria-pressed=${String(this.sourceKind === 'released')} @click=${() => this.choose('released')}>${i18next.t('figure.label.detail-released', { version: released.version })}</button>\n <button aria-pressed=${String(this.sourceKind === 'saved')} @click=${() => this.choose('saved')}>${savedLabel}</button>\n </div>`\n : html`<div source><span title=${i18next.t('figure.text.detail-unreleased')}>${savedLabel}</span></div>`}\n ${this.asset ? html`<dl stats>\n <div><dt>${i18next.t('figure.label.placement-limit')}</dt><dd>${capacity\n ? i18next.t('figure.text.fits-n-instances', { n: capacity.instances.toLocaleString() })\n : i18next.t('figure.text.not-measured-yet')}</dd></div>\n <div><dt>${i18next.t('figure.label.triangles')}</dt><dd>${cost?.triangles.toLocaleString() ?? '—'}</dd></div>\n <div><dt>${i18next.t('figure.label.budget-groups')}</dt><dd>${cost?.groups ?? '—'}</dd></div>\n <div><dt>${i18next.t('figure.label.budget-parts')}</dt><dd>${this.asset.appearance.length}</dd></div>\n </dl>` : nothing}\n ${saved.description ? html`<p description>${saved.description}</p>` : nothing}\n </section>` : nothing}\n ${this.asset ? html`<figure-asset-viewer .asset=${this.asset}\n .keepShot=${saved?.id ? (dataUrl: string) => this.keepThumbnail(saved.id, dataUrl) : undefined}>\n ${saved ? html`<figure-facts-list slot=\"facts\" .asset=${this.asset} .row=${saved}></figure-facts-list>` : nothing}\n </figure-asset-viewer>` : nothing}\n </div>`\n }\n}\n\n/** The name a board stores: `{namespace}/{type}` (ADR-0114). */\nfunction fullName(figure: Figure): string {\n return figure.namespace ? `${figure.namespace}/${figure.type}` : figure.type\n}\n\nfunction visibleBar(root: Document | ShadowRoot): Element | undefined {\n for (const element of root.querySelectorAll('*')) {\n if (element.tagName === 'OX-CANVAS-BAR' && element.getBoundingClientRect().height > 0) return element\n if (element.shadowRoot) {\n const found = visibleBar(element.shadowRoot)\n if (found) return found\n }\n }\n}\n\nlet dock: FigureDetailDock | undefined\n/** The page the preview was opened on; the preview belongs to that page and closes when the shell leaves it. */\nlet openedOn: string | undefined\nfunction hideDetail() {\n store.dispatch({ type: UPDATE_VIEWPART, name: VIEWPART, override: { show: false } })\n}\nexport function openFigureDetail(figure: Figure) {\n if (!dock) {\n dock = document.createElement('figure-detail-dock') as FigureDetailDock\n }\n openedOn = (store.getState() as { route?: { page?: string } }).route?.page\n store.dispatch({ type: UPDATE_VIEWPART, name: ASSISTANT_DOCK_VIEWPART, override: { show: false } })\n appendViewpart({ name: VIEWPART, position: VIEWPART_POSITION.ASIDEBAR,\n viewpart: { show: true, persistent: true, level: VIEWPART_LEVEL.TOPMOST, template: html`${dock}` } })\n void dock.show(figure.id)\n}\n"]}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import '@material/web/icon/icon.js';
|
|
2
2
|
import { LitElement } from 'lit';
|
|
3
|
+
import '@things-factory/figure-viewer/dist-client/figure-thumb-mark.js';
|
|
3
4
|
import type { Figure } from '../types.js';
|
|
4
5
|
declare const FigureThumb_base: (new (...args: any[]) => LitElement) & typeof LitElement;
|
|
5
6
|
/**
|
|
@@ -3,6 +3,7 @@ import '@material/web/icon/icon.js';
|
|
|
3
3
|
import { css, html, LitElement } from 'lit';
|
|
4
4
|
import { customElement, property } from 'lit/decorators.js';
|
|
5
5
|
import { i18next, localize } from '@operato/i18n';
|
|
6
|
+
import '@things-factory/figure-viewer/dist-client/figure-thumb-mark.js';
|
|
6
7
|
import { getPathInfo } from '@operato/utils';
|
|
7
8
|
/**
|
|
8
9
|
* 그림을 부르는 주소.
|
|
@@ -59,24 +60,6 @@ let FigureThumb = class FigureThumb extends localize(i18next)(LitElement) {
|
|
|
59
60
|
object-fit: contain;
|
|
60
61
|
}
|
|
61
62
|
|
|
62
|
-
/* 아직 그림이 없는 것을 빈 사각형으로 두지 않는다 — 왜 없는지 말한다 */
|
|
63
|
-
div[empty] {
|
|
64
|
-
display: flex;
|
|
65
|
-
flex-direction: column;
|
|
66
|
-
align-items: center;
|
|
67
|
-
justify-content: center;
|
|
68
|
-
gap: var(--spacing-tiny, 4px);
|
|
69
|
-
width: 100%;
|
|
70
|
-
height: 100%;
|
|
71
|
-
font: var(--label-font, inherit);
|
|
72
|
-
font-size: 0.7rem;
|
|
73
|
-
color: var(--md-sys-color-outline);
|
|
74
|
-
}
|
|
75
|
-
div[empty] md-icon {
|
|
76
|
-
--md-icon-size: 26px;
|
|
77
|
-
color: var(--md-sys-color-outline-variant);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
63
|
/*
|
|
81
64
|
초안 표시.
|
|
82
65
|
|
|
@@ -195,10 +178,7 @@ let FigureThumb = class FigureThumb extends localize(i18next)(LitElement) {
|
|
|
195
178
|
${src
|
|
196
179
|
? html `<img src=${src} alt=${figure.name || ''} loading="lazy" />`
|
|
197
180
|
: html `
|
|
198
|
-
<
|
|
199
|
-
<md-icon>deployed_code</md-icon>
|
|
200
|
-
${i18next.t('figure.text.thumbnail-not-made-yet')}
|
|
201
|
-
</div>
|
|
181
|
+
<figure-thumb-mark .label=${i18next.t('figure.text.thumbnail-not-made-yet')}></figure-thumb-mark>
|
|
202
182
|
`}
|
|
203
183
|
${figure.state === 'released' ? '' : html `<span draft>${i18next.t('figure.label.draft')}</span>`}
|
|
204
184
|
${figure.tags?.length
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"figure-thumb.js","sourceRoot":"","sources":["../../client/viewparts/figure-thumb.ts"],"names":[],"mappings":";AAAA,OAAO,4BAA4B,CAAA;AAEnC,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,KAAK,CAAA;AAC3C,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAE3D,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAI5C;;;;;;;;;GASG;AACH,SAAS,YAAY,CAAC,MAAc;IAClC,IAAI,CAAC,MAAM,CAAC,kBAAkB,EAAE,CAAC;QAC/B,OAAO,SAAS,CAAA;IAClB,CAAC;IAED,MAAM,EAAE,WAAW,EAAE,GAAG,WAAW,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;IACtD,MAAM,KAAK,GAAG,IAAI,IAAI,CAAC,MAAM,CAAC,kBAAkB,CAAC,CAAC,OAAO,EAAE,CAAA;IAE3D,OAAO,GAAG,WAAW,qBAAqB,MAAM,CAAC,EAAE,MAAM,KAAK,EAAE,CAAA;AAClE,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AAEI,IAAM,WAAW,GAAjB,MAAM,WAAY,SAAQ,QAAQ,CAAC,OAAO,CAAC,CAAC,UAAU,CAAC;aACrD,WAAM,GAAG,GAAG,CAAA
|
|
1
|
+
{"version":3,"file":"figure-thumb.js","sourceRoot":"","sources":["../../client/viewparts/figure-thumb.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\n\nimport { css, html, LitElement } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport '@things-factory/figure-viewer/dist-client/figure-thumb-mark.js'\nimport { getPathInfo } from '@operato/utils'\n\nimport type { Figure } from '../types.js'\n\n/**\n * 그림을 부르는 주소.\n *\n * **base64 를 목록 응답에 실어 오지 않는다.** data URL 은 브라우저가 캐시할 수 없어, 카탈로그를\n * 열 때마다 그림 전부가 다시 온다([확인함] 표본 14개 227KB). 주소로 부르면 캐시가 되고, 판을\n * 가리는 `v` 가 바뀐 것만 다시 받는다. 내주는 자리는 `figure-thumbnail-router` 다.\n *\n * 도메인 조각(`/domain/{subdomain}`)은 셸이 쓰는 것과 같은 함수로 읽는다 — 화면이 주소를\n * 손으로 짜맞추면 도메인이 바뀌는 배치에서 조용히 틀린다.\n */\nfunction thumbnailSrc(figure: Figure): string | undefined {\n if (!figure.thumbnailUpdatedAt) {\n return undefined\n }\n\n const { contextPath } = getPathInfo(location.pathname)\n const stamp = new Date(figure.thumbnailUpdatedAt).getTime()\n\n return `${contextPath}/figure-thumbnail/${figure.id}?v=${stamp}`\n}\n\n/**\n * 카드의 그림 칸.\n *\n * ## 왜 컴포넌트인가\n *\n * 카탈로그는 `ox-grist` 의 CARD 모드로 그린다. 격자에게 그림 칸은 컬럼 하나이고, 그 칸을 그리는\n * 것은 컬럼의 `record.renderer` 다. 그림 위에 얹히는 것들(초안 표시, 삭제)은 **그림 칸 안에**\n * 있어야 하므로 renderer 가 그려야 하는데, renderer 의 결과는 `ox-card-field` 의 shadow root\n * 안으로 들어간다 — 화면의 `static styles` 가 닿지 않고, 인라인 style 로는 `:hover` 를 쓸 수 없다.\n *\n * 그래서 그림 칸만 컴포넌트로 둔다. 자기 스타일을 갖고 있으니 hover 도 테마도 정상으로 돈다.\n * 격자로 옮기기 전 손으로 만들었던 카드의 위쪽 절반이 그대로 이 자리다.\n *\n * ## 삭제가 왜 여기 있나\n *\n * 격자의 gutter 줄에 두면 카드 아래에 단추 줄이 하나 생긴다. 감춰도 자리가 남고, 자리를 없애면\n * 눌릴 곳이 사라진다. 되돌릴 수 없는 조작은 **고르는 눈길에 걸리지 않아야** 하므로 그림 오른쪽\n * 위, 손이 갔을 때만 나오는 자리에 둔다.\n */\n@customElement('figure-thumb')\nexport class FigureThumb extends localize(i18next)(LitElement) {\n static styles = css`\n :host {\n position: relative;\n display: block;\n width: 100%;\n height: 100%;\n overflow: hidden;\n }\n\n img {\n display: block;\n width: 100%;\n height: 100%;\n /* 형상을 자르지 않는다 — 긴 것(컨베이어)과 높은 것(시그널타워)이 한 줄에 선다 */\n object-fit: contain;\n }\n\n /*\n 초안 표시.\n\n 발행된 것에는 아무 말도 하지 않는다 — 카탈로그에 있는 것은 대개 발행된 것이라, 카드마다\n 「발행됨」이 붙으면 아무것도 가르지 않고 자리만 먹는다. 초안만 말하면 이 표시가 있는 카드가\n 곧 「아직 쓰면 안 되는 것」이 된다.\n */\n span[draft] {\n position: absolute;\n top: 6px;\n left: 6px;\n padding: 1px 7px;\n font: var(--label-font, inherit);\n font-size: 0.68rem;\n line-height: 1.5;\n color: var(--md-sys-color-on-secondary-container);\n background-color: var(--md-sys-color-secondary-container);\n border-radius: 4px;\n }\n\n /*\n 태그 — **그림 위에 얹는다.**\n\n 카드 본문에 한 줄로 두었더니 태그가 있는 카드만 그만큼 높아져 격자가 들쭉날쭉해졌다.\n 태그는 도형마다 개수가 다르므로 본문에 두는 한 이 문제가 남는다. 그림 위는 이미 자리가\n 잡혀 있고(초안 표시·삭제), 얹어도 카드 높이가 변하지 않는다.\n\n 아래쪽에 둔다 — 위는 초안 표시와 삭제 단추가 쓴다.\n\n **투명한 알약이다.** 면을 꽉 채우면 그림 위에 흰 띠가 생겨 형상을 가린다 — 카탈로그에서\n 먼저 보아야 하는 것은 형상이다. 그래서 바탕은 거의 비우고, 읽히는 것은 뒤를 흐리는 것과\n 실낱 테두리가 맡는다. 흐림이 안 되는 환경에서도 테두리가 남아 글자가 형상에 묻히지 않는다.\n */\n div[tags] {\n position: absolute;\n right: 6px;\n bottom: 6px;\n left: 6px;\n display: flex;\n flex-wrap: wrap-reverse;\n justify-content: flex-end;\n gap: 3px;\n /* 태그가 많아도 그림을 덮지 않는다 — 넘치는 것은 위로 잘린다 */\n max-height: 44px;\n overflow: hidden;\n /* 알약 사이의 빈 자리는 그림이 받는다 — 거기서 누르면 저작면이 열린다 */\n pointer-events: none;\n }\n /*\n **알약은 누를 수 있다.** 누르면 그 태그로 목록을 거른다.\n\n 태그를 눈에 보이게 한 다음의 물음은 「그래서 이걸로 뭘 하나」다. 같은 태그가 붙은 것을\n 보려고 태그를 눈으로 읽어 검색창에 옮겨 적게 두면, 보여 준 뜻이 절반만 산다.\n\n 카드 자체가 저작면을 여는 자리이므로 알약에서 그 클릭을 멈춘다 — 안 멈추면 태그를 누른\n 사람이 편집기로 끌려간다.\n */\n div[tags] button {\n pointer-events: auto;\n cursor: pointer;\n padding: 0 7px;\n font: var(--label-font, inherit);\n font-size: 0.66rem;\n line-height: 1.7;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n background-color: color-mix(in srgb, var(--md-sys-color-surface, #fff) 34%, transparent);\n border: 1px solid color-mix(in srgb, var(--md-sys-color-outline, #888) 42%, transparent);\n backdrop-filter: blur(4px);\n border-radius: 999em;\n white-space: nowrap;\n }\n div[tags] button:hover,\n div[tags] button:focus-visible {\n color: var(--md-sys-color-on-surface);\n background-color: color-mix(in srgb, var(--md-sys-color-surface, #fff) 72%, transparent);\n border-color: var(--md-sys-color-primary);\n outline: none;\n }\n\n button[remove] {\n position: absolute;\n top: 5px;\n right: 5px;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 3px;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n /* 그림이 밝을 수도 어두울 수도 있다 — 옅은 면을 깔아 아이콘이 어디서나 읽히게 한다 */\n background-color: color-mix(in srgb, var(--md-sys-color-surface, #fff) 82%, transparent);\n border: none;\n border-radius: 4px;\n cursor: pointer;\n opacity: 0;\n transition: opacity 0.12s ease-out;\n }\n button[remove] md-icon {\n --md-icon-size: 18px;\n }\n :host(:hover) button[remove],\n button[remove]:focus-visible {\n opacity: 1;\n }\n button[remove]:hover {\n color: var(--md-sys-color-on-error, #fff);\n background-color: var(--md-sys-color-error);\n }\n `\n\n @property({ type: Object }) figure!: Figure\n\n render() {\n const figure = this.figure\n if (!figure) {\n return html``\n }\n\n const src = thumbnailSrc(figure)\n\n return html`\n ${src\n ? html`<img src=${src} alt=${figure.name || ''} loading=\"lazy\" />`\n : html`\n <figure-thumb-mark .label=${i18next.t('figure.text.thumbnail-not-made-yet')}></figure-thumb-mark>\n `}\n ${figure.state === 'released' ? '' : html`<span draft>${i18next.t('figure.label.draft')}</span>`}\n ${figure.tags?.length\n ? html`<div tags>\n ${figure.tags.map(\n tag => html`\n <button\n title=${i18next.t('figure.text.find-by-this-tag')}\n @click=${(e: Event) => {\n e.stopPropagation()\n this.dispatchEvent(\n new CustomEvent('search-tag', { detail: { tag }, bubbles: true, composed: true })\n )\n }}\n >\n ${tag}\n </button>\n `\n )}\n </div>`\n : ''}\n\n <button\n remove\n title=${i18next.t('figure.button.delete-figure')}\n @click=${(e: Event) => {\n /* 카드 클릭(=저작면 열기)까지 올라가지 않게 여기서 멈춘다 */\n e.stopPropagation()\n this.dispatchEvent(new CustomEvent('remove'))\n }}\n >\n <md-icon>delete</md-icon>\n </button>\n `\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@things-factory/figure-ui",
|
|
3
|
-
"version": "10.1.
|
|
3
|
+
"version": "10.1.155",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public",
|
|
6
6
|
"@things-factory:registry": "https://registry.npmjs.org"
|
|
@@ -41,9 +41,10 @@
|
|
|
41
41
|
"@operato/slot-layout-editor": "^10.29.25",
|
|
42
42
|
"@operato/styles": "^10.0.0",
|
|
43
43
|
"@operato/utils": "^10.0.0",
|
|
44
|
-
"@things-factory/ai-assistant": "^10.1.
|
|
45
|
-
"@things-factory/figure-service": "^10.1.
|
|
44
|
+
"@things-factory/ai-assistant": "^10.1.155",
|
|
45
|
+
"@things-factory/figure-service": "^10.1.155",
|
|
46
|
+
"@things-factory/figure-viewer": "^10.1.155",
|
|
46
47
|
"three": "^0.185.1"
|
|
47
48
|
},
|
|
48
|
-
"gitHead": "
|
|
49
|
+
"gitHead": "0211c9c0764a26c10151fdc7d60094fc334905ff"
|
|
49
50
|
}
|
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
/* Isolated product detail: fixtures are in memory, never saved to the running app. */
|
|
2
2
|
import 'figure-ui-client/viewparts/figure-detail-dock.js'
|
|
3
3
|
import { i18next } from '@operato/i18n'
|
|
4
|
-
import { addFigureBox, createFigureAsset, editFigurePart, applyFigureAuthoring, serializeFigureAsset } from '@hatiolab/figure-model'
|
|
4
|
+
import { addFigureBox, createFigureAsset, editFigurePart, applyFigureAuthoring, proposeFigureOccupancy, serializeFigureAsset } from '@hatiolab/figure-model'
|
|
5
5
|
|
|
6
6
|
async function start() {
|
|
7
7
|
i18next.addResourceBundle('ko', 'translations', await fetch('/ko.json').then(r => r.json()), true, true)
|
|
8
8
|
await i18next.changeLanguage('ko')
|
|
9
9
|
let asset = addFigureBox(createFigureAsset('클립 시험'), { id: 'block', size: { x: 100, y: 100, z: 100 }, position: { x: 0, y: 50, z: 0 } })
|
|
10
|
+
/* A declared size, as every figure the modeller saves has: the board places it at that size, and the sliders open. */
|
|
11
|
+
asset = applyFigureAuthoring(asset, { kind: 'declare-instance-size', range: { x: { min: 50, max: 400 }, y: { min: 50, max: 400 }, z: { min: 50, max: 400 } }, at: { x: 100, y: 100, z: 100 } })
|
|
12
|
+
asset = applyFigureAuthoring(asset, { kind: 'follow-size', part: 'block', axes: ['x', 'y', 'z'] })
|
|
10
13
|
asset = applyFigureAuthoring(asset, { kind: 'set-joint', joint: {
|
|
11
14
|
id: 'block', type: 'prismatic', body0: { plane: 'mounting-plane' }, body1: { part: 'block', face: 'bottom' }, axis: 'X', lowerLimit: 0, upperLimit: 200, state: { id: 'shift', start: 0 }
|
|
12
15
|
} })
|
|
13
16
|
asset = editFigurePart(asset, { kind: 'set-driver', state: 'shift', clip: 'cycle', duration: 4, keys: [{ seconds: 0, value: 0 }, { seconds: 2, value: 200 }, { seconds: 4, value: 0 }] })
|
|
17
|
+
/* Its room declared from its parts over the whole motion, as the modeller does before a release. */
|
|
18
|
+
asset = applyFigureAuthoring(asset, { kind: 'declare-occupancy', proposal: proposeFigureOccupancy(asset, { over: 'range' }) })
|
|
14
19
|
const source = serializeFigureAsset(asset)
|
|
15
20
|
const dock = document.createElement('figure-detail-dock')
|
|
16
21
|
let fail = true
|
|
@@ -19,6 +24,9 @@ async function start() {
|
|
|
19
24
|
if (id === 'slow') await new Promise(done => setTimeout(done, 1200))
|
|
20
25
|
return { saved: { id, name: id === 'slow' ? '늦은 이전 모델' : '클립 시험', type: 'DETAIL_TEST', state: 'draft', source }, released: id === 'invalid' ? { version: 1, source: 'broken json' } : undefined }
|
|
21
26
|
}
|
|
27
|
+
/* The picture is kept here, not sent: its size and kind are shown for the check. */
|
|
28
|
+
const kept = document.createElement('output'); kept.setAttribute('aria-label','썸네일')
|
|
29
|
+
dock.keepThumbnail = async (id, url) => { kept.textContent = `${id} ${url.slice(0, 15)} ${url.length}` }
|
|
22
30
|
const controls = document.createElement('nav')
|
|
23
31
|
for (const [label, id] of [['클립 모델 열기','motion'],['연결 오류 시험','retry'],['깨진 발행판 시험','invalid'],['느린 모델 열기','slow']]) {
|
|
24
32
|
const button = document.createElement('button'); button.textContent = label
|
|
@@ -29,6 +37,6 @@ async function start() {
|
|
|
29
37
|
const original = document.createElement('output'); original.setAttribute('aria-label','원본 보존')
|
|
30
38
|
const check = document.createElement('button'); check.textContent='원본 확인'
|
|
31
39
|
check.onclick=()=>original.textContent=serializeFigureAsset(asset)===source?'원본 그대로':'원본 변경됨'
|
|
32
|
-
controls.append(check,original)
|
|
40
|
+
controls.append(check,original,kept)
|
|
33
41
|
}
|
|
34
42
|
start()
|
|
@@ -633,7 +633,7 @@ describe('AI proposals reaching the page', () => {
|
|
|
633
633
|
test('a proposal that would stop a clip is refused whole, the clip named', async () => {
|
|
634
634
|
const before = await read(() => product.view())
|
|
635
635
|
const refused = await offer({ steps: [{ kind: 'remove-part', target: 'leaf' }], create: false, context: before.context, revision: before.revision })
|
|
636
|
-
assert.deepEqual({ applied: refused.applied, code: refused.code, clips: refused.clips
|
|
636
|
+
assert.deepEqual({ applied: refused.applied, code: refused.code, clips: refused.details?.reason === 'clips-dropped' ? refused.details.clips.map(c => c.clip) : undefined }, { applied: false, code: 'CLIPS_DROPPED', clips: ['swing'] })
|
|
637
637
|
const after = await read(() => product.view())
|
|
638
638
|
assert.deepEqual({ source: after.source, revision: after.revision }, { source: before.source, revision: before.revision })
|
|
639
639
|
})
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import './modeller-page.js'
|
|
7
7
|
import { captureFigureThumbnail } from 'figure-ui-client/modeller/thumbnail.js'
|
|
8
8
|
import { drawableFigureParts } from 'figure-ui-client/modeller/renderer.js'
|
|
9
|
-
import { previewTypeOf } from 'figure-
|
|
9
|
+
import { previewTypeOf } from '@things-factory/figure-viewer/dist-client/preview-model.js'
|
|
10
10
|
import { figureReportOf } from 'figure-ui-client/modeller/maturity.js'
|
|
11
11
|
import { hasFigure } from '@hatiolab/things-scene'
|
|
12
12
|
import {
|
|
@@ -2,9 +2,14 @@ import { createServer } from 'node:http'
|
|
|
2
2
|
import { readFileSync } from 'node:fs'
|
|
3
3
|
import { dirname, resolve } from 'node:path'
|
|
4
4
|
import { fileURLToPath } from 'node:url'
|
|
5
|
+
import { createRequire } from 'node:module'
|
|
5
6
|
import { buildPage } from './build.mjs'
|
|
6
7
|
const here=dirname(fileURLToPath(import.meta.url))
|
|
7
8
|
const bundle=await buildPage('detail-page.js')
|
|
8
|
-
const html=`<!doctype html><html><head><meta charset="utf-8"><title>Figure 상세 검증</title><link rel="stylesheet" href="/theme.css"><style>body{margin:0;font-family:sans-serif;display:flex;height:100vh}nav{flex:1;padding:24px;display:flex;flex-direction:column;gap:12px}nav button{padding:12px}figure-detail-dock{position:static!important;height:100%!important}output{display:block}</style></head><body><script type="module" src="/page.js"></script></body></html>`
|
|
9
|
+
const html=`<!doctype html><html><head><meta charset="utf-8"><title>Figure 상세 검증</title><link rel="stylesheet" href="/theme.css"><link rel="stylesheet" href="/node_modules/material-symbols/index.css"><style>body{margin:0;font-family:sans-serif;display:flex;height:100vh}nav{flex:1;padding:24px;display:flex;flex-direction:column;gap:12px}nav button{padding:12px}figure-detail-dock{position:static!important;height:100%!important}output{display:block}</style></head><body><script type="module" src="/page.js"></script></body></html>`
|
|
9
10
|
const routes={ '/page.js':[bundle,'text/javascript'], '/ko.json':[resolve(here,'../../translations/ko.json'),'application/json'], '/theme.css':[resolve(here,'../../client/harness-theme/light.css'),'text/css'] }
|
|
10
|
-
|
|
11
|
+
/* The shell's icon font, so md-icon draws its glyphs here as it does in the app. */
|
|
12
|
+
const symbols=dirname(createRequire(import.meta.url).resolve('material-symbols/index.css'))
|
|
13
|
+
const fontFile=url=>url.startsWith('/node_modules/material-symbols/')&&resolve(symbols,url.slice('/node_modules/material-symbols/'.length).split('?')[0])
|
|
14
|
+
const fontType=f=>f.endsWith('.css')?'text/css':f.endsWith('.woff2')?'font/woff2':'application/octet-stream'
|
|
15
|
+
createServer((req,res)=>{const font=fontFile(req.url);if(font&&font.startsWith(symbols)){try{const body=readFileSync(font);res.writeHead(200,{'Content-Type':fontType(font)});return res.end(body)}catch{res.writeHead(404);return res.end()}}const match=routes[req.url];res.writeHead(200,{'Content-Type':match?.[1]??'text/html; charset=utf-8'});res.end(match?readFileSync(match[0]):html)}).listen(3394,'127.0.0.1',()=>console.log('Detail fixture: http://localhost:3394'))
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The modeller's posing panel (figure-controls) reaches the editor: every key it says is in every language.
|
|
3
|
+
* Split from controls.test.ts when the controls moved to @things-factory/figure-viewer (2026-10-12).
|
|
4
|
+
*/
|
|
5
|
+
import { test } from 'node:test'
|
|
6
|
+
import assert from 'node:assert/strict'
|
|
7
|
+
import { readFileSync } from 'node:fs'
|
|
8
|
+
import { fileURLToPath } from 'node:url'
|
|
9
|
+
|
|
10
|
+
const read = (path: string) => readFileSync(fileURLToPath(new URL(path, import.meta.url)), 'utf8')
|
|
11
|
+
|
|
12
|
+
test('편집기에 닿는다 — 값 바꾸기는 입력으로, 이름 · 범위 · 시간 · 기본 자세는 부품 편집으로', () => {
|
|
13
|
+
const controls = read('../client/modeller/figure-controls.ts')
|
|
14
|
+
const keys = [...controls.matchAll(/'(figure\.[a-z.-]*[a-z])'/g)].map(m => m[1])
|
|
15
|
+
for (const lang of ['ko', 'en', 'ja', 'zh', 'ms', 'es']) {
|
|
16
|
+
const table = JSON.parse(read(`../translations/${lang}.json`))
|
|
17
|
+
for (const key of keys) assert.ok(table[key], `${lang}: ${key}`)
|
|
18
|
+
}
|
|
19
|
+
})
|
|
@@ -27,6 +27,7 @@ import assert from 'node:assert/strict'
|
|
|
27
27
|
import { readFileSync, readdirSync } from 'node:fs'
|
|
28
28
|
import { fileURLToPath } from 'node:url'
|
|
29
29
|
import { join } from 'node:path'
|
|
30
|
+
import { wordsOf } from './words.ts'
|
|
30
31
|
|
|
31
32
|
const HERE = fileURLToPath(new URL('.', import.meta.url))
|
|
32
33
|
const TRANSLATIONS = join(HERE, '../translations')
|
|
@@ -46,6 +47,8 @@ function screens(dir: string, into: string[] = []): string[] {
|
|
|
46
47
|
|
|
47
48
|
const FILES = screens(CLIENT)
|
|
48
49
|
const KO = JSON.parse(readFileSync(join(TRANSLATIONS, 'ko.json'), 'utf-8')) as Record<string, string>
|
|
50
|
+
/* What the app loads: these words and figure-viewer's, whose components figure-ui uses. */
|
|
51
|
+
const LOADED = wordsOf('ko')
|
|
49
52
|
|
|
50
53
|
test('번역 파일의 모든 키가 figure. 접두 아래 있다', () => {
|
|
51
54
|
const loose = Object.keys(KO).filter(key => !key.startsWith('figure.'))
|
|
@@ -96,7 +99,7 @@ test('통째로 부르는 키가 번역 파일에 있다 — 없으면 화면에
|
|
|
96
99
|
코드에서 열거 배열을 읽어야 하므로 여기서는 짝이 갈린 것만 아래에서 못 박는다.
|
|
97
100
|
*/
|
|
98
101
|
if (/[-.]$/.test(key)) continue
|
|
99
|
-
if (!(key in
|
|
102
|
+
if (!(key in LOADED)) missing.push(`${path.slice(CLIENT.length + 1)} ${key}`)
|
|
100
103
|
}
|
|
101
104
|
}
|
|
102
105
|
|