polarvo-layout 1.0.15 → 1.0.17
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/BaseLayout.vue +13 -6
- package/src/components/Layout/FreeLayout.vue +32 -8
- package/src/components/Layout/GridLayout copy.vue +187 -0
- package/src/components/Layout/GridLayout.vue +98 -70
- package/src/components/Layout/PolarLayout.vue +22 -2
- package/src/core/engines/DisplayEngine.js +9 -4
- package/src/core/engines/FreeDropEngine copy.js +758 -0
- package/src/core/engines/FreeDropEngine.js +77 -60
- package/src/core/engines/GridDropEngine copy.js +640 -0
- package/src/core/engines/GridDropEngine.js +73 -133
- package/src/core/managers/EngineManager.js +3 -2
- package/src/library/FreeDropLibrary.js +18 -11
- package/src/library/GridDropLibrary.js +28 -19
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div
|
|
3
3
|
:id="preview ? 'previewLayout' : 'baseLayout'"
|
|
4
|
-
class="grid h-full p-2 bg-transparent rounded-lg
|
|
4
|
+
class="grid h-full p-2 bg-transparent rounded-lg"
|
|
5
5
|
:class="[layoutType + '-layout', `grid-cols-${gridNumber?.column}`, 'mb-4' ? layoutType === 'three-column-split' : '']"
|
|
6
6
|
:style="getBaseStyle"
|
|
7
7
|
>
|
|
8
8
|
<template v-for="(section, key) in layoutData" :key="key">
|
|
9
9
|
<PolarLayout
|
|
10
10
|
:id="preview ? `${key}Preview` : `${key}Layout`"
|
|
11
|
+
:mode="section.mode"
|
|
11
12
|
:style="getSectionStyle(section)"
|
|
12
13
|
:class="[getSectionClass(key, section), activeSection === key && preview ? 'text-blue-400 bg-blue-50' : 'text-gray-400']"
|
|
13
14
|
@click.stop="emit('click:section', key)"
|
|
@@ -16,11 +17,19 @@
|
|
|
16
17
|
{{ key }}
|
|
17
18
|
</div>
|
|
18
19
|
<template v-if="section.mode === 'grid'">
|
|
19
|
-
<GridLayout
|
|
20
|
+
<GridLayout
|
|
21
|
+
:id="`grid-${key}`"
|
|
22
|
+
:polarvo="polarvo"
|
|
23
|
+
:preview="preview"
|
|
24
|
+
:sectionKey="key"
|
|
25
|
+
:sectionData="section"
|
|
26
|
+
:elementIds="section.elementIds"
|
|
27
|
+
/>
|
|
20
28
|
</template>
|
|
21
29
|
|
|
22
30
|
<template v-else>
|
|
23
31
|
<FreeLayout
|
|
32
|
+
:id="`free-${key}`"
|
|
24
33
|
:polarvo="polarvo"
|
|
25
34
|
:preview="preview"
|
|
26
35
|
:sectionKey="key"
|
|
@@ -31,7 +40,6 @@
|
|
|
31
40
|
</PolarLayout>
|
|
32
41
|
</template>
|
|
33
42
|
</div>
|
|
34
|
-
|
|
35
43
|
</template>
|
|
36
44
|
|
|
37
45
|
<script setup>
|
|
@@ -70,8 +78,8 @@ const { layoutName, layoutData, activeSection, gapSize, gridNumber, gridRatio }
|
|
|
70
78
|
|
|
71
79
|
const getBaseStyle = computed(() => {
|
|
72
80
|
return {
|
|
73
|
-
gridTemplateColumns: gridRatio.value?.column.map((ratio) => `${ratio}%`).join(' ')
|
|
74
|
-
gridTemplateRows: gridRatio.value?.row.map((ratio) => `${ratio}%`).join(' ')
|
|
81
|
+
gridTemplateColumns: gridRatio.value?.column.map((ratio) => `${ratio}%`).join(' '),
|
|
82
|
+
gridTemplateRows: gridRatio.value?.row.map((ratio) => `${ratio}%`).join(' '),
|
|
75
83
|
marginRight: layoutType.value === 'no-split' ? '0' : layoutType.value === 'three-column-split' ? '14px' : '8px',
|
|
76
84
|
gap: `${gapSize.value}px`,
|
|
77
85
|
};
|
|
@@ -89,7 +97,6 @@ const getSectionClass = (key, section) => ({
|
|
|
89
97
|
'section-active': activeSection.value === key,
|
|
90
98
|
disabled: activeSection.value != key && !props.preview,
|
|
91
99
|
});
|
|
92
|
-
|
|
93
100
|
</script>
|
|
94
101
|
|
|
95
102
|
<style scoped>
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
<div v-if="!preview">
|
|
3
3
|
<div
|
|
4
4
|
v-for="(item, index) in filteredElements"
|
|
5
|
-
:key="
|
|
5
|
+
:key="item.id"
|
|
6
6
|
:id="item.id"
|
|
7
7
|
class="absolute user-select-none"
|
|
8
8
|
:style="getElementStyle(item)"
|
|
9
9
|
:class="{ 'z-50': item.id === activeId, 'bg-white border-2 border-blue-400': item.id === activeId }"
|
|
10
|
-
@mousedown.stop="activeEditMode ? null :
|
|
10
|
+
@mousedown.stop="activeEditMode ? null : handleMouseDown($event, item.id)"
|
|
11
11
|
>
|
|
12
12
|
<!-- 리사이즈 핸들 -->
|
|
13
13
|
<div v-if="item.id === activeId" class="absolute inset-0 pointer-events-none">
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
|
|
38
38
|
<component
|
|
39
39
|
:is="dynamicComponent(item.type)"
|
|
40
|
-
:id="`${sectionKey}-${
|
|
40
|
+
:id="`${sectionKey}-${item.id}`"
|
|
41
41
|
:rootId="item.id"
|
|
42
42
|
:ref="(el) => setComponentRef(el, item.id)"
|
|
43
43
|
v-bind="item"
|
|
@@ -71,7 +71,17 @@
|
|
|
71
71
|
import LockIcon from '../../icons/action/LockIcon.vue';
|
|
72
72
|
import UnlockIcon from '../../icons/action/UnlockIcon.vue';
|
|
73
73
|
|
|
74
|
-
import {
|
|
74
|
+
import {
|
|
75
|
+
toRefs,
|
|
76
|
+
defineAsyncComponent,
|
|
77
|
+
markRaw,
|
|
78
|
+
getCurrentInstance,
|
|
79
|
+
onMounted,
|
|
80
|
+
onUnmounted,
|
|
81
|
+
inject,
|
|
82
|
+
watch,
|
|
83
|
+
computed,
|
|
84
|
+
} from 'vue';
|
|
75
85
|
|
|
76
86
|
const props = defineProps({
|
|
77
87
|
polarvo: {
|
|
@@ -99,13 +109,19 @@ const props = defineProps({
|
|
|
99
109
|
const { setActiveDesign } = props.polarvo.display;
|
|
100
110
|
const { updateActiveElement } = props.polarvo.layout;
|
|
101
111
|
const { elements, activeId, handles, guides, activeElement } = toRefs(props.polarvo.freeDrop.state);
|
|
102
|
-
const { getElementStyle, toggleLock,
|
|
112
|
+
const { getElementStyle, toggleLock, handleMouseDown, startResize } = props.polarvo.freeDrop;
|
|
103
113
|
const filteredElements = computed(() => {
|
|
104
114
|
return elements.value.filter((el) => props.elementIds?.includes(el.id) && el.section === props.sectionKey);
|
|
105
115
|
});
|
|
106
116
|
|
|
117
|
+
const _componentCache = new Map();
|
|
118
|
+
const modules = import.meta.glob('@/components/elements/*.vue');
|
|
119
|
+
|
|
107
120
|
function dynamicComponent(elName) {
|
|
108
|
-
|
|
121
|
+
if (_componentCache.has(elName)) {
|
|
122
|
+
return _componentCache.get(elName);
|
|
123
|
+
}
|
|
124
|
+
|
|
109
125
|
const matched = Object.keys(modules).find((path) => {
|
|
110
126
|
return path.includes(elName);
|
|
111
127
|
});
|
|
@@ -114,7 +130,9 @@ function dynamicComponent(elName) {
|
|
|
114
130
|
return markRaw(defineAsyncComponent(async () => ({ template: '<span style="display:none"></span>' })));
|
|
115
131
|
}
|
|
116
132
|
|
|
117
|
-
|
|
133
|
+
const component = markRaw(defineAsyncComponent(modules[matched]));
|
|
134
|
+
_componentCache.set(elName, component);
|
|
135
|
+
return component;
|
|
118
136
|
}
|
|
119
137
|
|
|
120
138
|
function setComponentRef(el, elementId) {
|
|
@@ -132,6 +150,7 @@ function toggleEditMode() {
|
|
|
132
150
|
activeEditMode.value = !activeEditMode.value;
|
|
133
151
|
}
|
|
134
152
|
|
|
153
|
+
let _isElementSwitching = false;
|
|
135
154
|
import { omit, cloneDeep } from 'lodash-es';
|
|
136
155
|
watch(
|
|
137
156
|
() => activeElement.value?.id,
|
|
@@ -140,6 +159,7 @@ watch(
|
|
|
140
159
|
if (!newId) {
|
|
141
160
|
setActiveDesign(null);
|
|
142
161
|
}
|
|
162
|
+
_isElementSwitching = true;
|
|
143
163
|
// selectedElement.value = structuredClone(toRaw(activeElement.value));
|
|
144
164
|
selectedElement.value = cloneDeep(activeElement.value);
|
|
145
165
|
|
|
@@ -149,11 +169,15 @@ watch(
|
|
|
149
169
|
);
|
|
150
170
|
|
|
151
171
|
// 특정 키 값을 제외하고 변경 감지
|
|
152
|
-
const selectedElementForWatch = computed(() => omit(selectedElement.value, ['position', 'size', 'id']));
|
|
172
|
+
const selectedElementForWatch = computed(() => omit(selectedElement.value, ['mode', 'section', 'position', 'size', 'id']));
|
|
153
173
|
|
|
154
174
|
watch(
|
|
155
175
|
() => selectedElementForWatch.value,
|
|
156
176
|
(newElement) => {
|
|
177
|
+
if (_isElementSwitching) {
|
|
178
|
+
_isElementSwitching = false;
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
157
181
|
updateActiveElement(selectedElement.value?.id, newElement);
|
|
158
182
|
},
|
|
159
183
|
{ deep: true },
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<template v-if="!preview">
|
|
3
|
+
<div
|
|
4
|
+
v-for="(item, index) in filteredElements"
|
|
5
|
+
:key="item.id"
|
|
6
|
+
:id="item.id"
|
|
7
|
+
class="relative bg-gray-200"
|
|
8
|
+
:style="getElementStyle('element', item)"
|
|
9
|
+
:class="{ 'z-50': item.id === activeId, 'bg-white border-2 border-blue-400': item.id === activeId }"
|
|
10
|
+
@mousedown.stop="activeEditMode ? null : handleMouseDown($event, item)"
|
|
11
|
+
>
|
|
12
|
+
<!-- 리사이즈 핸들 -->
|
|
13
|
+
<div v-if="item.id === activeId">
|
|
14
|
+
<div class="absolute w-4 h-4 bottom-0 right-0 bg-blue-400 z-10 cursor-se-resize" @mousedown="startResize($event, item.id)"></div>
|
|
15
|
+
<div class="flex gap-1 m-1">
|
|
16
|
+
<!-- 잠금 토글-->
|
|
17
|
+
<div class="z-10 pointer-events-auto cursor-pointer" @click="toggleLock()">
|
|
18
|
+
<LockIcon v-if="item.isLocked" />
|
|
19
|
+
<UnlockIcon v-else />
|
|
20
|
+
</div>
|
|
21
|
+
<!-- 편집 토글-->
|
|
22
|
+
<div class="z-10 pointer-events-auto cursor-pointer">
|
|
23
|
+
<div v-if="item.type === 'inputForm'" @click="toggleEditMode">
|
|
24
|
+
<div v-if="activeEditMode" class="bg-red-100 text-red-400 px-2 py-1 text-sm rounded-md hover:opacity-80 animate-pulse">
|
|
25
|
+
편집모드 on
|
|
26
|
+
</div>
|
|
27
|
+
<div v-else class="bg-gray-100 px-2 py-1 text-sm rounded-md hover:bg-gray-200">편집모드 off</div>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<component
|
|
34
|
+
:is="dynamicComponent(item.type)"
|
|
35
|
+
:id="`${sectionKey}-${item.id}`"
|
|
36
|
+
:rootId="item.id"
|
|
37
|
+
:ref="(el) => setComponentRef(el, item.id)"
|
|
38
|
+
v-bind="item"
|
|
39
|
+
class="hover:opacity-80"
|
|
40
|
+
:class="activeEditMode ? `pointer-events-auto ` : `pointer-events-none`"
|
|
41
|
+
/>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<!-- 오버레이 -->
|
|
45
|
+
<div v-if="activeEditMode" class="absolute inset-0 bg-black opacity-50"></div>
|
|
46
|
+
|
|
47
|
+
<!-- 빈 그리드 설정 -->
|
|
48
|
+
<div
|
|
49
|
+
v-for="(cell, index) in emptyData[sectionKey]"
|
|
50
|
+
:key="`${cell.row}-${cell.col}`"
|
|
51
|
+
class="flex items-center justify-center bg-gray-100 border border-gray-300"
|
|
52
|
+
:style="getElementStyle('empty', cell)"
|
|
53
|
+
@mouseenter="detectHoverCell(cell)"
|
|
54
|
+
>
|
|
55
|
+
<div
|
|
56
|
+
class="text-sm w-8 h-8 rounded-full bg-gray-300 flex items-center justify-center text-gray-500 hover:bg-blue-200 hover:text-white transition-colors cursor-pointer"
|
|
57
|
+
@mousedown="handleMouseDown($event, cell)"
|
|
58
|
+
>
|
|
59
|
+
+
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
</template>
|
|
63
|
+
</template>
|
|
64
|
+
|
|
65
|
+
<script setup>
|
|
66
|
+
import LockIcon from '../../icons/action/LockIcon.vue';
|
|
67
|
+
import UnlockIcon from '../../icons/action/UnlockIcon.vue';
|
|
68
|
+
|
|
69
|
+
import {
|
|
70
|
+
toRefs,
|
|
71
|
+
ref,
|
|
72
|
+
defineAsyncComponent,
|
|
73
|
+
markRaw,
|
|
74
|
+
getCurrentInstance,
|
|
75
|
+
onMounted,
|
|
76
|
+
onUnmounted,
|
|
77
|
+
inject,
|
|
78
|
+
watch,
|
|
79
|
+
computed,
|
|
80
|
+
} from 'vue';
|
|
81
|
+
const emit = defineEmits(['click:element']);
|
|
82
|
+
const props = defineProps({
|
|
83
|
+
polarvo: {
|
|
84
|
+
type: Object,
|
|
85
|
+
required: true,
|
|
86
|
+
},
|
|
87
|
+
preview: {
|
|
88
|
+
type: Boolean,
|
|
89
|
+
default: false,
|
|
90
|
+
},
|
|
91
|
+
sectionKey: {
|
|
92
|
+
type: String,
|
|
93
|
+
required: true,
|
|
94
|
+
},
|
|
95
|
+
elementIds: {
|
|
96
|
+
type: Array,
|
|
97
|
+
required: true,
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const { setActiveDesign } = props.polarvo.display;
|
|
102
|
+
const { updateActiveElement } = props.polarvo.layout;
|
|
103
|
+
const { elements, emptyData, activeId, activeCell, activeElement } = toRefs(props.polarvo.gridDrop.state);
|
|
104
|
+
const { getElementStyle, setActiveElement, toggleLock, startDrag, handleMouseDown, detectHoverCell, startResize } = props.polarvo.gridDrop;
|
|
105
|
+
const filteredElements = computed(() => {
|
|
106
|
+
return elements.value.filter((el) => props.elementIds?.includes(el.id) && el.section === props.sectionKey);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const _componentCache = new Map();
|
|
110
|
+
const modules = import.meta.glob('@/components/elements/*.vue');
|
|
111
|
+
|
|
112
|
+
function dynamicComponent(elName) {
|
|
113
|
+
if (_componentCache.has(elName)) {
|
|
114
|
+
return _componentCache.get(elName);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const matched = Object.keys(modules).find((path) => {
|
|
118
|
+
return path.includes(elName);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
if (!matched) {
|
|
122
|
+
return markRaw(defineAsyncComponent(async () => ({ template: '<span style="display:none"></span>' })));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const component = markRaw(defineAsyncComponent(modules[matched]));
|
|
126
|
+
_componentCache.set(elName, component);
|
|
127
|
+
return component;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function setComponentRef(el, elementId) {
|
|
131
|
+
if (el) {
|
|
132
|
+
props.polarvo.components.register('elements', `${props.sectionKey}-${elementId}`, el);
|
|
133
|
+
} else {
|
|
134
|
+
props.polarvo.components.unregister('elements', `${props.sectionKey}-${elementId}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const selectedElement = inject('selectedElement');
|
|
139
|
+
const activeEditMode = inject('activeEditMode');
|
|
140
|
+
|
|
141
|
+
function toggleEditMode() {
|
|
142
|
+
activeEditMode.value = !activeEditMode.value;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
let _isElementSwitching = false;
|
|
146
|
+
import { omit, cloneDeep } from 'lodash-es';
|
|
147
|
+
watch(
|
|
148
|
+
() => activeElement.value?.id,
|
|
149
|
+
(newId, oldId) => {
|
|
150
|
+
if (newId === oldId) return;
|
|
151
|
+
if (!newId) {
|
|
152
|
+
setActiveDesign(null);
|
|
153
|
+
}
|
|
154
|
+
_isElementSwitching = true;
|
|
155
|
+
// selectedElement.value = structuredClone(toRaw(activeElement.value));
|
|
156
|
+
selectedElement.value = cloneDeep(activeElement.value);
|
|
157
|
+
|
|
158
|
+
// activeElement 바뀌면 편집 모드 해제
|
|
159
|
+
activeEditMode.value = false;
|
|
160
|
+
},
|
|
161
|
+
{ immediate: true },
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
// 특정 키 값을 제외하고 변경 감지
|
|
165
|
+
const selectedElementForWatch = computed(() => omit(selectedElement.value, ['mode', 'section', 'position', 'size', 'id']));
|
|
166
|
+
|
|
167
|
+
watch(
|
|
168
|
+
() => selectedElementForWatch.value,
|
|
169
|
+
(newElement) => {
|
|
170
|
+
if (_isElementSwitching) {
|
|
171
|
+
_isElementSwitching = false;
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
updateActiveElement(selectedElement.value?.id, newElement);
|
|
175
|
+
},
|
|
176
|
+
{ deep: true },
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
onMounted(() => {
|
|
180
|
+
props.polarvo.components.register('sections', props.sectionKey, getCurrentInstance());
|
|
181
|
+
activeEditMode.value = false;
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
onUnmounted(() => {
|
|
185
|
+
props.polarvo.components.unregister('sections', props.sectionKey);
|
|
186
|
+
});
|
|
187
|
+
</script>
|
|
@@ -1,63 +1,66 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<template v-if="!preview">
|
|
3
3
|
<div
|
|
4
|
-
v-for="(item, index) in
|
|
5
|
-
:key="
|
|
6
|
-
:id="item.id"
|
|
7
|
-
|
|
8
|
-
:
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
v-for="(item, index) in mergedData"
|
|
5
|
+
:key="item.id ?? `${item.row}-${item.col}`"
|
|
6
|
+
:id="item.id ?? `${item.row}-${item.col}`"
|
|
7
|
+
:style="getElementStyle(item.id ? 'element' : 'empty', item)"
|
|
8
|
+
:class="
|
|
9
|
+
item.id
|
|
10
|
+
? ['relative bg-gray-200', { 'z-50 bg-white border-2 border-blue-400': item.id === activeId }]
|
|
11
|
+
: 'flex items-center justify-center bg-gray-100 border border-gray-300'
|
|
12
|
+
"
|
|
13
|
+
@mousedown="activeEditMode ? null : handleMouseDown($event, item)"
|
|
14
|
+
@mouseenter="item.id ? null : detectHoverCell(item)"
|
|
11
15
|
>
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
16
|
+
<template v-if="item.id">
|
|
17
|
+
<div>
|
|
18
|
+
<!-- 리사이즈 핸들 -->
|
|
19
|
+
<div v-if="item.id === activeId">
|
|
20
|
+
<div
|
|
21
|
+
class="absolute w-4 h-4 bottom-0 right-0 bg-blue-400 z-10 cursor-se-resize"
|
|
22
|
+
@mousedown="startResize($event, item.id)"
|
|
23
|
+
></div>
|
|
24
|
+
<div class="flex gap-1 m-1">
|
|
25
|
+
<!-- 잠금 토글-->
|
|
26
|
+
<div class="z-10 pointer-events-auto cursor-pointer" @click="toggleLock()">
|
|
27
|
+
<LockIcon v-if="item.isLocked" />
|
|
28
|
+
<UnlockIcon v-else />
|
|
29
|
+
</div>
|
|
30
|
+
<!-- 편집 토글-->
|
|
31
|
+
<div class="z-10 pointer-events-auto cursor-pointer">
|
|
32
|
+
<div v-if="item.type === 'inputForm'" @click="toggleEditMode">
|
|
33
|
+
<div v-if="activeEditMode" class="bg-red-100 text-red-400 px-2 py-1 text-sm rounded-md hover:opacity-80 animate-pulse">
|
|
34
|
+
편집모드 on
|
|
35
|
+
</div>
|
|
36
|
+
<div v-else class="bg-gray-100 px-2 py-1 text-sm rounded-md hover:bg-gray-200">편집모드 off</div>
|
|
37
|
+
</div>
|
|
26
38
|
</div>
|
|
27
|
-
<div v-else class="bg-gray-100 px-2 py-1 text-sm rounded-md hover:bg-gray-200">편집모드 off</div>
|
|
28
39
|
</div>
|
|
29
40
|
</div>
|
|
41
|
+
|
|
42
|
+
<component
|
|
43
|
+
:is="dynamicComponent(item.type)"
|
|
44
|
+
:id="`${sectionKey}-${item.id}`"
|
|
45
|
+
:rootId="item.id"
|
|
46
|
+
:ref="(el) => setComponentRef(el, item.id)"
|
|
47
|
+
v-bind="item"
|
|
48
|
+
class="hover:opacity-80"
|
|
49
|
+
:class="activeEditMode ? `pointer-events-auto ` : `pointer-events-none`"
|
|
50
|
+
/>
|
|
30
51
|
</div>
|
|
31
|
-
</div>
|
|
32
|
-
|
|
33
|
-
<component
|
|
34
|
-
:is="dynamicComponent(item.type)"
|
|
35
|
-
:id="`${sectionKey}-${index}`"
|
|
36
|
-
:rootId="item.id"
|
|
37
|
-
:ref="(el) => setComponentRef(el, item.id)"
|
|
38
|
-
v-bind="item"
|
|
39
|
-
class="hover:opacity-80"
|
|
40
|
-
:class="activeEditMode ? `pointer-events-auto ` : `pointer-events-none`"
|
|
41
|
-
/>
|
|
42
|
-
</div>
|
|
43
52
|
|
|
44
|
-
|
|
45
|
-
|
|
53
|
+
<!-- 오버레이 -->
|
|
54
|
+
<div v-if="activeEditMode && item.id !== activeId" class="absolute inset-0 bg-black opacity-50"></div>
|
|
55
|
+
</template>
|
|
46
56
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
>
|
|
55
|
-
<div
|
|
56
|
-
class="text-sm w-8 h-8 rounded-full bg-gray-300 flex items-center justify-center text-gray-500 hover:bg-blue-200 hover:text-white transition-colors cursor-pointer"
|
|
57
|
-
@click="setActiveElement({ position: cell }, 'click')"
|
|
58
|
-
>
|
|
59
|
-
+
|
|
60
|
-
</div>
|
|
57
|
+
<template v-else>
|
|
58
|
+
<div
|
|
59
|
+
class="text-sm w-8 h-8 rounded-full bg-gray-300 flex items-center justify-center text-gray-500 hover:bg-blue-200 hover:text-white transition-colors cursor-pointer"
|
|
60
|
+
>
|
|
61
|
+
+
|
|
62
|
+
</div>
|
|
63
|
+
</template>
|
|
61
64
|
</div>
|
|
62
65
|
</template>
|
|
63
66
|
</template>
|
|
@@ -66,19 +69,7 @@
|
|
|
66
69
|
import LockIcon from '../../icons/action/LockIcon.vue';
|
|
67
70
|
import UnlockIcon from '../../icons/action/UnlockIcon.vue';
|
|
68
71
|
|
|
69
|
-
import {
|
|
70
|
-
nextTick,
|
|
71
|
-
toRefs,
|
|
72
|
-
defineAsyncComponent,
|
|
73
|
-
markRaw,
|
|
74
|
-
getCurrentInstance,
|
|
75
|
-
onMounted,
|
|
76
|
-
onUnmounted,
|
|
77
|
-
inject,
|
|
78
|
-
watch,
|
|
79
|
-
toRaw,
|
|
80
|
-
computed,
|
|
81
|
-
} from 'vue';
|
|
72
|
+
import { toRefs, ref, defineAsyncComponent, markRaw, getCurrentInstance, onMounted, onUnmounted, inject, watch, computed } from 'vue';
|
|
82
73
|
const emit = defineEmits(['click:element']);
|
|
83
74
|
const props = defineProps({
|
|
84
75
|
polarvo: {
|
|
@@ -93,6 +84,10 @@ const props = defineProps({
|
|
|
93
84
|
type: String,
|
|
94
85
|
required: true,
|
|
95
86
|
},
|
|
87
|
+
sectionData: {
|
|
88
|
+
type: Object,
|
|
89
|
+
required: true,
|
|
90
|
+
},
|
|
96
91
|
elementIds: {
|
|
97
92
|
type: Array,
|
|
98
93
|
required: true,
|
|
@@ -101,14 +96,39 @@ const props = defineProps({
|
|
|
101
96
|
|
|
102
97
|
const { setActiveDesign } = props.polarvo.display;
|
|
103
98
|
const { updateActiveElement } = props.polarvo.layout;
|
|
104
|
-
const { elements,
|
|
105
|
-
const { getElementStyle, setActiveElement, toggleLock,
|
|
106
|
-
|
|
107
|
-
|
|
99
|
+
const { elements, activeId, activeCell, activeElement } = toRefs(props.polarvo.gridDrop.state);
|
|
100
|
+
const { getElementStyle, setActiveElement, toggleLock, handleMouseDown, detectHoverCell, startResize } = props.polarvo.gridDrop;
|
|
101
|
+
|
|
102
|
+
const mergedData = computed(() => {
|
|
103
|
+
const { gridColumns, gridRows } = props.sectionData?.config ?? { gridColumns: 3, gridRows: 3 };
|
|
104
|
+
const grid = Array.from({ length: gridRows + 1 }, () => Array(gridColumns + 1).fill(false));
|
|
105
|
+
|
|
106
|
+
const filled = elements.value.filter((el) => props.elementIds?.includes(el.id) && el.section === props.sectionKey);
|
|
107
|
+
|
|
108
|
+
filled.forEach(({ position: { col, row }, size: { colSpan, rowSpan } }) => {
|
|
109
|
+
for (let r = row; r < row + rowSpan; r++) {
|
|
110
|
+
for (let c = col; c < col + colSpan; c++) {
|
|
111
|
+
if (r >= 1 && c >= 1 && r <= gridRows && c <= gridColumns) {
|
|
112
|
+
grid[r][c] = true;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const empty = [];
|
|
119
|
+
for (let r = 1; r <= gridRows; r++) for (let c = 1; c <= gridColumns; c++) if (!grid[r][c]) empty.push({ row: r, col: c });
|
|
120
|
+
|
|
121
|
+
return [...filled, ...empty];
|
|
108
122
|
});
|
|
109
123
|
|
|
124
|
+
const _componentCache = new Map();
|
|
125
|
+
const modules = import.meta.glob('@/components/elements/*.vue');
|
|
126
|
+
|
|
110
127
|
function dynamicComponent(elName) {
|
|
111
|
-
|
|
128
|
+
if (_componentCache.has(elName)) {
|
|
129
|
+
return _componentCache.get(elName);
|
|
130
|
+
}
|
|
131
|
+
|
|
112
132
|
const matched = Object.keys(modules).find((path) => {
|
|
113
133
|
return path.includes(elName);
|
|
114
134
|
});
|
|
@@ -117,7 +137,9 @@ function dynamicComponent(elName) {
|
|
|
117
137
|
return markRaw(defineAsyncComponent(async () => ({ template: '<span style="display:none"></span>' })));
|
|
118
138
|
}
|
|
119
139
|
|
|
120
|
-
|
|
140
|
+
const component = markRaw(defineAsyncComponent(modules[matched]));
|
|
141
|
+
_componentCache.set(elName, component);
|
|
142
|
+
return component;
|
|
121
143
|
}
|
|
122
144
|
|
|
123
145
|
function setComponentRef(el, elementId) {
|
|
@@ -135,6 +157,7 @@ function toggleEditMode() {
|
|
|
135
157
|
activeEditMode.value = !activeEditMode.value;
|
|
136
158
|
}
|
|
137
159
|
|
|
160
|
+
let _isElementSwitching = false;
|
|
138
161
|
import { omit, cloneDeep } from 'lodash-es';
|
|
139
162
|
watch(
|
|
140
163
|
() => activeElement.value?.id,
|
|
@@ -143,6 +166,7 @@ watch(
|
|
|
143
166
|
if (!newId) {
|
|
144
167
|
setActiveDesign(null);
|
|
145
168
|
}
|
|
169
|
+
_isElementSwitching = true;
|
|
146
170
|
// selectedElement.value = structuredClone(toRaw(activeElement.value));
|
|
147
171
|
selectedElement.value = cloneDeep(activeElement.value);
|
|
148
172
|
|
|
@@ -153,11 +177,15 @@ watch(
|
|
|
153
177
|
);
|
|
154
178
|
|
|
155
179
|
// 특정 키 값을 제외하고 변경 감지
|
|
156
|
-
const selectedElementForWatch = computed(() => omit(selectedElement.value, ['position', 'size', 'id']));
|
|
180
|
+
const selectedElementForWatch = computed(() => omit(selectedElement.value, ['mode', 'section', 'position', 'size', 'id']));
|
|
157
181
|
|
|
158
182
|
watch(
|
|
159
183
|
() => selectedElementForWatch.value,
|
|
160
184
|
(newElement) => {
|
|
185
|
+
if (_isElementSwitching) {
|
|
186
|
+
_isElementSwitching = false;
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
161
189
|
updateActiveElement(selectedElement.value?.id, newElement);
|
|
162
190
|
},
|
|
163
191
|
{ deep: true },
|
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div id="polarLayout" class="w-full h-full rounded-md">
|
|
2
|
+
<div id="polarLayout" class="w-full h-full rounded-md" :class="mode === 'grid' ? 'polar-grid' : null">
|
|
3
3
|
<slot></slot>
|
|
4
4
|
</div>
|
|
5
|
-
</template>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script setup>
|
|
8
|
+
const props = defineProps({
|
|
9
|
+
mode: {
|
|
10
|
+
type: String,
|
|
11
|
+
default: 'free',
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<style scoped>
|
|
17
|
+
.polar-grid {
|
|
18
|
+
display: grid;
|
|
19
|
+
grid-template-columns: repeat(var(--grid-columns, 3), 1fr);
|
|
20
|
+
gap: var(--grid-gap, 5px);
|
|
21
|
+
width: 100%;
|
|
22
|
+
height: 100%;
|
|
23
|
+
border-radius: 0.375rem;
|
|
24
|
+
}
|
|
25
|
+
</style>
|
|
@@ -72,14 +72,19 @@ class DisplayEngine {
|
|
|
72
72
|
|
|
73
73
|
this._subscribe('gridDrop:setActiveElement', ({ action }) => {
|
|
74
74
|
if (action === 'update') return;
|
|
75
|
-
if (action === 'click') {
|
|
76
|
-
|
|
77
|
-
} else {
|
|
75
|
+
// if (action === 'click') {
|
|
76
|
+
// this.setActiveMenu('element');
|
|
77
|
+
// } else {
|
|
78
78
|
this.setActiveMenu(null);
|
|
79
|
-
}
|
|
79
|
+
// }
|
|
80
80
|
|
|
81
81
|
this.setActiveDesign(null);
|
|
82
82
|
});
|
|
83
|
+
|
|
84
|
+
this._subscribe('gridDrop:requestOpenElementBar', () => {
|
|
85
|
+
this.setActiveMenu('element');
|
|
86
|
+
this.setActiveDesign(null);
|
|
87
|
+
})
|
|
83
88
|
}
|
|
84
89
|
|
|
85
90
|
/** 삭제 및 정리 */
|