@things-factory/figure-ui 10.1.5 → 10.1.6

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 (75) hide show
  1. package/client/graphql/index.ts +35 -3
  2. package/client/modeller/figure-ask.ts +5 -5
  3. package/client/modeller/figure-canvas.ts +1 -1
  4. package/client/modeller/figure-capabilities.ts +372 -0
  5. package/client/modeller/figure-history-panel.ts +171 -18
  6. package/client/modeller/figure-inspector.ts +392 -76
  7. package/client/modeller/figure-parts.ts +15 -15
  8. package/client/modeller/figure-preview.ts +2 -2
  9. package/client/modeller/figure-report.ts +102 -37
  10. package/client/modeller/figure-settings.ts +20 -20
  11. package/client/modeller/figure-side.ts +37 -9
  12. package/client/modeller/figure-source.test.ts +15 -1
  13. package/client/modeller/figure-source.ts +31 -15
  14. package/client/modeller/figure-thumbnail.ts +49 -4
  15. package/client/modeller/maturity.ts +10 -10
  16. package/client/pages/figure-list-page.ts +152 -66
  17. package/client/pages/figure-modeller-page.ts +260 -43
  18. package/client/types.ts +25 -0
  19. package/client/viewparts/figure-thumb.ts +172 -0
  20. package/dist-client/graphql/index.d.ts +8 -1
  21. package/dist-client/graphql/index.js +32 -3
  22. package/dist-client/graphql/index.js.map +1 -1
  23. package/dist-client/modeller/figure-ask.js +5 -5
  24. package/dist-client/modeller/figure-ask.js.map +1 -1
  25. package/dist-client/modeller/figure-canvas.js +1 -1
  26. package/dist-client/modeller/figure-canvas.js.map +1 -1
  27. package/dist-client/modeller/figure-capabilities.d.ts +42 -0
  28. package/dist-client/modeller/figure-capabilities.js +344 -0
  29. package/dist-client/modeller/figure-capabilities.js.map +1 -0
  30. package/dist-client/modeller/figure-history-panel.d.ts +15 -1
  31. package/dist-client/modeller/figure-history-panel.js +165 -18
  32. package/dist-client/modeller/figure-history-panel.js.map +1 -1
  33. package/dist-client/modeller/figure-inspector.d.ts +84 -0
  34. package/dist-client/modeller/figure-inspector.js +379 -75
  35. package/dist-client/modeller/figure-inspector.js.map +1 -1
  36. package/dist-client/modeller/figure-parts.js +15 -15
  37. package/dist-client/modeller/figure-parts.js.map +1 -1
  38. package/dist-client/modeller/figure-preview.js +2 -2
  39. package/dist-client/modeller/figure-preview.js.map +1 -1
  40. package/dist-client/modeller/figure-report.d.ts +18 -1
  41. package/dist-client/modeller/figure-report.js +105 -37
  42. package/dist-client/modeller/figure-report.js.map +1 -1
  43. package/dist-client/modeller/figure-settings.js +19 -19
  44. package/dist-client/modeller/figure-settings.js.map +1 -1
  45. package/dist-client/modeller/figure-side.d.ts +1 -0
  46. package/dist-client/modeller/figure-side.js +36 -8
  47. package/dist-client/modeller/figure-side.js.map +1 -1
  48. package/dist-client/modeller/figure-source.d.ts +15 -8
  49. package/dist-client/modeller/figure-source.js +16 -7
  50. package/dist-client/modeller/figure-source.js.map +1 -1
  51. package/dist-client/modeller/figure-thumbnail.js +40 -4
  52. package/dist-client/modeller/figure-thumbnail.js.map +1 -1
  53. package/dist-client/modeller/maturity.js +10 -10
  54. package/dist-client/modeller/maturity.js.map +1 -1
  55. package/dist-client/pages/figure-list-page.d.ts +1 -4
  56. package/dist-client/pages/figure-list-page.js +150 -63
  57. package/dist-client/pages/figure-list-page.js.map +1 -1
  58. package/dist-client/pages/figure-modeller-page.d.ts +45 -0
  59. package/dist-client/pages/figure-modeller-page.js +259 -43
  60. package/dist-client/pages/figure-modeller-page.js.map +1 -1
  61. package/dist-client/tsconfig.tsbuildinfo +1 -1
  62. package/dist-client/types.d.ts +23 -0
  63. package/dist-client/types.js.map +1 -1
  64. package/dist-client/viewparts/figure-thumb.d.ts +29 -0
  65. package/dist-client/viewparts/figure-thumb.js +168 -0
  66. package/dist-client/viewparts/figure-thumb.js.map +1 -0
  67. package/dist-server/tsconfig.tsbuildinfo +1 -1
  68. package/package.json +7 -5
  69. package/test/i18n-prefix-guard.test.ts +129 -0
  70. package/test/korean-register-guard.test.ts +83 -0
  71. package/translations/en.json +307 -264
  72. package/translations/ja.json +275 -264
  73. package/translations/ko.json +307 -264
  74. package/translations/ms.json +275 -264
  75. package/translations/zh.json +275 -264
@@ -1 +1 @@
1
- {"version":3,"file":"figure-preview.js","sourceRoot":"","sources":["../../client/modeller/figure-preview.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,KAAK,CAAA;AACtD,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAEzE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACjD,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAA;AAE1D,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAE/D;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,oDAAoD;AACpD,MAAM,SAAS,GAAG,SAAS,CAAA;AAE3B,iBAAiB;AACjB,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAA;AAE/B;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,YAAY,GAAoE;IAC3F,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE;IACxB,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE;IAC1B,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE;IAC1B,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE;IAC1B,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE;IACjC,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE;IACjC,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE;IACjC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,EAAE;CACzC,CAAA;AAeM,IAAM,aAAa,GAAnB,MAAM,aAAc,SAAQ,QAAQ,CAAC,OAAO,CAAC,CAAC,UAAU,CAAC;IAAzD;;QA8FL,8BAA8B;QACb,WAAM,GAAG,EAAE,CAAA;QAO5B,8CAA8C;QAC7B,YAAO,GAA2B,EAAE,CAAA;IA6MvD,CAAC;aAnTQ,WAAM,GAAG,GAAG,CAAA;;;;;0BAKK,SAAS,CAAC,SAAS,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0BA+EpB,SAAS,CAAC,SAAS,CAAC;;GAE3C,AAtFY,CAsFZ;IAkBD,MAAM;QACJ,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,EAAE,UAAU,IAAI,EAAE,CAAA;QAE3C,OAAO,IAAI,CAAA;;QAEP,IAAI,CAAC,MAAM;YACX,CAAC,CAAC,IAAI,CAAA,eAAe,IAAI,CAAC,MAAM,QAAQ;YACxC,CAAC,CAAC,IAAI,CAAA;gBACE,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAA,SAAS,GAAG,CAAC,IAAI,SAAS,CAAC;;cAEzD,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE;KAC1D,CAAA;IACH,CAAC;IAED;;;;;;OAMG;IACK,WAAW,CAAC,KAA8C;QAChE,OAAO,IAAI,CAAA;;UAEL,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE;YACjB,MAAM,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI,MAAM,CAAC,KAAK,MAAM,CAAA;YAC9C,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;YAEvD,OAAO,IAAI,CAAA;;sBAEC,IAAI,CAAC,IAAI,gBAAgB,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI;;;;sBAI3C,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;;yBAET,MAAM,CAAC,KAAK,CAAC;yBACb,CAAC,CAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAE,CAAC,CAAC,MAA2B,CAAC,KAAK,CAAC,CAAC;;wBAElF,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;;WAE7B,CAAA;QACH,CAAC,CAAC;;KAEL,CAAA;IACH,CAAC;IAED;;;;;OAKG;IACK,KAAK,CAAC,IAAY,EAAE,KAAa;QACvC,IAAI,CAAC,OAAO,GAAG,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAA;QAEjD,KAAK,MAAM,SAAS,IAAI,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,UAAU,IAAI,EAAE,EAAE,CAAC;YAC3D,SAAS,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC,CAAA;QAC/C,CAAC;IACH,CAAC;IAED,UAAU,CAAC,OAA6B;QACtC,IAAI,CAAC,IAAI,CAAC,UAAU;YAAE,OAAM;QAC5B,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC;YAAE,IAAI,CAAC,OAAO,EAAE,CAAA;IAC3C,CAAC;IAED,YAAY;QACV,IAAI,CAAC,OAAO,EAAE,CAAA;QACd,IAAI,CAAC,OAAO,GAAG,IAAI,cAAc,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC,CAAA;QAC/D,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;IAC5B,CAAC;IAED,oBAAoB;QAClB,KAAK,CAAC,oBAAoB,EAAE,CAAA;QAC5B,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,CAAA;QAC1B,IAAI,CAAC,OAAO,GAAG,SAAS,CAAA;QACxB,IAAI,CAAC,QAAQ,EAAE,CAAA;IACjB,CAAC;IAED;;;;;OAKG;IACK,OAAO;QACb,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAA;QAC1B,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACzC,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,2BAA2B,CAAC,CAAA;YACpD,IAAI,CAAC,QAAQ,EAAE,CAAA;YACf,OAAM;QACR,CAAC;QAED,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;QACxC,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,QAAQ,KAAK,SAAS;YAAE,OAAM;QAErD,MAAM,EAAE,MAAM,EAAE,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAA;QACnC,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACtB,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,gCAAgC,CAAC,CAAA;YACzD,IAAI,CAAC,QAAQ,EAAE,CAAA;YACf,OAAM;QACR,CAAC;QAED,IAAI,CAAC,QAAQ,EAAE,CAAA;QACf,oDAAoD;QACpD,IAAI,CAAC,OAAO,GAAG,EAAE,CAAA;QAEjB,IAAI,CAAC;YACH,cAAc,CAAC,OAAO,CAAC,MAAM,CAAU,CAAC,CAAA;YAExC;;;;;cAKE;YACF,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,CAAA;YACvB,MAAM,OAAO,GAAG,cAAc,CAAA;YAC9B,kCAAkC;YAClC,MAAM,MAAM,GAAG,CAAC,GAAkC,EAAE,EAAE,CAAC,CAAC;gBACtD,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC;gBAC9C,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC;gBAC9C,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC;aAC/C,CAAC,CAAA;YACF,oDAAoD;YACpD,gBAAgB;YAChB,MAAM,OAAO,GAAG,CAAC,CAAA;YACjB,MAAM,IAAI,GAAG,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC,GAAG,OAAO,GAAG,IAAI,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC,GAAG,OAAO,GAAG,IAAI,EAAE,CAAA;YACvE,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,MAAM,GAAG,OAAO,CAAC,CAAA;YACrD,MAAM,KAAK,GAAG;gBACZ,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,OAAO,CAAC;gBACnC,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC;gBACjC;;;;;kBAKE;gBACF,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,OAAO,GAAG,GAAG,CAAC;aAC1C,CAAA;YAED;;;;;;cAME;YACF,MAAM,UAAU,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,EAAE,EAAE;gBAC9C,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,CAAA;gBACxB,MAAM,MAAM,GAAG,EAAE,GAAG,OAAO,CAAA;gBAC3B,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,OAAO,CAAC,CAAA;gBACpC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAA;gBACzC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAA;gBAE1C,OAAO;oBACL,IAAI,EAAE,MAAM,CAAC,IAAI;oBACjB,EAAE,EAAE,MAAM,GAAG,CAAC,IAAI,EAAE;oBACpB,4CAA4C;oBAC5C,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;oBACxD,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;oBACrD,KAAK;oBACL,MAAM;oBACN,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;iBACnC,CAAA;YACH,CAAC,CAAC,CAAA;YAEF,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC;gBAClB,MAAM,EAAE,IAAI,CAAC,KAAK;gBAClB,KAAK,EAAE;oBACL,IAAI,EAAE,aAAa;oBACnB,KAAK,EAAE,KAAK,CAAC,KAAK;oBAClB,MAAM,EAAE,KAAK,CAAC,MAAM;oBACpB,KAAK,EAAE,KAAK,CAAC,KAAK;oBAClB,MAAM,EAAE,IAAI;oBACZ,WAAW,EAAE,QAAQ;oBACrB,IAAI,EAAE,IAAI;oBACV,GAAG,EAAE,OAAO;oBACZ,QAAQ,EAAE,SAAS;oBACnB,KAAK,EAAE,KAAK;oBACZ,UAAU,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,gBAAgB,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;oBAC3D,UAAU;iBACX;gBACD,8BAA8B;gBAC9B,IAAI,EAAE,CAAC;aACR,CAAgB,CAAA;YAEjB,IAAI,CAAC,QAAQ,GAAG,SAAS,CAAA;YACzB,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,CAAA;YACrB,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,CAAA;YACzB,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,gBAAgB,EAAE,EAAE,CAAA;YACrC,IAAI,CAAC,MAAM,GAAG,EAAE,CAAA;QAClB,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,kCAAkC;YAClC,IAAI,CAAC,MAAM,GAAI,CAAW,CAAC,OAAO,CAAA;QACpC,CAAC;IACH,CAAC;IAEO,QAAQ;QACd,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,CAAA;QACvB,IAAI,CAAC,KAAK,GAAG,SAAS,CAAA;QACtB,IAAI,CAAC,QAAQ,GAAG,SAAS,CAAA;IAC3B,CAAC;;AAzN2B;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;6CAAsB;AAEpB;IAA5B,KAAK,CAAC,YAAY,CAAC;8BAAiB,cAAc;IAEnD,8BAA8B;;4CAFqB;AAGlC;IAAhB,KAAK,EAAE;;6CAAoB;AAQX;IAAhB,KAAK,EAAE;;8CAA6C;AAvG1C,aAAa;IADzB,aAAa,CAAC,gBAAgB,CAAC;GACnB,aAAa,CAoTzB","sourcesContent":["import { css, html, LitElement, unsafeCSS } from 'lit'\nimport { customElement, property, query, state } from 'lit/decorators.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport { compile, validate } from '@hatiolab/figure-model'\nimport type { FigureSource } from '@hatiolab/figure-model'\nimport { create, registerFigure } from '@hatiolab/things-scene'\n\n/**\n * 저작 중인 것을 **보드에 놓았을 때**의 모습.\n *\n * ## 편집 캔버스로는 볼 수 없다\n *\n * 편집 캔버스는 부품을 컴포넌트로 늘어놓는다. 그래서 늘 기준 크기다. 그런데 이 형식의\n * 값 절반은 **크기가 달라졌을 때 어떻게 반응하는가**에 있다. 기둥만 늘어나는 시그널타워,\n * 롤러 개수가 느는 컨베이어는 기준 크기에서는 아무것도 보여 주지 못한다.\n *\n * 그래서 여기서는 저작한 것을 실제로 컴파일해 `FigureInstance` 로 세운다. 보드가 하는\n * 일을 그대로 한다 — 청사진을 등록하고, 타입 이름만 적은 컴포넌트를 놓는다.\n *\n * ## 여러 크기를 한 번에 놓는다\n *\n * 하나만 놓으면 \"이 크기에서는 이렇다\" 밖에 알 수 없다. 기준 크기와 늘린 것을 나란히\n * 놓아야 무엇이 따라 늘고 무엇이 그대로인지 보인다.\n *\n * ## 편집하지 않는다\n *\n * 여기서 집거나 끌 수 없다. 편집은 캔버스에서 한다. 보여 주기만 하는 자리라 씬을\n * 뷰 모드로 세운다.\n */\n\n/** 배경. 편집 캔버스와 같은 값을 쓴다 — 갈리면 두 화면이 다른 도구처럼 보인다. */\nconst WORKBENCH = '#7c7c7c'\n\n/** 얼마나 키워 보나. */\nexport const PREVIEW_FACTOR = 2\n\n/**\n * 어느 축을 키워 놓아 보나 — **일곱 조합 전부.**\n *\n * 1 배 · 2 배 · 3 배를 나란히 놓던 자리다. 그러면 세 축이 늘 같이 늘어나므로, 축마다\n * 따로 걸리는 규칙을 눈으로 볼 수가 없었다. 실제로 못 보고 넘어간 것들이 있다 — 컨베이어의\n * 롤러가 폭을 안 따라가던 것, 문의 폭이 높이를 안 따라가던 것이 다 한 축만 늘렸을 때\n * 드러나는 것이었다.\n *\n * 여덟이다. 첫째가 저작한 크기이고 나머지 일곱이 조합이다. 한 줄로 늘어놓으면 화면에\n * 안 담겨서 **네 칸씩 두 줄**로 놓는다.\n */\nexport const PREVIEW_WAYS: readonly { name: string; axes: readonly ('x' | 'y' | 'z')[] }[] = [\n { name: '기준', axes: [] },\n { name: 'x', axes: ['x'] },\n { name: 'y', axes: ['y'] },\n { name: 'z', axes: ['z'] },\n { name: 'x·y', axes: ['x', 'y'] },\n { name: 'x·z', axes: ['x', 'z'] },\n { name: 'y·z', axes: ['y', 'z'] },\n { name: 'x·y·z', axes: ['x', 'y', 'z'] }\n]\n\ninterface SceneHandle {\n dispose?: () => void\n resize?: () => void\n fit?: (type?: string) => void\n root?: {\n invalidate?: () => void\n fitCameraToModel?: (margin?: number) => void\n /** 세워 둔 인스턴스들. clip 을 몰 때 전부에 같은 값을 준다. */\n components?: { set?: (property: string, value: unknown) => void }[]\n }\n}\n\n@customElement('figure-preview')\nexport class FigurePreview extends localize(i18next)(LitElement) {\n static styles = css`\n :host {\n position: relative;\n display: flex;\n overflow: hidden;\n background-color: ${unsafeCSS(WORKBENCH)};\n font: var(--label-font, inherit);\n font-size: 0.76rem;\n }\n\n div[stage] {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n }\n\n /* 어느 것이 몇 배인지 — 나란히 놓아도 표가 없으면 읽을 수 없다 */\n div[legend] {\n position: absolute;\n top: 8px;\n left: 8px;\n display: flex;\n gap: 6px;\n padding: 4px 8px;\n color: rgba(255, 255, 255, 0.9);\n background-color: rgba(28, 28, 30, 0.6);\n border-radius: 6px;\n font-size: 0.72rem;\n }\n\n /*\n Clip 조작. **저작자가 제 애니메이션을 만져 볼 유일한 자리다.**\n\n 보드에서는 트윈이 clip 을 몬다 — 「닙이 40% 열렸다」는 사실이 어디선가 온다. 저작\n 중에는 그 사실을 주는 것이 없어서, 손대지 않으면 hold 인 clip 은 0 에 머문 채\n 영영 안 움직인다. 포크가 안 올라가는 것을 저작자가 「안 되는구나」로 읽게 된다.\n\n **미리보기 것이지 자산의 것이 아니다.** 여기서 민 값은 저장되지 않는다.\n */\n div[clips] {\n position: absolute;\n right: 8px;\n bottom: 8px;\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 8px 10px;\n color: rgba(255, 255, 255, 0.92);\n background-color: rgba(28, 28, 30, 0.72);\n border-radius: 8px;\n font-size: 0.72rem;\n }\n div[clips] label {\n display: grid;\n grid-template-columns: auto 96px 34px;\n align-items: center;\n gap: 8px;\n }\n div[clips] span[drive] {\n opacity: 0.6;\n font-size: 0.66rem;\n }\n div[clips] input {\n width: 96px;\n accent-color: var(--md-sys-color-primary);\n }\n div[clips] output {\n font-variant-numeric: tabular-nums;\n text-align: right;\n opacity: 0.85;\n }\n\n /* 세울 수 없는 상태를 빈 화면으로 두지 않는다 — 왜 안 보이는지 말한다 */\n div[cannot] {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: var(--spacing-large, 12px);\n text-align: center;\n line-height: 1.6;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n background-color: ${unsafeCSS(WORKBENCH)};\n }\n `\n\n /** 저작 중인 것. 저장 형식으로 받는다 — 보드가 소비하는 것이 이 형식이다. */\n @property({ type: Object }) source?: FigureSource\n\n @query('div[stage]') private stage!: HTMLDivElement\n\n /** 세울 수 없는 사유. 없으면 세운 것이다. */\n @state() private cannot = ''\n\n private scene?: SceneHandle\n /** 지금 세워 둔 것의 값. 같으면 다시 세우지 않는다. */\n private standing?: string\n private watcher?: ResizeObserver\n\n /** 지금 민 clip 값. clip 이름 → 수. **저장되지 않는다.** */\n @state() private driving: Record<string, number> = {}\n\n render() {\n const clips = this.source?.animations ?? []\n\n return html`\n <div stage></div>\n ${this.cannot\n ? html`<div cannot>${this.cannot}</div>`\n : html`<div legend>\n ${PREVIEW_WAYS.map(way => html`<span>${way.name}</span>`)}\n </div>\n ${clips.length > 0 ? this.renderClips(clips) : ''}`}\n `\n }\n\n /**\n * Clip 하나에 손잡이 하나.\n *\n * 수의 뜻이 `drive` 에 따라 다르므로 그것을 이름 옆에 적는다. `loop` 는 배속이라 0 이\n * 멈춤이고, `hold` 는 0~1 위치라 0 이 처음이다. 둘을 같은 눈금으로 두면 「1」이 무슨\n * 뜻인지 매번 헷갈린다.\n */\n private renderClips(clips: NonNullable<FigureSource['animations']>) {\n return html`\n <div clips>\n ${clips.map(clip => {\n const loop = (clip.drive ?? 'loop') === 'loop'\n const value = this.driving[clip.name] ?? (loop ? 1 : 0)\n\n return html`\n <label>\n <span>${clip.name} <span drive>${loop ? '배속' : '위치'}</span></span>\n <input\n type=\"range\"\n min=\"0\"\n max=${loop ? 3 : 1}\n step=\"0.05\"\n .value=${String(value)}\n @input=${(e: Event) => this.drive(clip.name, Number((e.target as HTMLInputElement).value))}\n />\n <output>${value.toFixed(2)}</output>\n </label>\n `\n })}\n </div>\n `\n }\n\n /**\n * 세워 둔 인스턴스 전부에 같은 값을 준다.\n *\n * 하나만 몰면 세 배율이 서로 다른 자세로 서서, 크기 반응을 보려고 나란히 놓은 뜻이\n * 사라진다.\n */\n private drive(name: string, value: number): void {\n this.driving = { ...this.driving, [name]: value }\n\n for (const component of this.scene?.root?.components ?? []) {\n component.set?.('clips', { ...this.driving })\n }\n }\n\n willUpdate(changed: Map<string, unknown>) {\n if (!this.hasUpdated) return\n if (changed.has('source')) this.standUp()\n }\n\n firstUpdated() {\n this.standUp()\n this.watcher = new ResizeObserver(() => this.scene?.resize?.())\n this.watcher.observe(this)\n }\n\n disconnectedCallback() {\n super.disconnectedCallback()\n this.watcher?.disconnect()\n this.watcher = undefined\n this.teardown()\n }\n\n /**\n * 저작한 것을 컴파일해 세운다.\n *\n * **형식을 어겼으면 세우지 않는다.** 반쯤 세운 것을 보여 주면 무엇이 잘못됐는지 알 수\n * 없다. 판정 패널이 이미 사유를 말하고 있으므로 여기서는 세울 수 없다고만 한다.\n */\n private standUp() {\n const source = this.source\n if (!source || source.parts.length === 0) {\n this.cannot = i18next.t('text.preview-needs-a-part')\n this.teardown()\n return\n }\n\n const signature = JSON.stringify(source)\n if (this.scene && this.standing === signature) return\n\n const { errors } = validate(source)\n if (errors.length > 0) {\n this.cannot = i18next.t('text.preview-blocked-by-errors')\n this.teardown()\n return\n }\n\n this.teardown()\n // 표본이 바뀌면 민 값도 처음으로 돌린다 — 다른 자산의 clip 이름이 남으면 안 된다.\n this.driving = {}\n\n try {\n registerFigure(compile(source) as never)\n\n /*\n 보드가 하는 일을 그대로 한다. 컴포넌트에는 타입 이름과 자리·크기만 적는다 —\n 청사진도 원본도 보드에 들어가지 않는다.\n\n 가장 큰 것이 들어갈 만큼 판을 잡고, 세로로 줄을 나눠 나란히 놓는다.\n */\n const { base } = source\n const biggest = PREVIEW_FACTOR\n /** 이 조합에서 축별 배율. 조합에 든 축만 키운다. */\n const growth = (way: (typeof PREVIEW_WAYS)[number]) => ({\n x: way.axes.includes('x') ? PREVIEW_FACTOR : 1,\n y: way.axes.includes('y') ? PREVIEW_FACTOR : 1,\n z: way.axes.includes('z') ? PREVIEW_FACTOR : 1\n })\n // 여백을 넉넉히 두면 화면에서 형상이 작아진다. 가장 큰 것이 겨우 들어갈 만큼만 잡는다.\n /** 네 칸씩 두 줄. */\n const COLUMNS = 4\n const cell = { x: base.x * biggest * 1.25, z: base.z * biggest * 1.25 }\n const rows = Math.ceil(PREVIEW_WAYS.length / COLUMNS)\n const board = {\n width: Math.round(cell.x * COLUMNS),\n height: Math.round(cell.z * rows),\n /*\n 판의 높이도 가장 큰 인스턴스를 담아야 한다.\n\n 안 주면 카메라가 평면만 보고 자리를 잡아서, 키가 큰 모델은 밑동만 보인다.\n 시그널타워를 2 배로 놓으면 2200 인데 판이 그것을 모르면 받침만 화면에 든다.\n */\n depth: Math.round(base.y * biggest * 1.1)\n }\n\n /*\n 씬의 낱말이 형식과 다르다 — `width` 가 형식의 x, `height` 가 z, `depth` 가 y 다.\n 옮기는 일을 여기 한 줄에 모아 둔다.\n\n `depth` 를 늘 적는 이유가 있다. 안 적으면 평면 두 축의 기하평균으로 잡혀서,\n 「y 만 키운다」가 x·z 를 안 건드리는데도 높이가 안 오른다.\n */\n const components = PREVIEW_WAYS.map((way, at) => {\n const grow = growth(way)\n const column = at % COLUMNS\n const row = Math.floor(at / COLUMNS)\n const width = Math.round(base.x * grow.x)\n const height = Math.round(base.z * grow.z)\n\n return {\n type: source.type,\n id: `at-${way.name}`,\n // 칸 가운데에 놓는다 — 크기가 저마다 달라 왼쪽에 맞추면 줄이 안 읽힌다.\n left: Math.round(column * cell.x + (cell.x - width) / 2),\n top: Math.round(row * cell.z + (cell.z - height) / 2),\n width,\n height,\n depth: Math.round(base.y * grow.y)\n }\n })\n\n this.scene = create({\n target: this.stage,\n model: {\n type: 'model-layer',\n width: board.width,\n height: board.height,\n depth: board.depth,\n threed: true,\n environment: 'studio',\n grid: true,\n sky: 'color',\n skyColor: WORKBENCH,\n floor: false,\n pixelRatio: Math.min(3, (window.devicePixelRatio || 1) * 2),\n components\n },\n // 보기만 하는 자리다. 집거나 끌 수 없어야 한다.\n mode: 0\n }) as SceneHandle\n\n this.standing = signature\n this.scene.resize?.()\n this.scene.fit?.('ratio')\n this.scene.root?.fitCameraToModel?.()\n this.cannot = ''\n } catch (e) {\n // 원인을 지어내지 않는다 — 던진 말을 그대로 보여 준다.\n this.cannot = (e as Error).message\n }\n }\n\n private teardown() {\n this.scene?.dispose?.()\n this.scene = undefined\n this.standing = undefined\n }\n}\n"]}
1
+ {"version":3,"file":"figure-preview.js","sourceRoot":"","sources":["../../client/modeller/figure-preview.ts"],"names":[],"mappings":"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","sourcesContent":["import { css, html, LitElement, unsafeCSS } from 'lit'\nimport { customElement, property, query, state } from 'lit/decorators.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport { compile, validate } from '@hatiolab/figure-model'\nimport type { FigureSource } from '@hatiolab/figure-model'\nimport { create, registerFigure } from '@hatiolab/things-scene'\n\n/**\n * 저작 중인 것을 **보드에 놓았을 때**의 모습.\n *\n * ## 편집 캔버스로는 볼 수 없다\n *\n * 편집 캔버스는 부품을 컴포넌트로 늘어놓는다. 그래서 늘 기준 크기다. 그런데 이 형식의\n * 값 절반은 **크기가 달라졌을 때 어떻게 반응하는가**에 있다. 기둥만 늘어나는 시그널타워,\n * 롤러 개수가 느는 컨베이어는 기준 크기에서는 아무것도 보여 주지 못한다.\n *\n * 그래서 여기서는 저작한 것을 실제로 컴파일해 `FigureInstance` 로 세운다. 보드가 하는\n * 일을 그대로 한다 — 청사진을 등록하고, 타입 이름만 적은 컴포넌트를 놓는다.\n *\n * ## 여러 크기를 한 번에 놓는다\n *\n * 하나만 놓으면 \"이 크기에서는 이렇다\" 밖에 알 수 없다. 기준 크기와 늘린 것을 나란히\n * 놓아야 무엇이 따라 늘고 무엇이 그대로인지 보인다.\n *\n * ## 편집하지 않는다\n *\n * 여기서 집거나 끌 수 없다. 편집은 캔버스에서 한다. 보여 주기만 하는 자리라 씬을\n * 뷰 모드로 세운다.\n */\n\n/** 배경. 편집 캔버스와 같은 값을 쓴다 — 갈리면 두 화면이 다른 도구처럼 보인다. */\nconst WORKBENCH = '#7c7c7c'\n\n/** 얼마나 키워 보나. */\nexport const PREVIEW_FACTOR = 2\n\n/**\n * 어느 축을 키워 놓아 보나 — **일곱 조합 전부.**\n *\n * 1 배 · 2 배 · 3 배를 나란히 놓던 자리다. 그러면 세 축이 늘 같이 늘어나므로, 축마다\n * 따로 걸리는 규칙을 눈으로 볼 수가 없었다. 실제로 못 보고 넘어간 것들이 있다 — 컨베이어의\n * 롤러가 폭을 안 따라가던 것, 문의 폭이 높이를 안 따라가던 것이 다 한 축만 늘렸을 때\n * 드러나는 것이었다.\n *\n * 여덟이다. 첫째가 저작한 크기이고 나머지 일곱이 조합이다. 한 줄로 늘어놓으면 화면에\n * 안 담겨서 **네 칸씩 두 줄**로 놓는다.\n */\nexport const PREVIEW_WAYS: readonly { name: string; axes: readonly ('x' | 'y' | 'z')[] }[] = [\n { name: '기준', axes: [] },\n { name: 'x', axes: ['x'] },\n { name: 'y', axes: ['y'] },\n { name: 'z', axes: ['z'] },\n { name: 'x·y', axes: ['x', 'y'] },\n { name: 'x·z', axes: ['x', 'z'] },\n { name: 'y·z', axes: ['y', 'z'] },\n { name: 'x·y·z', axes: ['x', 'y', 'z'] }\n]\n\ninterface SceneHandle {\n dispose?: () => void\n resize?: () => void\n fit?: (type?: string) => void\n root?: {\n invalidate?: () => void\n fitCameraToModel?: (margin?: number) => void\n /** 세워 둔 인스턴스들. clip 을 몰 때 전부에 같은 값을 준다. */\n components?: { set?: (property: string, value: unknown) => void }[]\n }\n}\n\n@customElement('figure-preview')\nexport class FigurePreview extends localize(i18next)(LitElement) {\n static styles = css`\n :host {\n position: relative;\n display: flex;\n overflow: hidden;\n background-color: ${unsafeCSS(WORKBENCH)};\n font: var(--label-font, inherit);\n font-size: 0.76rem;\n }\n\n div[stage] {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n }\n\n /* 어느 것이 몇 배인지 — 나란히 놓아도 표가 없으면 읽을 수 없다 */\n div[legend] {\n position: absolute;\n top: 8px;\n left: 8px;\n display: flex;\n gap: 6px;\n padding: 4px 8px;\n color: rgba(255, 255, 255, 0.9);\n background-color: rgba(28, 28, 30, 0.6);\n border-radius: 6px;\n font-size: 0.72rem;\n }\n\n /*\n Clip 조작. **저작자가 제 애니메이션을 만져 볼 유일한 자리다.**\n\n 보드에서는 트윈이 clip 을 몬다 — 「닙이 40% 열렸다」는 사실이 어디선가 온다. 저작\n 중에는 그 사실을 주는 것이 없어서, 손대지 않으면 hold 인 clip 은 0 에 머문 채\n 영영 안 움직인다. 포크가 안 올라가는 것을 저작자가 「안 되는구나」로 읽게 된다.\n\n **미리보기 것이지 자산의 것이 아니다.** 여기서 민 값은 저장되지 않는다.\n */\n div[clips] {\n position: absolute;\n right: 8px;\n bottom: 8px;\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 8px 10px;\n color: rgba(255, 255, 255, 0.92);\n background-color: rgba(28, 28, 30, 0.72);\n border-radius: 8px;\n font-size: 0.72rem;\n }\n div[clips] label {\n display: grid;\n grid-template-columns: auto 96px 34px;\n align-items: center;\n gap: 8px;\n }\n div[clips] span[drive] {\n opacity: 0.6;\n font-size: 0.66rem;\n }\n div[clips] input {\n width: 96px;\n accent-color: var(--md-sys-color-primary);\n }\n div[clips] output {\n font-variant-numeric: tabular-nums;\n text-align: right;\n opacity: 0.85;\n }\n\n /* 세울 수 없는 상태를 빈 화면으로 두지 않는다 — 왜 안 보이는지 말한다 */\n div[cannot] {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: var(--spacing-large, 12px);\n text-align: center;\n line-height: 1.6;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n background-color: ${unsafeCSS(WORKBENCH)};\n }\n `\n\n /** 저작 중인 것. 저장 형식으로 받는다 — 보드가 소비하는 것이 이 형식이다. */\n @property({ type: Object }) source?: FigureSource\n\n @query('div[stage]') private stage!: HTMLDivElement\n\n /** 세울 수 없는 사유. 없으면 세운 것이다. */\n @state() private cannot = ''\n\n private scene?: SceneHandle\n /** 지금 세워 둔 것의 값. 같으면 다시 세우지 않는다. */\n private standing?: string\n private watcher?: ResizeObserver\n\n /** 지금 민 clip 값. clip 이름 → 수. **저장되지 않는다.** */\n @state() private driving: Record<string, number> = {}\n\n render() {\n const clips = this.source?.animations ?? []\n\n return html`\n <div stage></div>\n ${this.cannot\n ? html`<div cannot>${this.cannot}</div>`\n : html`<div legend>\n ${PREVIEW_WAYS.map(way => html`<span>${way.name}</span>`)}\n </div>\n ${clips.length > 0 ? this.renderClips(clips) : ''}`}\n `\n }\n\n /**\n * Clip 하나에 손잡이 하나.\n *\n * 수의 뜻이 `drive` 에 따라 다르므로 그것을 이름 옆에 적는다. `loop` 는 배속이라 0 이\n * 멈춤이고, `hold` 는 0~1 위치라 0 이 처음이다. 둘을 같은 눈금으로 두면 「1」이 무슨\n * 뜻인지 매번 헷갈린다.\n */\n private renderClips(clips: NonNullable<FigureSource['animations']>) {\n return html`\n <div clips>\n ${clips.map(clip => {\n const loop = (clip.drive ?? 'loop') === 'loop'\n const value = this.driving[clip.name] ?? (loop ? 1 : 0)\n\n return html`\n <label>\n <span>${clip.name} <span drive>${loop ? '배속' : '위치'}</span></span>\n <input\n type=\"range\"\n min=\"0\"\n max=${loop ? 3 : 1}\n step=\"0.05\"\n .value=${String(value)}\n @input=${(e: Event) => this.drive(clip.name, Number((e.target as HTMLInputElement).value))}\n />\n <output>${value.toFixed(2)}</output>\n </label>\n `\n })}\n </div>\n `\n }\n\n /**\n * 세워 둔 인스턴스 전부에 같은 값을 준다.\n *\n * 하나만 몰면 세 배율이 서로 다른 자세로 서서, 크기 반응을 보려고 나란히 놓은 뜻이\n * 사라진다.\n */\n private drive(name: string, value: number): void {\n this.driving = { ...this.driving, [name]: value }\n\n for (const component of this.scene?.root?.components ?? []) {\n component.set?.('clips', { ...this.driving })\n }\n }\n\n willUpdate(changed: Map<string, unknown>) {\n if (!this.hasUpdated) return\n if (changed.has('source')) this.standUp()\n }\n\n firstUpdated() {\n this.standUp()\n this.watcher = new ResizeObserver(() => this.scene?.resize?.())\n this.watcher.observe(this)\n }\n\n disconnectedCallback() {\n super.disconnectedCallback()\n this.watcher?.disconnect()\n this.watcher = undefined\n this.teardown()\n }\n\n /**\n * 저작한 것을 컴파일해 세운다.\n *\n * **형식을 어겼으면 세우지 않는다.** 반쯤 세운 것을 보여 주면 무엇이 잘못됐는지 알 수\n * 없다. 판정 패널이 이미 사유를 말하고 있으므로 여기서는 세울 수 없다고만 한다.\n */\n private standUp() {\n const source = this.source\n if (!source || source.parts.length === 0) {\n this.cannot = i18next.t('figure.text.preview-needs-a-part')\n this.teardown()\n return\n }\n\n const signature = JSON.stringify(source)\n if (this.scene && this.standing === signature) return\n\n const { errors } = validate(source)\n if (errors.length > 0) {\n this.cannot = i18next.t('figure.text.preview-blocked-by-errors')\n this.teardown()\n return\n }\n\n this.teardown()\n // 표본이 바뀌면 민 값도 처음으로 돌린다 — 다른 자산의 clip 이름이 남으면 안 된다.\n this.driving = {}\n\n try {\n registerFigure(compile(source) as never)\n\n /*\n 보드가 하는 일을 그대로 한다. 컴포넌트에는 타입 이름과 자리·크기만 적는다 —\n 청사진도 원본도 보드에 들어가지 않는다.\n\n 가장 큰 것이 들어갈 만큼 판을 잡고, 세로로 줄을 나눠 나란히 놓는다.\n */\n const { base } = source\n const biggest = PREVIEW_FACTOR\n /** 이 조합에서 축별 배율. 조합에 든 축만 키운다. */\n const growth = (way: (typeof PREVIEW_WAYS)[number]) => ({\n x: way.axes.includes('x') ? PREVIEW_FACTOR : 1,\n y: way.axes.includes('y') ? PREVIEW_FACTOR : 1,\n z: way.axes.includes('z') ? PREVIEW_FACTOR : 1\n })\n // 여백을 넉넉히 두면 화면에서 형상이 작아진다. 가장 큰 것이 겨우 들어갈 만큼만 잡는다.\n /** 네 칸씩 두 줄. */\n const COLUMNS = 4\n const cell = { x: base.x * biggest * 1.25, z: base.z * biggest * 1.25 }\n const rows = Math.ceil(PREVIEW_WAYS.length / COLUMNS)\n const board = {\n width: Math.round(cell.x * COLUMNS),\n height: Math.round(cell.z * rows),\n /*\n 판의 높이도 가장 큰 인스턴스를 담아야 한다.\n\n 안 주면 카메라가 평면만 보고 자리를 잡아서, 키가 큰 모델은 밑동만 보인다.\n 시그널타워를 2 배로 놓으면 2200 인데 판이 그것을 모르면 받침만 화면에 든다.\n */\n depth: Math.round(base.y * biggest * 1.1)\n }\n\n /*\n 씬의 낱말이 형식과 다르다 — `width` 가 형식의 x, `height` 가 z, `depth` 가 y 다.\n 옮기는 일을 여기 한 줄에 모아 둔다.\n\n `depth` 를 늘 적는 이유가 있다. 안 적으면 평면 두 축의 기하평균으로 잡혀서,\n 「y 만 키운다」가 x·z 를 안 건드리는데도 높이가 안 오른다.\n */\n const components = PREVIEW_WAYS.map((way, at) => {\n const grow = growth(way)\n const column = at % COLUMNS\n const row = Math.floor(at / COLUMNS)\n const width = Math.round(base.x * grow.x)\n const height = Math.round(base.z * grow.z)\n\n return {\n type: source.type,\n id: `at-${way.name}`,\n // 칸 가운데에 놓는다 — 크기가 저마다 달라 왼쪽에 맞추면 줄이 안 읽힌다.\n left: Math.round(column * cell.x + (cell.x - width) / 2),\n top: Math.round(row * cell.z + (cell.z - height) / 2),\n width,\n height,\n depth: Math.round(base.y * grow.y)\n }\n })\n\n this.scene = create({\n target: this.stage,\n model: {\n type: 'model-layer',\n width: board.width,\n height: board.height,\n depth: board.depth,\n threed: true,\n environment: 'studio',\n grid: true,\n sky: 'color',\n skyColor: WORKBENCH,\n floor: false,\n pixelRatio: Math.min(3, (window.devicePixelRatio || 1) * 2),\n components\n },\n // 보기만 하는 자리다. 집거나 끌 수 없어야 한다.\n mode: 0\n }) as SceneHandle\n\n this.standing = signature\n this.scene.resize?.()\n this.scene.fit?.('ratio')\n this.scene.root?.fitCameraToModel?.()\n this.cannot = ''\n } catch (e) {\n // 원인을 지어내지 않는다 — 던진 말을 그대로 보여 준다.\n this.cannot = (e as Error).message\n }\n }\n\n private teardown() {\n this.scene?.dispose?.()\n this.scene = undefined\n this.standing = undefined\n }\n}\n"]}
@@ -30,6 +30,23 @@ export declare class FigureReport extends FigureReport_base {
30
30
  private expanded;
31
31
  private errors;
32
32
  private violations;
33
+ /**
34
+ * 크기를 바꿔 봤을 때 갈라지는가 — **발행을 막는 것**이다.
35
+ *
36
+ * 이력 탭이 같은 판정을 서버에서 받아 발행 문을 잠그지만, 그것은 **저장된 정본**에 대한
37
+ * 것이라 저장하기 전에는 답하지 못한다. 여기서는 저작 중인 정본을 그대로 재므로, 부품을
38
+ * 옮기는 그 자리에서 갈라짐이 생겼는지 사라졌는지 보인다 — 저장하고 탭을 옮겨 확인하게
39
+ * 두면 고칠 때가 이미 지나 있다.
40
+ */
41
+ private sizing;
42
+ /**
43
+ * 아직 부품이 하나도 없나 — **저작 전이지 형식을 어긴 것이 아니다.**
44
+ *
45
+ * 형식은 부품 0개를 오류로 본다(`missing-parts`). 발행 관점에서는 맞다. 그런데 새로 만들기를
46
+ * 누른 직후가 바로 그 상태이므로, 그것을 그대로 오류로 그리면 **아무것도 하지 않았는데 붉은
47
+ * 화면**을 보게 된다. 그래서 이 자리에서 둘을 가른다.
48
+ */
49
+ private bare;
33
50
  willUpdate(changed: Map<string, unknown>): void;
34
51
  render(): import("lit-html").TemplateResult<1>;
35
52
  /**
@@ -98,7 +115,7 @@ export declare class FigureReport extends FigureReport_base {
98
115
  * 그것을 그대로 실으면 다른 언어 사용자에게 한국어가 나온다.
99
116
  *
100
117
  * `budgets` 는 **배열**이다. `Object.entries` 로 돌면 키가 인덱스(0·1·2…)가 되어
101
- * `label.budget-0` 같은 없는 키를 찾는다 — 화면을 띄워 보고서야 드러났다.
118
+ * `figure.label.budget-0` 같은 없는 키를 찾는다 — 화면을 띄워 보고서야 드러났다.
102
119
  * 배열을 그대로 돌고 `use.key` 를 쓴다.
103
120
  *
104
121
  * 그래서 **안정된 `key`** 로 이 화면의 i18n 을 찾는다. `key` 는 열거형이라 형식의
@@ -5,7 +5,7 @@ import { customElement, property, state } from 'lit/decorators.js';
5
5
  import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
6
6
  import { i18next, localize } from '@operato/i18n';
7
7
  import { ScrollbarStyles } from '@operato/styles';
8
- import { compile, costCurveSvg, costOf, scoreOf, validate } from '@hatiolab/figure-model';
8
+ import { compile, costCurveSvg, costOf, scoreOf, sizingReport, validate } from '@hatiolab/figure-model';
9
9
  import { capacityOf } from './scene-budget.js';
10
10
  import { maturityOf } from './maturity.js';
11
11
  /**
@@ -29,6 +29,23 @@ let FigureReport = class FigureReport extends localize(i18next)(LitElement) {
29
29
  this.expanded = false;
30
30
  this.errors = [];
31
31
  this.violations = [];
32
+ /**
33
+ * 크기를 바꿔 봤을 때 갈라지는가 — **발행을 막는 것**이다.
34
+ *
35
+ * 이력 탭이 같은 판정을 서버에서 받아 발행 문을 잠그지만, 그것은 **저장된 정본**에 대한
36
+ * 것이라 저장하기 전에는 답하지 못한다. 여기서는 저작 중인 정본을 그대로 재므로, 부품을
37
+ * 옮기는 그 자리에서 갈라짐이 생겼는지 사라졌는지 보인다 — 저장하고 탭을 옮겨 확인하게
38
+ * 두면 고칠 때가 이미 지나 있다.
39
+ */
40
+ this.sizing = [];
41
+ /**
42
+ * 아직 부품이 하나도 없나 — **저작 전이지 형식을 어긴 것이 아니다.**
43
+ *
44
+ * 형식은 부품 0개를 오류로 본다(`missing-parts`). 발행 관점에서는 맞다. 그런데 새로 만들기를
45
+ * 누른 직후가 바로 그 상태이므로, 그것을 그대로 오류로 그리면 **아무것도 하지 않았는데 붉은
46
+ * 화면**을 보게 된다. 그래서 이 자리에서 둘을 가른다.
47
+ */
48
+ this.bare = false;
32
49
  }
33
50
  static { this.styles = [
34
51
  ScrollbarStyles,
@@ -432,10 +449,17 @@ let FigureReport = class FigureReport extends localize(i18next)(LitElement) {
432
449
  }
433
450
  }
434
451
  render() {
435
- if (!this.source) {
452
+ /*
453
+ 부품이 없으면 이 자리는 **판정이 아니라 안내**다. 이미 있던 「부품을 추가하면 나옵니다」가
454
+ 정확히 그 말이므로 그것을 쓴다 — 같은 상태에 문장을 둘 두지 않는다.
455
+
456
+ 아래 셈들(머리·축·한도·추이)은 부품이 있어야 뜻이 있고, 재지 않은 값을 들고 그리면 0 으로
457
+ 보인다. 0 은 「나쁘다」로 읽히므로 그 자리에 두지 않는다.
458
+ */
459
+ if (!this.source || this.bare) {
436
460
  return html `
437
461
  <section>
438
- <p quiet>${i18next.t('text.report-appears-when-you-add-a-part')}</p>
462
+ <p quiet>${i18next.t('figure.text.report-appears-when-you-add-a-part')}</p>
439
463
  </section>
440
464
  `;
441
465
  }
@@ -465,34 +489,34 @@ let FigureReport = class FigureReport extends localize(i18next)(LitElement) {
465
489
  return nothing;
466
490
  return html `
467
491
  <section>
468
- <h3>${i18next.t('label.maturity')}</h3>
469
- <p lead>${i18next.t('label.axis-scale')} — ${i18next.t('text.how-many-fit')}</p>
492
+ <h3>${i18next.t('figure.label.maturity')}</h3>
493
+ <p lead>${i18next.t('figure.label.axis-scale')} — ${i18next.t('figure.text.how-many-fit')}</p>
470
494
  <div verdict>
471
495
  <span capacity>${capacity.instances.toLocaleString()}</span>
472
- <span unit>${i18next.t('text.instances-unit')}</span>
496
+ <span unit>${i18next.t('figure.text.instances-unit')}</span>
473
497
  <span
474
498
  grade
475
499
  ?tight=${score.grade === 'C'}
476
500
  ?over=${score.grade === 'D'}
477
- title=${i18next.t('text.grade-explained')}
501
+ title=${i18next.t('figure.text.grade-explained')}
478
502
  >
479
503
  ${score.grade}
480
- <small>${i18next.t('text.headroom-percent', { percent: Math.round(score.score) })}</small>
504
+ <small>${i18next.t('figure.text.headroom-percent', { percent: Math.round(score.score) })}</small>
481
505
  </span>
482
506
  </div>
483
507
  <p because>
484
- ${i18next.t('text.grade-decided-by')}
485
- <strong>${i18next.t('label.budget-' + score.tightest.key)}</strong>
508
+ ${i18next.t('figure.text.grade-decided-by')}
509
+ <strong>${i18next.t('figure.label.budget-' + score.tightest.key)}</strong>
486
510
  — ${score.tightest.used} / ${score.tightest.limit}
487
511
  (${Math.round((score.tightest.used / score.tightest.limit) * 100)}%)
488
512
  </p>
489
513
  ${this.renderGradeScale(score.grade)}
490
514
  <p basis>
491
- ${i18next.t('text.bound-by-' + capacity.bound, {
515
+ ${i18next.t('figure.text.bound-by-' + capacity.bound, {
492
516
  per: capacity.per.toLocaleString(),
493
517
  budget: capacity.budget.toLocaleString()
494
518
  })}
495
- <em>${i18next.t('text.budget-not-measured-yet')}</em>
519
+ <em>${i18next.t('figure.text.budget-not-measured-yet')}</em>
496
520
  </p>
497
521
  </section>
498
522
  `;
@@ -515,7 +539,7 @@ let FigureReport = class FigureReport extends localize(i18next)(LitElement) {
515
539
  ];
516
540
  return html `
517
541
  <p scale>
518
- <span>${i18next.t('text.grade-scale-lead')}</span>
542
+ <span>${i18next.t('figure.text.grade-scale-lead')}</span>
519
543
  ${bounds.map(([grade, bound]) => html `<span ?now=${grade === now}><b>${grade}</b> ${bound}</span>`)}
520
544
  </p>
521
545
  `;
@@ -542,7 +566,7 @@ let FigureReport = class FigureReport extends localize(i18next)(LitElement) {
542
566
  const all = maturityOf(this.source, this.cost, this.score);
543
567
  return html `
544
568
  <section>
545
- <h3>${i18next.t('label.by-axis')}</h3>
569
+ <h3>${i18next.t('figure.label.by-axis')}</h3>
546
570
  <div radar>${this.renderRadar(all.filter(axis => axis.ratio !== undefined))}</div>
547
571
  ${this.renderUnmeasured(all.filter(axis => axis.ratio === undefined))}
548
572
  <ul axes>
@@ -592,7 +616,7 @@ let FigureReport = class FigureReport extends localize(i18next)(LitElement) {
592
616
  const anchor = x < cx - 3 ? 'end' : x > cx + 3 ? 'start' : 'middle';
593
617
  return svg `
594
618
  <text x=${x.toFixed(1)} y=${(y + 1.8).toFixed(1)} text-anchor=${anchor}>
595
- ${i18next.t('label.axis-' + axis.key + '-short')}
619
+ ${i18next.t('figure.label.axis-' + axis.key + '-short')}
596
620
  </text>
597
621
  `;
598
622
  })}
@@ -611,19 +635,19 @@ let FigureReport = class FigureReport extends localize(i18next)(LitElement) {
611
635
  return nothing;
612
636
  return html `
613
637
  <p unmeasured>
614
- ${i18next.t('text.axes-not-measurable-yet', {
615
- axes: axes.map(axis => i18next.t('label.axis-' + axis.key)).join(' · ')
638
+ ${i18next.t('figure.text.axes-not-measurable-yet', {
639
+ axes: axes.map(axis => i18next.t('figure.label.axis-' + axis.key)).join(' · ')
616
640
  })}
617
641
  </p>
618
642
  `;
619
643
  }
620
644
  renderAxis(axis) {
621
- const name = i18next.t('label.axis-' + axis.key);
645
+ const name = i18next.t('figure.label.axis-' + axis.key);
622
646
  if (axis.blocked) {
623
647
  return html `
624
648
  <li blocked>
625
649
  <span>${name}</span>
626
- <span reading>${i18next.t('text.not-measurable-yet')}</span>
650
+ <span reading>${i18next.t('figure.text.not-measurable-yet')}</span>
627
651
  <em>${i18next.t(axis.blocked)}</em>
628
652
  </li>
629
653
  `;
@@ -651,7 +675,7 @@ let FigureReport = class FigureReport extends localize(i18next)(LitElement) {
651
675
  * 그것을 그대로 실으면 다른 언어 사용자에게 한국어가 나온다.
652
676
  *
653
677
  * `budgets` 는 **배열**이다. `Object.entries` 로 돌면 키가 인덱스(0·1·2…)가 되어
654
- * `label.budget-0` 같은 없는 키를 찾는다 — 화면을 띄워 보고서야 드러났다.
678
+ * `figure.label.budget-0` 같은 없는 키를 찾는다 — 화면을 띄워 보고서야 드러났다.
655
679
  * 배열을 그대로 돌고 `use.key` 를 쓴다.
656
680
  *
657
681
  * 그래서 **안정된 `key`** 로 이 화면의 i18n 을 찾는다. `key` 는 열거형이라 형식의
@@ -664,14 +688,14 @@ let FigureReport = class FigureReport extends localize(i18next)(LitElement) {
664
688
  return nothing;
665
689
  return html `
666
690
  <section>
667
- <h3>${i18next.t('label.budgets')}</h3>
691
+ <h3>${i18next.t('figure.label.budgets')}</h3>
668
692
  <ul budgets>
669
693
  ${score.budgets.map(use => {
670
694
  const ratio = Math.min(1, use.ratio);
671
695
  const tight = use.key === score.tightest.key;
672
696
  return html `
673
697
  <li>
674
- <span>${i18next.t('label.budget-' + use.key)}</span>
698
+ <span>${i18next.t('figure.label.budget-' + use.key)}</span>
675
699
  <span>${use.used} / ${use.limit}</span>
676
700
  <div bar><i ?tight=${tight} style="width:${Math.round(ratio * 100)}%"></i></div>
677
701
  </li>
@@ -687,21 +711,21 @@ let FigureReport = class FigureReport extends localize(i18next)(LitElement) {
687
711
  return nothing;
688
712
  return html `
689
713
  <section>
690
- <h3>${i18next.t('label.weight')}</h3>
714
+ <h3>${i18next.t('figure.label.weight')}</h3>
691
715
  <dl metrics>
692
- <dt>${i18next.t('label.triangles')}</dt>
716
+ <dt>${i18next.t('figure.label.triangles')}</dt>
693
717
  <dd>${cost.triangles.toLocaleString()}</dd>
694
718
 
695
- <dt>${i18next.t('label.triangles-at-100')}</dt>
719
+ <dt>${i18next.t('figure.label.triangles-at-100')}</dt>
696
720
  <dd>${cost.at.triangles.toLocaleString()}</dd>
697
721
 
698
- <dt>${i18next.t('label.draw-calls-at-100')}</dt>
722
+ <dt>${i18next.t('figure.label.draw-calls-at-100')}</dt>
699
723
  <dd>${cost.at.drawCalls.toLocaleString()}</dd>
700
724
 
701
- <dt>${i18next.t('label.distinct-shapes')}</dt>
725
+ <dt>${i18next.t('figure.label.distinct-shapes')}</dt>
702
726
  <dd>${cost.distinctShapes}</dd>
703
727
 
704
- <dt>${i18next.t('label.distinct-materials')}</dt>
728
+ <dt>${i18next.t('figure.label.distinct-materials')}</dt>
705
729
  <dd>${cost.distinctMaterials}</dd>
706
730
  </dl>
707
731
  </section>
@@ -723,14 +747,14 @@ let FigureReport = class FigureReport extends localize(i18next)(LitElement) {
723
747
  return nothing;
724
748
  return html `
725
749
  <section>
726
- <h3>${i18next.t('label.reuse-trend')}</h3>
750
+ <h3>${i18next.t('figure.label.reuse-trend')}</h3>
727
751
  ${many
728
752
  ? html `
729
753
  <dl metrics>
730
- <dt>${i18next.t('label.triangles-at-10k')}</dt>
754
+ <dt>${i18next.t('figure.label.triangles-at-10k')}</dt>
731
755
  <dd>${many.at.triangles.toLocaleString()}</dd>
732
756
 
733
- <dt>${i18next.t('label.draw-calls-at-10k')}</dt>
757
+ <dt>${i18next.t('figure.label.draw-calls-at-10k')}</dt>
734
758
  <dd>${many.at.drawCalls.toLocaleString()}</dd>
735
759
  </dl>
736
760
  `
@@ -738,7 +762,7 @@ let FigureReport = class FigureReport extends localize(i18next)(LitElement) {
738
762
  ${this.curve
739
763
  ? html `
740
764
  <button expand @click=${() => (this.expanded = true)}>
741
- ${i18next.t('button.see-the-trend-chart')}
765
+ ${i18next.t('figure.button.see-the-trend-chart')}
742
766
  </button>
743
767
  `
744
768
  : nothing}
@@ -754,7 +778,7 @@ let FigureReport = class FigureReport extends localize(i18next)(LitElement) {
754
778
  <figure @click=${(e) => e.stopPropagation()}>
755
779
  ${unsafeSVG(this.curve)}
756
780
  <figcaption>
757
- <button close @click=${() => (this.expanded = false)}>${i18next.t('button.close')}</button>
781
+ <button close @click=${() => (this.expanded = false)}>${i18next.t('figure.button.close')}</button>
758
782
  </figcaption>
759
783
  </figure>
760
784
  </div>
@@ -768,18 +792,24 @@ let FigureReport = class FigureReport extends localize(i18next)(LitElement) {
768
792
  * 검사가 돌았는지 알 수 없다.
769
793
  */
770
794
  renderFindings() {
771
- if (this.errors.length === 0 && this.violations.length === 0) {
795
+ if (this.errors.length === 0 && this.violations.length === 0 && this.sizing.length === 0) {
772
796
  return html `
773
797
  <section>
774
- <h3>${i18next.t('label.findings')}</h3>
775
- <p quiet>${i18next.t('text.no-findings')}</p>
798
+ <h3>${i18next.t('figure.label.findings')}</h3>
799
+ <p quiet>${i18next.t('figure.text.no-findings')}</p>
776
800
  </section>
777
801
  `;
778
802
  }
779
803
  return html `
780
804
  <section>
781
- <h3>${i18next.t('label.findings')}</h3>
805
+ <h3>${i18next.t('figure.label.findings')}</h3>
782
806
  <ul findings>
807
+ ${this.sizing.map(finding => html `
808
+ <li ?violation=${!finding.blocking}>
809
+ <md-icon>call_split</md-icon>
810
+ <span>${finding.message} ${finding.at ? html `<code at>${finding.at}</code>` : ''}</span>
811
+ </li>
812
+ `)}
783
813
  ${this.errors.map(e => html `
784
814
  <li>
785
815
  <md-icon>error</md-icon>
@@ -807,6 +837,23 @@ let FigureReport = class FigureReport extends localize(i18next)(LitElement) {
807
837
  this.curve = '';
808
838
  this.errors = [];
809
839
  this.violations = [];
840
+ this.sizing = [];
841
+ return;
842
+ }
843
+ /*
844
+ 부품이 없으면 판정을 아예 하지 않는다 — 잴 것이 없다. 「무엇을 고쳐야 하나」가 아니라
845
+ 「무엇부터 놓나」인 상태이고, 그 안내는 부품 목록이 이미 하고 있다.
846
+ */
847
+ this.bare = !source.parts?.length;
848
+ if (this.bare) {
849
+ this.cost = undefined;
850
+ this.score = undefined;
851
+ this.many = undefined;
852
+ this.capacity = undefined;
853
+ this.curve = '';
854
+ this.errors = [];
855
+ this.violations = [];
856
+ this.sizing = [];
810
857
  return;
811
858
  }
812
859
  const result = validate(source);
@@ -819,10 +866,23 @@ let FigureReport = class FigureReport extends localize(i18next)(LitElement) {
819
866
  this.many = undefined;
820
867
  this.capacity = undefined;
821
868
  this.curve = '';
869
+ this.sizing = [];
822
870
  this.dispatchEvent(new CustomEvent('verdict-changed', { detail: {}, bubbles: true, composed: true }));
823
871
  return;
824
872
  }
825
873
  const blueprint = compile(source);
874
+ /*
875
+ 크기를 바꿔 보는 일곱 조합. 판정 규칙은 `figure-model` 의 `gate.ts` 가 정한 것을 그대로
876
+ 쓴다 — 무엇이 막는 것인지 화면이 다시 정하면 서버와 다른 말을 하게 된다.
877
+ */
878
+ const report = sizingReport(blueprint);
879
+ this.sizing = report.findings.map(finding => ({
880
+ code: finding.code,
881
+ message: finding.message,
882
+ /* 숫자가 아니라 자리를 말한다 — 갈라져 나온 부품 이름이 고칠 자리다. */
883
+ at: finding.clumps.map(clump => [...clump].sort().join('+')).join(' | ') || undefined,
884
+ blocking: finding.code === 'sizing-splits'
885
+ }));
826
886
  this.cost = costOf(blueprint, 100);
827
887
  // 1만 개는 figure-model 에 다시 묻는다 — 화면이 곱셈으로 짐작하지 않는다.
828
888
  this.many = costOf(blueprint, 10000);
@@ -874,6 +934,14 @@ __decorate([
874
934
  state(),
875
935
  __metadata("design:type", Array)
876
936
  ], FigureReport.prototype, "violations", void 0);
937
+ __decorate([
938
+ state(),
939
+ __metadata("design:type", Array)
940
+ ], FigureReport.prototype, "sizing", void 0);
941
+ __decorate([
942
+ state(),
943
+ __metadata("design:type", Object)
944
+ ], FigureReport.prototype, "bare", void 0);
877
945
  FigureReport = __decorate([
878
946
  customElement('figure-report')
879
947
  ], FigureReport);