@fmsim/board 2.0.0-beta.95 → 2.0.0-beta.96
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.
- package/custom-elements.json +1923 -1303
- package/dist/src/modeller/property-sidebar/inspector/inspector-controller.d.ts +58 -0
- package/dist/src/modeller/property-sidebar/inspector/inspector-controller.js +258 -0
- package/dist/src/modeller/property-sidebar/inspector/inspector-controller.js.map +1 -0
- package/dist/src/modeller/property-sidebar/inspector/inspector-group-view.d.ts +13 -0
- package/dist/src/modeller/property-sidebar/inspector/inspector-group-view.js +48 -0
- package/dist/src/modeller/property-sidebar/inspector/inspector-group-view.js.map +1 -0
- package/dist/src/modeller/property-sidebar/inspector/inspector-highlight.d.ts +7 -0
- package/dist/src/modeller/property-sidebar/inspector/inspector-highlight.js +25 -0
- package/dist/src/modeller/property-sidebar/inspector/inspector-highlight.js.map +1 -0
- package/dist/src/modeller/property-sidebar/inspector/inspector-model.d.ts +44 -0
- package/dist/src/modeller/property-sidebar/inspector/inspector-model.js +72 -0
- package/dist/src/modeller/property-sidebar/inspector/inspector-model.js.map +1 -0
- package/dist/src/modeller/property-sidebar/inspector/inspector-tree-view.d.ts +18 -0
- package/dist/src/modeller/property-sidebar/inspector/inspector-tree-view.js +61 -0
- package/dist/src/modeller/property-sidebar/inspector/inspector-tree-view.js.map +1 -0
- package/dist/src/modeller/property-sidebar/inspector/inspector.d.ts +17 -12
- package/dist/src/modeller/property-sidebar/inspector/inspector.js +264 -151
- package/dist/src/modeller/property-sidebar/inspector/inspector.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
|
@@ -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":"AAUA,OAAO,EAAE,IAAI,EAAE,OAAO,EAAkB,MAAM,KAAK,CAAA;AAEnD,OAAO,EAAE,YAAY,EAAc,MAAM,sBAAsB,CAAA;AAC/D,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAA;AAcpD,wCAAwC;AACxC,SAAS,aAAa,CAAC,SAAoB,EAAE,KAAmB;IAC9D,IAAI,YAAY,CAAC,SAAS,EAAE,KAAK,CAAC;QAAE,OAAO,IAAI,CAAA;IAC/C,MAAM,QAAQ,GAAG,SAAS,CAAC,WAAW,EAAE,CAAC,CAAC,CAAE,SAAuB,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAA;IACnF,OAAO,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,aAAa,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAA;AAC5D,CAAC;AAED,kDAAkD;AAClD,MAAM,UAAU,YAAY,CAAC,SAAoB,EAAE,KAAmB;IACpE,IAAI,CAAC,GAAG,YAAY,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IAC9C,MAAM,QAAQ,GAAG,SAAS,CAAC,WAAW,EAAE,CAAC,CAAC,CAAE,SAAuB,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAA;IACnF,KAAK,MAAM,KAAK,IAAI,QAAQ;QAAE,CAAC,IAAI,YAAY,CAAC,KAAK,EAAE,KAAK,CAAC,CAAA;IAC7D,OAAO,CAAC,CAAA;AACV,CAAC;AAED,SAAS,UAAU,CAAC,SAAoB,EAAE,KAAa,EAAE,GAAoB;IAC3E,IAAI,CAAC,SAAS,IAAI,CAAC,aAAa,CAAC,SAAS,EAAE,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;QACvD,OAAO,IAAI,CAAA,EAAE,CAAA;IACf,CAAC;IAED,MAAM,QAAQ,GAAG,SAAS,CAAC,WAAW,EAAE,CAAC,CAAC,CAAE,SAAuB,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAA;IACnF,MAAM,QAAQ,GAAG,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,aAAa,CAAC,KAAK,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;IAC3G,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,SAAS,CAAC,KAAK,CAAA;IACvD,MAAM,KAAK,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;IAE1F,OAAO,IAAI,CAAA;;;kBAGK,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;mBACnC,SAAS;kBACV,SAAS,CAAC,WAAW,EAAE;;;UAG/B,KAAK,GAAG,CAAC;QACT,CAAC,CAAC,IAAI,CAAA;qCACqB,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,YAAY;8CAChD,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC;mBACpD,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,WAAW;;qBAE5C,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;aAC1C;QACH,CAAC,CAAC,IAAI,CAAA,SAAS,GAAG,CAAC,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,SAAS;;sBAEjC,GAAG,CAAC,eAAe,CAAC,SAAS,CAAC;;6BAEvB,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA,eAAe,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,EAAE,GAAG,CAAC,KAAK,CAAC;UAC5F,KAAK,CAAC,CAAC,CAAC,IAAI,CAAA,uBAAuB,SAAS,CAAC,KAAK,EAAE,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,OAAO;;;QAGpF,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,KAAK,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC;;GAE7D,CAAA;AACH,CAAC;AAED,8BAA8B;AAC9B,MAAM,UAAU,mBAAmB,CAAC,IAA2B,EAAE,GAAoB;IACnF,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAA,EAAE,CAAA;IACxB,OAAO,UAAU,CAAC,IAAI,EAAE,CAAC,EAAE,GAAG,CAAC,CAAA;AACjC,CAAC","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 {
|
|
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
|
|
8
|
+
private viewMode;
|
|
9
|
+
private controller;
|
|
9
10
|
private show;
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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.
|
|
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,92 @@ export class SceneInspector extends LitElement {
|
|
|
32
66
|
@change=${(e) => (this.searchText = (e.target.value || '').toLowerCase())}
|
|
33
67
|
></ox-input-search>
|
|
34
68
|
|
|
35
|
-
<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
|
+
});
|
|
95
|
+
}
|
|
96
|
+
return renderInspectorTree(this.scene.root, {
|
|
97
|
+
selected: this.controller.selected,
|
|
98
|
+
terms,
|
|
99
|
+
count: countMatches(this.scene.root, terms),
|
|
100
|
+
isExtended: (c) => this.controller.isExtended(c),
|
|
101
|
+
nodeHandleClass: (c) => this.controller.nodeHandleClass(c)
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
_setViewMode(mode) {
|
|
105
|
+
if (this.viewMode === mode)
|
|
106
|
+
return;
|
|
107
|
+
this.viewMode = mode;
|
|
108
|
+
writeStoredViewMode(mode); // 세션 간 유지
|
|
109
|
+
}
|
|
38
110
|
firstUpdated() {
|
|
39
111
|
dispatchEvent(new Event('resize'));
|
|
40
112
|
this.renderRoot.addEventListener('click', this._onclick.bind(this));
|
|
41
113
|
this.renderRoot.addEventListener('dblclick', this._ondblclick.bind(this));
|
|
42
114
|
}
|
|
115
|
+
/** 외부에서 강제 재렌더용. */
|
|
43
116
|
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
117
|
this.requestUpdate();
|
|
54
118
|
}
|
|
55
119
|
updated(change) {
|
|
56
120
|
if (change.has('scene')) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
121
|
+
this.controller.bind(this.scene, () => this.requestUpdate());
|
|
122
|
+
}
|
|
123
|
+
this._maybeReveal();
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* 선택이 바뀌면(캔버스 등) 해당 항목을 리스트에 노출한다 — 그룹 뷰면 그룹을 펼친 뒤 스크롤.
|
|
127
|
+
* scrollIntoView({block:'nearest'}) 라 이미 보이는 경우(인스펙터 클릭 등)엔 사실상 no-op.
|
|
128
|
+
*/
|
|
129
|
+
_maybeReveal() {
|
|
130
|
+
const selected = this.controller.selected;
|
|
131
|
+
const changed = selected.length !== this._lastSelected.length || selected.some((c, i) => c !== this._lastSelected[i]);
|
|
132
|
+
if (!changed)
|
|
133
|
+
return;
|
|
134
|
+
if (selected.length === 0) {
|
|
135
|
+
this._lastSelected = selected;
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (this.viewMode === 'group') {
|
|
139
|
+
const types = [...new Set(selected.map(c => c.state.type))];
|
|
140
|
+
if (types.some(t => !this.controller.isGroupExpanded(t))) {
|
|
141
|
+
this.controller.expandAllGroups(types);
|
|
142
|
+
return; // _lastSelected 미갱신 → 다음 updated 에서 changed 유지 → 그때 스크롤
|
|
68
143
|
}
|
|
69
144
|
}
|
|
145
|
+
this._lastSelected = selected;
|
|
146
|
+
this.updateComplete.then(() => {
|
|
147
|
+
const el = this.renderRoot.querySelector('.component[selected]');
|
|
148
|
+
el === null || el === void 0 ? void 0 : el.scrollIntoView({ block: 'nearest' });
|
|
149
|
+
});
|
|
70
150
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
var component;
|
|
151
|
+
/** 이벤트 타겟에서 상위로 올라가며 `.component` 를 가진 요소를 찾는다. */
|
|
152
|
+
_resolveComponent(e) {
|
|
153
|
+
let targetElement = e.target;
|
|
154
|
+
let component;
|
|
76
155
|
while (!component && targetElement) {
|
|
77
156
|
component = targetElement.component;
|
|
78
157
|
if (component)
|
|
@@ -81,14 +160,48 @@ export class SceneInspector extends LitElement {
|
|
|
81
160
|
if (!targetElement || targetElement === this.renderRoot)
|
|
82
161
|
break;
|
|
83
162
|
}
|
|
163
|
+
return component;
|
|
164
|
+
}
|
|
165
|
+
_onclick(e) {
|
|
166
|
+
var _a, _b, _c;
|
|
167
|
+
e.stopPropagation();
|
|
168
|
+
const target = e.target;
|
|
169
|
+
// 그룹 dim-out 아이콘 클릭 → 그룹(타입) 포커스 dim 토글 (헤더 펼침보다 먼저 처리)
|
|
170
|
+
if (target.classList.contains('group-dim')) {
|
|
171
|
+
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');
|
|
172
|
+
if (type != null)
|
|
173
|
+
this.controller.toggleTypeDim(type);
|
|
174
|
+
this.requestUpdate();
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
// 타입 그룹 헤더 클릭 → 그룹 펼침/접힘 (멤버 행은 헤더의 자손이 아니므로 여기 안 걸림)
|
|
178
|
+
const header = (_c = target.closest) === null || _c === void 0 ? void 0 : _c.call(target, '[data-group-type]');
|
|
179
|
+
if (header) {
|
|
180
|
+
const type = header.getAttribute('data-group-type');
|
|
181
|
+
if (type != null)
|
|
182
|
+
this.controller.toggleGroup(type);
|
|
183
|
+
this.requestUpdate();
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
const classList = e.target.classList;
|
|
187
|
+
const component = this._resolveComponent(e);
|
|
84
188
|
if (component) {
|
|
85
189
|
if (classList.contains('eye')) {
|
|
86
|
-
this.toggleHidden(component);
|
|
190
|
+
this.controller.toggleHidden(component);
|
|
191
|
+
}
|
|
192
|
+
else if (classList.contains('lock')) {
|
|
193
|
+
this.controller.toggleLocked(component);
|
|
87
194
|
}
|
|
88
195
|
else if (classList.contains('extended') || classList.contains('collapsed')) {
|
|
89
|
-
this.toggleExtended(component);
|
|
196
|
+
this.controller.toggleExtended(component);
|
|
197
|
+
}
|
|
198
|
+
// shift = 범위(볼륨) 선택/블록 해제, ctrl/cmd = 개별 토글, 그 외 = 단일 선택
|
|
199
|
+
if (e.shiftKey) {
|
|
200
|
+
this.controller.selectRange(component, parseQuery(this.searchText));
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
this.controller.select(component, e.metaKey || e.ctrlKey);
|
|
90
204
|
}
|
|
91
|
-
this.selectComponent(component, e.shiftKey);
|
|
92
205
|
}
|
|
93
206
|
else {
|
|
94
207
|
if (classList.contains('inspector')) {
|
|
@@ -100,117 +213,16 @@ export class SceneInspector extends LitElement {
|
|
|
100
213
|
}
|
|
101
214
|
_ondblclick(e) {
|
|
102
215
|
e.stopPropagation();
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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];
|
|
216
|
+
const component = this._resolveComponent(e);
|
|
217
|
+
if (component) {
|
|
218
|
+
if (component.isContainer()) {
|
|
219
|
+
this.controller.toggleExtended(component);
|
|
159
220
|
}
|
|
221
|
+
// 캔버스를 해당 컴포넌트로 센터 이동(줌).
|
|
222
|
+
this.controller.centerTo(component);
|
|
160
223
|
}
|
|
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
224
|
this.requestUpdate();
|
|
167
225
|
}
|
|
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
|
-
const foundChildren = (component.components || []).filter((child) => this.shouldBeShown(child, counting)).length;
|
|
175
|
-
return !!(found || foundChildren > 0);
|
|
176
|
-
}
|
|
177
|
-
renderComponent(component, depth) {
|
|
178
|
-
var _a;
|
|
179
|
-
if (!component) {
|
|
180
|
-
return html ``;
|
|
181
|
-
}
|
|
182
|
-
if (!this.shouldBeShown(component, depth == 0)) {
|
|
183
|
-
return html ``;
|
|
184
|
-
}
|
|
185
|
-
const children = (component.isContainer() && component.components) || [];
|
|
186
|
-
const extended = this.isExtended(component) ? children.filter(child => this.shouldBeShown(child)) : [];
|
|
187
|
-
const { type, id, tag, class: clazz } = component.state;
|
|
188
|
-
const name = (id ? `#${id}` : '') + (tag ? `@${tag}` : '') + (clazz ? `.(${clazz})` : '');
|
|
189
|
-
return html `
|
|
190
|
-
<div
|
|
191
|
-
class="component"
|
|
192
|
-
?selected=${(((_a = this.scene) === null || _a === void 0 ? void 0 : _a.selected) || []).indexOf(component) > -1}
|
|
193
|
-
.component=${component}
|
|
194
|
-
?sortable=${component.isContainer()}
|
|
195
|
-
>
|
|
196
|
-
<span>
|
|
197
|
-
${depth > 0
|
|
198
|
-
? html `
|
|
199
|
-
<md-icon class="eye">${component.get('hidden') ? 'visibility_off' : 'visibility'}</md-icon>
|
|
200
|
-
<pre>${' '.repeat(depth)}</pre>
|
|
201
|
-
`
|
|
202
|
-
: html ` <pre>${' '.repeat(depth + 2)}</pre> `}
|
|
203
|
-
|
|
204
|
-
<span class=${this.getNodeHandleClass(component)}> </span>
|
|
205
|
-
|
|
206
|
-
<span class="type">${depth == 0 ? html `ROOT(count: ${this.count})` : type}</span>
|
|
207
|
-
${name ? html ` <span class="name">${name}</span> ` : html ``}
|
|
208
|
-
</span>
|
|
209
|
-
|
|
210
|
-
${extended.map(child => this.renderComponent(child, depth + 1))}
|
|
211
|
-
</div>
|
|
212
|
-
`;
|
|
213
|
-
}
|
|
214
226
|
}
|
|
215
227
|
SceneInspector.styles = [
|
|
216
228
|
css `
|
|
@@ -277,11 +289,26 @@ SceneInspector.styles = [
|
|
|
277
289
|
}
|
|
278
290
|
|
|
279
291
|
.eye {
|
|
280
|
-
margin: 0 0 0
|
|
292
|
+
margin: 0 0 0 4px;
|
|
281
293
|
vertical-align: middle;
|
|
282
294
|
opacity: 0.7;
|
|
283
|
-
font-size:
|
|
295
|
+
font-size: 1em;
|
|
296
|
+
width: 1em;
|
|
284
297
|
color: var(--scene-inspector-eye-icon-color);
|
|
298
|
+
cursor: pointer;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
.lock {
|
|
302
|
+
margin: 0;
|
|
303
|
+
vertical-align: middle;
|
|
304
|
+
opacity: 0.3;
|
|
305
|
+
font-size: 1em;
|
|
306
|
+
width: 1em;
|
|
307
|
+
cursor: pointer;
|
|
308
|
+
}
|
|
309
|
+
.lock[locked] {
|
|
310
|
+
opacity: 0.9;
|
|
311
|
+
color: var(--md-sys-color-error, #d9534f);
|
|
285
312
|
}
|
|
286
313
|
|
|
287
314
|
.collapsed::before,
|
|
@@ -316,6 +343,89 @@ SceneInspector.styles = [
|
|
|
316
343
|
|
|
317
344
|
pre {
|
|
318
345
|
display: inline;
|
|
346
|
+
/* 트리 인덴트(수직라인까지의 간격) 최소화 — depth 당 좁은 폭 */
|
|
347
|
+
font-size: 0.6em;
|
|
348
|
+
margin: 0;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/* 뷰 모드 툴바 */
|
|
352
|
+
div[toolbar] {
|
|
353
|
+
display: flex;
|
|
354
|
+
align-items: center;
|
|
355
|
+
gap: 4px;
|
|
356
|
+
padding: 2px 8px 4px;
|
|
357
|
+
}
|
|
358
|
+
div[toolbar] .spacer {
|
|
359
|
+
flex: 1;
|
|
360
|
+
}
|
|
361
|
+
div[toolbar] md-icon {
|
|
362
|
+
cursor: pointer;
|
|
363
|
+
opacity: 0.5;
|
|
364
|
+
font-size: 18px;
|
|
365
|
+
border-radius: 4px;
|
|
366
|
+
padding: 2px;
|
|
367
|
+
}
|
|
368
|
+
div[toolbar] md-icon:hover {
|
|
369
|
+
opacity: 0.9;
|
|
370
|
+
}
|
|
371
|
+
div[toolbar] md-icon[selected] {
|
|
372
|
+
opacity: 1;
|
|
373
|
+
color: var(--md-sys-color-primary, #4a9eff);
|
|
374
|
+
background: rgba(0, 0, 0, 0.05);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/* 타입 그룹 뷰 */
|
|
378
|
+
.group-header {
|
|
379
|
+
display: flex;
|
|
380
|
+
align-items: center;
|
|
381
|
+
gap: 4px;
|
|
382
|
+
font-size: 14px;
|
|
383
|
+
font-weight: 600;
|
|
384
|
+
cursor: pointer;
|
|
385
|
+
padding: 2px 0;
|
|
386
|
+
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
|
387
|
+
}
|
|
388
|
+
.group-header:hover {
|
|
389
|
+
background: rgba(0, 0, 0, 0.03);
|
|
390
|
+
}
|
|
391
|
+
.group-count {
|
|
392
|
+
margin-left: auto;
|
|
393
|
+
font-size: 0.72em;
|
|
394
|
+
font-weight: normal;
|
|
395
|
+
background: var(--scene-inspector-name-background-color);
|
|
396
|
+
color: #fff;
|
|
397
|
+
border-radius: 999px;
|
|
398
|
+
padding: 0 6px;
|
|
399
|
+
}
|
|
400
|
+
.group-dim {
|
|
401
|
+
margin: 0 4px 0 6px;
|
|
402
|
+
font-size: 1em;
|
|
403
|
+
opacity: 0.35;
|
|
404
|
+
cursor: pointer;
|
|
405
|
+
}
|
|
406
|
+
.group-dim:hover {
|
|
407
|
+
opacity: 0.8;
|
|
408
|
+
}
|
|
409
|
+
.group-header[dimmed] .group-dim {
|
|
410
|
+
opacity: 0.95;
|
|
411
|
+
color: var(--md-sys-color-primary, #4a9eff);
|
|
412
|
+
}
|
|
413
|
+
.group-header[dimmed] .group-type {
|
|
414
|
+
opacity: 0.45;
|
|
415
|
+
}
|
|
416
|
+
.group-member {
|
|
417
|
+
padding-left: 8px;
|
|
418
|
+
}
|
|
419
|
+
.muted {
|
|
420
|
+
opacity: 0.4;
|
|
421
|
+
font-style: italic;
|
|
422
|
+
}
|
|
423
|
+
/* 검색 매치 하이라이트 */
|
|
424
|
+
mark {
|
|
425
|
+
background: var(--md-sys-color-tertiary-container, #ffe082);
|
|
426
|
+
color: inherit;
|
|
427
|
+
border-radius: 2px;
|
|
428
|
+
padding: 0 1px;
|
|
319
429
|
}
|
|
320
430
|
`
|
|
321
431
|
];
|
|
@@ -325,4 +435,7 @@ __decorate([
|
|
|
325
435
|
__decorate([
|
|
326
436
|
state()
|
|
327
437
|
], SceneInspector.prototype, "searchText", void 0);
|
|
438
|
+
__decorate([
|
|
439
|
+
state()
|
|
440
|
+
], SceneInspector.prototype, "viewMode", void 0);
|
|
328
441
|
//# sourceMappingURL=inspector.js.map
|