@fmsim/board 1.0.88 → 2.0.0-beta.101
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/custom-elements.json +7366 -3448
- package/demo/index-modeller.html +1 -1
- package/demo/index-player.html +2 -2
- package/demo/index-viewer.html +2 -2
- package/demo/index.html +2 -2
- package/dist/src/component/etc.js +36 -0
- package/dist/src/component/etc.js.map +1 -1
- package/dist/src/component/index.d.ts +1 -0
- package/dist/src/component/index.js +1 -0
- package/dist/src/component/index.js.map +1 -1
- package/dist/src/component/restful-attachment-creation-card.js +1 -1
- package/dist/src/component/restful-attachment-creation-card.js.map +1 -1
- package/dist/src/component/restful-attachment-selector.js +8 -9
- package/dist/src/component/restful-attachment-selector.js.map +1 -1
- package/dist/src/component/restful-input-attachment-selector.d.ts +1 -1
- package/dist/src/component/restful-input-attachment-selector.js +3 -3
- package/dist/src/component/restful-input-attachment-selector.js.map +1 -1
- package/dist/src/component/restful-input-fill-style.d.ts +13 -2
- package/dist/src/component/restful-input-fill-style.js.map +1 -1
- package/dist/src/component/restful-property-editor-backdrop-faces.d.ts +50 -0
- package/dist/src/component/restful-property-editor-backdrop-faces.js +524 -0
- package/dist/src/component/restful-property-editor-backdrop-faces.js.map +1 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/layers/mcs-mouse-event-handler.js +8 -0
- package/dist/src/layers/mcs-mouse-event-handler.js.map +1 -1
- package/dist/src/layers/movement-layer.d.ts +7 -6
- package/dist/src/layers/movement-layer.js +56 -11
- package/dist/src/layers/movement-layer.js.map +1 -1
- package/dist/src/modeller/bulk-create-dialog.d.ts +51 -0
- package/dist/src/modeller/bulk-create-dialog.js +531 -0
- package/dist/src/modeller/bulk-create-dialog.js.map +1 -0
- package/dist/src/modeller/bulk-create.d.ts +101 -0
- package/dist/src/modeller/bulk-create.js +199 -0
- package/dist/src/modeller/bulk-create.js.map +1 -0
- package/dist/src/modeller/component-toolbar/component-menu.d.ts +1 -1
- package/dist/src/modeller/component-toolbar/component-toolbar.d.ts +4 -4
- package/dist/src/modeller/component-toolbar/component-toolbar.js.map +1 -1
- package/dist/src/modeller/edit-toolbar-style.js +43 -3
- package/dist/src/modeller/edit-toolbar-style.js.map +1 -1
- package/dist/src/modeller/edit-toolbar.d.ts +21 -14
- package/dist/src/modeller/edit-toolbar.js +371 -213
- package/dist/src/modeller/edit-toolbar.js.map +1 -1
- package/dist/src/modeller/property-sidebar/abstract-property.js +10 -2
- package/dist/src/modeller/property-sidebar/abstract-property.js.map +1 -1
- package/dist/src/modeller/property-sidebar/data-binding/data-binding-mapper.js +1 -1
- package/dist/src/modeller/property-sidebar/data-binding/data-binding-mapper.js.map +1 -1
- package/dist/src/modeller/property-sidebar/data-binding/data-binding.d.ts +2 -2
- package/dist/src/modeller/property-sidebar/data-binding/data-binding.js +18 -18
- package/dist/src/modeller/property-sidebar/data-binding/data-binding.js.map +1 -1
- package/dist/src/modeller/property-sidebar/effects/effects.d.ts +10 -1
- package/dist/src/modeller/property-sidebar/effects/effects.js +66 -1
- package/dist/src/modeller/property-sidebar/effects/effects.js.map +1 -1
- package/dist/src/modeller/property-sidebar/effects/event-handler-adapter.d.ts +44 -0
- package/dist/src/modeller/property-sidebar/effects/event-handler-adapter.js +70 -0
- package/dist/src/modeller/property-sidebar/effects/event-handler-adapter.js.map +1 -0
- package/dist/src/modeller/property-sidebar/effects/property-event-hover.js +3 -3
- package/dist/src/modeller/property-sidebar/effects/property-event-hover.js.map +1 -1
- package/dist/src/modeller/property-sidebar/effects/property-event.d.ts +13 -3
- package/dist/src/modeller/property-sidebar/effects/property-event.js +20 -11
- package/dist/src/modeller/property-sidebar/effects/property-event.js.map +1 -1
- package/dist/src/modeller/property-sidebar/inspector/inspector-controller.d.ts +62 -0
- package/dist/src/modeller/property-sidebar/inspector/inspector-controller.js +276 -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 +15 -0
- package/dist/src/modeller/property-sidebar/inspector/inspector-group-view.js +56 -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 +281 -154
- package/dist/src/modeller/property-sidebar/inspector/inspector.js.map +1 -1
- package/dist/src/modeller/property-sidebar/property-sidebar.d.ts +2 -2
- package/dist/src/modeller/property-sidebar/property-sidebar.js +11 -24
- package/dist/src/modeller/property-sidebar/property-sidebar.js.map +1 -1
- package/dist/src/modeller/property-sidebar/shapes/ox-input-entity-id.d.ts +75 -0
- package/dist/src/modeller/property-sidebar/shapes/ox-input-entity-id.js +346 -0
- package/dist/src/modeller/property-sidebar/shapes/ox-input-entity-id.js.map +1 -0
- package/dist/src/modeller/property-sidebar/shapes/shapes.d.ts +23 -2
- package/dist/src/modeller/property-sidebar/shapes/shapes.js +96 -4
- package/dist/src/modeller/property-sidebar/shapes/shapes.js.map +1 -1
- package/dist/src/modeller/property-sidebar/specifics/specifics.d.ts +25 -1
- package/dist/src/modeller/property-sidebar/specifics/specifics.js +112 -2
- package/dist/src/modeller/property-sidebar/specifics/specifics.js.map +1 -1
- package/dist/src/modeller/property-sidebar/styles/styles.d.ts +6 -4
- package/dist/src/modeller/property-sidebar/styles/styles.js +140 -437
- package/dist/src/modeller/property-sidebar/styles/styles.js.map +1 -1
- package/dist/src/modeller/scene-viewer/ox-scene-layer.d.ts +4 -3
- package/dist/src/modeller/scene-viewer/ox-scene-layer.js +2 -1
- package/dist/src/modeller/scene-viewer/ox-scene-layer.js.map +1 -1
- package/dist/src/modeller/scene-viewer/ox-scene-viewer.d.ts +6 -5
- package/dist/src/modeller/scene-viewer/ox-scene-viewer.js +7 -3
- package/dist/src/modeller/scene-viewer/ox-scene-viewer.js.map +1 -1
- package/dist/src/ox-board-component-info.d.ts +3 -3
- package/dist/src/ox-board-component-info.js +58 -19
- package/dist/src/ox-board-component-info.js.map +1 -1
- package/dist/src/ox-board-list.js +12 -12
- package/dist/src/ox-board-list.js.map +1 -1
- package/dist/src/ox-board-modeller.d.ts +9 -6
- package/dist/src/ox-board-modeller.js +32 -33
- package/dist/src/ox-board-modeller.js.map +1 -1
- package/dist/src/ox-board-viewer.d.ts +129 -17
- package/dist/src/ox-board-viewer.js +804 -115
- package/dist/src/ox-board-viewer.js.map +1 -1
- package/dist/src/ox-editor-board-selector.d.ts +4 -1
- package/dist/src/ox-editor-board-selector.js +41 -12
- package/dist/src/ox-editor-board-selector.js.map +1 -1
- package/dist/src/ox-playback-controls.d.ts +157 -0
- package/dist/src/ox-playback-controls.js +1089 -0
- package/dist/src/ox-playback-controls.js.map +1 -0
- package/dist/src/ox-property-editor-theme.js +2 -2
- package/dist/src/ox-property-editor-theme.js.map +1 -1
- package/dist/src/playback/carrier-tracking-store.d.ts +51 -0
- package/dist/src/playback/carrier-tracking-store.js +115 -0
- package/dist/src/playback/carrier-tracking-store.js.map +1 -0
- package/dist/src/playback/playback-engine.d.ts +106 -0
- package/dist/src/playback/playback-engine.js +395 -0
- package/dist/src/playback/playback-engine.js.map +1 -0
- package/dist/src/selector/board-creation-popup.d.ts +4 -5
- package/dist/src/selector/board-creation-popup.js +16 -19
- package/dist/src/selector/board-creation-popup.js.map +1 -1
- package/dist/src/selector/board-thumbnail-card.js +8 -8
- package/dist/src/selector/board-thumbnail-card.js.map +1 -1
- package/dist/src/selector/ox-board-creation-card.d.ts +1 -1
- package/dist/src/selector/ox-board-creation-card.js +7 -7
- package/dist/src/selector/ox-board-creation-card.js.map +1 -1
- package/dist/src/selector/ox-board-selector.js +21 -10
- package/dist/src/selector/ox-board-selector.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/icons/components/text.png +0 -0
- package/icons/fab/mode-2d.svg +25 -0
- package/icons/fab/mode-3d.svg +25 -0
- package/icons/fab/playback-play.svg +49 -0
- package/icons/fab/playback-stop.svg +50 -0
- package/icons/icon-htoolbar.png +0 -0
- package/package.json +22 -21
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
export type Pattern = 'grid' | 'line-h' | 'line-v';
|
|
2
|
+
export interface Area {
|
|
3
|
+
left: number;
|
|
4
|
+
top: number;
|
|
5
|
+
width: number;
|
|
6
|
+
height: number;
|
|
7
|
+
}
|
|
8
|
+
export interface BulkCreateOptions {
|
|
9
|
+
/** 복제할 원본 컴포넌트 state. id, left, top 제외하고 모두 상속. */
|
|
10
|
+
template: Record<string, any>;
|
|
11
|
+
/** 타겟 영역 (보드 좌표). */
|
|
12
|
+
area: Area;
|
|
13
|
+
/** 분포 패턴. */
|
|
14
|
+
pattern: Pattern;
|
|
15
|
+
/**
|
|
16
|
+
* 총 개수. grid 패턴에선 rows × cols 로 분해.
|
|
17
|
+
* rows/cols 가 명시되면 그것 사용, 미명시 시 area 비율과 count 로 자동 결정.
|
|
18
|
+
*/
|
|
19
|
+
count: number;
|
|
20
|
+
/** grid pattern 용 — 행 수. 미지정 시 area 비율 + count 로 자동. */
|
|
21
|
+
rows?: number;
|
|
22
|
+
/** grid pattern 용 — 열 수. 미지정 시 area 비율 + count 로 자동. */
|
|
23
|
+
cols?: number;
|
|
24
|
+
/**
|
|
25
|
+
* ID 패턴. placeholder:
|
|
26
|
+
* `{i}` — 일련번호 (0-based + idStart)
|
|
27
|
+
* `{r}` — 행 index (grid 만, 0-based + idStart)
|
|
28
|
+
* `{c}` — 열 index (grid 만, 0-based + idStart)
|
|
29
|
+
* 예: "PORT_{i}", "P_{r}_{c}", "CELL{r}{c}"
|
|
30
|
+
*/
|
|
31
|
+
idPattern: string;
|
|
32
|
+
/** ID 시작 번호. default 1. */
|
|
33
|
+
idStart?: number;
|
|
34
|
+
/** ID 숫자 자릿수 (zero-pad). 0 = 미적용. default 0. */
|
|
35
|
+
idPadding?: number;
|
|
36
|
+
/**
|
|
37
|
+
* 컴포넌트의 크기를 사용할지 (= template.width/height 그대로) 또는 cell 에
|
|
38
|
+
* 맞춰 자동 조정할지. default 'template' (원본 크기 유지).
|
|
39
|
+
*/
|
|
40
|
+
sizeMode?: 'template' | 'fit-cell';
|
|
41
|
+
}
|
|
42
|
+
export interface GeneratedModel {
|
|
43
|
+
type?: string;
|
|
44
|
+
id: string;
|
|
45
|
+
left: number;
|
|
46
|
+
top: number;
|
|
47
|
+
width: number;
|
|
48
|
+
height: number;
|
|
49
|
+
[key: string]: any;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* 타겟 영역과 총 count 로 grid 의 (rows, cols) 자동 결정.
|
|
53
|
+
* area 의 종횡비를 가능한 한 보존.
|
|
54
|
+
*/
|
|
55
|
+
export declare function deriveGridShape(count: number, area: Area, rows?: number, cols?: number): {
|
|
56
|
+
rows: number;
|
|
57
|
+
cols: number;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* 메인 헬퍼 — 옵션을 받아 새 컴포넌트 model 배열 반환.
|
|
61
|
+
*
|
|
62
|
+
* **주의**: 반환된 model 은 `scene.add(models)` 로 추가하면 된다. ID 충돌 사전
|
|
63
|
+
* 검사는 호출 측 책임 — `findIdConflicts` 와 결합 사용.
|
|
64
|
+
*/
|
|
65
|
+
export declare function generateBulkComponents(opts: BulkCreateOptions): GeneratedModel[];
|
|
66
|
+
/**
|
|
67
|
+
* 생성될 model 의 id 중 기존 scene 의 id 와 충돌하는 것 검출.
|
|
68
|
+
* @returns 충돌하는 id 배열 (없으면 빈 배열).
|
|
69
|
+
*/
|
|
70
|
+
export declare function findIdConflicts(models: GeneratedModel[], existingIds: Iterable<string>): string[];
|
|
71
|
+
/**
|
|
72
|
+
* 템플릿의 id 문자열로부터 다음 컴포넌트들의 ID 패턴을 자동 추론.
|
|
73
|
+
*
|
|
74
|
+
* 규칙:
|
|
75
|
+
* - 끝에 숫자 있음: `"PORT_05"` → `{ pattern: "PORT_{i}", start: 6, padding: 2 }`
|
|
76
|
+
* - 끝에 숫자 없음: `"abc"` → `{ pattern: "abc_{i}", start: 1, padding: 0 }`
|
|
77
|
+
* - 빈 id: `{ pattern: "{i}", start: 1, padding: 0 }`
|
|
78
|
+
*
|
|
79
|
+
* padding 은 *leading zero* 가 있을 때만 적용:
|
|
80
|
+
* - "05" → padding 2 (= "01", "02"... 형식 유지)
|
|
81
|
+
* - "5" → padding 0 (= "1", "2"... 형식)
|
|
82
|
+
*/
|
|
83
|
+
export declare function inferIdPattern(id: string | undefined | null): {
|
|
84
|
+
pattern: string;
|
|
85
|
+
start: number;
|
|
86
|
+
padding: number;
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* 패턴이 매칭하는 기존 ID 들 중 *최대 번호 + 1* 반환 — 반복 bulk-create 시
|
|
90
|
+
* 시작 번호 자동 advance 용.
|
|
91
|
+
*
|
|
92
|
+
* 예:
|
|
93
|
+
* pattern = "PORT_{i}", existingIds = ["PORT_05", "PORT_12", "Other_1"]
|
|
94
|
+
* → 13 (= 12 + 1)
|
|
95
|
+
*
|
|
96
|
+
* 매칭 안 되면 fallbackStart 반환 (기본 inferIdPattern 의 start).
|
|
97
|
+
*
|
|
98
|
+
* NOTE: 현재 `{i}` placeholder 만 인식. `{r}` / `{c}` 는 grid 전용이므로 별도
|
|
99
|
+
* 처리 필요 (추후).
|
|
100
|
+
*/
|
|
101
|
+
export declare function nextAvailableStart(idPattern: string, existingIds: Iterable<string>, fallbackStart: number): number;
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright © HatioLab Inc. All rights reserved.
|
|
3
|
+
*
|
|
4
|
+
* Bulk-create — 단일 템플릿 컴포넌트로부터 N개의 컴포넌트 model 을 일괄 생성
|
|
5
|
+
* 하는 순수 헬퍼 모음.
|
|
6
|
+
*
|
|
7
|
+
* 설계 원칙:
|
|
8
|
+
* - 순수 함수 (UI / scene side-effect 없음)
|
|
9
|
+
* - 도메인 중립 (factory, board-ai, fmsim, operato-* 등 모든 consumer 가 동일하게 사용)
|
|
10
|
+
* - 의존성 0 (Three.js / Lit 불필요)
|
|
11
|
+
*
|
|
12
|
+
* 사용 패턴:
|
|
13
|
+
* 1. consumer 가 template (= 컴포넌트 model state) + area + 옵션 수집
|
|
14
|
+
* 2. generateBulkComponents() → 새 컴포넌트 model 배열 반환
|
|
15
|
+
* 3. scene.undoableChange(() => scene.add(models)) — 단일 undo 단위로 추가
|
|
16
|
+
*
|
|
17
|
+
* 결합 사용:
|
|
18
|
+
* - deriveGridShape: 격자 패턴의 (rows, cols) 자동 결정
|
|
19
|
+
* - inferIdPattern: 템플릿의 id 로부터 ID 패턴/시작/padding 자동 추출
|
|
20
|
+
* - nextAvailableStart: 반복 호출 시 시작번호 자동 advance (existingIds 스캔)
|
|
21
|
+
* - findIdConflicts: 생성될 ID 가 scene 의 기존 ID 와 충돌하는지 검출
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* 타겟 영역과 총 count 로 grid 의 (rows, cols) 자동 결정.
|
|
25
|
+
* area 의 종횡비를 가능한 한 보존.
|
|
26
|
+
*/
|
|
27
|
+
export function deriveGridShape(count, area, rows, cols) {
|
|
28
|
+
if (rows && cols)
|
|
29
|
+
return { rows, cols };
|
|
30
|
+
if (rows)
|
|
31
|
+
return { rows, cols: Math.ceil(count / rows) };
|
|
32
|
+
if (cols)
|
|
33
|
+
return { rows: Math.ceil(count / cols), cols };
|
|
34
|
+
// 자동: area 의 종횡비와 비슷한 grid 도출.
|
|
35
|
+
// cols / rows ≈ width / height → cols ≈ sqrt(count × width / height)
|
|
36
|
+
const aspect = area.width / Math.max(area.height, 1);
|
|
37
|
+
const c = Math.max(1, Math.round(Math.sqrt(count * aspect)));
|
|
38
|
+
const r = Math.max(1, Math.ceil(count / c));
|
|
39
|
+
return { rows: r, cols: c };
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* ID 패턴 치환 — {i}, {r}, {c} placeholder.
|
|
43
|
+
*/
|
|
44
|
+
function formatId(pattern, i, r, c, start, padding) {
|
|
45
|
+
const pad = (n) => {
|
|
46
|
+
const v = n + start;
|
|
47
|
+
return padding > 0 ? String(v).padStart(padding, '0') : String(v);
|
|
48
|
+
};
|
|
49
|
+
return pattern
|
|
50
|
+
.replace(/\{i\}/g, pad(i))
|
|
51
|
+
.replace(/\{r\}/g, pad(r))
|
|
52
|
+
.replace(/\{c\}/g, pad(c));
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* 메인 헬퍼 — 옵션을 받아 새 컴포넌트 model 배열 반환.
|
|
56
|
+
*
|
|
57
|
+
* **주의**: 반환된 model 은 `scene.add(models)` 로 추가하면 된다. ID 충돌 사전
|
|
58
|
+
* 검사는 호출 측 책임 — `findIdConflicts` 와 결합 사용.
|
|
59
|
+
*/
|
|
60
|
+
export function generateBulkComponents(opts) {
|
|
61
|
+
const { template, area, pattern, count, idPattern, idStart = 1, idPadding = 0, sizeMode = 'template' } = opts;
|
|
62
|
+
if (count <= 0)
|
|
63
|
+
return [];
|
|
64
|
+
if (!template || typeof template !== 'object')
|
|
65
|
+
return [];
|
|
66
|
+
// template 에서 제외할 필드. id/위치 는 새로 생성. (width/height 는 sizeMode 에 따라 처리)
|
|
67
|
+
const { id: _id, left: _l, top: _t, width: _w, height: _h, ...rest } = template;
|
|
68
|
+
const tplW = typeof template.width === 'number' ? template.width : 50;
|
|
69
|
+
const tplH = typeof template.height === 'number' ? template.height : 50;
|
|
70
|
+
const result = [];
|
|
71
|
+
if (pattern === 'grid') {
|
|
72
|
+
const { rows, cols } = deriveGridShape(count, area, opts.rows, opts.cols);
|
|
73
|
+
const cellW = area.width / cols;
|
|
74
|
+
const cellH = area.height / rows;
|
|
75
|
+
const itemW = sizeMode === 'fit-cell' ? Math.min(cellW, cellH) * 0.8 : tplW;
|
|
76
|
+
const itemH = sizeMode === 'fit-cell' ? Math.min(cellW, cellH) * 0.8 : tplH;
|
|
77
|
+
let i = 0;
|
|
78
|
+
outer: for (let r = 0; r < rows; r++) {
|
|
79
|
+
for (let c = 0; c < cols; c++) {
|
|
80
|
+
if (i >= count)
|
|
81
|
+
break outer;
|
|
82
|
+
const cx = area.left + cellW * (c + 0.5);
|
|
83
|
+
const cy = area.top + cellH * (r + 0.5);
|
|
84
|
+
result.push({
|
|
85
|
+
...rest,
|
|
86
|
+
id: formatId(idPattern, i, r, c, idStart, idPadding),
|
|
87
|
+
left: cx - itemW / 2,
|
|
88
|
+
top: cy - itemH / 2,
|
|
89
|
+
width: itemW,
|
|
90
|
+
height: itemH
|
|
91
|
+
});
|
|
92
|
+
i++;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
else if (pattern === 'line-h' || pattern === 'line-v') {
|
|
97
|
+
const isH = pattern === 'line-h';
|
|
98
|
+
const span = isH ? area.width : area.height;
|
|
99
|
+
const cellSize = span / count;
|
|
100
|
+
const itemW = sizeMode === 'fit-cell' && isH ? cellSize * 0.8 : tplW;
|
|
101
|
+
const itemH = sizeMode === 'fit-cell' && !isH ? cellSize * 0.8 : tplH;
|
|
102
|
+
for (let i = 0; i < count; i++) {
|
|
103
|
+
const along = (i + 0.5) * cellSize;
|
|
104
|
+
const cx = isH ? area.left + along : area.left + area.width / 2;
|
|
105
|
+
const cy = isH ? area.top + area.height / 2 : area.top + along;
|
|
106
|
+
result.push({
|
|
107
|
+
...rest,
|
|
108
|
+
id: formatId(idPattern, i, 0, i, idStart, idPadding),
|
|
109
|
+
left: cx - itemW / 2,
|
|
110
|
+
top: cy - itemH / 2,
|
|
111
|
+
width: itemW,
|
|
112
|
+
height: itemH
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return result;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* 생성될 model 의 id 중 기존 scene 의 id 와 충돌하는 것 검출.
|
|
120
|
+
* @returns 충돌하는 id 배열 (없으면 빈 배열).
|
|
121
|
+
*/
|
|
122
|
+
export function findIdConflicts(models, existingIds) {
|
|
123
|
+
const set = existingIds instanceof Set ? existingIds : new Set(existingIds);
|
|
124
|
+
const conflicts = [];
|
|
125
|
+
for (const m of models) {
|
|
126
|
+
if (set.has(m.id))
|
|
127
|
+
conflicts.push(m.id);
|
|
128
|
+
}
|
|
129
|
+
return conflicts;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* 템플릿의 id 문자열로부터 다음 컴포넌트들의 ID 패턴을 자동 추론.
|
|
133
|
+
*
|
|
134
|
+
* 규칙:
|
|
135
|
+
* - 끝에 숫자 있음: `"PORT_05"` → `{ pattern: "PORT_{i}", start: 6, padding: 2 }`
|
|
136
|
+
* - 끝에 숫자 없음: `"abc"` → `{ pattern: "abc_{i}", start: 1, padding: 0 }`
|
|
137
|
+
* - 빈 id: `{ pattern: "{i}", start: 1, padding: 0 }`
|
|
138
|
+
*
|
|
139
|
+
* padding 은 *leading zero* 가 있을 때만 적용:
|
|
140
|
+
* - "05" → padding 2 (= "01", "02"... 형식 유지)
|
|
141
|
+
* - "5" → padding 0 (= "1", "2"... 형식)
|
|
142
|
+
*/
|
|
143
|
+
export function inferIdPattern(id) {
|
|
144
|
+
if (!id || typeof id !== 'string') {
|
|
145
|
+
return { pattern: '{i}', start: 1, padding: 0 };
|
|
146
|
+
}
|
|
147
|
+
const m = id.match(/^(.*?)(\d+)$/);
|
|
148
|
+
if (m) {
|
|
149
|
+
const prefix = m[1];
|
|
150
|
+
const numStr = m[2];
|
|
151
|
+
const num = parseInt(numStr, 10);
|
|
152
|
+
const padding = numStr.length > 1 && numStr.startsWith('0') ? numStr.length : 0;
|
|
153
|
+
return {
|
|
154
|
+
pattern: `${prefix}{i}`,
|
|
155
|
+
start: num + 1,
|
|
156
|
+
padding
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
return {
|
|
160
|
+
pattern: `${id}_{i}`,
|
|
161
|
+
start: 1,
|
|
162
|
+
padding: 0
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* 패턴이 매칭하는 기존 ID 들 중 *최대 번호 + 1* 반환 — 반복 bulk-create 시
|
|
167
|
+
* 시작 번호 자동 advance 용.
|
|
168
|
+
*
|
|
169
|
+
* 예:
|
|
170
|
+
* pattern = "PORT_{i}", existingIds = ["PORT_05", "PORT_12", "Other_1"]
|
|
171
|
+
* → 13 (= 12 + 1)
|
|
172
|
+
*
|
|
173
|
+
* 매칭 안 되면 fallbackStart 반환 (기본 inferIdPattern 의 start).
|
|
174
|
+
*
|
|
175
|
+
* NOTE: 현재 `{i}` placeholder 만 인식. `{r}` / `{c}` 는 grid 전용이므로 별도
|
|
176
|
+
* 처리 필요 (추후).
|
|
177
|
+
*/
|
|
178
|
+
export function nextAvailableStart(idPattern, existingIds, fallbackStart) {
|
|
179
|
+
if (!idPattern.includes('{i}'))
|
|
180
|
+
return fallbackStart;
|
|
181
|
+
// pattern → regex. {i} → 캡쳐, {r}/{c} → 그냥 숫자, 특수문자 escape.
|
|
182
|
+
const escaped = idPattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
183
|
+
const re = new RegExp('^' +
|
|
184
|
+
escaped
|
|
185
|
+
.replace(/\\\{i\\\}/g, '(\\d+)')
|
|
186
|
+
.replace(/\\\{[rc]\\\}/g, '\\d+') +
|
|
187
|
+
'$');
|
|
188
|
+
let max = -1;
|
|
189
|
+
for (const id of existingIds) {
|
|
190
|
+
const m = id.match(re);
|
|
191
|
+
if (m) {
|
|
192
|
+
const n = parseInt(m[1], 10);
|
|
193
|
+
if (!Number.isNaN(n) && n > max)
|
|
194
|
+
max = n;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return max >= 0 ? max + 1 : fallbackStart;
|
|
198
|
+
}
|
|
199
|
+
//# sourceMappingURL=bulk-create.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bulk-create.js","sourceRoot":"","sources":["../../../src/modeller/bulk-create.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * Bulk-create — 단일 템플릿 컴포넌트로부터 N개의 컴포넌트 model 을 일괄 생성\n * 하는 순수 헬퍼 모음.\n *\n * 설계 원칙:\n * - 순수 함수 (UI / scene side-effect 없음)\n * - 도메인 중립 (factory, board-ai, fmsim, operato-* 등 모든 consumer 가 동일하게 사용)\n * - 의존성 0 (Three.js / Lit 불필요)\n *\n * 사용 패턴:\n * 1. consumer 가 template (= 컴포넌트 model state) + area + 옵션 수집\n * 2. generateBulkComponents() → 새 컴포넌트 model 배열 반환\n * 3. scene.undoableChange(() => scene.add(models)) — 단일 undo 단위로 추가\n *\n * 결합 사용:\n * - deriveGridShape: 격자 패턴의 (rows, cols) 자동 결정\n * - inferIdPattern: 템플릿의 id 로부터 ID 패턴/시작/padding 자동 추출\n * - nextAvailableStart: 반복 호출 시 시작번호 자동 advance (existingIds 스캔)\n * - findIdConflicts: 생성될 ID 가 scene 의 기존 ID 와 충돌하는지 검출\n */\n\nexport type Pattern = 'grid' | 'line-h' | 'line-v'\n\nexport interface Area {\n left: number\n top: number\n width: number\n height: number\n}\n\nexport interface BulkCreateOptions {\n /** 복제할 원본 컴포넌트 state. id, left, top 제외하고 모두 상속. */\n template: Record<string, any>\n /** 타겟 영역 (보드 좌표). */\n area: Area\n /** 분포 패턴. */\n pattern: Pattern\n /**\n * 총 개수. grid 패턴에선 rows × cols 로 분해.\n * rows/cols 가 명시되면 그것 사용, 미명시 시 area 비율과 count 로 자동 결정.\n */\n count: number\n /** grid pattern 용 — 행 수. 미지정 시 area 비율 + count 로 자동. */\n rows?: number\n /** grid pattern 용 — 열 수. 미지정 시 area 비율 + count 로 자동. */\n cols?: number\n /**\n * ID 패턴. placeholder:\n * `{i}` — 일련번호 (0-based + idStart)\n * `{r}` — 행 index (grid 만, 0-based + idStart)\n * `{c}` — 열 index (grid 만, 0-based + idStart)\n * 예: \"PORT_{i}\", \"P_{r}_{c}\", \"CELL{r}{c}\"\n */\n idPattern: string\n /** ID 시작 번호. default 1. */\n idStart?: number\n /** ID 숫자 자릿수 (zero-pad). 0 = 미적용. default 0. */\n idPadding?: number\n /**\n * 컴포넌트의 크기를 사용할지 (= template.width/height 그대로) 또는 cell 에\n * 맞춰 자동 조정할지. default 'template' (원본 크기 유지).\n */\n sizeMode?: 'template' | 'fit-cell'\n}\n\nexport interface GeneratedModel {\n type?: string\n id: string\n left: number\n top: number\n width: number\n height: number\n [key: string]: any\n}\n\n/**\n * 타겟 영역과 총 count 로 grid 의 (rows, cols) 자동 결정.\n * area 의 종횡비를 가능한 한 보존.\n */\nexport function deriveGridShape(\n count: number,\n area: Area,\n rows?: number,\n cols?: number\n): { rows: number; cols: number } {\n if (rows && cols) return { rows, cols }\n if (rows) return { rows, cols: Math.ceil(count / rows) }\n if (cols) return { rows: Math.ceil(count / cols), cols }\n\n // 자동: area 의 종횡비와 비슷한 grid 도출.\n // cols / rows ≈ width / height → cols ≈ sqrt(count × width / height)\n const aspect = area.width / Math.max(area.height, 1)\n const c = Math.max(1, Math.round(Math.sqrt(count * aspect)))\n const r = Math.max(1, Math.ceil(count / c))\n return { rows: r, cols: c }\n}\n\n/**\n * ID 패턴 치환 — {i}, {r}, {c} placeholder.\n */\nfunction formatId(\n pattern: string,\n i: number,\n r: number,\n c: number,\n start: number,\n padding: number\n): string {\n const pad = (n: number) => {\n const v = n + start\n return padding > 0 ? String(v).padStart(padding, '0') : String(v)\n }\n return pattern\n .replace(/\\{i\\}/g, pad(i))\n .replace(/\\{r\\}/g, pad(r))\n .replace(/\\{c\\}/g, pad(c))\n}\n\n/**\n * 메인 헬퍼 — 옵션을 받아 새 컴포넌트 model 배열 반환.\n *\n * **주의**: 반환된 model 은 `scene.add(models)` 로 추가하면 된다. ID 충돌 사전\n * 검사는 호출 측 책임 — `findIdConflicts` 와 결합 사용.\n */\nexport function generateBulkComponents(opts: BulkCreateOptions): GeneratedModel[] {\n const {\n template,\n area,\n pattern,\n count,\n idPattern,\n idStart = 1,\n idPadding = 0,\n sizeMode = 'template'\n } = opts\n\n if (count <= 0) return []\n if (!template || typeof template !== 'object') return []\n\n // template 에서 제외할 필드. id/위치 는 새로 생성. (width/height 는 sizeMode 에 따라 처리)\n const { id: _id, left: _l, top: _t, width: _w, height: _h, ...rest } = template\n\n const tplW = typeof template.width === 'number' ? template.width : 50\n const tplH = typeof template.height === 'number' ? template.height : 50\n\n const result: GeneratedModel[] = []\n\n if (pattern === 'grid') {\n const { rows, cols } = deriveGridShape(count, area, opts.rows, opts.cols)\n const cellW = area.width / cols\n const cellH = area.height / rows\n const itemW = sizeMode === 'fit-cell' ? Math.min(cellW, cellH) * 0.8 : tplW\n const itemH = sizeMode === 'fit-cell' ? Math.min(cellW, cellH) * 0.8 : tplH\n\n let i = 0\n outer: for (let r = 0; r < rows; r++) {\n for (let c = 0; c < cols; c++) {\n if (i >= count) break outer\n const cx = area.left + cellW * (c + 0.5)\n const cy = area.top + cellH * (r + 0.5)\n result.push({\n ...rest,\n id: formatId(idPattern, i, r, c, idStart, idPadding),\n left: cx - itemW / 2,\n top: cy - itemH / 2,\n width: itemW,\n height: itemH\n })\n i++\n }\n }\n } else if (pattern === 'line-h' || pattern === 'line-v') {\n const isH = pattern === 'line-h'\n const span = isH ? area.width : area.height\n const cellSize = span / count\n const itemW = sizeMode === 'fit-cell' && isH ? cellSize * 0.8 : tplW\n const itemH = sizeMode === 'fit-cell' && !isH ? cellSize * 0.8 : tplH\n\n for (let i = 0; i < count; i++) {\n const along = (i + 0.5) * cellSize\n const cx = isH ? area.left + along : area.left + area.width / 2\n const cy = isH ? area.top + area.height / 2 : area.top + along\n result.push({\n ...rest,\n id: formatId(idPattern, i, 0, i, idStart, idPadding),\n left: cx - itemW / 2,\n top: cy - itemH / 2,\n width: itemW,\n height: itemH\n })\n }\n }\n\n return result\n}\n\n/**\n * 생성될 model 의 id 중 기존 scene 의 id 와 충돌하는 것 검출.\n * @returns 충돌하는 id 배열 (없으면 빈 배열).\n */\nexport function findIdConflicts(\n models: GeneratedModel[],\n existingIds: Iterable<string>\n): string[] {\n const set = existingIds instanceof Set ? existingIds : new Set(existingIds)\n const conflicts: string[] = []\n for (const m of models) {\n if (set.has(m.id)) conflicts.push(m.id)\n }\n return conflicts\n}\n\n/**\n * 템플릿의 id 문자열로부터 다음 컴포넌트들의 ID 패턴을 자동 추론.\n *\n * 규칙:\n * - 끝에 숫자 있음: `\"PORT_05\"` → `{ pattern: \"PORT_{i}\", start: 6, padding: 2 }`\n * - 끝에 숫자 없음: `\"abc\"` → `{ pattern: \"abc_{i}\", start: 1, padding: 0 }`\n * - 빈 id: `{ pattern: \"{i}\", start: 1, padding: 0 }`\n *\n * padding 은 *leading zero* 가 있을 때만 적용:\n * - \"05\" → padding 2 (= \"01\", \"02\"... 형식 유지)\n * - \"5\" → padding 0 (= \"1\", \"2\"... 형식)\n */\nexport function inferIdPattern(id: string | undefined | null): {\n pattern: string\n start: number\n padding: number\n} {\n if (!id || typeof id !== 'string') {\n return { pattern: '{i}', start: 1, padding: 0 }\n }\n const m = id.match(/^(.*?)(\\d+)$/)\n if (m) {\n const prefix = m[1]\n const numStr = m[2]\n const num = parseInt(numStr, 10)\n const padding = numStr.length > 1 && numStr.startsWith('0') ? numStr.length : 0\n return {\n pattern: `${prefix}{i}`,\n start: num + 1,\n padding\n }\n }\n return {\n pattern: `${id}_{i}`,\n start: 1,\n padding: 0\n }\n}\n\n/**\n * 패턴이 매칭하는 기존 ID 들 중 *최대 번호 + 1* 반환 — 반복 bulk-create 시\n * 시작 번호 자동 advance 용.\n *\n * 예:\n * pattern = \"PORT_{i}\", existingIds = [\"PORT_05\", \"PORT_12\", \"Other_1\"]\n * → 13 (= 12 + 1)\n *\n * 매칭 안 되면 fallbackStart 반환 (기본 inferIdPattern 의 start).\n *\n * NOTE: 현재 `{i}` placeholder 만 인식. `{r}` / `{c}` 는 grid 전용이므로 별도\n * 처리 필요 (추후).\n */\nexport function nextAvailableStart(\n idPattern: string,\n existingIds: Iterable<string>,\n fallbackStart: number\n): number {\n if (!idPattern.includes('{i}')) return fallbackStart\n // pattern → regex. {i} → 캡쳐, {r}/{c} → 그냥 숫자, 특수문자 escape.\n const escaped = idPattern.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n const re = new RegExp(\n '^' +\n escaped\n .replace(/\\\\\\{i\\\\\\}/g, '(\\\\d+)')\n .replace(/\\\\\\{[rc]\\\\\\}/g, '\\\\d+') +\n '$'\n )\n let max = -1\n for (const id of existingIds) {\n const m = id.match(re)\n if (m) {\n const n = parseInt(m[1], 10)\n if (!Number.isNaN(n) && n > max) max = n\n }\n }\n return max >= 0 ? max + 1 : fallbackStart\n}\n"]}
|
|
@@ -5,7 +5,7 @@ import { LitElement, PropertyValues } from 'lit';
|
|
|
5
5
|
import { Scene } from '@hatiolab/things-scene';
|
|
6
6
|
import { Pallet, PalletItem } from '../../types';
|
|
7
7
|
import { ComponentDetail } from './component-detail';
|
|
8
|
-
declare const ComponentMenu_base: typeof LitElement & import("@open-wc/dedupe-mixin").Constructor<import("@open-wc/scoped-elements/types
|
|
8
|
+
declare const ComponentMenu_base: typeof LitElement & import("@open-wc/dedupe-mixin").Constructor<import("@open-wc/scoped-elements/types.js").ScopedElementsHost>;
|
|
9
9
|
export declare class ComponentMenu extends ComponentMenu_base {
|
|
10
10
|
static styles: import("lit").CSSResult[];
|
|
11
11
|
groups: Pallet[];
|
|
@@ -2,16 +2,16 @@
|
|
|
2
2
|
* @license Copyright © HatioLab Inc. All rights reserved.
|
|
3
3
|
*/
|
|
4
4
|
import { LitElement, PropertyValues } from 'lit';
|
|
5
|
-
import { Scene,
|
|
5
|
+
import { Scene, SceneMode } from '@hatiolab/things-scene';
|
|
6
6
|
import { ComponentGroup } from '../../types';
|
|
7
7
|
import { ComponentMenu } from './component-menu.js';
|
|
8
|
-
declare const ComponentToolbar_base: typeof LitElement & import("@open-wc/dedupe-mixin").Constructor<import("@open-wc/scoped-elements/types
|
|
8
|
+
declare const ComponentToolbar_base: typeof LitElement & import("@open-wc/dedupe-mixin").Constructor<import("@open-wc/scoped-elements/types.js").ScopedElementsHost>;
|
|
9
9
|
export declare class ComponentToolbar extends ComponentToolbar_base {
|
|
10
10
|
static styles: import("lit").CSSResult[];
|
|
11
11
|
componentGroupList: ComponentGroup[];
|
|
12
12
|
group?: string | null;
|
|
13
13
|
scene: Scene;
|
|
14
|
-
mode:
|
|
14
|
+
mode: SceneMode;
|
|
15
15
|
shift: HTMLImageElement;
|
|
16
16
|
private menu;
|
|
17
17
|
private componentsContainer;
|
|
@@ -23,7 +23,7 @@ export declare class ComponentToolbar extends ComponentToolbar_base {
|
|
|
23
23
|
};
|
|
24
24
|
render(): import("lit-html").TemplateResult<1>;
|
|
25
25
|
onWheelEvent(e: Event): void;
|
|
26
|
-
_setMode(mode:
|
|
26
|
+
_setMode(mode: SceneMode): void;
|
|
27
27
|
_onClickShift(e: MouseEvent): void;
|
|
28
28
|
_onClickGroup(e: MouseEvent): Promise<void>;
|
|
29
29
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"component-toolbar.js","sourceRoot":"","sources":["../../../../src/modeller/component-toolbar/component-toolbar.ts"],"names":[],"mappings":"AAAA;;GAEG;;AAEH,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,EAAkB,MAAM,KAAK,CAAA;AAC3D,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAE7D,OAAO,EAAS,UAAU,
|
|
1
|
+
{"version":3,"file":"component-toolbar.js","sourceRoot":"","sources":["../../../../src/modeller/component-toolbar/component-toolbar.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @license Copyright © HatioLab Inc. All rights reserved.\n */\n\nimport { css, html, LitElement, PropertyValues } from 'lit'\nimport { property, query, queryAll } from 'lit/decorators.js'\n\nimport { Scene, SCENE_MODE, SceneMode } from '@hatiolab/things-scene'\nimport { ScopedElementsMixin } from '@open-wc/scoped-elements'\n\nimport { ComponentGroup } from '../../types'\nimport { ComponentMenu } from './component-menu.js'\nimport { ICON_EDIT_MODE, ICON_SHIFT_MODE } from './mode-icons'\n\nexport class ComponentToolbar extends ScopedElementsMixin(LitElement) {\n static styles = [\n css`\n :host {\n left: 0;\n display: block;\n position: relative;\n\n width: var(--component-toolbar-icon-size);\n background-color: var(--component-toolbar-background-color);\n }\n\n [components] {\n display: block;\n padding: 0;\n margin: 0;\n\n width: 100%;\n height: 100%;\n overflow: hidden;\n }\n\n [components] > img {\n display: flex;\n flex-direction: row;\n\n width: var(--component-toolbar-icon-size);\n height: var(--component-toolbar-icon-size);\n min-width: 50%;\n\n border-bottom: var(--component-toolbar-border);\n margin: 0;\n padding: 6px;\n box-sizing: border-box;\n }\n\n #shift[active] {\n background-color: #beb9b3;\n }\n\n component-menu {\n position: absolute;\n top: 0;\n left: var(--component-toolbar-icon-size);\n height: 100%;\n outline: none;\n }\n\n component-menu[hidden] {\n display: none;\n }\n `\n ]\n\n @property({ type: Array }) componentGroupList: ComponentGroup[] = []\n @property({ type: String }) group?: string | null\n @property({ type: Object }) scene!: Scene\n @property({ type: Number }) mode: SceneMode = SCENE_MODE.EDIT\n\n @query('#shift') shift!: HTMLImageElement\n @query('component-menu') private menu!: HTMLElement\n @query('div[components]') private componentsContainer!: HTMLElement\n @queryAll('[data-group]') private groups!: NodeListOf<HTMLElement>\n\n private icons: { [name: string]: string } = {}\n\n updated(changes: PropertyValues<this>) {\n if (changes.has('componentGroupList')) {\n const color = getComputedStyle(this, null).getPropertyValue('--primary-color')\n\n this.icons = (this.componentGroupList || [])\n .filter(group => group.templates?.length > 0)\n .reduce((sum, group: ComponentGroup) => {\n sum[group.name] =\n 'data:image/svg+xml;charset=UTF-8;base64,' + btoa(group.icon.replace(/{{strokeColor}}/g, color))\n return sum\n }, {} as { [name: string]: string })\n\n this.icons['mode-edit'] =\n 'data:image/svg+xml;charset=UTF-8;base64,' + btoa(ICON_EDIT_MODE.replace(/{{strokeColor}}/g, color))\n this.icons['mode-shift'] =\n 'data:image/svg+xml;charset=UTF-8;base64,' + btoa(ICON_SHIFT_MODE.replace(/{{strokeColor}}/g, color))\n }\n\n if (changes.has('group')) {\n this.groups.forEach(group => {\n group.getAttribute('data-group') === this.group\n ? group.setAttribute('active', '')\n : group.removeAttribute('active')\n })\n\n this.group ? this.menu.removeAttribute('hidden') : this.menu.setAttribute('hidden', '')\n }\n }\n\n static get scopedElements() {\n return {\n 'component-menu': ComponentMenu\n }\n }\n\n render() {\n const componentList = this.componentGroupList || []\n const modeIcon = this.icons[this.mode === SCENE_MODE.SHIFT ? 'mode-shift' : 'mode-edit']\n\n return html`\n <div components @mousewheel=${(e: MouseEvent) => this.onWheelEvent(e)}>\n <img id=\"shift\" .src=${modeIcon} @click=${(e: MouseEvent) => this._onClickShift(e)} />\n\n ${componentList\n .filter(group => group.templates?.length > 0)\n .map(\n group => html`\n <img\n data-group=${group.name}\n @click=${(e: MouseEvent) => this._onClickGroup(e)}\n .src=${this.icons[group.name]}\n />\n `\n )}\n </div>\n\n <component-menu\n tabindex=\"-1\"\n @focusout=${() => {\n this.group = null\n }}\n .scene=${this.scene}\n .group=${this.group}\n .groups=${componentList}\n hidden\n >\n </component-menu>\n `\n }\n\n onWheelEvent(e: Event) {\n var delta = Math.max(-1, Math.min(1, (e as WheelEvent).deltaY || -(e as WheelEvent).detail))\n this.componentsContainer.scrollTop += delta * 10\n\n e.preventDefault()\n }\n\n _setMode(mode: SceneMode) {\n this.mode = mode\n\n this.dispatchEvent(\n new CustomEvent('mode-changed', {\n bubbles: true,\n composed: true,\n detail: { value: mode }\n })\n )\n }\n\n _onClickShift(e: MouseEvent) {\n const shift = this.shift\n\n if (shift.hasAttribute('active')) {\n shift.removeAttribute('active')\n this._setMode(SCENE_MODE.EDIT)\n } else {\n shift.setAttribute('active', '')\n this._setMode(SCENE_MODE.SHIFT)\n }\n }\n\n async _onClickGroup(e: MouseEvent) {\n var button = e.target as HTMLElement\n\n this.group = button!.closest('[data-group]')?.getAttribute('data-group')\n\n if (!this.group) return\n\n await this.updateComplete\n\n this.menu?.focus()\n }\n}\n"]}
|
|
@@ -4,8 +4,7 @@
|
|
|
4
4
|
import { css } from 'lit';
|
|
5
5
|
export const style = css `
|
|
6
6
|
:host {
|
|
7
|
-
color:
|
|
8
|
-
background-color: var(--edit-toolbar-background-color, #394e64);
|
|
7
|
+
background-color: var(--edit-toolbar-background-color, var(--md-sys-color-secondary, #394e64));
|
|
9
8
|
|
|
10
9
|
overflow-x: hidden;
|
|
11
10
|
}
|
|
@@ -13,7 +12,7 @@ export const style = css `
|
|
|
13
12
|
[tools] {
|
|
14
13
|
display: flex;
|
|
15
14
|
align-items: center;
|
|
16
|
-
overflow:
|
|
15
|
+
overflow: hidden;
|
|
17
16
|
padding: 0px 10px;
|
|
18
17
|
}
|
|
19
18
|
|
|
@@ -170,6 +169,10 @@ export const style = css `
|
|
|
170
169
|
background-position-y: -1142px;
|
|
171
170
|
}
|
|
172
171
|
|
|
172
|
+
#databind-copy {
|
|
173
|
+
background-position-y: -1692px;
|
|
174
|
+
}
|
|
175
|
+
|
|
173
176
|
#context-menu {
|
|
174
177
|
background-position-y: -692px;
|
|
175
178
|
}
|
|
@@ -198,6 +201,43 @@ export const style = css `
|
|
|
198
201
|
background-position-y: -1593px;
|
|
199
202
|
}
|
|
200
203
|
|
|
204
|
+
#align-z-front {
|
|
205
|
+
background-position-y: -142px;
|
|
206
|
+
filter: hue-rotate(180deg);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
#align-z-middle {
|
|
210
|
+
background-position-y: -192px;
|
|
211
|
+
filter: hue-rotate(180deg);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
#align-z-back {
|
|
215
|
+
background-position-y: -242px;
|
|
216
|
+
filter: hue-rotate(180deg);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
#distribute-z {
|
|
220
|
+
background-position-y: -1593px;
|
|
221
|
+
filter: hue-rotate(180deg);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.gizmo-btn {
|
|
225
|
+
background: none !important;
|
|
226
|
+
color: rgba(255, 255, 255, 0.5);
|
|
227
|
+
min-width: 24px;
|
|
228
|
+
display: inline-flex;
|
|
229
|
+
align-items: center;
|
|
230
|
+
justify-content: center;
|
|
231
|
+
cursor: pointer;
|
|
232
|
+
border-radius: 3px;
|
|
233
|
+
padding: 2px;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.gizmo-btn[disabled] {
|
|
237
|
+
opacity: 0.3;
|
|
238
|
+
pointer-events: none;
|
|
239
|
+
}
|
|
240
|
+
|
|
201
241
|
#toggle-property {
|
|
202
242
|
background-position-y: -1392px;
|
|
203
243
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"edit-toolbar-style.js","sourceRoot":"","sources":["../../../src/modeller/edit-toolbar-style.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAA;AAEzB,MAAM,CAAC,MAAM,KAAK,GAAG,GAAG,CAAA
|
|
1
|
+
{"version":3,"file":"edit-toolbar-style.js","sourceRoot":"","sources":["../../../src/modeller/edit-toolbar-style.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAA;AAEzB,MAAM,CAAC,MAAM,KAAK,GAAG,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0QvB,CAAA","sourcesContent":["/**\n * @license Copyright © HatioLab Inc. All rights reserved.\n */\n\nimport { css } from 'lit'\n\nexport const style = css`\n :host {\n background-color: var(--edit-toolbar-background-color, var(--md-sys-color-secondary, #394e64));\n\n overflow-x: hidden;\n }\n\n [tools] {\n display: flex;\n align-items: center;\n overflow: hidden;\n padding: 0px 10px;\n }\n\n [tools] > * {\n padding: 0px;\n }\n\n [tools] > span[button] {\n min-width: 30px;\n }\n\n [tools] > span[padding] {\n flex: 1;\n }\n\n [tools] > .vline {\n display: block;\n flex: none;\n border-left: 1px solid rgba(255, 255, 255, 0.2);\n border-right: 1px solid rgba(0, 0, 0, 0.15);\n width: 0px;\n height: 18px;\n margin: 0 3px;\n }\n\n span[button] {\n min-height: 35px;\n\n background: var(--url-icon-htoolbar) no-repeat;\n background-position-x: 50%;\n opacity: 0.8;\n }\n span[button]:hover {\n opacity: 1;\n background-color: rgba(0, 0, 0, 0.1);\n cursor: pointer;\n }\n\n #fullscreen,\n #toggle-property {\n flex: none;\n }\n\n #align-left {\n background-position-y: 8px;\n }\n\n #align-center {\n background-position-y: -42px;\n }\n\n #align-right {\n background-position-y: -92px;\n }\n\n #align-top {\n background-position-y: -142px;\n }\n\n #align-middle {\n background-position-y: -192px;\n }\n\n #align-bottom {\n background-position-y: -242px;\n }\n\n #undo {\n background-position-y: -592px;\n }\n\n #redo {\n background-position-y: -642px;\n }\n\n #front {\n background-position-y: -292px;\n }\n\n #back {\n background-position-y: -342px;\n }\n\n #forward {\n background-position-y: -392px;\n }\n\n #backward {\n background-position-y: -442px;\n }\n\n #symmetry-x {\n background-position-y: -492px;\n }\n\n #symmetry-y {\n background-position-y: -542px;\n }\n\n #group {\n background-position-y: -492px;\n }\n\n #ungroup {\n background-position-y: -542px;\n }\n\n #fullscreen {\n background-position-y: -692px;\n }\n\n #toggle-property {\n background-position-y: -692px;\n float: right;\n }\n\n #zoomin {\n background-position-y: -742px;\n }\n\n #zoomout {\n background-position-y: -792px;\n }\n\n #fit-scene {\n background-position-y: -1492px;\n }\n\n #cut {\n background-position-y: -842px;\n }\n\n #copy {\n background-position-y: -892px;\n }\n\n #paste {\n background-position-y: -942px;\n }\n\n #delete {\n background-position-y: -992px;\n }\n\n #font-increase {\n background-position-y: -1042px;\n }\n\n #font-decrease {\n background-position-y: -1092px;\n }\n\n #style-copy {\n background-position-y: -1142px;\n }\n\n #databind-copy {\n background-position-y: -1692px;\n }\n\n #context-menu {\n background-position-y: -692px;\n }\n\n #symmetry-x {\n background-position-y: -1192px;\n }\n\n #symmetry-y {\n background-position-y: -1242px;\n }\n\n #rotate-cw {\n background-position-y: -1292px;\n }\n\n #rotate-ccw {\n background-position-y: -1342px;\n }\n\n #distribute-horizontal {\n background-position-y: -1542px;\n }\n\n #distribute-vertical {\n background-position-y: -1593px;\n }\n\n #align-z-front {\n background-position-y: -142px;\n filter: hue-rotate(180deg);\n }\n\n #align-z-middle {\n background-position-y: -192px;\n filter: hue-rotate(180deg);\n }\n\n #align-z-back {\n background-position-y: -242px;\n filter: hue-rotate(180deg);\n }\n\n #distribute-z {\n background-position-y: -1593px;\n filter: hue-rotate(180deg);\n }\n\n .gizmo-btn {\n background: none !important;\n color: rgba(255, 255, 255, 0.5);\n min-width: 24px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n border-radius: 3px;\n padding: 2px;\n }\n\n .gizmo-btn[disabled] {\n opacity: 0.3;\n pointer-events: none;\n }\n\n #toggle-property {\n background-position-y: -1392px;\n }\n\n #preview {\n background-position-y: -1640px;\n }\n\n /* bigger buttons */\n #fullscreen {\n background: var(--url-icon-fullscreen) 50% 10px no-repeat;\n width: var(--edit-toolbar-bigger-icon-size);\n height: var(--edit-toolbar-bigger-icon-size);\n border-left: var(--edit-toolbar-bigger-icon-line);\n }\n\n #toggle-property {\n background: var(--url-icon-collapse) 80% 10px no-repeat;\n width: var(--edit-toolbar-bigger-icon-size);\n height: var(--edit-toolbar-bigger-icon-size);\n border-left: var(--edit-toolbar-bigger-icon-line);\n }\n\n #toggle-property[active] {\n background: var(--url-icon-collapse-active) 80% 10px no-repeat;\n }\n\n #generate-model {\n background-position-y: -1492px;\n }\n`\n"]}
|
|
@@ -3,26 +3,15 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { LitElement, PropertyValues } from 'lit';
|
|
5
5
|
import { Component, Scene } from '@hatiolab/things-scene';
|
|
6
|
+
import './bulk-create-dialog.js';
|
|
6
7
|
export declare class EditToolbar extends LitElement {
|
|
7
8
|
static styles: import("lit").CSSResult[];
|
|
8
9
|
scene?: Scene;
|
|
9
10
|
selected: any[];
|
|
10
11
|
hideProperty: boolean;
|
|
12
|
+
private _dimension;
|
|
13
|
+
private _gizmoAttached;
|
|
11
14
|
private cliped?;
|
|
12
|
-
private redo;
|
|
13
|
-
private undo;
|
|
14
|
-
private fullscreen;
|
|
15
|
-
private cut;
|
|
16
|
-
private copy;
|
|
17
|
-
private paste;
|
|
18
|
-
private delete;
|
|
19
|
-
private forward;
|
|
20
|
-
private backward;
|
|
21
|
-
private front;
|
|
22
|
-
private back;
|
|
23
|
-
private aligners;
|
|
24
|
-
private zorders;
|
|
25
|
-
private distributes;
|
|
26
15
|
firstUpdated(): void;
|
|
27
16
|
updated(changes: PropertyValues<this>): void;
|
|
28
17
|
render(): import("lit-html").TemplateResult<1>;
|
|
@@ -31,17 +20,35 @@ export declare class EditToolbar extends LitElement {
|
|
|
31
20
|
getSymbol(key: string): string;
|
|
32
21
|
private getShortcutString;
|
|
33
22
|
onShortcut(e: KeyboardEvent): boolean;
|
|
23
|
+
private _setDisabled;
|
|
34
24
|
onExecute(command: string, undoable: boolean, redoable: boolean): void;
|
|
35
25
|
onUndo(undoable: boolean, redoable: boolean): void;
|
|
36
26
|
onRedo(undoable: boolean, redoable: boolean): void;
|
|
37
27
|
onSceneChanged(after?: Scene, before?: Scene): void;
|
|
28
|
+
private _onDimensionChanged;
|
|
29
|
+
private _onGizmoAttachChanged;
|
|
30
|
+
private _setGizmoMode;
|
|
38
31
|
onSelectedChanged(after: Component[], before: Component[]): void;
|
|
39
32
|
onTapUndo(): void;
|
|
40
33
|
onTapRedo(): void;
|
|
34
|
+
onStartStylePasteMode(): void;
|
|
35
|
+
onStartDatabindPasteMode(): void;
|
|
41
36
|
onTapCut(): void;
|
|
42
37
|
onTapCopy(): Promise<void>;
|
|
43
38
|
onTapPaste(): void;
|
|
44
39
|
onTapDelete(): void;
|
|
40
|
+
/**
|
|
41
|
+
* 멀티 컴포넌트 일괄 붙여넣기 — Ctrl+Shift+V 진입점.
|
|
42
|
+
*
|
|
43
|
+
* 흐름:
|
|
44
|
+
* 1. 클립보드 (this.cliped) 검증 — *단일* 컴포넌트만 허용
|
|
45
|
+
* 2. scene.startBulkCreateMode → AddLayer 가 캔버스 위에 드래그 박스 그림
|
|
46
|
+
* 3. 사용자가 영역 드래그 → Promise resolve({area, template})
|
|
47
|
+
* ESC/취소 → Promise resolve(null)
|
|
48
|
+
* 4. 영역이 결정되면 dialog 오픈
|
|
49
|
+
*/
|
|
50
|
+
onBulkPaste(): Promise<void>;
|
|
51
|
+
private _openBulkCreateDialog;
|
|
45
52
|
onTapSelectAll(): void;
|
|
46
53
|
onTapFontIncrease(e: TouchEvent): void;
|
|
47
54
|
onTapFontDecrease(e: TouchEvent): void;
|