@things-factory/figure-ui 10.1.223 → 10.1.224

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 (59) hide show
  1. package/client/modeller/canvas.ts +2 -2
  2. package/client/modeller/consumer-capabilities.ts +66 -0
  3. package/client/modeller/data-interface.ts +230 -80
  4. package/client/modeller/editor.ts +14 -7
  5. package/client/modeller/figure-capability-panel.ts +145 -1
  6. package/client/modeller/figure-data-panel.ts +495 -188
  7. package/client/modeller/figure-links.ts +130 -12
  8. package/client/modeller/figure-preview.ts +19 -2
  9. package/client/modeller/figure-slot-layout-panel.ts +7 -3
  10. package/client/modeller/links.ts +58 -0
  11. package/client/modeller/refusals.ts +12 -4
  12. package/client/modeller/slot-layout-host.ts +76 -5
  13. package/dist-client/modeller/canvas.js +2 -2
  14. package/dist-client/modeller/canvas.js.map +1 -1
  15. package/dist-client/modeller/consumer-capabilities.d.ts +29 -0
  16. package/dist-client/modeller/consumer-capabilities.js +60 -0
  17. package/dist-client/modeller/consumer-capabilities.js.map +1 -0
  18. package/dist-client/modeller/data-interface.d.ts +83 -17
  19. package/dist-client/modeller/data-interface.js +194 -82
  20. package/dist-client/modeller/data-interface.js.map +1 -1
  21. package/dist-client/modeller/editor.d.ts +5 -2
  22. package/dist-client/modeller/editor.js +16 -5
  23. package/dist-client/modeller/editor.js.map +1 -1
  24. package/dist-client/modeller/figure-capability-panel.d.ts +12 -0
  25. package/dist-client/modeller/figure-capability-panel.js +123 -1
  26. package/dist-client/modeller/figure-capability-panel.js.map +1 -1
  27. package/dist-client/modeller/figure-data-panel.d.ts +57 -13
  28. package/dist-client/modeller/figure-data-panel.js +461 -180
  29. package/dist-client/modeller/figure-data-panel.js.map +1 -1
  30. package/dist-client/modeller/figure-links.d.ts +11 -0
  31. package/dist-client/modeller/figure-links.js +137 -16
  32. package/dist-client/modeller/figure-links.js.map +1 -1
  33. package/dist-client/modeller/figure-preview.d.ts +4 -0
  34. package/dist-client/modeller/figure-preview.js +19 -1
  35. package/dist-client/modeller/figure-preview.js.map +1 -1
  36. package/dist-client/modeller/figure-slot-layout-panel.js +6 -4
  37. package/dist-client/modeller/figure-slot-layout-panel.js.map +1 -1
  38. package/dist-client/modeller/links.d.ts +22 -0
  39. package/dist-client/modeller/links.js +48 -0
  40. package/dist-client/modeller/links.js.map +1 -1
  41. package/dist-client/modeller/refusals.js +14 -6
  42. package/dist-client/modeller/refusals.js.map +1 -1
  43. package/dist-client/modeller/slot-layout-host.d.ts +28 -3
  44. package/dist-client/modeller/slot-layout-host.js +68 -6
  45. package/dist-client/modeller/slot-layout-host.js.map +1 -1
  46. package/dist-client/tsconfig.tsbuildinfo +1 -1
  47. package/package.json +7 -7
  48. package/test/browser/data-table-page.js +9 -0
  49. package/test/browser/links.check.mjs +25 -3
  50. package/test/consumer-capabilities.test.ts +116 -0
  51. package/test/data-motion.test.ts +94 -27
  52. package/test/links.test.ts +15 -0
  53. package/test/slot-layout-host.test.ts +63 -5
  54. package/translations/en.json +81 -23
  55. package/translations/es.json +81 -23
  56. package/translations/ja.json +81 -23
  57. package/translations/ko.json +81 -23
  58. package/translations/ms.json +81 -23
  59. package/translations/zh.json +81 -23
@@ -7,7 +7,8 @@ import './figure-size-editor.js';
7
7
  import { customElement, property, state } from 'lit/decorators.js';
8
8
  import { i18next, localize } from '@operato/i18n';
9
9
  import { figureFaceOverhangOf, figureMotionsOf, motionIdOf } from '@hatiolab/figure-model';
10
- import { acceptsOf, AXES, canFasten, canFollow, positionIsShare, heldBy, shownWhenOf, visibilityControlsOf, fasteningOf, hasInstanceSize, holdsThings, leadersFor, motionOf, motionFormOf, motionFormRefusalOf, endPlaceKeyOf, motionJoint, controlIdFor, withoutAxis, placeFacesOf, placeIdFor, placesOn, rowAxesOf, rowBlockOf, fixedJointOf, rowOf, positionsWorkedOut } from './links.js';
10
+ import { acceptsOf, AXES, canFasten, canFollow, positionIsShare, heldBy, shownWhenOf, visibilityControlsOf, fasteningOf, hasInstanceSize, holdsThings, leadersFor, motionOf, motionFormOf, motionFormRefusalOf, endPlaceKeyOf, motionJoint, controlIdFor, withoutAxis, placeFacesOf, placeIdFor, placesOn, rowAxesOf, rowBlockOf, fixedJointOf, rowOf, positionsWorkedOut, followOf, followableAxes, measureNow, freshStateSize, stateSizeIdFor, stateSizeCommandOf, stateSizeFixedOf } from './links.js';
11
+ import { measureFactor } from './size-response.js';
11
12
  import { SidePanelStyles } from '@things-factory/figure-viewer/dist-client/side-panel-styles.js';
12
13
  import { insideAlignmentOf } from './fasten-geometry.js';
13
14
  import { fastenFormOf, fastenPlanOf, fastenSentenceOf } from './fasten-form.js';
@@ -62,7 +63,11 @@ let FigureLinks = class FigureLinks extends localize(i18next)(LitElement) {
62
63
  /** Why 정렬·간격 고치기 moved nothing, said under it; empty once it moved the part or the part changed. */
63
64
  this.fixNote = '';
64
65
  this.motion = freshMotion();
66
+ /* The form for a measure a control sets (「상태에 따라 크기」). */
67
+ this.stateSize = freshStateSize();
65
68
  this.place = freshPlace();
69
+ /** The id of the place the form changes, kept so the place keeps its id (a release refuses a place id that goes); null adds one. */
70
+ this.placeEditing = null;
66
71
  this.row = freshRow();
67
72
  /** The motion form is open over the part's own motion, to change it where it is. */
68
73
  this.editingMotion = false;
@@ -151,6 +156,7 @@ let FigureLinks = class FigureLinks extends localize(i18next)(LitElement) {
151
156
  this.loadedFor = this.partId;
152
157
  this.motion = freshMotion();
153
158
  this.place = freshPlace();
159
+ this.placeEditing = null;
154
160
  this.row = freshRow();
155
161
  this.rowOpen = false;
156
162
  this.rowCandidate = undefined;
@@ -186,7 +192,7 @@ let FigureLinks = class FigureLinks extends localize(i18next)(LitElement) {
186
192
  return nothing;
187
193
  if (this.section === 'motion')
188
194
  return html `<section><h3>${i18next.t('figure.label.motion')}${this.help.button('figure.text.motion-explained')}</h3>${this.help.hint('figure.text.motion-explained')}
189
- ${this.renderMotion(asset)}${this.renderShown(asset)}</section>`;
195
+ ${this.renderMotion(asset)}${this.renderShown(asset)}${this.renderStateSize(asset)}</section>`;
190
196
  if (this.section === 'places') {
191
197
  /*
192
198
  One idea, places, two ways to make them (ADR-0118): one at a time on this part, or many by a rule -- the figure's
@@ -253,6 +259,11 @@ let FigureLinks = class FigureLinks extends localize(i18next)(LitElement) {
253
259
  this.candidate = this.fastenCandidate;
254
260
  return shown;
255
261
  }
262
+ /** A place on this part into the form, to change it under its own id. */
263
+ editPlace(one) {
264
+ this.placeEditing = one.id;
265
+ this.place = { role: one.role, face: one.face ?? 'top', label: one.label, accepts: (one.accepts ?? []).join(', '), capacity: one.capacity };
266
+ }
256
267
  /** Where things are held on this part: a seat things rest on, or a grip things are held in. */
257
268
  renderPlaces(asset) {
258
269
  const faces = placeFacesOf(asset, this.partId);
@@ -267,6 +278,9 @@ let FigureLinks = class FigureLinks extends localize(i18next)(LitElement) {
267
278
  <b bound>${one.label}</b>
268
279
  <small>${one.face ? i18next.t('figure.label.face-' + one.face) + ' · ' : ''}${placeTerms(one, (key, params) => i18next.t(key, params))}</small>
269
280
  </span>
281
+ <button quiet edit-place ?disabled=${this.locked} @click=${() => this.editPlace(one)}>
282
+ <md-icon>edit</md-icon>${i18next.t('figure.button.edit-place')}
283
+ </button>
270
284
  <button quiet ?disabled=${this.locked} @click=${() => this.command({ kind: 'remove-binding', id: one.id })}>
271
285
  <md-icon>close</md-icon>${i18next.t('figure.button.remove-place')}
272
286
  </button>
@@ -290,27 +304,35 @@ let FigureLinks = class FigureLinks extends localize(i18next)(LitElement) {
290
304
  <span>${i18next.t('figure.label.place-name')}</span>
291
305
  <input .value=${p.label} placeholder=${roleName(p.role)} @change=${(e) => (this.place = { ...p, label: e.target.value })} />
292
306
  <span>${i18next.t('figure.label.place-accepts')}</span>
293
- <input .value=${p.accepts} placeholder="BOX, TOTE" @input=${(e) => (this.place = { ...p, accepts: e.target.value })} />
307
+ <input .value=${p.accepts} placeholder=${i18next.t('figure.text.place-accepts-any')} @input=${(e) => (this.place = { ...p, accepts: e.target.value })} />
294
308
  <span>${i18next.t('figure.label.place-capacity')}</span>
295
309
  <input type="number" min="1" step="1" .value=${String(p.capacity)} @change=${(e) => (this.place = { ...p, capacity: e.target.valueAsNumber })} />
296
310
  </div>
297
- ${accepts.length ? nothing : html `<small>${i18next.t('figure.text.place-accepts-needed')}</small>`}
298
311
  <button
299
312
  act
300
- ?disabled=${this.locked || !accepts.length || !capacityOk}
301
- @click=${() => this.command({
302
- kind: 'add-binding',
303
- id: placeIdFor(asset, this.partId, p.role),
304
- label: p.label.trim() || roleName(p.role),
305
- role: p.role,
306
- part: this.partId,
307
- face,
308
- accepts,
309
- capacity: p.capacity
310
- })}
313
+ ?disabled=${this.locked || !capacityOk}
314
+ @click=${() => {
315
+ this.command({
316
+ kind: 'add-binding',
317
+ id: this.placeEditing ?? placeIdFor(asset, this.partId, p.role),
318
+ label: p.label.trim() || roleName(p.role),
319
+ role: p.role,
320
+ part: this.partId,
321
+ face,
322
+ /* None named: it takes any item (ADR-0051 §4.1 decision 5). */
323
+ ...(accepts.length ? { accepts } : {}),
324
+ capacity: p.capacity,
325
+ ...(this.placeEditing ? { replace: true } : {})
326
+ });
327
+ this.placeEditing = null;
328
+ this.place = freshPlace();
329
+ }}
311
330
  >
312
- <md-icon>back_hand</md-icon>${i18next.t('figure.button.add-place')}
331
+ <md-icon>back_hand</md-icon>${i18next.t(this.placeEditing ? 'figure.button.save-place' : 'figure.button.add-place')}
313
332
  </button>
333
+ ${this.placeEditing
334
+ ? html `<button quiet @click=${() => ((this.placeEditing = null), (this.place = freshPlace()))}>${i18next.t('figure.button.cancel-place-edit')}</button>`
335
+ : nothing}
314
336
  `;
315
337
  }
316
338
  /** The figure holds things: `Capacity` and `Holdable`, the ladder's middle rung. One part edit, one undo step. */
@@ -519,6 +541,97 @@ let FigureLinks = class FigureLinks extends localize(i18next)(LitElement) {
519
541
  * When the part is drawn: always, or while a control is at least a value -- a lamp lit while the gate is open. One
520
542
  * part edit (`set-visibility`), one step. Offered once the figure has a control; a motion is what makes one.
521
543
  */
544
+ /**
545
+ * A measure a control sets (modeller snag C1, 2026-10-26): a wound roll shrinking to its core. Each measure that does is
546
+ * one line with the way back to a fixed number; the form makes another. Measures read as the size row shows them --
547
+ * a cylinder's X as its diameter.
548
+ */
549
+ renderStateSize(asset) {
550
+ const revision = this.revision;
551
+ if (!revision)
552
+ return nothing;
553
+ const part = this.partId;
554
+ const axes = followableAxes(asset, part);
555
+ if (!axes.length)
556
+ return nothing;
557
+ const word = (axis) => `${axis.toUpperCase()}${measureFactor(asset, part, axis) === 2 ? ' · ' + i18next.t('figure.label.size-diameter') : ''}`;
558
+ const unitWord = (unit) => i18next.t(`figure.label.state-unit-${unit}`);
559
+ const sized = axes.flatMap(axis => {
560
+ const f = followOf(asset, part, axis);
561
+ return f.of === 'state' ? [{ axis, f }] : [];
562
+ });
563
+ const free = axes.filter(axis => !sized.some(one => one.axis === axis));
564
+ const form = this.stateSize;
565
+ const axis = form.axis && free.includes(form.axis) ? form.axis : free[0] ?? '';
566
+ const factor = axis ? measureFactor(asset, part, axis) : 1;
567
+ const atMax = Number.isFinite(form.atMax) ? form.atMax : axis ? (measureNow(revision, part, axis) ?? NaN) : NaN;
568
+ const id = form.id || stateSizeIdFor(asset, part);
569
+ const controls = visibilityControlsOf(asset);
570
+ const command = stateSizeCommandOf(asset, part, { ...form, axis, atMax }, factor, id);
571
+ /* A control already there is taken with its unit and range; another unit or range would be refused. */
572
+ const pickControl = (value) => {
573
+ const there = controls.find(c => c.id === value);
574
+ this.stateSize = there && ['percent', 'ratio', 'mm'].includes(there.unit)
575
+ ? { ...form, id: value, unit: there.unit, min: there.min, max: there.max }
576
+ : { ...form, id: value };
577
+ };
578
+ const number = (key) => (e) => (this.stateSize = { ...form, axis, [key]: e.target.valueAsNumber });
579
+ return html `<h4 state-size>${i18next.t('figure.label.state-size')}</h4>
580
+ ${sized.map(({ axis: one, f }) => {
581
+ const k = measureFactor(asset, part, one);
582
+ const input = controls.find(c => c.id === f.input);
583
+ return html `<div pair state-size-row=${one}>
584
+ <span>${word(one)}
585
+ <small>${i18next.t('figure.text.state-size-row', {
586
+ control: input?.label ?? f.input,
587
+ min: String(input?.min ?? ''),
588
+ max: String(input?.max ?? ''),
589
+ unit: input ? unitWord(input.unit) : '',
590
+ from: String(Math.round(f.at.min * k * 1000) / 1000),
591
+ to: String(Math.round(f.at.max * k * 1000) / 1000)
592
+ })}</small>
593
+ </span>
594
+ <button act ?disabled=${this.locked} @click=${() => {
595
+ const back = stateSizeFixedOf(asset, part, one);
596
+ if (back)
597
+ this.command(back);
598
+ }}><md-icon>close</md-icon>${i18next.t('figure.button.state-size-fix')}</button>
599
+ </div>`;
600
+ })}
601
+ ${free.length
602
+ ? html `<div form state-size-form>
603
+ <span>${i18next.t('figure.label.state-size-measure')}</span>
604
+ <select ?disabled=${this.locked} @change=${(e) => (this.stateSize = { ...form, axis: e.target.value, atMax: NaN })}>
605
+ ${free.map(one => html `<option value=${one} ?selected=${one === axis}>${word(one)}</option>`)}
606
+ </select>
607
+ <span>${i18next.t('figure.label.control-id')}</span>
608
+ <input list="state-size-controls" .value=${id} ?disabled=${this.locked} @change=${(e) => pickControl(e.target.value.trim())} />
609
+ <datalist id="state-size-controls">${controls.map(c => html `<option value=${c.id}>${c.label}</option>`)}</datalist>
610
+ <span>${i18next.t('figure.label.state-size-unit')}</span>
611
+ <select ?disabled=${this.locked} @change=${(e) => {
612
+ const unit = e.target.value;
613
+ this.stateSize = { ...form, axis, unit, ...(unit === 'percent' ? { min: 0, max: 100 } : unit === 'ratio' ? { min: 0, max: 1 } : {}) };
614
+ }}>
615
+ ${['percent', 'ratio', 'mm'].map(u => html `<option value=${u} ?selected=${u === form.unit}>${unitWord(u)}</option>`)}
616
+ </select>
617
+ <span>${i18next.t('figure.label.control-range')} (${unitWord(form.unit)})</span>
618
+ <div pair>
619
+ <input type="number" step="any" .value=${String(form.min)} ?disabled=${this.locked} @change=${number('min')} />
620
+ <input type="number" step="any" .value=${String(form.max)} ?disabled=${this.locked} @change=${number('max')} />
621
+ </div>
622
+ <span>${i18next.t('figure.label.state-size-at-min')} (mm)</span>
623
+ <input type="number" step="any" min="0" state-size-at-min .value=${Number.isFinite(form.atMin) ? String(form.atMin) : ''} ?disabled=${this.locked} @change=${number('atMin')} />
624
+ <span>${i18next.t('figure.label.state-size-at-max')} (mm)</span>
625
+ <input type="number" step="any" min="0" state-size-at-max .value=${Number.isFinite(atMax) ? String(atMax) : ''} ?disabled=${this.locked} @change=${number('atMax')} />
626
+ </div>
627
+ <button act add-state-size ?disabled=${this.locked || !command} @click=${() => {
628
+ if (!command)
629
+ return;
630
+ this.command(command);
631
+ this.stateSize = freshStateSize();
632
+ }}><md-icon>open_in_full</md-icon>${i18next.t('figure.button.add-state-size')}</button>`
633
+ : nothing}`;
634
+ }
522
635
  renderShown(asset) {
523
636
  const controls = visibilityControlsOf(asset);
524
637
  if (!controls.length)
@@ -783,10 +896,18 @@ __decorate([
783
896
  state(),
784
897
  __metadata("design:type", Object)
785
898
  ], FigureLinks.prototype, "motion", void 0);
899
+ __decorate([
900
+ state(),
901
+ __metadata("design:type", Object)
902
+ ], FigureLinks.prototype, "stateSize", void 0);
786
903
  __decorate([
787
904
  state(),
788
905
  __metadata("design:type", Object)
789
906
  ], FigureLinks.prototype, "place", void 0);
907
+ __decorate([
908
+ state(),
909
+ __metadata("design:type", Object)
910
+ ], FigureLinks.prototype, "placeEditing", void 0);
790
911
  __decorate([
791
912
  state(),
792
913
  __metadata("design:type", String)