@things-factory/figure-ui 10.1.26 → 10.1.28

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 (83) hide show
  1. package/client/graphql/index.ts +23 -0
  2. package/client/modeller/base-box.ts +104 -0
  3. package/client/modeller/figure-animations.ts +253 -32
  4. package/client/modeller/figure-canvas.ts +18 -3
  5. package/client/modeller/figure-inspector.ts +292 -3
  6. package/client/modeller/figure-preview.ts +57 -2
  7. package/client/modeller/figure-report.ts +9 -4
  8. package/client/modeller/figure-side.ts +9 -1
  9. package/client/modeller/figure-source.ts +24 -4
  10. package/client/modeller/figure-view.ts +20 -0
  11. package/client/modeller/joint-edits.ts +262 -0
  12. package/client/modeller/joint-gizmo.ts +118 -0
  13. package/client/modeller/pose-defaults.ts +67 -0
  14. package/client/modeller/proposal.ts +9 -3
  15. package/client/modeller/scene-budget.ts +31 -40
  16. package/client/pages/figure-list-page.ts +4 -9
  17. package/client/pages/figure-modeller-page.ts +124 -41
  18. package/dist-client/graphql/index.d.ts +7 -0
  19. package/dist-client/graphql/index.js +21 -0
  20. package/dist-client/graphql/index.js.map +1 -1
  21. package/dist-client/modeller/base-box.d.ts +47 -0
  22. package/dist-client/modeller/base-box.js +52 -0
  23. package/dist-client/modeller/base-box.js.map +1 -0
  24. package/dist-client/modeller/figure-animations.d.ts +29 -0
  25. package/dist-client/modeller/figure-animations.js +228 -25
  26. package/dist-client/modeller/figure-animations.js.map +1 -1
  27. package/dist-client/modeller/figure-canvas.d.ts +9 -0
  28. package/dist-client/modeller/figure-canvas.js +14 -3
  29. package/dist-client/modeller/figure-canvas.js.map +1 -1
  30. package/dist-client/modeller/figure-inspector.d.ts +37 -0
  31. package/dist-client/modeller/figure-inspector.js +253 -2
  32. package/dist-client/modeller/figure-inspector.js.map +1 -1
  33. package/dist-client/modeller/figure-preview.d.ts +15 -0
  34. package/dist-client/modeller/figure-preview.js +48 -2
  35. package/dist-client/modeller/figure-preview.js.map +1 -1
  36. package/dist-client/modeller/figure-report.d.ts +3 -2
  37. package/dist-client/modeller/figure-report.js +9 -4
  38. package/dist-client/modeller/figure-report.js.map +1 -1
  39. package/dist-client/modeller/figure-side.d.ts +5 -1
  40. package/dist-client/modeller/figure-side.js +12 -0
  41. package/dist-client/modeller/figure-side.js.map +1 -1
  42. package/dist-client/modeller/figure-source.d.ts +15 -0
  43. package/dist-client/modeller/figure-source.js +8 -4
  44. package/dist-client/modeller/figure-source.js.map +1 -1
  45. package/dist-client/modeller/figure-view.d.ts +16 -0
  46. package/dist-client/modeller/figure-view.js +15 -0
  47. package/dist-client/modeller/figure-view.js.map +1 -1
  48. package/dist-client/modeller/joint-edits.d.ts +49 -0
  49. package/dist-client/modeller/joint-edits.js +223 -0
  50. package/dist-client/modeller/joint-edits.js.map +1 -0
  51. package/dist-client/modeller/joint-gizmo.d.ts +31 -0
  52. package/dist-client/modeller/joint-gizmo.js +104 -0
  53. package/dist-client/modeller/joint-gizmo.js.map +1 -0
  54. package/dist-client/modeller/pose-defaults.d.ts +9 -0
  55. package/dist-client/modeller/pose-defaults.js +35 -0
  56. package/dist-client/modeller/pose-defaults.js.map +1 -0
  57. package/dist-client/modeller/proposal.d.ts +1 -1
  58. package/dist-client/modeller/proposal.js +8 -3
  59. package/dist-client/modeller/proposal.js.map +1 -1
  60. package/dist-client/modeller/scene-budget.d.ts +31 -34
  61. package/dist-client/modeller/scene-budget.js +28 -37
  62. package/dist-client/modeller/scene-budget.js.map +1 -1
  63. package/dist-client/pages/figure-list-page.js +4 -9
  64. package/dist-client/pages/figure-list-page.js.map +1 -1
  65. package/dist-client/pages/figure-modeller-page.d.ts +43 -0
  66. package/dist-client/pages/figure-modeller-page.js +121 -40
  67. package/dist-client/pages/figure-modeller-page.js.map +1 -1
  68. package/dist-client/tsconfig.tsbuildinfo +1 -1
  69. package/dist-server/tsconfig.tsbuildinfo +1 -1
  70. package/package.json +3 -3
  71. package/test/ai-proposal-contract.test.ts +16 -11
  72. package/test/base-box.test.ts +154 -0
  73. package/test/figure-source.test.ts +32 -0
  74. package/test/joint-edits.test.ts +165 -0
  75. package/test/joint-gizmo.test.ts +174 -0
  76. package/test/pose-defaults.test.ts +71 -0
  77. package/test/preview-board-depth.test.ts +27 -0
  78. package/test/scene-budget.test.ts +53 -0
  79. package/translations/en.json +37 -2
  80. package/translations/ja.json +37 -2
  81. package/translations/ko.json +37 -2
  82. package/translations/ms.json +37 -2
  83. package/translations/zh.json +37 -2
@@ -1,5 +1,6 @@
1
1
  import '@material/web/icon/icon.js';
2
2
  import { LitElement } from 'lit';
3
+ import type { ContractChange } from '@hatiolab/figure-model';
3
4
  import type { FigureDraft, PartModel } from './figure-source.js';
4
5
  declare const FigureInspector_base: (new (...args: any[]) => LitElement) & typeof LitElement;
5
6
  /**
@@ -57,6 +58,14 @@ export declare class FigureInspector extends FigureInspector_base {
57
58
  draft?: FigureDraft;
58
59
  /** 초안 위의 부품들. 씬 모델 그대로다. */
59
60
  parts: PartModel[];
61
+ /**
62
+ * 이 고침이 이미 놓인 인스턴스에 무엇을 하나 — 발행된 도형에만 있다(ADR-0063 결정 3).
63
+ *
64
+ * 발행 문에서만 말하면 저작자는 열 군데를 고친 뒤에 듣는다. 고치는 칸 옆에서 말한다.
65
+ */
66
+ contract: ContractChange[];
67
+ /** 견주지 못한 까닭. 빈 목록과 뜻이 다르다 — 「깨지 않는다」가 아니라 「모른다」다. */
68
+ contractFailure?: string;
60
69
  selected: number;
61
70
  /** `part` 는 HTMLElement 의 속성(DOMTokenList)이다 — 가리면 형이 어긋난다. */
62
71
  private get chosen();
@@ -72,6 +81,34 @@ export declare class FigureInspector extends FigureInspector_base {
72
81
  * 컨베이어가 길어지면 실을 자리가 늘어나는 것이 그것이고, 여기 적은 값은 안 쓰인다.
73
82
  */
74
83
  private renderRole;
84
+ /**
85
+ * How this part hangs from another and moves relative to it (ADR-0066).
86
+ *
87
+ * The parent is written on the part. The joint says how the part moves relative to that parent, and the
88
+ * parameter it comes with is what drives it; the preview shows that parameter as a slider.
89
+ */
90
+ private renderJoint;
91
+ private setParent;
92
+ /**
93
+ * 기준 상자를 고친다 — 부품은 저장되는 자리에 그대로 둔 채(`base-box.ts`).
94
+ *
95
+ * 초안과 부품이 함께 바뀌므로 둘을 한 번에 올린다. 따로 올리면 되돌리기가 둘로 갈리고, 그 사이
96
+ * 한 번만 되돌린 화면은 부품이 반쯤 밀린 모습이 된다.
97
+ */
98
+ /**
99
+ * 기준 상자를 고친 것이 **이미 놓인 인스턴스에 무엇을 하나** — 고친 칸 바로 아래에서 말한다.
100
+ *
101
+ * 막는 것과 알리는 것을 가른다. 비율이 달라지면 놓인 것이 찌그러지므로 발행이 막히고, 크기만
102
+ * 달라지는 것은 실척이 어긋날 뿐이라 알리기만 한다. 까닭은 형식이 적은 것을 그대로 옮긴다 —
103
+ * 여기서 다시 쓰면 두 벌이 되고 곧 갈린다.
104
+ */
105
+ private renderBaseContract;
106
+ private resizeBase;
107
+ /** 부품 전부를 감싸는 가장 작은 상자로. 맞출 것이 없으면 아무 일도 안 한다. */
108
+ private fitBaseToParts;
109
+ private setJointType;
110
+ private setJointAxis;
111
+ private tellDraft;
75
112
  /**
76
113
  * 역할 하나를 켜고 끈다.
77
114
  *
@@ -6,10 +6,24 @@ import { live } from 'lit/directives/live.js';
6
6
  import { i18next, localize } from '@operato/i18n';
7
7
  import { ScrollbarStyles } from '@operato/styles';
8
8
  import { EXTRUDE_PRIMITIVES, MATERIAL_PRESETS, PRIMITIVE_KINDS, SEGMENTED_PRIMITIVES, SEGMENT_PRESETS, SIZING_RULES, trianglesOf } from '@hatiolab/figure-model';
9
- import { ANCHOR_RULES, PART_ROLES, REPEAT_LIMIT, anchorOfPart, repeatPlanOfPart } from '@hatiolab/figure-model';
9
+ import { ANCHOR_RULES, JOINT_TYPES, PART_ROLES, REPEAT_LIMIT, anchorOfPart, repeatPlanOfPart } from '@hatiolab/figure-model';
10
10
  import { activePalette } from '@hatiolab/things-scene';
11
11
  import { partToFigure, partFromFigure } from './figure-source.js';
12
12
  import { canMirror } from './part-edits.js';
13
+ import { jointOf, parentChoices, patchJoint, setJointType } from './joint-edits.js';
14
+ import { baseFittingParts, baseOf, resizeBase } from './base-box.js';
15
+ /** Axis choices for a joint. Written as a direction the author reads, stored as a vector. */
16
+ const JOINT_AXES = [
17
+ { key: '+x', axis: { x: 1, y: 0, z: 0 } },
18
+ { key: '-x', axis: { x: -1, y: 0, z: 0 } },
19
+ { key: '+y', axis: { x: 0, y: 1, z: 0 } },
20
+ { key: '-y', axis: { x: 0, y: -1, z: 0 } },
21
+ { key: '+z', axis: { x: 0, y: 0, z: 1 } },
22
+ { key: '-z', axis: { x: 0, y: 0, z: -1 } }
23
+ ];
24
+ function axisKeyOf(axis) {
25
+ return JOINT_AXES.find(one => one.axis.x === axis.x && one.axis.y === axis.y && one.axis.z === axis.z)?.key;
26
+ }
13
27
  /**
14
28
  * 고른 부품의 속성.
15
29
  *
@@ -61,6 +75,12 @@ let FigureInspector = class FigureInspector extends localize(i18next)(LitElement
61
75
  this.opened = new Set();
62
76
  /** 초안 위의 부품들. 씬 모델 그대로다. */
63
77
  this.parts = [];
78
+ /**
79
+ * 이 고침이 이미 놓인 인스턴스에 무엇을 하나 — 발행된 도형에만 있다(ADR-0063 결정 3).
80
+ *
81
+ * 발행 문에서만 말하면 저작자는 열 군데를 고친 뒤에 듣는다. 고치는 칸 옆에서 말한다.
82
+ */
83
+ this.contract = [];
64
84
  this.selected = -1;
65
85
  }
66
86
  static { this.styles = [
@@ -288,9 +308,60 @@ let FigureInspector = class FigureInspector extends localize(i18next)(LitElement
288
308
  grid-template-columns: repeat(3, 1fr);
289
309
  gap: 4px;
290
310
  }
311
+ /*
312
+ 값이 둘인 줄은 셋으로 나누지 않는다. 가동 범위를 세 칸짜리 줄에 넣었더니 칸이 폭의 1/3 이라
313
+ 「최소」라는 말과 -120 이 한 칸에 못 들어가 숫자가 잘렸다.
314
+ */
315
+ div[double] {
316
+ display: grid;
317
+ grid-template-columns: repeat(2, 1fr);
318
+ gap: 4px;
319
+ }
320
+ /* 칸 아래에 붙는 한 줄짜리 행동. 칸과 같은 폭으로 둬야 한 묶음으로 읽힌다. */
321
+ button[fit] {
322
+ display: inline-flex;
323
+ align-items: center;
324
+ justify-content: center;
325
+ gap: 4px;
326
+ width: 100%;
327
+ margin-top: 4px;
328
+ padding: 3px 8px;
329
+ font: inherit;
330
+ font-size: 0.72rem;
331
+ color: var(--md-sys-color-on-surface);
332
+ background-color: var(--md-sys-color-surface-container);
333
+ border: 1px solid var(--md-sys-color-outline-variant);
334
+ border-radius: 6px;
335
+ cursor: pointer;
336
+ }
337
+ /* 고친 칸 바로 아래에 붙는 말. 막는 것은 오류 색, 알리는 것은 조용한 색. */
338
+ p[contract] {
339
+ display: flex;
340
+ gap: 4px;
341
+ margin: 4px 0 0;
342
+ font-size: 0.68rem;
343
+ line-height: 1.4;
344
+ color: var(--md-sys-color-on-surface-variant);
345
+ }
346
+ p[contract][blocking] {
347
+ color: var(--md-sys-color-error);
348
+ }
349
+ p[contract] md-icon {
350
+ --md-icon-size: 14px;
351
+ flex: none;
352
+ }
353
+
354
+ button[fit]:hover {
355
+ background-color: var(--md-sys-color-surface-container-high);
356
+ }
357
+ button[fit] md-icon {
358
+ --md-icon-size: 15px;
359
+ }
291
360
  div[field] + div[field],
292
361
  div[triple] + div[field],
293
362
  div[field] + div[triple],
363
+ div[double] + div[field],
364
+ div[field] + div[double],
294
365
  div[inline-field] + div[inline-field],
295
366
  div[inline-field] + div[field],
296
367
  div[field] + div[inline-field],
@@ -796,7 +867,7 @@ let FigureInspector = class FigureInspector extends localize(i18next)(LitElement
796
867
  묶음은 아예 나오지 않는다 — 해 둬도 아무 일도 안 일어나는 칸을 보여 주지 않는다.
797
868
  */
798
869
  return html `
799
- ${this.renderFigure(true)} ${this.renderIdentity(shown)} ${this.renderRole(shown)}
870
+ ${this.renderFigure(true)} ${this.renderIdentity(shown)} ${this.renderRole(shown)} ${this.renderJoint(shown)}
800
871
  ${this.renderPlacement(shown)} ${this.renderShape(shown)} ${this.renderMaterial(shown)}
801
872
  ${this.renderBehaviour(shown)}
802
873
  `;
@@ -855,6 +926,157 @@ let FigureInspector = class FigureInspector extends localize(i18next)(LitElement
855
926
  </section>
856
927
  `;
857
928
  }
929
+ /**
930
+ * How this part hangs from another and moves relative to it (ADR-0066).
931
+ *
932
+ * The parent is written on the part. The joint says how the part moves relative to that parent, and the
933
+ * parameter it comes with is what drives it; the preview shows that parameter as a slider.
934
+ */
935
+ renderJoint(part) {
936
+ const joint = jointOf(this.draft, part.name);
937
+ const choices = parentChoices(this.parts, part.name);
938
+ const unit = joint?.type === 'prismatic' ? 'mm' : 'deg';
939
+ const axisKey = joint ? axisKeyOf(joint.axis) : undefined;
940
+ const driver = joint
941
+ ? this.draft?.parameters?.find(one => one.clip?.channels?.some(channel => channel.target === joint.name))
942
+ : undefined;
943
+ return html `
944
+ <section>
945
+ <h3>${i18next.t('figure.label.joint')}${this.help('figure.text.joint-explained')}</h3>
946
+
947
+ <div inline-field>
948
+ <label>${i18next.t('figure.label.joint-parent')}</label>
949
+ <select @change=${(e) => this.setParent(e.target.value)}>
950
+ <option value="" ?selected=${part.parent === undefined}>${i18next.t('figure.label.joint-parent-none')}</option>
951
+ ${part.parent !== undefined && !choices.includes(part.parent)
952
+ ? html `<option value=${part.parent} selected>${part.parent}</option>`
953
+ : nothing}
954
+ ${choices.map(name => html `<option value=${name} ?selected=${name === part.parent}>${name}</option>`)}
955
+ </select>
956
+ </div>
957
+
958
+ <div inline-field>
959
+ <label>${i18next.t('figure.label.joint-type')}</label>
960
+ <select @change=${(e) => this.setJointType(part, e.target.value)}>
961
+ <option value="" ?selected=${!joint}>${i18next.t('figure.label.joint-type-none')}</option>
962
+ ${JOINT_TYPES.map(type => html `<option value=${type} ?selected=${joint?.type === type}>${i18next.t('figure.label.joint-type-' + type)}</option>`)}
963
+ </select>
964
+ </div>
965
+
966
+ ${joint
967
+ ? html `
968
+ <div inline-field>
969
+ <label>${i18next.t('figure.label.joint-axis')}</label>
970
+ <select @change=${(e) => this.setJointAxis(joint.name, e.target.value)}>
971
+ ${axisKey === undefined
972
+ ? html `<option value="" selected>${joint.axis.x}, ${joint.axis.y}, ${joint.axis.z}</option>`
973
+ : nothing}
974
+ ${JOINT_AXES.map(one => html `<option value=${one.key} ?selected=${one.key === axisKey}>${one.key.toUpperCase()}</option>`)}
975
+ </select>
976
+ </div>
977
+
978
+ <div vector-row>
979
+ <label>${i18next.t('figure.label.joint-origin')}</label>
980
+ <div triple>
981
+ ${['x', 'y', 'z'].map(axis => this.number(axis.toUpperCase(), joint.origin[axis], value => this.tellDraft(patchJoint(this.draft, joint.name, { origin: { ...joint.origin, [axis]: value } }))))}
982
+ </div>
983
+ </div>
984
+
985
+ ${joint.limits
986
+ ? html `
987
+ <div vector-row>
988
+ <label>${i18next.t('figure.label.joint-limits-' + unit)}</label>
989
+ <div double>
990
+ ${this.number(i18next.t('figure.label.joint-limit-min'), joint.limits.min, value => this.tellDraft(patchJoint(this.draft, joint.name, { limits: { ...joint.limits, min: value } })))}
991
+ ${this.number(i18next.t('figure.label.joint-limit-max'), joint.limits.max, value => this.tellDraft(patchJoint(this.draft, joint.name, { limits: { ...joint.limits, max: value } })))}
992
+ </div>
993
+ </div>
994
+ ${joint.limits.min > 0 || joint.limits.max < 0
995
+ ? html `<p warn>${i18next.t('figure.text.joint-limits-must-include-zero')}</p>`
996
+ : nothing}
997
+ `
998
+ : nothing}
999
+
1000
+ <p quiet>
1001
+ ${driver
1002
+ ? i18next.t('figure.text.joint-driven-by', { name: driver.label ?? driver.name })
1003
+ : i18next.t('figure.text.joint-not-driven')}
1004
+ </p>
1005
+ `
1006
+ : nothing}
1007
+ </section>
1008
+ `;
1009
+ }
1010
+ setParent(name) {
1011
+ this.change(part => {
1012
+ const { parent: _dropped, ...rest } = part;
1013
+ return name === '' ? rest : { ...rest, parent: name };
1014
+ });
1015
+ }
1016
+ /**
1017
+ * 기준 상자를 고친다 — 부품은 저장되는 자리에 그대로 둔 채(`base-box.ts`).
1018
+ *
1019
+ * 초안과 부품이 함께 바뀌므로 둘을 한 번에 올린다. 따로 올리면 되돌리기가 둘로 갈리고, 그 사이
1020
+ * 한 번만 되돌린 화면은 부품이 반쯤 밀린 모습이 된다.
1021
+ */
1022
+ /**
1023
+ * 기준 상자를 고친 것이 **이미 놓인 인스턴스에 무엇을 하나** — 고친 칸 바로 아래에서 말한다.
1024
+ *
1025
+ * 막는 것과 알리는 것을 가른다. 비율이 달라지면 놓인 것이 찌그러지므로 발행이 막히고, 크기만
1026
+ * 달라지는 것은 실척이 어긋날 뿐이라 알리기만 한다. 까닭은 형식이 적은 것을 그대로 옮긴다 —
1027
+ * 여기서 다시 쓰면 두 벌이 되고 곧 갈린다.
1028
+ */
1029
+ renderBaseContract() {
1030
+ if (this.contractFailure) {
1031
+ return html `<p contract>
1032
+ <md-icon>help</md-icon>
1033
+ <span>${i18next.t('figure.text.contract-not-compared', { why: this.contractFailure })}</span>
1034
+ </p>`;
1035
+ }
1036
+ const mine = this.contract.filter(one => one.code === 'base-ratio-changed' || one.code === 'base-size-changed');
1037
+ if (mine.length === 0)
1038
+ return nothing;
1039
+ return html `${mine.map(one => html `<p contract ?blocking=${one.blocking}>
1040
+ <md-icon>${one.blocking ? 'block' : 'info'}</md-icon>
1041
+ <span><b>${one.message}</b> ${one.why}</span>
1042
+ </p>`)}`;
1043
+ }
1044
+ resizeBase(patch) {
1045
+ if (!this.draft)
1046
+ return;
1047
+ const next = resizeBase(this.draft, this.parts, { ...baseOf(this.draft), ...patch });
1048
+ if (next.draft === this.draft)
1049
+ return;
1050
+ this.dispatchEvent(new CustomEvent('resize-base', {
1051
+ detail: { draft: next.draft, parts: next.parts },
1052
+ bubbles: true,
1053
+ composed: true
1054
+ }));
1055
+ }
1056
+ /** 부품 전부를 감싸는 가장 작은 상자로. 맞출 것이 없으면 아무 일도 안 한다. */
1057
+ fitBaseToParts() {
1058
+ if (!this.draft)
1059
+ return;
1060
+ const fitted = baseFittingParts(this.draft, this.parts);
1061
+ if (fitted)
1062
+ this.resizeBase(fitted);
1063
+ }
1064
+ setJointType(part, value) {
1065
+ if (!this.draft)
1066
+ return;
1067
+ /* 붙은 부품을 함께 준다 — 새 관절이 둘이 맞닿는 자리에 서려면 그쪽 치수를 알아야 한다. */
1068
+ const parent = part.parent ? this.parts.find(one => one.name === part.parent) : undefined;
1069
+ this.tellDraft(setJointType(this.draft, part, value === '' ? undefined : value, parent ? partToFigure(parent, this.draft) : undefined));
1070
+ }
1071
+ setJointAxis(name, key) {
1072
+ const choice = JOINT_AXES.find(one => one.key === key);
1073
+ if (!choice || !this.draft)
1074
+ return;
1075
+ this.tellDraft(patchJoint(this.draft, name, { axis: { ...choice.axis } }));
1076
+ }
1077
+ tellDraft(draft) {
1078
+ this.dispatchEvent(new CustomEvent('update-draft', { detail: { draft }, bubbles: true, composed: true }));
1079
+ }
858
1080
  /**
859
1081
  * 역할 하나를 켜고 끈다.
860
1082
  *
@@ -971,6 +1193,27 @@ let FigureInspector = class FigureInspector extends localize(i18next)(LitElement
971
1193
  ${this.helpHint('figure.text.placement-explained')}
972
1194
  </div>
973
1195
 
1196
+ <!--
1197
+ 기준 상자 — **고치는 길이 화면에 있어야 한다.** 부품이 이 상자를 넘으면 발행이 막히고,
1198
+ 고치는 방법은 상자 숫자를 키우는 것이다(ADR-0044 ④). 그 칸이 없어서 저작자는 넘쳤다는
1199
+ 말만 듣고 고칠 자리를 못 찾았다.
1200
+ -->
1201
+ <div field>
1202
+ <label>${i18next.t('figure.label.base-box')}${this.helpButton('figure.text.base-box-explained')}</label>
1203
+ <div triple>
1204
+ ${this.number('X', this.draft?.width ?? 0, value => this.resizeBase({ width: value }), 1)}
1205
+ ${this.number('Y', this.draft?.depth ?? 0, value => this.resizeBase({ depth: value }), 1)}
1206
+ ${this.number('Z', this.draft?.height ?? 0, value => this.resizeBase({ height: value }), 1)}
1207
+ </div>
1208
+ ${this.parts.length > 0
1209
+ ? html `<button fit @click=${() => this.fitBaseToParts()} title=${i18next.t('figure.text.fit-base-explained')}>
1210
+ <md-icon>fit_screen</md-icon>${i18next.t('figure.button.fit-base-to-parts')}
1211
+ </button>`
1212
+ : nothing}
1213
+ ${this.renderBaseContract()}
1214
+ ${this.helpHint('figure.text.base-box-explained')}
1215
+ </div>
1216
+
974
1217
  <div inline-field>
975
1218
  <label>${i18next.t('figure.label.category')}${this.helpButton('figure.text.category-explained')}</label>
976
1219
  <input
@@ -1816,6 +2059,14 @@ __decorate([
1816
2059
  property({ type: Array }),
1817
2060
  __metadata("design:type", Array)
1818
2061
  ], FigureInspector.prototype, "parts", void 0);
2062
+ __decorate([
2063
+ property({ type: Array }),
2064
+ __metadata("design:type", Array)
2065
+ ], FigureInspector.prototype, "contract", void 0);
2066
+ __decorate([
2067
+ property({ type: String }),
2068
+ __metadata("design:type", String)
2069
+ ], FigureInspector.prototype, "contractFailure", void 0);
1819
2070
  __decorate([
1820
2071
  property({ type: Number }),
1821
2072
  __metadata("design:type", Object)