@things-factory/figure-ui 10.1.125 → 10.1.127

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 (42) hide show
  1. package/client/modeller/align.ts +6 -4
  2. package/client/modeller/canvas.ts +6 -1
  3. package/client/modeller/editor.ts +149 -34
  4. package/client/modeller/evaluated.ts +47 -0
  5. package/client/modeller/figure-facts.ts +8 -0
  6. package/client/modeller/figure-preview.ts +38 -16
  7. package/client/modeller/figure-properties.ts +32 -0
  8. package/client/modeller/figure-verdict.ts +10 -1
  9. package/client/modeller/joint-fan.ts +4 -7
  10. package/client/modeller/links.ts +2 -1
  11. package/dist-client/modeller/align.d.ts +2 -1
  12. package/dist-client/modeller/align.js +6 -4
  13. package/dist-client/modeller/align.js.map +1 -1
  14. package/dist-client/modeller/canvas.js +8 -1
  15. package/dist-client/modeller/canvas.js.map +1 -1
  16. package/dist-client/modeller/editor.d.ts +35 -1
  17. package/dist-client/modeller/editor.js +138 -37
  18. package/dist-client/modeller/editor.js.map +1 -1
  19. package/dist-client/modeller/evaluated.d.ts +12 -0
  20. package/dist-client/modeller/evaluated.js +42 -0
  21. package/dist-client/modeller/evaluated.js.map +1 -0
  22. package/dist-client/modeller/figure-facts.d.ts +0 -7
  23. package/dist-client/modeller/figure-facts.js +9 -0
  24. package/dist-client/modeller/figure-facts.js.map +1 -1
  25. package/dist-client/modeller/figure-preview.js +38 -16
  26. package/dist-client/modeller/figure-preview.js.map +1 -1
  27. package/dist-client/modeller/figure-properties.d.ts +8 -2
  28. package/dist-client/modeller/figure-properties.js +26 -0
  29. package/dist-client/modeller/figure-properties.js.map +1 -1
  30. package/dist-client/modeller/figure-verdict.d.ts +2 -0
  31. package/dist-client/modeller/figure-verdict.js +10 -2
  32. package/dist-client/modeller/figure-verdict.js.map +1 -1
  33. package/dist-client/modeller/joint-fan.js +4 -7
  34. package/dist-client/modeller/joint-fan.js.map +1 -1
  35. package/dist-client/modeller/links.js +3 -2
  36. package/dist-client/modeller/links.js.map +1 -1
  37. package/dist-client/modeller/product-session.d.ts +68 -112
  38. package/dist-client/tsconfig.tsbuildinfo +1 -1
  39. package/package.json +4 -4
  40. package/test/evaluated.test.ts +40 -0
  41. package/test/gizmo.test.ts +1 -1
  42. package/test/mode-switch.test.ts +41 -0
@@ -7,7 +7,7 @@ import { notify } from '@operato/layout';
7
7
  import { OxPrompt } from '@operato/popup';
8
8
  import { customElement, property, state } from 'lit/decorators.js';
9
9
  import { live } from 'lit/directives/live.js';
10
- import { PART_LIMIT, parseFigureRecipe, resolveFigureRecipe, figurePartCountOf, figureRecipeIdFor, parseFigureAsset, figureSizeEditChanges, figureScaleAnchorOf, figureSectionMeasuresOf, figurePlaneAlignChanges, figurePartFaceOf, figureRowOf, figureFaceOverhangOf, figureRoomOnlyInputsOf, figureRoomRefitOf } from '@hatiolab/figure-model';
10
+ import { PART_LIMIT, parseFigureRecipe, resolveFigureRecipe, figurePartCountOf, figureRecipeIdFor, parseFigureAsset, figureSizeEditChanges, figureScaleAnchorOf, figureSectionMeasuresOf, figurePlaneAlignChanges, figurePartFaceOf, figureRowOf, figureFaceOverhangOf, figureRoomOnlyInputsOf, figureRoomRefitOf, seedFigureEvaluation, upgradeFigurePlacement } from '@hatiolab/figure-model';
11
11
  import './figure-recipe-catalog.js';
12
12
  import { captureFigureThumbnail } from './thumbnail.js';
13
13
  import { drawableFigureParts } from './renderer.js';
@@ -39,6 +39,7 @@ import { roomRefitRowsOf } from './occupancy.js';
39
39
  import { editRefusalOf, inDeclaredRoom, inputRangeRefusalOf, narrowingRetryOf, partRefusalOf, placementRefusalOf, recipeRefusalOf, removalNoticeOf, sizeRangeRefusalOf } from './refusals.js';
40
40
  import { endPlaceKeyOf, fieldWordsOf, followOf, measureNow, partSummaryOf, instanceRangeOf, instanceRangeToDeclare, proportionToFollow, rangeDiffers, rangeWith, turnsAboutAJoint, valueNow } from './links.js';
41
41
  import { jointFanOf } from './joint-fan.js';
42
+ import { seedEvaluatedGeometry } from './evaluated.js';
42
43
  import { jointDragOf } from './joint-drag.js';
43
44
  import { liveOverSession, NOTHING_LIVE, scrubbed, scrubStep } from './live.js';
44
45
  import { startingShapeKindOfBody } from './starting-shape.js';
@@ -55,6 +56,18 @@ function foldSaid() {
55
56
  return false;
56
57
  }
57
58
  /** Product editor surface. The injected session is shared by page persistence and AI commands. */
59
+ /** The words a panel is given when there are none: one array, so it does not read as a change. */
60
+ const NO_WORDS = [];
61
+ /** How long a command's candidate rests before its preview is worked out. */
62
+ const PREVIEW_SETTLE_MS = 150;
63
+ function deepFreeze(value) {
64
+ if (value && typeof value === 'object' && !Object.isFrozen(value)) {
65
+ Object.freeze(value);
66
+ for (const inner of Object.values(value))
67
+ deepFreeze(inner);
68
+ }
69
+ return value;
70
+ }
58
71
  let FigureEditor = class FigureEditor extends LitElement {
59
72
  constructor() {
60
73
  super(...arguments);
@@ -219,6 +232,10 @@ let FigureEditor = class FigureEditor extends LitElement {
219
232
  min-width: 0;
220
233
  min-height: 0;
221
234
  }
235
+ /* The edit canvas stays standing while the preview is in front (a 3D scene costs a third of a second to make), out of sight. */
236
+ div[middle][hidden] {
237
+ display: none;
238
+ }
222
239
  ox-canvas-bar .status {
223
240
  padding: 0 8px;
224
241
  color: var(--md-sys-color-on-surface-variant);
@@ -497,6 +514,8 @@ let FigureEditor = class FigureEditor extends LitElement {
497
514
  disconnectedCallback() {
498
515
  window.removeEventListener('keydown', this.onKey, true);
499
516
  this.widthWatch.disconnect();
517
+ clearTimeout(this.previewTimer);
518
+ clearTimeout(this.detachTimer);
500
519
  super.disconnectedCallback();
501
520
  }
502
521
  /*
@@ -837,7 +856,7 @@ let FigureEditor = class FigureEditor extends LitElement {
837
856
  */
838
857
  insertRecipe(recipe, values) {
839
858
  try {
840
- const asset = JSON.parse(this.session.source());
859
+ const asset = this.plainAsset();
841
860
  const checked = parseFigureRecipe(recipe);
842
861
  const id = figureRecipeIdFor(asset, checked);
843
862
  const steps = resolveFigureRecipe(checked, { id, ...(checked.target === 'part' ? { target: this.selected } : {}), values });
@@ -854,7 +873,7 @@ let FigureEditor = class FigureEditor extends LitElement {
854
873
  /** The design values of the asset being edited. */
855
874
  designValues() {
856
875
  try {
857
- return this.session ? (JSON.parse(this.session.source()).designInputs ?? {}) : {};
876
+ return this.session ? (this.plainAsset().designInputs ?? {}) : {};
858
877
  }
859
878
  catch {
860
879
  return {};
@@ -867,7 +886,7 @@ let FigureEditor = class FigureEditor extends LitElement {
867
886
  if (!id || !this.session || !this.session.read().geometry.some(g => g.id === id))
868
887
  return none;
869
888
  try {
870
- const asset = JSON.parse(this.session.source());
889
+ const asset = this.plainAsset();
871
890
  const reach = gizmoReachOf(asset, id);
872
891
  /*
873
892
  A size drag shows the face it keeps and the commit keeps the same (figureScaleAnchorOf). Where figure-model cannot
@@ -895,7 +914,7 @@ let FigureEditor = class FigureEditor extends LitElement {
895
914
  transformed(id, result) {
896
915
  const view = this.session.read();
897
916
  /* A part hanging from a joint: the drag edits the joint -- its value, or where it sits (ADR-0093). */
898
- const asset = JSON.parse(this.session.source());
917
+ const asset = this.plainAsset();
899
918
  const drawn = view.geometry.find(g => g.id === id);
900
919
  const drag = drawn ? jointDragOf(asset, ownerOf(id), drawn, result, this.currentInputs()) : null;
901
920
  if (drag) {
@@ -978,7 +997,7 @@ let FigureEditor = class FigureEditor extends LitElement {
978
997
  /** 맞추기 — one tool, one step of the history, through the same inputs as the gizmo. */
979
998
  align(tool) {
980
999
  this.run(() => {
981
- const asset = JSON.parse(this.session.source());
1000
+ const asset = this.plainAsset();
982
1001
  const changes = alignChanges(asset, this.selected, this.reachOf(this.selected), tool);
983
1002
  if (changes.length)
984
1003
  this.session.apply(changes);
@@ -993,7 +1012,7 @@ let FigureEditor = class FigureEditor extends LitElement {
993
1012
  const copy = this.session.read().geometry.find(g => !before.has(g.id))?.id;
994
1013
  if (!copy)
995
1014
  return;
996
- this.session.apply(mirrorChanges(JSON.parse(this.session.source()), id, copy, axis));
1015
+ this.session.apply(mirrorChanges(this.plainAsset(), id, copy, axis));
997
1016
  this.selected = copy;
998
1017
  });
999
1018
  }
@@ -1030,7 +1049,7 @@ let FigureEditor = class FigureEditor extends LitElement {
1030
1049
  @click=${() => this.alignToPlane()}>${i18next.t(`figure.button.align-to-${placement}`)}</button>`;
1031
1050
  }
1032
1051
  alignToPlane() {
1033
- const asset = parseFigureAsset(this.session.source());
1052
+ const asset = this.parsedAsset();
1034
1053
  const { changes, contact, refusal } = figurePlaneAlignChanges(asset, this.selected);
1035
1054
  if (refusal) {
1036
1055
  this.error = i18next.t(`figure.text.align-refused-${refusal.toLowerCase().replace(/_/g, '-')}`, { part: partLabelOf(this.selected) });
@@ -1047,7 +1066,7 @@ let FigureEditor = class FigureEditor extends LitElement {
1047
1066
  return null;
1048
1067
  let asset;
1049
1068
  try {
1050
- asset = JSON.parse(this.session.source());
1069
+ asset = this.plainAsset();
1051
1070
  }
1052
1071
  catch {
1053
1072
  return null;
@@ -1113,22 +1132,39 @@ let FigureEditor = class FigureEditor extends LitElement {
1113
1132
  : String(error);
1114
1133
  }
1115
1134
  }
1116
- /** Size response, fastening and motion for the selected part, as authoring commands. */
1117
- renderLinks(unavailable, section) {
1118
- let asset;
1135
+ dimsOf(asset) {
1136
+ const view = this.session?.read();
1137
+ const key = `${view?.context}|${view?.revision}|${this.selected}`;
1138
+ if (this.dimsSaid?.key === key)
1139
+ return this.dimsSaid.dims;
1140
+ let dims;
1119
1141
  try {
1120
- asset = JSON.parse(this.session.source());
1142
+ dims = boxOf(asset, this.selected, this.currentInputs())?.dims;
1121
1143
  }
1122
1144
  catch {
1123
- return null;
1145
+ dims = undefined;
1124
1146
  }
1125
- let dims;
1147
+ this.dimsSaid = { key, dims };
1148
+ return dims;
1149
+ }
1150
+ previewWordsNow() {
1151
+ const preview = this.currentPreview();
1152
+ const language = i18next.language;
1153
+ if (this.previewWordsSaid && this.previewWordsSaid.preview === preview && this.previewWordsSaid.part === this.selected && this.previewWordsSaid.language === language)
1154
+ return this.previewWordsSaid.words;
1155
+ const words = previewWordsOf(preview, this.selected, (key, params) => i18next.t(key, params));
1156
+ this.previewWordsSaid = { preview, part: this.selected, language, words };
1157
+ return words;
1158
+ }
1159
+ renderLinks(unavailable, section) {
1160
+ let asset;
1126
1161
  try {
1127
- dims = boxOf(asset, this.selected)?.dims;
1162
+ asset = this.plainAsset();
1128
1163
  }
1129
1164
  catch {
1130
- dims = undefined;
1165
+ return null;
1131
1166
  }
1167
+ const dims = this.dimsOf(asset);
1132
1168
  return html `<figure-links
1133
1169
  .asset=${asset}
1134
1170
  .section=${section}
@@ -1136,10 +1172,10 @@ let FigureEditor = class FigureEditor extends LitElement {
1136
1172
  .dims=${dims}
1137
1173
  ?locked=${unavailable}
1138
1174
  @command=${(e) => this.linkCommand(e.detail.action)}
1139
- .previewWords=${previewWordsOf(this.currentPreview(), this.selected, (key, params) => i18next.t(key, params))}
1175
+ .previewWords=${this.previewWordsNow()}
1140
1176
  .detachWords=${this.detachWords(asset)}
1141
- .overhangWords=${section === 'shape' ? this.overhangWords(asset) : []}
1142
- .overhangNote=${section === 'shape' ? this.overhangNote(asset) : []}
1177
+ .overhangWords=${section === 'shape' ? this.overhangWords(asset) : NO_WORDS}
1178
+ .overhangNote=${section === 'shape' ? this.overhangNote(asset) : NO_WORDS}
1143
1179
  .motionEnds=${section === 'motion' ? this.motionEnds() : null}
1144
1180
  @go-fasten=${() => this.goFasten()}
1145
1181
  @preview-command=${(e) => this.previewCommand(e.detail)}
@@ -1170,7 +1206,7 @@ let FigureEditor = class FigureEditor extends LitElement {
1170
1206
  renderControls(view, unavailable) {
1171
1207
  let asset;
1172
1208
  try {
1173
- asset = JSON.parse(this.session.source());
1209
+ asset = this.plainAsset();
1174
1210
  }
1175
1211
  catch {
1176
1212
  return null;
@@ -1259,7 +1295,7 @@ let FigureEditor = class FigureEditor extends LitElement {
1259
1295
  /* A size typed in keeps the face the figure's placement keeps -- figure-model's one rule, as the gizmo and "follow the size" do. */
1260
1296
  const part = this.selected;
1261
1297
  const change = { kind: field.role === 'state' ? 'set-state' : 'set-design', input: field.id, unit: field.unit, value };
1262
- this.run(() => this.session.apply(field.role !== 'state' && part ? figureSizeEditChanges(parseFigureAsset(this.session.source()), part, [change]) : [change]));
1298
+ this.run(() => this.session.apply(field.role !== 'state' && part ? figureSizeEditChanges(this.parsedAsset(), part, [change]) : [change]));
1263
1299
  // Rejected edits must also restore the displayed numeric value.
1264
1300
  input.value = String(this.session.read().fields.find(i => i.id === field.id).value);
1265
1301
  }
@@ -1597,7 +1633,7 @@ let FigureEditor = class FigureEditor extends LitElement {
1597
1633
  if (!Number.isFinite(pitch) || pitch === field.value)
1598
1634
  return;
1599
1635
  /* Read from a design input the board may set: the input's value changes, the row keeps reading it (ADR-0096). */
1600
- const from = this.pitchInputOf(JSON.parse(this.session.source()), this.selected);
1636
+ const from = this.pitchInputOf(this.plainAsset(), this.selected);
1601
1637
  if (from) {
1602
1638
  this.run(() => this.session.apply([{ kind: 'set-design', input: from, unit: 'mm', value: pitch }]));
1603
1639
  input.value = String(this.session.read().fields.find(i => i.id === from)?.value ?? field.value);
@@ -1633,20 +1669,29 @@ let FigureEditor = class FigureEditor extends LitElement {
1633
1669
  return i18next.t('figure.text.release-state-released', { version: String(version) });
1634
1670
  return version > 0 ? i18next.t('figure.text.release-state-draft-over', { version: String(version) }) : i18next.t('figure.text.release-state-draft-new');
1635
1671
  }
1636
- /** The fastening form's command, seen before it is done; null clears what was shown. */
1637
1672
  previewCommand(candidate) {
1673
+ clearTimeout(this.previewTimer);
1638
1674
  if (!candidate || !this.session || !this.selected) {
1639
1675
  this.preview = undefined;
1640
1676
  return;
1641
1677
  }
1642
1678
  const view = this.session.read();
1679
+ const same = this.preview && this.preview.key === JSON.stringify(candidate) && this.preview.context === view.context && this.preview.part === this.selected && 'revision' in this.preview && this.preview.revision === view.revision;
1680
+ if (same)
1681
+ return;
1682
+ this.previewTimer = setTimeout(() => this.computePreview(candidate), PREVIEW_SETTLE_MS);
1683
+ }
1684
+ computePreview(candidate) {
1685
+ if (!this.session || !this.selected)
1686
+ return;
1687
+ const view = this.session.read();
1643
1688
  const held = { context: view.context, revision: view.revision, key: JSON.stringify(candidate), part: this.selected };
1644
1689
  try {
1645
1690
  const seen = this.session.previewCommand(candidate.action);
1646
1691
  let face;
1647
1692
  if (candidate.face) {
1648
1693
  try {
1649
- face = figurePartFaceOf(parseFigureAsset(this.session.source()), candidate.face.part, candidate.face.face, this.currentInputs());
1694
+ face = figurePartFaceOf(this.parsedAsset(), candidate.face.part, candidate.face.face, this.currentInputs());
1650
1695
  }
1651
1696
  catch {
1652
1697
  face = undefined;
@@ -1681,14 +1726,32 @@ let FigureEditor = class FigureEditor extends LitElement {
1681
1726
  return undefined;
1682
1727
  return p;
1683
1728
  }
1729
+ /**
1730
+ * Said once the figure has rested: working it out is a preview of the command on a copy (tens of milliseconds), and the
1731
+ * edit that made this version should be on screen before it. Until then nothing is said, as for a part with no joint.
1732
+ */
1684
1733
  detachWords(asset) {
1685
1734
  const view = this.session?.read();
1686
1735
  const joint = asset?.joints?.find(j => j.body1.part === this.selected);
1687
1736
  if (!asset || !view || !joint || !this.session)
1688
- return [];
1737
+ return NO_WORDS;
1689
1738
  const key = `${view.context}|${view.revision}|${this.selected}`;
1690
1739
  if (this.detachSaid?.key === key)
1691
1740
  return this.detachSaid.words;
1741
+ if (this.detachAsked !== key) {
1742
+ this.detachAsked = key;
1743
+ clearTimeout(this.detachTimer);
1744
+ this.detachTimer = setTimeout(() => {
1745
+ const now = this.session?.read();
1746
+ if (!now || `${now.context}|${now.revision}|${this.selected}` !== key)
1747
+ return;
1748
+ this.computeDetachWords(asset, key);
1749
+ this.requestUpdate();
1750
+ }, PREVIEW_SETTLE_MS);
1751
+ }
1752
+ return NO_WORDS;
1753
+ }
1754
+ computeDetachWords(asset, key) {
1692
1755
  const say = (k, params) => i18next.t(k, params);
1693
1756
  let words;
1694
1757
  try {
@@ -1717,7 +1780,7 @@ let FigureEditor = class FigureEditor extends LitElement {
1717
1780
  overhangWords(asset) {
1718
1781
  const view = this.session?.read();
1719
1782
  if (!asset || !view || !this.selected)
1720
- return [];
1783
+ return NO_WORDS;
1721
1784
  const key = `${view.context}|${view.revision}|${this.selected}`;
1722
1785
  if (this.overhangSaid?.key === key)
1723
1786
  return this.overhangSaid.words;
@@ -1759,7 +1822,7 @@ let FigureEditor = class FigureEditor extends LitElement {
1759
1822
  if (this.motionEndsSaid?.key !== key) {
1760
1823
  let ends = null;
1761
1824
  try {
1762
- ends = motionEndsOf(parseFigureAsset(this.session.source()), this.selected, this.currentInputs());
1825
+ ends = motionEndsOf(this.parsedAsset(), this.selected, this.currentInputs());
1763
1826
  }
1764
1827
  catch {
1765
1828
  ends = null;
@@ -1803,9 +1866,46 @@ let FigureEditor = class FigureEditor extends LitElement {
1803
1866
  if (id && (this.tab === 'verdict' || this.tab === 'view' || this.tab === 'history'))
1804
1867
  this.tab = 'properties';
1805
1868
  }
1869
+ /**
1870
+ * The figure as the session holds it now, parsed once per text: a render asks for it a dozen times, and parsing the whole
1871
+ * figure is most of what a selection costs. Read, never changed -- what changes a figure goes through the session.
1872
+ */
1873
+ /**
1874
+ * The session has evaluated this figure at this version, with these inputs: the screen's questions of the text it just
1875
+ * parsed are answered from that, not by evaluating the figure again (each is tens of milliseconds on a real figure).
1876
+ */
1877
+ seeded(asset) {
1878
+ const view = this.session?.read();
1879
+ if (view?.geometry.length) {
1880
+ const values = this.currentInputs();
1881
+ seedEvaluatedGeometry(asset, values, view.geometry);
1882
+ seedFigureEvaluation(asset, values, view.geometry);
1883
+ }
1884
+ return asset;
1885
+ }
1886
+ parsedAsset() {
1887
+ const text = this.session.source();
1888
+ if (this.parsed?.text !== text) {
1889
+ /* The session's text is what its workspace exported, compiled then: it is read here, not compiled again. */
1890
+ const plain = this.plainAsset();
1891
+ const upgraded = upgradeFigurePlacement(plain);
1892
+ this.parsed = { text, asset: upgraded === plain ? plain : this.seeded(deepFreeze(upgraded)) };
1893
+ }
1894
+ return this.parsed.asset;
1895
+ }
1896
+ /**
1897
+ * The figure as plain data, parsed once per text and frozen: the render, the gizmo's reach and every tool read it, and
1898
+ * a copy for each of them cost more than the selection they serve. A change goes through the session, never through this.
1899
+ */
1900
+ plainAsset() {
1901
+ const text = this.session.source();
1902
+ if (this.plain?.text !== text)
1903
+ this.plain = { text, asset: this.seeded(deepFreeze(JSON.parse(text))) };
1904
+ return this.plain.asset;
1905
+ }
1806
1906
  asset() {
1807
1907
  try {
1808
- return JSON.parse(this.session.source());
1908
+ return this.plainAsset();
1809
1909
  }
1810
1910
  catch {
1811
1911
  return undefined;
@@ -1939,7 +2039,7 @@ let FigureEditor = class FigureEditor extends LitElement {
1939
2039
  let summary;
1940
2040
  try {
1941
2041
  summary = this.selected
1942
- ? partSummaryOf(parseFigureAsset(this.session.source()), ownerOf(this.selected), (key, params) => i18next.t(key, params), i18next.language || 'ko')
2042
+ ? partSummaryOf(this.parsedAsset(), ownerOf(this.selected), (key, params) => i18next.t(key, params), i18next.language || 'ko')
1943
2043
  : undefined;
1944
2044
  }
1945
2045
  catch {
@@ -1979,7 +2079,7 @@ let FigureEditor = class FigureEditor extends LitElement {
1979
2079
  */
1980
2080
  let roomOnly = [];
1981
2081
  try {
1982
- roomOnly = this.selected ? figureRoomOnlyInputsOf(parseFigureAsset(this.session.source()), this.selected) : [];
2082
+ roomOnly = this.selected ? figureRoomOnlyInputsOf(this.parsedAsset(), this.selected) : [];
1983
2083
  }
1984
2084
  catch {
1985
2085
  roomOnly = [];
@@ -1990,7 +2090,7 @@ let FigureEditor = class FigureEditor extends LitElement {
1990
2090
  try {
1991
2091
  const body = view.geometry.find(g => g.id === this.selected);
1992
2092
  const placed = body ? placementOf(body) : undefined;
1993
- const parsed = this.selected ? parseFigureAsset(this.session.source()) : undefined;
2093
+ const parsed = this.selected ? this.parsedAsset() : undefined;
1994
2094
  if (placed && parsed)
1995
2095
  held = {
1996
2096
  position: placed.position,
@@ -2084,7 +2184,8 @@ let FigureEditor = class FigureEditor extends LitElement {
2084
2184
  }}
2085
2185
  @part-edit=${(e) => this.run(() => this.session.edit(e.detail.edit))}
2086
2186
  ></figure-preview>`
2087
- : html `<div middle>
2187
+ : nothing}
2188
+ <div middle ?hidden=${this.middle === 'preview'}>
2088
2189
  ${view.geometry.length === 0 && !unavailable ? this.renderStart() : nothing}
2089
2190
  ${this.sectionEditing && this.sectionEditing === this.selected && !unavailable ? this.renderSectionEditor(view.geometry) : nothing}
2090
2191
  <figure-canvas
@@ -2101,9 +2202,9 @@ let FigureEditor = class FigureEditor extends LitElement {
2101
2202
  @part-transformed=${(e) => this.transformed(e.detail.id, e.detail.result)}
2102
2203
  @part-selected=${(e) => this.choose(e.detail)}
2103
2204
  @render-error=${(e) => {
2104
- this.error = e.detail.message;
2105
- }}
2106
- ></figure-canvas></div>`}
2205
+ this.error = e.detail.message;
2206
+ }}
2207
+ ></figure-canvas></div>
2107
2208
  <aside slot="end">
2108
2209
  <div tabs>
2109
2210
  ${this.renderTab('properties', 'tune', i18next.t('figure.label.properties'))}
@@ -2285,7 +2386,7 @@ let FigureEditor = class FigureEditor extends LitElement {
2285
2386
  async askRoomRefit() {
2286
2387
  let refit;
2287
2388
  try {
2288
- refit = figureRoomRefitOf(parseFigureAsset(this.session.source()));
2389
+ refit = figureRoomRefitOf(this.parsedAsset());
2289
2390
  }
2290
2391
  catch (error) {
2291
2392
  this.error = this.said(error);