@operato/board 10.0.0-beta.5 → 10.0.0-beta.51
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/CHANGELOG.md +422 -0
- package/dist/src/component/container.js +18 -3
- package/dist/src/component/container.js.map +1 -1
- package/dist/src/component/conveyance.d.ts +2 -0
- package/dist/src/component/conveyance.js +38 -0
- package/dist/src/component/conveyance.js.map +1 -0
- package/dist/src/component/etc.js +2 -10
- package/dist/src/component/etc.js.map +1 -1
- package/dist/src/component/facility.d.ts +2 -0
- package/dist/src/component/facility.js +35 -0
- package/dist/src/component/facility.js.map +1 -0
- package/dist/src/component/index.d.ts +5 -0
- package/dist/src/component/index.js +5 -0
- package/dist/src/component/index.js.map +1 -1
- package/dist/src/component/line.js +4 -28
- package/dist/src/component/line.js.map +1 -1
- package/dist/src/component/manufacturing.d.ts +2 -0
- package/dist/src/component/manufacturing.js +41 -0
- package/dist/src/component/manufacturing.js.map +1 -0
- package/dist/src/component/register-default-groups.js +19 -2
- package/dist/src/component/register-default-groups.js.map +1 -1
- package/dist/src/component/shape.js +5 -29
- package/dist/src/component/shape.js.map +1 -1
- package/dist/src/component/storage.d.ts +2 -0
- package/dist/src/component/storage.js +27 -0
- package/dist/src/component/storage.js.map +1 -0
- package/dist/src/component/text-and-media.js +2 -25
- package/dist/src/component/text-and-media.js.map +1 -1
- package/dist/src/component/transport.d.ts +2 -0
- package/dist/src/component/transport.js +36 -0
- package/dist/src/component/transport.js.map +1 -0
- package/dist/src/component/warehouse.d.ts +1 -0
- package/dist/src/component/warehouse.js +8 -1
- package/dist/src/component/warehouse.js.map +1 -1
- package/dist/src/data-storage/board-model-cache.d.ts +30 -0
- package/dist/src/data-storage/board-model-cache.js +93 -0
- package/dist/src/data-storage/board-model-cache.js.map +1 -0
- package/dist/src/graphql/playback-buffer.d.ts +79 -0
- package/dist/src/graphql/playback-buffer.js +139 -0
- package/dist/src/graphql/playback-buffer.js.map +1 -0
- package/dist/src/graphql/playback-buffer.test.d.ts +1 -0
- package/dist/src/graphql/playback-buffer.test.js +261 -0
- package/dist/src/graphql/playback-buffer.test.js.map +1 -0
- package/dist/src/graphql/playback-subscription.d.ts +89 -0
- package/dist/src/graphql/playback-subscription.js +258 -0
- package/dist/src/graphql/playback-subscription.js.map +1 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.js +2 -0
- package/dist/src/index.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.js +8 -1
- package/dist/src/modeller/component-toolbar/component-menu.js.map +1 -1
- package/dist/src/modeller/edit-toolbar-style.js +38 -1
- package/dist/src/modeller/edit-toolbar-style.js.map +1 -1
- package/dist/src/modeller/edit-toolbar.d.ts +21 -16
- package/dist/src/modeller/edit-toolbar.js +362 -201
- package/dist/src/modeller/edit-toolbar.js.map +1 -1
- package/dist/src/modeller/scene-viewer/ox-scene-viewer.d.ts +2 -1
- package/dist/src/modeller/scene-viewer/ox-scene-viewer.js +7 -11
- package/dist/src/modeller/scene-viewer/ox-scene-viewer.js.map +1 -1
- package/dist/src/ox-board-modeller.d.ts +8 -1
- package/dist/src/ox-board-modeller.js +125 -6
- package/dist/src/ox-board-modeller.js.map +1 -1
- package/dist/src/ox-board-preview.d.ts +36 -0
- package/dist/src/ox-board-preview.js +114 -0
- package/dist/src/ox-board-preview.js.map +1 -0
- package/dist/src/ox-board-template-list.d.ts +1 -0
- package/dist/src/ox-board-template-list.js +19 -1
- package/dist/src/ox-board-template-list.js.map +1 -1
- package/dist/src/ox-board-viewer.d.ts +50 -1
- package/dist/src/ox-board-viewer.js +271 -28
- package/dist/src/ox-board-viewer.js.map +1 -1
- package/dist/src/ox-playback-controls.d.ts +56 -0
- package/dist/src/ox-playback-controls.js +515 -0
- package/dist/src/ox-playback-controls.js.map +1 -0
- package/dist/src/selector/ox-board-selector.js +11 -1
- package/dist/src/selector/ox-board-selector.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +17 -12
- package/translations/en.json +19 -1
- package/translations/ja.json +19 -1
- package/translations/ko.json +19 -1
- package/translations/ms.json +19 -1
- package/translations/zh.json +19 -1
|
@@ -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"]}
|
|
@@ -25,7 +25,14 @@ export class ComponentMenu extends ScopedElementsMixin(LitElement) {
|
|
|
25
25
|
render() {
|
|
26
26
|
return this.group
|
|
27
27
|
? html `
|
|
28
|
-
<h2 onclick=${(e) => e.stopPropagation()}
|
|
28
|
+
<h2 onclick=${(e) => e.stopPropagation()}>
|
|
29
|
+
${ /* group label — translate via group.<name> key, fall back to the
|
|
30
|
+
raw group name so untranslated groups keep working.
|
|
31
|
+
"list" is also translatable; defaults to the literal "list"
|
|
32
|
+
which is what users have been seeing all along. */''}
|
|
33
|
+
${i18next.t(`group.${this.group}`, { defaultValue: this.group })}
|
|
34
|
+
${i18next.t('label.list', { defaultValue: 'list' })}
|
|
35
|
+
</h2>
|
|
29
36
|
|
|
30
37
|
<div templates @mouseover=${(e) => this.onHoverComponent(e)}>
|
|
31
38
|
${(this.templates || []).map(template => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"component-menu.js","sourceRoot":"","sources":["../../../../src/modeller/component-toolbar/component-menu.ts"],"names":[],"mappings":"AAAA;;GAEG;;AAEH,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,EAAkB,MAAM,KAAK,CAAA;AAC3D,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAG1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AAGvC,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAEvD,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,2CAA2C,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAA;AAE1F,MAAM,OAAO,aAAc,SAAQ,mBAAmB,CAAC,UAAU,CAAC;IAAlE;;QAuF8B,WAAM,GAAa,EAAE,CAAA;QACrB,UAAK,GAAiB,IAAI,CAAA;QAC1B,UAAK,GAAkB,EAAE,CAAA;QAE5C,cAAS,GAAiB,EAAE,CAAA;
|
|
1
|
+
{"version":3,"file":"component-menu.js","sourceRoot":"","sources":["../../../../src/modeller/component-toolbar/component-menu.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, state } from 'lit/decorators.js'\n\nimport { Scene } from '@hatiolab/things-scene'\nimport { ScopedElementsMixin } from '@open-wc/scoped-elements'\nimport { ScrollbarStyles } from '@operato/styles'\nimport { i18next } from '@operato/i18n'\n\nimport { Pallet, PalletItem } from '../../types.js'\nimport { ComponentDetail } from './component-detail.js'\n\nconst noImage = new URL('../../../../icons/components/no-image.png', import.meta.url).href\n\nexport class ComponentMenu extends ScopedElementsMixin(LitElement) {\n static styles = [\n ScrollbarStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n align-content: stretch;\n\n background-color: var(--component-menu-background-color, var(--md-sys-color-secondary-container));\n color: var(--component-menu-color, var(--md-sys-color-on-secondary-container));\n margin: 0px;\n padding: 0px;\n\n width: 180px;\n height: 100%;\n\n overflow: visible;\n\n border: 2px solid var(--component-menu-border-color, var(--md-sys-color-secondary));\n box-sizing: border-box;\n\n position: absolute;\n top: 0px;\n\n z-index: 1;\n }\n\n h2 {\n background-color: var(--component-menu-border-color, var(--md-sys-color-secondary));\n padding: 1px 5px;\n margin: 0;\n font: var(--component-menu-title);\n color: var(--md-sys-color-on-secondary);\n text-transform: capitalize;\n }\n\n [templates] {\n flex: 1;\n\n display: block;\n margin: 0;\n padding: 0;\n overflow-y: auto;\n\n background-color: var(--component-menu-background-color, var(--md-sys-color-secondary-container));\n color: var(--component-menu-color, var(--md-sys-color-on-secondary-container));\n }\n\n [template] {\n display: flex;\n align-items: center;\n min-height: var(--component-menu-item-icon-size);\n padding: 0 5px 0 0;\n border-bottom: 1px solid rgba(0, 0, 0, 0.1);\n font-size: 11px;\n color: var(--component-menu-item-color, var(--md-sys-color-on-secondary-container));\n text-transform: capitalize;\n }\n\n [template]:hover,\n [template]:focus {\n color: var(--component-menu-item-hover-color, var(--md-sys-color-secondary));\n font-weight: bold;\n cursor: pointer;\n }\n\n [template] img {\n margin: 5px;\n width: var(--component-menu-item-icon-size);\n height: var(--component-menu-item-icon-size);\n }\n\n component-detail {\n position: absolute;\n top: 0;\n left: 180px;\n height: 100%;\n outline: none;\n }\n\n component-detail[hidden] {\n display: none;\n }\n `\n ]\n\n @property({ type: Object }) groups: Pallet[] = []\n @property({ type: Object }) scene: Scene | null = null\n @property({ type: String }) group: string | null = ''\n\n @state() templates: PalletItem[] = []\n @state() template: PalletItem | any\n\n @query('component-detail') detail!: HTMLElement\n\n static get scopedElements() {\n return {\n 'component-detail': ComponentDetail\n }\n }\n\n render() {\n return this.group\n ? html`\n <h2 onclick=${(e: MouseEvent) => e.stopPropagation()}>\n ${/* group label — translate via group.<name> key, fall back to the\n raw group name so untranslated groups keep working.\n \"list\" is also translatable; defaults to the literal \"list\"\n which is what users have been seeing all along. */ ''}\n ${i18next.t(`group.${this.group}`, { defaultValue: this.group })}\n ${i18next.t('label.list', { defaultValue: 'list' })}\n </h2>\n\n <div templates @mouseover=${(e: MouseEvent) => this.onHoverComponent(e)}>\n ${(this.templates || []).map(template => {\n const { type } = template\n\n return html`\n <div @click=${this.onClickTemplate} data-type=${type} template>\n <img src=${String(this.templateIcon(template))} />${i18next.t(`component.${type}`)}\n </div>\n `\n })}\n </div>\n\n <component-detail\n tabindex=\"-1\"\n @focusout=${() => {\n this.template = null\n }}\n .template=${this.template}\n hidden\n >\n </component-detail>\n `\n : html``\n }\n\n updated(changes: PropertyValues<this>) {\n if (changes.has('group')) {\n if (!this.group) {\n this.templates = []\n this.setAttribute('hidden', '')\n } else {\n this.templates = this.groups.find((g: Pallet) => g.name === this.group)?.templates || []\n this.removeAttribute('active')\n }\n }\n\n if (changes.has('template')) {\n this.template && this.template.about\n ? this.detail.removeAttribute('hidden')\n : this.detail.setAttribute('hidden', '')\n }\n }\n\n findTemplate(type: string | null | undefined) {\n this.template = type && this.templates.find(template => template.type == type)\n }\n\n onHoverComponent(e: MouseEvent) {\n const button = e.target as HTMLElement\n this.findTemplate(button!.closest('[data-type]')?.getAttribute('data-type'))\n }\n\n onClickTemplate(e: MouseEvent) {\n var item = e.target as HTMLElement\n var type = item.closest('[data-type]')?.getAttribute('data-type')\n\n if (!type) {\n return\n }\n\n if (this.scene) {\n this.scene.rootContainer?.trigger('addstop') // 새로운 컴포넌트 클릭시 이전 추가 모드 정지\n this.template = this.templates.find(template => template.type == type)\n this.template && this.scene.startAddMode(JSON.parse(JSON.stringify(this.template.model)))\n }\n\n this.group = null\n }\n\n templateIcon(template: PalletItem) {\n return template.icon || noImage\n }\n}\n"]}
|
|
@@ -12,7 +12,7 @@ export const style = css `
|
|
|
12
12
|
[tools] {
|
|
13
13
|
display: flex;
|
|
14
14
|
align-items: center;
|
|
15
|
-
overflow:
|
|
15
|
+
overflow: hidden;
|
|
16
16
|
padding: 0px 10px;
|
|
17
17
|
}
|
|
18
18
|
|
|
@@ -201,6 +201,43 @@ export const style = css `
|
|
|
201
201
|
background-position-y: -1593px;
|
|
202
202
|
}
|
|
203
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
|
+
|
|
204
241
|
#toggle-property {
|
|
205
242
|
background-position-y: -1392px;
|
|
206
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsQvB,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"]}
|
|
@@ -3,28 +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 styleCopy;
|
|
16
|
-
private databindCopy;
|
|
17
|
-
private cut;
|
|
18
|
-
private copy;
|
|
19
|
-
private paste;
|
|
20
|
-
private delete;
|
|
21
|
-
private forward;
|
|
22
|
-
private backward;
|
|
23
|
-
private front;
|
|
24
|
-
private back;
|
|
25
|
-
private aligners;
|
|
26
|
-
private zorders;
|
|
27
|
-
private distributes;
|
|
28
15
|
firstUpdated(): void;
|
|
29
16
|
updated(changes: PropertyValues<this>): void;
|
|
30
17
|
render(): import("lit-html").TemplateResult<1>;
|
|
@@ -32,10 +19,14 @@ export declare class EditToolbar extends LitElement {
|
|
|
32
19
|
getSymbol(key: string): string;
|
|
33
20
|
private getShortcutString;
|
|
34
21
|
onShortcut(e: KeyboardEvent): boolean;
|
|
22
|
+
private _setDisabled;
|
|
35
23
|
onExecute(command: string, undoable: boolean, redoable: boolean): void;
|
|
36
24
|
onUndo(undoable: boolean, redoable: boolean): void;
|
|
37
25
|
onRedo(undoable: boolean, redoable: boolean): void;
|
|
38
26
|
onSceneChanged(after?: Scene, before?: Scene): void;
|
|
27
|
+
private _onDimensionChanged;
|
|
28
|
+
private _onGizmoAttachChanged;
|
|
29
|
+
private _setGizmoMode;
|
|
39
30
|
onSelectedChanged(after: Component[], before: Component[]): void;
|
|
40
31
|
onTapUndo(): void;
|
|
41
32
|
onTapRedo(): void;
|
|
@@ -45,6 +36,18 @@ export declare class EditToolbar extends LitElement {
|
|
|
45
36
|
onTapCopy(): Promise<void>;
|
|
46
37
|
onTapPaste(): void;
|
|
47
38
|
onTapDelete(): void;
|
|
39
|
+
/**
|
|
40
|
+
* 멀티 컴포넌트 일괄 붙여넣기 — Ctrl+Shift+V 진입점.
|
|
41
|
+
*
|
|
42
|
+
* 흐름:
|
|
43
|
+
* 1. 클립보드 (this.cliped) 검증 — *단일* 컴포넌트만 허용
|
|
44
|
+
* 2. scene.startBulkCreateMode → AddLayer 가 캔버스 위에 드래그 박스 그림
|
|
45
|
+
* 3. 사용자가 영역 드래그 → Promise resolve({area, template})
|
|
46
|
+
* ESC/취소 → Promise resolve(null)
|
|
47
|
+
* 4. 영역이 결정되면 dialog 오픈
|
|
48
|
+
*/
|
|
49
|
+
onBulkPaste(): Promise<void>;
|
|
50
|
+
private _openBulkCreateDialog;
|
|
48
51
|
onTapSelectAll(): void;
|
|
49
52
|
onTapFontIncrease(e: TouchEvent): void;
|
|
50
53
|
onTapFontDecrease(e: TouchEvent): void;
|
|
@@ -56,6 +59,8 @@ export declare class EditToolbar extends LitElement {
|
|
|
56
59
|
onTapToggle(): void;
|
|
57
60
|
onTapFitScene(): void;
|
|
58
61
|
onTapPreview(): void;
|
|
62
|
+
onTapDataBinding(): void;
|
|
59
63
|
onTapDownloadModel(): void;
|
|
64
|
+
onTapEditModel(): void;
|
|
60
65
|
onTapDistribute(e: TouchEvent | string): void;
|
|
61
66
|
}
|