@things-factory/figure-ui 10.1.154 → 10.1.156

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.
Files changed (110) hide show
  1. package/client/modeller/canvas.ts +1 -1
  2. package/client/modeller/editor.ts +2 -2
  3. package/client/modeller/figure-capability-panel.ts +6 -24
  4. package/client/modeller/figure-carrier.ts +1 -1
  5. package/client/modeller/figure-clips.ts +1 -1
  6. package/client/modeller/figure-controls.ts +2 -2
  7. package/client/modeller/figure-face-mapping.ts +1 -1
  8. package/client/modeller/figure-links.ts +3 -2
  9. package/client/modeller/figure-material-panel.ts +1 -1
  10. package/client/modeller/figure-preview.ts +2 -2
  11. package/client/modeller/figure-properties.ts +1 -1
  12. package/client/modeller/figure-section-editor.ts +1 -1
  13. package/client/modeller/figure-section-panel.ts +1 -1
  14. package/client/modeller/figure-settings.ts +2 -2
  15. package/client/modeller/figure-verdict.ts +1 -1
  16. package/client/pages/figure-list-page.ts +7 -1
  17. package/client/viewparts/figure-detail-dock.ts +90 -41
  18. package/client/viewparts/figure-thumb.ts +2 -22
  19. package/dist-client/modeller/canvas.d.ts +1 -1
  20. package/dist-client/modeller/canvas.js +1 -1
  21. package/dist-client/modeller/canvas.js.map +1 -1
  22. package/dist-client/modeller/editor.js +2 -2
  23. package/dist-client/modeller/editor.js.map +1 -1
  24. package/dist-client/modeller/figure-capability-panel.d.ts +0 -3
  25. package/dist-client/modeller/figure-capability-panel.js +5 -18
  26. package/dist-client/modeller/figure-capability-panel.js.map +1 -1
  27. package/dist-client/modeller/figure-carrier.js +1 -1
  28. package/dist-client/modeller/figure-carrier.js.map +1 -1
  29. package/dist-client/modeller/figure-clips.js +1 -1
  30. package/dist-client/modeller/figure-clips.js.map +1 -1
  31. package/dist-client/modeller/figure-controls.js +2 -2
  32. package/dist-client/modeller/figure-controls.js.map +1 -1
  33. package/dist-client/modeller/figure-face-mapping.js +1 -1
  34. package/dist-client/modeller/figure-face-mapping.js.map +1 -1
  35. package/dist-client/modeller/figure-links.js +3 -2
  36. package/dist-client/modeller/figure-links.js.map +1 -1
  37. package/dist-client/modeller/figure-material-panel.js +1 -1
  38. package/dist-client/modeller/figure-material-panel.js.map +1 -1
  39. package/dist-client/modeller/figure-preview.d.ts +1 -1
  40. package/dist-client/modeller/figure-preview.js +2 -2
  41. package/dist-client/modeller/figure-preview.js.map +1 -1
  42. package/dist-client/modeller/figure-properties.js +1 -1
  43. package/dist-client/modeller/figure-properties.js.map +1 -1
  44. package/dist-client/modeller/figure-section-editor.js +1 -1
  45. package/dist-client/modeller/figure-section-editor.js.map +1 -1
  46. package/dist-client/modeller/figure-section-panel.js +1 -1
  47. package/dist-client/modeller/figure-section-panel.js.map +1 -1
  48. package/dist-client/modeller/figure-settings.d.ts +1 -1
  49. package/dist-client/modeller/figure-settings.js +1 -1
  50. package/dist-client/modeller/figure-settings.js.map +1 -1
  51. package/dist-client/modeller/figure-verdict.js +1 -1
  52. package/dist-client/modeller/figure-verdict.js.map +1 -1
  53. package/dist-client/pages/figure-list-page.d.ts +1 -0
  54. package/dist-client/pages/figure-list-page.js +11 -1
  55. package/dist-client/pages/figure-list-page.js.map +1 -1
  56. package/dist-client/tsconfig.tsbuildinfo +1 -1
  57. package/dist-client/viewparts/figure-detail-dock.d.ts +10 -1
  58. package/dist-client/viewparts/figure-detail-dock.js +93 -41
  59. package/dist-client/viewparts/figure-detail-dock.js.map +1 -1
  60. package/dist-client/viewparts/figure-thumb.d.ts +1 -0
  61. package/dist-client/viewparts/figure-thumb.js +2 -22
  62. package/dist-client/viewparts/figure-thumb.js.map +1 -1
  63. package/package.json +5 -4
  64. package/test/browser/detail-page.js +10 -2
  65. package/test/browser/product-page.check.mjs +1 -1
  66. package/test/browser/screen-page.js +1 -1
  67. package/test/browser/serve-detail.mjs +7 -2
  68. package/test/figure-controls-keys.test.ts +19 -0
  69. package/test/i18n-prefix-guard.test.ts +4 -1
  70. package/test/layout.test.ts +4 -3
  71. package/test/links.test.ts +5 -4
  72. package/test/properties.test.ts +2 -1
  73. package/test/refusals.test.ts +2 -1
  74. package/test/theme.test.ts +1 -1
  75. package/test/verdict.test.ts +2 -1
  76. package/test/words.ts +20 -0
  77. package/translations/en.json +2 -33
  78. package/translations/es.json +2 -33
  79. package/translations/ja.json +2 -33
  80. package/translations/ko.json +2 -33
  81. package/translations/ms.json +2 -33
  82. package/translations/zh.json +2 -33
  83. package/client/modeller/controls.ts +0 -54
  84. package/client/modeller/figure-view.ts +0 -151
  85. package/client/modeller/preview-model.ts +0 -89
  86. package/client/modeller/side-panel-styles.ts +0 -374
  87. package/client/viewparts/detail-motion.ts +0 -11
  88. package/client/viewparts/figure-asset-viewer.ts +0 -177
  89. package/dist-client/modeller/controls.d.ts +0 -28
  90. package/dist-client/modeller/controls.js +0 -28
  91. package/dist-client/modeller/controls.js.map +0 -1
  92. package/dist-client/modeller/figure-view.d.ts +0 -117
  93. package/dist-client/modeller/figure-view.js +0 -78
  94. package/dist-client/modeller/figure-view.js.map +0 -1
  95. package/dist-client/modeller/preview-model.d.ts +0 -54
  96. package/dist-client/modeller/preview-model.js +0 -79
  97. package/dist-client/modeller/preview-model.js.map +0 -1
  98. package/dist-client/modeller/side-panel-styles.d.ts +0 -27
  99. package/dist-client/modeller/side-panel-styles.js +0 -374
  100. package/dist-client/modeller/side-panel-styles.js.map +0 -1
  101. package/dist-client/viewparts/detail-motion.d.ts +0 -6
  102. package/dist-client/viewparts/detail-motion.js +0 -9
  103. package/dist-client/viewparts/detail-motion.js.map +0 -1
  104. package/dist-client/viewparts/figure-asset-viewer.d.ts +0 -31
  105. package/dist-client/viewparts/figure-asset-viewer.js +0 -198
  106. package/dist-client/viewparts/figure-asset-viewer.js.map +0 -1
  107. package/test/controls.test.ts +0 -60
  108. package/test/detail-motion.test.ts +0 -26
  109. package/test/preview.test.ts +0 -53
  110. package/test/view.test.ts +0 -13
@@ -4,7 +4,7 @@ import { create, Model, niceGridStep } from '@hatiolab/things-scene'
4
4
 
5
5
  import type { GizmoReach } from './gizmo.js'
6
6
  import type { FigureRenderPart } from './renderer.js'
7
- import { DEFAULT_VIEW, sceneLook, WORKBENCH, type ViewSettings } from './figure-view.js'
7
+ import { DEFAULT_VIEW, sceneLook, WORKBENCH, type ViewSettings } from '@things-factory/figure-viewer/dist-client/figure-view.js'
8
8
  import { boardFor, changedKeys, componentOf, gizmoResultOf, type Board, type ScaleAnchor } from './stage-model.js'
9
9
  import { JOINT_FAN } from './joint-fan.js'
10
10
  import { SLOT_MARK } from './slot-layout-host.js'
@@ -26,7 +26,7 @@ import './figure-controls.js'
26
26
  import './figure-clips.js'
27
27
  import './figure-preview.js'
28
28
  import './figure-settings.js'
29
- import { DEFAULT_VIEW, type ViewSettings } from './figure-view.js'
29
+ import { DEFAULT_VIEW, type ViewSettings } from '@things-factory/figure-viewer/dist-client/figure-view.js'
30
30
  import './figure-capability-panel.js'
31
31
  import { gizmoChanges, gizmoReachOf, type GizmoReach, type GizmoResult } from './gizmo.js'
32
32
  import type { KeyAction } from './keys.js'
@@ -35,7 +35,7 @@ import { PaneFold } from './pane-fold.js'
35
35
  import { PanelMemory } from './panel-memory.js'
36
36
  import { RefusalTeller, type Retry } from './refusal-teller.js'
37
37
  import { groupFields, ownerOf, partLabelOf, repeatOf, type FieldLike, type FieldGroup } from './fields.js'
38
- import { SidePanelStyles } from './side-panel-styles.js'
38
+ import { SidePanelStyles } from '@things-factory/figure-viewer/dist-client/side-panel-styles.js'
39
39
  import { EditorStyles } from './editor-styles.js'
40
40
  import { ScrollbarStyles } from '@operato/styles'
41
41
  import { alignChanges, canMirror, mirrorChanges, type AlignTool } from './align.js'
@@ -4,28 +4,10 @@ import { live } from 'lit/directives/live.js'
4
4
 
5
5
  import { i18next, localize } from '@operato/i18n'
6
6
  import { figureBindingsOf } from '@hatiolab/figure-model'
7
- import type { FigureCapability, FigureAsset } from '@hatiolab/figure-model'
8
- import { SidePanelStyles } from './side-panel-styles.js'
9
-
10
- /*
11
- One rung per set of the format's capability names. Typed as the format's names, so a misspelt rung does not pass
12
- (ADR-0051 ④).
13
- */
14
- type Rung = 'none' | 'hold' | 'flow'
15
- const RUNGS: Readonly<Record<Rung, readonly FigureCapability[]>> = Object.freeze({
16
- none: [],
17
- hold: ['Capacity', 'Holdable'],
18
- flow: ['FlowNode']
19
- })
20
-
21
- /** Which rung a set of names is, or null when it is none of them -- not invented. */
22
- export function rungOf(names: readonly string[] | undefined): Rung | null {
23
- const has = new Set(names ?? [])
24
- if (has.size === 0) return 'none'
25
- for (const [rung, wanted] of Object.entries(RUNGS) as [Rung, readonly FigureCapability[]][])
26
- if (wanted.length === has.size && wanted.every(name => has.has(name))) return rung
27
- return null
28
- }
7
+ import type { FigureAsset } from '@hatiolab/figure-model'
8
+ import { RUNGS, rungOf, type Rung } from '@things-factory/figure-viewer/dist-client/capability-rung.js'
9
+ import { SidePanelStyles } from '@things-factory/figure-viewer/dist-client/side-panel-styles.js'
10
+ import { placeTerms } from '@things-factory/figure-viewer/dist-client/detail-facts.js'
29
11
 
30
12
  /**
31
13
  * 자리·능력 — what the figure does on a board: the ladder from drawing only, to
@@ -116,8 +98,8 @@ export class FigureCapabilityPanel extends localize(i18next)(LitElement) {
116
98
  one => html`<button place ?on=${one.part === this.selected} @click=${() => one.part && this.pick(one.part)}>
117
99
  <strong>${one.label}</strong>
118
100
  <small>
119
- ${i18next.t('figure.label.place-' + one.role)} · ${one.part ?? '?'}${one.face ? ' · ' + i18next.t('figure.label.face-' + one.face) : ''} ·
120
- ${one.accepts.join(', ')} · ${i18next.t('figure.label.place-holds', { count: one.capacity })}
101
+ ${one.part ?? '?'}${one.face ? ' · ' + i18next.t('figure.label.face-' + one.face) : ''} ·
102
+ ${placeTerms(one, (key, params) => i18next.t(key, params))}
121
103
  </small>
122
104
  </button>`
123
105
  )
@@ -6,7 +6,7 @@ import { i18next, localize } from '@operato/i18n'
6
6
  import type { FigureAsset, FigureAuthoringAction } from '@hatiolab/figure-model'
7
7
 
8
8
  import { CARRIER_AXES, carrierCommandOf, carrierDraftOf, draftIsDeclared, type CarrierAxis, type CarrierDraft } from './carrier-declaration.js'
9
- import { SidePanelStyles } from './side-panel-styles.js'
9
+ import { SidePanelStyles } from '@things-factory/figure-viewer/dist-client/side-panel-styles.js'
10
10
 
11
11
  const AXIS_LETTER: Record<CarrierAxis, string> = { width: 'W', length: 'L', height: 'H' }
12
12
 
@@ -7,7 +7,7 @@ import { i18next, localize } from '@operato/i18n'
7
7
  import type { FigureAsset } from '@hatiolab/figure-model'
8
8
 
9
9
  import { clipsOf, poseAt, trackEdit, undrivenOf, withKey, withoutKey, type Clip, type ClipTrack } from './clips.js'
10
- import { SidePanelStyles } from './side-panel-styles.js'
10
+ import { SidePanelStyles } from '@things-factory/figure-viewer/dist-client/side-panel-styles.js'
11
11
  import { HelpToggles } from './help.js'
12
12
 
13
13
  /**
@@ -6,9 +6,9 @@ import { customElement, property, state } from 'lit/decorators.js'
6
6
  import { i18next, localize } from '@operato/i18n'
7
7
  import type { FigureAsset } from '@hatiolab/figure-model'
8
8
 
9
- import { controlsOf, poseAsDefault, poseDiffers, type Control } from './controls.js'
9
+ import { controlsOf, poseAsDefault, poseDiffers, type Control } from '@things-factory/figure-viewer/dist-client/controls.js'
10
10
  import { dialShape } from './angle-dial.js'
11
- import { SidePanelStyles } from './side-panel-styles.js'
11
+ import { SidePanelStyles } from '@things-factory/figure-viewer/dist-client/side-panel-styles.js'
12
12
 
13
13
  /**
14
14
  * The figure's controls and its pose tools: each value a board sets to pose the figure,
@@ -4,7 +4,7 @@ import { customElement, property, state } from 'lit/decorators.js'
4
4
  import { i18next, localize } from '@operato/i18n'
5
5
  import type { FigureFaceMappingNeed } from '@hatiolab/figure-model'
6
6
 
7
- import { SidePanelStyles } from './side-panel-styles.js'
7
+ import { SidePanelStyles } from '@things-factory/figure-viewer/dist-client/side-panel-styles.js'
8
8
  import { DETACH, faceMappingRowsOf, remapOf, rowKey } from './face-mapping.js'
9
9
 
10
10
  /**
@@ -53,13 +53,14 @@ import {
53
53
  type Follow,
54
54
  type MotionForm
55
55
  } from './links.js'
56
- import { SidePanelStyles } from './side-panel-styles.js'
56
+ import { SidePanelStyles } from '@things-factory/figure-viewer/dist-client/side-panel-styles.js'
57
57
  import './figure-fasten-drawing.js'
58
58
  import type { FastenChange } from './figure-fasten-drawing.js'
59
59
  import { faceViewOf, insideAlignmentOf, modeOf, myFaceFor, turnsInPlace, type FaceView, type FastenMode } from './fasten-geometry.js'
60
60
  import type { FigureRevision } from './revision.js'
61
61
  import { HelpToggles } from './help.js'
62
62
  import { jointAxisWord } from './joint-axis.js'
63
+ import { placeTerms } from '@things-factory/figure-viewer/dist-client/detail-facts.js'
63
64
 
64
65
  const FACES = Object.keys(FIGURE_FACES) as FigureFace[]
65
66
  /** The target list's entry for the plane the figure stands by. */
@@ -296,7 +297,7 @@ export class FigureLinks extends localize(i18next)(LitElement) {
296
297
  one => html`<div pair>
297
298
  <span>
298
299
  <b bound>${one.label}</b>
299
- <small>${roleName(one.role)} · ${one.face ? i18next.t('figure.label.face-' + one.face) : ''} · ${one.accepts.join(', ')} · ${i18next.t('figure.label.place-holds', { count: one.capacity })}</small>
300
+ <small>${one.face ? i18next.t('figure.label.face-' + one.face) + ' · ' : ''}${placeTerms(one, (key, params) => i18next.t(key, params))}</small>
300
301
  </span>
301
302
  <button act ?disabled=${this.locked} @click=${() => this.command({ kind: 'remove-binding', id: one.id })}>
302
303
  <md-icon>close</md-icon>${i18next.t('figure.button.remove-place')}
@@ -5,7 +5,7 @@ import { i18next, localize } from '@operato/i18n'
5
5
  import { MATERIAL_PRESETS, SEGMENT_PRESETS, figureTrianglesOf } from '@hatiolab/figure-model'
6
6
  import { activePalette } from '@hatiolab/things-scene'
7
7
  import type { FigureAssetGeometry, FigureMaterial, FigurePartEdit } from '@hatiolab/figure-model'
8
- import { SidePanelStyles } from './side-panel-styles.js'
8
+ import { SidePanelStyles } from '@things-factory/figure-viewer/dist-client/side-panel-styles.js'
9
9
  import { isRoundBody } from './starting-shape.js'
10
10
  import { glowOf, materialEditOf, offeredPaletteOf } from './material-edit.js'
11
11
 
@@ -6,10 +6,10 @@ import { compileFigureAsset } from '@hatiolab/figure-model'
6
6
  import type { FigureAsset } from '@hatiolab/figure-model'
7
7
  import { create, registerFigure, unregisterFigure } from '@hatiolab/things-scene'
8
8
 
9
- import { DEFAULT_VIEW, previewBoardDepth, sceneLook, WORKBENCH, type ViewSettings } from './figure-view.js'
9
+ import { DEFAULT_VIEW, previewBoardDepth, sceneLook, WORKBENCH, type ViewSettings } from '@things-factory/figure-viewer/dist-client/figure-view.js'
10
10
  import { dialShape } from './angle-dial.js'
11
11
  import { derivedOf, type FigureRevision } from './revision.js'
12
- import { authoredSizeOf, PREVIEW_FACTOR, PREVIEW_WAYS, previewClampsOf, previewInstances, previewTypeOf, setupPartOf } from './preview-model.js'
12
+ import { authoredSizeOf, PREVIEW_FACTOR, PREVIEW_WAYS, previewClampsOf, previewInstances, previewTypeOf, setupPartOf } from '@things-factory/figure-viewer/dist-client/preview-model.js'
13
13
 
14
14
  /**
15
15
  * The figure placed on a board.
@@ -13,7 +13,7 @@ import { occupancyFromParts } from './occupancy.js'
13
13
  import { tagsOf, figureFigureFacts, type Placement, type FigureFigureFacts } from './figure-facts.js'
14
14
  import { offeredMountingOf } from './mounting-part.js'
15
15
  import type { FigureRevision } from './revision.js'
16
- import { SidePanelStyles } from './side-panel-styles.js'
16
+ import { SidePanelStyles } from '@things-factory/figure-viewer/dist-client/side-panel-styles.js'
17
17
  import { ScrollbarStyles } from '@operato/styles'
18
18
 
19
19
  const PLACEMENTS: Placement[] = ['floor', 'ceiling', 'center', 'wall']
@@ -5,7 +5,7 @@ import { live } from 'lit/directives/live.js'
5
5
  import { i18next, localize } from '@operato/i18n'
6
6
  import { figureCavitySectionOf, figureSectionSidesOf } from '@hatiolab/figure-model'
7
7
  import type { FigureAuthoringAction, FigureEvaluatedShape, FigureFaceMappingNeed, FigureSection, FigureSectionMeasure, FigureSectionPoint } from '@hatiolab/figure-model'
8
- import { SidePanelStyles } from './side-panel-styles.js'
8
+ import { SidePanelStyles } from '@things-factory/figure-viewer/dist-client/side-panel-styles.js'
9
9
  import { apexOver, cavityKeyOf, drawnOf, insertAfter, markedBy, measureNoteOf, movePoint, pointLabel, removePoint, sectionActionOf, sectionOf, workingIssuesOf, workingOf, workingShapeOf, type SectionWorking } from './section-drawing.js'
10
10
  import { cavityDepthOf, cavityRequestOf, cavityResultOf, hollowableOf, sameOf, sidesOf } from './section-controls.js'
11
11
  import './figure-cavity-drawing.js'
@@ -5,7 +5,7 @@ import { live } from 'lit/directives/live.js'
5
5
 
6
6
  import { i18next, localize } from '@operato/i18n'
7
7
  import type { FigureAuthoringAction, FigureEvaluatedShape, FigureFaceMappingNeed } from '@hatiolab/figure-model'
8
- import { SidePanelStyles } from './side-panel-styles.js'
8
+ import { SidePanelStyles } from '@things-factory/figure-viewer/dist-client/side-panel-styles.js'
9
9
  import { drawnOf, sectionPointsWordsOf } from './section-drawing.js'
10
10
  import './figure-cavity-drawing.js'
11
11
  import './figure-face-mapping.js'
@@ -4,8 +4,8 @@ import { customElement, property } from 'lit/decorators.js'
4
4
  import { i18next, localize } from '@operato/i18n'
5
5
  import { ScrollbarStyles } from '@operato/styles'
6
6
 
7
- import { DEFAULT_VIEW, ENVIRONMENTS, SCENE_REFERENCES } from './figure-view.js'
8
- import type { EnvironmentName, ViewSettings } from './figure-view.js'
7
+ import { DEFAULT_VIEW, ENVIRONMENTS, SCENE_REFERENCES } from '@things-factory/figure-viewer/dist-client/figure-view.js'
8
+ import type { EnvironmentName, ViewSettings } from '@things-factory/figure-viewer/dist-client/figure-view.js'
9
9
 
10
10
  /**
11
11
  * 저작 화면의 **보기 설정**.
@@ -12,7 +12,7 @@ import type { FigureInspection } from '../types.js'
12
12
  import { gateFindingStyles, renderGateFinding } from './gate-findings.js'
13
13
  import { MEASURED } from './scene-budget.js'
14
14
  import type { FigureRevision } from './revision.js'
15
- import { SidePanelStyles } from './side-panel-styles.js'
15
+ import { SidePanelStyles } from '@things-factory/figure-viewer/dist-client/side-panel-styles.js'
16
16
  import { CURVE_SIZE, curveLayout, gradeBoundsOf, shortNumber, verdictMeasureOf } from './maturity.js'
17
17
  import { newestAnswer } from './newest-answer.js'
18
18
  import type { FigureBudgetUse, FigureMaturityAxis, FigureReport } from './maturity.js'
@@ -16,7 +16,7 @@ import { capacityFrom } from '../modeller/scene-budget.js'
16
16
  import { deleteFigure, fetchFigureList } from '../graphql/index.js'
17
17
  import type { Figure } from '../types.js'
18
18
  import { modellerPathOf } from '../modeller/modeller-route.js'
19
- import { openFigureDetail } from '../viewparts/figure-detail-dock.js'
19
+ import { FIGURE_THUMBNAIL_CHANGED, openFigureDetail } from '../viewparts/figure-detail-dock.js'
20
20
 
21
21
  /**
22
22
  * 이 값 아래면 점수를 색으로 눈에 띄게 한다.
@@ -560,6 +560,11 @@ export class FigureListPage extends FigureListPageBase {
560
560
  }
561
561
  }
562
562
  auth.on('profile', this.listen)
563
+ /* A picture chosen in the preview: the cards are read again, with the picture's new stamp. */
564
+ window.addEventListener(FIGURE_THUMBNAIL_CHANGED, this.pictureChanged)
565
+ }
566
+ private pictureChanged = () => {
567
+ if (this.active) this.grist?.fetch()
563
568
  }
564
569
 
565
570
  disconnectedCallback() {
@@ -567,6 +572,7 @@ export class FigureListPage extends FigureListPageBase {
567
572
  auth.off?.('profile', this.listen)
568
573
  this.listen = undefined
569
574
  }
575
+ window.removeEventListener(FIGURE_THUMBNAIL_CHANGED, this.pictureChanged)
570
576
  super.disconnectedCallback()
571
577
  }
572
578
 
@@ -1,3 +1,4 @@
1
+ import '@material/web/icon/icon.js'
1
2
  import { css, html, LitElement, nothing } from 'lit'
2
3
  import { customElement, property, state } from 'lit/decorators.js'
3
4
  import { i18next, localize } from '@operato/i18n'
@@ -7,13 +8,17 @@ 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, type FigureAsset } from '@hatiolab/figure-model'
9
10
  import { capacityFrom } from '../modeller/scene-budget.js'
10
- import { fetchFigure, fetchReleasedFigure } from '../graphql/index.js'
11
+ import { SidePanelStyles } from '@things-factory/figure-viewer/dist-client/side-panel-styles.js'
12
+ import { fetchFigure, fetchReleasedFigure, updateFigure } from '../graphql/index.js'
11
13
  import { modellerPathOf } from '../modeller/modeller-route.js'
12
14
  import { leftOpeningPage } from './detail-route.js'
13
15
  import type { Figure } from '../types.js'
14
- import './figure-asset-viewer.js'
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
 
16
19
  const VIEWPART = 'figure-detail'
20
+ /** Said on window when a figure's card picture was replaced from the preview: `{ detail: { id } }`. */
21
+ export const FIGURE_THUMBNAIL_CHANGED = 'figure-thumbnail-changed'
17
22
  export interface FigureDetailRecord {
18
23
  saved: Figure
19
24
  released?: { version: number; source: string }
@@ -22,23 +27,41 @@ export interface FigureDetailRecord {
22
27
  /** Catalogue details stay while the catalogue is shown; choosing a size or a pose has no write path. */
23
28
  @customElement('figure-detail-dock')
24
29
  export class FigureDetailDock extends localize(i18next)(LitElement) {
25
- static styles = [ScrollbarStyles, css`
26
- :host { display: flex; flex-direction: column; width: 440px; max-width: 100vw; height: 100%; min-height: 0;
27
- 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; }
28
- 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); }
29
- h2 { margin: 0; font-size: var(--ui-t-title, 16px); }
30
- 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; }
30
+ /*
31
+ The modeller's side panel look (SidePanelStyles). What the preview is opened for sits above the model -- name, type,
32
+ which model is shown, how many fit -- as plain text; the controls are borderless icons and quiet text, so the model
33
+ and its numbers are what the eye meets.
34
+ */
35
+ static styles = [ScrollbarStyles, SidePanelStyles, css`
36
+ :host { display: flex; flex-direction: column; width: 400px; max-width: 100vw; height: 100%; min-height: 0;
37
+ background: var(--md-sys-color-surface); border-left: 1px solid var(--md-sys-color-outline-variant); }
38
+ header { display: flex; align-items: flex-start; gap: 2px; padding: 12px 8px 4px 16px; }
39
+ [title-block] { flex: 1; min-width: 0; display: grid; gap: 2px; }
40
+ 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; }
41
+ code { font-family: var(--mono-font, monospace); color: var(--md-sys-color-on-surface-variant); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
42
+ button { font: inherit; color: inherit; cursor: pointer; }
43
+ button:focus-visible { outline: 2px solid var(--md-sys-color-primary); outline-offset: 1px; }
44
+ button[icon] { display: inline-grid; place-items: center; width: 28px; height: 28px; padding: 0; border: none; border-radius: 6px;
45
+ background: transparent; color: var(--md-sys-color-on-surface-variant); }
46
+ button[icon]:hover:not(:disabled) { background: var(--md-sys-color-surface-container-high); color: var(--md-sys-color-on-surface); }
47
+ button[icon]:disabled { opacity: 0.38; cursor: default; }
48
+ button[icon] md-icon { --md-icon-size: 18px; }
31
49
  [body] { flex: 1; overflow: auto; min-height: 0; }
32
- [meta], [status] { padding: 12px 16px; }
33
- [meta] { display: grid; gap: 8px; }
34
- [summary] { padding: 12px 16px; border-bottom: 1px solid var(--md-sys-color-outline-variant); }
35
- h3 { margin: 0 0 8px; font-size: var(--ui-t-body, 13px); }
36
- dl { display: grid; grid-template-columns: 1fr auto; gap: 6px 12px; margin: 0; }
37
- dd { margin: 0; font-variant-numeric: tabular-nums; }
50
+ [meta] { display: grid; gap: 8px; padding: 0 16px 12px; }
51
+ /* Which model is shown: words that change colour, not boxes. */
52
+ [source] { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 12px; color: var(--md-sys-color-on-surface-variant); }
53
+ [source] button { padding: 0; border: none; background: none; color: var(--md-sys-color-on-surface-variant); }
54
+ [source] button:hover { color: var(--md-sys-color-on-surface); }
55
+ [source] button[aria-pressed='true'] { color: var(--md-sys-color-primary); font-weight: 600; }
56
+ dl { margin: 0; }
57
+ dt { color: var(--md-sys-color-on-surface-variant); }
58
+ dd { margin: 0; color: var(--md-sys-color-on-surface); }
59
+ dl[stats] { display: flex; flex-wrap: wrap; gap: 4px 16px; }
60
+ dl[stats] > div { display: flex; align-items: baseline; gap: 4px; }
61
+ dl[stats] dd { font-weight: 600; font-variant-numeric: tabular-nums; }
38
62
  p { margin: 0; line-height: 1.5; }
39
- code { color: var(--md-sys-color-on-surface-variant); }
40
- footer { padding: 12px 16px; border-top: 1px solid var(--md-sys-color-outline-variant); }
41
- footer button { width: 100%; background: var(--md-sys-color-primary-container); color: var(--md-sys-color-on-primary-container); }
63
+ [description] { color: var(--md-sys-color-on-surface-variant); }
64
+ [status] { display: grid; gap: 8px; justify-items: start; padding: 12px 16px; }
42
65
  @media (max-width: 1439.98px) {
43
66
  :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%); }
44
67
  }
@@ -48,6 +71,14 @@ export class FigureDetailDock extends localize(i18next)(LitElement) {
48
71
  if (!saved?.source) throw Error(i18next.t('figure.text.detail-no-source'))
49
72
  return { saved, released }
50
73
  }
74
+ /**
75
+ * Keeps the picture taken in the preview as the figure's card picture. A patch of the picture alone does not make a
76
+ * released figure a draft (`updateFigure`). The catalogue is told, and reads its cards again.
77
+ */
78
+ @property({ attribute: false }) keepThumbnail: (id: string, dataUrl: string) => Promise<unknown> = async (id, dataUrl) => {
79
+ await updateFigure(id, { thumbnail: dataUrl })
80
+ window.dispatchEvent(new CustomEvent(FIGURE_THUMBNAIL_CHANGED, { detail: { id } }))
81
+ }
51
82
  @state() private figureId = ''
52
83
  @state() private record?: FigureDetailRecord
53
84
  @state() private asset?: FigureAsset
@@ -133,38 +164,56 @@ export class FigureDetailDock extends localize(i18next)(LitElement) {
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
- return html`<header><h2>${saved?.name || i18next.t('figure.title.detail')}</h2>
139
- <button aria-label=${i18next.t('figure.button.close')} @click=${() => hideDetail()}>×</button></header>
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=${() => {
178
+ // The modeller shows the figure itself; a preview of it beside the editor only covers the work.
179
+ hideDetail()
180
+ navigate(modellerPathOf(saved))
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>
140
184
  <div body>
141
185
  ${this.loading ? html`<p status role="status">${i18next.t('figure.text.detail-loading')}</p>` : nothing}
142
- ${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}
143
- ${saved ? html`<section meta><code>${saved.type}</code><p>${saved.description || ''}</p>
144
- <label>${i18next.t('figure.label.detail-source')}
145
- <select aria-label=${i18next.t('figure.label.detail-source')} .value=${this.sourceKind}
146
- @change=${(e: Event) => this.choose((e.target as HTMLSelectElement).value as 'saved' | 'released')}>
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}
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}
151
203
  </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=${() => {
161
- // The modeller shows the figure itself; a preview of it beside the editor only covers the work.
162
- hideDetail()
163
- navigate(modellerPathOf(saved))
164
- }}>${i18next.t('figure.button.detail-edit')}</button></footer>`
204
+ ${this.asset ? html`<figure-asset-viewer .asset=${this.asset}
205
+ .keepShot=${saved?.id ? (dataUrl: string) => 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
 
212
+ /** The name a board stores: `{namespace}/{type}` (ADR-0114). */
213
+ function fullName(figure: Figure): string {
214
+ return figure.namespace ? `${figure.namespace}/${figure.type}` : figure.type
215
+ }
216
+
168
217
  function visibleBar(root: Document | ShadowRoot): Element | undefined {
169
218
  for (const element of root.querySelectorAll('*')) {
170
219
  if (element.tagName === 'OX-CANVAS-BAR' && element.getBoundingClientRect().height > 0) return element
@@ -4,6 +4,7 @@ import { css, html, LitElement } from 'lit'
4
4
  import { customElement, property } from 'lit/decorators.js'
5
5
 
6
6
  import { i18next, localize } from '@operato/i18n'
7
+ import '@things-factory/figure-viewer/dist-client/figure-thumb-mark.js'
7
8
  import { getPathInfo } from '@operato/utils'
8
9
 
9
10
  import type { Figure } from '../types.js'
@@ -67,24 +68,6 @@ export class FigureThumb extends localize(i18next)(LitElement) {
67
68
  object-fit: contain;
68
69
  }
69
70
 
70
- /* 아직 그림이 없는 것을 빈 사각형으로 두지 않는다 — 왜 없는지 말한다 */
71
- div[empty] {
72
- display: flex;
73
- flex-direction: column;
74
- align-items: center;
75
- justify-content: center;
76
- gap: var(--spacing-tiny, 4px);
77
- width: 100%;
78
- height: 100%;
79
- font: var(--label-font, inherit);
80
- font-size: 0.7rem;
81
- color: var(--md-sys-color-outline);
82
- }
83
- div[empty] md-icon {
84
- --md-icon-size: 26px;
85
- color: var(--md-sys-color-outline-variant);
86
- }
87
-
88
71
  /*
89
72
  초안 표시.
90
73
 
@@ -208,10 +191,7 @@ export class FigureThumb extends localize(i18next)(LitElement) {
208
191
  ${src
209
192
  ? html`<img src=${src} alt=${figure.name || ''} loading="lazy" />`
210
193
  : html`
211
- <div empty>
212
- <md-icon>deployed_code</md-icon>
213
- ${i18next.t('figure.text.thumbnail-not-made-yet')}
214
- </div>
194
+ <figure-thumb-mark .label=${i18next.t('figure.text.thumbnail-not-made-yet')}></figure-thumb-mark>
215
195
  `}
216
196
  ${figure.state === 'released' ? '' : html`<span draft>${i18next.t('figure.label.draft')}</span>`}
217
197
  ${figure.tags?.length
@@ -1,7 +1,7 @@
1
1
  import { LitElement, type PropertyValues } from 'lit';
2
2
  import type { GizmoReach } from './gizmo.js';
3
3
  import type { FigureRenderPart } from './renderer.js';
4
- import { type ViewSettings } from './figure-view.js';
4
+ import { type ViewSettings } from '@things-factory/figure-viewer/dist-client/figure-view.js';
5
5
  import { type ScaleAnchor } from './stage-model.js';
6
6
  export declare class FigureCanvas extends LitElement {
7
7
  static styles: import("lit").CSSResult;
@@ -2,7 +2,7 @@ import { __decorate, __metadata } from "tslib";
2
2
  import { LitElement, html, css, unsafeCSS } from 'lit';
3
3
  import { customElement, property, query } from 'lit/decorators.js';
4
4
  import { create, Model, niceGridStep } from '@hatiolab/things-scene';
5
- import { DEFAULT_VIEW, sceneLook, WORKBENCH } from './figure-view.js';
5
+ import { DEFAULT_VIEW, sceneLook, WORKBENCH } from '@things-factory/figure-viewer/dist-client/figure-view.js';
6
6
  import { boardFor, changedKeys, componentOf, gizmoResultOf } from './stage-model.js';
7
7
  import { JOINT_FAN } from './joint-fan.js';
8
8
  import { SLOT_MARK } from './slot-layout-host.js';