@things-factory/figure-ui 10.1.48 → 10.1.50

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 (104) hide show
  1. package/client/graphql/v3-store.ts +2 -1
  2. package/client/modeller/figure-history-panel.ts +9 -1
  3. package/client/modeller/figure-v3-first-save.ts +98 -0
  4. package/client/modeller/figure-v3-links.ts +89 -26
  5. package/client/modeller/figure-v3-parts.ts +86 -10
  6. package/client/modeller/figure-v3-properties.ts +2 -1
  7. package/client/modeller/side-panel-styles.ts +8 -0
  8. package/client/modeller/v3-canvas.ts +86 -12
  9. package/client/modeller/v3-editor.ts +244 -37
  10. package/client/modeller/v3-fields.ts +15 -1
  11. package/client/modeller/v3-first-save.ts +34 -0
  12. package/client/modeller/v3-gizmo.ts +52 -3
  13. package/client/modeller/v3-joint-drag.ts +116 -0
  14. package/client/modeller/v3-joint-fan.ts +24 -11
  15. package/client/modeller/v3-keys.ts +12 -1
  16. package/client/modeller/v3-links.ts +72 -5
  17. package/client/modeller/v3-live.ts +11 -0
  18. package/client/modeller/v3-product-session.ts +4 -2
  19. package/client/modeller/v3-refusals.ts +21 -0
  20. package/client/modeller/v3-stage-model.ts +24 -0
  21. package/client/pages/figure-v3-modeller-page.ts +46 -7
  22. package/dist-client/graphql/v3-store.js +2 -1
  23. package/dist-client/graphql/v3-store.js.map +1 -1
  24. package/dist-client/modeller/figure-history-panel.js +9 -1
  25. package/dist-client/modeller/figure-history-panel.js.map +1 -1
  26. package/dist-client/modeller/figure-v3-first-save.d.ts +20 -0
  27. package/dist-client/modeller/figure-v3-first-save.js +118 -0
  28. package/dist-client/modeller/figure-v3-first-save.js.map +1 -0
  29. package/dist-client/modeller/figure-v3-links.d.ts +5 -0
  30. package/dist-client/modeller/figure-v3-links.js +81 -23
  31. package/dist-client/modeller/figure-v3-links.js.map +1 -1
  32. package/dist-client/modeller/figure-v3-parts.d.ts +13 -0
  33. package/dist-client/modeller/figure-v3-parts.js +81 -10
  34. package/dist-client/modeller/figure-v3-parts.js.map +1 -1
  35. package/dist-client/modeller/figure-v3-properties.js +2 -1
  36. package/dist-client/modeller/figure-v3-properties.js.map +1 -1
  37. package/dist-client/modeller/side-panel-styles.js +8 -0
  38. package/dist-client/modeller/side-panel-styles.js.map +1 -1
  39. package/dist-client/modeller/v3-canvas.d.ts +13 -0
  40. package/dist-client/modeller/v3-canvas.js +88 -12
  41. package/dist-client/modeller/v3-canvas.js.map +1 -1
  42. package/dist-client/modeller/v3-clips.d.ts +1 -1
  43. package/dist-client/modeller/v3-editor.d.ts +32 -1
  44. package/dist-client/modeller/v3-editor.js +245 -35
  45. package/dist-client/modeller/v3-editor.js.map +1 -1
  46. package/dist-client/modeller/v3-fields.d.ts +6 -0
  47. package/dist-client/modeller/v3-fields.js +14 -2
  48. package/dist-client/modeller/v3-fields.js.map +1 -1
  49. package/dist-client/modeller/v3-first-save.d.ts +17 -0
  50. package/dist-client/modeller/v3-first-save.js +27 -0
  51. package/dist-client/modeller/v3-first-save.js.map +1 -0
  52. package/dist-client/modeller/v3-gizmo.d.ts +18 -0
  53. package/dist-client/modeller/v3-gizmo.js +45 -2
  54. package/dist-client/modeller/v3-gizmo.js.map +1 -1
  55. package/dist-client/modeller/v3-joint-drag.d.ts +19 -0
  56. package/dist-client/modeller/v3-joint-drag.js +97 -0
  57. package/dist-client/modeller/v3-joint-drag.js.map +1 -0
  58. package/dist-client/modeller/v3-joint-fan.js +24 -11
  59. package/dist-client/modeller/v3-joint-fan.js.map +1 -1
  60. package/dist-client/modeller/v3-keys.d.ts +9 -1
  61. package/dist-client/modeller/v3-keys.js +3 -0
  62. package/dist-client/modeller/v3-keys.js.map +1 -1
  63. package/dist-client/modeller/v3-links.d.ts +45 -2
  64. package/dist-client/modeller/v3-links.js +52 -3
  65. package/dist-client/modeller/v3-links.js.map +1 -1
  66. package/dist-client/modeller/v3-live.d.ts +6 -0
  67. package/dist-client/modeller/v3-live.js +10 -0
  68. package/dist-client/modeller/v3-live.js.map +1 -1
  69. package/dist-client/modeller/v3-product-session.d.ts +4 -1
  70. package/dist-client/modeller/v3-product-session.js +2 -2
  71. package/dist-client/modeller/v3-product-session.js.map +1 -1
  72. package/dist-client/modeller/v3-refusals.d.ts +4 -0
  73. package/dist-client/modeller/v3-refusals.js +21 -0
  74. package/dist-client/modeller/v3-refusals.js.map +1 -0
  75. package/dist-client/modeller/v3-stage-model.d.ts +8 -0
  76. package/dist-client/modeller/v3-stage-model.js +26 -0
  77. package/dist-client/modeller/v3-stage-model.js.map +1 -1
  78. package/dist-client/pages/figure-v3-modeller-page.d.ts +4 -0
  79. package/dist-client/pages/figure-v3-modeller-page.js +45 -7
  80. package/dist-client/pages/figure-v3-modeller-page.js.map +1 -1
  81. package/dist-client/tsconfig.tsbuildinfo +1 -1
  82. package/package.json +7 -5
  83. package/test/browser/build.mjs +62 -0
  84. package/test/browser/modeller-page.js +198 -0
  85. package/test/browser/modeller.check.mjs +334 -0
  86. package/test/browser/serve.mjs +43 -0
  87. package/test/joints.ts +33 -0
  88. package/test/v3-clips.test.ts +3 -2
  89. package/test/v3-controls.test.ts +3 -2
  90. package/test/v3-fields.test.ts +38 -1
  91. package/test/v3-first-save.test.ts +75 -0
  92. package/test/v3-gizmo.test.ts +40 -5
  93. package/test/v3-joint-drag.test.ts +93 -0
  94. package/test/v3-joint-fan.test.ts +16 -6
  95. package/test/v3-keys.test.ts +15 -2
  96. package/test/v3-links.test.ts +62 -14
  97. package/test/v3-live.test.ts +23 -4
  98. package/test/v3-maturity.test.ts +3 -7
  99. package/test/v3-refusals.test.ts +28 -0
  100. package/translations/en.json +43 -5
  101. package/translations/ja.json +42 -4
  102. package/translations/ko.json +42 -4
  103. package/translations/ms.json +42 -4
  104. package/translations/zh.json +42 -4
@@ -1 +1 @@
1
- {"version":3,"file":"v3-store.js","sourceRoot":"","sources":["../../client/graphql/v3-store.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,aAAa,CAAA;AAC7B,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAGxC,gGAAgG;AAChG,MAAM,CAAC,MAAM,cAAc,GAAmB;IAC5C,KAAK,CAAC,IAAI,CAAC,EAAE;QACX,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,EAAE,CAAC,CAAA;QACpC,IAAI,CAAC,MAAM,EAAE,MAAM;YAAE,MAAM,KAAK,CAAC,kBAAkB,CAAC,CAAA;QACpD,OAAO,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAA;IACjD,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,OAAO;QAChB,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC;YACnC,QAAQ,EAAE,GAAG,CAAA;;;;;;;;;;;;;OAaZ;YACD,SAAS,EAAE,OAAO;SACnB,CAAC,CAAA;QACF,IAAI,QAAQ,CAAC,MAAM,EAAE,MAAM;YAAE,MAAM,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAA;QACxF,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,EAAE,YAA2C,CAAA;QACzE,IAAI,CAAC,MAAM,EAAE,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM;YAAE,MAAM,KAAK,CAAC,wBAAwB,CAAC,CAAA;QACxE,OAAO,MAAM,CAAA;IACf,CAAC;CACF,CAAA","sourcesContent":["import gql from 'graphql-tag'\nimport { client } from '@operato/graphql'\nimport { fetchFigure } from './index.js'\nimport type { V3ProductStore, V3ProductRecord } from '../modeller/v3-product-session.js'\n\n/** V3 source is sent intact. No conversion into the old parts/base authoring representation. */\nexport const v3ProductStore: V3ProductStore = {\n async load(id) {\n const record = await fetchFigure(id)\n if (!record?.source) throw Error('Figure 정본이 없습니다.')\n return { id: record.id, source: record.source }\n },\n async save(request) {\n const response = await client.mutate({\n mutation: gql`\n mutation SaveFigureV3($source: String!, $id: String, $expectedSource: String, $creationId: String, $thumbnail: String) {\n saveFigureV3(\n source: $source\n id: $id\n expectedSource: $expectedSource\n creationId: $creationId\n thumbnail: $thumbnail\n ) {\n id\n source\n }\n }\n `,\n variables: request\n })\n if (response.errors?.length) throw Error(response.errors.map(e => e.message).join('\\n'))\n const result = response.data?.saveFigureV3 as V3ProductRecord | undefined\n if (!result?.id || !result.source) throw Error('서버가 저장 결과를 돌려주지 않았습니다.')\n return result\n }\n}\n"]}
1
+ {"version":3,"file":"v3-store.js","sourceRoot":"","sources":["../../client/graphql/v3-store.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,aAAa,CAAA;AAC7B,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAGxC,gGAAgG;AAChG,MAAM,CAAC,MAAM,cAAc,GAAmB;IAC5C,KAAK,CAAC,IAAI,CAAC,EAAE;QACX,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,EAAE,CAAC,CAAA;QACpC,IAAI,CAAC,MAAM,EAAE,MAAM;YAAE,MAAM,KAAK,CAAC,kBAAkB,CAAC,CAAA;QACpD,OAAO,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAA;IACjD,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,OAAO;QAChB,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC;YACnC,QAAQ,EAAE,GAAG,CAAA;;;;;;;;;;;;;;OAcZ;YACD,SAAS,EAAE,OAAO;SACnB,CAAC,CAAA;QACF,IAAI,QAAQ,CAAC,MAAM,EAAE,MAAM;YAAE,MAAM,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAA;QACxF,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,EAAE,YAA2C,CAAA;QACzE,IAAI,CAAC,MAAM,EAAE,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM;YAAE,MAAM,KAAK,CAAC,wBAAwB,CAAC,CAAA;QACxE,OAAO,MAAM,CAAA;IACf,CAAC;CACF,CAAA","sourcesContent":["import gql from 'graphql-tag'\nimport { client } from '@operato/graphql'\nimport { fetchFigure } from './index.js'\nimport type { V3ProductStore, V3ProductRecord } from '../modeller/v3-product-session.js'\n\n/** V3 source is sent intact. No conversion into the old parts/base authoring representation. */\nexport const v3ProductStore: V3ProductStore = {\n async load(id) {\n const record = await fetchFigure(id)\n if (!record?.source) throw Error('Figure 정본이 없습니다.')\n return { id: record.id, source: record.source }\n },\n async save(request) {\n const response = await client.mutate({\n mutation: gql`\n mutation SaveFigureV3($source: String!, $id: String, $expectedSource: String, $creationId: String, $thumbnail: String, $type: String) {\n saveFigureV3(\n source: $source\n id: $id\n expectedSource: $expectedSource\n creationId: $creationId\n thumbnail: $thumbnail\n type: $type\n ) {\n id\n source\n }\n }\n `,\n variables: request\n })\n if (response.errors?.length) throw Error(response.errors.map(e => e.message).join('\\n'))\n const result = response.data?.saveFigureV3 as V3ProductRecord | undefined\n if (!result?.id || !result.source) throw Error('서버가 저장 결과를 돌려주지 않았습니다.')\n return result\n }\n}\n"]}
@@ -6,6 +6,7 @@ import { customElement, property, state } from 'lit/decorators.js';
6
6
  import { i18next, localize } from '@operato/i18n';
7
7
  import { ScrollbarStyles } from '@operato/styles';
8
8
  import { fetchFigureVersions, inspectFigure, releaseFigure, revertFigureVersion } from '../graphql/index.js';
9
+ import { OxPrompt } from '@operato/popup';
9
10
  /**
10
11
  * 이력 — **발행하는 자리이자, 발행된 판들을 보는 자리.**
11
12
  *
@@ -521,7 +522,14 @@ let FigureHistoryPanel = class FigureHistoryPanel extends localize(i18next)(LitE
521
522
  }
522
523
  async revert(version) {
523
524
  /* 되돌아갈 수 없는 조작은 아니지만(판본은 남는다), 지금 화면의 것이 덮인다 — 이름으로 확인한다. */
524
- if (!confirm(i18next.t('figure.text.revert-to-version-confirm', { version }))) {
525
+ const agreed = await OxPrompt.open({
526
+ type: 'warning',
527
+ title: i18next.t('figure.button.revert'),
528
+ text: i18next.t('figure.text.revert-to-version-confirm', { version }),
529
+ confirmButton: { text: i18next.t('figure.button.revert') },
530
+ cancelButton: { text: i18next.t('figure.button.keep-editing') }
531
+ });
532
+ if (!agreed) {
525
533
  return;
526
534
  }
527
535
  this.busy = true;
@@ -1 +1 @@
1
- {"version":3,"file":"figure-history-panel.js","sourceRoot":"","sources":["../../client/modeller/figure-history-panel.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\n\nimport { gateFindingStyles, renderGateFinding } from './gate-findings.js'\nimport { css, html, LitElement, nothing } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport { ScrollbarStyles } from '@operato/styles'\n\nimport { fetchFigureVersions, inspectFigure, releaseFigure, revertFigureVersion } from '../graphql/index.js'\nimport type { FigureGateFinding, FigureGateFix, FigureGateFixPlan, FigureInspection, FigureVersion } from '../types.js'\n\n/**\n * 이력 — **발행하는 자리이자, 발행된 판들을 보는 자리.**\n *\n * ## 왜 저장 옆이 아니라 여기인가\n *\n * 저장은 하루에 수십 번이고 발행은 드물다. 두 단추를 나란히 두면 저장하려다 발행을 누른다.\n * 그리고 발행은 **소비처에게 하는 약속**이라, 무엇을 약속하는지(지금 몇 판인가 · 저장 안 한 것이\n * 있나 · 지난 판이 무엇이었나)를 함께 보여 주는 자리에 있어야 한다.\n *\n * ## 저장하지 않은 것이 있으면 발행하지 않는다\n *\n * 발행은 **저장된 것**을 내보낸다. 화면에 고친 것이 남아 있는 채로 발행하면, 저작자가 보고 있는\n * 것과 약속한 것이 다르다. 그래서 막고, 왜 막혔는지 적는다.\n *\n * ## 되돌리기는 초안으로 돌아온다\n *\n * 옛 판의 정본·속성·그림이 돌아오고 이름·설명은 그대로다. 되살렸다고 그 판이 다시 발행되지는\n * 않는다 — 다시 내보내려면 발행을 한 번 더 하고, 그때 번호는 이미 쓴 다음 번호를 받는다.\n */\n@customElement('figure-history-panel')\nexport class FigureHistoryPanel extends localize(i18next)(LitElement) {\n static styles = [\n gateFindingStyles,\n ScrollbarStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n min-height: 0;\n overflow-y: auto;\n background-color: var(--md-sys-color-surface-container-lowest);\n color: var(--md-sys-color-on-surface);\n font: var(--label-font, inherit);\n font-size: 0.76rem;\n }\n\n :host([off]) {\n display: none;\n }\n\n section {\n padding: var(--spacing-medium, 8px) var(--spacing-large, 12px);\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n }\n\n h3 {\n margin: 0 0 6px 0;\n font-size: var(--ui-t-micro, 11px);\n font-weight: 700;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n\n /* 지금 무엇인가 — 발행을 누르기 전에 읽는 한 줄 */\n div[now] {\n display: flex;\n align-items: center;\n gap: 6px;\n margin-bottom: 8px;\n }\n\n span[state] {\n padding: 2px 8px;\n border-radius: 10px;\n font-size: 0.7rem;\n font-weight: 700;\n color: var(--md-sys-color-on-secondary-container);\n background-color: var(--md-sys-color-secondary-container);\n }\n\n span[state][released] {\n color: var(--md-sys-color-on-primary);\n background-color: var(--md-sys-color-primary);\n }\n\n span[ver] {\n font-family: var(--mono-font, monospace);\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n\n input,\n button {\n font: var(--input-field-font, inherit);\n font-size: 0.76rem;\n }\n\n input[note] {\n width: 100%;\n box-sizing: border-box;\n padding: 5px 8px;\n color: var(--md-sys-color-on-surface);\n background-color: var(--md-sys-color-surface-container-lowest);\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n }\n\n button[release] {\n width: 100%;\n margin-top: 6px;\n padding: 6px 0;\n font-weight: 700;\n color: var(--ui-on-accent, #fff);\n background-color: var(--md-sys-color-primary);\n border: 0;\n border-radius: 6px;\n cursor: pointer;\n }\n\n button[release][disabled] {\n opacity: 0.45;\n cursor: default;\n }\n\n /* 왜 못 누르는지 — 눌러 보고 알게 하지 않는다 */\n p[why] {\n margin: 6px 0 0 0;\n font-size: 0.7rem;\n line-height: 1.5;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n\n p[failed] {\n margin: 6px 0 0 0;\n font-size: 0.7rem;\n line-height: 1.5;\n color: var(--md-sys-color-error);\n }\n\n ul {\n margin: 0;\n padding: 0;\n list-style: none;\n }\n\n li {\n display: grid;\n grid-template-columns: auto 1fr auto;\n gap: 4px 8px;\n align-items: baseline;\n padding: 7px 0;\n border-bottom: 1px dashed var(--md-sys-color-outline-variant);\n }\n\n li:last-child {\n border-bottom: 0;\n }\n\n li span[no] {\n font-family: var(--mono-font, monospace);\n font-weight: 700;\n }\n\n li span[when] {\n grid-column: 2 / 4;\n font-size: 0.7rem;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n\n li span[comment] {\n grid-column: 1 / 4;\n line-height: 1.5;\n }\n\n li button[revert] {\n padding: 2px 8px;\n color: var(--md-sys-color-on-surface);\n background: none;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n cursor: pointer;\n }\n\n li button[revert]:hover {\n color: var(--md-sys-color-on-primary-container);\n background-color: var(--md-sys-color-primary-container);\n border-color: transparent;\n }\n\n /*\n 게이트 사유. 막는 것은 색으로도 말하고, 알리는 것은 조용히 둔다 — 둘을 같은 톤으로 두면\n 「이 중 어느 것이 발행을 막았나」를 사람이 다시 세어야 한다.\n */\n\n /* 설명으로 끝내지 않는다. 이 단추는 원본을 바꾸지 않고 모델러에 후보만 연다. */\n li[finding] button[preview-fix] {\n margin-top: 3px;\n padding: 4px 7px;\n font-weight: 700;\n color: var(--md-sys-color-on-primary-container, var(--md-sys-color-on-surface));\n background-color: var(--md-sys-color-primary-container, transparent);\n border: 0;\n border-radius: 5px;\n cursor: pointer;\n }\n\n li[finding] button[preview-fix]:hover {\n filter: brightness(1.08);\n }\n\n li[finding] button[apply-fix] {\n margin-top: 3px;\n padding: 4px 7px;\n font-weight: 700;\n color: var(--ui-on-accent, #fff);\n background-color: var(--md-sys-color-primary);\n border: 0;\n border-radius: 5px;\n cursor: pointer;\n }\n\n div[ratio-choice] {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n margin-top: 4px;\n }\n\n div[ratio-choice] button:not([apply-fix]) {\n padding: 4px 7px;\n color: var(--md-sys-color-on-surface);\n background: transparent;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 5px;\n cursor: pointer;\n }\n\n div[ratio-choice] button[selected] {\n color: var(--md-sys-color-on-primary-container);\n background: var(--md-sys-color-primary-container);\n border-color: transparent;\n }\n\n p[quiet] {\n margin: 0;\n line-height: 1.6;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n `\n ]\n\n /** 지금 열려 있는 도형. 없으면(새로 만드는 중) 발행할 것이 없다. */\n @property({ type: String }) figureId?: string\n @property({ type: String }) figureState?: 'draft' | 'released'\n @property({ type: Number }) figureVersion = 0\n /** 저장하지 않은 것이 있나 — 있으면 발행을 막는다. */\n @property({ type: Boolean }) dirty = false\n /**\n * 열린 도형의 형식. 발행 · 판본 · 복원은 figure id 로만 서버를 묻고 서버의 한 문이 두 형식을 다 읽으므로 두 화면이\n * 이 패널을 함께 쓴다. 갈리는 것은 조치 단추 하나다 — 부품을 옮기는 두 조치는 형식마다 다른 편집이라, 받는\n * 화면이 없는 단추를 그리면 눌러도 아무 일이 없다.\n */\n @property({ type: String }) format: 'v2' | 'v3' = 'v2'\n /**\n * 행의 타입. 판정 하나(`type-is-placeholder`)가 이것을 본다 — V3 자산은 타입을 원본에 적지 않아서, 타입을 바꾸면\n * 원본도 dirty 도 그대로다. 그래서 이 값이 바뀌면 판정을 다시 묻는다. 안 그러면 타입을 정한 뒤에도 「임시 타입」\n * 판정이 남아 발행 단추가 잠긴 채다.\n */\n @property({ type: String }) figureType?: string\n /** 직전 발행판의 기준 상자. 외형 변화 안내에서 X·Z 복원 선택지에만 쓴다. */\n @property({ attribute: false }) releasedBase?: { x: number; y: number; z: number }\n\n @state() private versions: FigureVersion[] = []\n @state() private busy = false\n @state() private failure = ''\n @state() private keepChangedAppearance = false\n /**\n * 발행 판정. **저장된 정본에 대한 것**이라 저장할 때마다 다시 묻는다(`figureVersion` 이 아니라\n * `dirty` 가 풀리는 순간이 그때다).\n */\n @state() private inspection?: FigureInspection\n\n updated(changes: Map<string, unknown>) {\n if (changes.has('figureId') || changes.has('figureVersion')) {\n void this.reload()\n }\n\n /*\n 저장이 끝난 순간(dirty 가 풀림) 정본이 바뀌었으니 판정을 다시 묻는다. 저작 중(dirty)에는\n 묻지 않는다 — 서버에 있는 것은 저장 전 정본이라, 그 답은 화면에 있는 것과 다른 것을 말한다.\n */\n if (changes.has('figureId') || changes.has('dirty') || changes.has('figureType')) {\n void this.inspect()\n }\n }\n\n private async inspect() {\n this.keepChangedAppearance = false\n if (!this.figureId || this.dirty) {\n this.inspection = undefined\n return\n }\n\n try {\n this.inspection = await inspectFigure(this.figureId)\n } catch (e) {\n /* 판정을 못 받은 것을 「통과」로 두지 않는다 — 모르는 것이다. 단추는 서버가 막는다. */\n this.inspection = undefined\n this.failure = (e as Error).message\n }\n }\n\n private async reload() {\n if (!this.figureId) {\n this.versions = []\n return\n }\n\n try {\n this.versions = await fetchFigureVersions(this.figureId)\n this.failure = ''\n } catch (e) {\n this.failure = (e as Error).message\n }\n }\n\n render() {\n return html`\n <section>\n <h3>${i18next.t('figure.label.release')}</h3>\n ${this.figureId ? this.renderRelease() : html`<p quiet>${i18next.t('figure.text.save-the-figure-before-releasing')}</p>`}\n </section>\n\n <section>\n <h3>${i18next.t('figure.label.released-versions')}</h3>\n ${this.versions.length\n ? html`<ul>\n ${this.versions.map(version => this.renderVersion(version))}\n </ul>`\n : html`<p quiet>${i18next.t('figure.text.nothing-released-yet')}</p>`}\n </section>\n `\n }\n\n private renderRelease() {\n const released = this.figureState === 'released'\n\n /*\n 게이트가 막는 것과 알리는 것을 함께 낸다. **막는 것은 단추를 잠근다** — 눌러 보고 서버에게\n 거절당하는 것으로 알게 하지 않는다. 알리는 것은 잠그지 않고 그대로 보여 준다.\n */\n const blocking = (this.inspection?.findings ?? []).filter(finding => finding.blocking)\n const advisory = (this.inspection?.findings ?? []).filter(finding => !finding.blocking)\n const appearanceChoiceNeeded = advisory.some(finding => finding.code === 'base-ratio-changed') && !this.keepChangedAppearance\n\n /*\n ── 「발행되나」에 **한 번만** 답한다 ─────────────────────────────────────\n 전에는 막는 목록의 머리가 「이것들이 발행을 막습니다」, 알리는 목록의 머리가 「발행은 되지만\n 확인해 보십시오」였다. 두 줄이 나란히 서서 화면이 「막힌다」와 「된다」를 함께 말했고, 사람이\n 「그래서 되는 건가」를 되물었다.\n\n 답은 이 한 줄이 하고, 아래 목록들은 **이유**일 뿐이다 — 그래서 목록 머리는 문장이 아니라\n 이름씨다(「고쳐야 하는 것」·「막지는 않는 것」).\n */\n const why = this.dirty\n ? i18next.t('figure.text.release-needs-a-save-first')\n : released\n ? i18next.t('figure.text.already-released-edit-to-release-again')\n : appearanceChoiceNeeded\n ? i18next.t('figure.text.choose-appearance-change')\n : blocking.length\n ? i18next.t('figure.text.cannot-release-now')\n : ''\n\n return html`\n <div now>\n <span state ?released=${released}>${i18next.t(released ? 'figure.label.figure-released' : 'figure.label.draft')}</span>\n <span ver>${i18next.t('figure.text.version-number', { version: this.figureVersion ?? 0 })}</span>\n </div>\n\n <input note placeholder=${i18next.t('figure.text.what-changed-in-this-release')} ?disabled=${!!why || this.busy} />\n\n <button release ?disabled=${!!why || this.busy} @click=${() => this.release()}>\n ${this.busy ? i18next.t('figure.text.releasing') : i18next.t('figure.button.release-figure')}\n </button>\n\n ${why ? html`<p why>${why}</p>` : nothing}\n ${blocking.length\n ? html`<ul gate>\n <li head>${i18next.t('figure.text.must-fix-to-release')}</li>\n ${blocking.map(finding => this.renderFinding(finding, true))}\n </ul>`\n : nothing}\n ${advisory.length\n ? html`<ul gate>\n <li head quiet>${i18next.t('figure.text.does-not-block')}</li>\n ${advisory.map(finding => this.renderFinding(finding, false))}\n </ul>`\n : nothing}\n ${this.failure ? html`<p failed>${this.failure}</p>` : nothing}\n `\n }\n\n /**\n * 사유 한 줄 — **잰 것 · 왜 · 어떻게** 셋을 함께 말한다.\n *\n * 잰 것만 보여 줬더니 「1 조각이 2 조각으로 갈라진다」에서 사람이 멈췄다. 왜 나쁜지도, 무엇을\n * 바꾸면 되는지도 알 수 없었다. 그 셋은 형식(`figure-model` 의 gate)이 만들고 여기서는 자리만\n * 준다 — 화면이 문장을 지으면 서버가 거절할 때와 다른 말을 하게 된다.\n */\n private renderFinding(finding: FigureGateFinding, blocking: boolean) {\n return renderGateFinding(finding, blocking, html`\n ${this.offersFit(finding.code)\n ? html`<button fit-base @click=${() => this.fitBase()}>\n ${i18next.t('figure.button.fit-base-to-parts')}\n </button>`\n : nothing}\n ${finding.code === 'parts-off-placement-face' && this.format === 'v2'\n ? html`<button fit-base @click=${() => this.settleOnFloor()}>\n ${i18next.t('figure.button.settle-on-floor')}\n </button>`\n : nothing}\n ${finding.code === 'base-ratio-changed' && this.releasedBase\n ? html`<div ratio-choice>\n <button apply-fix @click=${() => this.restoreReleasedFootprint()}>\n ${i18next.t('figure.button.restore-released-base')}\n </button>\n <button ?selected=${this.keepChangedAppearance} @click=${() => this.keepChangedFootprint()}>\n ${i18next.t('figure.button.keep-changed-appearance')}\n </button>\n </div>\n <span how-line><md-icon>info</md-icon>${i18next.t('figure.text.appearance-choice-explained')}</span>`\n : nothing}\n ${(finding.fixes ?? []).map(fix => html`\n ${fix.safe ? html`\n <button apply-fix @click=${() => this.applyFix(fix)}>\n ${i18next.t('figure.button.apply-fix', { part: fix.part, axis: fix.axis.toUpperCase(), from: fix.from ?? 'auto', to: fix.to })}\n </button>\n ` : nothing}\n <button preview-fix @click=${() => this.previewFix(fix)}>\n ${i18next.t('figure.button.preview-fix', { part: fix.part, axis: fix.axis.toUpperCase(), from: fix.from ?? 'auto', to: fix.to })}\n </button>\n `)}\n ${finding.fixPlan?.safe ? html`\n <span how-line><md-icon>auto_fix_high</md-icon>${i18next.t('figure.text.recommended-fix-explained', { n: finding.fixPlan.fixes.length })}</span>\n <button apply-fix @click=${() => this.applyFixPlan(finding.fixPlan!)}>\n ${i18next.t('figure.button.apply-recommended-fix')}\n </button>\n ` : nothing}\n `)\n }\n\n /**\n * 게이트는 수정을 저장하지 않는다. 부모 모델러가 현재 정본과 이 한 수정안을 나란히 렌더하고,\n * 저작자가 '변경 받기'를 눌렀을 때에만 자기 작업 사본에 적용한다.\n */\n private previewFix(fix: FigureGateFix) {\n this.dispatchEvent(new CustomEvent('preview-sizing-fix', { detail: fix, bubbles: true, composed: true }))\n }\n\n /** 서버가 전수 재검사해 모든 차단 split이 사라진 후보만 작업 사본에 원클릭 적용한다. 저장은 하지 않는다. */\n private applyFix(fix: FigureGateFix) {\n if (!fix.safe) return\n this.dispatchEvent(new CustomEvent('apply-sizing-fix', { detail: fix, bubbles: true, composed: true }))\n }\n\n /** Apply the server-remeasured coordinated repair to the draft only; save remains explicit. */\n private applyFixPlan(plan: FigureGateFixPlan) {\n if (!plan.safe || plan.fixes.length === 0) return\n this.dispatchEvent(new CustomEvent('apply-sizing-fix-plan', { detail: plan, bubbles: true, composed: true }))\n }\n\n /**\n * 넘친 것을 담는 길 하나를 여기서도 내민다 — **같은 동작이 속성 패널에도 있다**(「부품에 맞추기」).\n *\n * 그 단추를 아는 저작자는 거기서 누른다. 발행이 막혔다는 말을 여기서 읽은 사람은 그 단추가 있는\n * 것을 모르고, 문구는 그것을 가리킬 수 없다 — 문장은 형식이 쓰고 형식은 화면의 단추를 모른다.\n * 그래서 화면이 자기 자리에서 같은 동작을 낸다. 누르면 상자만 커지고 부품은 제자리이며, 저장은\n * 여전히 저작자가 따로 한다.\n */\n /**\n * 「부품에 맞추기」를 내미는 판정. V2 는 기준 상자를 부품에 맞추고, V3 는 공간을 부품에서 다시 선언한다\n * (`declare-occupancy`) — 둘 다 부품은 그대로 두고 선언만 바꾼다. V3 에서 공간을 아직 선언하지 않은 도형도\n * 같은 단추로 선언한다: 새로 만든 V3 도형은 전부 그 판정으로 막히고, 화면에 다른 길이 없다.\n *\n * V3 의 장착면이 떠 있는 것은 여기 없다. 부품 하나를 평면에 붙이면 그 부품에 붙지 않은 것은 따라오지 않아\n * V2 의 「바닥에 앉히기」와 뜻이 다르다. 판정의 「어떻게」가 할 일을 말한다.\n */\n private offersFit(code: string): boolean {\n return this.format === 'v3'\n ? code === 'part-outside-occupancy' || code === 'occupancy-undeclared'\n : code === 'part-outside-base'\n }\n\n private fitBase() {\n this.dispatchEvent(new CustomEvent('fit-base-to-parts', { bubbles: true, composed: true }))\n }\n\n /**\n * 떠 있는 것을 앉힌다. 부품 하나를 고르고 「바닥에」를 누르는 길은 전부터 있었으나, 그 길은\n * 고른 것 하나만 옮기고 **여기서는 닿지 않는다.** 막혔다는 말을 읽은 자리에서 조치가 되게 한다.\n */\n private settleOnFloor() {\n this.dispatchEvent(new CustomEvent('settle-parts-on-floor', { bubbles: true, composed: true }))\n }\n\n /** 기준 상자의 X·Z만 직전 발행값으로 되돌린다. Y와 부품 편집은 보존한다. */\n private restoreReleasedFootprint() {\n if (!this.releasedBase) return\n this.dispatchEvent(new CustomEvent('restore-released-footprint', {\n detail: { x: this.releasedBase.x, z: this.releasedBase.z }, bubbles: true, composed: true\n }))\n }\n\n /** 현재 외형으로 발행하겠다는 명시적 선택이다. 저장·발행은 이어서 따로 한다. */\n private keepChangedFootprint() {\n this.keepChangedAppearance = true\n }\n\n private renderVersion(version: FigureVersion) {\n return html`\n <li>\n <span no>${i18next.t('figure.text.version-number', { version: version.version })}</span>\n <span when>\n ${version.updatedAt ? new Date(version.updatedAt).toLocaleString() : ''}\n ${version.updater?.name ? ` · ${version.updater.name}` : ''}\n </span>\n <button revert ?disabled=${this.busy} @click=${() => this.revert(version.version)}>\n ${i18next.t('figure.button.revert')}\n </button>\n ${version.comment ? html`<span comment>${version.comment}</span>` : nothing}\n </li>\n `\n }\n\n private async release() {\n const input = this.renderRoot.querySelector('input[note]') as HTMLInputElement\n const comment = input?.value?.trim()\n\n this.busy = true\n this.failure = ''\n\n try {\n const figure = await releaseFigure(this.figureId!, comment || undefined)\n if (input) input.value = ''\n /* 정본을 든 자리가 갱신한다 — 여기서 페이지 상태를 고쳐 쓰지 않는다. */\n this.dispatchEvent(new CustomEvent('figure-released', { detail: { figure }, bubbles: true, composed: true }))\n await this.reload()\n await this.inspect()\n } catch (e) {\n this.failure = (e as Error).message\n } finally {\n this.busy = false\n }\n }\n\n private async revert(version: number) {\n /* 되돌아갈 수 없는 조작은 아니지만(판본은 남는다), 지금 화면의 것이 덮인다 — 이름으로 확인한다. */\n if (!confirm(i18next.t('figure.text.revert-to-version-confirm', { version }))) {\n return\n }\n\n this.busy = true\n this.failure = ''\n\n try {\n const result = await revertFigureVersion(this.figureId!, version)\n this.dispatchEvent(new CustomEvent('figure-reverted', { detail: result, bubbles: true, composed: true }))\n await this.reload()\n /*\n **되돌리면 정본이 바뀌므로 판정도 바뀐다.** 이것을 안 하면 옛 판의 사유가 새 정본 위에\n 남아 화면이 거짓말한다 — 되돌리기는 `dirty` 도 `figureVersion` 도 건드리지 않으므로\n `updated` 의 감시로는 다시 묻지 않는다.\n */\n await this.inspect()\n } catch (e) {\n this.failure = (e as Error).message\n } finally {\n this.busy = false\n }\n }\n}\n"]}
1
+ {"version":3,"file":"figure-history-panel.js","sourceRoot":"","sources":["../../client/modeller/figure-history-panel.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\n\nimport { gateFindingStyles, renderGateFinding } from './gate-findings.js'\nimport { css, html, LitElement, nothing } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport { ScrollbarStyles } from '@operato/styles'\n\nimport { fetchFigureVersions, inspectFigure, releaseFigure, revertFigureVersion } from '../graphql/index.js'\nimport type { FigureGateFinding, FigureGateFix, FigureGateFixPlan, FigureInspection, FigureVersion } from '../types.js'\nimport { OxPrompt } from '@operato/popup'\n\n/**\n * 이력 — **발행하는 자리이자, 발행된 판들을 보는 자리.**\n *\n * ## 왜 저장 옆이 아니라 여기인가\n *\n * 저장은 하루에 수십 번이고 발행은 드물다. 두 단추를 나란히 두면 저장하려다 발행을 누른다.\n * 그리고 발행은 **소비처에게 하는 약속**이라, 무엇을 약속하는지(지금 몇 판인가 · 저장 안 한 것이\n * 있나 · 지난 판이 무엇이었나)를 함께 보여 주는 자리에 있어야 한다.\n *\n * ## 저장하지 않은 것이 있으면 발행하지 않는다\n *\n * 발행은 **저장된 것**을 내보낸다. 화면에 고친 것이 남아 있는 채로 발행하면, 저작자가 보고 있는\n * 것과 약속한 것이 다르다. 그래서 막고, 왜 막혔는지 적는다.\n *\n * ## 되돌리기는 초안으로 돌아온다\n *\n * 옛 판의 정본·속성·그림이 돌아오고 이름·설명은 그대로다. 되살렸다고 그 판이 다시 발행되지는\n * 않는다 — 다시 내보내려면 발행을 한 번 더 하고, 그때 번호는 이미 쓴 다음 번호를 받는다.\n */\n@customElement('figure-history-panel')\nexport class FigureHistoryPanel extends localize(i18next)(LitElement) {\n static styles = [\n gateFindingStyles,\n ScrollbarStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n min-height: 0;\n overflow-y: auto;\n background-color: var(--md-sys-color-surface-container-lowest);\n color: var(--md-sys-color-on-surface);\n font: var(--label-font, inherit);\n font-size: 0.76rem;\n }\n\n :host([off]) {\n display: none;\n }\n\n section {\n padding: var(--spacing-medium, 8px) var(--spacing-large, 12px);\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n }\n\n h3 {\n margin: 0 0 6px 0;\n font-size: var(--ui-t-micro, 11px);\n font-weight: 700;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n\n /* 지금 무엇인가 — 발행을 누르기 전에 읽는 한 줄 */\n div[now] {\n display: flex;\n align-items: center;\n gap: 6px;\n margin-bottom: 8px;\n }\n\n span[state] {\n padding: 2px 8px;\n border-radius: 10px;\n font-size: 0.7rem;\n font-weight: 700;\n color: var(--md-sys-color-on-secondary-container);\n background-color: var(--md-sys-color-secondary-container);\n }\n\n span[state][released] {\n color: var(--md-sys-color-on-primary);\n background-color: var(--md-sys-color-primary);\n }\n\n span[ver] {\n font-family: var(--mono-font, monospace);\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n\n input,\n button {\n font: var(--input-field-font, inherit);\n font-size: 0.76rem;\n }\n\n input[note] {\n width: 100%;\n box-sizing: border-box;\n padding: 5px 8px;\n color: var(--md-sys-color-on-surface);\n background-color: var(--md-sys-color-surface-container-lowest);\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n }\n\n button[release] {\n width: 100%;\n margin-top: 6px;\n padding: 6px 0;\n font-weight: 700;\n color: var(--ui-on-accent, #fff);\n background-color: var(--md-sys-color-primary);\n border: 0;\n border-radius: 6px;\n cursor: pointer;\n }\n\n button[release][disabled] {\n opacity: 0.45;\n cursor: default;\n }\n\n /* 왜 못 누르는지 — 눌러 보고 알게 하지 않는다 */\n p[why] {\n margin: 6px 0 0 0;\n font-size: 0.7rem;\n line-height: 1.5;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n\n p[failed] {\n margin: 6px 0 0 0;\n font-size: 0.7rem;\n line-height: 1.5;\n color: var(--md-sys-color-error);\n }\n\n ul {\n margin: 0;\n padding: 0;\n list-style: none;\n }\n\n li {\n display: grid;\n grid-template-columns: auto 1fr auto;\n gap: 4px 8px;\n align-items: baseline;\n padding: 7px 0;\n border-bottom: 1px dashed var(--md-sys-color-outline-variant);\n }\n\n li:last-child {\n border-bottom: 0;\n }\n\n li span[no] {\n font-family: var(--mono-font, monospace);\n font-weight: 700;\n }\n\n li span[when] {\n grid-column: 2 / 4;\n font-size: 0.7rem;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n\n li span[comment] {\n grid-column: 1 / 4;\n line-height: 1.5;\n }\n\n li button[revert] {\n padding: 2px 8px;\n color: var(--md-sys-color-on-surface);\n background: none;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n cursor: pointer;\n }\n\n li button[revert]:hover {\n color: var(--md-sys-color-on-primary-container);\n background-color: var(--md-sys-color-primary-container);\n border-color: transparent;\n }\n\n /*\n 게이트 사유. 막는 것은 색으로도 말하고, 알리는 것은 조용히 둔다 — 둘을 같은 톤으로 두면\n 「이 중 어느 것이 발행을 막았나」를 사람이 다시 세어야 한다.\n */\n\n /* 설명으로 끝내지 않는다. 이 단추는 원본을 바꾸지 않고 모델러에 후보만 연다. */\n li[finding] button[preview-fix] {\n margin-top: 3px;\n padding: 4px 7px;\n font-weight: 700;\n color: var(--md-sys-color-on-primary-container, var(--md-sys-color-on-surface));\n background-color: var(--md-sys-color-primary-container, transparent);\n border: 0;\n border-radius: 5px;\n cursor: pointer;\n }\n\n li[finding] button[preview-fix]:hover {\n filter: brightness(1.08);\n }\n\n li[finding] button[apply-fix] {\n margin-top: 3px;\n padding: 4px 7px;\n font-weight: 700;\n color: var(--ui-on-accent, #fff);\n background-color: var(--md-sys-color-primary);\n border: 0;\n border-radius: 5px;\n cursor: pointer;\n }\n\n div[ratio-choice] {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n margin-top: 4px;\n }\n\n div[ratio-choice] button:not([apply-fix]) {\n padding: 4px 7px;\n color: var(--md-sys-color-on-surface);\n background: transparent;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 5px;\n cursor: pointer;\n }\n\n div[ratio-choice] button[selected] {\n color: var(--md-sys-color-on-primary-container);\n background: var(--md-sys-color-primary-container);\n border-color: transparent;\n }\n\n p[quiet] {\n margin: 0;\n line-height: 1.6;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n `\n ]\n\n /** 지금 열려 있는 도형. 없으면(새로 만드는 중) 발행할 것이 없다. */\n @property({ type: String }) figureId?: string\n @property({ type: String }) figureState?: 'draft' | 'released'\n @property({ type: Number }) figureVersion = 0\n /** 저장하지 않은 것이 있나 — 있으면 발행을 막는다. */\n @property({ type: Boolean }) dirty = false\n /**\n * 열린 도형의 형식. 발행 · 판본 · 복원은 figure id 로만 서버를 묻고 서버의 한 문이 두 형식을 다 읽으므로 두 화면이\n * 이 패널을 함께 쓴다. 갈리는 것은 조치 단추 하나다 — 부품을 옮기는 두 조치는 형식마다 다른 편집이라, 받는\n * 화면이 없는 단추를 그리면 눌러도 아무 일이 없다.\n */\n @property({ type: String }) format: 'v2' | 'v3' = 'v2'\n /**\n * 행의 타입. 판정 하나(`type-is-placeholder`)가 이것을 본다 — V3 자산은 타입을 원본에 적지 않아서, 타입을 바꾸면\n * 원본도 dirty 도 그대로다. 그래서 이 값이 바뀌면 판정을 다시 묻는다. 안 그러면 타입을 정한 뒤에도 「임시 타입」\n * 판정이 남아 발행 단추가 잠긴 채다.\n */\n @property({ type: String }) figureType?: string\n /** 직전 발행판의 기준 상자. 외형 변화 안내에서 X·Z 복원 선택지에만 쓴다. */\n @property({ attribute: false }) releasedBase?: { x: number; y: number; z: number }\n\n @state() private versions: FigureVersion[] = []\n @state() private busy = false\n @state() private failure = ''\n @state() private keepChangedAppearance = false\n /**\n * 발행 판정. **저장된 정본에 대한 것**이라 저장할 때마다 다시 묻는다(`figureVersion` 이 아니라\n * `dirty` 가 풀리는 순간이 그때다).\n */\n @state() private inspection?: FigureInspection\n\n updated(changes: Map<string, unknown>) {\n if (changes.has('figureId') || changes.has('figureVersion')) {\n void this.reload()\n }\n\n /*\n 저장이 끝난 순간(dirty 가 풀림) 정본이 바뀌었으니 판정을 다시 묻는다. 저작 중(dirty)에는\n 묻지 않는다 — 서버에 있는 것은 저장 전 정본이라, 그 답은 화면에 있는 것과 다른 것을 말한다.\n */\n if (changes.has('figureId') || changes.has('dirty') || changes.has('figureType')) {\n void this.inspect()\n }\n }\n\n private async inspect() {\n this.keepChangedAppearance = false\n if (!this.figureId || this.dirty) {\n this.inspection = undefined\n return\n }\n\n try {\n this.inspection = await inspectFigure(this.figureId)\n } catch (e) {\n /* 판정을 못 받은 것을 「통과」로 두지 않는다 — 모르는 것이다. 단추는 서버가 막는다. */\n this.inspection = undefined\n this.failure = (e as Error).message\n }\n }\n\n private async reload() {\n if (!this.figureId) {\n this.versions = []\n return\n }\n\n try {\n this.versions = await fetchFigureVersions(this.figureId)\n this.failure = ''\n } catch (e) {\n this.failure = (e as Error).message\n }\n }\n\n render() {\n return html`\n <section>\n <h3>${i18next.t('figure.label.release')}</h3>\n ${this.figureId ? this.renderRelease() : html`<p quiet>${i18next.t('figure.text.save-the-figure-before-releasing')}</p>`}\n </section>\n\n <section>\n <h3>${i18next.t('figure.label.released-versions')}</h3>\n ${this.versions.length\n ? html`<ul>\n ${this.versions.map(version => this.renderVersion(version))}\n </ul>`\n : html`<p quiet>${i18next.t('figure.text.nothing-released-yet')}</p>`}\n </section>\n `\n }\n\n private renderRelease() {\n const released = this.figureState === 'released'\n\n /*\n 게이트가 막는 것과 알리는 것을 함께 낸다. **막는 것은 단추를 잠근다** — 눌러 보고 서버에게\n 거절당하는 것으로 알게 하지 않는다. 알리는 것은 잠그지 않고 그대로 보여 준다.\n */\n const blocking = (this.inspection?.findings ?? []).filter(finding => finding.blocking)\n const advisory = (this.inspection?.findings ?? []).filter(finding => !finding.blocking)\n const appearanceChoiceNeeded = advisory.some(finding => finding.code === 'base-ratio-changed') && !this.keepChangedAppearance\n\n /*\n ── 「발행되나」에 **한 번만** 답한다 ─────────────────────────────────────\n 전에는 막는 목록의 머리가 「이것들이 발행을 막습니다」, 알리는 목록의 머리가 「발행은 되지만\n 확인해 보십시오」였다. 두 줄이 나란히 서서 화면이 「막힌다」와 「된다」를 함께 말했고, 사람이\n 「그래서 되는 건가」를 되물었다.\n\n 답은 이 한 줄이 하고, 아래 목록들은 **이유**일 뿐이다 — 그래서 목록 머리는 문장이 아니라\n 이름씨다(「고쳐야 하는 것」·「막지는 않는 것」).\n */\n const why = this.dirty\n ? i18next.t('figure.text.release-needs-a-save-first')\n : released\n ? i18next.t('figure.text.already-released-edit-to-release-again')\n : appearanceChoiceNeeded\n ? i18next.t('figure.text.choose-appearance-change')\n : blocking.length\n ? i18next.t('figure.text.cannot-release-now')\n : ''\n\n return html`\n <div now>\n <span state ?released=${released}>${i18next.t(released ? 'figure.label.figure-released' : 'figure.label.draft')}</span>\n <span ver>${i18next.t('figure.text.version-number', { version: this.figureVersion ?? 0 })}</span>\n </div>\n\n <input note placeholder=${i18next.t('figure.text.what-changed-in-this-release')} ?disabled=${!!why || this.busy} />\n\n <button release ?disabled=${!!why || this.busy} @click=${() => this.release()}>\n ${this.busy ? i18next.t('figure.text.releasing') : i18next.t('figure.button.release-figure')}\n </button>\n\n ${why ? html`<p why>${why}</p>` : nothing}\n ${blocking.length\n ? html`<ul gate>\n <li head>${i18next.t('figure.text.must-fix-to-release')}</li>\n ${blocking.map(finding => this.renderFinding(finding, true))}\n </ul>`\n : nothing}\n ${advisory.length\n ? html`<ul gate>\n <li head quiet>${i18next.t('figure.text.does-not-block')}</li>\n ${advisory.map(finding => this.renderFinding(finding, false))}\n </ul>`\n : nothing}\n ${this.failure ? html`<p failed>${this.failure}</p>` : nothing}\n `\n }\n\n /**\n * 사유 한 줄 — **잰 것 · 왜 · 어떻게** 셋을 함께 말한다.\n *\n * 잰 것만 보여 줬더니 「1 조각이 2 조각으로 갈라진다」에서 사람이 멈췄다. 왜 나쁜지도, 무엇을\n * 바꾸면 되는지도 알 수 없었다. 그 셋은 형식(`figure-model` 의 gate)이 만들고 여기서는 자리만\n * 준다 — 화면이 문장을 지으면 서버가 거절할 때와 다른 말을 하게 된다.\n */\n private renderFinding(finding: FigureGateFinding, blocking: boolean) {\n return renderGateFinding(finding, blocking, html`\n ${this.offersFit(finding.code)\n ? html`<button fit-base @click=${() => this.fitBase()}>\n ${i18next.t('figure.button.fit-base-to-parts')}\n </button>`\n : nothing}\n ${finding.code === 'parts-off-placement-face' && this.format === 'v2'\n ? html`<button fit-base @click=${() => this.settleOnFloor()}>\n ${i18next.t('figure.button.settle-on-floor')}\n </button>`\n : nothing}\n ${finding.code === 'base-ratio-changed' && this.releasedBase\n ? html`<div ratio-choice>\n <button apply-fix @click=${() => this.restoreReleasedFootprint()}>\n ${i18next.t('figure.button.restore-released-base')}\n </button>\n <button ?selected=${this.keepChangedAppearance} @click=${() => this.keepChangedFootprint()}>\n ${i18next.t('figure.button.keep-changed-appearance')}\n </button>\n </div>\n <span how-line><md-icon>info</md-icon>${i18next.t('figure.text.appearance-choice-explained')}</span>`\n : nothing}\n ${(finding.fixes ?? []).map(fix => html`\n ${fix.safe ? html`\n <button apply-fix @click=${() => this.applyFix(fix)}>\n ${i18next.t('figure.button.apply-fix', { part: fix.part, axis: fix.axis.toUpperCase(), from: fix.from ?? 'auto', to: fix.to })}\n </button>\n ` : nothing}\n <button preview-fix @click=${() => this.previewFix(fix)}>\n ${i18next.t('figure.button.preview-fix', { part: fix.part, axis: fix.axis.toUpperCase(), from: fix.from ?? 'auto', to: fix.to })}\n </button>\n `)}\n ${finding.fixPlan?.safe ? html`\n <span how-line><md-icon>auto_fix_high</md-icon>${i18next.t('figure.text.recommended-fix-explained', { n: finding.fixPlan.fixes.length })}</span>\n <button apply-fix @click=${() => this.applyFixPlan(finding.fixPlan!)}>\n ${i18next.t('figure.button.apply-recommended-fix')}\n </button>\n ` : nothing}\n `)\n }\n\n /**\n * 게이트는 수정을 저장하지 않는다. 부모 모델러가 현재 정본과 이 한 수정안을 나란히 렌더하고,\n * 저작자가 '변경 받기'를 눌렀을 때에만 자기 작업 사본에 적용한다.\n */\n private previewFix(fix: FigureGateFix) {\n this.dispatchEvent(new CustomEvent('preview-sizing-fix', { detail: fix, bubbles: true, composed: true }))\n }\n\n /** 서버가 전수 재검사해 모든 차단 split이 사라진 후보만 작업 사본에 원클릭 적용한다. 저장은 하지 않는다. */\n private applyFix(fix: FigureGateFix) {\n if (!fix.safe) return\n this.dispatchEvent(new CustomEvent('apply-sizing-fix', { detail: fix, bubbles: true, composed: true }))\n }\n\n /** Apply the server-remeasured coordinated repair to the draft only; save remains explicit. */\n private applyFixPlan(plan: FigureGateFixPlan) {\n if (!plan.safe || plan.fixes.length === 0) return\n this.dispatchEvent(new CustomEvent('apply-sizing-fix-plan', { detail: plan, bubbles: true, composed: true }))\n }\n\n /**\n * 넘친 것을 담는 길 하나를 여기서도 내민다 — **같은 동작이 속성 패널에도 있다**(「부품에 맞추기」).\n *\n * 그 단추를 아는 저작자는 거기서 누른다. 발행이 막혔다는 말을 여기서 읽은 사람은 그 단추가 있는\n * 것을 모르고, 문구는 그것을 가리킬 수 없다 — 문장은 형식이 쓰고 형식은 화면의 단추를 모른다.\n * 그래서 화면이 자기 자리에서 같은 동작을 낸다. 누르면 상자만 커지고 부품은 제자리이며, 저장은\n * 여전히 저작자가 따로 한다.\n */\n /**\n * 「부품에 맞추기」를 내미는 판정. V2 는 기준 상자를 부품에 맞추고, V3 는 공간을 부품에서 다시 선언한다\n * (`declare-occupancy`) — 둘 다 부품은 그대로 두고 선언만 바꾼다. V3 에서 공간을 아직 선언하지 않은 도형도\n * 같은 단추로 선언한다: 새로 만든 V3 도형은 전부 그 판정으로 막히고, 화면에 다른 길이 없다.\n *\n * V3 의 장착면이 떠 있는 것은 여기 없다. 부품 하나를 평면에 붙이면 그 부품에 붙지 않은 것은 따라오지 않아\n * V2 의 「바닥에 앉히기」와 뜻이 다르다. 판정의 「어떻게」가 할 일을 말한다.\n */\n private offersFit(code: string): boolean {\n return this.format === 'v3'\n ? code === 'part-outside-occupancy' || code === 'occupancy-undeclared'\n : code === 'part-outside-base'\n }\n\n private fitBase() {\n this.dispatchEvent(new CustomEvent('fit-base-to-parts', { bubbles: true, composed: true }))\n }\n\n /**\n * 떠 있는 것을 앉힌다. 부품 하나를 고르고 「바닥에」를 누르는 길은 전부터 있었으나, 그 길은\n * 고른 것 하나만 옮기고 **여기서는 닿지 않는다.** 막혔다는 말을 읽은 자리에서 조치가 되게 한다.\n */\n private settleOnFloor() {\n this.dispatchEvent(new CustomEvent('settle-parts-on-floor', { bubbles: true, composed: true }))\n }\n\n /** 기준 상자의 X·Z만 직전 발행값으로 되돌린다. Y와 부품 편집은 보존한다. */\n private restoreReleasedFootprint() {\n if (!this.releasedBase) return\n this.dispatchEvent(new CustomEvent('restore-released-footprint', {\n detail: { x: this.releasedBase.x, z: this.releasedBase.z }, bubbles: true, composed: true\n }))\n }\n\n /** 현재 외형으로 발행하겠다는 명시적 선택이다. 저장·발행은 이어서 따로 한다. */\n private keepChangedFootprint() {\n this.keepChangedAppearance = true\n }\n\n private renderVersion(version: FigureVersion) {\n return html`\n <li>\n <span no>${i18next.t('figure.text.version-number', { version: version.version })}</span>\n <span when>\n ${version.updatedAt ? new Date(version.updatedAt).toLocaleString() : ''}\n ${version.updater?.name ? ` · ${version.updater.name}` : ''}\n </span>\n <button revert ?disabled=${this.busy} @click=${() => this.revert(version.version)}>\n ${i18next.t('figure.button.revert')}\n </button>\n ${version.comment ? html`<span comment>${version.comment}</span>` : nothing}\n </li>\n `\n }\n\n private async release() {\n const input = this.renderRoot.querySelector('input[note]') as HTMLInputElement\n const comment = input?.value?.trim()\n\n this.busy = true\n this.failure = ''\n\n try {\n const figure = await releaseFigure(this.figureId!, comment || undefined)\n if (input) input.value = ''\n /* 정본을 든 자리가 갱신한다 — 여기서 페이지 상태를 고쳐 쓰지 않는다. */\n this.dispatchEvent(new CustomEvent('figure-released', { detail: { figure }, bubbles: true, composed: true }))\n await this.reload()\n await this.inspect()\n } catch (e) {\n this.failure = (e as Error).message\n } finally {\n this.busy = false\n }\n }\n\n private async revert(version: number) {\n /* 되돌아갈 수 없는 조작은 아니지만(판본은 남는다), 지금 화면의 것이 덮인다 — 이름으로 확인한다. */\n const agreed = await OxPrompt.open({\n type: 'warning',\n title: i18next.t('figure.button.revert'),\n text: i18next.t('figure.text.revert-to-version-confirm', { version }),\n confirmButton: { text: i18next.t('figure.button.revert') },\n cancelButton: { text: i18next.t('figure.button.keep-editing') }\n })\n if (!agreed) {\n return\n }\n\n this.busy = true\n this.failure = ''\n\n try {\n const result = await revertFigureVersion(this.figureId!, version)\n this.dispatchEvent(new CustomEvent('figure-reverted', { detail: result, bubbles: true, composed: true }))\n await this.reload()\n /*\n **되돌리면 정본이 바뀌므로 판정도 바뀐다.** 이것을 안 하면 옛 판의 사유가 새 정본 위에\n 남아 화면이 거짓말한다 — 되돌리기는 `dirty` 도 `figureVersion` 도 건드리지 않으므로\n `updated` 의 감시로는 다시 묻지 않는다.\n */\n await this.inspect()\n } catch (e) {\n this.failure = (e as Error).message\n } finally {\n this.busy = false\n }\n }\n}\n"]}
@@ -0,0 +1,20 @@
1
+ import { LitElement } from 'lit';
2
+ import '@operato/popup/ox-popup-actions.js';
3
+ import '@material/web/button/filled-button.js';
4
+ import '@material/web/button/text-button.js';
5
+ /**
6
+ * The first save of a new figure: its type and its name, asked once (V2's save dialog, in the suite's popup look).
7
+ * Closes with the answer, or with nothing when cancelled -- then nothing is saved.
8
+ */
9
+ export declare class FigureV3FirstSave extends LitElement {
10
+ static styles: import("lit").CSSResult[];
11
+ /** The type the figure has so far -- empty for a temporary one, which is not offered. */
12
+ type: string;
13
+ name: string;
14
+ /** Type names already used in this domain. */
15
+ taken: string[];
16
+ private refusal;
17
+ render(): import("lit-html").TemplateResult<1>;
18
+ private onKey;
19
+ private confirm;
20
+ }
@@ -0,0 +1,118 @@
1
+ import { __decorate, __metadata } from "tslib";
2
+ import { LitElement, html, css, nothing } from 'lit';
3
+ import { customElement, property, state } from 'lit/decorators.js';
4
+ import { i18next } from '@operato/i18n';
5
+ import { closePopup } from '@operato/popup';
6
+ import '@operato/popup/ox-popup-actions.js';
7
+ import { CommonPopupStyles, FormFieldStyles } from '@operato/styles';
8
+ import '@material/web/button/filled-button.js';
9
+ import '@material/web/button/text-button.js';
10
+ import { firstSaveRefusalOf, releasableType, typeOf } from './v3-first-save.js';
11
+ /**
12
+ * The first save of a new figure: its type and its name, asked once (V2's save dialog, in the suite's popup look).
13
+ * Closes with the answer, or with nothing when cancelled -- then nothing is saved.
14
+ */
15
+ let FigureV3FirstSave = class FigureV3FirstSave extends LitElement {
16
+ constructor() {
17
+ super(...arguments);
18
+ /** The type the figure has so far -- empty for a temporary one, which is not offered. */
19
+ this.type = '';
20
+ this.name = '';
21
+ /** Type names already used in this domain. */
22
+ this.taken = [];
23
+ this.refusal = null;
24
+ this.onKey = (e) => {
25
+ if (e.key === 'Enter' && !e.isComposing)
26
+ this.confirm();
27
+ };
28
+ this.confirm = () => {
29
+ this.refusal = firstSaveRefusalOf(this.type, this.taken);
30
+ if (this.refusal)
31
+ return;
32
+ closePopup(this, { type: this.type, name: this.name.trim() || this.type });
33
+ };
34
+ }
35
+ static { this.styles = [
36
+ FormFieldStyles,
37
+ CommonPopupStyles,
38
+ css `
39
+ /* The popup's height is a share of the window (small is 40%): on a low window the buttons must still be reached. */
40
+ :host {
41
+ overflow-y: auto;
42
+ }
43
+ div[field] {
44
+ display: grid;
45
+ gap: 4px;
46
+ }
47
+ div[field] label {
48
+ font-size: var(--popup-font-size-small);
49
+ color: var(--md-sys-color-on-surface-variant);
50
+ }
51
+ div[field] input {
52
+ box-sizing: border-box;
53
+ width: 100%;
54
+ }
55
+ input[type-field] {
56
+ font-family: var(--mono-font, monospace);
57
+ }
58
+ `
59
+ ]; }
60
+ render() {
61
+ return html `
62
+ <div class="what">${i18next.t('figure.text.save-new-figure-desc')}</div>
63
+ <div field>
64
+ <label for="type">${i18next.t('figure.label.figure-type')}</label>
65
+ <input
66
+ id="type"
67
+ type-field
68
+ autofocus
69
+ .value=${this.type}
70
+ placeholder=${i18next.t('figure.text.figure-type-example')}
71
+ @input=${(e) => {
72
+ this.type = typeOf(e.target.value);
73
+ e.target.value = this.type;
74
+ this.refusal = null;
75
+ }}
76
+ @keydown=${this.onKey}
77
+ />
78
+ ${this.type && !releasableType(this.type) ? html `<div class="note">${i18next.t('figure.text.type-name-will-not-release')}</div>` : nothing}
79
+ </div>
80
+ <div field>
81
+ <label for="name">${i18next.t('figure.label.name')}</label>
82
+ <input
83
+ id="name"
84
+ .value=${this.name}
85
+ placeholder=${i18next.t('figure.text.figure-name-example')}
86
+ @input=${(e) => (this.name = e.target.value)}
87
+ @keydown=${this.onKey}
88
+ />
89
+ </div>
90
+ ${this.refusal ? html `<div class="failure" role="alert">${i18next.t(this.refusal.key, { type: this.refusal.type })}</div>` : nothing}
91
+ <ox-popup-actions>
92
+ <md-text-button slot="cancel" @click=${() => closePopup(this)}>${i18next.t('figure.button.cancel')}</md-text-button>
93
+ <md-filled-button slot="confirm" ?disabled=${!this.type} @click=${this.confirm}>${i18next.t('figure.button.save')}</md-filled-button>
94
+ </ox-popup-actions>
95
+ `;
96
+ }
97
+ };
98
+ __decorate([
99
+ property(),
100
+ __metadata("design:type", Object)
101
+ ], FigureV3FirstSave.prototype, "type", void 0);
102
+ __decorate([
103
+ property(),
104
+ __metadata("design:type", Object)
105
+ ], FigureV3FirstSave.prototype, "name", void 0);
106
+ __decorate([
107
+ property({ attribute: false }),
108
+ __metadata("design:type", Array)
109
+ ], FigureV3FirstSave.prototype, "taken", void 0);
110
+ __decorate([
111
+ state(),
112
+ __metadata("design:type", Object)
113
+ ], FigureV3FirstSave.prototype, "refusal", void 0);
114
+ FigureV3FirstSave = __decorate([
115
+ customElement('figure-v3-first-save')
116
+ ], FigureV3FirstSave);
117
+ export { FigureV3FirstSave };
118
+ //# sourceMappingURL=figure-v3-first-save.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"figure-v3-first-save.js","sourceRoot":"","sources":["../../client/modeller/figure-v3-first-save.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement, html, css, nothing } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { i18next } from '@operato/i18n'\nimport { closePopup } from '@operato/popup'\nimport '@operato/popup/ox-popup-actions.js'\nimport { CommonPopupStyles, FormFieldStyles } from '@operato/styles'\nimport '@material/web/button/filled-button.js'\nimport '@material/web/button/text-button.js'\n\nimport { firstSaveRefusalOf, releasableType, typeOf, type FirstSave } from './v3-first-save.js'\n\n/**\n * The first save of a new figure: its type and its name, asked once (V2's save dialog, in the suite's popup look).\n * Closes with the answer, or with nothing when cancelled -- then nothing is saved.\n */\n@customElement('figure-v3-first-save')\nexport class FigureV3FirstSave extends LitElement {\n static styles = [\n FormFieldStyles,\n CommonPopupStyles,\n css`\n /* The popup's height is a share of the window (small is 40%): on a low window the buttons must still be reached. */\n :host {\n overflow-y: auto;\n }\n div[field] {\n display: grid;\n gap: 4px;\n }\n div[field] label {\n font-size: var(--popup-font-size-small);\n color: var(--md-sys-color-on-surface-variant);\n }\n div[field] input {\n box-sizing: border-box;\n width: 100%;\n }\n input[type-field] {\n font-family: var(--mono-font, monospace);\n }\n `\n ]\n\n /** The type the figure has so far -- empty for a temporary one, which is not offered. */\n @property() type = ''\n @property() name = ''\n /** Type names already used in this domain. */\n @property({ attribute: false }) taken: string[] = []\n @state() private refusal: { key: string; type?: string } | null = null\n\n render() {\n return html`\n <div class=\"what\">${i18next.t('figure.text.save-new-figure-desc')}</div>\n <div field>\n <label for=\"type\">${i18next.t('figure.label.figure-type')}</label>\n <input\n id=\"type\"\n type-field\n autofocus\n .value=${this.type}\n placeholder=${i18next.t('figure.text.figure-type-example')}\n @input=${(e: Event) => {\n this.type = typeOf((e.target as HTMLInputElement).value)\n ;(e.target as HTMLInputElement).value = this.type\n this.refusal = null\n }}\n @keydown=${this.onKey}\n />\n ${this.type && !releasableType(this.type) ? html`<div class=\"note\">${i18next.t('figure.text.type-name-will-not-release')}</div>` : nothing}\n </div>\n <div field>\n <label for=\"name\">${i18next.t('figure.label.name')}</label>\n <input\n id=\"name\"\n .value=${this.name}\n placeholder=${i18next.t('figure.text.figure-name-example')}\n @input=${(e: Event) => (this.name = (e.target as HTMLInputElement).value)}\n @keydown=${this.onKey}\n />\n </div>\n ${this.refusal ? html`<div class=\"failure\" role=\"alert\">${i18next.t(this.refusal.key, { type: this.refusal.type })}</div>` : nothing}\n <ox-popup-actions>\n <md-text-button slot=\"cancel\" @click=${() => closePopup(this)}>${i18next.t('figure.button.cancel')}</md-text-button>\n <md-filled-button slot=\"confirm\" ?disabled=${!this.type} @click=${this.confirm}>${i18next.t('figure.button.save')}</md-filled-button>\n </ox-popup-actions>\n `\n }\n\n private onKey = (e: KeyboardEvent) => {\n if (e.key === 'Enter' && !e.isComposing) this.confirm()\n }\n\n private confirm = () => {\n this.refusal = firstSaveRefusalOf(this.type, this.taken)\n if (this.refusal) return\n closePopup(this, { type: this.type, name: this.name.trim() || this.type } satisfies FirstSave)\n }\n}\n"]}
@@ -43,6 +43,11 @@ export declare class FigureV3Links extends FigureV3Links_base {
43
43
  */
44
44
  private renderPositions;
45
45
  private renderFastening;
46
+ /**
47
+ * Where on the target face the part sits, on the two axes across it: centred, level with the target's edge on
48
+ * either side, or a distance off centre. A door hung level with the frame's front edge turns about that edge.
49
+ */
50
+ private renderAlignment;
46
51
  private renderMotion;
47
52
  private setKind;
48
53
  private command;
@@ -4,7 +4,7 @@ import { css, html, LitElement, nothing } from 'lit';
4
4
  import { customElement, property, state } from 'lit/decorators.js';
5
5
  import { i18next, localize } from '@operato/i18n';
6
6
  import { V3_FACES } from '@hatiolab/figure-model';
7
- import { acceptsOf, AXES, canFasten, canFollow, dimensionNow, followableAxes, positionIsShare, choiceOf, fastenedTo, followOf, hasInstanceSize, holdsThings, motionOf, placeFacesOf, placeIdFor, placesOn, positionsWorkedOut } from './v3-links.js';
7
+ import { acceptsOf, alignAxesOf, alignOf, AXES, canFasten, canFollow, dimensionNow, followableAxes, positionIsShare, choiceOf, fastenedTo, followOf, hasInstanceSize, holdsThings, leadersFor, motionOf, motionJoint, placeFacesOf, placeIdFor, placesOn, positionsWorkedOut } from './v3-links.js';
8
8
  import { SidePanelStyles } from './side-panel-styles.js';
9
9
  import { HelpToggles } from './v3-help.js';
10
10
  const FACES = Object.keys(V3_FACES);
@@ -30,8 +30,8 @@ let FigureV3Links = class FigureV3Links extends localize(i18next)(LitElement) {
30
30
  this.locked = false;
31
31
  /** Which ties this shows: the part's shape (size and placement, in 속성), its places (자리·능력) or its motion (움직임). */
32
32
  this.section = 'shape';
33
- this.fasten = { face: 'bottom', toPlane: false, target: '', targetFace: 'top', facing: 'meet', gap: 0 };
34
- this.motion = { kind: 'slide', axis: 'x', id: '', min: 0, max: 500 };
33
+ this.fasten = { face: 'bottom', toPlane: false, target: '', targetFace: 'top', facing: 'meet', gap: 0, align: {}, alignMm: {} };
34
+ this.motion = { kind: 'slide', axis: 'x', id: '', min: 0, max: 500, follow: '', multiplier: 1, offset: 0 };
35
35
  this.place = { role: 'grip', face: 'top', label: '', accepts: '', capacity: 1 };
36
36
  }
37
37
  static { this.styles = [SidePanelStyles, css `
@@ -246,6 +246,7 @@ let FigureV3Links = class FigureV3Links extends localize(i18next)(LitElement) {
246
246
  <option value="flush" ?selected=${f.facing === 'flush'}>${i18next.t('figure.label.facing-flush')}</option>
247
247
  </select>
248
248
  </div>`}
249
+ ${this.renderAlignment()}
249
250
  <div form>
250
251
  <span>${i18next.t('figure.label.gap')}</span>
251
252
  <input type="number" step="any" .value=${String(f.gap)} @change=${(e) => (this.fasten = { ...f, gap: Number(e.target.value) || 0 })} />
@@ -260,6 +261,7 @@ let FigureV3Links = class FigureV3Links extends localize(i18next)(LitElement) {
260
261
  to: f.toPlane ? { plane: 'mounting-plane' } : { part: f.target, face: f.targetFace },
261
262
  ...(f.toPlane ? {} : { facing: f.facing }),
262
263
  ...(f.gap ? { gap: f.gap } : {}),
264
+ ...(alignOf(f.toPlane ? 'plane' : f.targetFace, f.align, f.alignMm) ? { align: alignOf(f.toPlane ? 'plane' : f.targetFace, f.align, f.alignMm) } : {}),
263
265
  replace: !!to
264
266
  })}
265
267
  >
@@ -267,56 +269,112 @@ let FigureV3Links = class FigureV3Links extends localize(i18next)(LitElement) {
267
269
  </button>
268
270
  `;
269
271
  }
272
+ /**
273
+ * Where on the target face the part sits, on the two axes across it: centred, level with the target's edge on
274
+ * either side, or a distance off centre. A door hung level with the frame's front edge turns about that edge.
275
+ */
276
+ renderAlignment() {
277
+ const f = this.fasten;
278
+ const set = (axis, how) => (this.fasten = { ...f, align: { ...f.align, [axis]: how } });
279
+ const setMm = (axis, mm) => (this.fasten = { ...f, alignMm: { ...f.alignMm, [axis]: mm } });
280
+ return html `<div form>
281
+ ${alignAxesOf(f.toPlane ? 'plane' : f.targetFace).map(({ axis, edges }) => {
282
+ const how = f.align[axis] === 'min' || f.align[axis] === 'max' ? (edges ? f.align[axis] : 'centre') : (f.align[axis] ?? 'centre');
283
+ return html `<span>${i18next.t('figure.label.align-' + axis)}</span>
284
+ <select @change=${(e) => set(axis, e.target.value)}>
285
+ <option value="centre" ?selected=${how === 'centre'}>${i18next.t('figure.label.align-centre')}</option>
286
+ ${edges
287
+ ? html `<option value="min" ?selected=${how === 'min'}>${i18next.t(`figure.label.align-${axis}-min`)}</option>
288
+ <option value="max" ?selected=${how === 'max'}>${i18next.t(`figure.label.align-${axis}-max`)}</option>`
289
+ : nothing}
290
+ <option value="mm" ?selected=${how === 'mm'}>${i18next.t('figure.label.align-mm')}</option>
291
+ </select>
292
+ ${how === 'mm'
293
+ ? html `<span></span><input type="number" step="any" aria-label=${i18next.t('figure.label.align-mm')} .value=${String(f.alignMm[axis] ?? 0)} @change=${(e) => setMm(axis, Number(e.target.value) || 0)} />`
294
+ : nothing}`;
295
+ })}
296
+ </div>`;
297
+ }
270
298
  renderMotion(asset) {
271
299
  const now = motionOf(asset, this.partId);
300
+ const joint = asset.joints?.find(j => j.body1.part === this.partId);
272
301
  if (now) {
273
302
  return html `<div pair>
274
303
  <span>
275
304
  ${i18next.t('figure.label.motion-' + now.kind)} · ${String(now.axis).toUpperCase()}
276
- ${now.control ? html `<small>${now.control.id} (${now.control.unit}) ${now.control.min} … ${now.control.max}</small>` : nothing}
305
+ ${now.follows
306
+ ? html `<small>${i18next.t('figure.label.motion-follows', { joint: now.follows.joint, multiplier: now.follows.multiplier, offset: now.follows.offset ?? 0 })}</small>`
307
+ : now.control
308
+ ? html `<small>${now.control.id} (${now.control.unit}) ${now.control.min} … ${now.control.max}</small>`
309
+ : nothing}
277
310
  </span>
278
- <button act ?disabled=${this.locked} @click=${() => this.command({ kind: 'remove-motion', part: this.partId })}>
311
+ <button
312
+ act
313
+ ?disabled=${this.locked || !joint}
314
+ @click=${() => joint && this.command({ kind: 'set-joint', joint: { id: joint.id, type: 'fixed', body0: joint.body0, body1: joint.body1, ...(joint.origin ? { origin: joint.origin } : {}) } })}
315
+ >
279
316
  <md-icon>close</md-icon>${i18next.t('figure.button.remove-motion')}
280
317
  </button>
281
318
  </div>`;
282
319
  }
320
+ /* A joint needs a parent (URDF): a part fastened to nothing is fastened first. */
321
+ if (!joint)
322
+ return html `<p hint>${i18next.t('figure.text.fasten-before-moving')}</p>`;
283
323
  const m = this.motion;
284
- const unit = m.kind === 'turn' ? 'deg' : 'mm';
324
+ const unit = m.kind === 'slide' ? 'mm' : 'deg';
325
+ /* A follower follows a joint that moves the same way; a choice the kind no longer allows is dropped. */
326
+ const leaders = leadersFor(asset, this.partId, m.kind);
327
+ const follow = leaders.some(j => j.id === m.follow) ? m.follow : '';
285
328
  return html `
286
329
  <div pair>
287
330
  <select @change=${(e) => this.setKind(e.target.value)}>
288
331
  <option value="slide" ?selected=${m.kind === 'slide'}>${i18next.t('figure.label.motion-slide')}</option>
289
332
  <option value="turn" ?selected=${m.kind === 'turn'}>${i18next.t('figure.label.motion-turn')}</option>
333
+ <option value="continuous" ?selected=${m.kind === 'continuous'}>${i18next.t('figure.label.motion-continuous')}</option>
290
334
  </select>
291
335
  <select @change=${(e) => (this.motion = { ...m, axis: e.target.value })}>
292
336
  ${AXES.map(a => html `<option value=${a} ?selected=${a === m.axis}>${i18next.t('figure.label.motion-axis')} ${a.toUpperCase()}</option>`)}
293
337
  </select>
294
338
  </div>
295
- <div form>
296
- <span>${i18next.t('figure.label.control-id')}</span>
297
- <input .value=${m.id} placeholder=${`${this.partId}-${m.kind}`} @change=${(e) => (this.motion = { ...m, id: e.target.value.trim() })} />
298
- <span>${i18next.t('figure.label.control-range')} (${unit})</span>
299
- <div pair>
300
- <input type="number" step="any" .value=${String(m.min)} @change=${(e) => (this.motion = { ...m, min: Number(e.target.value) })} />
301
- <input type="number" step="any" .value=${String(m.max)} @change=${(e) => (this.motion = { ...m, max: Number(e.target.value) })} />
302
- </div>
303
- </div>
339
+ ${leaders.length
340
+ ? html `<div form>
341
+ <span>${i18next.t('figure.label.motion-follow')}</span>
342
+ <select @change=${(e) => (this.motion = { ...m, follow: e.target.value })}>
343
+ <option value="" ?selected=${!follow}>${i18next.t('figure.label.motion-own-control')}</option>
344
+ ${leaders.map(j => html `<option value=${j.id} ?selected=${j.id === follow}>${j.body1.part}${j.state ? ` · ${j.state.id}` : ''}</option>`)}
345
+ </select>
346
+ </div>`
347
+ : nothing}
348
+ ${follow
349
+ ? html `<div form>
350
+ <span>${i18next.t('figure.label.motion-multiplier')}</span>
351
+ <input type="number" step="any" .value=${String(m.multiplier)} @change=${(e) => (this.motion = { ...m, multiplier: Number(e.target.value) })} />
352
+ <span>${i18next.t('figure.label.motion-offset')} (${unit})</span>
353
+ <input type="number" step="any" .value=${String(m.offset)} @change=${(e) => (this.motion = { ...m, offset: Number(e.target.value) || 0 })} />
354
+ </div>`
355
+ : html `<div form>
356
+ <span>${i18next.t('figure.label.control-id')}</span>
357
+ <input .value=${m.id} placeholder=${`${this.partId}-${m.kind}`} @change=${(e) => (this.motion = { ...m, id: e.target.value.trim() })} />
358
+ ${m.kind === 'continuous'
359
+ ? nothing
360
+ : html `<span>${i18next.t('figure.label.control-range')} (${unit})</span>
361
+ <div pair>
362
+ <input type="number" step="any" .value=${String(m.min)} @change=${(e) => (this.motion = { ...m, min: Number(e.target.value) })} />
363
+ <input type="number" step="any" .value=${String(m.max)} @change=${(e) => (this.motion = { ...m, max: Number(e.target.value) })} />
364
+ </div>`}
365
+ </div>`}
304
366
  <button
305
367
  act
306
- ?disabled=${this.locked || !(m.max > m.min)}
307
- @click=${() => this.command({
308
- kind: 'add-motion',
309
- part: this.partId,
310
- motion: { kind: m.kind, axis: m.axis },
311
- state: { id: m.id || `${this.partId}-${m.kind}`, unit, min: m.min, max: m.max, start: m.min }
312
- })}
368
+ ?disabled=${this.locked || (follow ? !(Number.isFinite(m.multiplier) && m.multiplier !== 0) : m.kind !== 'continuous' && !(m.max > m.min && m.min <= 0 && 0 <= m.max))}
369
+ title=${!follow && m.kind !== 'continuous' && !(m.min <= 0 && 0 <= m.max) ? i18next.t('figure.text.joint-range-holds-zero') : ''}
370
+ @click=${() => this.command({ kind: 'set-joint', joint: motionJoint(joint, { ...m, follow }, this.partId) })}
313
371
  >
314
372
  <md-icon>animation</md-icon>${i18next.t('figure.button.add-motion')}
315
373
  </button>
316
374
  `;
317
375
  }
318
376
  setKind(kind) {
319
- this.motion = kind === 'turn' ? { ...this.motion, kind, min: -90, max: 90 } : { ...this.motion, kind, min: 0, max: 500 };
377
+ this.motion = kind === 'slide' ? { ...this.motion, kind, min: 0, max: 500 } : { ...this.motion, kind, min: -90, max: 90 };
320
378
  }
321
379
  command(action) {
322
380
  this.dispatchEvent(new CustomEvent('v3-command', { detail: { action }, bubbles: true, composed: true }));