polarvo-layout 1.0.68 → 1.0.70
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/Layout/FreeLayout.vue +1 -2
- package/src/components/Layout/GridLayout.vue +1 -0
- package/src/components/SideBar/ElementSideBar.vue +0 -2
- package/src/configs/resources/layoutResource.js +22 -10
- package/src/core/engines/DisplayEngine.js +5 -2
- package/src/core/engines/FreeDropEngine.js +18 -2
- package/src/core/engines/GridDropEngine.js +3 -2
- package/src/core/engines/LayoutEngine.js +1 -0
- package/src/core/managers/EngineManager.js +64 -44
- package/src/library/FreeDropLibrary.js +2 -2
- package/src/library/GridDropLibrary.js +2 -2
package/package.json
CHANGED
|
@@ -85,9 +85,8 @@ watch(
|
|
|
85
85
|
if (newId && activeElement.value?.section !== props.sectionKey) return;
|
|
86
86
|
|
|
87
87
|
if (!newId) {
|
|
88
|
-
console.l;
|
|
89
88
|
setActiveDesign(null);
|
|
90
|
-
setActiveMenu(null);
|
|
89
|
+
// setActiveMenu(null);
|
|
91
90
|
}
|
|
92
91
|
_isElementSwitching = true;
|
|
93
92
|
// selectedElement.value = structuredClone(toRaw(activeElement.value));
|
|
@@ -145,8 +145,6 @@ function buildCustomElements() {
|
|
|
145
145
|
function getElements() {
|
|
146
146
|
allElements.value.default = buildDefaultElements();
|
|
147
147
|
allElements.value.custom = buildCustomElements();
|
|
148
|
-
|
|
149
|
-
console.log("allElements.value.default", allElements.value);
|
|
150
148
|
}
|
|
151
149
|
|
|
152
150
|
onBeforeMount(() => {
|
|
@@ -15,9 +15,13 @@ const defaultConfig = {
|
|
|
15
15
|
},
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
-
const createSections = (count,
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
const createSections = (count, positions = {}) => {
|
|
19
|
+
return Object.fromEntries(
|
|
20
|
+
Array.from({ length: count }, (_, i) => {
|
|
21
|
+
const key = `section${i + 1}`;
|
|
22
|
+
return [key, { ...defaultConfig, layoutPosition: positions[key] }];
|
|
23
|
+
}),
|
|
24
|
+
);
|
|
21
25
|
};
|
|
22
26
|
|
|
23
27
|
// 기본 아이콘 import
|
|
@@ -34,49 +38,57 @@ export const layoutResource = (icons = {}) => ({
|
|
|
34
38
|
name: '전체화면',
|
|
35
39
|
columnNumber: 1,
|
|
36
40
|
rowNumber: 1,
|
|
37
|
-
sections: createSections(1),
|
|
41
|
+
sections: createSections(1, { section1: { col: 0, row: 0 } }),
|
|
38
42
|
icon: markRaw(icons.NoSplitIcon || NoSplitIcon),
|
|
39
43
|
},
|
|
40
44
|
TopBottomSplit: {
|
|
41
45
|
name: '상하 분할',
|
|
42
46
|
columnNumber: 1,
|
|
43
47
|
rowNumber: 2,
|
|
44
|
-
sections: createSections(2),
|
|
48
|
+
sections: createSections(2, { section1: { col: 0, row: 0 }, section2: { col: 0, row: 1 } }),
|
|
45
49
|
icon: markRaw(icons.TopBottomSplitIcon || TopBottomSplitIcon),
|
|
46
50
|
},
|
|
47
51
|
LeftRightSplit: {
|
|
48
52
|
name: '좌우 분할',
|
|
49
53
|
columnNumber: 2,
|
|
50
54
|
rowNumber: 1,
|
|
51
|
-
sections: createSections(2),
|
|
55
|
+
sections: createSections(2, { section1: { col: 0, row: 0 }, section2: { col: 1, row: 0 } }),
|
|
52
56
|
icon: markRaw(icons.LeftRightSplitIcon || LeftRightSplitIcon),
|
|
53
57
|
},
|
|
54
58
|
ThreeRowSplit: {
|
|
55
59
|
name: '수평 3분할',
|
|
56
60
|
columnNumber: 1,
|
|
57
61
|
rowNumber: 3,
|
|
58
|
-
sections: createSections(3),
|
|
62
|
+
sections: createSections(3, { section1: { col: 0, row: 0 }, section2: { col: 0, row: 1 }, section3: { col: 0, row: 2 } }),
|
|
59
63
|
icon: markRaw(icons.ThreeRowSplitIcon || ThreeRowSplitIcon),
|
|
60
64
|
},
|
|
61
65
|
ThreeColumnSplit: {
|
|
62
66
|
name: '수직 3분할',
|
|
63
67
|
columnNumber: 3,
|
|
64
68
|
rowNumber: 1,
|
|
65
|
-
sections: createSections(3),
|
|
69
|
+
sections: createSections(3, { section1: { col: 0, row: 0 }, section2: { col: 1, row: 0 }, section3: { col: 2, row: 0 } }),
|
|
66
70
|
icon: markRaw(icons.ThreeColumnSplitIcon || ThreeColumnSplitIcon),
|
|
67
71
|
},
|
|
68
72
|
MainBottomSplit: {
|
|
69
73
|
name: '메인-하단 분할',
|
|
70
74
|
columnNumber: 2,
|
|
71
75
|
rowNumber: 2,
|
|
72
|
-
sections: createSections(3
|
|
76
|
+
sections: createSections(3, {
|
|
77
|
+
section1: { col: 0, row: 0, colSpan: 2 },
|
|
78
|
+
section2: { col: 0, row: 1 },
|
|
79
|
+
section3: { col: 1, row: 1 },
|
|
80
|
+
}),
|
|
73
81
|
icon: markRaw(icons.MainBottomSplitIcon || MainBottomSplitIcon),
|
|
74
82
|
},
|
|
75
83
|
MainTopSplit: {
|
|
76
84
|
name: '메인-상단 분할',
|
|
77
85
|
columnNumber: 2,
|
|
78
86
|
rowNumber: 2,
|
|
79
|
-
sections: createSections(3
|
|
87
|
+
sections: createSections(3, {
|
|
88
|
+
section1: { col: 0, row: 0 },
|
|
89
|
+
section2: { col: 1, row: 0 },
|
|
90
|
+
section3: { col: 0, row: 1, colSpan: 2 },
|
|
91
|
+
}),
|
|
80
92
|
icon: markRaw(icons.MainTopSplitIcon || MainTopSplitIcon),
|
|
81
93
|
},
|
|
82
94
|
});
|
|
@@ -62,7 +62,9 @@ class DisplayEngine {
|
|
|
62
62
|
});
|
|
63
63
|
|
|
64
64
|
this._subscribe('freeDrop:setActiveElement', ({ action }) => {
|
|
65
|
-
if (
|
|
65
|
+
if (['update', 'sectionChange'].includes(action)) return;
|
|
66
|
+
if (action === 'click' && this.activeMenu === 'layout') return;
|
|
67
|
+
|
|
66
68
|
this.setActiveMenu(null);
|
|
67
69
|
this.setActiveDesign(null);
|
|
68
70
|
});
|
|
@@ -74,7 +76,8 @@ class DisplayEngine {
|
|
|
74
76
|
// });
|
|
75
77
|
|
|
76
78
|
this._subscribe('gridDrop:setActiveElement', ({ action }) => {
|
|
77
|
-
if (
|
|
79
|
+
if (['update', 'sectionChange'].includes(action)) return;
|
|
80
|
+
if (action === 'click' && this.activeMenu === 'layout') return;
|
|
78
81
|
this.setActiveMenu(null);
|
|
79
82
|
this.setActiveDesign(null);
|
|
80
83
|
});
|
|
@@ -89,7 +89,7 @@ class FreeDropEngine {
|
|
|
89
89
|
this._activeMode = $mode;
|
|
90
90
|
this._activeConfig = $config;
|
|
91
91
|
|
|
92
|
-
this.setActiveElement(null);
|
|
92
|
+
this.setActiveElement(null, 'sectionChange');
|
|
93
93
|
});
|
|
94
94
|
|
|
95
95
|
this._subscribe('system:setElements', ({ $elements }) => {
|
|
@@ -114,7 +114,7 @@ class FreeDropEngine {
|
|
|
114
114
|
this._elements = $elements;
|
|
115
115
|
this._elementsUpdate = true;
|
|
116
116
|
|
|
117
|
-
this.setActiveElement(this._elements.find((x) => x.id === $elementId));
|
|
117
|
+
this.setActiveElement(this._elements.find((x) => x.id === $elementId) ?? null);
|
|
118
118
|
|
|
119
119
|
this.eventBus.emit('freeDrop:requestUpdateData', {
|
|
120
120
|
elements: cloneDeep(this.freeElements),
|
|
@@ -578,6 +578,22 @@ class FreeDropEngine {
|
|
|
578
578
|
newW = this._snapToGrid(this._startSize.w + deltaX);
|
|
579
579
|
}
|
|
580
580
|
|
|
581
|
+
// 섹션 경계 제한 (반대쪽 끝은 고정한 채 넘친 만큼만 축소)
|
|
582
|
+
const layoutRect = this._getLayoutRect();
|
|
583
|
+
if (layoutRect) {
|
|
584
|
+
if (newY < 0) {
|
|
585
|
+
newH += newY;
|
|
586
|
+
newY = 0;
|
|
587
|
+
}
|
|
588
|
+
if (newX < 0) {
|
|
589
|
+
newW += newX;
|
|
590
|
+
newX = 0;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
newH = Math.round(Math.min(newH, layoutRect.height - newY));
|
|
594
|
+
newW = Math.round(Math.min(newW, layoutRect.width - newX));
|
|
595
|
+
}
|
|
596
|
+
|
|
581
597
|
// 최소 크기 제한
|
|
582
598
|
newW = Math.max(40, newW);
|
|
583
599
|
newH = Math.max(20, newH);
|
|
@@ -57,7 +57,7 @@ class GridDropEngine {
|
|
|
57
57
|
this._activeMode = $mode;
|
|
58
58
|
this._activeConfig = $config;
|
|
59
59
|
|
|
60
|
-
this.setActiveElement(null);
|
|
60
|
+
this.setActiveElement(null, 'sectionChange');
|
|
61
61
|
});
|
|
62
62
|
|
|
63
63
|
this._subscribe('system:setElements', ({ $elements }) => {
|
|
@@ -82,7 +82,8 @@ class GridDropEngine {
|
|
|
82
82
|
this._elements = $elements;
|
|
83
83
|
this._elementsUpdate = true;
|
|
84
84
|
|
|
85
|
-
this.setActiveElement(this._elements.find((x) => x.id === $elementId));
|
|
85
|
+
this.setActiveElement(this._elements.find((x) => x.id === $elementId) ?? null);
|
|
86
|
+
|
|
86
87
|
this.eventBus.emit('gridDrop:requestUpdateData', {
|
|
87
88
|
elements: cloneDeep(this.gridElements),
|
|
88
89
|
timestamp: Date.now(),
|
|
@@ -121,6 +121,7 @@ class EngineManager {
|
|
|
121
121
|
// layoutEngine
|
|
122
122
|
case 'layout': {
|
|
123
123
|
const prevRatio = this.getEngine('layout').gridRatio; // 복원 전 현재 비율
|
|
124
|
+
const prevLayoutData = this.state.layoutData; // 복원 전 layoutData (섹션 layoutPosition 비교용)
|
|
124
125
|
this.getEngine('layout')?.setState(state);
|
|
125
126
|
this.state.layoutData = state.layoutData;
|
|
126
127
|
this.setActiveSection(null, true);
|
|
@@ -129,7 +130,7 @@ class EngineManager {
|
|
|
129
130
|
prevRatio.column.length !== state.gridRatio.column.length || prevRatio.column.some((v, i) => v !== state.gridRatio.column[i]);
|
|
130
131
|
|
|
131
132
|
if (ratioChanged) {
|
|
132
|
-
this._updateElementsScaleByRatio('column', prevRatio, state.gridRatio);
|
|
133
|
+
this._updateElementsScaleByRatio('column', prevRatio, state.gridRatio, prevLayoutData);
|
|
133
134
|
}
|
|
134
135
|
break;
|
|
135
136
|
}
|
|
@@ -249,6 +250,22 @@ class EngineManager {
|
|
|
249
250
|
...safeChanges,
|
|
250
251
|
};
|
|
251
252
|
|
|
253
|
+
// free 모드 요소의 크기 직접 입력 시, 섹션 경계를 넘지 않도록 제한
|
|
254
|
+
if (updatedElement.mode === 'free' && safeChanges.size) {
|
|
255
|
+
const layoutEl = document.getElementById(`${updatedElement.section}Layout`);
|
|
256
|
+
if (layoutEl) {
|
|
257
|
+
const { width, height } = layoutEl.getBoundingClientRect();
|
|
258
|
+
const maxW = width - updatedElement.position.x;
|
|
259
|
+
const maxH = height - updatedElement.position.y;
|
|
260
|
+
|
|
261
|
+
updatedElement.size = {
|
|
262
|
+
...updatedElement.size,
|
|
263
|
+
w: Math.round(Math.max(40, Math.min(updatedElement.size.w, maxW))),
|
|
264
|
+
h: Math.round(Math.max(20, Math.min(updatedElement.size.h, maxH))),
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
252
269
|
this.state.elements = [...this.state.elements.slice(0, index), updatedElement, ...this.state.elements.slice(index + 1)];
|
|
253
270
|
|
|
254
271
|
// 변경된 element만 전달
|
|
@@ -328,12 +345,14 @@ class EngineManager {
|
|
|
328
345
|
});
|
|
329
346
|
|
|
330
347
|
this._subscribe('layout:updateLayoutName', ({ $layoutData, $gridRatio, $prev }) => {
|
|
348
|
+
const prevLayoutData = this.state.layoutData; // 레이아웃 전환 전 layoutData (섹션 layoutPosition 비교용)
|
|
331
349
|
this.state.layoutData = $layoutData;
|
|
350
|
+
|
|
332
351
|
this.setActiveSection(null, true);
|
|
333
352
|
|
|
334
353
|
const prevGridRatio = $prev?.gridRatio || { column: [100], row: [100] };
|
|
335
|
-
this._updateElementsScaleByRatio('column', prevGridRatio, $gridRatio);
|
|
336
|
-
this._updateElementsScaleByRatio('row', prevGridRatio, $gridRatio);
|
|
354
|
+
this._updateElementsScaleByRatio('column', prevGridRatio, $gridRatio, prevLayoutData);
|
|
355
|
+
this._updateElementsScaleByRatio('row', prevGridRatio, $gridRatio, prevLayoutData);
|
|
337
356
|
|
|
338
357
|
nextTick(() => {
|
|
339
358
|
this.eventBus.emit('system:historyCheckpoint', {
|
|
@@ -361,50 +380,51 @@ class EngineManager {
|
|
|
361
380
|
});
|
|
362
381
|
}
|
|
363
382
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
383
|
+
/** [내부함수] gridRatio 변경에 따라 요소 position/size 재계산
|
|
384
|
+
* @param {string} type - 'column' | 'row'
|
|
385
|
+
* @param {object} oldValue - 변경 전 gridRatio ({ column, row })
|
|
386
|
+
* @param {object} newValue - 변경 후 gridRatio ({ column, row })
|
|
387
|
+
* @param {object} oldLayoutData - 변경 전 layoutData.
|
|
388
|
+
* 생략 시: 섹션 비율만 조정하는 경우
|
|
389
|
+
* 레이아웃 자체가 바뀌는 호출은 변경 전 layoutData를 넘겨야 함
|
|
390
|
+
*/
|
|
391
|
+
_updateElementsScaleByRatio(type, oldValue, newValue, oldLayoutData = this.state.layoutData) {
|
|
392
|
+
// 축(column, row)에 따라 positionKey, sizeKey 결정
|
|
393
|
+
const positionKey = type === 'column' ? 'x' : 'y';
|
|
394
|
+
const sizeKey = type === 'column' ? 'w' : 'h';
|
|
395
|
+
|
|
396
|
+
// 요소를 받아서 좌표, 크기에 대해 배율을 곱해 새 요소 객체 반환하는 헬퍼
|
|
397
|
+
// column이면 x, w만 수정 / row면 y, h만 수정
|
|
398
|
+
const scaleElement = (el, scaleFactor) => ({
|
|
399
|
+
...el,
|
|
400
|
+
position: { ...el.position, [positionKey]: el.position[positionKey] * scaleFactor },
|
|
401
|
+
size: { ...el.size, [sizeKey]: el.size[sizeKey] * scaleFactor },
|
|
402
|
+
});
|
|
367
403
|
|
|
368
|
-
|
|
404
|
+
// 섹션이 전체 너비/높이에서 차지하는 퍼센트 계산
|
|
405
|
+
const axisRatio = (ratios, pos) => {
|
|
406
|
+
const start = type === 'column' ? pos.col : pos.row; // 섹션이 시작하는 인덱스
|
|
407
|
+
const span = (type === 'column' ? pos.colSpan : pos.rowSpan) ?? 1; // 섹션이 차지하는 칸 수
|
|
408
|
+
return ratios.slice(start, start + span).reduce((sum, v) => sum + v, 0) / 100;
|
|
409
|
+
};
|
|
369
410
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
this.
|
|
374
|
-
|
|
375
|
-
// 열 또는 행 개수가 줄어드는 경우 - 요소 크기 늘리기
|
|
376
|
-
else if (oldLength > newLength) {
|
|
377
|
-
newDisplaySize.px = newDisplaySize.px / oldLength;
|
|
378
|
-
this._updateElementsScale(newDisplaySize, this._displaySize);
|
|
379
|
-
}
|
|
380
|
-
// 열 또는 행 개수는 동일하지만 비율이 변경되는 경우 - 요소 크기 조정
|
|
381
|
-
else {
|
|
382
|
-
this.state.elements = this.state.elements.map((el) => {
|
|
383
|
-
const sectionIndex = el.section ? parseInt(el.section.replace('section', '')) - 1 : 0;
|
|
384
|
-
const oldRatio = oldValue[type][sectionIndex] / 100;
|
|
385
|
-
const newRatio = newValue[type][sectionIndex] / 100;
|
|
386
|
-
|
|
387
|
-
const scaleFactor = newRatio / oldRatio;
|
|
388
|
-
return type === 'column'
|
|
389
|
-
? {
|
|
390
|
-
...el,
|
|
391
|
-
position: { ...el.position, x: el.position.x * scaleFactor },
|
|
392
|
-
size: { ...el.size, w: el.size.w * scaleFactor },
|
|
393
|
-
}
|
|
394
|
-
: {
|
|
395
|
-
...el,
|
|
396
|
-
position: { ...el.position, y: el.position.y * scaleFactor },
|
|
397
|
-
size: { ...el.size, h: el.size.h * scaleFactor },
|
|
398
|
-
};
|
|
399
|
-
});
|
|
411
|
+
|
|
412
|
+
this.state.elements = this.state.elements.map((el) => {
|
|
413
|
+
const oldPos = oldLayoutData?.[el.section]?.layoutPosition; // 변경 전 요소의 섹션 위치
|
|
414
|
+
const newPos = this.state.layoutData?.[el.section]?.layoutPosition; // 변경 후 요소의 섹션 위치
|
|
415
|
+
if (!oldPos || !newPos) return el;
|
|
400
416
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
417
|
+
const oldRatio = axisRatio(oldValue[type], oldPos);
|
|
418
|
+
if (!oldRatio) return el;
|
|
419
|
+
const newRatio = axisRatio(newValue[type], newPos);
|
|
420
|
+
|
|
421
|
+
return scaleElement(el, newRatio / oldRatio);
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
this.eventBus.emit('system:requestElementsScale', {
|
|
425
|
+
$elements: this.state.elements,
|
|
426
|
+
timestamp: Date.now(),
|
|
427
|
+
});
|
|
408
428
|
}
|
|
409
429
|
|
|
410
430
|
/** activeSection 변경
|
|
@@ -143,9 +143,9 @@ function useFreeDrop(api) {
|
|
|
143
143
|
}
|
|
144
144
|
};
|
|
145
145
|
|
|
146
|
-
const setActiveElement = (element,
|
|
146
|
+
const setActiveElement = (element, action) => {
|
|
147
147
|
try {
|
|
148
|
-
api.freeDrop.setActiveElement(element,
|
|
148
|
+
api.freeDrop.setActiveElement(element, action);
|
|
149
149
|
} catch (error) {
|
|
150
150
|
console.warn('[FreeDropLibrary] setActiveElement 실행 중 오류 발생:', error);
|
|
151
151
|
}
|
|
@@ -118,9 +118,9 @@ function useGridDrop(api) {
|
|
|
118
118
|
}
|
|
119
119
|
};
|
|
120
120
|
|
|
121
|
-
const setActiveElement = (element,
|
|
121
|
+
const setActiveElement = (element, action) => {
|
|
122
122
|
try {
|
|
123
|
-
api.gridDrop.setActiveElement(element,
|
|
123
|
+
api.gridDrop.setActiveElement(element, action);
|
|
124
124
|
} catch (error) {
|
|
125
125
|
console.warn('[GridDropLibrary] setActiveElement 실행 중 오류 발생:', error);
|
|
126
126
|
}
|