@things-factory/figure-ui 10.1.193 → 10.1.195

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 (45) hide show
  1. package/client/modeller/editor-styles.ts +10 -3
  2. package/client/modeller/editor.ts +61 -47
  3. package/client/modeller/figure-fasten-dialog.ts +11 -5
  4. package/client/modeller/refusal-teller.ts +8 -0
  5. package/client/pages/figure-list-page.ts +27 -40
  6. package/client/pages/figure-public-list-page.ts +21 -0
  7. package/client/route.ts +4 -0
  8. package/dist-client/modeller/editor-styles.js +10 -3
  9. package/dist-client/modeller/editor-styles.js.map +1 -1
  10. package/dist-client/modeller/editor.d.ts +18 -9
  11. package/dist-client/modeller/editor.js +62 -46
  12. package/dist-client/modeller/editor.js.map +1 -1
  13. package/dist-client/modeller/figure-fasten-dialog.d.ts +3 -3
  14. package/dist-client/modeller/figure-fasten-dialog.js +11 -5
  15. package/dist-client/modeller/figure-fasten-dialog.js.map +1 -1
  16. package/dist-client/modeller/refusal-teller.d.ts +8 -0
  17. package/dist-client/modeller/refusal-teller.js +7 -0
  18. package/dist-client/modeller/refusal-teller.js.map +1 -1
  19. package/dist-client/pages/figure-list-page.d.ts +6 -3
  20. package/dist-client/pages/figure-list-page.js +25 -42
  21. package/dist-client/pages/figure-list-page.js.map +1 -1
  22. package/dist-client/pages/figure-public-list-page.d.ts +16 -0
  23. package/dist-client/pages/figure-public-list-page.js +21 -0
  24. package/dist-client/pages/figure-public-list-page.js.map +1 -0
  25. package/dist-client/route.d.ts +1 -1
  26. package/dist-client/route.js +3 -0
  27. package/dist-client/route.js.map +1 -1
  28. package/dist-client/tsconfig.tsbuildinfo +1 -1
  29. package/package.json +3 -3
  30. package/test/align.test.ts +13 -5
  31. package/test/browser/editing.check.mjs +9 -13
  32. package/test/browser/links.check.mjs +36 -22
  33. package/test/browser/list-page.js +35 -0
  34. package/test/browser/list.check.mjs +68 -0
  35. package/test/browser/modeller-page.js +48 -4
  36. package/test/browser/product-page.js +6 -0
  37. package/test/browser/props.check.mjs +2 -2
  38. package/test/figure-released.test.ts +1 -1
  39. package/things-factory.config.js +1 -0
  40. package/translations/en.json +4 -6
  41. package/translations/es.json +4 -6
  42. package/translations/ja.json +4 -6
  43. package/translations/ko.json +4 -6
  44. package/translations/ms.json +4 -6
  45. package/translations/zh.json +4 -6
@@ -80,8 +80,10 @@ let FigureEditor = class FigureEditor extends LitElement {
80
80
  this.error = '';
81
81
  this.tab = 'properties';
82
82
  /**
83
- * The fastening window open for the selected part (user 2026-10-17), in the standard popup (2026-10-18): chosen there,
84
- * applied once. It closes with nothing written when the part, the tab or the middle changes under it.
83
+ * The fastening window open for the selected part (user 2026-10-17): chosen there, applied once. It stands over the
84
+ * side panel, not over the canvas, so what is chosen is seen in 3D as it is chosen (user 2026-10-19: the standard
85
+ * popup, always modal, covered the preview). It closes with nothing written when the part, the tab, the middle or a
86
+ * bar panel changes under it.
85
87
  */
86
88
  this.fastening = false;
87
89
  /** Why the window's 「적용」 was refused, said in it. */
@@ -219,7 +221,7 @@ let FigureEditor = class FigureEditor extends LitElement {
219
221
  aria-pressed=${String(this.gizmoMode === tool.mode)}
220
222
  ?disabled=${!tool.can}
221
223
  aria-label=${i18next.t('figure.button.gizmo-' + tool.mode)}
222
- title=${`${i18next.t('figure.button.gizmo-' + tool.mode)} (${tool.key})${tool.can || !this.selected ? '' : ' · ' + i18next.t(tool.mode === 'scale' && this.sizeDragUnsure(this.asset(), this.selected) ? 'figure.text.gizmo-size-turned-fastening' : 'figure.text.gizmo-not-own', { part: partLabelOf(this.selected), extreme: this.keptExtremeWord() })}`}
224
+ title=${`${i18next.t('figure.button.gizmo-' + tool.mode)} (${tool.key})${tool.can || !this.selected ? '' : ' · ' + (tool.mode === 'scale' && this.sizeDragUnsure(this.asset(), this.selected) ? this.sizeDragRefusalOf(this.selected) : i18next.t('figure.text.gizmo-not-own'))}`}
223
225
  @click=${() => this.useGizmo(tool.mode)}
224
226
  >
225
227
  <md-icon>${tool.icon}</md-icon>
@@ -261,7 +263,7 @@ let FigureEditor = class FigureEditor extends LitElement {
261
263
  if (_changed.has('selected'))
262
264
  this.gizmoMode = undefined;
263
265
  /* The window is about the part chosen on the properties tab, being edited: it does not follow anywhere else. */
264
- if (this.fastening && (_changed.has('selected') || !this.selected || this.tab !== 'properties' || this.middle !== 'edit'))
266
+ if (this.fastening && (_changed.has('selected') || !this.selected || this.tab !== 'properties' || this.middle !== 'edit' || this.overlay))
265
267
  this.closeFastening();
266
268
  /*
267
269
  * The section editor is of one part: when the selection moves off it by any way -- another part picked, the scene's
@@ -295,7 +297,7 @@ let FigureEditor = class FigureEditor extends LitElement {
295
297
  if (mode === 'scale' && this.selected && this.gizmoMode !== 'scale') {
296
298
  const asset = this.asset();
297
299
  if (asset && this.sizeDragUnsure(asset, this.selected)) {
298
- this.error = i18next.t('figure.text.gizmo-size-turned-fastening', { part: partLabelOf(this.selected), extreme: this.keptExtremeWord() });
300
+ this.tellSizeByFields(this.selected);
299
301
  return;
300
302
  }
301
303
  }
@@ -575,6 +577,28 @@ let FigureEditor = class FigureEditor extends LitElement {
575
577
  return true;
576
578
  }
577
579
  }
580
+ /**
581
+ * Why this part's size is not dragged, in one line (user 2026-10-19: the reason told the faces kept, and was not
582
+ * understood): attached turned or twisted, or turned off its upright.
583
+ */
584
+ sizeDragRefusalOf(id) {
585
+ const fastened = !!this.asset()?.joints?.some(j => j.body1.part === ownerOf(id));
586
+ return i18next.t(fastened ? 'figure.text.gizmo-size-turned-fastening' : 'figure.text.gizmo-size-tilted', { part: partLabelOf(id) });
587
+ }
588
+ /** The size drag refused, said as a notice with the way to the part's size fields. */
589
+ tellSizeByFields(id) {
590
+ this.refusals.point(this.sizeDragRefusalOf(id), { label: i18next.t('figure.button.to-size-fields'), run: () => this.toSizeFields(id) });
591
+ }
592
+ /** To the part's first size field it takes typed, on the properties tab, ready to type over. */
593
+ async toSizeFields(id) {
594
+ this.middle = 'edit';
595
+ this.tab = 'properties';
596
+ this.selected = id;
597
+ await this.updateComplete;
598
+ const field = this.renderRoot.querySelector('[slot="size"] input:not([disabled]):not([readonly])');
599
+ field?.focus();
600
+ field?.select();
601
+ }
578
602
  /** A drag, written back as the part's own inputs — one step of the history, like typing the numbers. */
579
603
  transformed(id, result) {
580
604
  const view = this.session.read();
@@ -603,7 +627,7 @@ let FigureEditor = class FigureEditor extends LitElement {
603
627
  const jointed = !!asset.joints?.some(j => j.body1.part === ownerOf(id));
604
628
  /* A size drag this part cannot show truly is not written: it springs back, and the reason is said. */
605
629
  if (result.scale && result.scale.some(k => Math.abs(k - 1) > 1e-4) && this.sizeDragUnsure(asset, id)) {
606
- this.error = i18next.t('figure.text.gizmo-size-turned-fastening', { part: partLabelOf(id), extreme: this.keptExtremeWord() });
630
+ this.tellSizeByFields(id);
607
631
  return this.requestUpdate();
608
632
  }
609
633
  /*
@@ -681,10 +705,6 @@ let FigureEditor = class FigureEditor extends LitElement {
681
705
  * fastened. Refused, it says why.
682
706
  */
683
707
  /* What a tilted part's typed size keeps, by the figure's placement: not the floor's lowest point everywhere (designer 2026-09-27). */
684
- keptExtremeWord() {
685
- const placement = (this.asset())?.placement ?? 'floor';
686
- return i18next.t(`figure.label.kept-extreme-${placement}`);
687
- }
688
708
  /* How the selected part would meet the plane, worked out once per version and part. */
689
709
  planeContactOf(shown, part) {
690
710
  return derivedOf(shown, `plane-contact|${part}`, () => {
@@ -886,6 +906,7 @@ let FigureEditor = class FigureEditor extends LitElement {
886
906
  renderOverlay() {
887
907
  const close = () => (this.overlay = undefined);
888
908
  const panel = (which, label, body) => html `<div
909
+ side-overlay
889
910
  bar-overlay-panel=${which}
890
911
  role="dialog"
891
912
  aria-label=${i18next.t(label)}
@@ -1458,50 +1479,46 @@ let FigureEditor = class FigureEditor extends LitElement {
1458
1479
  this.openFastening();
1459
1480
  }
1460
1481
  openFastening() {
1461
- if (!this.selected || this.fastenPopup)
1482
+ if (!this.selected || this.fastening)
1462
1483
  return;
1463
1484
  this.fastenError = '';
1464
1485
  this.fastenHover = null;
1486
+ /* One panel over the side at a time: the view or the history gives way to the window. */
1487
+ this.overlay = undefined;
1465
1488
  this.fastening = true;
1466
- const content = document.createElement('figure-fasten-dialog');
1467
- content.addEventListener('preview-command', e => this.commandPreview.ask(e.detail));
1468
- content.addEventListener('fasten-hover', e => this.hoverFastening(e.detail));
1469
- content.addEventListener('fasten-apply', e => this.applyFastening(e.detail.action));
1470
- content.addEventListener('fasten-cancel', () => this.closeFastening());
1471
- this.fastenWindow = content;
1472
- this.tellFastening();
1473
- const popup = openPopup(html `${content}`, { title: i18next.t('figure.label.fastening'), size: 'fit', backdrop: true, movable: true, closable: true, escapable: true });
1474
- this.fastenPopup = popup;
1475
- /* Closed by its own X or Esc as well: the editor lets go of it the same way. */
1476
- popup.result.then(() => this.fastenPopup === popup && this.fastenClosed());
1477
1489
  }
1478
1490
  closeFastening() {
1479
- const popup = this.fastenPopup;
1480
- this.fastenClosed();
1481
- popup?.close();
1482
- }
1483
- fastenClosed() {
1484
- this.fastenPopup = undefined;
1485
- this.fastenWindow = undefined;
1486
1491
  this.fastening = false;
1487
1492
  this.fastenError = '';
1488
1493
  this.fastenHover = null;
1489
1494
  }
1490
- /** What the window shows is the editor's: the figure on screen, the part, and what the preview and a refusal said. */
1491
- tellFastening() {
1492
- const content = this.fastenWindow;
1493
- const view = this.session?.read();
1494
- if (!content || !view)
1495
- return;
1496
- content.revision = this.shown();
1497
- content.partId = this.selected;
1498
- content.locked = view.loading || !!view.error || view.saving || !view.context;
1499
- content.previewWords = this.commandPreview.words();
1500
- content.error = this.fastenError;
1501
- }
1502
- disconnectedCallback() {
1503
- super.disconnectedCallback();
1504
- this.closeFastening();
1495
+ /**
1496
+ * The fastening window over the side panel, as the bar's panels stand there: the figure on screen, the part, and what
1497
+ * the preview and a refusal said are the editor's. Its own Esc and 「취소」 close it, as its X does.
1498
+ */
1499
+ renderFastening(unavailable) {
1500
+ if (!this.fastening || !this.selected)
1501
+ return nothing;
1502
+ return html `<div side-overlay fasten-panel>
1503
+ <div overlay-head>
1504
+ <h3>${i18next.t('figure.label.fastening')}</h3>
1505
+ <button quiet fasten-close aria-label=${i18next.t('figure.button.close')} title=${i18next.t('figure.button.close')} @click=${() => this.closeFastening()}>
1506
+ <md-icon>close</md-icon>
1507
+ </button>
1508
+ </div>
1509
+ <figure-fasten-dialog
1510
+ overlay-body
1511
+ .revision=${this.shown()}
1512
+ .partId=${this.selected}
1513
+ ?locked=${unavailable}
1514
+ .previewWords=${this.commandPreview.words()}
1515
+ .error=${this.fastenError}
1516
+ @preview-command=${(e) => this.commandPreview.ask(e.detail)}
1517
+ @fasten-hover=${(e) => this.hoverFastening(e.detail)}
1518
+ @fasten-apply=${(e) => this.applyFastening(e.detail.action)}
1519
+ @fasten-cancel=${() => this.closeFastening()}
1520
+ ></figure-fasten-dialog>
1521
+ </div>`;
1505
1522
  }
1506
1523
  /** 「적용」: the command, one step; refused, the window stays open and says why. */
1507
1524
  applyFastening(action) {
@@ -1573,7 +1590,6 @@ let FigureEditor = class FigureEditor extends LitElement {
1573
1590
  }
1574
1591
  updated(changed) {
1575
1592
  super.updated?.(changed);
1576
- this.tellFastening();
1577
1593
  /* Nothing blocks release any more: the list under the bar closes, and opens fresh when something does again. */
1578
1594
  if (changed.has('releaseBlockers') && !this.releaseBlockers.length && this.blockersOpen)
1579
1595
  this.blockersOpen = false;
@@ -1964,7 +1980,7 @@ let FigureEditor = class FigureEditor extends LitElement {
1964
1980
  <div panel flush ?off=${tab !== 'verdict'}><slot name="verdict"></slot></div>
1965
1981
  </aside>
1966
1982
  </ox-three-pane>
1967
- ${this.renderOverlay()}`;
1983
+ ${this.renderOverlay()} ${this.renderFastening(unavailable)}`;
1968
1984
  }
1969
1985
  /** Position, rotation and size as rows of three cells; the shape's own measures one to a line, under their title. */
1970
1986
  renderGroups(groups, vectorTitle, singleTitle, unavailable, tools = null, held) {