@fmsim/board 2.0.0-beta.107 → 2.0.0-beta.109
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 +1495 -1495
- package/dist/src/board-i18n.d.ts +22 -0
- package/dist/src/board-i18n.js +98 -0
- package/dist/src/board-i18n.js.map +1 -0
- package/dist/src/modeller/property-sidebar/inspector/inspector-controller.d.ts +5 -1
- package/dist/src/modeller/property-sidebar/inspector/inspector-controller.js +16 -4
- package/dist/src/modeller/property-sidebar/inspector/inspector-controller.js.map +1 -1
- package/dist/src/modeller/property-sidebar/inspector/inspector-group-view.js +8 -3
- package/dist/src/modeller/property-sidebar/inspector/inspector-group-view.js.map +1 -1
- package/dist/src/modeller/property-sidebar/inspector/inspector.js +9 -4
- 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,22 @@
|
|
|
1
|
+
/** MCS 세션 캐시 ifactI18nObj 의 키 = 지원 언어 목록. 없으면 현재 언어만. */
|
|
2
|
+
export declare function getSupportedLanguages(): string[];
|
|
3
|
+
/**
|
|
4
|
+
* 언어 코드를 사람이 읽기 쉬운 이름으로 변환한다(표시는 현재 UI 언어).
|
|
5
|
+
* Intl.DisplayNames 로 'en'→English, 'en-US'→American English, 'zh_CN'→Chinese(China) 처럼
|
|
6
|
+
* 같은 언어의 여러 로케일도 구분되게 표기한다. 비표준/해석 불가 코드(예: 'ua')는 코드 그대로.
|
|
7
|
+
*/
|
|
8
|
+
export declare function languageLabel(code: string): string;
|
|
9
|
+
/** nameI18n(JSON 문자열 또는 객체)을 언어→이름 맵으로 파싱. */
|
|
10
|
+
export declare function parseNameI18n(nameI18n: any): Record<string, string>;
|
|
11
|
+
/** 채워진 항목만 남긴 맵을 JSON 문자열로. 비어 있으면 undefined(저장 안 함). */
|
|
12
|
+
export declare function stringifyNameI18n(map: Record<string, string>): string | undefined;
|
|
13
|
+
/**
|
|
14
|
+
* 언어 맵에서 lang 에 해당하는 값을 관대하게 찾는다. 없으면 ''.
|
|
15
|
+
* 1) 정확 일치 2) 구분자 정규화(zh-CN↔zh_CN) 3) 베이스 언어(en-US→en) 4) 키 베이스 일치
|
|
16
|
+
*/
|
|
17
|
+
export declare function pickLangValue(map: Record<string, string>, lang: string): string;
|
|
18
|
+
/** 현재 언어 보드명 → 없으면 대표명(name) 폴백. 보드명 표시/리스팅의 단일 진입점. */
|
|
19
|
+
export declare function resolveBoardName(board: {
|
|
20
|
+
name?: string;
|
|
21
|
+
nameI18n?: any;
|
|
22
|
+
} | null | undefined): string;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 보드명 다국어 유틸 (공유).
|
|
3
|
+
*
|
|
4
|
+
* 서버는 보드의 대표명(name) + 전체 다국어 맵(nameI18n JSON)만 내려주고, "현재 언어로 무엇을
|
|
5
|
+
* 보여줄지"는 클라이언트가 정한다. 보드명을 화면에 표시/리스팅하는 모든 곳에서 resolveBoardName 을
|
|
6
|
+
* 사용해야 일관되게 현재 언어 이름(없으면 대표명)으로 표시된다.
|
|
7
|
+
*
|
|
8
|
+
* @fmsim/board 에 두어 board/fmsim-restful 등 모든 클라이언트 컴포넌트가 공유한다.
|
|
9
|
+
*/
|
|
10
|
+
import { i18next } from '@operato/i18n';
|
|
11
|
+
/** MCS 세션 캐시 ifactI18nObj 의 키 = 지원 언어 목록. 없으면 현재 언어만. */
|
|
12
|
+
export function getSupportedLanguages() {
|
|
13
|
+
try {
|
|
14
|
+
const raw = sessionStorage.getItem('ifactI18nObj');
|
|
15
|
+
if (raw) {
|
|
16
|
+
const keys = Object.keys(JSON.parse(raw) || {});
|
|
17
|
+
if (keys.length)
|
|
18
|
+
return keys;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
catch (_a) {
|
|
22
|
+
/* 파싱 실패 시 폴백 */
|
|
23
|
+
}
|
|
24
|
+
const cur = i18next.language;
|
|
25
|
+
return cur ? [cur] : [];
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* 언어 코드를 사람이 읽기 쉬운 이름으로 변환한다(표시는 현재 UI 언어).
|
|
29
|
+
* Intl.DisplayNames 로 'en'→English, 'en-US'→American English, 'zh_CN'→Chinese(China) 처럼
|
|
30
|
+
* 같은 언어의 여러 로케일도 구분되게 표기한다. 비표준/해석 불가 코드(예: 'ua')는 코드 그대로.
|
|
31
|
+
*/
|
|
32
|
+
export function languageLabel(code) {
|
|
33
|
+
const c = (code || '').trim();
|
|
34
|
+
if (!c)
|
|
35
|
+
return '';
|
|
36
|
+
try {
|
|
37
|
+
const dn = new Intl.DisplayNames([i18next.language || 'en'], { type: 'language' });
|
|
38
|
+
const name = dn.of(c.replace(/_/g, '-'));
|
|
39
|
+
if (name && name.toLowerCase() !== c.toLowerCase())
|
|
40
|
+
return name;
|
|
41
|
+
}
|
|
42
|
+
catch (_a) {
|
|
43
|
+
/* 비표준 코드 등 → 코드 그대로 */
|
|
44
|
+
}
|
|
45
|
+
return c;
|
|
46
|
+
}
|
|
47
|
+
/** nameI18n(JSON 문자열 또는 객체)을 언어→이름 맵으로 파싱. */
|
|
48
|
+
export function parseNameI18n(nameI18n) {
|
|
49
|
+
if (!nameI18n)
|
|
50
|
+
return {};
|
|
51
|
+
if (typeof nameI18n === 'object')
|
|
52
|
+
return { ...nameI18n };
|
|
53
|
+
try {
|
|
54
|
+
return JSON.parse(nameI18n) || {};
|
|
55
|
+
}
|
|
56
|
+
catch (_a) {
|
|
57
|
+
return {};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/** 채워진 항목만 남긴 맵을 JSON 문자열로. 비어 있으면 undefined(저장 안 함). */
|
|
61
|
+
export function stringifyNameI18n(map) {
|
|
62
|
+
const cleaned = {};
|
|
63
|
+
for (const [k, v] of Object.entries(map || {})) {
|
|
64
|
+
if (v != null && String(v).trim() !== '')
|
|
65
|
+
cleaned[k] = String(v);
|
|
66
|
+
}
|
|
67
|
+
return Object.keys(cleaned).length ? JSON.stringify(cleaned) : undefined;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* 언어 맵에서 lang 에 해당하는 값을 관대하게 찾는다. 없으면 ''.
|
|
71
|
+
* 1) 정확 일치 2) 구분자 정규화(zh-CN↔zh_CN) 3) 베이스 언어(en-US→en) 4) 키 베이스 일치
|
|
72
|
+
*/
|
|
73
|
+
export function pickLangValue(map, lang) {
|
|
74
|
+
if (!lang || !map)
|
|
75
|
+
return '';
|
|
76
|
+
if (map[lang])
|
|
77
|
+
return map[lang];
|
|
78
|
+
const under = lang.replace(/-/g, '_');
|
|
79
|
+
if (map[under])
|
|
80
|
+
return map[under];
|
|
81
|
+
const dash = lang.replace(/_/g, '-');
|
|
82
|
+
if (map[dash])
|
|
83
|
+
return map[dash];
|
|
84
|
+
const base = lang.split(/[-_]/)[0];
|
|
85
|
+
if (map[base])
|
|
86
|
+
return map[base];
|
|
87
|
+
const hit = Object.keys(map).find(k => k.split(/[-_]/)[0] === base);
|
|
88
|
+
return (hit && map[hit]) || '';
|
|
89
|
+
}
|
|
90
|
+
/** 현재 언어 보드명 → 없으면 대표명(name) 폴백. 보드명 표시/리스팅의 단일 진입점. */
|
|
91
|
+
export function resolveBoardName(board) {
|
|
92
|
+
if (!board)
|
|
93
|
+
return '';
|
|
94
|
+
const map = parseNameI18n(board.nameI18n);
|
|
95
|
+
const lang = i18next.language || '';
|
|
96
|
+
return pickLangValue(map, lang) || board.name || '';
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=board-i18n.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"board-i18n.js","sourceRoot":"","sources":["../../src/board-i18n.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * 보드명 다국어 유틸 (공유).\n *\n * 서버는 보드의 대표명(name) + 전체 다국어 맵(nameI18n JSON)만 내려주고, \"현재 언어로 무엇을\n * 보여줄지\"는 클라이언트가 정한다. 보드명을 화면에 표시/리스팅하는 모든 곳에서 resolveBoardName 을\n * 사용해야 일관되게 현재 언어 이름(없으면 대표명)으로 표시된다.\n *\n * @fmsim/board 에 두어 board/fmsim-restful 등 모든 클라이언트 컴포넌트가 공유한다.\n */\nimport { i18next } from '@operato/i18n'\n\n/** MCS 세션 캐시 ifactI18nObj 의 키 = 지원 언어 목록. 없으면 현재 언어만. */\nexport function getSupportedLanguages(): string[] {\n try {\n const raw = sessionStorage.getItem('ifactI18nObj')\n if (raw) {\n const keys = Object.keys(JSON.parse(raw) || {})\n if (keys.length) return keys\n }\n } catch {\n /* 파싱 실패 시 폴백 */\n }\n const cur = i18next.language\n return cur ? [cur] : []\n}\n\n/**\n * 언어 코드를 사람이 읽기 쉬운 이름으로 변환한다(표시는 현재 UI 언어).\n * Intl.DisplayNames 로 'en'→English, 'en-US'→American English, 'zh_CN'→Chinese(China) 처럼\n * 같은 언어의 여러 로케일도 구분되게 표기한다. 비표준/해석 불가 코드(예: 'ua')는 코드 그대로.\n */\nexport function languageLabel(code: string): string {\n const c = (code || '').trim()\n if (!c) return ''\n try {\n const dn = new (Intl as any).DisplayNames([i18next.language || 'en'], { type: 'language' })\n const name = dn.of(c.replace(/_/g, '-'))\n if (name && name.toLowerCase() !== c.toLowerCase()) return name\n } catch {\n /* 비표준 코드 등 → 코드 그대로 */\n }\n return c\n}\n\n/** nameI18n(JSON 문자열 또는 객체)을 언어→이름 맵으로 파싱. */\nexport function parseNameI18n(nameI18n: any): Record<string, string> {\n if (!nameI18n) return {}\n if (typeof nameI18n === 'object') return { ...nameI18n }\n try {\n return JSON.parse(nameI18n) || {}\n } catch {\n return {}\n }\n}\n\n/** 채워진 항목만 남긴 맵을 JSON 문자열로. 비어 있으면 undefined(저장 안 함). */\nexport function stringifyNameI18n(map: Record<string, string>): string | undefined {\n const cleaned: Record<string, string> = {}\n for (const [k, v] of Object.entries(map || {})) {\n if (v != null && String(v).trim() !== '') cleaned[k] = String(v)\n }\n return Object.keys(cleaned).length ? JSON.stringify(cleaned) : undefined\n}\n\n/**\n * 언어 맵에서 lang 에 해당하는 값을 관대하게 찾는다. 없으면 ''.\n * 1) 정확 일치 2) 구분자 정규화(zh-CN↔zh_CN) 3) 베이스 언어(en-US→en) 4) 키 베이스 일치\n */\nexport function pickLangValue(map: Record<string, string>, lang: string): string {\n if (!lang || !map) return ''\n if (map[lang]) return map[lang]\n const under = lang.replace(/-/g, '_')\n if (map[under]) return map[under]\n const dash = lang.replace(/_/g, '-')\n if (map[dash]) return map[dash]\n const base = lang.split(/[-_]/)[0]\n if (map[base]) return map[base]\n const hit = Object.keys(map).find(k => k.split(/[-_]/)[0] === base)\n return (hit && map[hit]) || ''\n}\n\n/** 현재 언어 보드명 → 없으면 대표명(name) 폴백. 보드명 표시/리스팅의 단일 진입점. */\nexport function resolveBoardName(board: { name?: string; nameI18n?: any } | null | undefined): string {\n if (!board) return ''\n const map = parseNameI18n(board.nameI18n)\n const lang = i18next.language || ''\n return pickLangValue(map, lang) || board.name || ''\n}\n"]}
|
|
@@ -5,7 +5,11 @@ export declare class InspectorController {
|
|
|
5
5
|
private _extendedMap;
|
|
6
6
|
/** 타입 그룹 뷰의 펼침 상태 — 그룹 키(type)는 안정적이라 Set 으로 관리. 기본 전부 접힘. */
|
|
7
7
|
private _expandedGroups;
|
|
8
|
-
/**
|
|
8
|
+
/**
|
|
9
|
+
* 모델링 포커스 dim 처리된 그룹(타입) 집합.
|
|
10
|
+
* scene 에 보존(scene._inspectorDimmedTypes)하여 인스펙터 탭을 벗어났다 돌아와도 유지된다.
|
|
11
|
+
* (보드를 다시 열면 새 scene 이라 자연 초기화 — transient.) bind 에서 scene 것으로 재취득.
|
|
12
|
+
*/
|
|
9
13
|
private _dimmedTypes;
|
|
10
14
|
/** shift+click 범위(볼륨) 선택의 기준점 — 마지막 단일/토글 클릭. */
|
|
11
15
|
private _anchor?;
|
|
@@ -4,7 +4,11 @@ export class InspectorController {
|
|
|
4
4
|
this._extendedMap = new WeakMap();
|
|
5
5
|
/** 타입 그룹 뷰의 펼침 상태 — 그룹 키(type)는 안정적이라 Set 으로 관리. 기본 전부 접힘. */
|
|
6
6
|
this._expandedGroups = new Set();
|
|
7
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* 모델링 포커스 dim 처리된 그룹(타입) 집합.
|
|
9
|
+
* scene 에 보존(scene._inspectorDimmedTypes)하여 인스펙터 탭을 벗어났다 돌아와도 유지된다.
|
|
10
|
+
* (보드를 다시 열면 새 scene 이라 자연 초기화 — transient.) bind 에서 scene 것으로 재취득.
|
|
11
|
+
*/
|
|
8
12
|
this._dimmedTypes = new Set();
|
|
9
13
|
this._invalidateAndRefresh = () => {
|
|
10
14
|
this._index = undefined;
|
|
@@ -31,6 +35,12 @@ export class InspectorController {
|
|
|
31
35
|
this._onChange = onChange;
|
|
32
36
|
this._index = undefined;
|
|
33
37
|
if (scene && scene.root) {
|
|
38
|
+
// dim 상태는 scene 에 보존 — 인스펙터 탭을 벗어났다 돌아와도(재마운트) 유지된다.
|
|
39
|
+
const anyScene = scene;
|
|
40
|
+
if (!anyScene._inspectorDimmedTypes)
|
|
41
|
+
anyScene._inspectorDimmedTypes = new Set();
|
|
42
|
+
this._dimmedTypes = anyScene._inspectorDimmedTypes;
|
|
43
|
+
this._applyFocusFilter(); // 보존된 dim 을 캔버스에 재적용 (idempotent)
|
|
34
44
|
scene.on('selected', this._refresh, this);
|
|
35
45
|
scene.on('execute', this._invalidateAndRefresh, this);
|
|
36
46
|
scene.on('undo', this._invalidateAndRefresh, this);
|
|
@@ -41,8 +51,9 @@ export class InspectorController {
|
|
|
41
51
|
unbind() {
|
|
42
52
|
const scene = this._scene;
|
|
43
53
|
if (scene) {
|
|
44
|
-
// 포커스
|
|
45
|
-
scene.focusFilter
|
|
54
|
+
// dim(포커스 필터)은 편집 세션 동안 유지 — 인스펙터 탭을 벗어나도 해제하지 않는다.
|
|
55
|
+
// scene.focusFilter 와 scene._inspectorDimmedTypes 는 그대로 두고, 이벤트 구독만 해제.
|
|
56
|
+
// (보드를 다시 열면 새 scene 이라 자연 초기화된다.)
|
|
46
57
|
scene.off('selected', undefined, this);
|
|
47
58
|
scene.off('execute', undefined, this);
|
|
48
59
|
scene.off('undo', undefined, this);
|
|
@@ -51,7 +62,8 @@ export class InspectorController {
|
|
|
51
62
|
this._scene = undefined;
|
|
52
63
|
this._index = undefined;
|
|
53
64
|
this._extendedMap = new WeakMap();
|
|
54
|
-
|
|
65
|
+
// _dimmedTypes 는 scene 에 보존되므로 지우지 않는다. 로컬 참조만 분리(다음 bind 에서 재취득).
|
|
66
|
+
this._dimmedTypes = new Set();
|
|
55
67
|
}
|
|
56
68
|
get scene() {
|
|
57
69
|
return this._scene;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inspector-controller.js","sourceRoot":"","sources":["../../../../../src/modeller/property-sidebar/inspector/inspector-controller.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * Scene Inspector 컨트롤러.\n *\n * scene 과의 상호작용(구독·선택·가시성·확장 상태)과 모델 인덱스 캐시를 한 곳에 응집한다.\n * DOM 을 직접 다루지 않으며, 뷰 렌더러(트리/그룹)가 공유한다. 상태 변경 시 onChange 로\n * 호스트(LitElement)의 재렌더를 요청한다.\n */\nimport { Component, Container, Scene } from '@hatiolab/things-scene'\n\nimport { buildInspectorIndex, InspectorIndex, matchesQuery, SearchTerm } from './inspector-model.js'\n\nexport class InspectorController {\n private _scene?: Scene\n private _extendedMap = new WeakMap<Component, boolean>()\n /** 타입 그룹 뷰의 펼침 상태 — 그룹 키(type)는 안정적이라 Set 으로 관리. 기본 전부 접힘. */\n private _expandedGroups = new Set<string>()\n /** 모델링 포커스 dim 처리된 그룹(타입) 집합 — transient(비영속). */\n private _dimmedTypes = new Set<string>()\n /** shift+click 범위(볼륨) 선택의 기준점 — 마지막 단일/토글 클릭. */\n private _anchor?: Component\n private _index?: InspectorIndex\n private _onChange?: () => void\n\n /** scene 에 바인딩 — 이전 바인딩은 해제한다. onChange 는 재렌더 요청 콜백. */\n bind(scene: Scene | undefined, onChange: () => void): void {\n this.unbind()\n this._scene = scene\n this._onChange = onChange\n this._index = undefined\n\n if (scene && scene.root) {\n scene.on('selected', this._refresh, this)\n scene.on('execute', this._invalidateAndRefresh, this)\n scene.on('undo', this._invalidateAndRefresh, this)\n scene.on('redo', this._invalidateAndRefresh, this)\n this._refresh()\n }\n }\n\n unbind(): void {\n const scene = this._scene\n if (scene) {\n // 포커스 dim 은 transient — 인스펙터가 떠나면 캔버스 dim 해제.\n scene.focusFilter = undefined\n scene.off('selected', undefined, this)\n scene.off('execute', undefined, this)\n scene.off('undo', undefined, this)\n scene.off('redo', undefined, this)\n }\n this._scene = undefined\n this._index = undefined\n this._extendedMap = new WeakMap()\n this._dimmedTypes.clear()\n }\n\n get scene(): Scene | undefined {\n return this._scene\n }\n\n get selected(): Component[] {\n return this._scene?.selected || []\n }\n\n /** 캐시된 모델 인덱스 — 없으면 build. execute/undo/redo 시 무효화. */\n get index(): InspectorIndex | undefined {\n if (!this._scene?.root) return undefined\n if (!this._index) this._index = buildInspectorIndex(this._scene.root)\n return this._index\n }\n\n private _invalidateAndRefresh = (): void => {\n this._index = undefined\n this._refresh()\n }\n\n /** 선택된 컴포넌트의 조상을 펼치고 재렌더 요청. */\n private _refresh = (): void => {\n const selected = this._scene?.selected || []\n selected.forEach((component: Component) => {\n let parent = component.parent\n while (parent && !this._extendedMap.get(parent)) {\n this._extendedMap.set(parent, true)\n parent = parent.parent\n }\n })\n this._onChange?.()\n }\n\n isExtended(component: Component): boolean {\n return !!this._extendedMap.get(component)\n }\n\n toggleExtended(component: Component): void {\n if (this.isExtended(component)) {\n this._extendedMap.delete(component)\n } else {\n this._extendedMap.set(component, true)\n }\n this._onChange?.()\n }\n\n /** 트리 노드 핸들(펼침/접힘/공백) 클래스. */\n nodeHandleClass(component: Component): string {\n if (component.isContainer() && (component as Container).components.length > 0) {\n return this.isExtended(component) ? 'extended' : 'collapsed'\n }\n return 'collapsespace'\n }\n\n // ── 타입 그룹 뷰 확장 상태 ──\n\n isGroupExpanded(type: string): boolean {\n return this._expandedGroups.has(type)\n }\n\n toggleGroup(type: string): void {\n if (this._expandedGroups.has(type)) {\n this._expandedGroups.delete(type)\n } else {\n this._expandedGroups.add(type)\n }\n this._onChange?.()\n }\n\n /** 모든 그룹 접기 — \"그룹 레벨까지만 펼침\"(헤더만 표시). */\n collapseAllGroups(): void {\n if (this._expandedGroups.size === 0) return\n this._expandedGroups.clear()\n this._onChange?.()\n }\n\n /** 모든 그룹 펼치기. */\n expandAllGroups(types: string[]): void {\n types.forEach(t => this._expandedGroups.add(t))\n this._onChange?.()\n }\n\n // ── 모델링 포커스 dim (그룹 단위, transient) ──\n\n isTypeDimmed(type: string): boolean {\n return this._dimmedTypes.has(type)\n }\n\n /** 그룹(타입) dim-out 토글 → scene.focusFilter 재설정(편집 모드에서 캔버스 dim + pick 제외). */\n toggleTypeDim(type: string): void {\n if (this._dimmedTypes.has(type)) this._dimmedTypes.delete(type)\n else this._dimmedTypes.add(type)\n this._applyFocusFilter()\n this._onChange?.()\n }\n\n private _applyFocusFilter(): void {\n if (!this._scene) return\n if (this._dimmedTypes.size === 0) {\n this._scene.focusFilter = undefined\n } else {\n const dimmed = this._dimmedTypes\n // focus(정상) = dim 집합에 없는 타입. 불일치(=dim 그룹)만 캔버스에서 흐림 + pick 제외.\n this._scene.focusFilter = (component: Component) => !dimmed.has(component.state.type)\n }\n }\n\n // ── 그룹(타입) 단위 hidden 토글 (개별 hidden 의 집계 — dim 과 달리 영구 모델 속성) ──\n\n /** 그룹 전원이 hidden 이면 true(집계). 멤버/트리 eye 규약과 동일한 아이콘 판정에 사용. */\n isTypeHidden(type: string): boolean {\n const entries = this._index?.byType.get(type)\n return !!entries && entries.length > 0 && entries.every(e => !!e.component.get('hidden'))\n }\n\n /** 그룹(타입) hidden 토글 → 하나라도 보이면 전원 숨김, 전원 숨김이면 전원 표시. */\n toggleTypeHidden(type: string): void {\n const entries = this._index?.byType.get(type)\n if (!entries || entries.length === 0) return\n const target = !entries.every(e => !!e.component.get('hidden'))\n for (const e of entries) e.component.set('hidden', target)\n this._onChange?.()\n }\n\n /** 개별 컴포넌트 hidden(영구 모델 속성) 토글 — 기존 eye 동작. */\n toggleHidden(component: Component): void {\n component.set('hidden', !component.get('hidden'))\n this._onChange?.()\n }\n\n /** 개별 컴포넌트 locked(영구 모델 속성) 토글. */\n toggleLocked(component: Component): void {\n component.set('locked', !component.get('locked'))\n this._onChange?.()\n }\n\n /** 캔버스를 해당 컴포넌트로 센터 이동. */\n centerTo(component: Component): void {\n ;(this._scene as unknown as { centerTo?: (c: Component, animated?: boolean) => void })?.centerTo?.(component, true)\n }\n\n /** 선택 — append(ctrl/cmd) 시 토글식 다중 선택. anchor 를 갱신한다. */\n select(component: Component, append = false): void {\n const scene = this._scene\n if (!scene) return\n\n const selected = scene.selected\n if (append) {\n const idx = selected.findIndex((s: Component) => s === component)\n if (idx != -1) {\n selected.splice(idx, 1)\n scene.selected = [...selected]\n } else {\n scene.selected = [...selected, component]\n }\n } else {\n scene.selected = [component]\n }\n\n this._anchor = component\n this._decorate(component)\n this._onChange?.()\n }\n\n /**\n * shift+click 범위(볼륨) 선택 — anchor↔target 사이의 **보이는 트리 행**을 블록 선택한다.\n * target 이 이미 선택돼 있으면 그 범위를 블록 **해제**(additive/subtractive). anchor 는 유지.\n */\n selectRange(target: Component, terms: SearchTerm[]): void {\n const scene = this._scene\n if (!scene) return\n\n const order = this._visibleOrder(terms)\n const ti = order.indexOf(target)\n if (ti < 0) return\n\n const ai = this._anchor ? order.indexOf(this._anchor) : -1\n if (ai < 0) {\n // anchor 없음/보이지 않음 → 단일 토글로 폴백(+ anchor 설정)\n this.select(target, true)\n return\n }\n\n const [lo, hi] = ai <= ti ? [ai, ti] : [ti, ai]\n const range = order.slice(lo, hi + 1)\n const rangeSet = new Set(range)\n\n // target 이 이미 선택 상태면 블록 해제, 아니면 블록 선택.\n if (scene.selected.indexOf(target) > -1) {\n scene.selected = scene.selected.filter((c: Component) => !rangeSet.has(c))\n range.forEach(c => c.trigger('deco-border-reset'))\n } else {\n const cur = new Set(scene.selected)\n const next = [...scene.selected]\n range.forEach(c => {\n if (!cur.has(c)) next.push(c)\n this._decorate(c)\n })\n scene.selected = next\n }\n // anchor 는 유지 — 연속 shift+click 으로 범위 확장/축소.\n this._onChange?.()\n }\n\n /** fmsim 선택 강조 — deco-border (operato 는 reaction). */\n private _decorate(component: Component): void {\n component.trigger('deco-border-reset')\n component.trigger('deco-border', { distance: 8, lineWidth: 8, color: '#f007' })\n }\n\n /**\n * 현재 트리 뷰에 보이는 컴포넌트를 렌더 순서대로 반환 — 범위 선택의 기준.\n * renderNode 와 동일 규칙(shouldBeShown = 자신/자손 매치, isExtended 자식만 전개).\n */\n private _visibleOrder(terms: SearchTerm[]): Component[] {\n const root = this._scene?.root\n if (!root) return []\n\n const shown = (c: Component): boolean =>\n matchesQuery(c, terms) ||\n (c.isContainer() && (c as Container).components.some((child: Component) => shown(child)))\n\n const out: Component[] = []\n const walk = (c: Component) => {\n if (!shown(c)) return\n out.push(c)\n if (this.isExtended(c) && c.isContainer()) {\n for (const child of (c as Container).components) walk(child)\n }\n }\n walk(root)\n return out\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"inspector-controller.js","sourceRoot":"","sources":["../../../../../src/modeller/property-sidebar/inspector/inspector-controller.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * Scene Inspector 컨트롤러.\n *\n * scene 과의 상호작용(구독·선택·가시성·확장 상태)과 모델 인덱스 캐시를 한 곳에 응집한다.\n * DOM 을 직접 다루지 않으며, 뷰 렌더러(트리/그룹)가 공유한다. 상태 변경 시 onChange 로\n * 호스트(LitElement)의 재렌더를 요청한다.\n */\nimport { Component, Container, Scene } from '@hatiolab/things-scene'\n\nimport { buildInspectorIndex, InspectorIndex, matchesQuery, SearchTerm } from './inspector-model.js'\n\nexport class InspectorController {\n private _scene?: Scene\n private _extendedMap = new WeakMap<Component, boolean>()\n /** 타입 그룹 뷰의 펼침 상태 — 그룹 키(type)는 안정적이라 Set 으로 관리. 기본 전부 접힘. */\n private _expandedGroups = new Set<string>()\n /**\n * 모델링 포커스 dim 처리된 그룹(타입) 집합.\n * scene 에 보존(scene._inspectorDimmedTypes)하여 인스펙터 탭을 벗어났다 돌아와도 유지된다.\n * (보드를 다시 열면 새 scene 이라 자연 초기화 — transient.) bind 에서 scene 것으로 재취득.\n */\n private _dimmedTypes = new Set<string>()\n /** shift+click 범위(볼륨) 선택의 기준점 — 마지막 단일/토글 클릭. */\n private _anchor?: Component\n private _index?: InspectorIndex\n private _onChange?: () => void\n\n /** scene 에 바인딩 — 이전 바인딩은 해제한다. onChange 는 재렌더 요청 콜백. */\n bind(scene: Scene | undefined, onChange: () => void): void {\n this.unbind()\n this._scene = scene\n this._onChange = onChange\n this._index = undefined\n\n if (scene && scene.root) {\n // dim 상태는 scene 에 보존 — 인스펙터 탭을 벗어났다 돌아와도(재마운트) 유지된다.\n const anyScene = scene as any\n if (!anyScene._inspectorDimmedTypes) anyScene._inspectorDimmedTypes = new Set<string>()\n this._dimmedTypes = anyScene._inspectorDimmedTypes\n this._applyFocusFilter() // 보존된 dim 을 캔버스에 재적용 (idempotent)\n\n scene.on('selected', this._refresh, this)\n scene.on('execute', this._invalidateAndRefresh, this)\n scene.on('undo', this._invalidateAndRefresh, this)\n scene.on('redo', this._invalidateAndRefresh, this)\n this._refresh()\n }\n }\n\n unbind(): void {\n const scene = this._scene\n if (scene) {\n // dim(포커스 필터)은 편집 세션 동안 유지 — 인스펙터 탭을 벗어나도 해제하지 않는다.\n // scene.focusFilter 와 scene._inspectorDimmedTypes 는 그대로 두고, 이벤트 구독만 해제.\n // (보드를 다시 열면 새 scene 이라 자연 초기화된다.)\n scene.off('selected', undefined, this)\n scene.off('execute', undefined, this)\n scene.off('undo', undefined, this)\n scene.off('redo', undefined, this)\n }\n this._scene = undefined\n this._index = undefined\n this._extendedMap = new WeakMap()\n // _dimmedTypes 는 scene 에 보존되므로 지우지 않는다. 로컬 참조만 분리(다음 bind 에서 재취득).\n this._dimmedTypes = new Set<string>()\n }\n\n get scene(): Scene | undefined {\n return this._scene\n }\n\n get selected(): Component[] {\n return this._scene?.selected || []\n }\n\n /** 캐시된 모델 인덱스 — 없으면 build. execute/undo/redo 시 무효화. */\n get index(): InspectorIndex | undefined {\n if (!this._scene?.root) return undefined\n if (!this._index) this._index = buildInspectorIndex(this._scene.root)\n return this._index\n }\n\n private _invalidateAndRefresh = (): void => {\n this._index = undefined\n this._refresh()\n }\n\n /** 선택된 컴포넌트의 조상을 펼치고 재렌더 요청. */\n private _refresh = (): void => {\n const selected = this._scene?.selected || []\n selected.forEach((component: Component) => {\n let parent = component.parent\n while (parent && !this._extendedMap.get(parent)) {\n this._extendedMap.set(parent, true)\n parent = parent.parent\n }\n })\n this._onChange?.()\n }\n\n isExtended(component: Component): boolean {\n return !!this._extendedMap.get(component)\n }\n\n toggleExtended(component: Component): void {\n if (this.isExtended(component)) {\n this._extendedMap.delete(component)\n } else {\n this._extendedMap.set(component, true)\n }\n this._onChange?.()\n }\n\n /** 트리 노드 핸들(펼침/접힘/공백) 클래스. */\n nodeHandleClass(component: Component): string {\n if (component.isContainer() && (component as Container).components.length > 0) {\n return this.isExtended(component) ? 'extended' : 'collapsed'\n }\n return 'collapsespace'\n }\n\n // ── 타입 그룹 뷰 확장 상태 ──\n\n isGroupExpanded(type: string): boolean {\n return this._expandedGroups.has(type)\n }\n\n toggleGroup(type: string): void {\n if (this._expandedGroups.has(type)) {\n this._expandedGroups.delete(type)\n } else {\n this._expandedGroups.add(type)\n }\n this._onChange?.()\n }\n\n /** 모든 그룹 접기 — \"그룹 레벨까지만 펼침\"(헤더만 표시). */\n collapseAllGroups(): void {\n if (this._expandedGroups.size === 0) return\n this._expandedGroups.clear()\n this._onChange?.()\n }\n\n /** 모든 그룹 펼치기. */\n expandAllGroups(types: string[]): void {\n types.forEach(t => this._expandedGroups.add(t))\n this._onChange?.()\n }\n\n // ── 모델링 포커스 dim (그룹 단위, transient) ──\n\n isTypeDimmed(type: string): boolean {\n return this._dimmedTypes.has(type)\n }\n\n /** 그룹(타입) dim-out 토글 → scene.focusFilter 재설정(편집 모드에서 캔버스 dim + pick 제외). */\n toggleTypeDim(type: string): void {\n if (this._dimmedTypes.has(type)) this._dimmedTypes.delete(type)\n else this._dimmedTypes.add(type)\n this._applyFocusFilter()\n this._onChange?.()\n }\n\n private _applyFocusFilter(): void {\n if (!this._scene) return\n if (this._dimmedTypes.size === 0) {\n this._scene.focusFilter = undefined\n } else {\n const dimmed = this._dimmedTypes\n // focus(정상) = dim 집합에 없는 타입. 불일치(=dim 그룹)만 캔버스에서 흐림 + pick 제외.\n this._scene.focusFilter = (component: Component) => !dimmed.has(component.state.type)\n }\n }\n\n // ── 그룹(타입) 단위 hidden 토글 (개별 hidden 의 집계 — dim 과 달리 영구 모델 속성) ──\n\n /** 그룹 전원이 hidden 이면 true(집계). 멤버/트리 eye 규약과 동일한 아이콘 판정에 사용. */\n isTypeHidden(type: string): boolean {\n const entries = this._index?.byType.get(type)\n return !!entries && entries.length > 0 && entries.every(e => !!e.component.get('hidden'))\n }\n\n /** 그룹(타입) hidden 토글 → 하나라도 보이면 전원 숨김, 전원 숨김이면 전원 표시. */\n toggleTypeHidden(type: string): void {\n const entries = this._index?.byType.get(type)\n if (!entries || entries.length === 0) return\n const target = !entries.every(e => !!e.component.get('hidden'))\n for (const e of entries) e.component.set('hidden', target)\n this._onChange?.()\n }\n\n /** 개별 컴포넌트 hidden(영구 모델 속성) 토글 — 기존 eye 동작. */\n toggleHidden(component: Component): void {\n component.set('hidden', !component.get('hidden'))\n this._onChange?.()\n }\n\n /** 개별 컴포넌트 locked(영구 모델 속성) 토글. */\n toggleLocked(component: Component): void {\n component.set('locked', !component.get('locked'))\n this._onChange?.()\n }\n\n /** 캔버스를 해당 컴포넌트로 센터 이동. */\n centerTo(component: Component): void {\n ;(this._scene as unknown as { centerTo?: (c: Component, animated?: boolean) => void })?.centerTo?.(component, true)\n }\n\n /** 선택 — append(ctrl/cmd) 시 토글식 다중 선택. anchor 를 갱신한다. */\n select(component: Component, append = false): void {\n const scene = this._scene\n if (!scene) return\n\n const selected = scene.selected\n if (append) {\n const idx = selected.findIndex((s: Component) => s === component)\n if (idx != -1) {\n selected.splice(idx, 1)\n scene.selected = [...selected]\n } else {\n scene.selected = [...selected, component]\n }\n } else {\n scene.selected = [component]\n }\n\n this._anchor = component\n this._decorate(component)\n this._onChange?.()\n }\n\n /**\n * shift+click 범위(볼륨) 선택 — anchor↔target 사이의 **보이는 트리 행**을 블록 선택한다.\n * target 이 이미 선택돼 있으면 그 범위를 블록 **해제**(additive/subtractive). anchor 는 유지.\n */\n selectRange(target: Component, terms: SearchTerm[]): void {\n const scene = this._scene\n if (!scene) return\n\n const order = this._visibleOrder(terms)\n const ti = order.indexOf(target)\n if (ti < 0) return\n\n const ai = this._anchor ? order.indexOf(this._anchor) : -1\n if (ai < 0) {\n // anchor 없음/보이지 않음 → 단일 토글로 폴백(+ anchor 설정)\n this.select(target, true)\n return\n }\n\n const [lo, hi] = ai <= ti ? [ai, ti] : [ti, ai]\n const range = order.slice(lo, hi + 1)\n const rangeSet = new Set(range)\n\n // target 이 이미 선택 상태면 블록 해제, 아니면 블록 선택.\n if (scene.selected.indexOf(target) > -1) {\n scene.selected = scene.selected.filter((c: Component) => !rangeSet.has(c))\n range.forEach(c => c.trigger('deco-border-reset'))\n } else {\n const cur = new Set(scene.selected)\n const next = [...scene.selected]\n range.forEach(c => {\n if (!cur.has(c)) next.push(c)\n this._decorate(c)\n })\n scene.selected = next\n }\n // anchor 는 유지 — 연속 shift+click 으로 범위 확장/축소.\n this._onChange?.()\n }\n\n /** fmsim 선택 강조 — deco-border (operato 는 reaction). */\n private _decorate(component: Component): void {\n component.trigger('deco-border-reset')\n component.trigger('deco-border', { distance: 8, lineWidth: 8, color: '#f007' })\n }\n\n /**\n * 현재 트리 뷰에 보이는 컴포넌트를 렌더 순서대로 반환 — 범위 선택의 기준.\n * renderNode 와 동일 규칙(shouldBeShown = 자신/자손 매치, isExtended 자식만 전개).\n */\n private _visibleOrder(terms: SearchTerm[]): Component[] {\n const root = this._scene?.root\n if (!root) return []\n\n const shown = (c: Component): boolean =>\n matchesQuery(c, terms) ||\n (c.isContainer() && (c as Container).components.some((child: Component) => shown(child)))\n\n const out: Component[] = []\n const walk = (c: Component) => {\n if (!shown(c)) return\n out.push(c)\n if (this.isExtended(c) && c.isContainer()) {\n for (const child of (c as Container).components) walk(child)\n }\n }\n walk(root)\n return out\n }\n}\n"]}
|
|
@@ -38,9 +38,14 @@ function renderGroup(type, entries, ctx) {
|
|
|
38
38
|
<md-icon class="group-hidden" title="Show/hide this group"
|
|
39
39
|
>${ctx.isTypeHidden(type) ? 'visibility_off' : 'visibility'}</md-icon
|
|
40
40
|
>
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
${type === 'group'
|
|
42
|
+
? // 'group'(순수 컨테이너)은 dim 이 무의미 — 자식이 각자 다른 type 이라 캔버스에서 안 흐려짐.
|
|
43
|
+
// 단, 아이콘을 아예 빼면 우측 정렬된 hidden 아이콘이 dim 자리로 밀려 다른 그룹 행과
|
|
44
|
+
// 세로 정렬이 어긋난다. 같은 박스를 그대로 두되 보이지/눌리지 않게만 처리한다.
|
|
45
|
+
html `<md-icon class="group-dim placeholder" aria-hidden="true">blur_off</md-icon>`
|
|
46
|
+
: html `<md-icon class="group-dim" title="Dim this group in canvas (modeling focus)"
|
|
47
|
+
>${ctx.isTypeDimmed(type) ? 'blur_on' : 'blur_off'}</md-icon
|
|
48
|
+
>`}
|
|
44
49
|
</div>
|
|
45
50
|
${expanded ? members.map(entry => renderMember(entry, ctx.terms, ctx.selected)) : nothing}
|
|
46
51
|
</div>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inspector-group-view.js","sourceRoot":"","sources":["../../../../../src/modeller/property-sidebar/inspector/inspector-group-view.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,IAAI,EAAE,OAAO,EAAkB,MAAM,KAAK,CAAA;AAEnD,OAAO,EAA8B,YAAY,EAAc,MAAM,sBAAsB,CAAA;AAC3F,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAA;AAapD,SAAS,YAAY,CAAC,KAAiB,EAAE,KAAmB,EAAE,QAAqB;IACjF,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,CAAA;IACjC,OAAO,IAAI,CAAA;oDACuC,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,eAAe,SAAS;;+BAE7E,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,YAAY;wCAChD,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC;aACpD,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,WAAW;;eAE5C,IAAI;UACT,KAAK,CAAC,KAAK;QACX,CAAC,CAAC,IAAI,CAAA,sBAAsB,SAAS,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,SAAS;QAClE,CAAC,CAAC,IAAI,CAAA,oCAAoC;;;GAGjD,CAAA;AACH,CAAC;AAED,SAAS,WAAW,CAAC,IAAY,EAAE,OAAqB,EAAE,GAAqB;IAC7E,kCAAkC;IAClC,MAAM,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAA;IACtG,IAAI,GAAG,CAAC,KAAK,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAA,EAAE,CAAA;IAE3D,MAAM,QAAQ,GAAG,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,CAAA;IAE1C,OAAO,IAAI,CAAA;;;;0BAIa,IAAI;kBACZ,GAAG,CAAC,YAAY,CAAC,IAAI,CAAC;wBAChB,GAAG,CAAC,YAAY,CAAC,IAAI,CAAC;;sBAExB,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,WAAW;mCACtB,SAAS,CAAC,IAAI,EAAE,GAAG,CAAC,KAAK,CAAC;oCACzB,OAAO,CAAC,MAAM;;aAErC,GAAG,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,YAAY
|
|
1
|
+
{"version":3,"file":"inspector-group-view.js","sourceRoot":"","sources":["../../../../../src/modeller/property-sidebar/inspector/inspector-group-view.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * Scene Inspector 타입 그룹 뷰 렌더러.\n *\n * 계층을 무시하고 `state.type` 기준 **논리 버킷**(= \"컴포넌트 그룹\")으로 재편성해 렌더한다.\n * 그룹 헤더(타입 + 개수 + 펼침/접힘 + dim-out 토글)와 멤버 행(개별 컴포넌트)으로 구성.\n * SceneInspector shadow root 에 직접 렌더 — 멤버 행은 트리 뷰와 동일한 `.component`/eye 규약을\n * 재사용해 기존 클릭 위임(선택·hidden·lock 토글)을 그대로 쓴다. 그룹 헤더 토글은 `data-group-type` 로 처리.\n */\nimport { Component } from '@hatiolab/things-scene'\nimport { html, nothing, TemplateResult } from 'lit'\n\nimport { IndexEntry, InspectorIndex, matchesQuery, SearchTerm } from './inspector-model.js'\nimport { highlight } from './inspector-highlight.js'\n\nexport interface GroupViewContext {\n selected: Component[]\n /** 파싱된 전역 검색어(AND). */\n terms: SearchTerm[]\n isGroupExpanded: (type: string) => boolean\n /** 그룹(타입)이 모델링 포커스 dim 처리됐는지. */\n isTypeDimmed: (type: string) => boolean\n /** 그룹(타입) 전원이 hidden(숨김)인지 — 그룹 hidden 토글 아이콘 판정. */\n isTypeHidden: (type: string) => boolean\n}\n\nfunction renderMember(entry: IndexEntry, terms: SearchTerm[], selected: Component[]): TemplateResult {\n const component = entry.component\n return html`\n <div class=\"component group-member\" ?selected=${selected.indexOf(component) > -1} .component=${component}>\n <span>\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>${' '}</pre>\n ${entry.label\n ? html`<span class=\"name\">${highlight(entry.label, terms)}</span>`\n : html`<span class=\"muted\">(no id)</span>`}\n </span>\n </div>\n `\n}\n\nfunction renderGroup(type: string, entries: IndexEntry[], ctx: GroupViewContext): TemplateResult {\n // 전역 검색으로 멤버를 좁히고, 매치 없는 그룹은 숨긴다.\n const members = ctx.terms.length ? entries.filter(e => matchesQuery(e.component, ctx.terms)) : entries\n if (ctx.terms.length && members.length === 0) return html``\n\n const expanded = ctx.isGroupExpanded(type)\n\n return html`\n <div class=\"group\">\n <div\n class=\"group-header\"\n data-group-type=${type}\n ?dimmed=${ctx.isTypeDimmed(type)}\n ?hidden-group=${ctx.isTypeHidden(type)}\n >\n <span class=${expanded ? 'extended' : 'collapsed'}> </span>\n <span class=\"group-type\">${highlight(type, ctx.terms)}</span>\n <span class=\"group-count\">${members.length}</span>\n <md-icon class=\"group-hidden\" title=\"Show/hide this group\"\n >${ctx.isTypeHidden(type) ? 'visibility_off' : 'visibility'}</md-icon\n >\n ${type === 'group'\n ? // 'group'(순수 컨테이너)은 dim 이 무의미 — 자식이 각자 다른 type 이라 캔버스에서 안 흐려짐.\n // 단, 아이콘을 아예 빼면 우측 정렬된 hidden 아이콘이 dim 자리로 밀려 다른 그룹 행과\n // 세로 정렬이 어긋난다. 같은 박스를 그대로 두되 보이지/눌리지 않게만 처리한다.\n html`<md-icon class=\"group-dim placeholder\" aria-hidden=\"true\">blur_off</md-icon>`\n : html`<md-icon class=\"group-dim\" title=\"Dim this group in canvas (modeling focus)\"\n >${ctx.isTypeDimmed(type) ? 'blur_on' : 'blur_off'}</md-icon\n >`}\n </div>\n ${expanded ? members.map(entry => renderMember(entry, ctx.terms, ctx.selected)) : nothing}\n </div>\n `\n}\n\n/** 타입 그룹 뷰 전체 렌더 — 타입명 오름차순. */\nexport function renderInspectorGroupView(index: InspectorIndex | undefined, ctx: GroupViewContext): TemplateResult {\n if (!index) return html``\n const types = [...index.byType.keys()].sort((a, b) => a.localeCompare(b))\n return html`${types.map(type => renderGroup(type, index.byType.get(type)!, ctx))}`\n}\n"]}
|
|
@@ -411,17 +411,22 @@ SceneInspector.styles = [
|
|
|
411
411
|
font-size: 1em;
|
|
412
412
|
opacity: 0.35;
|
|
413
413
|
cursor: pointer;
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
margin: 0 2px 0 6px;
|
|
414
|
+
/* 간격은 .group-header 의 flex gap 하나로만 관리 — 개별 마진 중복 금지. */
|
|
415
|
+
margin: 0;
|
|
417
416
|
}
|
|
418
417
|
.group-dim {
|
|
419
|
-
margin:
|
|
418
|
+
margin-right: 2px; /* 우측 끝 살짝 여백 */
|
|
420
419
|
}
|
|
421
420
|
.group-hidden:hover,
|
|
422
421
|
.group-dim:hover {
|
|
423
422
|
opacity: 0.8;
|
|
424
423
|
}
|
|
424
|
+
/* 'group' 타입은 dim 미지원 — 자리(정렬)만 유지하고 아이콘은 숨긴다. */
|
|
425
|
+
.group-dim.placeholder {
|
|
426
|
+
visibility: hidden;
|
|
427
|
+
pointer-events: none;
|
|
428
|
+
cursor: default;
|
|
429
|
+
}
|
|
425
430
|
.group-header[hidden-group] .group-hidden,
|
|
426
431
|
.group-header[dimmed] .group-dim {
|
|
427
432
|
opacity: 0.95;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inspector.js","sourceRoot":"","sources":["../../../../../src/modeller/property-sidebar/inspector/inspector.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * SceneInspector — 속성패널 inspector 탭 호스트.\n *\n * 얇은 호스트: 검색/뷰모드 상태 + 클릭 위임만 담당하고, scene 상호작용은 InspectorController,\n * 렌더는 뷰 렌더러(트리/타입그룹)로 위임한다.\n */\nimport '@operato/input/ox-input-search.js'\n\nimport { css, html, LitElement, nothing, PropertyValues, TemplateResult } from 'lit'\nimport { property, state } from 'lit/decorators.js'\n\nimport { i18next } from '@operato/i18n'\n\nimport { Component, Scene } from '@hatiolab/things-scene'\n\nimport { parseQuery } from './inspector-model.js'\nimport { InspectorController } from './inspector-controller.js'\nimport { countMatches, renderInspectorTree } from './inspector-tree-view.js'\nimport { renderInspectorGroupView } from './inspector-group-view.js'\n\ntype InspectorViewMode = 'tree' | 'group'\n\n/** 뷰 모드 선호를 세션 간 유지(localStorage). 전역 사용자 선호. */\nconst VIEW_MODE_STORAGE_KEY = 'scene-inspector.view-mode'\n\nfunction readStoredViewMode(): InspectorViewMode {\n try {\n const v = localStorage.getItem(VIEW_MODE_STORAGE_KEY)\n return v === 'group' || v === 'tree' ? v : 'tree'\n } catch {\n return 'tree'\n }\n}\n\nfunction writeStoredViewMode(mode: InspectorViewMode): void {\n try {\n localStorage.setItem(VIEW_MODE_STORAGE_KEY, mode)\n } catch {\n /* localStorage 미가용 — 무시 */\n }\n}\n\nexport class SceneInspector extends LitElement {\n static styles = [\n css`\n :host {\n display: flex;\n flex-direction: column;\n\n color: var(--scene-inspector-color);\n }\n\n ox-input-search {\n --input-search-padding: 3px;\n --input-search-border-bottom: none;\n --input-search-focus-border-bottom: none;\n --input-search-font: normal 16px var(--theme-font);\n\n border-radius: 999px;\n box-sizing: border-box;\n border: 1px solid var(--secondary-color);\n padding: 0 10px;\n margin: 5px;\n }\n\n div[result] {\n flex: 1;\n\n display: flex;\n flex-direction: column;\n\n overflow-y: auto;\n }\n\n .component {\n display: block;\n overflow: hidden;\n border-bottom: 1px solid rgba(0, 0, 0, 0.05);\n font-size: 14px;\n }\n\n .component[selected] {\n background-color: var(--scene-inspector-selected-background-color);\n border-top: var(--scene-inspector-selected-border);\n border-bottom: var(--scene-inspector-selected-border);\n }\n [selected] .type {\n font-weight: bold;\n }\n\n span,\n i {\n display: inline-block;\n }\n\n span.type {\n text-overflow: ellipses;\n }\n\n span.name {\n background-color: var(--scene-inspector-name-background-color);\n border-radius: var(--border-radius);\n padding: 0 4px;\n color: #fff;\n font-size: 0.8em;\n }\n\n .eye {\n margin: 0 0 0 4px;\n vertical-align: middle;\n opacity: 0.7;\n font-size: 1em;\n width: 1em;\n color: var(--scene-inspector-eye-icon-color);\n cursor: pointer;\n }\n\n .lock {\n margin: 0;\n vertical-align: middle;\n opacity: 0.3;\n font-size: 1em;\n width: 1em;\n cursor: pointer;\n }\n .lock[locked] {\n opacity: 0.9;\n color: var(--md-sys-color-error, #d9534f);\n }\n\n .collapsed::before,\n .extended::before,\n .collapsespace::before {\n background: var(--url-icon-shell-inspector) no-repeat;\n width: 16px;\n height: 18px;\n display: inline-block;\n content: '';\n opacity: 0.6;\n }\n\n .collapsed::before {\n background-position: 100% -195px;\n }\n\n .extended::before {\n background-position: 100% -295px;\n }\n\n .collapsespace::before {\n background-position: 100% -395px;\n opacity: 0.9;\n width: 16px;\n }\n .collapsed,\n .extended,\n .collapsespace {\n border-left: 1px dotted rgba(0, 0, 0, 0.1);\n }\n\n pre {\n display: inline;\n /* 트리 인덴트(수직라인까지의 간격) 최소화 — depth 당 좁은 폭 */\n font-size: 0.6em;\n margin: 0;\n }\n\n /* 뷰 모드 툴바 */\n div[toolbar] {\n display: flex;\n align-items: center;\n gap: 4px;\n padding: 2px 8px 4px;\n }\n div[toolbar] .spacer {\n flex: 1;\n }\n div[toolbar] md-icon {\n cursor: pointer;\n opacity: 0.5;\n font-size: 18px;\n border-radius: 4px;\n padding: 2px;\n }\n div[toolbar] md-icon:hover {\n opacity: 0.9;\n }\n div[toolbar] md-icon[selected] {\n opacity: 1;\n color: var(--md-sys-color-primary, #4a9eff);\n background: rgba(0, 0, 0, 0.05);\n }\n\n /* 타입 그룹 뷰 */\n .group-header {\n display: flex;\n align-items: center;\n gap: 4px;\n font-size: 14px;\n font-weight: 600;\n cursor: pointer;\n padding: 2px 0;\n border-bottom: 1px solid rgba(0, 0, 0, 0.05);\n }\n .group-header:hover {\n background: rgba(0, 0, 0, 0.03);\n }\n .group-count {\n margin-left: auto;\n font-size: 0.72em;\n font-weight: normal;\n background: var(--scene-inspector-name-background-color);\n color: #fff;\n border-radius: 999px;\n padding: 0 6px;\n }\n .group-hidden,\n .group-dim {\n font-size: 1em;\n opacity: 0.35;\n cursor: pointer;\n }\n .group-hidden {\n margin: 0 2px 0 6px;\n }\n .group-dim {\n margin: 0 4px 0 2px;\n }\n .group-hidden:hover,\n .group-dim:hover {\n opacity: 0.8;\n }\n .group-header[hidden-group] .group-hidden,\n .group-header[dimmed] .group-dim {\n opacity: 0.95;\n color: var(--md-sys-color-primary, #4a9eff);\n }\n .group-header[dimmed] .group-type {\n opacity: 0.45;\n }\n .group-member {\n padding-left: 8px;\n }\n .muted {\n opacity: 0.4;\n font-style: italic;\n }\n /* 검색 매치 하이라이트 */\n mark {\n background: var(--md-sys-color-tertiary-container, #ffe082);\n color: inherit;\n border-radius: 2px;\n padding: 0 1px;\n }\n `\n ]\n\n @property({ type: Object }) scene?: Scene\n\n @state() private searchText: string = ''\n @state() private viewMode: InspectorViewMode = readStoredViewMode()\n\n private controller = new InspectorController()\n private show: boolean = false\n /** reveal(스크롤) 판정용 직전 선택. */\n private _lastSelected: Component[] = []\n\n disconnectedCallback() {\n super.disconnectedCallback()\n this.controller.unbind()\n }\n\n render() {\n return html`\n <ox-input-search\n .placeholder=${i18next.t('text.search with type, id or tag')}\n autofocus\n @change=${(e: Event) => (this.searchText = ((e.target as HTMLInputElement).value || '').toLowerCase())}\n ></ox-input-search>\n\n <div toolbar>\n <md-icon class=\"view-btn\" ?selected=${this.viewMode === 'tree'} title=\"Tree view\" @click=${() => this._setViewMode('tree')}>account_tree</md-icon>\n <md-icon class=\"view-btn\" ?selected=${this.viewMode === 'group'} title=\"Group by type\" @click=${() => this._setViewMode('group')}>category</md-icon>\n <span class=\"spacer\"></span>\n ${this.viewMode === 'group'\n ? html`\n <md-icon title=\"Expand all groups\" @click=${this._expandGroups}>unfold_more</md-icon>\n <md-icon title=\"Collapse to group level\" @click=${this._collapseGroups}>unfold_less</md-icon>\n `\n : nothing}\n </div>\n\n <div result>${this._renderResult()}</div>\n `\n }\n\n private _renderResult(): TemplateResult {\n if (!this.scene) return html``\n\n const terms = parseQuery(this.searchText)\n\n if (this.viewMode === 'group') {\n return renderInspectorGroupView(this.controller.index, {\n selected: this.controller.selected,\n terms,\n isGroupExpanded: (type: string) => this.controller.isGroupExpanded(type),\n isTypeDimmed: (type: string) => this.controller.isTypeDimmed(type),\n isTypeHidden: (type: string) => this.controller.isTypeHidden(type)\n })\n }\n\n return renderInspectorTree(this.scene.root, {\n selected: this.controller.selected,\n terms,\n count: countMatches(this.scene.root, terms),\n isExtended: (c: Component) => this.controller.isExtended(c),\n nodeHandleClass: (c: Component) => this.controller.nodeHandleClass(c)\n })\n }\n\n private _setViewMode(mode: InspectorViewMode) {\n if (this.viewMode === mode) return\n this.viewMode = mode\n writeStoredViewMode(mode) // 세션 간 유지\n }\n\n private _collapseGroups = () => {\n this.controller.collapseAllGroups()\n this.requestUpdate()\n }\n\n private _expandGroups = () => {\n const index = this.controller.index\n if (index) this.controller.expandAllGroups([...index.byType.keys()])\n this.requestUpdate()\n }\n\n firstUpdated() {\n dispatchEvent(new Event('resize'))\n\n this.renderRoot.addEventListener('click', this._onclick.bind(this) as EventListener)\n this.renderRoot.addEventListener('dblclick', this._ondblclick.bind(this) as EventListener)\n }\n\n /** 외부에서 강제 재렌더용. */\n refresh() {\n this.requestUpdate()\n }\n\n updated(change: PropertyValues<this>) {\n if (change.has('scene')) {\n this.controller.bind(this.scene, () => this.requestUpdate())\n }\n this._maybeReveal()\n }\n\n /**\n * 선택이 바뀌면(캔버스 등) 해당 항목을 리스트에 노출한다 — 그룹 뷰면 그룹을 펼친 뒤 스크롤.\n * scrollIntoView({block:'nearest'}) 라 이미 보이는 경우(인스펙터 클릭 등)엔 사실상 no-op.\n */\n private _maybeReveal() {\n const selected = this.controller.selected\n const changed =\n selected.length !== this._lastSelected.length || selected.some((c, i) => c !== this._lastSelected[i])\n if (!changed) return\n\n if (selected.length === 0) {\n this._lastSelected = selected\n return\n }\n\n if (this.viewMode === 'group') {\n const types = [...new Set(selected.map(c => c.state.type))]\n if (types.some(t => !this.controller.isGroupExpanded(t))) {\n this.controller.expandAllGroups(types)\n return // _lastSelected 미갱신 → 다음 updated 에서 changed 유지 → 그때 스크롤\n }\n }\n\n this._lastSelected = selected\n this.updateComplete.then(() => {\n const el = this.renderRoot.querySelector('.component[selected]') as HTMLElement | null\n el?.scrollIntoView({ block: 'nearest' })\n })\n }\n\n /** 이벤트 타겟에서 상위로 올라가며 `.component` 를 가진 요소를 찾는다. */\n private _resolveComponent(e: MouseEvent): Component | undefined {\n let targetElement = e.target as HTMLElement & { component: Component }\n let component: Component | undefined\n\n while (!component && targetElement) {\n component = targetElement.component\n\n if (component) break\n\n targetElement = targetElement.parentNode as HTMLElement & { component: Component }\n\n if (!targetElement || targetElement === this.renderRoot) break\n }\n\n return component\n }\n\n _onclick(e: MouseEvent) {\n e.stopPropagation()\n\n const target = e.target as HTMLElement\n\n // 그룹 hidden 아이콘 클릭 → 그룹(타입) 전원 hidden 토글 (헤더 펼침보다 먼저 처리)\n if (target.classList.contains('group-hidden')) {\n const type = target.closest?.('[data-group-type]')?.getAttribute('data-group-type')\n if (type != null) this.controller.toggleTypeHidden(type)\n this.requestUpdate()\n return\n }\n\n // 그룹 dim-out 아이콘 클릭 → 그룹(타입) 포커스 dim 토글 (헤더 펼침보다 먼저 처리)\n if (target.classList.contains('group-dim')) {\n const type = target.closest?.('[data-group-type]')?.getAttribute('data-group-type')\n if (type != null) this.controller.toggleTypeDim(type)\n this.requestUpdate()\n return\n }\n\n // 타입 그룹 헤더 클릭 → 그룹 펼침/접힘 (멤버 행은 헤더의 자손이 아니므로 여기 안 걸림)\n const header = target.closest?.('[data-group-type]') as HTMLElement | null\n if (header) {\n const type = header.getAttribute('data-group-type')\n if (type != null) this.controller.toggleGroup(type)\n this.requestUpdate()\n return\n }\n\n const classList = (e.target as HTMLElement).classList\n const component = this._resolveComponent(e)\n\n if (component) {\n if (classList.contains('eye')) {\n this.controller.toggleHidden(component)\n } else if (classList.contains('lock')) {\n this.controller.toggleLocked(component)\n } else if (classList.contains('extended') || classList.contains('collapsed')) {\n this.controller.toggleExtended(component)\n }\n\n // shift = 범위(볼륨) 선택/블록 해제, ctrl/cmd = 개별 토글, 그 외 = 단일 선택\n if (e.shiftKey) {\n this.controller.selectRange(component, parseQuery(this.searchText))\n } else {\n this.controller.select(component, e.metaKey || e.ctrlKey)\n }\n } else {\n if (classList.contains('inspector')) {\n this.show = !this.show\n this.style.height = this.show ? '100%' : ''\n }\n }\n\n this.requestUpdate()\n }\n\n _ondblclick(e: MouseEvent) {\n e.stopPropagation()\n\n const component = this._resolveComponent(e)\n\n if (component) {\n if (component.isContainer()) {\n this.controller.toggleExtended(component)\n }\n // 캔버스를 해당 컴포넌트로 센터 이동(줌).\n this.controller.centerTo(component)\n }\n\n this.requestUpdate()\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"inspector.js","sourceRoot":"","sources":["../../../../../src/modeller/property-sidebar/inspector/inspector.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * SceneInspector — 속성패널 inspector 탭 호스트.\n *\n * 얇은 호스트: 검색/뷰모드 상태 + 클릭 위임만 담당하고, scene 상호작용은 InspectorController,\n * 렌더는 뷰 렌더러(트리/타입그룹)로 위임한다.\n */\nimport '@operato/input/ox-input-search.js'\n\nimport { css, html, LitElement, nothing, PropertyValues, TemplateResult } from 'lit'\nimport { property, state } from 'lit/decorators.js'\n\nimport { i18next } from '@operato/i18n'\n\nimport { Component, Scene } from '@hatiolab/things-scene'\n\nimport { parseQuery } from './inspector-model.js'\nimport { InspectorController } from './inspector-controller.js'\nimport { countMatches, renderInspectorTree } from './inspector-tree-view.js'\nimport { renderInspectorGroupView } from './inspector-group-view.js'\n\ntype InspectorViewMode = 'tree' | 'group'\n\n/** 뷰 모드 선호를 세션 간 유지(localStorage). 전역 사용자 선호. */\nconst VIEW_MODE_STORAGE_KEY = 'scene-inspector.view-mode'\n\nfunction readStoredViewMode(): InspectorViewMode {\n try {\n const v = localStorage.getItem(VIEW_MODE_STORAGE_KEY)\n return v === 'group' || v === 'tree' ? v : 'tree'\n } catch {\n return 'tree'\n }\n}\n\nfunction writeStoredViewMode(mode: InspectorViewMode): void {\n try {\n localStorage.setItem(VIEW_MODE_STORAGE_KEY, mode)\n } catch {\n /* localStorage 미가용 — 무시 */\n }\n}\n\nexport class SceneInspector extends LitElement {\n static styles = [\n css`\n :host {\n display: flex;\n flex-direction: column;\n\n color: var(--scene-inspector-color);\n }\n\n ox-input-search {\n --input-search-padding: 3px;\n --input-search-border-bottom: none;\n --input-search-focus-border-bottom: none;\n --input-search-font: normal 16px var(--theme-font);\n\n border-radius: 999px;\n box-sizing: border-box;\n border: 1px solid var(--secondary-color);\n padding: 0 10px;\n margin: 5px;\n }\n\n div[result] {\n flex: 1;\n\n display: flex;\n flex-direction: column;\n\n overflow-y: auto;\n }\n\n .component {\n display: block;\n overflow: hidden;\n border-bottom: 1px solid rgba(0, 0, 0, 0.05);\n font-size: 14px;\n }\n\n .component[selected] {\n background-color: var(--scene-inspector-selected-background-color);\n border-top: var(--scene-inspector-selected-border);\n border-bottom: var(--scene-inspector-selected-border);\n }\n [selected] .type {\n font-weight: bold;\n }\n\n span,\n i {\n display: inline-block;\n }\n\n span.type {\n text-overflow: ellipses;\n }\n\n span.name {\n background-color: var(--scene-inspector-name-background-color);\n border-radius: var(--border-radius);\n padding: 0 4px;\n color: #fff;\n font-size: 0.8em;\n }\n\n .eye {\n margin: 0 0 0 4px;\n vertical-align: middle;\n opacity: 0.7;\n font-size: 1em;\n width: 1em;\n color: var(--scene-inspector-eye-icon-color);\n cursor: pointer;\n }\n\n .lock {\n margin: 0;\n vertical-align: middle;\n opacity: 0.3;\n font-size: 1em;\n width: 1em;\n cursor: pointer;\n }\n .lock[locked] {\n opacity: 0.9;\n color: var(--md-sys-color-error, #d9534f);\n }\n\n .collapsed::before,\n .extended::before,\n .collapsespace::before {\n background: var(--url-icon-shell-inspector) no-repeat;\n width: 16px;\n height: 18px;\n display: inline-block;\n content: '';\n opacity: 0.6;\n }\n\n .collapsed::before {\n background-position: 100% -195px;\n }\n\n .extended::before {\n background-position: 100% -295px;\n }\n\n .collapsespace::before {\n background-position: 100% -395px;\n opacity: 0.9;\n width: 16px;\n }\n .collapsed,\n .extended,\n .collapsespace {\n border-left: 1px dotted rgba(0, 0, 0, 0.1);\n }\n\n pre {\n display: inline;\n /* 트리 인덴트(수직라인까지의 간격) 최소화 — depth 당 좁은 폭 */\n font-size: 0.6em;\n margin: 0;\n }\n\n /* 뷰 모드 툴바 */\n div[toolbar] {\n display: flex;\n align-items: center;\n gap: 4px;\n padding: 2px 8px 4px;\n }\n div[toolbar] .spacer {\n flex: 1;\n }\n div[toolbar] md-icon {\n cursor: pointer;\n opacity: 0.5;\n font-size: 18px;\n border-radius: 4px;\n padding: 2px;\n }\n div[toolbar] md-icon:hover {\n opacity: 0.9;\n }\n div[toolbar] md-icon[selected] {\n opacity: 1;\n color: var(--md-sys-color-primary, #4a9eff);\n background: rgba(0, 0, 0, 0.05);\n }\n\n /* 타입 그룹 뷰 */\n .group-header {\n display: flex;\n align-items: center;\n gap: 4px;\n font-size: 14px;\n font-weight: 600;\n cursor: pointer;\n padding: 2px 0;\n border-bottom: 1px solid rgba(0, 0, 0, 0.05);\n }\n .group-header:hover {\n background: rgba(0, 0, 0, 0.03);\n }\n .group-count {\n margin-left: auto;\n font-size: 0.72em;\n font-weight: normal;\n background: var(--scene-inspector-name-background-color);\n color: #fff;\n border-radius: 999px;\n padding: 0 6px;\n }\n .group-hidden,\n .group-dim {\n font-size: 1em;\n opacity: 0.35;\n cursor: pointer;\n /* 간격은 .group-header 의 flex gap 하나로만 관리 — 개별 마진 중복 금지. */\n margin: 0;\n }\n .group-dim {\n margin-right: 2px; /* 우측 끝 살짝 여백 */\n }\n .group-hidden:hover,\n .group-dim:hover {\n opacity: 0.8;\n }\n /* 'group' 타입은 dim 미지원 — 자리(정렬)만 유지하고 아이콘은 숨긴다. */\n .group-dim.placeholder {\n visibility: hidden;\n pointer-events: none;\n cursor: default;\n }\n .group-header[hidden-group] .group-hidden,\n .group-header[dimmed] .group-dim {\n opacity: 0.95;\n color: var(--md-sys-color-primary, #4a9eff);\n }\n .group-header[dimmed] .group-type {\n opacity: 0.45;\n }\n .group-member {\n padding-left: 8px;\n }\n .muted {\n opacity: 0.4;\n font-style: italic;\n }\n /* 검색 매치 하이라이트 */\n mark {\n background: var(--md-sys-color-tertiary-container, #ffe082);\n color: inherit;\n border-radius: 2px;\n padding: 0 1px;\n }\n `\n ]\n\n @property({ type: Object }) scene?: Scene\n\n @state() private searchText: string = ''\n @state() private viewMode: InspectorViewMode = readStoredViewMode()\n\n private controller = new InspectorController()\n private show: boolean = false\n /** reveal(스크롤) 판정용 직전 선택. */\n private _lastSelected: Component[] = []\n\n disconnectedCallback() {\n super.disconnectedCallback()\n this.controller.unbind()\n }\n\n render() {\n return html`\n <ox-input-search\n .placeholder=${i18next.t('text.search with type, id or tag')}\n autofocus\n @change=${(e: Event) => (this.searchText = ((e.target as HTMLInputElement).value || '').toLowerCase())}\n ></ox-input-search>\n\n <div toolbar>\n <md-icon class=\"view-btn\" ?selected=${this.viewMode === 'tree'} title=\"Tree view\" @click=${() => this._setViewMode('tree')}>account_tree</md-icon>\n <md-icon class=\"view-btn\" ?selected=${this.viewMode === 'group'} title=\"Group by type\" @click=${() => this._setViewMode('group')}>category</md-icon>\n <span class=\"spacer\"></span>\n ${this.viewMode === 'group'\n ? html`\n <md-icon title=\"Expand all groups\" @click=${this._expandGroups}>unfold_more</md-icon>\n <md-icon title=\"Collapse to group level\" @click=${this._collapseGroups}>unfold_less</md-icon>\n `\n : nothing}\n </div>\n\n <div result>${this._renderResult()}</div>\n `\n }\n\n private _renderResult(): TemplateResult {\n if (!this.scene) return html``\n\n const terms = parseQuery(this.searchText)\n\n if (this.viewMode === 'group') {\n return renderInspectorGroupView(this.controller.index, {\n selected: this.controller.selected,\n terms,\n isGroupExpanded: (type: string) => this.controller.isGroupExpanded(type),\n isTypeDimmed: (type: string) => this.controller.isTypeDimmed(type),\n isTypeHidden: (type: string) => this.controller.isTypeHidden(type)\n })\n }\n\n return renderInspectorTree(this.scene.root, {\n selected: this.controller.selected,\n terms,\n count: countMatches(this.scene.root, terms),\n isExtended: (c: Component) => this.controller.isExtended(c),\n nodeHandleClass: (c: Component) => this.controller.nodeHandleClass(c)\n })\n }\n\n private _setViewMode(mode: InspectorViewMode) {\n if (this.viewMode === mode) return\n this.viewMode = mode\n writeStoredViewMode(mode) // 세션 간 유지\n }\n\n private _collapseGroups = () => {\n this.controller.collapseAllGroups()\n this.requestUpdate()\n }\n\n private _expandGroups = () => {\n const index = this.controller.index\n if (index) this.controller.expandAllGroups([...index.byType.keys()])\n this.requestUpdate()\n }\n\n firstUpdated() {\n dispatchEvent(new Event('resize'))\n\n this.renderRoot.addEventListener('click', this._onclick.bind(this) as EventListener)\n this.renderRoot.addEventListener('dblclick', this._ondblclick.bind(this) as EventListener)\n }\n\n /** 외부에서 강제 재렌더용. */\n refresh() {\n this.requestUpdate()\n }\n\n updated(change: PropertyValues<this>) {\n if (change.has('scene')) {\n this.controller.bind(this.scene, () => this.requestUpdate())\n }\n this._maybeReveal()\n }\n\n /**\n * 선택이 바뀌면(캔버스 등) 해당 항목을 리스트에 노출한다 — 그룹 뷰면 그룹을 펼친 뒤 스크롤.\n * scrollIntoView({block:'nearest'}) 라 이미 보이는 경우(인스펙터 클릭 등)엔 사실상 no-op.\n */\n private _maybeReveal() {\n const selected = this.controller.selected\n const changed =\n selected.length !== this._lastSelected.length || selected.some((c, i) => c !== this._lastSelected[i])\n if (!changed) return\n\n if (selected.length === 0) {\n this._lastSelected = selected\n return\n }\n\n if (this.viewMode === 'group') {\n const types = [...new Set(selected.map(c => c.state.type))]\n if (types.some(t => !this.controller.isGroupExpanded(t))) {\n this.controller.expandAllGroups(types)\n return // _lastSelected 미갱신 → 다음 updated 에서 changed 유지 → 그때 스크롤\n }\n }\n\n this._lastSelected = selected\n this.updateComplete.then(() => {\n const el = this.renderRoot.querySelector('.component[selected]') as HTMLElement | null\n el?.scrollIntoView({ block: 'nearest' })\n })\n }\n\n /** 이벤트 타겟에서 상위로 올라가며 `.component` 를 가진 요소를 찾는다. */\n private _resolveComponent(e: MouseEvent): Component | undefined {\n let targetElement = e.target as HTMLElement & { component: Component }\n let component: Component | undefined\n\n while (!component && targetElement) {\n component = targetElement.component\n\n if (component) break\n\n targetElement = targetElement.parentNode as HTMLElement & { component: Component }\n\n if (!targetElement || targetElement === this.renderRoot) break\n }\n\n return component\n }\n\n _onclick(e: MouseEvent) {\n e.stopPropagation()\n\n const target = e.target as HTMLElement\n\n // 그룹 hidden 아이콘 클릭 → 그룹(타입) 전원 hidden 토글 (헤더 펼침보다 먼저 처리)\n if (target.classList.contains('group-hidden')) {\n const type = target.closest?.('[data-group-type]')?.getAttribute('data-group-type')\n if (type != null) this.controller.toggleTypeHidden(type)\n this.requestUpdate()\n return\n }\n\n // 그룹 dim-out 아이콘 클릭 → 그룹(타입) 포커스 dim 토글 (헤더 펼침보다 먼저 처리)\n if (target.classList.contains('group-dim')) {\n const type = target.closest?.('[data-group-type]')?.getAttribute('data-group-type')\n if (type != null) this.controller.toggleTypeDim(type)\n this.requestUpdate()\n return\n }\n\n // 타입 그룹 헤더 클릭 → 그룹 펼침/접힘 (멤버 행은 헤더의 자손이 아니므로 여기 안 걸림)\n const header = target.closest?.('[data-group-type]') as HTMLElement | null\n if (header) {\n const type = header.getAttribute('data-group-type')\n if (type != null) this.controller.toggleGroup(type)\n this.requestUpdate()\n return\n }\n\n const classList = (e.target as HTMLElement).classList\n const component = this._resolveComponent(e)\n\n if (component) {\n if (classList.contains('eye')) {\n this.controller.toggleHidden(component)\n } else if (classList.contains('lock')) {\n this.controller.toggleLocked(component)\n } else if (classList.contains('extended') || classList.contains('collapsed')) {\n this.controller.toggleExtended(component)\n }\n\n // shift = 범위(볼륨) 선택/블록 해제, ctrl/cmd = 개별 토글, 그 외 = 단일 선택\n if (e.shiftKey) {\n this.controller.selectRange(component, parseQuery(this.searchText))\n } else {\n this.controller.select(component, e.metaKey || e.ctrlKey)\n }\n } else {\n if (classList.contains('inspector')) {\n this.show = !this.show\n this.style.height = this.show ? '100%' : ''\n }\n }\n\n this.requestUpdate()\n }\n\n _ondblclick(e: MouseEvent) {\n e.stopPropagation()\n\n const component = this._resolveComponent(e)\n\n if (component) {\n if (component.isContainer()) {\n this.controller.toggleExtended(component)\n }\n // 캔버스를 해당 컴포넌트로 센터 이동(줌).\n this.controller.centerTo(component)\n }\n\n this.requestUpdate()\n }\n}\n"]}
|