polarvo-layout 1.0.17 → 1.0.19
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/package.json +1 -1
- package/src/components/SideBar/ElementSideBar.vue +12 -5
- package/src/core/engines/FreeDropEngine.js +7 -6
- package/src/core/engines/GridDropEngine.js +7 -11
- package/src/core/managers/EngineManager.js +82 -51
- package/src/core/managers/originals/EngineManager copy.js +786 -0
- package/src/library/LayoutLibrary.js +5 -2
- /package/src/core/engines/{FreeDropEngine copy.js → originals/FreeDropEngine copy.js} +0 -0
- /package/src/core/engines/{GridDropEngine copy.js → originals/GridDropEngine copy.js} +0 -0
package/package.json
CHANGED
|
@@ -4,9 +4,8 @@
|
|
|
4
4
|
v-for="(item, index) in allElements"
|
|
5
5
|
:key="index"
|
|
6
6
|
class="flex flex-col items-center justify-center border border-gray-200 rounded-lg p-2 hover:bg-gray-50 transition-colors cursor-pointer active:scale-95"
|
|
7
|
-
@mousedown.stop="addElementToFree($event, item.elName)"
|
|
8
|
-
|
|
9
|
-
>
|
|
7
|
+
@mousedown.stop="sectionMode == 'free' ? addElementToFree($event, item.elName) : addElementToGrid(item.elName)"
|
|
8
|
+
>
|
|
10
9
|
<img :src="item.imgurl" alt="" class="w-24 h-auto rounded" />
|
|
11
10
|
<h4 class="text-sm">{{ item.elName }}</h4>
|
|
12
11
|
</div>
|
|
@@ -14,7 +13,7 @@
|
|
|
14
13
|
</template>
|
|
15
14
|
|
|
16
15
|
<script setup>
|
|
17
|
-
import { ref, onBeforeMount } from 'vue';
|
|
16
|
+
import { ref, onBeforeMount, toRefs, computed } from 'vue';
|
|
18
17
|
|
|
19
18
|
const props = defineProps({
|
|
20
19
|
polarvo: {
|
|
@@ -30,11 +29,16 @@ const props = defineProps({
|
|
|
30
29
|
const { addElement: addElementToFree } = props.polarvo.freeDrop;
|
|
31
30
|
const { addElement: addElementToGrid } = props.polarvo.gridDrop;
|
|
32
31
|
|
|
32
|
+
const { activeData } = toRefs(props.polarvo.layout.state);
|
|
33
|
+
const sectionMode = computed(() => activeData.value?.mode);
|
|
34
|
+
|
|
33
35
|
const allElements = ref([]);
|
|
34
36
|
|
|
35
37
|
/** 프로젝트 폴더에서 요소 컴포넌트 주소를 맞춰줘야 정상 작동합니다.
|
|
36
38
|
* (현재 /src/components/elements/ 내부에 모든 요소 컴포넌트가 있어야 함) */
|
|
37
39
|
const modules = import.meta.glob('@/components/elements/*.vue');
|
|
40
|
+
const imageModules = import.meta.glob('@/assets/el_img/*.{png,gif}', { eager: true });
|
|
41
|
+
|
|
38
42
|
async function getElements() {
|
|
39
43
|
// 특정 컴포넌트는 제외
|
|
40
44
|
const excludedElements = [
|
|
@@ -58,9 +62,12 @@ async function getElements() {
|
|
|
58
62
|
|
|
59
63
|
// 확장자 조건 설정
|
|
60
64
|
const ext = elName === 'dataList' ? 'gif' : 'png';
|
|
65
|
+
const imgKey = `/src/assets/el_img/${elName}.${ext}`;
|
|
66
|
+
|
|
61
67
|
let result = {
|
|
62
68
|
elName,
|
|
63
|
-
imgurl: new URL(`../../assets/el_img/${elName}.${ext}`, props.urlBase),
|
|
69
|
+
// imgurl: new URL(`../../assets/el_img/${elName}.${ext}`, props.urlBase),
|
|
70
|
+
imgurl: imageModules[imgKey]?.default ?? '',
|
|
64
71
|
// component: markRaw(defineAsyncComponent(modules[path]))
|
|
65
72
|
};
|
|
66
73
|
|
|
@@ -122,17 +122,18 @@ class FreeDropEngine {
|
|
|
122
122
|
});
|
|
123
123
|
});
|
|
124
124
|
|
|
125
|
-
this._subscribe('system:requestUpdateActiveElement', ({
|
|
126
|
-
this._elements
|
|
127
|
-
|
|
125
|
+
this._subscribe('system:requestUpdateActiveElement', ({ elementId, element }) => {
|
|
126
|
+
const index = this._elements.findIndex((x) => x.id === elementId);
|
|
127
|
+
|
|
128
|
+
if (index !== -1) {
|
|
129
|
+
this._elements[index] = element; // 업데이트
|
|
130
|
+
this._elementsUpdate = true;
|
|
131
|
+
}
|
|
128
132
|
|
|
129
|
-
const element = this._elements.find((x) => x.id === elementId);
|
|
130
133
|
this.setActiveElement(element, 'update');
|
|
131
|
-
// this.setActiveElement(element);
|
|
132
134
|
|
|
133
135
|
this.eventBus.emit('freeDrop:requestUpdateActiveElement', {
|
|
134
136
|
element: element,
|
|
135
|
-
// elements: cloneDeep(this.freeElements),
|
|
136
137
|
timestamp: Date.now(),
|
|
137
138
|
});
|
|
138
139
|
});
|
|
@@ -103,20 +103,18 @@ class GridDropEngine {
|
|
|
103
103
|
});
|
|
104
104
|
});
|
|
105
105
|
|
|
106
|
-
this._subscribe('system:requestUpdateActiveElement', ({
|
|
107
|
-
this._elements
|
|
108
|
-
|
|
106
|
+
this._subscribe('system:requestUpdateActiveElement', ({ elementId, element }) => {
|
|
107
|
+
const index = this._elements.findIndex((x) => x.id === elementId);
|
|
108
|
+
|
|
109
|
+
if (index !== -1) {
|
|
110
|
+
this._elements[index] = element; // 업데이트
|
|
111
|
+
this._elementsUpdate = true;
|
|
112
|
+
}
|
|
109
113
|
|
|
110
|
-
const element = this._elements.find((x) => x.id === elementId);
|
|
111
114
|
this.setActiveElement(element, 'update');
|
|
112
|
-
// Object.assign(
|
|
113
|
-
// element,
|
|
114
|
-
// elements.find((x) => x.id === elementId),
|
|
115
|
-
// );
|
|
116
115
|
|
|
117
116
|
this.eventBus.emit('gridDrop:requestUpdateActiveElement', {
|
|
118
117
|
element: element,
|
|
119
|
-
// elements: cloneDeep(this.gridElements),
|
|
120
118
|
timestamp: Date.now(),
|
|
121
119
|
});
|
|
122
120
|
});
|
|
@@ -212,7 +210,6 @@ class GridDropEngine {
|
|
|
212
210
|
this._elementsUpdate = false;
|
|
213
211
|
}
|
|
214
212
|
|
|
215
|
-
|
|
216
213
|
/** 활성화 배치요소 변경
|
|
217
214
|
* @param {object|null} element - 활성화할 배치요소 객체 (null인 경우 비활성화)
|
|
218
215
|
* @param {string|null} action - 변경 액션 (예: 'click', 'reset' 등)
|
|
@@ -391,7 +388,6 @@ class GridDropEngine {
|
|
|
391
388
|
// 리사이징 중이면 드래그 불가
|
|
392
389
|
if (this._dragState.isResizing) return;
|
|
393
390
|
|
|
394
|
-
|
|
395
391
|
// 기존 아이템인 경우
|
|
396
392
|
if (item.id) {
|
|
397
393
|
// 우클릭 시 해당 아이템 삭제
|
|
@@ -27,9 +27,7 @@ class EngineManager {
|
|
|
27
27
|
};
|
|
28
28
|
|
|
29
29
|
// (캐시) layoutData 및 activeSection 변경 감지용 -> activeData getter에서 사용
|
|
30
|
-
this._dataUpdate = false;
|
|
31
30
|
this._layoutData = null;
|
|
32
|
-
this._activeData = null;
|
|
33
31
|
|
|
34
32
|
// (캐시) dataConverter에서 사용하는 현재 컨테이너 크기
|
|
35
33
|
this._containerWidth = 1920;
|
|
@@ -43,11 +41,10 @@ class EngineManager {
|
|
|
43
41
|
* @return {object} - layoutData 중 activeSection 데이터 객체
|
|
44
42
|
*/
|
|
45
43
|
get activeData() {
|
|
46
|
-
if (this.
|
|
47
|
-
|
|
48
|
-
this._dataUpdate = false;
|
|
44
|
+
if (!this._layoutData || !this.state.activeSection) {
|
|
45
|
+
return null;
|
|
49
46
|
}
|
|
50
|
-
return this.
|
|
47
|
+
return this._layoutData[this.state.activeSection];
|
|
51
48
|
}
|
|
52
49
|
|
|
53
50
|
/** 상태 정보 반환
|
|
@@ -102,8 +99,6 @@ class EngineManager {
|
|
|
102
99
|
...updates,
|
|
103
100
|
};
|
|
104
101
|
|
|
105
|
-
this._dataUpdate = true;
|
|
106
|
-
|
|
107
102
|
// 이벤트 발행
|
|
108
103
|
this.eventBus.emit('system:updateActiveSectionConfig', {
|
|
109
104
|
type: type,
|
|
@@ -113,30 +108,38 @@ class EngineManager {
|
|
|
113
108
|
});
|
|
114
109
|
}
|
|
115
110
|
|
|
116
|
-
/** [내부함수] activeSection의 elementIds 업데이트
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
_updateActiveElementIds(elementIds) {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
111
|
+
// /** [내부함수] activeSection의 elementIds 업데이트
|
|
112
|
+
// * @param {Array} elementIds - 새로운 elementIds 배열
|
|
113
|
+
// */
|
|
114
|
+
// _updateActiveElementIds(elementIds) {
|
|
115
|
+
// if (!this._layoutData || !this.state.activeSection) {
|
|
116
|
+
// console.error('[EngineManager] activeData를 업데이트할 수 없습니다.');
|
|
117
|
+
// return;
|
|
118
|
+
// }
|
|
124
119
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
120
|
+
// if (!('elementIds' in this._layoutData[this.state.activeSection])) {
|
|
121
|
+
// this._layoutData[this.state.activeSection].elementIds = [];
|
|
122
|
+
// }
|
|
128
123
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
124
|
+
// this._layoutData[this.state.activeSection].elementIds = elementIds;
|
|
125
|
+
// }
|
|
132
126
|
|
|
133
127
|
/** 현재 컨테이너 크기 설정 */
|
|
134
128
|
setContainerSize() {
|
|
135
|
-
|
|
136
|
-
|
|
129
|
+
try {
|
|
130
|
+
const containerEl = document.getElementById('baseLayout');
|
|
131
|
+
if (!containerEl) {
|
|
132
|
+
console.warn('[EngineManager] baseLayout 요소를 찾을 수 없습니다.');
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
const { width, height } = containerEl.getBoundingClientRect();
|
|
137
136
|
|
|
138
|
-
|
|
139
|
-
|
|
137
|
+
this._containerWidth = width ?? this._containerWidth;
|
|
138
|
+
this._containerHeight = height ?? this._containerHeight;
|
|
139
|
+
} catch (error) {
|
|
140
|
+
console.error('[EngineManager] 컨테이너 크기 설정 중 오류 발생: ', error);
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
140
143
|
}
|
|
141
144
|
|
|
142
145
|
getContainerSize() {
|
|
@@ -149,8 +152,12 @@ class EngineManager {
|
|
|
149
152
|
/** elements 초기화
|
|
150
153
|
* @param {Array} elements - 새로운 elements 배열
|
|
151
154
|
*/
|
|
152
|
-
// [추가 예정] elements의 position, size (비율로 저장됨)를 실제 값으로 변환하여 rendering
|
|
153
155
|
setElements(elements) {
|
|
156
|
+
if (!elements || !Array.isArray(elements)) {
|
|
157
|
+
console.error('[EngineManager] 유효하지 않은 elements:', elements);
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
|
|
154
161
|
const denormalizedElements = dataConverter.denormalize(elements, this._containerWidth, this._containerHeight);
|
|
155
162
|
|
|
156
163
|
// section 정보가 없는 경우 section1으로 간주
|
|
@@ -158,7 +165,7 @@ class EngineManager {
|
|
|
158
165
|
|
|
159
166
|
this.eventBus.emit('system:setElements', {
|
|
160
167
|
layoutData: cloneDeep(this._layoutData),
|
|
161
|
-
elements:
|
|
168
|
+
elements: this.state.elements,
|
|
162
169
|
timestamp: Date.now(),
|
|
163
170
|
});
|
|
164
171
|
}
|
|
@@ -168,16 +175,29 @@ class EngineManager {
|
|
|
168
175
|
* @param {object} changes - 변경할 속성 객체
|
|
169
176
|
*/
|
|
170
177
|
updateActiveElement(id, changes) {
|
|
171
|
-
|
|
172
|
-
if (exists) {
|
|
173
|
-
Object.assign(exists, changes);
|
|
178
|
+
if (!id || !changes) return false;
|
|
174
179
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
180
|
+
const index = this.state.elements.findIndex((x) => x.id === id);
|
|
181
|
+
if (index === -1) return false;
|
|
182
|
+
|
|
183
|
+
// 보호필드 (id, mode, section)은 직접 업데이트 불가 - 레이아웃 엔진에서 일괄 관리
|
|
184
|
+
const { id: _, mode: __, section: ___, ...safeChanges } = changes;
|
|
185
|
+
if (Object.keys(safeChanges).length === 0) return false;
|
|
186
|
+
|
|
187
|
+
const updatedElement = {
|
|
188
|
+
...this.state.elements[index],
|
|
189
|
+
...safeChanges,
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
this.state.elements = [...this.state.elements.slice(0, index), updatedElement, ...this.state.elements.slice(index + 1)];
|
|
193
|
+
|
|
194
|
+
// 변경된 element만 전달
|
|
195
|
+
this.eventBus.emit('system:requestUpdateActiveElement', {
|
|
196
|
+
// elements: this.state.elements,
|
|
197
|
+
elementId: id,
|
|
198
|
+
element: updatedElement,
|
|
199
|
+
timestamp: Date.now(),
|
|
200
|
+
});
|
|
181
201
|
}
|
|
182
202
|
|
|
183
203
|
/** ---------------------------------- display Engine ---------------------------------- **/
|
|
@@ -194,10 +214,20 @@ class EngineManager {
|
|
|
194
214
|
|
|
195
215
|
_updateElementsScale(oldValue, newValue) {
|
|
196
216
|
const oldWidth = oldValue.px;
|
|
197
|
-
const oldHeight =
|
|
217
|
+
const oldHeight =
|
|
218
|
+
oldValue.px /
|
|
219
|
+
oldValue.aspectRatio
|
|
220
|
+
.split('/')
|
|
221
|
+
.map(Number)
|
|
222
|
+
.reduce((a, b) => a / b);
|
|
198
223
|
|
|
199
224
|
const newWidth = newValue.px;
|
|
200
|
-
const newHeight =
|
|
225
|
+
const newHeight =
|
|
226
|
+
newValue.px /
|
|
227
|
+
newValue.aspectRatio
|
|
228
|
+
.split('/')
|
|
229
|
+
.map(Number)
|
|
230
|
+
.reduce((a, b) => a / b);
|
|
201
231
|
|
|
202
232
|
nextTick(() => {
|
|
203
233
|
this.setContainerSize();
|
|
@@ -221,7 +251,6 @@ class EngineManager {
|
|
|
221
251
|
// layoutName 변경에 따른 activeSection 초기화
|
|
222
252
|
this._subscribe('layout:setLayoutName', ({ screenConfig }) => {
|
|
223
253
|
this._layoutData = screenConfig.layoutData;
|
|
224
|
-
this._dataUpdate = true;
|
|
225
254
|
this.setActiveSection('section1', true);
|
|
226
255
|
|
|
227
256
|
this.eventBus.emit('system:updateLayoutData', {
|
|
@@ -232,7 +261,6 @@ class EngineManager {
|
|
|
232
261
|
});
|
|
233
262
|
this._subscribe('layout:updateLayoutName', ({ layoutData }) => {
|
|
234
263
|
this._layoutData = layoutData;
|
|
235
|
-
this._dataUpdate = true;
|
|
236
264
|
this.setActiveSection(null, true);
|
|
237
265
|
|
|
238
266
|
this.eventBus.emit('system:updateLayoutData', {
|
|
@@ -251,7 +279,6 @@ class EngineManager {
|
|
|
251
279
|
if (!setting && this.state.activeSection === name) return;
|
|
252
280
|
|
|
253
281
|
this.state.activeSection = name;
|
|
254
|
-
this._dataUpdate = true;
|
|
255
282
|
|
|
256
283
|
this.eventBus.emit('system:updateActiveSection', {
|
|
257
284
|
activeSection: name,
|
|
@@ -328,9 +355,15 @@ class EngineManager {
|
|
|
328
355
|
_setupFreeDropEngineConnections() {
|
|
329
356
|
// 배치요소 추가 요청
|
|
330
357
|
this._subscribe('freeDrop:requestAddElement', ({ element }) => {
|
|
358
|
+
if (this.state.elements.some((el) => el.id === element.id)) {
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
|
|
331
362
|
this.state.elements.push(element);
|
|
332
|
-
this.
|
|
363
|
+
this._updateActiveSectionConfig('elementIds', { elementIds: [...this.activeData.elementIds, element.id] });
|
|
364
|
+
// this._updateActiveElementIds([...this.activeData.elementIds, element.id]);
|
|
333
365
|
|
|
366
|
+
console.log('this.state.elements', this.state.elements)
|
|
334
367
|
this.eventBus.emit('system:requestUpdateData', {
|
|
335
368
|
action: 'add',
|
|
336
369
|
elements: this.state.elements,
|
|
@@ -345,7 +378,8 @@ class EngineManager {
|
|
|
345
378
|
// 배치요소 삭제 요청
|
|
346
379
|
this._subscribe('freeDrop:requestDeleteElement', ({ elementId }) => {
|
|
347
380
|
this.state.elements = this.state.elements.filter((el) => el.id !== elementId);
|
|
348
|
-
this.
|
|
381
|
+
this._updateActiveSectionConfig('elementIds', { elementIds: this.activeData.elementIds.filter((elId) => elId !== elementId) });
|
|
382
|
+
// this._updateActiveElementIds(this.activeData.elementIds.filter((elId) => elId !== elementId));
|
|
349
383
|
|
|
350
384
|
this.eventBus.emit('system:requestUpdateData', {
|
|
351
385
|
action: 'delete',
|
|
@@ -381,7 +415,8 @@ class EngineManager {
|
|
|
381
415
|
// 배치요소 추가 요청
|
|
382
416
|
this._subscribe('gridDrop:requestAddElement', ({ element }) => {
|
|
383
417
|
this.state.elements.push(element);
|
|
384
|
-
this.
|
|
418
|
+
this._updateActiveSectionConfig('elementIds', { elementIds: [...this.activeData.elementIds, element.id] });
|
|
419
|
+
// this._updateActiveElementIds([...this.activeData.elementIds, element.id]);
|
|
385
420
|
|
|
386
421
|
this.eventBus.emit('system:requestUpdateData', {
|
|
387
422
|
action: 'add',
|
|
@@ -397,7 +432,8 @@ class EngineManager {
|
|
|
397
432
|
// 배치요소 삭제 요청
|
|
398
433
|
this._subscribe('gridDrop:requestDeleteElement', ({ elementId }) => {
|
|
399
434
|
this.state.elements = this.state.elements.filter((el) => el.id !== elementId);
|
|
400
|
-
this.
|
|
435
|
+
this._updateActiveSectionConfig('elementIds', { elementIds: this.activeData.elementIds.filter((elId) => elId !== elementId) });
|
|
436
|
+
// this._updateActiveElementIds(this.activeData.elementIds.filter((elId) => elId !== elementId));
|
|
401
437
|
|
|
402
438
|
this.eventBus.emit('system:requestUpdateData', {
|
|
403
439
|
action: 'delete',
|
|
@@ -412,7 +448,6 @@ class EngineManager {
|
|
|
412
448
|
|
|
413
449
|
// 배치요소 위치 및 사이즈 변경 요청
|
|
414
450
|
this._subscribe('gridDrop:requestUpdateElement', ({ historyEvent, elementId, position, size }) => {
|
|
415
|
-
|
|
416
451
|
const element = this.state.elements.find((el) => el.id === elementId);
|
|
417
452
|
if (element) {
|
|
418
453
|
element.position = position;
|
|
@@ -613,9 +648,7 @@ class EngineManager {
|
|
|
613
648
|
this.engines.clear();
|
|
614
649
|
this._initialized = false;
|
|
615
650
|
|
|
616
|
-
this._dataUpdate = false;
|
|
617
651
|
this._layoutData = null;
|
|
618
|
-
this._activeData = null;
|
|
619
652
|
|
|
620
653
|
this.state = {};
|
|
621
654
|
this.eventBus = null;
|
|
@@ -630,9 +663,7 @@ class EngineManager {
|
|
|
630
663
|
...this.config.initialState,
|
|
631
664
|
};
|
|
632
665
|
|
|
633
|
-
this._dataUpdate = false;
|
|
634
666
|
this._layoutData = null;
|
|
635
|
-
this._activeData = null;
|
|
636
667
|
|
|
637
668
|
this.eventBus.emit('system:enginesReset', {
|
|
638
669
|
timestamp: Date.now(),
|
|
@@ -0,0 +1,786 @@
|
|
|
1
|
+
import { cloneDeep } from 'lodash-es';
|
|
2
|
+
import { nextTick } from 'vue';
|
|
3
|
+
|
|
4
|
+
import EventBus from '../EventBus.js';
|
|
5
|
+
import ApiManager from '../ApiManager.js';
|
|
6
|
+
import utils from '../../../utils/index.js';
|
|
7
|
+
const { dataConverter } = utils;
|
|
8
|
+
|
|
9
|
+
import createDefaultConfig from '../../../configs/index.js';
|
|
10
|
+
const { initialState, resource } = createDefaultConfig();
|
|
11
|
+
|
|
12
|
+
// event 발행 시 'system'으로 시작하는 이름 사용
|
|
13
|
+
class EngineManager {
|
|
14
|
+
constructor(config = {}) {
|
|
15
|
+
this.eventBus = new EventBus();
|
|
16
|
+
this.engines = new Map();
|
|
17
|
+
this.config = { ...config };
|
|
18
|
+
|
|
19
|
+
// API 인스턴스
|
|
20
|
+
if (config.apiClient) {
|
|
21
|
+
this.apiManager = new ApiManager(config.apiClient, config.apiEndpoints);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
this.state = {
|
|
25
|
+
...initialState,
|
|
26
|
+
...config.initialState,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// (캐시) layoutData 및 activeSection 변경 감지용 -> activeData getter에서 사용
|
|
30
|
+
this._dataUpdate = false;
|
|
31
|
+
this._layoutData = null;
|
|
32
|
+
this._activeData = null;
|
|
33
|
+
|
|
34
|
+
// (캐시) dataConverter에서 사용하는 현재 컨테이너 크기
|
|
35
|
+
this._containerWidth = 1920;
|
|
36
|
+
this._containerHeight = 1080;
|
|
37
|
+
|
|
38
|
+
this._subscriptions = [];
|
|
39
|
+
this._initialized = false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** activeSection 데이터 반환
|
|
43
|
+
* @return {object} - layoutData 중 activeSection 데이터 객체
|
|
44
|
+
*/
|
|
45
|
+
get activeData() {
|
|
46
|
+
if (this._dataUpdate && this.state.activeSection) {
|
|
47
|
+
this._activeData = this._layoutData[this.state.activeSection];
|
|
48
|
+
this._dataUpdate = false;
|
|
49
|
+
}
|
|
50
|
+
return this._activeData;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** 상태 정보 반환
|
|
54
|
+
* @return {object} - 현재 상태 객체
|
|
55
|
+
*/
|
|
56
|
+
getState() {
|
|
57
|
+
return {
|
|
58
|
+
elements: this.state.elements,
|
|
59
|
+
layoutData: cloneDeep(this._layoutData),
|
|
60
|
+
activeData: cloneDeep(this.activeData),
|
|
61
|
+
activeSection: this.state.activeSection,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** 상태 정보 설정
|
|
66
|
+
* @param {object} state - 복원할 상태 객체
|
|
67
|
+
*/
|
|
68
|
+
setState(state) {
|
|
69
|
+
this.state.elements = state.elements;
|
|
70
|
+
this._layoutData = state.layoutData;
|
|
71
|
+
this.setActiveSection(state.activeSection);
|
|
72
|
+
|
|
73
|
+
this.eventBus.emit('system:restoredState', {
|
|
74
|
+
stateData: state,
|
|
75
|
+
timestamp: Date.now(),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** [내부함수] activeSection의 설정값 업데이트
|
|
80
|
+
* @param {string} type - 설정 타입 (mode, config)
|
|
81
|
+
* @param {object} updates - 업데이트할 설정 값 객체
|
|
82
|
+
*/
|
|
83
|
+
_updateActiveSectionConfig(type, updates) {
|
|
84
|
+
if (!this._layoutData || !this.state.activeSection) {
|
|
85
|
+
console.error('[EngineManager] activeData를 업데이트할 수 없습니다.');
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
Object.keys(updates).forEach((key) => {
|
|
90
|
+
if (!(key in this._layoutData[this.state.activeSection])) {
|
|
91
|
+
if (key == 'elementIds') {
|
|
92
|
+
this._layoutData[this.state.activeSection][key] = [];
|
|
93
|
+
} else {
|
|
94
|
+
this._layoutData[this.state.activeSection][key] = null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// 원본 데이터 업데이트
|
|
100
|
+
this._layoutData[this.state.activeSection] = {
|
|
101
|
+
...this._layoutData[this.state.activeSection],
|
|
102
|
+
...updates,
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
this._dataUpdate = true;
|
|
106
|
+
|
|
107
|
+
// 이벤트 발행
|
|
108
|
+
this.eventBus.emit('system:updateActiveSectionConfig', {
|
|
109
|
+
type: type,
|
|
110
|
+
layoutData: cloneDeep(this._layoutData),
|
|
111
|
+
activeData: cloneDeep(this.activeData),
|
|
112
|
+
timestamp: Date.now(),
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** [내부함수] activeSection의 elementIds 업데이트
|
|
117
|
+
* @param {Array} elementIds - 새로운 elementIds 배열
|
|
118
|
+
*/
|
|
119
|
+
_updateActiveElementIds(elementIds) {
|
|
120
|
+
if (!this._layoutData || !this.state.activeSection) {
|
|
121
|
+
console.error('[EngineManager] activeData를 업데이트할 수 없습니다.');
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (!('elementIds' in this._layoutData[this.state.activeSection])) {
|
|
126
|
+
this._layoutData[this.state.activeSection].elementIds = [];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
this._layoutData[this.state.activeSection].elementIds = elementIds;
|
|
130
|
+
this._dataUpdate = true;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** 현재 컨테이너 크기 설정 */
|
|
134
|
+
setContainerSize() {
|
|
135
|
+
const containerEl = document.getElementById('baseLayout');
|
|
136
|
+
const containerRect = containerEl.getBoundingClientRect();
|
|
137
|
+
|
|
138
|
+
this._containerWidth = containerRect.width ?? this._containerWidth;
|
|
139
|
+
this._containerHeight = containerRect.height ?? this._containerHeight;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
getContainerSize() {
|
|
143
|
+
return {
|
|
144
|
+
width: this._containerWidth,
|
|
145
|
+
height: this._containerHeight,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** elements 초기화
|
|
150
|
+
* @param {Array} elements - 새로운 elements 배열
|
|
151
|
+
*/
|
|
152
|
+
setElements(elements) {
|
|
153
|
+
const denormalizedElements = dataConverter.denormalize(elements, this._containerWidth, this._containerHeight);
|
|
154
|
+
|
|
155
|
+
// section 정보가 없는 경우 section1으로 간주
|
|
156
|
+
this.state.elements = denormalizedElements.map((x) => (x.section ? { ...x } : { ...x, section: 'section1' }));
|
|
157
|
+
|
|
158
|
+
this.eventBus.emit('system:setElements', {
|
|
159
|
+
layoutData: cloneDeep(this._layoutData),
|
|
160
|
+
elements: cloneDeep(this.state.elements),
|
|
161
|
+
timestamp: Date.now(),
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** activeElement 업데이트
|
|
166
|
+
* @param {string} id - 업데이트할 element의 ID
|
|
167
|
+
* @param {object} changes - 변경할 속성 객체
|
|
168
|
+
*/
|
|
169
|
+
updateActiveElement(id, changes) {
|
|
170
|
+
const exists = this.state.elements.find((x) => x.id === id);
|
|
171
|
+
if (exists) {
|
|
172
|
+
Object.assign(exists, changes);
|
|
173
|
+
|
|
174
|
+
this.eventBus.emit('system:requestUpdateActiveElement', {
|
|
175
|
+
elements: this.state.elements,
|
|
176
|
+
elementId: id,
|
|
177
|
+
timestamp: Date.now(),
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** ---------------------------------- display Engine ---------------------------------- **/
|
|
183
|
+
/** [내부함수] displayEngine 연결 설정 */
|
|
184
|
+
_setupDisplayEngineConnections() {
|
|
185
|
+
this._subscribe('display:updateDisplayMode', ({ displaySize, prev }) => {
|
|
186
|
+
this._updateElementsScale(prev.displaySize, displaySize);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
this._subscribe('display:updateDisplaySize', ({ displaySize, prev }) => {
|
|
190
|
+
this._updateElementsScale(prev.displaySize, displaySize);
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
_updateElementsScale(oldValue, newValue) {
|
|
195
|
+
const oldWidth = oldValue.px;
|
|
196
|
+
const oldHeight = oldValue.px / oldValue.aspectRatio.split('/').map(Number).reduce((a, b) => a / b);
|
|
197
|
+
|
|
198
|
+
const newWidth = newValue.px;
|
|
199
|
+
const newHeight = newValue.px / newValue.aspectRatio.split('/').map(Number).reduce((a, b) => a / b);
|
|
200
|
+
|
|
201
|
+
nextTick(() => {
|
|
202
|
+
this.setContainerSize();
|
|
203
|
+
|
|
204
|
+
const normalizeElements = dataConverter.normalize(this.state.elements, oldWidth, oldHeight);
|
|
205
|
+
const denormalizedElements = dataConverter.denormalize(normalizeElements, newWidth, newHeight);
|
|
206
|
+
|
|
207
|
+
this.state.elements = cloneDeep(denormalizedElements);
|
|
208
|
+
|
|
209
|
+
this.eventBus.emit('system:requestUpdateElements', {
|
|
210
|
+
historyEvent: true,
|
|
211
|
+
elements: cloneDeep(this.state.elements),
|
|
212
|
+
timestamp: Date.now(),
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** ---------------------------------- layout Engine ---------------------------------- **/
|
|
218
|
+
/** [내부함수] layoutEngine 연결 설정 */
|
|
219
|
+
_setupLayoutEngineConnections() {
|
|
220
|
+
// layoutName 변경에 따른 activeSection 초기화
|
|
221
|
+
this._subscribe('layout:setLayoutName', ({ screenConfig }) => {
|
|
222
|
+
this._layoutData = screenConfig.layoutData;
|
|
223
|
+
this._dataUpdate = true;
|
|
224
|
+
this.setActiveSection('section1', true);
|
|
225
|
+
|
|
226
|
+
this.eventBus.emit('system:updateLayoutData', {
|
|
227
|
+
layoutData: cloneDeep(this._layoutData),
|
|
228
|
+
activeData: cloneDeep(this.activeData),
|
|
229
|
+
timestamp: Date.now(),
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
this._subscribe('layout:updateLayoutName', ({ layoutData }) => {
|
|
233
|
+
this._layoutData = layoutData;
|
|
234
|
+
this._dataUpdate = true;
|
|
235
|
+
this.setActiveSection(null, true);
|
|
236
|
+
|
|
237
|
+
this.eventBus.emit('system:updateLayoutData', {
|
|
238
|
+
layoutData: cloneDeep(this._layoutData),
|
|
239
|
+
activeData: cloneDeep(this.activeData),
|
|
240
|
+
timestamp: Date.now(),
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/** activeSection 변경
|
|
246
|
+
* @param {string} name - 섹션 이름
|
|
247
|
+
* @param {boolean} setting - 설정모드 여부
|
|
248
|
+
*/
|
|
249
|
+
setActiveSection(name, setting = false) {
|
|
250
|
+
if (!setting && this.state.activeSection === name) return;
|
|
251
|
+
|
|
252
|
+
this.state.activeSection = name;
|
|
253
|
+
this._dataUpdate = true;
|
|
254
|
+
|
|
255
|
+
this.eventBus.emit('system:updateActiveSection', {
|
|
256
|
+
activeSection: name,
|
|
257
|
+
activeData: cloneDeep(this.activeData),
|
|
258
|
+
timestamp: Date.now(),
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/** sectionMode 변경
|
|
263
|
+
* @param {string} mode - 섹션 모드('free' | 'grid')
|
|
264
|
+
*/
|
|
265
|
+
setSectionMode(mode) {
|
|
266
|
+
if (!['free', 'grid'].includes(mode)) {
|
|
267
|
+
console.error('[EngineManager] 잘못된 섹션 모드:', mode);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (!this.activeData || this.activeData.mode === mode) return;
|
|
272
|
+
const updates = {
|
|
273
|
+
mode: mode,
|
|
274
|
+
elementIds: this.state.elements.filter((x) => x.mode === mode).map((x) => x.id),
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
this._updateActiveSectionConfig('mode', updates);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/** sectionConfig 변경
|
|
281
|
+
* @param {string} type - config 타입 (column, row, gap, guideLine)
|
|
282
|
+
* @param {number|boolean} config - 새로운 설정 값
|
|
283
|
+
*/
|
|
284
|
+
setSectionConfig(type, config) {
|
|
285
|
+
if (!['column', 'row', 'gap', 'guideLine'].includes(type)) {
|
|
286
|
+
console.error('[EngineManager] 잘못된 config 타입:', type);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (!this.activeData) return;
|
|
291
|
+
|
|
292
|
+
const limits = {
|
|
293
|
+
column: { min: 1, max: 12, name: '열 개수' },
|
|
294
|
+
row: { min: 1, max: 50, name: '행 개수' },
|
|
295
|
+
gap: { min: 0, max: 12, name: '갭 크기' },
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
let newConfig = {};
|
|
299
|
+
let value = config;
|
|
300
|
+
|
|
301
|
+
if (type === 'guideLine') {
|
|
302
|
+
newConfig = { showGuideLine: value };
|
|
303
|
+
} else {
|
|
304
|
+
if (value < limits[type].min) {
|
|
305
|
+
alert(`설정 가능한 최소 ${limits[type].name}는 ${limits[type].min}입니다.`);
|
|
306
|
+
}
|
|
307
|
+
if (value > limits[type].max) {
|
|
308
|
+
alert(`설정 가능한 최대 ${limits[type].name}는 ${limits[type].max}입니다.`);
|
|
309
|
+
}
|
|
310
|
+
value = Math.min(Math.max(limits[type].min, value), limits[type].max);
|
|
311
|
+
|
|
312
|
+
if (type === 'column') {
|
|
313
|
+
newConfig = { gridColumns: value };
|
|
314
|
+
} else if (type === 'row') {
|
|
315
|
+
newConfig = { gridRows: value };
|
|
316
|
+
} else if (type === 'gap') {
|
|
317
|
+
newConfig = { gridGap: value };
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const updates = { config: { ...this.activeData.config, ...newConfig } };
|
|
322
|
+
this._updateActiveSectionConfig('config', updates);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/** ---------------------------------- freeDrop Engine ---------------------------------- **/
|
|
326
|
+
/** [내부함수] freeDropEngine 연결 설정 */
|
|
327
|
+
_setupFreeDropEngineConnections() {
|
|
328
|
+
// 배치요소 추가 요청
|
|
329
|
+
this._subscribe('freeDrop:requestAddElement', ({ element }) => {
|
|
330
|
+
this.state.elements.push(element);
|
|
331
|
+
this._updateActiveElementIds([...this.activeData.elementIds, element.id]);
|
|
332
|
+
|
|
333
|
+
this.eventBus.emit('system:requestUpdateData', {
|
|
334
|
+
action: 'add',
|
|
335
|
+
elements: this.state.elements,
|
|
336
|
+
elementId: element.id,
|
|
337
|
+
layoutData: cloneDeep(this._layoutData),
|
|
338
|
+
activeData: cloneDeep(this.activeData),
|
|
339
|
+
activeSection: this.state.activeSection,
|
|
340
|
+
timestamp: Date.now(),
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
// 배치요소 삭제 요청
|
|
345
|
+
this._subscribe('freeDrop:requestDeleteElement', ({ elementId }) => {
|
|
346
|
+
this.state.elements = this.state.elements.filter((el) => el.id !== elementId);
|
|
347
|
+
this._updateActiveElementIds(this.activeData.elementIds.filter((elId) => elId !== elementId));
|
|
348
|
+
|
|
349
|
+
this.eventBus.emit('system:requestUpdateData', {
|
|
350
|
+
action: 'delete',
|
|
351
|
+
elements: this.state.elements,
|
|
352
|
+
elementId: null,
|
|
353
|
+
layoutData: cloneDeep(this._layoutData),
|
|
354
|
+
activeData: cloneDeep(this.activeData),
|
|
355
|
+
activeSection: this.state.activeSection,
|
|
356
|
+
timestamp: Date.now(),
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
// 배치요소 위치 및 사이즈 변경 반영 요청
|
|
361
|
+
this._subscribe('freeDrop:requestUpdateElement', ({ historyEvent, elementId, position, size, guides }) => {
|
|
362
|
+
const element = this.state.elements.find((el) => el.id === elementId);
|
|
363
|
+
if (element) {
|
|
364
|
+
element.position = position;
|
|
365
|
+
element.size = size;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
this.eventBus.emit('system:requestUpdateElements', {
|
|
369
|
+
historyEvent: historyEvent,
|
|
370
|
+
elements: cloneDeep(this.state.elements),
|
|
371
|
+
guides: guides,
|
|
372
|
+
timestamp: Date.now(),
|
|
373
|
+
});
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/** ---------------------------------- gridDrop Engine ---------------------------------- **/
|
|
378
|
+
/** [내부함수] gridDropEngine 연결 설정 */
|
|
379
|
+
_setupGridDropEngineConnections() {
|
|
380
|
+
// 배치요소 추가 요청
|
|
381
|
+
this._subscribe('gridDrop:requestAddElement', ({ element }) => {
|
|
382
|
+
this.state.elements.push(element);
|
|
383
|
+
this._updateActiveElementIds([...this.activeData.elementIds, element.id]);
|
|
384
|
+
|
|
385
|
+
this.eventBus.emit('system:requestUpdateData', {
|
|
386
|
+
action: 'add',
|
|
387
|
+
elements: this.state.elements,
|
|
388
|
+
elementId: element.id,
|
|
389
|
+
layoutData: cloneDeep(this._layoutData),
|
|
390
|
+
activeData: cloneDeep(this.activeData),
|
|
391
|
+
activeSection: this.state.activeSection,
|
|
392
|
+
timestamp: Date.now(),
|
|
393
|
+
});
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
// 배치요소 삭제 요청
|
|
397
|
+
this._subscribe('gridDrop:requestDeleteElement', ({ elementId }) => {
|
|
398
|
+
this.state.elements = this.state.elements.filter((el) => el.id !== elementId);
|
|
399
|
+
this._updateActiveElementIds(this.activeData.elementIds.filter((elId) => elId !== elementId));
|
|
400
|
+
|
|
401
|
+
this.eventBus.emit('system:requestUpdateData', {
|
|
402
|
+
action: 'delete',
|
|
403
|
+
elements: this.state.elements,
|
|
404
|
+
elementId: null,
|
|
405
|
+
layoutData: cloneDeep(this._layoutData),
|
|
406
|
+
activeData: cloneDeep(this.activeData),
|
|
407
|
+
activeSection: this.state.activeSection,
|
|
408
|
+
timestamp: Date.now(),
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
// 배치요소 위치 및 사이즈 변경 요청
|
|
413
|
+
this._subscribe('gridDrop:requestUpdateElement', ({ historyEvent, elementId, position, size }) => {
|
|
414
|
+
|
|
415
|
+
const element = this.state.elements.find((el) => el.id === elementId);
|
|
416
|
+
if (element) {
|
|
417
|
+
element.position = position;
|
|
418
|
+
if (size) {
|
|
419
|
+
element.size = size;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
this.eventBus.emit('system:requestUpdateElements', {
|
|
424
|
+
historyEvent: historyEvent,
|
|
425
|
+
elements: cloneDeep(this.state.elements),
|
|
426
|
+
timestamp: Date.now(),
|
|
427
|
+
});
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/** ---------------------------------- history Engine ---------------------------------- **/
|
|
432
|
+
/** [내부함수] historyEngine 연결 설정 */
|
|
433
|
+
_setupHistoryEngineConnections() {}
|
|
434
|
+
|
|
435
|
+
/** ---------------------------------- 구독 관련 메소드 ---------------------------------- **/
|
|
436
|
+
/** [내부함수] 구독 및 unsubscribe 함수 저장
|
|
437
|
+
* @param {string} name - 이벤트 이름
|
|
438
|
+
* @param {function} handler - 이벤트 핸들러 함수
|
|
439
|
+
* @return {function} - unsubscribe 함수
|
|
440
|
+
*/
|
|
441
|
+
_subscribe(name, handler) {
|
|
442
|
+
const unsubscribe = this.eventBus.subscribe(name, handler);
|
|
443
|
+
this._subscriptions.push(unsubscribe);
|
|
444
|
+
return unsubscribe;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/** ---------------------------------- 엔진 초기화 메서드 ---------------------------------- **/
|
|
448
|
+
/** [내부함수] 엔진 초기화 */
|
|
449
|
+
async initialize() {
|
|
450
|
+
// 초기화 되지 않은 경우에만 초기화 수행
|
|
451
|
+
if (!this._initialized) {
|
|
452
|
+
await this._createEngines();
|
|
453
|
+
await this._setupEngineConnections();
|
|
454
|
+
this._initialized = true;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/** [내부함수] 엔진 인스턴스 생성 */
|
|
459
|
+
async _createEngines() {
|
|
460
|
+
try {
|
|
461
|
+
console.log('[EngineManager] 엔진 생성 시작');
|
|
462
|
+
|
|
463
|
+
const [
|
|
464
|
+
{ default: DisplayEngine },
|
|
465
|
+
{ default: LayoutEngine },
|
|
466
|
+
{ default: FreeDropEngine },
|
|
467
|
+
{ default: GridDropEngine },
|
|
468
|
+
{ default: HistoryEngine },
|
|
469
|
+
] = await Promise.all([
|
|
470
|
+
import('../../engines/DisplayEngine.js'),
|
|
471
|
+
import('../../engines/LayoutEngine.js'),
|
|
472
|
+
import('../../engines/FreeDropEngine.js'),
|
|
473
|
+
import('../../engines/GridDropEngine.js'),
|
|
474
|
+
import('../../engines/HistoryEngine.js'),
|
|
475
|
+
]);
|
|
476
|
+
this.addEngine('display', DisplayEngine, {
|
|
477
|
+
eventBus: this.eventBus,
|
|
478
|
+
options: {
|
|
479
|
+
resource: resource.display,
|
|
480
|
+
},
|
|
481
|
+
});
|
|
482
|
+
this.addEngine('layout', LayoutEngine, {
|
|
483
|
+
eventBus: this.eventBus,
|
|
484
|
+
options: {
|
|
485
|
+
resource: resource.layout,
|
|
486
|
+
},
|
|
487
|
+
});
|
|
488
|
+
this.addEngine('freeDrop', FreeDropEngine, {
|
|
489
|
+
eventBus: this.eventBus,
|
|
490
|
+
options: {
|
|
491
|
+
gridSize: this.state.gridSize,
|
|
492
|
+
resource: resource.freedrop,
|
|
493
|
+
},
|
|
494
|
+
});
|
|
495
|
+
this.addEngine('gridDrop', GridDropEngine, {
|
|
496
|
+
eventBus: this.eventBus,
|
|
497
|
+
options: {
|
|
498
|
+
resource: resource.griddrop,
|
|
499
|
+
},
|
|
500
|
+
});
|
|
501
|
+
this.addEngine('history', HistoryEngine, {
|
|
502
|
+
eventBus: this.eventBus,
|
|
503
|
+
options: {
|
|
504
|
+
engines: this.engines,
|
|
505
|
+
manager: this,
|
|
506
|
+
},
|
|
507
|
+
});
|
|
508
|
+
console.log('[EngineManager] 엔진 생성 완료');
|
|
509
|
+
} catch (error) {
|
|
510
|
+
console.error('[EngineManager] 엔진 생성 중 오류 발생: ', error);
|
|
511
|
+
throw error;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/** [내부함수] 엔진 인스턴스 간 연결 설정 */
|
|
516
|
+
async _setupEngineConnections() {
|
|
517
|
+
this._setupDisplayEngineConnections();
|
|
518
|
+
this._setupLayoutEngineConnections();
|
|
519
|
+
this._setupFreeDropEngineConnections();
|
|
520
|
+
this._setupGridDropEngineConnections();
|
|
521
|
+
this._setupHistoryEngineConnections();
|
|
522
|
+
|
|
523
|
+
// 모든 연결 설정 완료 후 초기화 완료 이벤트 발행
|
|
524
|
+
this.eventBus.emit('system:engineInitialized', {
|
|
525
|
+
state: this.state,
|
|
526
|
+
timestamp: Date.now(),
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/** ---------------------------------- 엔진 관리 메소드 ---------------------------------- **/
|
|
531
|
+
/** 엔진 정보 불러오기
|
|
532
|
+
* @param {string} name - 엔진 이름
|
|
533
|
+
* @return {object} - 엔진 인스턴스
|
|
534
|
+
*/
|
|
535
|
+
getEngine(name) {
|
|
536
|
+
return this.engines.get(name);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/** 엔진 추가
|
|
540
|
+
* @param {string} name - 엔진 이름
|
|
541
|
+
* @param {class} engineClass - 엔진 클래스
|
|
542
|
+
* @param {object} config - 엔진 생성 옵션
|
|
543
|
+
* @return {boolean} - 추가 성공 여부
|
|
544
|
+
*/
|
|
545
|
+
addEngine(name, engineClass, config = {}) {
|
|
546
|
+
if (this.engines.has(name)) {
|
|
547
|
+
console.warn(`[EngineManager] 이미 존재하는 엔진 이름입니다: ${name}`);
|
|
548
|
+
return false;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
try {
|
|
552
|
+
const engine = new engineClass({
|
|
553
|
+
eventBus: this.eventBus,
|
|
554
|
+
...config,
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
this.engines.set(name, engine);
|
|
558
|
+
return true;
|
|
559
|
+
} catch (error) {
|
|
560
|
+
console.error(`[EngineManager] ${name} 엔진 추가 중 오류 발생`, error);
|
|
561
|
+
return false;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/** 엔진 제거
|
|
566
|
+
* @param {string} name - 엔진 이름
|
|
567
|
+
* @return {boolean} - 제거 성공 여부
|
|
568
|
+
*/
|
|
569
|
+
removeEngine(name) {
|
|
570
|
+
if (!this.engines.has(name)) {
|
|
571
|
+
console.warn(`[EngineManager] 존재하지 않는 엔진 이름입니다: ${name}`);
|
|
572
|
+
return false;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
try {
|
|
576
|
+
const engine = this.engines.get(name);
|
|
577
|
+
|
|
578
|
+
if (typeof engine.destroy === 'function') {
|
|
579
|
+
engine.destroy();
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
this.engines.delete(name);
|
|
583
|
+
console.log(`[EngineManager] ${name} 엔진이 제거되었습니다`);
|
|
584
|
+
return true;
|
|
585
|
+
} catch (error) {
|
|
586
|
+
console.error(`[EngineManager] ${name} 엔진 제거 중 오류 발생`, error);
|
|
587
|
+
return false;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/** 엔진 매니저 삭제 */
|
|
592
|
+
destroy() {
|
|
593
|
+
console.log('[EngineManager] 엔진 매니저 삭제 시작');
|
|
594
|
+
|
|
595
|
+
// 모든 구독 해제
|
|
596
|
+
this._subscriptions.forEach((unsubscribe) => {
|
|
597
|
+
if (typeof unsubscribe === 'function') {
|
|
598
|
+
unsubscribe();
|
|
599
|
+
}
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
this._subscriptions = [];
|
|
603
|
+
|
|
604
|
+
for (const [name, engine] of this.engines) {
|
|
605
|
+
try {
|
|
606
|
+
this.removeEngine(name);
|
|
607
|
+
} catch (error) {
|
|
608
|
+
console.error(`[EngineManager] ${name} 엔진 삭제 중 오류 발생`, error);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
this.engines.clear();
|
|
613
|
+
this._initialized = false;
|
|
614
|
+
|
|
615
|
+
this._dataUpdate = false;
|
|
616
|
+
this._layoutData = null;
|
|
617
|
+
this._activeData = null;
|
|
618
|
+
|
|
619
|
+
this.state = {};
|
|
620
|
+
this.eventBus = null;
|
|
621
|
+
|
|
622
|
+
console.log('[EngineManager] 엔진 매니저 삭제 완료');
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/** 엔진 초기화 */
|
|
626
|
+
resetEngines() {
|
|
627
|
+
this.state = {
|
|
628
|
+
...initialState,
|
|
629
|
+
...this.config.initialState,
|
|
630
|
+
};
|
|
631
|
+
|
|
632
|
+
this._dataUpdate = false;
|
|
633
|
+
this._layoutData = null;
|
|
634
|
+
this._activeData = null;
|
|
635
|
+
|
|
636
|
+
this.eventBus.emit('system:enginesReset', {
|
|
637
|
+
timestamp: Date.now(),
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/** ---------------------------------- 캡슐화 API 메소드 ---------------------------------- **/
|
|
642
|
+
/** 외부 접근용 API 반환
|
|
643
|
+
* @return {object} - 외부용 API 객체
|
|
644
|
+
*/
|
|
645
|
+
getAPI() {
|
|
646
|
+
const displayEngine = this.getEngine('display');
|
|
647
|
+
const layoutEngine = this.getEngine('layout');
|
|
648
|
+
const freeDropEngine = this.getEngine('freeDrop');
|
|
649
|
+
const gridDropEngine = this.getEngine('gridDrop');
|
|
650
|
+
const historyEngine = this.getEngine('history');
|
|
651
|
+
|
|
652
|
+
const api = {
|
|
653
|
+
eventBus: this.eventBus,
|
|
654
|
+
resetEngines: () => this.resetEngines(),
|
|
655
|
+
|
|
656
|
+
// 엔진별 상태 조회 메서드
|
|
657
|
+
getDisplayState: () => ({
|
|
658
|
+
activeMenu: displayEngine.activeMenu,
|
|
659
|
+
activeDesign: displayEngine.activeDesign,
|
|
660
|
+
displayMode: displayEngine.displayMode,
|
|
661
|
+
displaySize: cloneDeep(displayEngine.displaySize),
|
|
662
|
+
}),
|
|
663
|
+
|
|
664
|
+
getLayoutState: () => ({
|
|
665
|
+
layoutName: layoutEngine.layoutName,
|
|
666
|
+
layoutSource: layoutEngine.layoutSource,
|
|
667
|
+
elements: this.state.elements,
|
|
668
|
+
layoutData: layoutEngine.layoutData,
|
|
669
|
+
activeSection: this.state.activeSection,
|
|
670
|
+
activeData: this.activeData,
|
|
671
|
+
gapSize: layoutEngine.gapSize,
|
|
672
|
+
gridNumber: cloneDeep(layoutEngine.gridNumber),
|
|
673
|
+
gridRatio: cloneDeep(layoutEngine.gridRatio),
|
|
674
|
+
}),
|
|
675
|
+
|
|
676
|
+
getFreeDropState: () => ({
|
|
677
|
+
handles: freeDropEngine.getResourceByName('handles'),
|
|
678
|
+
}),
|
|
679
|
+
getGridDropState: () => ({}),
|
|
680
|
+
getHistoryState: () => ({}),
|
|
681
|
+
|
|
682
|
+
display: {
|
|
683
|
+
setActiveMenu: (name) => displayEngine.setActiveMenu(name),
|
|
684
|
+
setActiveDesign: (name) => displayEngine.setActiveDesign(name),
|
|
685
|
+
setDisplayMode: (mode) => displayEngine.setDisplayMode(mode),
|
|
686
|
+
setDisplaySize: (type, size) => displayEngine.setDisplaySize(type, size),
|
|
687
|
+
},
|
|
688
|
+
|
|
689
|
+
layout: {
|
|
690
|
+
// getLayoutName: () => layoutEngine.layoutName,
|
|
691
|
+
// getActiveData: () => this.activeData,
|
|
692
|
+
// getLayoutData: () => layoutEngine.layoutData,
|
|
693
|
+
getContainerSize: () => this.getContainerSize(),
|
|
694
|
+
setContainerSize: () => this.setContainerSize(),
|
|
695
|
+
|
|
696
|
+
setLayoutName: (name, userData = null, setting = false) => layoutEngine.setLayoutName(name, userData, setting),
|
|
697
|
+
setGapSize: (size) => layoutEngine.setGapSize(size),
|
|
698
|
+
setRatio: (type, index, ratio) => layoutEngine.setRatio(type, index, ratio),
|
|
699
|
+
// setUserLayoutData: (userData) => layoutEngine.setUserLayoutData(userData),
|
|
700
|
+
setActiveSection: (name) => this.setActiveSection(name),
|
|
701
|
+
updateActiveElement: (id, changes) => this.updateActiveElement(id, changes),
|
|
702
|
+
|
|
703
|
+
setElements: (elements) => this.setElements(elements),
|
|
704
|
+
setSectionMode: (mode) => this.setSectionMode(mode),
|
|
705
|
+
setSectionConfig: (type, config) => this.setSectionConfig(type, config),
|
|
706
|
+
},
|
|
707
|
+
|
|
708
|
+
freeDrop: {
|
|
709
|
+
// getFreeElements: () => freeDropEngine.freeElements,
|
|
710
|
+
getElementStyle: (element) => freeDropEngine.getElementStyle(element),
|
|
711
|
+
// getGuides: () => freeDropEngine.guides,
|
|
712
|
+
setActiveElement: (element, type) => freeDropEngine.setActiveElement(element, type),
|
|
713
|
+
|
|
714
|
+
addElement: (event, elName) => freeDropEngine.addElement(event, elName),
|
|
715
|
+
toggleLock: () => freeDropEngine.toggleLock(),
|
|
716
|
+
handleMouseDown: (event, id) => freeDropEngine.handleMouseDown(event, id),
|
|
717
|
+
startResize: (event, direction) => freeDropEngine.startResize(event, direction),
|
|
718
|
+
},
|
|
719
|
+
|
|
720
|
+
gridDrop: {
|
|
721
|
+
// getGridElements: () => gridDropEngine.gridElements,
|
|
722
|
+
getElementStyle: (type, cell) => gridDropEngine.getElementStyle(type, cell),
|
|
723
|
+
|
|
724
|
+
// setActiveCell: (type, cell) => gridDropEngine.setActiveCell(type, cell),
|
|
725
|
+
setActiveElement: (element, type) => gridDropEngine.setActiveElement(element, type),
|
|
726
|
+
detectHoverCell: (cell) => gridDropEngine.detectHoverCell(cell),
|
|
727
|
+
|
|
728
|
+
addElement: (elName) => gridDropEngine.addElement(elName),
|
|
729
|
+
toggleLock: () => gridDropEngine.toggleLock(),
|
|
730
|
+
startDrag: (event, id) => gridDropEngine.startDrag(event, id),
|
|
731
|
+
handleMouseDown: (event, id) => gridDropEngine.handleMouseDown(event, id),
|
|
732
|
+
startResize: (event, id) => gridDropEngine.startResize(event, id),
|
|
733
|
+
},
|
|
734
|
+
|
|
735
|
+
history: {
|
|
736
|
+
getHistoryState: () => historyEngine.historyState,
|
|
737
|
+
undo: () => historyEngine.undo(),
|
|
738
|
+
redo: () => historyEngine.redo(),
|
|
739
|
+
},
|
|
740
|
+
};
|
|
741
|
+
|
|
742
|
+
if (this.apiManager) {
|
|
743
|
+
api.userApi = {
|
|
744
|
+
logout: async () => this.apiManager.logout(),
|
|
745
|
+
save: async (data) => this.apiManager.save(data),
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// proxy 생성 함수
|
|
750
|
+
const createProxy = (api, path = []) => {
|
|
751
|
+
return new Proxy(api, {
|
|
752
|
+
get: (target, prop) => {
|
|
753
|
+
if (prop === 'isReady' && path.length === 0) {
|
|
754
|
+
return () => this._initialized;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
// 초기화 확인
|
|
758
|
+
if (path.length === 0 && !this._initialized) {
|
|
759
|
+
console.error('[EngineManager] 엔진이 초기화되지 않았습니다.');
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
const value = target[prop];
|
|
763
|
+
|
|
764
|
+
// 객체면 재귀적 proxy
|
|
765
|
+
if (typeof value === 'object' && value !== null) {
|
|
766
|
+
// EventBus는 직접 반환 (Proxy 적용하지 않음)
|
|
767
|
+
if (prop === 'eventBus') {
|
|
768
|
+
return value;
|
|
769
|
+
}
|
|
770
|
+
return createProxy(value, [...path, prop]);
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
if (typeof value === 'function') {
|
|
774
|
+
// 다른 메서드들은 EngineManager에 바인딩
|
|
775
|
+
return value.bind(this);
|
|
776
|
+
}
|
|
777
|
+
return value;
|
|
778
|
+
},
|
|
779
|
+
});
|
|
780
|
+
};
|
|
781
|
+
|
|
782
|
+
return createProxy(api);
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
export default EngineManager;
|
|
@@ -79,8 +79,11 @@ function useLayout(api) {
|
|
|
79
79
|
});
|
|
80
80
|
|
|
81
81
|
// elements 변경 감지
|
|
82
|
-
_subscribe('system:requestUpdateActiveElement', ({
|
|
83
|
-
state.elements
|
|
82
|
+
_subscribe('system:requestUpdateActiveElement', ({ elementId, element }) => {
|
|
83
|
+
const index = state.elements.findIndex((x) => x.id === elementId);
|
|
84
|
+
if (index !== -1) {
|
|
85
|
+
state.elements[index] = element; // 업데이트
|
|
86
|
+
}
|
|
84
87
|
});
|
|
85
88
|
|
|
86
89
|
_subscribe('system:setElements', ({ elements }) => {
|
|
File without changes
|
|
File without changes
|