@things-factory/figure-ui 10.1.114 → 10.1.116
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/graphql/index.ts +6 -1
- package/client/modeller/canvas.ts +6 -0
- package/client/modeller/editor.ts +4 -9
- package/client/modeller/figure-links.ts +20 -7
- package/client/modeller/figure-preview.ts +34 -10
- package/client/modeller/joint-drag.ts +3 -3
- package/client/modeller/links.ts +28 -11
- package/client/modeller/preview-model.ts +8 -0
- package/client/modeller/recipes.ts +6 -1
- package/client/pages/figure-list-page.ts +4 -4
- package/client/pages/figure-modeller-page.ts +7 -1
- package/client/types.ts +1 -1
- package/client/viewparts/detail-motion.ts +11 -0
- package/client/viewparts/figure-asset-viewer.ts +177 -0
- package/client/viewparts/figure-detail-dock.ts +187 -0
- package/dist-client/graphql/index.d.ts +5 -0
- package/dist-client/graphql/index.js +5 -1
- package/dist-client/graphql/index.js.map +1 -1
- package/dist-client/modeller/canvas.js +7 -0
- package/dist-client/modeller/canvas.js.map +1 -1
- package/dist-client/modeller/clips.d.ts +1 -1
- package/dist-client/modeller/editor.d.ts +3 -2
- package/dist-client/modeller/editor.js +4 -9
- package/dist-client/modeller/editor.js.map +1 -1
- package/dist-client/modeller/figure-links.js +35 -22
- package/dist-client/modeller/figure-links.js.map +1 -1
- package/dist-client/modeller/figure-preview.d.ts +2 -0
- package/dist-client/modeller/figure-preview.js +38 -10
- package/dist-client/modeller/figure-preview.js.map +1 -1
- package/dist-client/modeller/joint-drag.js +3 -3
- package/dist-client/modeller/joint-drag.js.map +1 -1
- package/dist-client/modeller/links.d.ts +18 -3
- package/dist-client/modeller/links.js +21 -10
- package/dist-client/modeller/links.js.map +1 -1
- package/dist-client/modeller/preview-model.d.ts +5 -0
- package/dist-client/modeller/preview-model.js +7 -0
- package/dist-client/modeller/preview-model.js.map +1 -1
- package/dist-client/modeller/recipes.js +5 -1
- package/dist-client/modeller/recipes.js.map +1 -1
- package/dist-client/pages/figure-list-page.d.ts +2 -3
- package/dist-client/pages/figure-list-page.js +4 -4
- package/dist-client/pages/figure-list-page.js.map +1 -1
- package/dist-client/pages/figure-modeller-page.d.ts +1 -0
- package/dist-client/pages/figure-modeller-page.js +7 -1
- package/dist-client/pages/figure-modeller-page.js.map +1 -1
- package/dist-client/route.d.ts +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/types.d.ts +1 -1
- package/dist-client/types.js.map +1 -1
- package/dist-client/viewparts/detail-motion.d.ts +6 -0
- package/dist-client/viewparts/detail-motion.js +9 -0
- package/dist-client/viewparts/detail-motion.js.map +1 -0
- package/dist-client/viewparts/figure-asset-viewer.d.ts +31 -0
- package/dist-client/viewparts/figure-asset-viewer.js +198 -0
- package/dist-client/viewparts/figure-asset-viewer.js.map +1 -0
- package/dist-client/viewparts/figure-detail-dock.d.ts +36 -0
- package/dist-client/viewparts/figure-detail-dock.js +224 -0
- package/dist-client/viewparts/figure-detail-dock.js.map +1 -0
- package/package.json +6 -6
- package/test/browser/detail-page.js +34 -0
- package/test/browser/serve-detail.mjs +10 -0
- package/test/detail-motion.test.ts +26 -0
- package/test/links.test.ts +20 -1
- package/test/modeller-route.test.ts +12 -2
- package/test/preview.test.ts +39 -1
- package/test/verdict.test.ts +3 -1
- package/translations/en.json +19 -1
- package/translations/ja.json +19 -1
- package/translations/ko.json +19 -1
- package/translations/ms.json +19 -1
- package/translations/zh.json +19 -1
package/dist-client/types.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 화면이 다루는 모양. 도형 자산의 타입은 `@hatiolab/figure-model` 이 정한다 — 여기서 다시 적지 않는다.
|
|
3
3
|
*/
|
|
4
|
-
/** 서버가 돌려주는 Figure 한 벌. `source` 는
|
|
4
|
+
/** 서버가 돌려주는 Figure 한 벌. `source` 는 상세 또는 저작면을 열 때만 받는다. */
|
|
5
5
|
export interface Figure {
|
|
6
6
|
id: string;
|
|
7
7
|
/** 컴포넌트 타입 이름. **만든 뒤에 고칠 수 없다.** */
|
package/dist-client/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../client/types.ts"],"names":[],"mappings":"AAAA;;GAEG","sourcesContent":["/**\n * 화면이 다루는 모양. 도형 자산의 타입은 `@hatiolab/figure-model` 이 정한다 — 여기서 다시 적지 않는다.\n */\n\n/** 서버가 돌려주는 Figure 한 벌. `source` 는
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../client/types.ts"],"names":[],"mappings":"AAAA;;GAEG","sourcesContent":["/**\n * 화면이 다루는 모양. 도형 자산의 타입은 `@hatiolab/figure-model` 이 정한다 — 여기서 다시 적지 않는다.\n */\n\n/** 서버가 돌려주는 Figure 한 벌. `source` 는 상세 또는 저작면을 열 때만 받는다. */\nexport interface Figure {\n id: string\n /** 컴포넌트 타입 이름. **만든 뒤에 고칠 수 없다.** */\n type: string\n name: string\n description?: string\n /** 분류 하나 — 팔레트에서 묶어 보이는 기준. */\n category?: string\n /** 태그 여럿 — 분류로는 못 하는 찾기. */\n tags?: string[]\n state?: 'draft' | 'released'\n /** 재활용 점수 0~100. 서버가 정본에서 계산한다 — 화면이 보내는 값이 아니다. */\n score?: number\n triangles?: number\n groups?: number\n thumbnail?: string\n thumbnailUpdatedAt?: string\n updatedAt?: string\n updater?: { id: string; name: string }\n /** 도형 자산 JSON 문자열. 목록 질의에는 실려 오지 않는다. */\n source?: string\n properties?: string\n version?: number\n}\n\n/**\n * 발행된 판 하나.\n *\n * 저작 중 저장은 여기 오지 않는다 — 판본은 발행에서만 생긴다.\n */\nexport interface FigureVersion {\n version: number\n /** 발행하며 남긴 말. */\n comment?: string\n state?: 'draft' | 'released'\n updatedAt?: string\n updater?: { id: string; name: string }\n score?: number\n triangles?: number\n groups?: number\n}\n\nexport interface FigureListResult {\n items: Figure[]\n total: number\n}\n\n/**\n * 새 Figure.\n *\n * `score`·`triangles`·`groups` 가 없다 — **서버가 정본에서 계산한다.** 화면이 보내면\n * 거짓이 들어오고 목록이 실물과 다른 것을 보여 준다.\n */\nexport interface NewFigure {\n type: string\n name: string\n description?: string\n category?: string\n tags?: string[]\n source: string\n properties?: string\n state?: 'draft' | 'released'\n thumbnail?: string\n}\n\n/**\n * 수정.\n *\n * `type` 이 없다 — 저장되는 식별자라 고칠 수 없다. 배치된 도면이 그 이름으로 타입을\n * 찾으므로, 바꾸면 그 도면이 빈 화면이 된다.\n */\nexport interface FigurePatch {\n /** 발행 전 초안에서만 받는다 — 발행된 뒤에는 문이 거절한다(ADR-0063 결정 4). */\n type?: string\n name?: string\n description?: string\n category?: string\n /** 빈 배열은 「전부 지운다」, 안 주는 것은 「건드리지 않는다」. */\n tags?: string[]\n source?: string\n properties?: string\n state?: 'draft' | 'released'\n thumbnail?: string\n}\n\n/** 검증 결과 한 줄. 코드는 figure-model 의 것을 그대로 쓴다. */\nexport interface FigureFinding {\n code: string\n message: string\n at?: string\n}\n\n/**\n * 발행 판정 한 줄.\n *\n * `FigureFinding` 과 달리 **`blocking` 이 있다.** 저장 쪽 발견은 정의상 막지 않는 것들이고,\n * 발행 판정은 막는 것과 알리는 것을 함께 내므로 어느 쪽인지를 값이 스스로 말한다 — 코드를 보고\n * 화면이 다시 분류하면 규칙이 두 벌이 된다.\n */\nexport interface FigureGateFinding {\n code: string\n message: string\n /** 왜 그것이 문제인가 — 사람이 무엇을 보게 되나. */\n why?: string\n /** 무엇을 바꾸면 되나 — 부품 이름과 값으로. */\n how?: string\n /** 갈라진 부품 이름들, 또는 정본 안의 경로. */\n at?: string\n blocking: boolean\n}\n\n/** 발행해도 되나. `blocked` 면 서버가 거절한다. */\nexport interface FigureInspection {\n blocked: boolean\n findings: FigureGateFinding[]\n}\n"]}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { FigureAsset } from '@hatiolab/figure-model';
|
|
2
|
+
/** External values win over drivers; release only the controls owned by the chosen clip. */
|
|
3
|
+
export declare function parametersForClip(asset: FigureAsset, values: Record<string, number>, clip: string): {
|
|
4
|
+
[k: string]: number;
|
|
5
|
+
};
|
|
6
|
+
export declare function clipForControl(asset: FigureAsset, id: string): string | undefined;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** External values win over drivers; release only the controls owned by the chosen clip. */
|
|
2
|
+
export function parametersForClip(asset, values, clip) {
|
|
3
|
+
const driven = new Set((asset.drivers ?? []).filter(d => d.clip === clip).map(d => d.state));
|
|
4
|
+
return Object.fromEntries(Object.entries(values).filter(([id]) => !driven.has(id)));
|
|
5
|
+
}
|
|
6
|
+
export function clipForControl(asset, id) {
|
|
7
|
+
return asset.drivers?.find(d => d.state === id)?.clip;
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=detail-motion.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detail-motion.js","sourceRoot":"","sources":["../../client/viewparts/detail-motion.ts"],"names":[],"mappings":"AAEA,4FAA4F;AAC5F,MAAM,UAAU,iBAAiB,CAAC,KAAkB,EAAE,MAA8B,EAAE,IAAY;IAChG,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAA;IAC5F,OAAO,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;AACrF,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAAkB,EAAE,EAAU;IAC3D,OAAO,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,EAAE,CAAC,EAAE,IAAI,CAAA;AACvD,CAAC","sourcesContent":["import type { FigureAsset } from '@hatiolab/figure-model'\n\n/** External values win over drivers; release only the controls owned by the chosen clip. */\nexport function parametersForClip(asset: FigureAsset, values: Record<string, number>, clip: string) {\n const driven = new Set((asset.drivers ?? []).filter(d => d.clip === clip).map(d => d.state))\n return Object.fromEntries(Object.entries(values).filter(([id]) => !driven.has(id)))\n}\n\nexport function clipForControl(asset: FigureAsset, id: string) {\n return asset.drivers?.find(d => d.state === id)?.clip\n}\n"]}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { LitElement, type PropertyValues } from 'lit';
|
|
2
|
+
import { type FigureAsset } from '@hatiolab/figure-model';
|
|
3
|
+
declare const FigureAssetViewer_base: (new (...args: any[]) => LitElement) & typeof LitElement;
|
|
4
|
+
/** A single board instance. Sizes and poses are transient, and never change the asset or call a mutation. */
|
|
5
|
+
export declare class FigureAssetViewer extends FigureAssetViewer_base {
|
|
6
|
+
static styles: import("lit").CSSResult;
|
|
7
|
+
asset?: FigureAsset;
|
|
8
|
+
private stage;
|
|
9
|
+
private sizes;
|
|
10
|
+
private values;
|
|
11
|
+
private playing;
|
|
12
|
+
private error;
|
|
13
|
+
private scene?;
|
|
14
|
+
private registered?;
|
|
15
|
+
private observer?;
|
|
16
|
+
private needsScene;
|
|
17
|
+
private played;
|
|
18
|
+
connectedCallback(): void;
|
|
19
|
+
protected updated(changed: PropertyValues): void;
|
|
20
|
+
firstUpdated(): void;
|
|
21
|
+
disconnectedCallback(): void;
|
|
22
|
+
render(): import("lit-html").TemplateResult<1>;
|
|
23
|
+
private tell;
|
|
24
|
+
private resize;
|
|
25
|
+
private pose;
|
|
26
|
+
private play;
|
|
27
|
+
private reset;
|
|
28
|
+
private standUp;
|
|
29
|
+
private teardown;
|
|
30
|
+
}
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { __decorate, __metadata } from "tslib";
|
|
2
|
+
import { css, html, LitElement, nothing } from 'lit';
|
|
3
|
+
import { customElement, property, query, state } from 'lit/decorators.js';
|
|
4
|
+
import { i18next, localize } from '@operato/i18n';
|
|
5
|
+
import { compileFigureAsset, FIGURE_INSTANCE_SIZE_INPUTS } from '@hatiolab/figure-model';
|
|
6
|
+
import { create, registerFigure, unregisterFigure } from '@hatiolab/things-scene';
|
|
7
|
+
import { authoredSizeOf } from '../modeller/preview-model.js';
|
|
8
|
+
import { controlsOf } from '../modeller/controls.js';
|
|
9
|
+
import { DEFAULT_VIEW, previewBoardDepth, sceneLook } from '../modeller/figure-view.js';
|
|
10
|
+
import { clipForControl, parametersForClip } from './detail-motion.js';
|
|
11
|
+
/** A single board instance. Sizes and poses are transient, and never change the asset or call a mutation. */
|
|
12
|
+
let FigureAssetViewer = class FigureAssetViewer extends localize(i18next)(LitElement) {
|
|
13
|
+
constructor() {
|
|
14
|
+
super(...arguments);
|
|
15
|
+
this.sizes = { x: 1, y: 1, z: 1 };
|
|
16
|
+
this.values = {};
|
|
17
|
+
this.playing = '';
|
|
18
|
+
this.error = '';
|
|
19
|
+
this.needsScene = false;
|
|
20
|
+
this.played = new Set();
|
|
21
|
+
}
|
|
22
|
+
static { this.styles = css `
|
|
23
|
+
:host { display: block; min-width: 0; }
|
|
24
|
+
[viewport] { height: 300px; position: relative; background: #858585; }
|
|
25
|
+
[stage] { position: absolute; inset: 0; width: 100%; height: 100%; }
|
|
26
|
+
section { padding: 12px 16px; border-bottom: 1px solid var(--md-sys-color-outline-variant); }
|
|
27
|
+
h3 { margin: 0 0 10px; font-size: var(--ui-t-body, 13px); }
|
|
28
|
+
label { display: grid; grid-template-columns: 72px minmax(0, 1fr) 90px; align-items: center; gap: 8px; margin: 8px 0; }
|
|
29
|
+
input { min-width: 0; width: 100%; box-sizing: border-box; }
|
|
30
|
+
input[type=number] { padding: 6px; }
|
|
31
|
+
input, button { font: inherit; color: inherit; }
|
|
32
|
+
button { padding: 8px 12px; border: 1px solid var(--md-sys-color-outline); border-radius: 8px; background: var(--md-sys-color-surface); cursor: pointer; }
|
|
33
|
+
[clips] { display: flex; flex-wrap: wrap; gap: 8px; }
|
|
34
|
+
p { font-size: 12px; line-height: 1.5; }
|
|
35
|
+
[error] { color: var(--md-sys-color-error); }
|
|
36
|
+
`; }
|
|
37
|
+
connectedCallback() {
|
|
38
|
+
super.connectedCallback();
|
|
39
|
+
this.needsScene = true;
|
|
40
|
+
if (this.hasUpdated)
|
|
41
|
+
this.observer?.observe(this.stage);
|
|
42
|
+
this.requestUpdate();
|
|
43
|
+
}
|
|
44
|
+
updated(changed) {
|
|
45
|
+
// Reconnection and a new asset can arrive in the same update. Build exactly once.
|
|
46
|
+
if (changed.has('asset') || this.needsScene) {
|
|
47
|
+
this.needsScene = false;
|
|
48
|
+
this.standUp();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
firstUpdated() {
|
|
52
|
+
this.observer = new ResizeObserver(() => this.scene?.resize?.());
|
|
53
|
+
this.observer.observe(this.stage);
|
|
54
|
+
}
|
|
55
|
+
disconnectedCallback() {
|
|
56
|
+
super.disconnectedCallback();
|
|
57
|
+
this.observer?.disconnect();
|
|
58
|
+
this.teardown();
|
|
59
|
+
}
|
|
60
|
+
render() {
|
|
61
|
+
const asset = this.asset;
|
|
62
|
+
return html `<div viewport><div stage></div></div>
|
|
63
|
+
${this.error ? html `<p error role="alert">${this.error}</p>` : nothing}
|
|
64
|
+
${asset ? html `<section>
|
|
65
|
+
<h3>${i18next.t('figure.label.detail-size')}</h3>
|
|
66
|
+
${['x', 'y', 'z'].map(axis => {
|
|
67
|
+
const input = asset.document.model.inputs.find(input => input.id === FIGURE_INSTANCE_SIZE_INPUTS[axis]);
|
|
68
|
+
return html `<label><span>${axis.toUpperCase()} · mm</span>
|
|
69
|
+
<input type="range" aria-label=${`${axis.toUpperCase()} mm`} min=${input?.min ?? this.sizes[axis]} max=${input?.max ?? this.sizes[axis]} step="any"
|
|
70
|
+
.value=${String(this.sizes[axis])} ?disabled=${!input || input.min === input.max}
|
|
71
|
+
@input=${(e) => this.resize(axis, e.target.valueAsNumber)}>
|
|
72
|
+
<input type="number" aria-label=${`${axis.toUpperCase()} mm value`} min=${input?.min ?? this.sizes[axis]} max=${input?.max ?? this.sizes[axis]} step="any"
|
|
73
|
+
.value=${String(this.sizes[axis])} ?disabled=${!input || input.min === input.max}
|
|
74
|
+
@change=${(e) => this.resize(axis, e.target.valueAsNumber)}>
|
|
75
|
+
</label>`;
|
|
76
|
+
})}
|
|
77
|
+
<button @click=${() => this.reset()}>${i18next.t('figure.button.detail-reset')}</button>
|
|
78
|
+
<p>${i18next.t('figure.text.detail-transient')}</p>
|
|
79
|
+
</section>
|
|
80
|
+
<section><h3>${i18next.t('figure.label.motion')}</h3>
|
|
81
|
+
${controlsOf(asset, this.values).map(c => html `<label><span>${c.label} · ${c.unit}</span>
|
|
82
|
+
<input type="range" aria-label=${c.label} min=${c.min} max=${c.max} step=${c.step || 'any'} .value=${String(c.value)}
|
|
83
|
+
?disabled=${!!this.playing && clipForControl(asset, c.id) === this.playing}
|
|
84
|
+
@input=${(e) => this.pose(c.id, e.target.valueAsNumber)}>
|
|
85
|
+
<output>${this.playing && clipForControl(asset, c.id) === this.playing ? i18next.t('figure.text.detail-playing')
|
|
86
|
+
: this.played.has(clipForControl(asset, c.id) ?? '') && !(c.id in this.values) ? i18next.t('figure.text.detail-paused') : Math.round(c.value * 100) / 100}</output></label>`)}
|
|
87
|
+
<div clips>${[...new Set((asset.drivers ?? []).map(d => d.clip))].map(name => html `<button aria-pressed=${String(this.playing === name)}
|
|
88
|
+
@click=${() => this.play(name)}>${this.playing === name ? '⏸' : '▶'} ${name}</button>`)}</div>
|
|
89
|
+
${!controlsOf(asset).length && ![...new Set((asset.drivers ?? []).map(d => d.clip))].length ? html `<p>${i18next.t('figure.text.detail-no-motion')}</p>` : nothing}
|
|
90
|
+
</section>` : nothing}`;
|
|
91
|
+
}
|
|
92
|
+
tell(key, value) {
|
|
93
|
+
this.scene?.root?.components?.[0]?.set(key, value);
|
|
94
|
+
}
|
|
95
|
+
resize(axis, value) {
|
|
96
|
+
const input = this.asset?.document.model.inputs.find(input => input.id === FIGURE_INSTANCE_SIZE_INPUTS[axis]);
|
|
97
|
+
if (!input || !Number.isFinite(value)) {
|
|
98
|
+
this.requestUpdate();
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const used = Math.min(input.max, Math.max(input.min, value));
|
|
102
|
+
this.sizes = { ...this.sizes, [axis]: used };
|
|
103
|
+
this.tell(axis === 'x' ? 'width' : axis === 'y' ? 'depth' : 'height', used);
|
|
104
|
+
this.scene?.root?.fitCameraToModel?.();
|
|
105
|
+
}
|
|
106
|
+
pose(id, value) {
|
|
107
|
+
if (!Number.isFinite(value))
|
|
108
|
+
return;
|
|
109
|
+
this.values = { ...this.values, [id]: value };
|
|
110
|
+
this.tell('parameters', this.values);
|
|
111
|
+
}
|
|
112
|
+
play(name) {
|
|
113
|
+
this.playing = this.playing === name ? '' : name;
|
|
114
|
+
if (this.playing && this.asset) {
|
|
115
|
+
this.played.add(name);
|
|
116
|
+
this.values = parametersForClip(this.asset, this.values, name);
|
|
117
|
+
this.tell('parameters', this.values);
|
|
118
|
+
}
|
|
119
|
+
this.tell('clips', Object.fromEntries([...new Set((this.asset?.drivers ?? []).map(d => d.clip))].map(key => [key, key === this.playing ? 1 : 0])));
|
|
120
|
+
}
|
|
121
|
+
reset() {
|
|
122
|
+
if (!this.asset)
|
|
123
|
+
return;
|
|
124
|
+
// A new instance also resets driver clocks. Clearing speeds alone freezes the last pose.
|
|
125
|
+
this.standUp();
|
|
126
|
+
}
|
|
127
|
+
standUp() {
|
|
128
|
+
this.teardown();
|
|
129
|
+
this.error = '';
|
|
130
|
+
const asset = this.asset;
|
|
131
|
+
if (!asset)
|
|
132
|
+
return;
|
|
133
|
+
this.values = {};
|
|
134
|
+
this.playing = '';
|
|
135
|
+
this.played.clear();
|
|
136
|
+
try {
|
|
137
|
+
compileFigureAsset(asset);
|
|
138
|
+
if (!asset.appearance.length)
|
|
139
|
+
throw Error(i18next.t('figure.text.preview-needs-a-part'));
|
|
140
|
+
this.sizes = authoredSizeOf(asset);
|
|
141
|
+
const type = `figure-detail-${crypto.randomUUID()}`;
|
|
142
|
+
registerFigure(type, asset);
|
|
143
|
+
this.registered = type;
|
|
144
|
+
const size = this.sizes;
|
|
145
|
+
this.scene = create({ target: this.stage, mode: 0, model: {
|
|
146
|
+
type: 'model-layer', width: size.x * 2, height: size.z * 2,
|
|
147
|
+
depth: previewBoardDepth({ placement: asset.placement, base: { y: size.y } }, DEFAULT_VIEW.ceilingHeight, 1),
|
|
148
|
+
threed: true, sky: 'color', ...sceneLook(DEFAULT_VIEW),
|
|
149
|
+
placement: DEFAULT_VIEW.placement, heights: { floor: 0, ceiling: DEFAULT_VIEW.ceilingHeight },
|
|
150
|
+
pixelRatio: Math.min(3, (window.devicePixelRatio || 1) * 2),
|
|
151
|
+
components: [{ type, id: 'detail-instance', left: size.x / 2, top: size.z / 2, width: size.x, height: size.z, depth: size.y }]
|
|
152
|
+
} });
|
|
153
|
+
this.scene.resize?.();
|
|
154
|
+
this.scene.fit?.('ratio');
|
|
155
|
+
this.scene.root?.fitCameraToModel?.();
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
this.error = error.message;
|
|
159
|
+
this.teardown();
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
teardown() {
|
|
163
|
+
this.scene?.dispose?.();
|
|
164
|
+
this.scene = undefined;
|
|
165
|
+
if (this.registered)
|
|
166
|
+
unregisterFigure(this.registered);
|
|
167
|
+
this.registered = undefined;
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
__decorate([
|
|
171
|
+
property({ attribute: false }),
|
|
172
|
+
__metadata("design:type", Object)
|
|
173
|
+
], FigureAssetViewer.prototype, "asset", void 0);
|
|
174
|
+
__decorate([
|
|
175
|
+
query('[stage]'),
|
|
176
|
+
__metadata("design:type", HTMLElement)
|
|
177
|
+
], FigureAssetViewer.prototype, "stage", void 0);
|
|
178
|
+
__decorate([
|
|
179
|
+
state(),
|
|
180
|
+
__metadata("design:type", Object)
|
|
181
|
+
], FigureAssetViewer.prototype, "sizes", void 0);
|
|
182
|
+
__decorate([
|
|
183
|
+
state(),
|
|
184
|
+
__metadata("design:type", Object)
|
|
185
|
+
], FigureAssetViewer.prototype, "values", void 0);
|
|
186
|
+
__decorate([
|
|
187
|
+
state(),
|
|
188
|
+
__metadata("design:type", Object)
|
|
189
|
+
], FigureAssetViewer.prototype, "playing", void 0);
|
|
190
|
+
__decorate([
|
|
191
|
+
state(),
|
|
192
|
+
__metadata("design:type", Object)
|
|
193
|
+
], FigureAssetViewer.prototype, "error", void 0);
|
|
194
|
+
FigureAssetViewer = __decorate([
|
|
195
|
+
customElement('figure-asset-viewer')
|
|
196
|
+
], FigureAssetViewer);
|
|
197
|
+
export { FigureAssetViewer };
|
|
198
|
+
//# sourceMappingURL=figure-asset-viewer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"figure-asset-viewer.js","sourceRoot":"","sources":["../../client/viewparts/figure-asset-viewer.ts"],"names":[],"mappings":"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","sourcesContent":["import { css, html, LitElement, nothing, type PropertyValues } from 'lit'\nimport { customElement, property, query, state } from 'lit/decorators.js'\nimport { i18next, localize } from '@operato/i18n'\nimport { compileFigureAsset, FIGURE_INSTANCE_SIZE_INPUTS, type FigureAsset } from '@hatiolab/figure-model'\nimport { create, registerFigure, unregisterFigure } from '@hatiolab/things-scene'\nimport { authoredSizeOf } from '../modeller/preview-model.js'\nimport { controlsOf } from '../modeller/controls.js'\nimport { DEFAULT_VIEW, previewBoardDepth, sceneLook } from '../modeller/figure-view.js'\nimport { clipForControl, parametersForClip } from './detail-motion.js'\n\ntype Axis = 'x' | 'y' | 'z'\ninterface Scene {\n dispose?(): void\n resize?(): void\n fit?(type?: string): void\n root?: {\n fitCameraToModel?(): void\n components?: Array<{ set(key: string, value: unknown): void }>\n }\n}\n\n/** A single board instance. Sizes and poses are transient, and never change the asset or call a mutation. */\n@customElement('figure-asset-viewer')\nexport class FigureAssetViewer extends localize(i18next)(LitElement) {\n static styles = css`\n :host { display: block; min-width: 0; }\n [viewport] { height: 300px; position: relative; background: #858585; }\n [stage] { position: absolute; inset: 0; width: 100%; height: 100%; }\n section { padding: 12px 16px; border-bottom: 1px solid var(--md-sys-color-outline-variant); }\n h3 { margin: 0 0 10px; font-size: var(--ui-t-body, 13px); }\n label { display: grid; grid-template-columns: 72px minmax(0, 1fr) 90px; align-items: center; gap: 8px; margin: 8px 0; }\n input { min-width: 0; width: 100%; box-sizing: border-box; }\n input[type=number] { padding: 6px; }\n input, button { font: inherit; color: inherit; }\n button { padding: 8px 12px; border: 1px solid var(--md-sys-color-outline); border-radius: 8px; background: var(--md-sys-color-surface); cursor: pointer; }\n [clips] { display: flex; flex-wrap: wrap; gap: 8px; }\n p { font-size: 12px; line-height: 1.5; }\n [error] { color: var(--md-sys-color-error); }\n `\n @property({ attribute: false }) asset?: FigureAsset\n @query('[stage]') private stage!: HTMLElement\n @state() private sizes: Record<Axis, number> = { x: 1, y: 1, z: 1 }\n @state() private values: Record<string, number> = {}\n @state() private playing = ''\n @state() private error = ''\n private scene?: Scene\n private registered?: string\n private observer?: ResizeObserver\n private needsScene = false\n private played = new Set<string>()\n\n connectedCallback() {\n super.connectedCallback()\n this.needsScene = true\n if (this.hasUpdated) this.observer?.observe(this.stage)\n this.requestUpdate()\n }\n\n protected updated(changed: PropertyValues) {\n // Reconnection and a new asset can arrive in the same update. Build exactly once.\n if (changed.has('asset') || this.needsScene) {\n this.needsScene = false\n this.standUp()\n }\n }\n firstUpdated() {\n this.observer = new ResizeObserver(() => this.scene?.resize?.())\n this.observer.observe(this.stage)\n }\n disconnectedCallback() {\n super.disconnectedCallback()\n this.observer?.disconnect()\n this.teardown()\n }\n\n render() {\n const asset = this.asset\n return html`<div viewport><div stage></div></div>\n ${this.error ? html`<p error role=\"alert\">${this.error}</p>` : nothing}\n ${asset ? html`<section>\n <h3>${i18next.t('figure.label.detail-size')}</h3>\n ${(['x', 'y', 'z'] as const).map(axis => {\n const input = asset.document.model.inputs.find(input => input.id === FIGURE_INSTANCE_SIZE_INPUTS[axis])\n return html`<label><span>${axis.toUpperCase()} · mm</span>\n <input type=\"range\" aria-label=${`${axis.toUpperCase()} mm`} min=${input?.min ?? this.sizes[axis]} max=${input?.max ?? this.sizes[axis]} step=\"any\"\n .value=${String(this.sizes[axis])} ?disabled=${!input || input.min === input.max}\n @input=${(e: Event) => this.resize(axis, (e.target as HTMLInputElement).valueAsNumber)}>\n <input type=\"number\" aria-label=${`${axis.toUpperCase()} mm value`} min=${input?.min ?? this.sizes[axis]} max=${input?.max ?? this.sizes[axis]} step=\"any\"\n .value=${String(this.sizes[axis])} ?disabled=${!input || input.min === input.max}\n @change=${(e: Event) => this.resize(axis, (e.target as HTMLInputElement).valueAsNumber)}>\n </label>`\n })}\n <button @click=${() => this.reset()}>${i18next.t('figure.button.detail-reset')}</button>\n <p>${i18next.t('figure.text.detail-transient')}</p>\n </section>\n <section><h3>${i18next.t('figure.label.motion')}</h3>\n ${controlsOf(asset, this.values).map(c => html`<label><span>${c.label} · ${c.unit}</span>\n <input type=\"range\" aria-label=${c.label} min=${c.min} max=${c.max} step=${c.step || 'any'} .value=${String(c.value)}\n ?disabled=${!!this.playing && clipForControl(asset, c.id) === this.playing}\n @input=${(e: Event) => this.pose(c.id, (e.target as HTMLInputElement).valueAsNumber)}>\n <output>${this.playing && clipForControl(asset, c.id) === this.playing ? i18next.t('figure.text.detail-playing')\n : this.played.has(clipForControl(asset, c.id) ?? '') && !(c.id in this.values) ? i18next.t('figure.text.detail-paused') : Math.round(c.value * 100) / 100}</output></label>`)}\n <div clips>${[...new Set((asset.drivers ?? []).map(d => d.clip))].map(name => html`<button aria-pressed=${String(this.playing === name)}\n @click=${() => this.play(name)}>${this.playing === name ? '⏸' : '▶'} ${name}</button>`)}</div>\n ${!controlsOf(asset).length && ![...new Set((asset.drivers ?? []).map(d => d.clip))].length ? html`<p>${i18next.t('figure.text.detail-no-motion')}</p>` : nothing}\n </section>` : nothing}`\n }\n\n private tell(key: string, value: unknown) {\n this.scene?.root?.components?.[0]?.set(key, value)\n }\n private resize(axis: Axis, value: number) {\n const input = this.asset?.document.model.inputs.find(input => input.id === FIGURE_INSTANCE_SIZE_INPUTS[axis])\n if (!input || !Number.isFinite(value)) { this.requestUpdate(); return }\n const used = Math.min(input.max, Math.max(input.min, value))\n this.sizes = { ...this.sizes, [axis]: used }\n this.tell(axis === 'x' ? 'width' : axis === 'y' ? 'depth' : 'height', used)\n this.scene?.root?.fitCameraToModel?.()\n }\n private pose(id: string, value: number) {\n if (!Number.isFinite(value)) return\n this.values = { ...this.values, [id]: value }\n this.tell('parameters', this.values)\n }\n private play(name: string) {\n this.playing = this.playing === name ? '' : name\n if (this.playing && this.asset) {\n this.played.add(name)\n this.values = parametersForClip(this.asset, this.values, name)\n this.tell('parameters', this.values)\n }\n this.tell('clips', Object.fromEntries([...new Set((this.asset?.drivers ?? []).map(d => d.clip))].map(key => [key, key === this.playing ? 1 : 0])))\n }\n private reset() {\n if (!this.asset) return\n // A new instance also resets driver clocks. Clearing speeds alone freezes the last pose.\n this.standUp()\n }\n private standUp() {\n this.teardown()\n this.error = ''\n const asset = this.asset\n if (!asset) return\n this.values = {}\n this.playing = ''\n this.played.clear()\n try {\n compileFigureAsset(asset)\n if (!asset.appearance.length) throw Error(i18next.t('figure.text.preview-needs-a-part'))\n this.sizes = authoredSizeOf(asset)\n const type = `figure-detail-${crypto.randomUUID()}`\n registerFigure(type, asset)\n this.registered = type\n const size = this.sizes\n this.scene = create({ target: this.stage, mode: 0, model: {\n type: 'model-layer', width: size.x * 2, height: size.z * 2,\n depth: previewBoardDepth({ placement: asset.placement, base: { y: size.y } }, DEFAULT_VIEW.ceilingHeight, 1),\n threed: true, sky: 'color', ...sceneLook(DEFAULT_VIEW),\n placement: DEFAULT_VIEW.placement, heights: { floor: 0, ceiling: DEFAULT_VIEW.ceilingHeight },\n pixelRatio: Math.min(3, (window.devicePixelRatio || 1) * 2),\n components: [{ type, id: 'detail-instance', left: size.x / 2, top: size.z / 2, width: size.x, height: size.z, depth: size.y }]\n } }) as Scene\n this.scene.resize?.()\n this.scene.fit?.('ratio')\n this.scene.root?.fitCameraToModel?.()\n } catch (error) {\n this.error = (error as Error).message\n this.teardown()\n }\n }\n private teardown() {\n this.scene?.dispose?.()\n this.scene = undefined\n if (this.registered) unregisterFigure(this.registered)\n this.registered = undefined\n }\n}\n"]}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { LitElement } from 'lit';
|
|
2
|
+
import type { Figure } from '../types.js';
|
|
3
|
+
import './figure-asset-viewer.js';
|
|
4
|
+
export interface FigureDetailRecord {
|
|
5
|
+
saved: Figure;
|
|
6
|
+
released?: {
|
|
7
|
+
version: number;
|
|
8
|
+
source: string;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
declare const FigureDetailDock_base: (new (...args: any[]) => LitElement) & typeof LitElement;
|
|
12
|
+
/** Catalogue details outlive the page; choosing a size or a pose has no write path. */
|
|
13
|
+
export declare class FigureDetailDock extends FigureDetailDock_base {
|
|
14
|
+
static styles: import("lit").CSSResult;
|
|
15
|
+
load: (id: string) => Promise<FigureDetailRecord>;
|
|
16
|
+
private figureId;
|
|
17
|
+
private record?;
|
|
18
|
+
private asset?;
|
|
19
|
+
private sourceKind;
|
|
20
|
+
private loading;
|
|
21
|
+
private error;
|
|
22
|
+
private cost?;
|
|
23
|
+
private ticket;
|
|
24
|
+
private unsubscribe?;
|
|
25
|
+
private boundary?;
|
|
26
|
+
private boundaryObserver?;
|
|
27
|
+
show(id: string): Promise<void>;
|
|
28
|
+
private choose;
|
|
29
|
+
connectedCallback(): void;
|
|
30
|
+
disconnectedCallback(): void;
|
|
31
|
+
private place;
|
|
32
|
+
private escape;
|
|
33
|
+
render(): import("lit-html").TemplateResult<1>;
|
|
34
|
+
}
|
|
35
|
+
export declare function openFigureDetail(figure: Figure): void;
|
|
36
|
+
export {};
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { __decorate, __metadata } from "tslib";
|
|
2
|
+
import { css, html, LitElement, nothing } from 'lit';
|
|
3
|
+
import { customElement, property, state } from 'lit/decorators.js';
|
|
4
|
+
import { i18next, localize } from '@operato/i18n';
|
|
5
|
+
import { appendViewpart, UPDATE_VIEWPART, VIEWPART_POSITION, VIEWPART_LEVEL } from '@operato/layout';
|
|
6
|
+
import { navigate, store } from '@operato/shell';
|
|
7
|
+
import { ASSISTANT_DOCK_VIEWPART } from '@things-factory/ai-assistant/dist-client/components/assistant-dock.js';
|
|
8
|
+
import { figureCostOf, parseFigureAsset } from '@hatiolab/figure-model';
|
|
9
|
+
import { capacityFrom } from '../modeller/scene-budget.js';
|
|
10
|
+
import { fetchFigure, fetchReleasedFigure } from '../graphql/index.js';
|
|
11
|
+
import { modellerPathOf } from '../modeller/modeller-route.js';
|
|
12
|
+
import './figure-asset-viewer.js';
|
|
13
|
+
const VIEWPART = 'figure-detail';
|
|
14
|
+
/** Catalogue details outlive the page; choosing a size or a pose has no write path. */
|
|
15
|
+
let FigureDetailDock = class FigureDetailDock extends localize(i18next)(LitElement) {
|
|
16
|
+
constructor() {
|
|
17
|
+
super(...arguments);
|
|
18
|
+
this.load = async (id) => {
|
|
19
|
+
const [saved, released] = await Promise.all([fetchFigure(id), fetchReleasedFigure(id)]);
|
|
20
|
+
if (!saved?.source)
|
|
21
|
+
throw Error(i18next.t('figure.text.detail-no-source'));
|
|
22
|
+
return { saved, released };
|
|
23
|
+
};
|
|
24
|
+
this.figureId = '';
|
|
25
|
+
this.sourceKind = 'saved';
|
|
26
|
+
this.loading = false;
|
|
27
|
+
this.error = '';
|
|
28
|
+
this.ticket = 0;
|
|
29
|
+
this.place = () => {
|
|
30
|
+
const bar = visibleBar(document);
|
|
31
|
+
if (bar !== this.boundary) {
|
|
32
|
+
this.boundaryObserver?.disconnect();
|
|
33
|
+
this.boundary = bar;
|
|
34
|
+
if (bar) {
|
|
35
|
+
this.boundaryObserver = new ResizeObserver(this.place);
|
|
36
|
+
this.boundaryObserver.observe(bar);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
this.style.setProperty('--figure-detail-top', `${bar?.getBoundingClientRect().bottom ?? 44}px`);
|
|
40
|
+
};
|
|
41
|
+
this.escape = (e) => {
|
|
42
|
+
if (e.key === 'Escape')
|
|
43
|
+
hideDetail();
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
static { this.styles = css `
|
|
47
|
+
:host { display: flex; flex-direction: column; width: 440px; max-width: 100vw; height: 100%; min-height: 0;
|
|
48
|
+
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; }
|
|
49
|
+
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); }
|
|
50
|
+
h2 { margin: 0; font-size: var(--ui-t-title, 16px); }
|
|
51
|
+
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; }
|
|
52
|
+
[body] { flex: 1; overflow: auto; min-height: 0; }
|
|
53
|
+
[meta], [status] { padding: 12px 16px; }
|
|
54
|
+
[meta] { display: grid; gap: 8px; }
|
|
55
|
+
[summary] { padding: 12px 16px; border-bottom: 1px solid var(--md-sys-color-outline-variant); }
|
|
56
|
+
h3 { margin: 0 0 8px; font-size: var(--ui-t-body, 13px); }
|
|
57
|
+
dl { display: grid; grid-template-columns: 1fr auto; gap: 6px 12px; margin: 0; }
|
|
58
|
+
dd { margin: 0; font-variant-numeric: tabular-nums; }
|
|
59
|
+
p { margin: 0; line-height: 1.5; }
|
|
60
|
+
code { color: var(--md-sys-color-on-surface-variant); }
|
|
61
|
+
footer { padding: 12px 16px; border-top: 1px solid var(--md-sys-color-outline-variant); }
|
|
62
|
+
footer button { width: 100%; background: var(--md-sys-color-primary-container); color: var(--md-sys-color-on-primary-container); }
|
|
63
|
+
@media (max-width: 1439.98px) {
|
|
64
|
+
: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%); }
|
|
65
|
+
}
|
|
66
|
+
`; }
|
|
67
|
+
async show(id) {
|
|
68
|
+
const ticket = ++this.ticket;
|
|
69
|
+
this.figureId = id;
|
|
70
|
+
this.record = undefined;
|
|
71
|
+
this.asset = undefined;
|
|
72
|
+
this.cost = undefined;
|
|
73
|
+
this.loading = true;
|
|
74
|
+
this.error = '';
|
|
75
|
+
try {
|
|
76
|
+
const record = await this.load(id);
|
|
77
|
+
if (ticket !== this.ticket)
|
|
78
|
+
return;
|
|
79
|
+
this.record = record;
|
|
80
|
+
this.choose(record.released ? 'released' : 'saved');
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
if (ticket === this.ticket)
|
|
84
|
+
this.error = i18next.t('figure.text.detail-load-failed');
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
if (ticket === this.ticket)
|
|
88
|
+
this.loading = false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
choose(kind) {
|
|
92
|
+
this.sourceKind = kind;
|
|
93
|
+
this.asset = undefined;
|
|
94
|
+
this.cost = undefined;
|
|
95
|
+
this.error = '';
|
|
96
|
+
try {
|
|
97
|
+
const source = kind === 'released' ? this.record?.released?.source : this.record?.saved.source;
|
|
98
|
+
if (!source)
|
|
99
|
+
throw Error('missing source');
|
|
100
|
+
const asset = parseFigureAsset(source);
|
|
101
|
+
this.cost = figureCostOf(asset);
|
|
102
|
+
this.asset = asset;
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
this.error = i18next.t('figure.text.detail-invalid-source');
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
connectedCallback() {
|
|
109
|
+
super.connectedCallback();
|
|
110
|
+
window.addEventListener('resize', this.place);
|
|
111
|
+
window.addEventListener('popstate', this.place);
|
|
112
|
+
this.unsubscribe = store.subscribe(() => {
|
|
113
|
+
const state = store.getState();
|
|
114
|
+
const parts = state.layout?.viewparts;
|
|
115
|
+
if (parts?.[ASSISTANT_DOCK_VIEWPART]?.show && parts?.[VIEWPART]?.show)
|
|
116
|
+
hideDetail();
|
|
117
|
+
else
|
|
118
|
+
requestAnimationFrame(this.place);
|
|
119
|
+
});
|
|
120
|
+
this.place();
|
|
121
|
+
window.addEventListener('keydown', this.escape);
|
|
122
|
+
}
|
|
123
|
+
disconnectedCallback() {
|
|
124
|
+
super.disconnectedCallback();
|
|
125
|
+
window.removeEventListener('resize', this.place);
|
|
126
|
+
window.removeEventListener('popstate', this.place);
|
|
127
|
+
window.removeEventListener('keydown', this.escape);
|
|
128
|
+
this.unsubscribe?.();
|
|
129
|
+
this.boundaryObserver?.disconnect();
|
|
130
|
+
this.boundary = undefined;
|
|
131
|
+
}
|
|
132
|
+
render() {
|
|
133
|
+
const saved = this.record?.saved;
|
|
134
|
+
const cost = this.cost;
|
|
135
|
+
const capacity = cost ? capacityFrom(cost.groups, cost.triangles) : undefined;
|
|
136
|
+
return html `<header><h2>${saved?.name || i18next.t('figure.title.detail')}</h2>
|
|
137
|
+
<button aria-label=${i18next.t('figure.button.close')} @click=${() => hideDetail()}>×</button></header>
|
|
138
|
+
<div body>
|
|
139
|
+
${this.loading ? html `<p status role="status">${i18next.t('figure.text.detail-loading')}</p>` : nothing}
|
|
140
|
+
${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}
|
|
141
|
+
${saved ? html `<section meta><code>${saved.type}</code><p>${saved.description || ''}</p>
|
|
142
|
+
<label>${i18next.t('figure.label.detail-source')}
|
|
143
|
+
<select aria-label=${i18next.t('figure.label.detail-source')} .value=${this.sourceKind}
|
|
144
|
+
@change=${(e) => this.choose(e.target.value)}>
|
|
145
|
+
${this.record?.released ? html `<option value="released">${i18next.t('figure.label.detail-released', { version: this.record.released.version })}</option>` : nothing}
|
|
146
|
+
<option value="saved">${i18next.t('figure.label.detail-saved')} · ${i18next.t(saved.state === 'released' ? 'figure.label.figure-released' : 'figure.label.draft')}</option>
|
|
147
|
+
</select></label>
|
|
148
|
+
${!this.record?.released ? html `<p>${i18next.t('figure.text.detail-unreleased')}</p>` : nothing}
|
|
149
|
+
</section>` : nothing}
|
|
150
|
+
${this.asset ? html `<figure-asset-viewer .asset=${this.asset}></figure-asset-viewer>
|
|
151
|
+
<section summary><h3>${i18next.t('figure.label.detail-summary')}</h3><dl>
|
|
152
|
+
<dt>${i18next.t('figure.label.budget-parts')}</dt><dd>${this.asset.appearance.length}</dd>
|
|
153
|
+
<dt>${i18next.t('figure.label.budget-groups')}</dt><dd>${cost?.groups ?? '—'}</dd>
|
|
154
|
+
<dt>${i18next.t('figure.label.triangles')}</dt><dd>${cost?.triangles ?? '—'}</dd>
|
|
155
|
+
</dl>${capacity ? html `<p>${i18next.t('figure.text.parts-budget', { triangles: cost.triangles, groups: cost.groups, instances: capacity.instances })}</p>` : nothing}
|
|
156
|
+
</section>` : nothing}
|
|
157
|
+
</div>
|
|
158
|
+
<footer><button ?disabled=${!saved} @click=${() => {
|
|
159
|
+
navigate(modellerPathOf(saved));
|
|
160
|
+
requestAnimationFrame(() => requestAnimationFrame(this.place));
|
|
161
|
+
}}>${i18next.t('figure.button.detail-edit')}</button></footer>`;
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
__decorate([
|
|
165
|
+
property({ attribute: false }),
|
|
166
|
+
__metadata("design:type", Function)
|
|
167
|
+
], FigureDetailDock.prototype, "load", void 0);
|
|
168
|
+
__decorate([
|
|
169
|
+
state(),
|
|
170
|
+
__metadata("design:type", Object)
|
|
171
|
+
], FigureDetailDock.prototype, "figureId", void 0);
|
|
172
|
+
__decorate([
|
|
173
|
+
state(),
|
|
174
|
+
__metadata("design:type", Object)
|
|
175
|
+
], FigureDetailDock.prototype, "record", void 0);
|
|
176
|
+
__decorate([
|
|
177
|
+
state(),
|
|
178
|
+
__metadata("design:type", Object)
|
|
179
|
+
], FigureDetailDock.prototype, "asset", void 0);
|
|
180
|
+
__decorate([
|
|
181
|
+
state(),
|
|
182
|
+
__metadata("design:type", String)
|
|
183
|
+
], FigureDetailDock.prototype, "sourceKind", void 0);
|
|
184
|
+
__decorate([
|
|
185
|
+
state(),
|
|
186
|
+
__metadata("design:type", Object)
|
|
187
|
+
], FigureDetailDock.prototype, "loading", void 0);
|
|
188
|
+
__decorate([
|
|
189
|
+
state(),
|
|
190
|
+
__metadata("design:type", Object)
|
|
191
|
+
], FigureDetailDock.prototype, "error", void 0);
|
|
192
|
+
__decorate([
|
|
193
|
+
state(),
|
|
194
|
+
__metadata("design:type", void 0)
|
|
195
|
+
], FigureDetailDock.prototype, "cost", void 0);
|
|
196
|
+
FigureDetailDock = __decorate([
|
|
197
|
+
customElement('figure-detail-dock')
|
|
198
|
+
], FigureDetailDock);
|
|
199
|
+
export { FigureDetailDock };
|
|
200
|
+
function visibleBar(root) {
|
|
201
|
+
for (const element of root.querySelectorAll('*')) {
|
|
202
|
+
if (element.tagName === 'OX-CANVAS-BAR' && element.getBoundingClientRect().height > 0)
|
|
203
|
+
return element;
|
|
204
|
+
if (element.shadowRoot) {
|
|
205
|
+
const found = visibleBar(element.shadowRoot);
|
|
206
|
+
if (found)
|
|
207
|
+
return found;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
let dock;
|
|
212
|
+
function hideDetail() {
|
|
213
|
+
store.dispatch({ type: UPDATE_VIEWPART, name: VIEWPART, override: { show: false } });
|
|
214
|
+
}
|
|
215
|
+
export function openFigureDetail(figure) {
|
|
216
|
+
if (!dock) {
|
|
217
|
+
dock = document.createElement('figure-detail-dock');
|
|
218
|
+
}
|
|
219
|
+
store.dispatch({ type: UPDATE_VIEWPART, name: ASSISTANT_DOCK_VIEWPART, override: { show: false } });
|
|
220
|
+
appendViewpart({ name: VIEWPART, position: VIEWPART_POSITION.ASIDEBAR,
|
|
221
|
+
viewpart: { show: true, persistent: true, level: VIEWPART_LEVEL.TOPMOST, template: html `${dock}` } });
|
|
222
|
+
void dock.show(figure.id);
|
|
223
|
+
}
|
|
224
|
+
//# sourceMappingURL=figure-detail-dock.js.map
|
|
@@ -0,0 +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 { 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 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 outlive the page; 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 = 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 }> } }\n const parts = state.layout?.viewparts\n if (parts?.[ASSISTANT_DOCK_VIEWPART]?.show && parts?.[VIEWPART]?.show) 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 navigate(modellerPathOf(saved))\n requestAnimationFrame(() => requestAnimationFrame(this.place))\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\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 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"]}
|