@things-factory/figure-ui 10.1.146 → 10.1.149

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 (209) hide show
  1. package/client/bootstrap.ts +10 -2
  2. package/client/graphql/index.ts +20 -0
  3. package/client/modeller/canvas.ts +6 -2
  4. package/client/modeller/carrier-declaration.ts +63 -0
  5. package/client/modeller/editor.ts +62 -27
  6. package/client/modeller/figure-carrier.ts +117 -0
  7. package/client/modeller/figure-cavity-drawing.ts +3 -9
  8. package/client/modeller/figure-first-save.ts +4 -7
  9. package/client/modeller/figure-history-panel.ts +2 -3
  10. package/client/modeller/figure-links.ts +46 -60
  11. package/client/modeller/figure-material-panel.ts +4 -14
  12. package/client/modeller/figure-mesh-choice.ts +68 -0
  13. package/client/modeller/figure-parts.ts +23 -1
  14. package/client/modeller/figure-preview.ts +2 -2
  15. package/client/modeller/figure-properties.ts +5 -3
  16. package/client/modeller/figure-section-editor.ts +12 -33
  17. package/client/modeller/figure-section-panel.ts +6 -7
  18. package/client/modeller/figure-slot-layout-panel.ts +208 -0
  19. package/client/modeller/figure-verdict.ts +21 -31
  20. package/client/modeller/first-save.ts +23 -0
  21. package/client/modeller/links.ts +52 -1
  22. package/client/modeller/material-edit.ts +38 -0
  23. package/client/modeller/maturity.ts +28 -0
  24. package/client/modeller/mesh-import.ts +77 -0
  25. package/client/modeller/modeller-route.ts +9 -0
  26. package/client/modeller/mounting-part.ts +15 -0
  27. package/client/modeller/newest-answer.ts +21 -0
  28. package/client/modeller/preview-model.ts +5 -0
  29. package/client/modeller/refusals.ts +168 -82
  30. package/client/modeller/section-controls.ts +25 -1
  31. package/client/modeller/section-drawing.ts +43 -1
  32. package/client/modeller/slot-layout-host.ts +140 -0
  33. package/client/pages/figure-modeller-page.ts +6 -5
  34. package/client/viewparts/detail-route.ts +7 -0
  35. package/client/viewparts/figure-detail-dock.ts +1 -3
  36. package/dist-client/bootstrap.d.ts +1 -1
  37. package/dist-client/bootstrap.js +9 -2
  38. package/dist-client/bootstrap.js.map +1 -1
  39. package/dist-client/graphql/index.d.ts +5 -0
  40. package/dist-client/graphql/index.js +19 -0
  41. package/dist-client/graphql/index.js.map +1 -1
  42. package/dist-client/modeller/canvas.d.ts +2 -0
  43. package/dist-client/modeller/canvas.js +10 -2
  44. package/dist-client/modeller/canvas.js.map +1 -1
  45. package/dist-client/modeller/carrier-declaration.d.ts +31 -0
  46. package/dist-client/modeller/carrier-declaration.js +53 -0
  47. package/dist-client/modeller/carrier-declaration.js.map +1 -0
  48. package/dist-client/modeller/editor.d.ts +13 -0
  49. package/dist-client/modeller/editor.js +62 -27
  50. package/dist-client/modeller/editor.js.map +1 -1
  51. package/dist-client/modeller/figure-carrier.d.ts +26 -0
  52. package/dist-client/modeller/figure-carrier.js +123 -0
  53. package/dist-client/modeller/figure-carrier.js.map +1 -0
  54. package/dist-client/modeller/figure-cavity-drawing.js +3 -11
  55. package/dist-client/modeller/figure-cavity-drawing.js.map +1 -1
  56. package/dist-client/modeller/figure-first-save.d.ts +4 -0
  57. package/dist-client/modeller/figure-first-save.js +5 -8
  58. package/dist-client/modeller/figure-first-save.js.map +1 -1
  59. package/dist-client/modeller/figure-history-panel.js +2 -4
  60. package/dist-client/modeller/figure-history-panel.js.map +1 -1
  61. package/dist-client/modeller/figure-links.d.ts +4 -1
  62. package/dist-client/modeller/figure-links.js +47 -57
  63. package/dist-client/modeller/figure-links.js.map +1 -1
  64. package/dist-client/modeller/figure-material-panel.d.ts +0 -1
  65. package/dist-client/modeller/figure-material-panel.js +4 -14
  66. package/dist-client/modeller/figure-material-panel.js.map +1 -1
  67. package/dist-client/modeller/figure-mesh-choice.d.ts +16 -0
  68. package/dist-client/modeller/figure-mesh-choice.js +75 -0
  69. package/dist-client/modeller/figure-mesh-choice.js.map +1 -0
  70. package/dist-client/modeller/figure-parts.d.ts +1 -1
  71. package/dist-client/modeller/figure-parts.js +22 -0
  72. package/dist-client/modeller/figure-parts.js.map +1 -1
  73. package/dist-client/modeller/figure-preview.js +2 -2
  74. package/dist-client/modeller/figure-preview.js.map +1 -1
  75. package/dist-client/modeller/figure-properties.d.ts +2 -1
  76. package/dist-client/modeller/figure-properties.js +5 -3
  77. package/dist-client/modeller/figure-properties.js.map +1 -1
  78. package/dist-client/modeller/figure-section-editor.d.ts +2 -2
  79. package/dist-client/modeller/figure-section-editor.js +12 -33
  80. package/dist-client/modeller/figure-section-editor.js.map +1 -1
  81. package/dist-client/modeller/figure-section-panel.js +6 -7
  82. package/dist-client/modeller/figure-section-panel.js.map +1 -1
  83. package/dist-client/modeller/figure-slot-layout-panel.d.ts +36 -0
  84. package/dist-client/modeller/figure-slot-layout-panel.js +198 -0
  85. package/dist-client/modeller/figure-slot-layout-panel.js.map +1 -0
  86. package/dist-client/modeller/figure-verdict.d.ts +1 -1
  87. package/dist-client/modeller/figure-verdict.js +19 -37
  88. package/dist-client/modeller/figure-verdict.js.map +1 -1
  89. package/dist-client/modeller/first-save.d.ts +10 -0
  90. package/dist-client/modeller/first-save.js +16 -0
  91. package/dist-client/modeller/first-save.js.map +1 -1
  92. package/dist-client/modeller/links.d.ts +26 -1
  93. package/dist-client/modeller/links.js +43 -0
  94. package/dist-client/modeller/links.js.map +1 -1
  95. package/dist-client/modeller/material-edit.d.ts +11 -0
  96. package/dist-client/modeller/material-edit.js +28 -0
  97. package/dist-client/modeller/material-edit.js.map +1 -0
  98. package/dist-client/modeller/maturity.d.ts +15 -0
  99. package/dist-client/modeller/maturity.js +23 -0
  100. package/dist-client/modeller/maturity.js.map +1 -1
  101. package/dist-client/modeller/mesh-import.d.ts +47 -0
  102. package/dist-client/modeller/mesh-import.js +47 -0
  103. package/dist-client/modeller/mesh-import.js.map +1 -0
  104. package/dist-client/modeller/modeller-route.d.ts +5 -0
  105. package/dist-client/modeller/modeller-route.js +9 -0
  106. package/dist-client/modeller/modeller-route.js.map +1 -1
  107. package/dist-client/modeller/mounting-part.d.ts +14 -0
  108. package/dist-client/modeller/mounting-part.js +10 -0
  109. package/dist-client/modeller/mounting-part.js.map +1 -0
  110. package/dist-client/modeller/newest-answer.d.ts +12 -0
  111. package/dist-client/modeller/newest-answer.js +21 -0
  112. package/dist-client/modeller/newest-answer.js.map +1 -0
  113. package/dist-client/modeller/preview-model.d.ts +2 -0
  114. package/dist-client/modeller/preview-model.js +4 -0
  115. package/dist-client/modeller/preview-model.js.map +1 -1
  116. package/dist-client/modeller/refusals.d.ts +26 -5
  117. package/dist-client/modeller/refusals.js +184 -86
  118. package/dist-client/modeller/refusals.js.map +1 -1
  119. package/dist-client/modeller/section-controls.d.ts +17 -1
  120. package/dist-client/modeller/section-controls.js +18 -0
  121. package/dist-client/modeller/section-controls.js.map +1 -1
  122. package/dist-client/modeller/section-drawing.d.ts +24 -1
  123. package/dist-client/modeller/section-drawing.js +42 -0
  124. package/dist-client/modeller/section-drawing.js.map +1 -1
  125. package/dist-client/modeller/slot-layout-host.d.ts +81 -0
  126. package/dist-client/modeller/slot-layout-host.js +105 -0
  127. package/dist-client/modeller/slot-layout-host.js.map +1 -0
  128. package/dist-client/pages/figure-modeller-page.js +5 -4
  129. package/dist-client/pages/figure-modeller-page.js.map +1 -1
  130. package/dist-client/tsconfig.tsbuildinfo +1 -1
  131. package/dist-client/viewparts/detail-route.d.ts +5 -0
  132. package/dist-client/viewparts/detail-route.js +8 -0
  133. package/dist-client/viewparts/detail-route.js.map +1 -0
  134. package/dist-client/viewparts/figure-detail-dock.d.ts +0 -1
  135. package/dist-client/viewparts/figure-detail-dock.js +1 -3
  136. package/dist-client/viewparts/figure-detail-dock.js.map +1 -1
  137. package/package.json +9 -8
  138. package/test/align.test.ts +0 -9
  139. package/test/browser/assistant-stand-in.js +32 -0
  140. package/test/browser/build.mjs +6 -1
  141. package/test/browser/editing-page.js +224 -0
  142. package/test/browser/editing.check.mjs +550 -0
  143. package/test/browser/links-page.js +113 -0
  144. package/test/browser/links.check.mjs +568 -0
  145. package/test/browser/modeller.check.mjs +21 -0
  146. package/test/browser/parts-page.js +120 -0
  147. package/test/browser/parts.check.mjs +328 -0
  148. package/test/browser/places-mesh-page.js +36 -0
  149. package/test/browser/places-mesh.check.mjs +239 -0
  150. package/test/browser/product-page.check.mjs +682 -0
  151. package/test/browser/product-page.js +276 -0
  152. package/test/browser/screen-page.js +112 -0
  153. package/test/browser/screen-product-page.js +62 -0
  154. package/test/browser/screen.check.mjs +1003 -0
  155. package/test/browser/section-page.js +256 -0
  156. package/test/browser/section.check.mjs +719 -0
  157. package/test/browser/serve.mjs +20 -12
  158. package/test/carrier-declaration.test.ts +69 -0
  159. package/test/cavity-drawing.test.ts +2 -18
  160. package/test/clip-ai-proposal.test.ts +1 -5
  161. package/test/clip-consent.test.ts +0 -5
  162. package/test/clips.test.ts +0 -7
  163. package/test/command-preview.test.ts +0 -68
  164. package/test/controls.test.ts +0 -9
  165. package/test/detail-dock.test.ts +6 -25
  166. package/test/face-mapping.test.ts +1 -20
  167. package/test/fasten-drawing.test.ts +0 -23
  168. package/test/fields.test.ts +6 -33
  169. package/test/figure-assistant-surface.test.ts +32 -38
  170. package/test/first-minute.test.ts +3 -55
  171. package/test/first-save-suggest.test.ts +11 -8
  172. package/test/first-save.test.ts +4 -26
  173. package/test/gizmo.test.ts +0 -25
  174. package/test/joint-fan.test.ts +0 -10
  175. package/test/keys.test.ts +0 -7
  176. package/test/layout.test.ts +4 -73
  177. package/test/links.test.ts +48 -67
  178. package/test/live.test.ts +0 -34
  179. package/test/material.test.ts +17 -26
  180. package/test/maturity.test.ts +13 -16
  181. package/test/mesh-import.test.ts +76 -0
  182. package/test/modeller-route.test.ts +3 -19
  183. package/test/package-download.test.ts +6 -7
  184. package/test/parts-wiring.test.ts +9 -51
  185. package/test/preview.test.ts +5 -46
  186. package/test/properties.test.ts +11 -26
  187. package/test/proposal-record.test.ts +2 -19
  188. package/test/recipes.test.ts +0 -15
  189. package/test/refusals.test.ts +257 -151
  190. package/test/release-entry.test.ts +1 -9
  191. package/test/release-panel.test.ts +1 -22
  192. package/test/resize-with-placement.test.ts +0 -32
  193. package/test/revert-choice.test.ts +1 -22
  194. package/test/revert-keeping.test.ts +1 -12
  195. package/test/save-landed.test.ts +16 -14
  196. package/test/scene-canvas.test.ts +2 -13
  197. package/test/section-editor.test.ts +30 -83
  198. package/test/section-panel.test.ts +22 -17
  199. package/test/slot-layout-host.test.ts +117 -0
  200. package/test/theme.test.ts +5 -9
  201. package/test/verdict.test.ts +44 -41
  202. package/test/view.test.ts +5 -12
  203. package/translations/en.json +61 -1
  204. package/translations/es.json +61 -1
  205. package/translations/ja.json +61 -1
  206. package/translations/ko.json +61 -1
  207. package/translations/ms.json +61 -1
  208. package/translations/zh.json +61 -1
  209. package/test/thumbnail.test.ts +0 -29
@@ -0,0 +1,256 @@
1
+ /*
2
+ * The page the section check drives: the modeller page's editor (`modeller-page.js`), with the figures the section
3
+ * editor, the section panel, the inside's drawing, the fastening drawing and the face choice are tried on, and what the
4
+ * check reads of them. Only reading here: every change the check makes goes through the mouse and the keyboard.
5
+ */
6
+ import './modeller-page.js'
7
+ import { html, render } from 'lit'
8
+ import { setOpenPopupImplementation } from '@operato/popup'
9
+ import { addFigureBox, addFigurePrimitive, applyFigureAuthoring, createFigureAsset, serializeFigureAsset } from '@hatiolab/figure-model'
10
+
11
+ /*
12
+ * Popups drawn as the layout draws them -- an overlay of their own in the body, the result given when it leaves -- for
13
+ * the section editor's close question: the page has no layout to append it to.
14
+ */
15
+ let popups = 0
16
+ setOpenPopupImplementation((template, options = {}) => {
17
+ const name = `$popup-${popups++}`
18
+ const fragment = document.createDocumentFragment()
19
+ render(html`<ox-floating-overlay id=${name} name=${name} .backdrop=${!!options.backdrop} .title=${options.title || ''} .size=${options.size || 'small'} .historical=${false}>${template}</ox-floating-overlay>`, fragment)
20
+ document.body.appendChild(fragment)
21
+ const handle = {
22
+ name,
23
+ close: () => {
24
+ const popup = document.getElementById(name)
25
+ if (!popup) return false
26
+ popup.close()
27
+ return true
28
+ },
29
+ closed: false
30
+ }
31
+ document.addEventListener('overlay-closed', function listener(e) {
32
+ if (e.detail !== name) return
33
+ document.removeEventListener('overlay-closed', listener)
34
+ handle.closed = true
35
+ handle.onclosed && handle.onclosed()
36
+ })
37
+ return handle
38
+ })
39
+
40
+ const PALETTE = { 'palette.dark': '#2b2b2b', 'palette.neutral': '#9a9a9a', 'palette.primary': '#477b9e', 'palette.accent': '#f79c40', 'palette.light': '#dddddd' }
41
+
42
+ function figure(name) {
43
+ const a = createFigureAsset(name)
44
+ a.palette = { ...a.palette, ...PALETTE }
45
+ return a
46
+ }
47
+
48
+ const TRIANGLE = [{ x: -300, z: -200 }, { x: 300, z: -200 }, { x: 0, z: 250 }]
49
+ const polygon = (a, id, points) => addFigurePrimitive(a, { id, kind: 'polygon', dimensions: { height: 200 }, points, position: { x: 0, y: 100, z: 0 } })
50
+ /** A box set beside the part being edited, for picking another part. */
51
+ const beside = a => addFigureBox(a, { id: 'block', size: { x: 200, y: 200, z: 200 }, position: { x: 900, y: 100, z: 0 } })
52
+
53
+ const FIGURES = {
54
+ /* A drawn section of four points, and a box beside it. */
55
+ quad: () => beside(polygon(figure('Quad'), 'wedge', [{ x: -300, z: -200 }, { x: 300, z: -200 }, { x: 300, z: 250 }, { x: -300, z: 250 }])),
56
+ wedge: () => polygon(figure('Wedge'), 'wedge', TRIANGLE),
57
+ /* The triangle with a plate on its top: its end cannot be drawn to one point while the plate stands there (A5-1). */
58
+ 'capped-wedge': () => {
59
+ const a = addFigureBox(polygon(figure('Capped'), 'wedge', TRIANGLE), { id: 'cap', size: { x: 100, y: 20, z: 100 }, position: { x: 0, y: 0, z: 0 } })
60
+ return applyFigureAuthoring(a, { kind: 'attach', part: 'cap', face: 'bottom', to: { part: 'wedge', face: 'top' } })
61
+ },
62
+ pyramid: () => applyFigureAuthoring(polygon(figure('Pyramid'), 'wedge', TRIANGLE), { kind: 'set-section', part: 'wedge', apex: { x: 0, z: 0 } }),
63
+ /* A box standing on the floor: a length change keeps its bottom. */
64
+ 'floor-box': () => applyFigureAuthoring(addFigureBox(figure('Floor'), { id: 'box', size: { x: 400, y: 200, z: 300 }, position: { x: 0, y: 100, z: 0 } }), { kind: 'set-placement', placement: 'floor' }),
65
+ /* A box whose sizes follow the figure's: none of them is its own to set. */
66
+ follows: () => applyFigureAuthoring(addFigureBox(figure('Follows'), { id: 'box', size: { x: 400, y: 400, z: 400 }, position: { x: 0, y: 200, z: 0 } }), { kind: 'follow-size', part: 'box' }),
67
+ /* A tray: 40 mm walls, a 30 mm floor, open on top; and a box beside it. */
68
+ tray: () =>
69
+ beside(applyFigureAuthoring(addFigureBox(figure('Tray'), { id: 'tray', size: { x: 400, y: 200, z: 300 }, position: { x: 0, y: 100, z: 0 } }), {
70
+ kind: 'set-cavity',
71
+ part: 'tray',
72
+ cavity: { wall: 40, start: { closed: 30 }, end: 'open' }
73
+ })),
74
+ /* A shallow tray, 20 mm deep inside 30: the handles of the two walls across it would stand on each other. */
75
+ 'shallow-tray': () =>
76
+ applyFigureAuthoring(addFigureBox(figure('Shallow'), { id: 'tray', size: { x: 400, y: 200, z: 30 }, position: { x: 0, y: 100, z: 0 } }), {
77
+ kind: 'set-cavity',
78
+ part: 'tray',
79
+ cavity: { wall: 5, start: { closed: 10 }, end: 'open' }
80
+ }),
81
+ /* A tote on the floor with a divider standing on its inside floor, and a box beside it (ADR-0100 A8). */
82
+ tote: () => {
83
+ let a = addFigureBox(figure('Tote'), { id: 'bin', size: { x: 600, y: 300, z: 400 }, position: { x: 0, y: 150, z: 0 } })
84
+ a = applyFigureAuthoring(a, { kind: 'attach', part: 'bin', face: 'bottom', to: { plane: 'mounting-plane' } })
85
+ a = applyFigureAuthoring(a, { kind: 'set-cavity', part: 'bin', cavity: { wall: 10, start: { closed: 15 }, end: 'open' } })
86
+ a = addFigureBox(a, { id: 'divider', size: { x: 10, y: 200, z: 380 }, position: { x: 0, y: 0, z: 0 } })
87
+ return beside(applyFigureAuthoring(a, { kind: 'attach', part: 'divider', face: 'bottom', to: { part: 'bin', face: 'inside-bottom' } }))
88
+ }
89
+ }
90
+
91
+ /** Every element matching `selector`, through every shadow root. */
92
+ function deep(selector, root = document) {
93
+ const found = [...root.querySelectorAll(selector)]
94
+ for (const element of root.querySelectorAll('*')) if (element.shadowRoot) found.push(...deep(selector, element.shadowRoot))
95
+ return found
96
+ }
97
+
98
+ const words = element => element?.textContent?.replace(/\s+/g, ' ').trim() ?? ''
99
+
100
+ /**
101
+ * The first element matching `selector` (through shadow roots), inside the first `within` when given, whose text is
102
+ * `text` (or holds it, with `has`), or whose aria-label is `label`; `index` picks among several.
103
+ */
104
+ window.findDeep = (selector, { within, text, has, label, index = 0 } = {}) => {
105
+ const roots = within ? deep(within).map(host => host.shadowRoot ?? host) : [document]
106
+ const all = roots.flatMap(root => deep(selector, root))
107
+ const fit = all.filter(
108
+ e => (text === undefined || words(e) === text) && (has === undefined || words(e).includes(has)) && (label === undefined || e.getAttribute('aria-label') === label)
109
+ )
110
+ return fit[index] ?? null
111
+ }
112
+
113
+ const editor = () => document.querySelector('figure-editor')
114
+ const canvas = () => deep('figure-canvas')[0]
115
+ const shapeWords = shape => {
116
+ if (!shape) return null
117
+ const drawn = shape.extrusion ?? shape.loft
118
+ return {
119
+ section: drawn ? JSON.parse(JSON.stringify(drawn.section)) : null,
120
+ apex: shape.loft?.apex ?? null,
121
+ length: drawn ? Math.round(drawn.end - drawn.start) : null,
122
+ cavity: shape.cavity ? JSON.parse(JSON.stringify({ wall: shape.cavity.wall, start: shape.cavity.start, end: shape.cavity.end })) : null
123
+ }
124
+ }
125
+ const box = element => {
126
+ const r = element.getBoundingClientRect()
127
+ return { left: r.left, top: r.top, right: r.right, bottom: r.bottom, width: r.width, height: r.height }
128
+ }
129
+
130
+ Object.assign(window.probe, {
131
+ /** A part's section, length and inside as the canvas draws it now -- a value in hand included. */
132
+ drawnShape(id) {
133
+ return shapeWords(canvas()?.parts?.find(p => p.id === id)?.shape)
134
+ },
135
+ /** A part's section, length and inside as the figure holds it. */
136
+ heldShape(id) {
137
+ return shapeWords(editor().session.read().geometry.find(g => g.id === id)?.shape)
138
+ },
139
+ /** The section editor as a person sees it; null when it is not open. */
140
+ sectionEditor() {
141
+ const host = deep('figure-section-editor')[0]
142
+ if (!host) return null
143
+ const root = host.shadowRoot
144
+ const caption = root.querySelector('div[caption]')
145
+ const buttons = [...root.querySelectorAll('footer button')]
146
+ const shown = e => !!e && getComputedStyle(e).display !== 'none'
147
+ const length = root.querySelector('div[sizes] label[mm]')
148
+ const apex = root.querySelector('label[apex]')
149
+ return {
150
+ caption: words(caption),
151
+ lastValid: !!caption?.hasAttribute('last-valid'),
152
+ stale: !!root.querySelector('footer span[grow][stale]'),
153
+ apply: { disabled: buttons.at(-1)?.disabled ?? null, text: words(buttons.at(-1)) },
154
+ points: root.querySelectorAll('ol[points] li').length,
155
+ issues: [...root.querySelectorAll('ul[issues] li')].map(words),
156
+ note: words(root.querySelector('small[role=status]')),
157
+ length: length ? { label: words(length.querySelector('span')), disabled: length.querySelector('input').disabled, note: words(length.querySelector('small')) } : null,
158
+ apex: apex ? { checked: apex.querySelector('input').checked, disabled: apex.querySelector('input').disabled, words: words(apex.querySelector('small')) } : null,
159
+ narrow: host.hasAttribute('narrow'),
160
+ show: host.getAttribute('show'),
161
+ tabs: shown(root.querySelector('nav[tabs]')),
162
+ body: shown(root.querySelector('div[body]')),
163
+ host: box(host),
164
+ room: box(host.parentElement),
165
+ bodyBox: box(root.querySelector('div[body]')),
166
+ footer: buttons.map(box),
167
+ faceMapping: !!root.querySelector('figure-face-mapping'),
168
+ cavityDrawing: !!root.querySelector('figure-cavity-drawing')
169
+ }
170
+ },
171
+ /** The close question, while it is asked: its words and its buttons. */
172
+ closeChoice() {
173
+ const host = deep('figure-section-close-choice')[0]
174
+ if (!host?.isConnected) return null
175
+ return { words: [...host.shadowRoot.querySelectorAll('p')].map(words), buttons: deep('md-text-button, md-filled-button', host.shadowRoot).map(words) }
176
+ },
177
+ /** The section panel as a person sees it: its words, its own number fields, and what it holds. */
178
+ sectionPanel() {
179
+ const host = deep('figure-section-panel')[0]
180
+ if (!host) return null
181
+ const root = host.shadowRoot
182
+ return {
183
+ edit: !!root.querySelector('button[act]'),
184
+ text: words(root),
185
+ inputs: [...root.querySelectorAll('input[type=number]')].map(i => ({ id: i.id, value: i.value })),
186
+ cavityToggle: !!root.querySelector('label[toggle] input'),
187
+ cavityDrawing: !!root.querySelector('figure-cavity-drawing'),
188
+ faceMapping: !!root.querySelector('figure-face-mapping')
189
+ }
190
+ },
191
+ /** The inside's drawing in `within`: its refusal, an open number field, its walls' handles and numbers. */
192
+ cavityDrawing(within = 'figure-section-panel') {
193
+ const host = window.findDeep('figure-cavity-drawing', { within })
194
+ if (!host) return null
195
+ const root = host.shadowRoot
196
+ const top = root.querySelector('svg')
197
+ return {
198
+ refusal: words(root.querySelector('small[refusal]')),
199
+ typing: root.querySelector('input') ? { label: root.querySelector('input').getAttribute('aria-label'), focused: root.activeElement === root.querySelector('input') } : null,
200
+ section: box(top.querySelector('rect, ellipse')),
201
+ walls: Object.fromEntries([...top.querySelectorAll('g[wall]')].map(g => [g.getAttribute('wall'), { handle: box(g.querySelector('rect')), number: box(g.querySelector('text')) }])),
202
+ ends: Object.fromEntries([...root.querySelectorAll('g[end]')].map(g => [g.getAttribute('end'), { text: words(g.querySelector('text')), typed: g.querySelector('text').hasAttribute('typed') }]))
203
+ }
204
+ },
205
+ /** The fastening drawing as a person sees it: the ways offered and the one on, the faces of the target. */
206
+ fastenDrawing() {
207
+ const host = deep('figure-fasten-drawing')[0]
208
+ if (!host) return null
209
+ const root = host.shadowRoot
210
+ return {
211
+ modes: [...root.querySelectorAll('button[mode]')].map(b => b.getAttribute('mode')),
212
+ on: root.querySelector('button[mode][on]')?.getAttribute('mode') ?? null
213
+ }
214
+ },
215
+ /** The labels before the fastening form's lists, and what each holds. */
216
+ fastenForm() {
217
+ return deep('select')
218
+ .filter(s => s.previousElementSibling)
219
+ .map(s => ({ label: words(s.previousElementSibling), value: s.value, options: [...s.options].map(o => ({ value: o.value, text: words(o) })) }))
220
+ },
221
+ /** The page point of one of a part's buttons in the parts list (`remove-part`, ...). */
222
+ partOp(id, op) {
223
+ const label = deep('span[label]').find(s => s.getAttribute('title') === id)
224
+ let row = label
225
+ while (row && !row.querySelector?.(`button[op="${op}"]`)) row = row.parentElement
226
+ const button = row?.querySelector(`button[op="${op}"]`)
227
+ if (!button) return null
228
+ button.scrollIntoView({ block: 'center' })
229
+ const r = button.getBoundingClientRect()
230
+ return { x: r.left + r.width / 2, y: r.top + r.height / 2 }
231
+ },
232
+ /** Where the keys go: the focused element and the focused ones inside it, through shadow roots, by tag. */
233
+ activePath() {
234
+ const path = []
235
+ for (let e = document.activeElement; e; e = e.shadowRoot?.activeElement) path.push(e.tagName)
236
+ return path
237
+ },
238
+ /** The fastening a part has, as the figure holds it. */
239
+ fastening(part) {
240
+ return (JSON.parse(editor().session.source()).joints ?? []).find(j => j.body1.part === part) ?? null
241
+ },
242
+ /** The face choice, wherever it stands: its rows and whether apply is open. */
243
+ faceMapping() {
244
+ const host = deep('figure-face-mapping')[0]
245
+ if (!host) return null
246
+ const root = host.shadowRoot
247
+ return { rows: root.querySelectorAll('select').length, apply: { disabled: root.querySelector('button[act]').disabled } }
248
+ }
249
+ })
250
+
251
+ window.sectionHarness = {
252
+ /** Mounts one of the figures above, or one of the modeller page's own (`arm`, `door`, `rollers`). */
253
+ mount(name) {
254
+ return FIGURES[name] ? window.harness.mount('box', serializeFigureAsset(FIGURES[name]())) : window.harness.mount(name)
255
+ }
256
+ }