@things-factory/figure-ui 10.1.272 → 10.1.273

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 (56) hide show
  1. package/client/modeller/data-interface.ts +30 -18
  2. package/client/modeller/editor-styles.ts +17 -6
  3. package/client/modeller/editor.ts +2 -0
  4. package/client/modeller/fasten-form.ts +12 -2
  5. package/client/modeller/fasten-geometry.ts +5 -3
  6. package/client/modeller/figure-data-panel.ts +384 -200
  7. package/client/modeller/figure-fasten-drawing.ts +21 -13
  8. package/client/modeller/figure-links.ts +24 -9
  9. package/client/modeller/figure-parts.ts +7 -11
  10. package/client/modeller/figure-preview.ts +258 -76
  11. package/dist-client/modeller/clips.d.ts +1 -4
  12. package/dist-client/modeller/data-interface.d.ts +11 -8
  13. package/dist-client/modeller/data-interface.js +27 -17
  14. package/dist-client/modeller/data-interface.js.map +1 -1
  15. package/dist-client/modeller/editor-styles.js +17 -6
  16. package/dist-client/modeller/editor-styles.js.map +1 -1
  17. package/dist-client/modeller/editor.js +2 -0
  18. package/dist-client/modeller/editor.js.map +1 -1
  19. package/dist-client/modeller/fasten-form.d.ts +5 -0
  20. package/dist-client/modeller/fasten-form.js +8 -2
  21. package/dist-client/modeller/fasten-form.js.map +1 -1
  22. package/dist-client/modeller/fasten-geometry.d.ts +3 -2
  23. package/dist-client/modeller/fasten-geometry.js +5 -3
  24. package/dist-client/modeller/fasten-geometry.js.map +1 -1
  25. package/dist-client/modeller/figure-data-panel.d.ts +10 -19
  26. package/dist-client/modeller/figure-data-panel.js +382 -190
  27. package/dist-client/modeller/figure-data-panel.js.map +1 -1
  28. package/dist-client/modeller/figure-fasten-drawing.js +21 -13
  29. package/dist-client/modeller/figure-fasten-drawing.js.map +1 -1
  30. package/dist-client/modeller/figure-links.js +31 -16
  31. package/dist-client/modeller/figure-links.js.map +1 -1
  32. package/dist-client/modeller/figure-parts.js +7 -11
  33. package/dist-client/modeller/figure-parts.js.map +1 -1
  34. package/dist-client/modeller/figure-preview.d.ts +13 -0
  35. package/dist-client/modeller/figure-preview.js +257 -77
  36. package/dist-client/modeller/figure-preview.js.map +1 -1
  37. package/dist-client/tsconfig.tsbuildinfo +1 -1
  38. package/package.json +8 -7
  39. package/test/browser/editing-page.js +1 -1
  40. package/test/browser/links.check.mjs +30 -0
  41. package/test/browser/modeller.check.mjs +2 -2
  42. package/test/browser/parts-page.js +4 -0
  43. package/test/browser/parts.check.mjs +16 -10
  44. package/test/browser/screen-page.js +12 -1
  45. package/test/browser/screen.check.mjs +142 -11
  46. package/test/browser/serve-response.mjs +5 -2
  47. package/test/browser/side-panel-sweep.check.mjs +171 -10
  48. package/test/data-motion.test.ts +39 -8
  49. package/test/fasten-form.test.ts +25 -0
  50. package/test/fasten-geometry.test.ts +13 -0
  51. package/translations/en.json +23 -11
  52. package/translations/es.json +22 -10
  53. package/translations/ja.json +22 -10
  54. package/translations/ko.json +24 -12
  55. package/translations/ms.json +22 -10
  56. package/translations/zh.json +22 -10
@@ -94,13 +94,14 @@ import { methodOf, type DataMethod } from '@things-factory/figure-viewer/dist-cl
94
94
  export { methodOf, type DataMethod }
95
95
 
96
96
  /**
97
- * The methods a field allows (ADR-0166 2): a number as it is, scaled or by a range table; a text or true/false by a code
98
- * table, or as it is where a behavior takes it so; a list as it is (its items). A new field is made by a table.
97
+ * The methods a field allows (ADR-0166 2, amendment 1): a number as it is, scaled, by a code table (status codes sent as
98
+ * numbers, 4002) or by a range table; a text or true/false by a code table, or as it is where a behavior takes it so; a
99
+ * list as it is (its items). A new field is made by a table.
99
100
  */
100
101
  export function methodsOf(node: FigureDataNode | null, choices: BehaviorChoice[]): DataMethod[] {
101
102
  if (!node) return ['code', 'ranges']
102
103
  const fitting = choices.some(c => fitsField(node, c))
103
- if (node.kind === 'number') return [...(fitting ? (['value', 'scale'] as const) : []), 'ranges']
104
+ if (node.kind === 'number') return [...(fitting ? (['value', 'scale'] as const) : []), 'code', 'ranges']
104
105
  if (node.kind === 'text' || node.kind === 'boolean') return [...(fitting ? (['value'] as const) : []), 'code']
105
106
  return fitting ? ['value'] : []
106
107
  }
@@ -170,6 +171,12 @@ export function bindingDraftOf(asset: FigureAsset, accessor: string, node: Figur
170
171
  /* ---------------------------------------------------------------- tables (ADR-0166 3) */
171
172
 
172
173
  /** The first code a new code table names, and the next ones: `CODE`, `CODE2`, … */
174
+ /** A new code for a number field's code table: one past the largest taken, or 0 (ADR-0166 amendment 1). */
175
+ export function freeNumberCodeOf(taken: string[]): string {
176
+ const numbers = taken.map(Number).filter(n => Number.isInteger(n) && n >= 0)
177
+ return String(numbers.length ? Math.max(...numbers) + 1 : 0)
178
+ }
179
+
173
180
  export function freeCodeOf(taken: string[]): string {
174
181
  if (!taken.includes('CODE')) return 'CODE'
175
182
  let n = 2
@@ -186,28 +193,26 @@ export const legendRoleOf = (id: string): string =>
186
193
 
187
194
  /**
188
195
  * A first table on a field (ADR-0166 10): a row per value of a closed field, an open field's rows typed later, one
189
- * interval over the whole of a number. A new field is made by the table (P2): a code table's a text whose values are the
190
- * table's keys, starting with one code; a range table's a number. Returns the binding and, for a new field, the schema
191
- * with it.
196
+ * interval over the whole of a number. The field is one the schema has: the data tab adds a binding in the field's row.
192
197
  */
193
198
  export function tableDraftOf(
194
199
  asset: FigureAsset,
195
- field: { path: string; node: FigureDataNode } | { name: string },
200
+ field: { path: string; node: FigureDataNode },
196
201
  method: 'code' | 'ranges',
197
202
  targets: FigureDataTargets
198
- ): { binding: FigureDataTableBinding; schema?: FigureDataField[] } {
203
+ ): { binding: FigureDataTableBinding } {
199
204
  const taken = (asset.dataInterface?.bindings ?? []).map(b => b.id)
200
- const made = 'name' in field
201
- const name = made ? field.name : (field.path.replace(/\[\]/g, '').split('.').pop() ?? field.path)
205
+ const name = field.path.replace(/\[\]/g, '').split('.').pop() ?? field.path
202
206
  const id = freeIdOf(taken, name.toLowerCase())
203
- const node: FigureDataNode = made ? (method === 'code' ? { kind: 'text', values: ['CODE'] } : { kind: 'number' }) : field.node
204
- const accessor = made ? field.name : accessorOfRow(field.path)
207
+ const node = field.node
208
+ const accessor = accessorOfRow(field.path)
205
209
  const table: Record<string, FigureDataTableEntry> =
206
- method === 'code' ? Object.fromEntries(domainOf(node).map(v => [String(v), {}])) : { [`${node.kind === 'number' ? (node.min ?? '') : ''}~`]: {} }
207
- const binding: FigureDataTableBinding = { id, accessor, rule: 'table', lookup: method, targets: tidyTargets(targets), table }
208
- if (!made) return { binding }
209
- const schema = [...(asset.dataInterface?.schema ?? []), { name: field.name, ...node } as FigureDataField]
210
- return { binding, schema }
210
+ method === 'code'
211
+ ? node.kind === 'number'
212
+ ? { [freeNumberCodeOf([])]: {} }
213
+ : Object.fromEntries(domainOf(node).map(v => [String(v), {}]))
214
+ : { [`${node.kind === 'number' ? (node.min ?? '') : ''}~`]: {} }
215
+ return { binding: { id, accessor, rule: 'table', lookup: method, targets: tidyTargets(targets), table } }
211
216
  }
212
217
 
213
218
  /** Targets with empty lists left out, as the kernel keeps them. */
@@ -217,8 +222,9 @@ const tidyTargets = (t: FigureDataTargets): FigureDataTargets => ({
217
222
  ...(t.group ? { group: t.group } : {})
218
223
  })
219
224
 
220
- /** A look with only the columns its targets take: a target taken away takes its columns with it. */
225
+ /** A look with only the columns its targets take: a target taken away takes its columns with it. A look that keeps stays so. */
221
226
  function lookFor(look: FigureDataTableEntry, t: FigureDataTargets): FigureDataTableEntry {
227
+ if (look.keep) return { keep: true }
222
228
  const out: FigureDataTableEntry = {}
223
229
  if (t.parts?.length) {
224
230
  if (look.color !== undefined) out.color = look.color
@@ -245,6 +251,12 @@ export function tableWithTargets(b: FigureDataTableBinding, targets: FigureDataT
245
251
  /** The table with one look set: the whole look given, a column left out cleared. */
246
252
  export const tableWithLook = (b: FigureDataTableBinding, key: string, look: FigureDataTableEntry): FigureDataTableBinding => ({ ...b, table: { ...b.table, [key]: lookFor(look, b.targets) } })
247
253
 
254
+ /**
255
+ * The table whose other values keep what it last showed, or go to the default look (an empty one): the 「그대로 둠」 of
256
+ * the 「그 밖의 값」 row (ADR-0166 amendment 1).
257
+ */
258
+ export const tableWithKeep = (b: FigureDataTableBinding, keep: boolean): FigureDataTableBinding => ({ ...b, table: { ...b.table, default: keep ? { keep: true } : {} } })
259
+
248
260
  /** The table's rows renamed, added or taken away, in order; `default` kept last. */
249
261
  export function tableWithKeys(b: FigureDataTableBinding, rows: Array<[string, FigureDataTableEntry]>): FigureDataTableBinding {
250
262
  const d = b.table.default
@@ -411,14 +411,25 @@ export const EditorStyles = css`
411
411
  z-index: 2;
412
412
  display: grid;
413
413
  gap: 4px;
414
- padding: 10px 12px;
415
- background: var(--md-sys-color-surface-container-lowest);
416
- border-bottom: 1px solid var(--md-sys-color-outline-variant);
414
+ padding: 6px 12px;
415
+ /* Solid where a theme gives no colour: a sticky line with none let the rows scrolled under it show through. */
416
+ background: var(--md-sys-color-surface-container-lowest, #fff);
417
+ border-bottom: 1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, 0.12));
417
418
  }
418
- /* The panel's three sizes (side-panel-styles): the part's kind at the title's, its two lines at the hint's. */
419
+ /*
420
+ The part's name and its kind on one line: two lines on every tab, over a name field saying it again on the
421
+ properties tab, took the panel's top (user 2026-11-12).
422
+ */
419
423
  div[subject-summary] {
420
- display: grid;
421
- gap: 2px;
424
+ display: flex;
425
+ align-items: baseline;
426
+ gap: 6px;
427
+ min-width: 0;
428
+ }
429
+ div[subject-summary] > * {
430
+ overflow: hidden;
431
+ white-space: nowrap;
432
+ text-overflow: ellipsis;
422
433
  }
423
434
  div[subject-summary] b {
424
435
  font-size: var(--ui-t-body, 13px);
@@ -2215,6 +2215,8 @@ export class FigureEditor extends LitElement {
2215
2215
  .asset=${asset}
2216
2216
  .selected=${this.selected}
2217
2217
  ?locked=${unavailable}
2218
+ ?previewing=${this.middle === 'preview'}
2219
+ @show-preview=${() => (this.middle = 'preview')}
2218
2220
  @data-sample=${(e: CustomEvent) => (this.dataSample = e.detail.data)}
2219
2221
  @legend-sample=${(e: CustomEvent) => (this.legendSample = e.detail.legends)}
2220
2222
  @part-edit=${(e: CustomEvent) => this.run(() => this.session!.edit(e.detail.edit))}
@@ -51,6 +51,11 @@ export interface FastenForm {
51
51
  alignInput: Partial<Record<Axis, string>>
52
52
  /** On a point of the target's curved surface instead of a face (ADR-0140 3.9): where, as the record says it. */
53
53
  surface?: FigureSurfacePlace
54
+ /**
55
+ * Opened on the part's own fastening: aimed again, it goes there with that fastening's gap and place across the face
56
+ * (tutorial 2026-11-12). 「현재 상태로 붙이기」 -- where it stands kept -- is a loose part's.
57
+ */
58
+ fastened?: boolean
54
59
  }
55
60
 
56
61
  export const freshFasten = (): FastenForm => ({
@@ -73,8 +78,12 @@ export function fastenFormOf(revision: FigureRevision, part: string): FastenForm
73
78
  /* A part still where it was added was put nowhere by the author: fastened, it goes on the face, no gap to keep. */
74
79
  return addedUnplaced(revision, part) ? { ...freshFasten(), gapFrom: 'pressed' } : freshFasten()
75
80
  }
76
- /* A gap the record works out is the author's; a number is where the part stands, so aiming again keeps it there. */
77
- return { ...freshFasten(), ...own, gapFrom: own.gapKept !== undefined ? 'typed' : 'kept' } as FastenForm
81
+ /*
82
+ Its fastening is the author's, gap and place: aimed at another face or part through 「바꾸기」 it goes there with them
83
+ (tutorial 2026-11-12: a pillar copied from the front one and turned to the body's back face stayed in front, its gap
84
+ worked out as the whole body's depth).
85
+ */
86
+ return { ...freshFasten(), ...own, gapFrom: 'typed', fastened: true } as FastenForm
78
87
  }
79
88
 
80
89
  /** A part fastened to nothing that still stands where the palette put it: the author has put it nowhere to keep. */
@@ -195,6 +204,7 @@ export function aimFasten(
195
204
  * arm aimed at the body's side stood off the face, out of reach of the drawing).
196
205
  */
197
206
  const centred = { align: {}, alignMm: {}, alignInput: {} }
207
+ if (f.fastened) return f
198
208
  return { ...f, ...(aim && !addedUnplaced(revision, part) ? placeKeptOf(revision, part, aim) : centred) }
199
209
  }
200
210
 
@@ -188,8 +188,10 @@ export function fastenLabelsOf(
188
188
  /**
189
189
  * How much of an axis the face drawing shows, mm: room around the face for the part to go past its edges by about half
190
190
  * itself, and for a part bigger than the face lined up with one of its edges (a mast flush with a low body's bottom) to
191
- * show whole; a plane with no size, three parts wide.
191
+ * show whole; a plane with no size, three parts wide. A part standing `at` mm off the centre is reached too, its far
192
+ * edge inside (roll cradle tutorial 2026-11-12: a box left far up had its size label cut off the drawing).
192
193
  */
193
- export function drawingSpanOf(face: number | undefined, part: number): number {
194
- return face !== undefined ? Math.max(face + part, 2 * part - face) : part * 3
194
+ export function drawingSpanOf(face: number | undefined, part: number, at = 0): number {
195
+ const room = face !== undefined ? Math.max(face + part, 2 * part - face) : part * 3
196
+ return Math.max(room, 2 * Math.abs(at) + part)
195
197
  }