@fmsim/board 1.0.88 → 2.0.0-beta.101

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 (144) hide show
  1. package/custom-elements.json +7366 -3448
  2. package/demo/index-modeller.html +1 -1
  3. package/demo/index-player.html +2 -2
  4. package/demo/index-viewer.html +2 -2
  5. package/demo/index.html +2 -2
  6. package/dist/src/component/etc.js +36 -0
  7. package/dist/src/component/etc.js.map +1 -1
  8. package/dist/src/component/index.d.ts +1 -0
  9. package/dist/src/component/index.js +1 -0
  10. package/dist/src/component/index.js.map +1 -1
  11. package/dist/src/component/restful-attachment-creation-card.js +1 -1
  12. package/dist/src/component/restful-attachment-creation-card.js.map +1 -1
  13. package/dist/src/component/restful-attachment-selector.js +8 -9
  14. package/dist/src/component/restful-attachment-selector.js.map +1 -1
  15. package/dist/src/component/restful-input-attachment-selector.d.ts +1 -1
  16. package/dist/src/component/restful-input-attachment-selector.js +3 -3
  17. package/dist/src/component/restful-input-attachment-selector.js.map +1 -1
  18. package/dist/src/component/restful-input-fill-style.d.ts +13 -2
  19. package/dist/src/component/restful-input-fill-style.js.map +1 -1
  20. package/dist/src/component/restful-property-editor-backdrop-faces.d.ts +50 -0
  21. package/dist/src/component/restful-property-editor-backdrop-faces.js +524 -0
  22. package/dist/src/component/restful-property-editor-backdrop-faces.js.map +1 -0
  23. package/dist/src/index.d.ts +1 -0
  24. package/dist/src/index.js +1 -0
  25. package/dist/src/index.js.map +1 -1
  26. package/dist/src/layers/mcs-mouse-event-handler.js +8 -0
  27. package/dist/src/layers/mcs-mouse-event-handler.js.map +1 -1
  28. package/dist/src/layers/movement-layer.d.ts +7 -6
  29. package/dist/src/layers/movement-layer.js +56 -11
  30. package/dist/src/layers/movement-layer.js.map +1 -1
  31. package/dist/src/modeller/bulk-create-dialog.d.ts +51 -0
  32. package/dist/src/modeller/bulk-create-dialog.js +531 -0
  33. package/dist/src/modeller/bulk-create-dialog.js.map +1 -0
  34. package/dist/src/modeller/bulk-create.d.ts +101 -0
  35. package/dist/src/modeller/bulk-create.js +199 -0
  36. package/dist/src/modeller/bulk-create.js.map +1 -0
  37. package/dist/src/modeller/component-toolbar/component-menu.d.ts +1 -1
  38. package/dist/src/modeller/component-toolbar/component-toolbar.d.ts +4 -4
  39. package/dist/src/modeller/component-toolbar/component-toolbar.js.map +1 -1
  40. package/dist/src/modeller/edit-toolbar-style.js +43 -3
  41. package/dist/src/modeller/edit-toolbar-style.js.map +1 -1
  42. package/dist/src/modeller/edit-toolbar.d.ts +21 -14
  43. package/dist/src/modeller/edit-toolbar.js +371 -213
  44. package/dist/src/modeller/edit-toolbar.js.map +1 -1
  45. package/dist/src/modeller/property-sidebar/abstract-property.js +10 -2
  46. package/dist/src/modeller/property-sidebar/abstract-property.js.map +1 -1
  47. package/dist/src/modeller/property-sidebar/data-binding/data-binding-mapper.js +1 -1
  48. package/dist/src/modeller/property-sidebar/data-binding/data-binding-mapper.js.map +1 -1
  49. package/dist/src/modeller/property-sidebar/data-binding/data-binding.d.ts +2 -2
  50. package/dist/src/modeller/property-sidebar/data-binding/data-binding.js +18 -18
  51. package/dist/src/modeller/property-sidebar/data-binding/data-binding.js.map +1 -1
  52. package/dist/src/modeller/property-sidebar/effects/effects.d.ts +10 -1
  53. package/dist/src/modeller/property-sidebar/effects/effects.js +66 -1
  54. package/dist/src/modeller/property-sidebar/effects/effects.js.map +1 -1
  55. package/dist/src/modeller/property-sidebar/effects/event-handler-adapter.d.ts +44 -0
  56. package/dist/src/modeller/property-sidebar/effects/event-handler-adapter.js +70 -0
  57. package/dist/src/modeller/property-sidebar/effects/event-handler-adapter.js.map +1 -0
  58. package/dist/src/modeller/property-sidebar/effects/property-event-hover.js +3 -3
  59. package/dist/src/modeller/property-sidebar/effects/property-event-hover.js.map +1 -1
  60. package/dist/src/modeller/property-sidebar/effects/property-event.d.ts +13 -3
  61. package/dist/src/modeller/property-sidebar/effects/property-event.js +20 -11
  62. package/dist/src/modeller/property-sidebar/effects/property-event.js.map +1 -1
  63. package/dist/src/modeller/property-sidebar/inspector/inspector-controller.d.ts +62 -0
  64. package/dist/src/modeller/property-sidebar/inspector/inspector-controller.js +276 -0
  65. package/dist/src/modeller/property-sidebar/inspector/inspector-controller.js.map +1 -0
  66. package/dist/src/modeller/property-sidebar/inspector/inspector-group-view.d.ts +15 -0
  67. package/dist/src/modeller/property-sidebar/inspector/inspector-group-view.js +56 -0
  68. package/dist/src/modeller/property-sidebar/inspector/inspector-group-view.js.map +1 -0
  69. package/dist/src/modeller/property-sidebar/inspector/inspector-highlight.d.ts +7 -0
  70. package/dist/src/modeller/property-sidebar/inspector/inspector-highlight.js +25 -0
  71. package/dist/src/modeller/property-sidebar/inspector/inspector-highlight.js.map +1 -0
  72. package/dist/src/modeller/property-sidebar/inspector/inspector-model.d.ts +44 -0
  73. package/dist/src/modeller/property-sidebar/inspector/inspector-model.js +72 -0
  74. package/dist/src/modeller/property-sidebar/inspector/inspector-model.js.map +1 -0
  75. package/dist/src/modeller/property-sidebar/inspector/inspector-tree-view.d.ts +18 -0
  76. package/dist/src/modeller/property-sidebar/inspector/inspector-tree-view.js +61 -0
  77. package/dist/src/modeller/property-sidebar/inspector/inspector-tree-view.js.map +1 -0
  78. package/dist/src/modeller/property-sidebar/inspector/inspector.d.ts +17 -12
  79. package/dist/src/modeller/property-sidebar/inspector/inspector.js +281 -154
  80. package/dist/src/modeller/property-sidebar/inspector/inspector.js.map +1 -1
  81. package/dist/src/modeller/property-sidebar/property-sidebar.d.ts +2 -2
  82. package/dist/src/modeller/property-sidebar/property-sidebar.js +11 -24
  83. package/dist/src/modeller/property-sidebar/property-sidebar.js.map +1 -1
  84. package/dist/src/modeller/property-sidebar/shapes/ox-input-entity-id.d.ts +75 -0
  85. package/dist/src/modeller/property-sidebar/shapes/ox-input-entity-id.js +346 -0
  86. package/dist/src/modeller/property-sidebar/shapes/ox-input-entity-id.js.map +1 -0
  87. package/dist/src/modeller/property-sidebar/shapes/shapes.d.ts +23 -2
  88. package/dist/src/modeller/property-sidebar/shapes/shapes.js +96 -4
  89. package/dist/src/modeller/property-sidebar/shapes/shapes.js.map +1 -1
  90. package/dist/src/modeller/property-sidebar/specifics/specifics.d.ts +25 -1
  91. package/dist/src/modeller/property-sidebar/specifics/specifics.js +112 -2
  92. package/dist/src/modeller/property-sidebar/specifics/specifics.js.map +1 -1
  93. package/dist/src/modeller/property-sidebar/styles/styles.d.ts +6 -4
  94. package/dist/src/modeller/property-sidebar/styles/styles.js +140 -437
  95. package/dist/src/modeller/property-sidebar/styles/styles.js.map +1 -1
  96. package/dist/src/modeller/scene-viewer/ox-scene-layer.d.ts +4 -3
  97. package/dist/src/modeller/scene-viewer/ox-scene-layer.js +2 -1
  98. package/dist/src/modeller/scene-viewer/ox-scene-layer.js.map +1 -1
  99. package/dist/src/modeller/scene-viewer/ox-scene-viewer.d.ts +6 -5
  100. package/dist/src/modeller/scene-viewer/ox-scene-viewer.js +7 -3
  101. package/dist/src/modeller/scene-viewer/ox-scene-viewer.js.map +1 -1
  102. package/dist/src/ox-board-component-info.d.ts +3 -3
  103. package/dist/src/ox-board-component-info.js +58 -19
  104. package/dist/src/ox-board-component-info.js.map +1 -1
  105. package/dist/src/ox-board-list.js +12 -12
  106. package/dist/src/ox-board-list.js.map +1 -1
  107. package/dist/src/ox-board-modeller.d.ts +9 -6
  108. package/dist/src/ox-board-modeller.js +32 -33
  109. package/dist/src/ox-board-modeller.js.map +1 -1
  110. package/dist/src/ox-board-viewer.d.ts +129 -17
  111. package/dist/src/ox-board-viewer.js +804 -115
  112. package/dist/src/ox-board-viewer.js.map +1 -1
  113. package/dist/src/ox-editor-board-selector.d.ts +4 -1
  114. package/dist/src/ox-editor-board-selector.js +41 -12
  115. package/dist/src/ox-editor-board-selector.js.map +1 -1
  116. package/dist/src/ox-playback-controls.d.ts +157 -0
  117. package/dist/src/ox-playback-controls.js +1089 -0
  118. package/dist/src/ox-playback-controls.js.map +1 -0
  119. package/dist/src/ox-property-editor-theme.js +2 -2
  120. package/dist/src/ox-property-editor-theme.js.map +1 -1
  121. package/dist/src/playback/carrier-tracking-store.d.ts +51 -0
  122. package/dist/src/playback/carrier-tracking-store.js +115 -0
  123. package/dist/src/playback/carrier-tracking-store.js.map +1 -0
  124. package/dist/src/playback/playback-engine.d.ts +106 -0
  125. package/dist/src/playback/playback-engine.js +395 -0
  126. package/dist/src/playback/playback-engine.js.map +1 -0
  127. package/dist/src/selector/board-creation-popup.d.ts +4 -5
  128. package/dist/src/selector/board-creation-popup.js +16 -19
  129. package/dist/src/selector/board-creation-popup.js.map +1 -1
  130. package/dist/src/selector/board-thumbnail-card.js +8 -8
  131. package/dist/src/selector/board-thumbnail-card.js.map +1 -1
  132. package/dist/src/selector/ox-board-creation-card.d.ts +1 -1
  133. package/dist/src/selector/ox-board-creation-card.js +7 -7
  134. package/dist/src/selector/ox-board-creation-card.js.map +1 -1
  135. package/dist/src/selector/ox-board-selector.js +21 -10
  136. package/dist/src/selector/ox-board-selector.js.map +1 -1
  137. package/dist/tsconfig.tsbuildinfo +1 -1
  138. package/icons/components/text.png +0 -0
  139. package/icons/fab/mode-2d.svg +25 -0
  140. package/icons/fab/mode-3d.svg +25 -0
  141. package/icons/fab/playback-play.svg +49 -0
  142. package/icons/fab/playback-stop.svg +50 -0
  143. package/icons/icon-htoolbar.png +0 -0
  144. package/package.json +22 -21
@@ -0,0 +1,61 @@
1
+ import { html, nothing } from 'lit';
2
+ import { matchesQuery } from './inspector-model.js';
3
+ import { highlight } from './inspector-highlight.js';
4
+ /** 컴포넌트 자신 또는 자손 중 검색 매치가 있으면 표시 대상. */
5
+ function shouldBeShown(component, terms) {
6
+ if (matchesQuery(component, terms))
7
+ return true;
8
+ const children = component.isContainer() ? component.components : [];
9
+ return children.some(child => shouldBeShown(child, terms));
10
+ }
11
+ /** 검색 매치 개수(루트 포함, 전체 트리) — ROOT 노드 count 표시용. */
12
+ export function countMatches(component, terms) {
13
+ let n = matchesQuery(component, terms) ? 1 : 0;
14
+ const children = component.isContainer() ? component.components : [];
15
+ for (const child of children)
16
+ n += countMatches(child, terms);
17
+ return n;
18
+ }
19
+ function renderNode(component, depth, ctx) {
20
+ if (!component || !shouldBeShown(component, ctx.terms)) {
21
+ return html ``;
22
+ }
23
+ const children = component.isContainer() ? component.components : [];
24
+ const extended = ctx.isExtended(component) ? children.filter(child => shouldBeShown(child, ctx.terms)) : [];
25
+ const { type, id, tag, class: clazz } = component.state;
26
+ const label = (id ? `#${id}` : '') + (tag ? `@${tag}` : '') + (clazz ? `.(${clazz})` : '');
27
+ return html `
28
+ <div
29
+ class="component"
30
+ ?selected=${ctx.selected.indexOf(component) > -1}
31
+ .component=${component}
32
+ ?sortable=${component.isContainer()}
33
+ >
34
+ <span>
35
+ ${depth > 0
36
+ ? html `
37
+ <md-icon class="eye">${component.get('hidden') ? 'visibility_off' : 'visibility'}</md-icon>
38
+ <md-icon class="lock" ?locked=${!!component.get('locked')}
39
+ >${component.get('locked') ? 'lock' : 'lock_open'}</md-icon
40
+ >
41
+ <pre>${' '.repeat(Math.max(0, depth - 1))}</pre>
42
+ `
43
+ : html ` <pre>${' '.repeat(depth + 1)}</pre> `}
44
+
45
+ <span class=${ctx.nodeHandleClass(component)}> </span>
46
+
47
+ <span class="type">${depth == 0 ? html `ROOT(count: ${ctx.count})` : highlight(type, ctx.terms)}</span>
48
+ ${label ? html ` <span class="name">${highlight(label, ctx.terms)}</span> ` : nothing}
49
+ </span>
50
+
51
+ ${extended.map(child => renderNode(child, depth + 1, ctx))}
52
+ </div>
53
+ `;
54
+ }
55
+ /** scene.root 부터 트리를 렌더한다. */
56
+ export function renderInspectorTree(root, ctx) {
57
+ if (!root)
58
+ return html ``;
59
+ return renderNode(root, 0, ctx);
60
+ }
61
+ //# sourceMappingURL=inspector-tree-view.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inspector-tree-view.js","sourceRoot":"","sources":["../../../../../src/modeller/property-sidebar/inspector/inspector-tree-view.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * Scene Inspector 트리 뷰 렌더러.\n *\n * 계층(Container/Group) 구조를 재귀로 그린다. SceneInspector 의 shadow root 에 직접 렌더되며\n * (클릭 위임이 `.component` 프로퍼티/classList 에 의존하므로 별도 커스텀엘리먼트로 분리하지 않는다),\n * 순수 렌더 함수로 컨트롤러/상태를 주입받는다.\n */\nimport { Component, Container } from '@hatiolab/things-scene'\nimport { html, nothing, TemplateResult } from 'lit'\n\nimport { matchesQuery, SearchTerm } from './inspector-model.js'\nimport { highlight } from './inspector-highlight.js'\n\n/** 트리 렌더에 필요한 컨텍스트(호스트/컨트롤러가 주입). */\nexport interface TreeViewContext {\n /** 현재 선택 컴포넌트 목록. */\n selected: Component[]\n /** 파싱된 검색어(AND). */\n terms: SearchTerm[]\n /** ROOT 노드에 표시할 매치 개수. */\n count: number\n isExtended: (c: Component) => boolean\n nodeHandleClass: (c: Component) => string\n}\n\n/** 컴포넌트 자신 또는 자손 중 검색 매치가 있으면 표시 대상. */\nfunction shouldBeShown(component: Component, terms: SearchTerm[]): boolean {\n if (matchesQuery(component, terms)) return true\n const children = component.isContainer() ? (component as Container).components : []\n return children.some(child => shouldBeShown(child, terms))\n}\n\n/** 검색 매치 개수(루트 포함, 전체 트리) — ROOT 노드 count 표시용. */\nexport function countMatches(component: Component, terms: SearchTerm[]): number {\n let n = matchesQuery(component, terms) ? 1 : 0\n const children = component.isContainer() ? (component as Container).components : []\n for (const child of children) n += countMatches(child, terms)\n return n\n}\n\nfunction renderNode(component: Component, depth: number, ctx: TreeViewContext): TemplateResult {\n if (!component || !shouldBeShown(component, ctx.terms)) {\n return html``\n }\n\n const children = component.isContainer() ? (component as Container).components : []\n const extended = ctx.isExtended(component) ? children.filter(child => shouldBeShown(child, ctx.terms)) : []\n const { type, id, tag, class: clazz } = component.state\n const label = (id ? `#${id}` : '') + (tag ? `@${tag}` : '') + (clazz ? `.(${clazz})` : '')\n\n return html`\n <div\n class=\"component\"\n ?selected=${ctx.selected.indexOf(component) > -1}\n .component=${component}\n ?sortable=${component.isContainer()}\n >\n <span>\n ${depth > 0\n ? html`\n <md-icon class=\"eye\">${component.get('hidden') ? 'visibility_off' : 'visibility'}</md-icon>\n <md-icon class=\"lock\" ?locked=${!!component.get('locked')}\n >${component.get('locked') ? 'lock' : 'lock_open'}</md-icon\n >\n <pre>${' '.repeat(Math.max(0, depth - 1))}</pre>\n `\n : html` <pre>${' '.repeat(depth + 1)}</pre> `}\n\n <span class=${ctx.nodeHandleClass(component)}> </span>\n\n <span class=\"type\">${depth == 0 ? html`ROOT(count: ${ctx.count})` : highlight(type, ctx.terms)}</span>\n ${label ? html` <span class=\"name\">${highlight(label, ctx.terms)}</span> ` : nothing}\n </span>\n\n ${extended.map(child => renderNode(child, depth + 1, ctx))}\n </div>\n `\n}\n\n/** scene.root 부터 트리를 렌더한다. */\nexport function renderInspectorTree(root: Component | undefined, ctx: TreeViewContext): TemplateResult {\n if (!root) return html``\n return renderNode(root, 0, ctx)\n}\n"]}
@@ -1,27 +1,32 @@
1
1
  import '@operato/input/ox-input-search.js';
2
2
  import { LitElement, PropertyValues, TemplateResult } from 'lit';
3
- import { Component, Scene } from '@hatiolab/things-scene';
3
+ import { Scene } from '@hatiolab/things-scene';
4
4
  export declare class SceneInspector extends LitElement {
5
5
  static styles: import("lit").CSSResult[];
6
6
  scene?: Scene;
7
7
  private searchText;
8
- private _extendedMap;
8
+ private viewMode;
9
+ private controller;
9
10
  private show;
10
- private count;
11
- disconnectScene(scene?: Scene): void;
11
+ /** reveal(스크롤) 판정용 직전 선택. */
12
+ private _lastSelected;
12
13
  disconnectedCallback(): void;
13
14
  render(): TemplateResult<1>;
15
+ private _renderResult;
16
+ private _setViewMode;
17
+ private _collapseGroups;
18
+ private _expandGroups;
14
19
  firstUpdated(): void;
20
+ /** 외부에서 강제 재렌더용. */
15
21
  refresh(): void;
16
22
  updated(change: PropertyValues<this>): void;
23
+ /**
24
+ * 선택이 바뀌면(캔버스 등) 해당 항목을 리스트에 노출한다 — 그룹 뷰면 그룹을 펼친 뒤 스크롤.
25
+ * scrollIntoView({block:'nearest'}) 라 이미 보이는 경우(인스펙터 클릭 등)엔 사실상 no-op.
26
+ */
27
+ private _maybeReveal;
28
+ /** 이벤트 타겟에서 상위로 올라가며 `.component` 를 가진 요소를 찾는다. */
29
+ private _resolveComponent;
17
30
  _onclick(e: MouseEvent): void;
18
31
  _ondblclick(e: MouseEvent): void;
19
- get extendedMap(): any;
20
- getNodeHandleClass(component: Component): "collapsed" | "extended" | "collapsespace";
21
- isExtended(component: Component): boolean;
22
- toggleExtended(component: Component): void;
23
- toggleHidden(component: Component): void;
24
- selectComponent(component: Component, append?: boolean): void;
25
- shouldBeShown(component: Component, counting?: boolean): boolean;
26
- renderComponent(component: Component, depth: number): TemplateResult;
27
32
  }
@@ -1,30 +1,64 @@
1
1
  import { __decorate } from "tslib";
2
+ /*
3
+ * Copyright © HatioLab Inc. All rights reserved.
4
+ *
5
+ * SceneInspector — 속성패널 inspector 탭 호스트.
6
+ *
7
+ * 얇은 호스트: 검색/뷰모드 상태 + 클릭 위임만 담당하고, scene 상호작용은 InspectorController,
8
+ * 렌더는 뷰 렌더러(트리/타입그룹)로 위임한다.
9
+ */
2
10
  import '@operato/input/ox-input-search.js';
3
- import { css, html, LitElement } from 'lit';
11
+ import { css, html, LitElement, nothing } from 'lit';
4
12
  import { property, state } from 'lit/decorators.js';
5
13
  import { i18next } from '@operato/i18n';
14
+ import { parseQuery } from './inspector-model.js';
15
+ import { InspectorController } from './inspector-controller.js';
16
+ import { countMatches, renderInspectorTree } from './inspector-tree-view.js';
17
+ import { renderInspectorGroupView } from './inspector-group-view.js';
18
+ /** 뷰 모드 선호를 세션 간 유지(localStorage). 전역 사용자 선호. */
19
+ const VIEW_MODE_STORAGE_KEY = 'scene-inspector.view-mode';
20
+ function readStoredViewMode() {
21
+ try {
22
+ const v = localStorage.getItem(VIEW_MODE_STORAGE_KEY);
23
+ return v === 'group' || v === 'tree' ? v : 'tree';
24
+ }
25
+ catch (_a) {
26
+ return 'tree';
27
+ }
28
+ }
29
+ function writeStoredViewMode(mode) {
30
+ try {
31
+ localStorage.setItem(VIEW_MODE_STORAGE_KEY, mode);
32
+ }
33
+ catch (_a) {
34
+ /* localStorage 미가용 — 무시 */
35
+ }
36
+ }
6
37
  export class SceneInspector extends LitElement {
7
38
  constructor() {
8
39
  super(...arguments);
9
40
  this.searchText = '';
41
+ this.viewMode = readStoredViewMode();
42
+ this.controller = new InspectorController();
10
43
  this.show = false;
11
- this.count = -1;
12
- }
13
- disconnectScene(scene) {
14
- if (scene) {
15
- scene.off('selected', undefined, this);
16
- scene.off('execute', undefined, this);
17
- scene.off('undo', undefined, this);
18
- scene.off('redo', undefined, this);
19
- }
44
+ /** reveal(스크롤) 판정용 직전 선택. */
45
+ this._lastSelected = [];
46
+ this._collapseGroups = () => {
47
+ this.controller.collapseAllGroups();
48
+ this.requestUpdate();
49
+ };
50
+ this._expandGroups = () => {
51
+ const index = this.controller.index;
52
+ if (index)
53
+ this.controller.expandAllGroups([...index.byType.keys()]);
54
+ this.requestUpdate();
55
+ };
20
56
  }
21
57
  disconnectedCallback() {
22
58
  super.disconnectedCallback();
23
- this.disconnectScene(this.scene);
24
- delete this._extendedMap;
59
+ this.controller.unbind();
25
60
  }
26
61
  render() {
27
- this.count = 0;
28
62
  return html `
29
63
  <ox-input-search
30
64
  .placeholder=${i18next.t('text.search with type, id or tag')}
@@ -32,47 +66,93 @@ export class SceneInspector extends LitElement {
32
66
  @change=${(e) => (this.searchText = (e.target.value || '').toLowerCase())}
33
67
  ></ox-input-search>
34
68
 
35
- <div result>${!this.scene ? html `` : this.renderComponent(this.scene.root, 0)}</div>
69
+ <div toolbar>
70
+ <md-icon class="view-btn" ?selected=${this.viewMode === 'tree'} title="Tree view" @click=${() => this._setViewMode('tree')}>account_tree</md-icon>
71
+ <md-icon class="view-btn" ?selected=${this.viewMode === 'group'} title="Group by type" @click=${() => this._setViewMode('group')}>category</md-icon>
72
+ <span class="spacer"></span>
73
+ ${this.viewMode === 'group'
74
+ ? html `
75
+ <md-icon title="Expand all groups" @click=${this._expandGroups}>unfold_more</md-icon>
76
+ <md-icon title="Collapse to group level" @click=${this._collapseGroups}>unfold_less</md-icon>
77
+ `
78
+ : nothing}
79
+ </div>
80
+
81
+ <div result>${this._renderResult()}</div>
36
82
  `;
37
83
  }
84
+ _renderResult() {
85
+ if (!this.scene)
86
+ return html ``;
87
+ const terms = parseQuery(this.searchText);
88
+ if (this.viewMode === 'group') {
89
+ return renderInspectorGroupView(this.controller.index, {
90
+ selected: this.controller.selected,
91
+ terms,
92
+ isGroupExpanded: (type) => this.controller.isGroupExpanded(type),
93
+ isTypeDimmed: (type) => this.controller.isTypeDimmed(type),
94
+ isTypeHidden: (type) => this.controller.isTypeHidden(type)
95
+ });
96
+ }
97
+ return renderInspectorTree(this.scene.root, {
98
+ selected: this.controller.selected,
99
+ terms,
100
+ count: countMatches(this.scene.root, terms),
101
+ isExtended: (c) => this.controller.isExtended(c),
102
+ nodeHandleClass: (c) => this.controller.nodeHandleClass(c)
103
+ });
104
+ }
105
+ _setViewMode(mode) {
106
+ if (this.viewMode === mode)
107
+ return;
108
+ this.viewMode = mode;
109
+ writeStoredViewMode(mode); // 세션 간 유지
110
+ }
38
111
  firstUpdated() {
39
112
  dispatchEvent(new Event('resize'));
40
113
  this.renderRoot.addEventListener('click', this._onclick.bind(this));
41
114
  this.renderRoot.addEventListener('dblclick', this._ondblclick.bind(this));
42
115
  }
116
+ /** 외부에서 강제 재렌더용. */
43
117
  refresh() {
44
- var _a;
45
- let selected = ((_a = this.scene) === null || _a === void 0 ? void 0 : _a.selected) || [];
46
- selected.forEach(component => {
47
- let parent = component.parent;
48
- while (parent && !this.extendedMap.get(parent)) {
49
- this.extendedMap.set(parent, true);
50
- parent = parent.parent;
51
- }
52
- });
53
118
  this.requestUpdate();
54
119
  }
55
120
  updated(change) {
56
121
  if (change.has('scene')) {
57
- let oldScene = change.get('scene');
58
- if (oldScene) {
59
- this.disconnectScene(oldScene);
60
- delete this._extendedMap;
61
- }
62
- if (this.scene && this.scene.root) {
63
- this.scene.on('selected', this.refresh, this);
64
- this.scene.on('execute', this.refresh, this);
65
- this.scene.on('undo', this.refresh, this);
66
- this.scene.on('redo', this.refresh, this);
67
- this.refresh();
122
+ this.controller.bind(this.scene, () => this.requestUpdate());
123
+ }
124
+ this._maybeReveal();
125
+ }
126
+ /**
127
+ * 선택이 바뀌면(캔버스 등) 해당 항목을 리스트에 노출한다 — 그룹 뷰면 그룹을 펼친 뒤 스크롤.
128
+ * scrollIntoView({block:'nearest'}) 라 이미 보이는 경우(인스펙터 클릭 등)엔 사실상 no-op.
129
+ */
130
+ _maybeReveal() {
131
+ const selected = this.controller.selected;
132
+ const changed = selected.length !== this._lastSelected.length || selected.some((c, i) => c !== this._lastSelected[i]);
133
+ if (!changed)
134
+ return;
135
+ if (selected.length === 0) {
136
+ this._lastSelected = selected;
137
+ return;
138
+ }
139
+ if (this.viewMode === 'group') {
140
+ const types = [...new Set(selected.map(c => c.state.type))];
141
+ if (types.some(t => !this.controller.isGroupExpanded(t))) {
142
+ this.controller.expandAllGroups(types);
143
+ return; // _lastSelected 미갱신 → 다음 updated 에서 changed 유지 → 그때 스크롤
68
144
  }
69
145
  }
146
+ this._lastSelected = selected;
147
+ this.updateComplete.then(() => {
148
+ const el = this.renderRoot.querySelector('.component[selected]');
149
+ el === null || el === void 0 ? void 0 : el.scrollIntoView({ block: 'nearest' });
150
+ });
70
151
  }
71
- _onclick(e) {
72
- e.stopPropagation();
73
- var targetElement = e.target;
74
- var classList = targetElement.classList;
75
- var component;
152
+ /** 이벤트 타겟에서 상위로 올라가며 `.component` 를 가진 요소를 찾는다. */
153
+ _resolveComponent(e) {
154
+ let targetElement = e.target;
155
+ let component;
76
156
  while (!component && targetElement) {
77
157
  component = targetElement.component;
78
158
  if (component)
@@ -81,14 +161,56 @@ export class SceneInspector extends LitElement {
81
161
  if (!targetElement || targetElement === this.renderRoot)
82
162
  break;
83
163
  }
164
+ return component;
165
+ }
166
+ _onclick(e) {
167
+ var _a, _b, _c, _d, _e;
168
+ e.stopPropagation();
169
+ const target = e.target;
170
+ // 그룹 hidden 아이콘 클릭 → 그룹(타입) 전원 hidden 토글 (헤더 펼침보다 먼저 처리)
171
+ if (target.classList.contains('group-hidden')) {
172
+ const type = (_b = (_a = target.closest) === null || _a === void 0 ? void 0 : _a.call(target, '[data-group-type]')) === null || _b === void 0 ? void 0 : _b.getAttribute('data-group-type');
173
+ if (type != null)
174
+ this.controller.toggleTypeHidden(type);
175
+ this.requestUpdate();
176
+ return;
177
+ }
178
+ // 그룹 dim-out 아이콘 클릭 → 그룹(타입) 포커스 dim 토글 (헤더 펼침보다 먼저 처리)
179
+ if (target.classList.contains('group-dim')) {
180
+ const type = (_d = (_c = target.closest) === null || _c === void 0 ? void 0 : _c.call(target, '[data-group-type]')) === null || _d === void 0 ? void 0 : _d.getAttribute('data-group-type');
181
+ if (type != null)
182
+ this.controller.toggleTypeDim(type);
183
+ this.requestUpdate();
184
+ return;
185
+ }
186
+ // 타입 그룹 헤더 클릭 → 그룹 펼침/접힘 (멤버 행은 헤더의 자손이 아니므로 여기 안 걸림)
187
+ const header = (_e = target.closest) === null || _e === void 0 ? void 0 : _e.call(target, '[data-group-type]');
188
+ if (header) {
189
+ const type = header.getAttribute('data-group-type');
190
+ if (type != null)
191
+ this.controller.toggleGroup(type);
192
+ this.requestUpdate();
193
+ return;
194
+ }
195
+ const classList = e.target.classList;
196
+ const component = this._resolveComponent(e);
84
197
  if (component) {
85
198
  if (classList.contains('eye')) {
86
- this.toggleHidden(component);
199
+ this.controller.toggleHidden(component);
200
+ }
201
+ else if (classList.contains('lock')) {
202
+ this.controller.toggleLocked(component);
87
203
  }
88
204
  else if (classList.contains('extended') || classList.contains('collapsed')) {
89
- this.toggleExtended(component);
205
+ this.controller.toggleExtended(component);
206
+ }
207
+ // shift = 범위(볼륨) 선택/블록 해제, ctrl/cmd = 개별 토글, 그 외 = 단일 선택
208
+ if (e.shiftKey) {
209
+ this.controller.selectRange(component, parseQuery(this.searchText));
210
+ }
211
+ else {
212
+ this.controller.select(component, e.metaKey || e.ctrlKey);
90
213
  }
91
- this.selectComponent(component, e.shiftKey);
92
214
  }
93
215
  else {
94
216
  if (classList.contains('inspector')) {
@@ -100,120 +222,16 @@ export class SceneInspector extends LitElement {
100
222
  }
101
223
  _ondblclick(e) {
102
224
  e.stopPropagation();
103
- var targetElement = e.target;
104
- var component;
105
- while (!component && targetElement) {
106
- component = targetElement.component;
107
- if (component)
108
- break;
109
- targetElement = targetElement.parentNode;
110
- if (!targetElement || targetElement === this.renderRoot)
111
- break;
112
- }
113
- if (component && component.isContainer()) {
114
- this.toggleExtended(component);
115
- }
116
- this.requestUpdate();
117
- }
118
- get extendedMap() {
119
- if (!this._extendedMap) {
120
- this._extendedMap = new WeakMap();
121
- }
122
- return this._extendedMap;
123
- }
124
- getNodeHandleClass(component) {
125
- if (component.isContainer() && component.components.length > 0) {
126
- return !!this.extendedMap.get(component) ? 'extended' : 'collapsed';
127
- }
128
- else {
129
- return 'collapsespace';
130
- }
131
- }
132
- isExtended(component) {
133
- return !!this.extendedMap.get(component);
134
- }
135
- toggleExtended(component) {
136
- var extended = this.isExtended(component);
137
- if (extended) {
138
- this.extendedMap.delete(component);
139
- }
140
- else {
141
- this.extendedMap.set(component, !extended);
142
- }
143
- this.requestUpdate();
144
- }
145
- toggleHidden(component) {
146
- component.set('hidden', !component.hidden);
147
- this.requestUpdate();
148
- }
149
- selectComponent(component, append = false) {
150
- const selected = this.scene.selected;
151
- if (append) {
152
- const idx = selected.findIndex(s => s === component);
153
- if (idx != -1) {
154
- selected.splice(idx, 1);
155
- this.scene.selected = [...selected];
156
- }
157
- else {
158
- this.scene.selected = [...selected, component];
225
+ const component = this._resolveComponent(e);
226
+ if (component) {
227
+ if (component.isContainer()) {
228
+ this.controller.toggleExtended(component);
159
229
  }
230
+ // 캔버스를 해당 컴포넌트로 센터 이동(줌).
231
+ this.controller.centerTo(component);
160
232
  }
161
- else {
162
- this.scene.selected = [component];
163
- }
164
- component.trigger('deco-border-reset');
165
- component.trigger('deco-border', { distance: 8, lineWidth: 8, color: '#f007' });
166
233
  this.requestUpdate();
167
234
  }
168
- shouldBeShown(component, counting = false) {
169
- const { type, name, id, tag } = component.state;
170
- const found = !this.searchText || `${type} ${name || ''} ${id || ''} ${tag || ''}`.toLowerCase().search(this.searchText) > -1;
171
- if (counting && found) {
172
- this.count++;
173
- }
174
- if (counting) {
175
- console.log(found);
176
- }
177
- const foundChildren = (component.components || []).filter((child) => this.shouldBeShown(child, counting)).length;
178
- return !!(found || foundChildren > 0);
179
- }
180
- renderComponent(component, depth) {
181
- var _a;
182
- if (!component) {
183
- return html ``;
184
- }
185
- if (!this.shouldBeShown(component, depth == 0)) {
186
- return html ``;
187
- }
188
- const children = (component.isContainer() && component.components) || [];
189
- const extended = this.isExtended(component) ? children.filter(child => this.shouldBeShown(child)) : [];
190
- const { type, id, tag, class: clazz } = component.state;
191
- const name = (id ? `#${id}` : '') + (tag ? `@${tag}` : '') + (clazz ? `.(${clazz})` : '');
192
- return html `
193
- <div
194
- class="component"
195
- ?selected=${(((_a = this.scene) === null || _a === void 0 ? void 0 : _a.selected) || []).indexOf(component) > -1}
196
- .component=${component}
197
- ?sortable=${component.isContainer()}
198
- >
199
- <span>
200
- ${depth > 0
201
- ? html `
202
- <mwc-icon class="eye">${component.get('hidden') ? 'visibility_off' : 'visibility'}</mwc-icon>
203
- <pre>${' '.repeat(depth)}</pre>
204
- `
205
- : html ` <pre>${' '.repeat(depth + 2)}</pre> `}
206
-
207
- <span class=${this.getNodeHandleClass(component)}> </span>
208
-
209
- <span class="type">${depth == 0 ? html `ROOT(count: ${this.count})` : type}</span>
210
- ${name ? html ` <span class="name">${name}</span> ` : html ``}
211
- </span>
212
-
213
- ${extended.map(child => this.renderComponent(child, depth + 1))}
214
- </div>
215
- `;
216
- }
217
235
  }
218
236
  SceneInspector.styles = [
219
237
  css `
@@ -280,11 +298,26 @@ SceneInspector.styles = [
280
298
  }
281
299
 
282
300
  .eye {
283
- margin: 0 0 0 5px;
301
+ margin: 0 0 0 4px;
284
302
  vertical-align: middle;
285
303
  opacity: 0.7;
286
- font-size: 1.1em;
304
+ font-size: 1em;
305
+ width: 1em;
287
306
  color: var(--scene-inspector-eye-icon-color);
307
+ cursor: pointer;
308
+ }
309
+
310
+ .lock {
311
+ margin: 0;
312
+ vertical-align: middle;
313
+ opacity: 0.3;
314
+ font-size: 1em;
315
+ width: 1em;
316
+ cursor: pointer;
317
+ }
318
+ .lock[locked] {
319
+ opacity: 0.9;
320
+ color: var(--md-sys-color-error, #d9534f);
288
321
  }
289
322
 
290
323
  .collapsed::before,
@@ -319,6 +352,97 @@ SceneInspector.styles = [
319
352
 
320
353
  pre {
321
354
  display: inline;
355
+ /* 트리 인덴트(수직라인까지의 간격) 최소화 — depth 당 좁은 폭 */
356
+ font-size: 0.6em;
357
+ margin: 0;
358
+ }
359
+
360
+ /* 뷰 모드 툴바 */
361
+ div[toolbar] {
362
+ display: flex;
363
+ align-items: center;
364
+ gap: 4px;
365
+ padding: 2px 8px 4px;
366
+ }
367
+ div[toolbar] .spacer {
368
+ flex: 1;
369
+ }
370
+ div[toolbar] md-icon {
371
+ cursor: pointer;
372
+ opacity: 0.5;
373
+ font-size: 18px;
374
+ border-radius: 4px;
375
+ padding: 2px;
376
+ }
377
+ div[toolbar] md-icon:hover {
378
+ opacity: 0.9;
379
+ }
380
+ div[toolbar] md-icon[selected] {
381
+ opacity: 1;
382
+ color: var(--md-sys-color-primary, #4a9eff);
383
+ background: rgba(0, 0, 0, 0.05);
384
+ }
385
+
386
+ /* 타입 그룹 뷰 */
387
+ .group-header {
388
+ display: flex;
389
+ align-items: center;
390
+ gap: 4px;
391
+ font-size: 14px;
392
+ font-weight: 600;
393
+ cursor: pointer;
394
+ padding: 2px 0;
395
+ border-bottom: 1px solid rgba(0, 0, 0, 0.05);
396
+ }
397
+ .group-header:hover {
398
+ background: rgba(0, 0, 0, 0.03);
399
+ }
400
+ .group-count {
401
+ margin-left: auto;
402
+ font-size: 0.72em;
403
+ font-weight: normal;
404
+ background: var(--scene-inspector-name-background-color);
405
+ color: #fff;
406
+ border-radius: 999px;
407
+ padding: 0 6px;
408
+ }
409
+ .group-hidden,
410
+ .group-dim {
411
+ font-size: 1em;
412
+ opacity: 0.35;
413
+ cursor: pointer;
414
+ }
415
+ .group-hidden {
416
+ margin: 0 2px 0 6px;
417
+ }
418
+ .group-dim {
419
+ margin: 0 4px 0 2px;
420
+ }
421
+ .group-hidden:hover,
422
+ .group-dim:hover {
423
+ opacity: 0.8;
424
+ }
425
+ .group-header[hidden-group] .group-hidden,
426
+ .group-header[dimmed] .group-dim {
427
+ opacity: 0.95;
428
+ color: var(--md-sys-color-primary, #4a9eff);
429
+ }
430
+ .group-header[dimmed] .group-type {
431
+ opacity: 0.45;
432
+ }
433
+ .group-member {
434
+ padding-left: 8px;
435
+ }
436
+ .muted {
437
+ opacity: 0.4;
438
+ font-style: italic;
439
+ }
440
+ /* 검색 매치 하이라이트 */
441
+ mark {
442
+ background: var(--md-sys-color-tertiary-container, #ffe082);
443
+ color: inherit;
444
+ border-radius: 2px;
445
+ padding: 0 1px;
322
446
  }
323
447
  `
324
448
  ];
@@ -328,4 +452,7 @@ __decorate([
328
452
  __decorate([
329
453
  state()
330
454
  ], SceneInspector.prototype, "searchText", void 0);
455
+ __decorate([
456
+ state()
457
+ ], SceneInspector.prototype, "viewMode", void 0);
331
458
  //# sourceMappingURL=inspector.js.map