@things-factory/figure-ui 10.1.121 → 10.1.122

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.
@@ -9,9 +9,9 @@ export interface FigureDetailRecord {
9
9
  };
10
10
  }
11
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. */
12
+ /** Catalogue details stay while the catalogue is shown; choosing a size or a pose has no write path. */
13
13
  export declare class FigureDetailDock extends FigureDetailDock_base {
14
- static styles: import("lit").CSSResult;
14
+ static styles: import("lit").CSSResult[];
15
15
  load: (id: string) => Promise<FigureDetailRecord>;
16
16
  private figureId;
17
17
  private record?;
@@ -32,5 +32,6 @@ export declare class FigureDetailDock extends FigureDetailDock_base {
32
32
  private escape;
33
33
  render(): import("lit-html").TemplateResult<1>;
34
34
  }
35
+ export declare function leftOpeningPage(opened: string | undefined, current: string | undefined): boolean;
35
36
  export declare function openFigureDetail(figure: Figure): void;
36
37
  export {};
@@ -4,6 +4,7 @@ import { customElement, property, state } from 'lit/decorators.js';
4
4
  import { i18next, localize } from '@operato/i18n';
5
5
  import { appendViewpart, UPDATE_VIEWPART, VIEWPART_POSITION, VIEWPART_LEVEL } from '@operato/layout';
6
6
  import { navigate, store } from '@operato/shell';
7
+ import { ScrollbarStyles } from '@operato/styles';
7
8
  import { ASSISTANT_DOCK_VIEWPART } from '@things-factory/ai-assistant/dist-client/components/assistant-dock.js';
8
9
  import { figureCostOf, parseFigureAsset } from '@hatiolab/figure-model';
9
10
  import { capacityFrom } from '../modeller/scene-budget.js';
@@ -11,7 +12,7 @@ import { fetchFigure, fetchReleasedFigure } from '../graphql/index.js';
11
12
  import { modellerPathOf } from '../modeller/modeller-route.js';
12
13
  import './figure-asset-viewer.js';
13
14
  const VIEWPART = 'figure-detail';
14
- /** Catalogue details outlive the page; choosing a size or a pose has no write path. */
15
+ /** Catalogue details stay while the catalogue is shown; choosing a size or a pose has no write path. */
15
16
  let FigureDetailDock = class FigureDetailDock extends localize(i18next)(LitElement) {
16
17
  constructor() {
17
18
  super(...arguments);
@@ -43,7 +44,7 @@ let FigureDetailDock = class FigureDetailDock extends localize(i18next)(LitEleme
43
44
  hideDetail();
44
45
  };
45
46
  }
46
- static { this.styles = css `
47
+ static { this.styles = [ScrollbarStyles, css `
47
48
  :host { display: flex; flex-direction: column; width: 440px; max-width: 100vw; height: 100%; min-height: 0;
48
49
  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
50
  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); }
@@ -63,7 +64,7 @@ let FigureDetailDock = class FigureDetailDock extends localize(i18next)(LitEleme
63
64
  @media (max-width: 1439.98px) {
64
65
  :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
  }
66
- `; }
67
+ `]; }
67
68
  async show(id) {
68
69
  const ticket = ++this.ticket;
69
70
  this.figureId = id;
@@ -112,7 +113,7 @@ let FigureDetailDock = class FigureDetailDock extends localize(i18next)(LitEleme
112
113
  this.unsubscribe = store.subscribe(() => {
113
114
  const state = store.getState();
114
115
  const parts = state.layout?.viewparts;
115
- if (parts?.[ASSISTANT_DOCK_VIEWPART]?.show && parts?.[VIEWPART]?.show)
116
+ if (parts?.[VIEWPART]?.show && (parts?.[ASSISTANT_DOCK_VIEWPART]?.show || leftOpeningPage(openedOn, state.route?.page)))
116
117
  hideDetail();
117
118
  else
118
119
  requestAnimationFrame(this.place);
@@ -156,8 +157,9 @@ let FigureDetailDock = class FigureDetailDock extends localize(i18next)(LitEleme
156
157
  </section>` : nothing}
157
158
  </div>
158
159
  <footer><button ?disabled=${!saved} @click=${() => {
160
+ // The modeller shows the figure itself; a preview of it beside the editor only covers the work.
161
+ hideDetail();
159
162
  navigate(modellerPathOf(saved));
160
- requestAnimationFrame(() => requestAnimationFrame(this.place));
161
163
  }}>${i18next.t('figure.button.detail-edit')}</button></footer>`;
162
164
  }
163
165
  };
@@ -209,6 +211,11 @@ function visibleBar(root) {
209
211
  }
210
212
  }
211
213
  let dock;
214
+ /** The page the preview was opened on; the preview belongs to that page and closes when the shell leaves it. */
215
+ let openedOn;
216
+ export function leftOpeningPage(opened, current) {
217
+ return opened !== undefined && current !== opened;
218
+ }
212
219
  function hideDetail() {
213
220
  store.dispatch({ type: UPDATE_VIEWPART, name: VIEWPART, override: { show: false } });
214
221
  }
@@ -216,6 +223,7 @@ export function openFigureDetail(figure) {
216
223
  if (!dock) {
217
224
  dock = document.createElement('figure-detail-dock');
218
225
  }
226
+ openedOn = store.getState().route?.page;
219
227
  store.dispatch({ type: UPDATE_VIEWPART, name: ASSISTANT_DOCK_VIEWPART, override: { show: false } });
220
228
  appendViewpart({ name: VIEWPART, position: VIEWPART_POSITION.ASIDEBAR,
221
229
  viewpart: { show: true, persistent: true, level: VIEWPART_LEVEL.TOPMOST, template: html `${dock}` } });
@@ -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 { 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"]}
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 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\nexport function leftOpeningPage(opened: string | undefined, current: string | undefined) {\n return opened !== undefined && current !== opened\n}\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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@things-factory/figure-ui",
3
- "version": "10.1.121",
3
+ "version": "10.1.122",
4
4
  "publishConfig": {
5
5
  "access": "public",
6
6
  "@things-factory:registry": "https://registry.npmjs.org"
@@ -40,9 +40,9 @@
40
40
  "@operato/shell": "^10.0.0",
41
41
  "@operato/styles": "^10.0.0",
42
42
  "@operato/utils": "^10.0.0",
43
- "@things-factory/ai-assistant": "^10.1.121",
44
- "@things-factory/figure-service": "^10.1.121",
43
+ "@things-factory/ai-assistant": "^10.1.122",
44
+ "@things-factory/figure-service": "^10.1.122",
45
45
  "three": "^0.185.1"
46
46
  },
47
- "gitHead": "23536a73b10f103048a2c893d3960b2a4e0f03dd"
47
+ "gitHead": "a66715e94ef6064cade05229de57d0fe5798d1fb"
48
48
  }
@@ -0,0 +1,31 @@
1
+ import { test } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import { readFileSync } from 'node:fs'
4
+ import { fileURLToPath } from 'node:url'
5
+
6
+ const dock = readFileSync(fileURLToPath(new URL('../client/viewparts/figure-detail-dock.ts', import.meta.url)), 'utf8')
7
+
8
+ test('opening the editor from a catalogue preview closes the preview first', () => {
9
+ const footer = dock.slice(dock.indexOf('<footer>'), dock.indexOf('</footer>'))
10
+ const hide = footer.indexOf('hideDetail()')
11
+ const go = footer.indexOf('navigate(modellerPathOf(saved))')
12
+ assert.ok(go > 0, 'the edit button opens the modeller')
13
+ assert.ok(hide > 0 && hide < go, 'the edit button hides the preview before it navigates')
14
+ })
15
+
16
+ test('the preview scrolls with the shared scrollbar styles', () => {
17
+ assert.match(dock, /import \{ ScrollbarStyles \} from '@operato\/styles'/)
18
+ assert.match(dock, /static styles = \[ScrollbarStyles, css`/)
19
+ })
20
+
21
+ test('leaving the catalogue by any route closes the preview', () => {
22
+ const open = dock.slice(dock.indexOf('export function openFigureDetail'))
23
+ assert.ok(open.includes('openedOn = (store.getState() as { route?: { page?: string } }).route?.page'), 'opening remembers the page')
24
+ assert.ok(dock.includes('leftOpeningPage(openedOn, state.route?.page))) hideDetail()'), 'a route change away from it hides the preview')
25
+ const body = dock.slice(dock.indexOf('export function leftOpeningPage'))
26
+ const left = new Function('opened', 'current', body.slice(body.indexOf('{') + 1, body.indexOf('\n}')))
27
+ assert.equal(left('figure-list', 'figure-list'), false)
28
+ assert.equal(left('figure-list', 'figure-modeller'), true)
29
+ assert.equal(left('figure-list', 'board-list'), true)
30
+ assert.equal(left(undefined, 'figure-modeller'), false)
31
+ })