polarvo-layout 1.0.23 → 1.0.25
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/FastMenu/DesignFastmenu.vue +0 -1
- package/src/components/Layout/FreeItem.vue +169 -0
- package/src/components/Layout/FreeLayout.vue +9 -162
- package/src/components/Layout/GridItem.vue +108 -0
- package/src/components/Layout/GridLayout.vue +12 -98
- package/src/components/Layout/{GridLayout copy.vue → originals/FreeLayout_0416.vue} +125 -28
- package/src/components/Layout/originals/GridLayout_0416.vue +202 -0
- package/src/core/engines/FreeDropEngine.js +38 -13
- package/src/core/engines/GridDropEngine.js +5 -5
- package/src/core/engines/originals/{FreeDropEngine_0402.js → FreeDropEngine_0416.js} +44 -45
- package/src/core/managers/EngineManager.js +8 -2
- package/src/library/FreeDropLibrary.js +15 -3
- package/src/library/originals/{FreeDropLibrary_0402.js → FreeDropLibrary_0416.js} +7 -4
- package/src/core/engines/originals/FreeDropEngine copy.js +0 -758
- package/src/core/engines/originals/GridDropEngine copy.js +0 -640
- package/src/core/managers/originals/EngineManager copy.js +0 -786
package/package.json
CHANGED
|
@@ -52,7 +52,6 @@ const selectedElement = inject('selectedElement');
|
|
|
52
52
|
// const emits = defineEmits(['click:designBar']);
|
|
53
53
|
function clickDesignBar(designMenu) {
|
|
54
54
|
if (designMenu == 'option') {
|
|
55
|
-
// 테스트용 -> inputForm만 남기고 추후 삭제 예정
|
|
56
55
|
designMenu = activeElement.value?.type === 'inputForm' ? 'option-form' : 'option-default';
|
|
57
56
|
}
|
|
58
57
|
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
:id="item.id"
|
|
4
|
+
class="absolute user-select-none"
|
|
5
|
+
:style="itemStyle"
|
|
6
|
+
:class="{ 'z-50': item.id === activeId, 'bg-white border-2 border-blue-400': item.id === activeId }"
|
|
7
|
+
@mousedown.stop="activeEditMode ? null : handleMouseDown($event, item.id)"
|
|
8
|
+
>
|
|
9
|
+
|
|
10
|
+
<!-- 리사이즈 핸들 -->
|
|
11
|
+
<div v-if="item.id === activeId" class="absolute inset-0 pointer-events-none">
|
|
12
|
+
<div
|
|
13
|
+
v-for="handle in handles"
|
|
14
|
+
:key="handle.name"
|
|
15
|
+
:class="`handle absolute bg-blue-400 z-10 pointer-events-auto hover:bg-blue-700 ${handle.name}`"
|
|
16
|
+
@mousedown="startResize($event, handle.name)"
|
|
17
|
+
></div>
|
|
18
|
+
<div class="flex flex-row m-1 gap-1">
|
|
19
|
+
<!-- 잠금 토글-->
|
|
20
|
+
<div class="z-10 pointer-events-auto cursor-pointer" @click="toggleLock()">
|
|
21
|
+
<LockIcon v-if="item.isLocked" />
|
|
22
|
+
<UnlockIcon v-else />
|
|
23
|
+
</div>
|
|
24
|
+
<!-- 편집 토글-->
|
|
25
|
+
<div class="z-10 pointer-events-auto cursor-pointer">
|
|
26
|
+
<div v-if="item.type === 'inputForm'" @click="toggleEditMode">
|
|
27
|
+
<div v-if="activeEditMode" class="bg-red-100 text-red-400 px-2 py-1 text-sm rounded-md hover:opacity-80 animate-pulse">
|
|
28
|
+
편집모드 on
|
|
29
|
+
</div>
|
|
30
|
+
<div v-else class="bg-gray-100 px-2 py-1 text-sm rounded-md hover:bg-gray-200">편집모드 off</div>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<component
|
|
37
|
+
:is="dynamicComponent"
|
|
38
|
+
:id="`${sectionKey}-${item.id}`"
|
|
39
|
+
:rootId="item.id"
|
|
40
|
+
:ref="setRef"
|
|
41
|
+
v-bind="item"
|
|
42
|
+
class="hover:opacity-80"
|
|
43
|
+
:class="activeEditMode ? `pointer-events-auto ` : `pointer-events-none`"
|
|
44
|
+
/>
|
|
45
|
+
</div>
|
|
46
|
+
</template>
|
|
47
|
+
|
|
48
|
+
<script setup>
|
|
49
|
+
import { computed, inject, toRefs, defineAsyncComponent, markRaw } from 'vue';
|
|
50
|
+
import LockIcon from '../../icons/action/LockIcon.vue';
|
|
51
|
+
import UnlockIcon from '../../icons/action/UnlockIcon.vue';
|
|
52
|
+
|
|
53
|
+
const props = defineProps({
|
|
54
|
+
item: { type: Object, required: true },
|
|
55
|
+
polarvo: { type: Object, required: true },
|
|
56
|
+
sectionKey: { type: String, required: true },
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const { activeId, handles } = toRefs(props.polarvo.freeDrop.state);
|
|
60
|
+
const { getElementStyle, handleMouseDown, startResize, toggleLock } = props.polarvo.freeDrop;
|
|
61
|
+
|
|
62
|
+
const activeEditMode = inject('activeEditMode');
|
|
63
|
+
function toggleEditMode() {
|
|
64
|
+
activeEditMode.value = !activeEditMode.value;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const itemStyle = computed(() => getElementStyle(props.item));
|
|
68
|
+
|
|
69
|
+
const _componentCache = new Map();
|
|
70
|
+
const _modules = import.meta.glob('@/components/elements/*.vue');
|
|
71
|
+
|
|
72
|
+
const dynamicComponent = computed(() => {
|
|
73
|
+
const elName = props.item.type;
|
|
74
|
+
if (_componentCache.has(elName)) return _componentCache.get(elName);
|
|
75
|
+
|
|
76
|
+
const matched = Object.keys(_modules).find((path) => path.includes(elName));
|
|
77
|
+
if (!matched) {
|
|
78
|
+
const fallback = markRaw(defineAsyncComponent(async () => ({ template: '<span style="display:none"></span>' })));
|
|
79
|
+
_componentCache.set(elName, fallback);
|
|
80
|
+
return fallback;
|
|
81
|
+
}
|
|
82
|
+
const component = markRaw(defineAsyncComponent(_modules[matched]));
|
|
83
|
+
_componentCache.set(elName, component);
|
|
84
|
+
return component;
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
function setRef(el) {
|
|
88
|
+
if (el) {
|
|
89
|
+
props.polarvo.components.register('elements', `${props.sectionKey}-${props.item.id}`, el);
|
|
90
|
+
} else {
|
|
91
|
+
props.polarvo.components.unregister('elements', `${props.sectionKey}-${props.item.id}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
</script>
|
|
95
|
+
|
|
96
|
+
<style lang="scss" scoped>
|
|
97
|
+
|
|
98
|
+
.handle {
|
|
99
|
+
// 모서리
|
|
100
|
+
&.nw {
|
|
101
|
+
top: -4px;
|
|
102
|
+
left: -4px;
|
|
103
|
+
width: 8px;
|
|
104
|
+
height: 8px;
|
|
105
|
+
cursor: nw-resize;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
&.ne {
|
|
109
|
+
top: -4px;
|
|
110
|
+
right: -4px;
|
|
111
|
+
width: 8px;
|
|
112
|
+
height: 8px;
|
|
113
|
+
cursor: ne-resize;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
&.sw {
|
|
117
|
+
bottom: -4px;
|
|
118
|
+
left: -4px;
|
|
119
|
+
width: 8px;
|
|
120
|
+
height: 8px;
|
|
121
|
+
cursor: sw-resize;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
&.se {
|
|
125
|
+
bottom: -4px;
|
|
126
|
+
right: -4px;
|
|
127
|
+
width: 8px;
|
|
128
|
+
height: 8px;
|
|
129
|
+
cursor: se-resize;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// 가장자리
|
|
133
|
+
&.n {
|
|
134
|
+
top: -4px;
|
|
135
|
+
left: 50%;
|
|
136
|
+
transform: translateX(-50%);
|
|
137
|
+
width: 8px;
|
|
138
|
+
height: 8px;
|
|
139
|
+
cursor: n-resize;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
&.s {
|
|
143
|
+
bottom: -4px;
|
|
144
|
+
left: 50%;
|
|
145
|
+
transform: translateX(-50%);
|
|
146
|
+
width: 8px;
|
|
147
|
+
height: 8px;
|
|
148
|
+
cursor: s-resize;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
&.w {
|
|
152
|
+
top: 50%;
|
|
153
|
+
left: -4px;
|
|
154
|
+
transform: translateY(-50%);
|
|
155
|
+
width: 8px;
|
|
156
|
+
height: 8px;
|
|
157
|
+
cursor: w-resize;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
&.e {
|
|
161
|
+
top: 50%;
|
|
162
|
+
right: -4px;
|
|
163
|
+
transform: translateY(-50%);
|
|
164
|
+
width: 8px;
|
|
165
|
+
height: 8px;
|
|
166
|
+
cursor: e-resize;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
</style>
|
|
@@ -1,50 +1,12 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div v-if="!preview">
|
|
3
|
-
<
|
|
3
|
+
<FreeItem
|
|
4
4
|
v-for="(item, index) in filteredElements"
|
|
5
5
|
:key="item.id"
|
|
6
|
-
:
|
|
7
|
-
|
|
8
|
-
:
|
|
9
|
-
|
|
10
|
-
@mousedown.stop="activeEditMode ? null : handleMouseDown($event, item.id)"
|
|
11
|
-
>
|
|
12
|
-
<!-- 리사이즈 핸들 -->
|
|
13
|
-
<div v-if="item.id === activeId" class="absolute inset-0 pointer-events-none">
|
|
14
|
-
<div
|
|
15
|
-
v-for="handle in handles"
|
|
16
|
-
:key="handle.name"
|
|
17
|
-
:class="`handle absolute bg-blue-400 z-10 pointer-events-auto hover:bg-blue-700 ${handle.name}`"
|
|
18
|
-
@mousedown="startResize($event, handle.name)"
|
|
19
|
-
></div>
|
|
20
|
-
<div class="flex flex-row m-1 gap-1">
|
|
21
|
-
<!-- 잠금 토글-->
|
|
22
|
-
<div class="z-10 pointer-events-auto cursor-pointer" @click="toggleLock()">
|
|
23
|
-
<LockIcon v-if="item.isLocked" />
|
|
24
|
-
<UnlockIcon v-else />
|
|
25
|
-
</div>
|
|
26
|
-
<!-- 편집 토글-->
|
|
27
|
-
<div class="z-10 pointer-events-auto cursor-pointer">
|
|
28
|
-
<div v-if="item.type === 'inputForm'" @click="toggleEditMode">
|
|
29
|
-
<div v-if="activeEditMode" class="bg-red-100 text-red-400 px-2 py-1 text-sm rounded-md hover:opacity-80 animate-pulse">
|
|
30
|
-
편집모드 on
|
|
31
|
-
</div>
|
|
32
|
-
<div v-else class="bg-gray-100 px-2 py-1 text-sm rounded-md hover:bg-gray-200">편집모드 off</div>
|
|
33
|
-
</div>
|
|
34
|
-
</div>
|
|
35
|
-
</div>
|
|
36
|
-
</div>
|
|
37
|
-
|
|
38
|
-
<component
|
|
39
|
-
:is="dynamicComponent(item.type)"
|
|
40
|
-
:id="`${sectionKey}-${item.id}`"
|
|
41
|
-
:rootId="item.id"
|
|
42
|
-
:ref="(el) => setComponentRef(el, item.id)"
|
|
43
|
-
v-bind="item"
|
|
44
|
-
class="hover:opacity-80"
|
|
45
|
-
:class="activeEditMode ? `pointer-events-auto ` : `pointer-events-none`"
|
|
46
|
-
/>
|
|
47
|
-
</div>
|
|
6
|
+
:item="item"
|
|
7
|
+
:polarvo="props.polarvo"
|
|
8
|
+
:sectionKey="props.sectionKey"
|
|
9
|
+
></FreeItem>
|
|
48
10
|
|
|
49
11
|
<!-- 오버레이 -->
|
|
50
12
|
<div v-if="activeEditMode" class="absolute inset-0 bg-black opacity-50"></div>
|
|
@@ -68,20 +30,9 @@
|
|
|
68
30
|
</template>
|
|
69
31
|
|
|
70
32
|
<script setup>
|
|
71
|
-
import
|
|
72
|
-
import UnlockIcon from '../../icons/action/UnlockIcon.vue';
|
|
33
|
+
import FreeItem from './FreeItem.vue';
|
|
73
34
|
|
|
74
|
-
import {
|
|
75
|
-
toRefs,
|
|
76
|
-
defineAsyncComponent,
|
|
77
|
-
markRaw,
|
|
78
|
-
getCurrentInstance,
|
|
79
|
-
onMounted,
|
|
80
|
-
onUnmounted,
|
|
81
|
-
inject,
|
|
82
|
-
watch,
|
|
83
|
-
computed,
|
|
84
|
-
} from 'vue';
|
|
35
|
+
import { toRefs, getCurrentInstance, onMounted, onUnmounted, inject, watch, computed } from 'vue';
|
|
85
36
|
|
|
86
37
|
const props = defineProps({
|
|
87
38
|
polarvo: {
|
|
@@ -108,48 +59,14 @@ const props = defineProps({
|
|
|
108
59
|
|
|
109
60
|
const { setActiveDesign } = props.polarvo.display;
|
|
110
61
|
const { updateActiveElement } = props.polarvo.layout;
|
|
111
|
-
const { elements,
|
|
112
|
-
const { getElementStyle, toggleLock, handleMouseDown, startResize } = props.polarvo.freeDrop;
|
|
62
|
+
const { elements, guides, activeElement } = toRefs(props.polarvo.freeDrop.state);
|
|
113
63
|
const filteredElements = computed(() => {
|
|
114
64
|
return elements.value.filter((el) => props.elementIds?.includes(el.id) && el.section === props.sectionKey);
|
|
115
65
|
});
|
|
116
66
|
|
|
117
|
-
const _componentCache = new Map();
|
|
118
|
-
const modules = import.meta.glob('@/components/elements/*.vue');
|
|
119
|
-
|
|
120
|
-
function dynamicComponent(elName) {
|
|
121
|
-
if (_componentCache.has(elName)) {
|
|
122
|
-
return _componentCache.get(elName);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const matched = Object.keys(modules).find((path) => {
|
|
126
|
-
return path.includes(elName);
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
if (!matched) {
|
|
130
|
-
return markRaw(defineAsyncComponent(async () => ({ template: '<span style="display:none"></span>' })));
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const component = markRaw(defineAsyncComponent(modules[matched]));
|
|
134
|
-
_componentCache.set(elName, component);
|
|
135
|
-
return component;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function setComponentRef(el, elementId) {
|
|
139
|
-
if (el) {
|
|
140
|
-
props.polarvo.components.register('elements', `${props.sectionKey}-${elementId}`, el);
|
|
141
|
-
} else {
|
|
142
|
-
props.polarvo.components.unregister('elements', `${props.sectionKey}-${elementId}`);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
67
|
const selectedElement = inject('selectedElement');
|
|
147
68
|
const activeEditMode = inject('activeEditMode');
|
|
148
69
|
|
|
149
|
-
function toggleEditMode() {
|
|
150
|
-
activeEditMode.value = !activeEditMode.value;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
70
|
let _isElementSwitching = false;
|
|
154
71
|
import { omit, cloneDeep } from 'lodash-es';
|
|
155
72
|
watch(
|
|
@@ -159,6 +76,7 @@ watch(
|
|
|
159
76
|
if (!newId) {
|
|
160
77
|
setActiveDesign(null);
|
|
161
78
|
}
|
|
79
|
+
|
|
162
80
|
_isElementSwitching = true;
|
|
163
81
|
// selectedElement.value = structuredClone(toRaw(activeElement.value));
|
|
164
82
|
selectedElement.value = cloneDeep(activeElement.value);
|
|
@@ -194,77 +112,6 @@ onUnmounted(() => {
|
|
|
194
112
|
</script>
|
|
195
113
|
|
|
196
114
|
<style lang="scss" scoped>
|
|
197
|
-
.handle {
|
|
198
|
-
// 모서리
|
|
199
|
-
&.nw {
|
|
200
|
-
top: -4px;
|
|
201
|
-
left: -4px;
|
|
202
|
-
width: 8px;
|
|
203
|
-
height: 8px;
|
|
204
|
-
cursor: nw-resize;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
&.ne {
|
|
208
|
-
top: -4px;
|
|
209
|
-
right: -4px;
|
|
210
|
-
width: 8px;
|
|
211
|
-
height: 8px;
|
|
212
|
-
cursor: ne-resize;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
&.sw {
|
|
216
|
-
bottom: -4px;
|
|
217
|
-
left: -4px;
|
|
218
|
-
width: 8px;
|
|
219
|
-
height: 8px;
|
|
220
|
-
cursor: sw-resize;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
&.se {
|
|
224
|
-
bottom: -4px;
|
|
225
|
-
right: -4px;
|
|
226
|
-
width: 8px;
|
|
227
|
-
height: 8px;
|
|
228
|
-
cursor: se-resize;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// 가장자리
|
|
232
|
-
&.n {
|
|
233
|
-
top: -4px;
|
|
234
|
-
left: 50%;
|
|
235
|
-
transform: translateX(-50%);
|
|
236
|
-
width: 8px;
|
|
237
|
-
height: 8px;
|
|
238
|
-
cursor: n-resize;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
&.s {
|
|
242
|
-
bottom: -4px;
|
|
243
|
-
left: 50%;
|
|
244
|
-
transform: translateX(-50%);
|
|
245
|
-
width: 8px;
|
|
246
|
-
height: 8px;
|
|
247
|
-
cursor: s-resize;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
&.w {
|
|
251
|
-
top: 50%;
|
|
252
|
-
left: -4px;
|
|
253
|
-
transform: translateY(-50%);
|
|
254
|
-
width: 8px;
|
|
255
|
-
height: 8px;
|
|
256
|
-
cursor: w-resize;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
&.e {
|
|
260
|
-
top: 50%;
|
|
261
|
-
right: -4px;
|
|
262
|
-
transform: translateY(-50%);
|
|
263
|
-
width: 8px;
|
|
264
|
-
height: 8px;
|
|
265
|
-
cursor: e-resize;
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
115
|
|
|
269
116
|
.guide {
|
|
270
117
|
&.vertical {
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
:id="item.id ?? `${item.row}-${item.col}`"
|
|
4
|
+
:class="
|
|
5
|
+
item.id
|
|
6
|
+
? ['relative bg-gray-200 user-select-none', { 'z-50 bg-white border-2 border-blue-400': item.id === activeId }]
|
|
7
|
+
: 'flex items-center justify-center bg-gray-100 border border-gray-300'
|
|
8
|
+
"
|
|
9
|
+
:style="itemStyle"
|
|
10
|
+
@mousedown.stop="activeEditMode ? null : handleMouseDown($event, item)"
|
|
11
|
+
@mouseenter="item.id ? null : detectHoverCell(item)"
|
|
12
|
+
>
|
|
13
|
+
<template v-if="item.id">
|
|
14
|
+
<!-- 리사이즈 핸들 -->
|
|
15
|
+
<div v-if="item.id === activeId" class="absolute inset-0 pointer-events-none">
|
|
16
|
+
<div
|
|
17
|
+
class="absolute w-4 h-4 bottom-0 right-0 bg-blue-400 z-10 pointer-events-auto cursor-se-resize"
|
|
18
|
+
@mousedown="startResize($event, item.id)"
|
|
19
|
+
></div>
|
|
20
|
+
<div class="flex gap-1 m-1">
|
|
21
|
+
<!-- 잠금 토글-->
|
|
22
|
+
<div class="z-10 pointer-events-auto cursor-pointer" @click="toggleLock()">
|
|
23
|
+
<LockIcon v-if="item.isLocked" />
|
|
24
|
+
<UnlockIcon v-else />
|
|
25
|
+
</div>
|
|
26
|
+
<!-- 편집 토글-->
|
|
27
|
+
<div class="z-10 pointer-events-auto cursor-pointer">
|
|
28
|
+
<div v-if="item.type === 'inputForm'" @click="toggleEditMode">
|
|
29
|
+
<div v-if="activeEditMode" class="bg-red-100 text-red-400 px-2 py-1 text-sm rounded-md hover:opacity-80 animate-pulse">
|
|
30
|
+
편집모드 on
|
|
31
|
+
</div>
|
|
32
|
+
<div v-else class="bg-gray-100 px-2 py-1 text-sm rounded-md hover:bg-gray-200">편집모드 off</div>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
<component
|
|
39
|
+
:is="dynamicComponent"
|
|
40
|
+
:id="`${sectionKey}-${item.id}`"
|
|
41
|
+
:rootId="item.id"
|
|
42
|
+
:ref="setRef"
|
|
43
|
+
v-bind="item"
|
|
44
|
+
class="hover:opacity-80"
|
|
45
|
+
:class="activeEditMode ? `pointer-events-auto ` : `pointer-events-none`"
|
|
46
|
+
/>
|
|
47
|
+
|
|
48
|
+
<!-- 오버레이 -->
|
|
49
|
+
<div v-if="activeEditMode && item.id !== activeId" class="absolute inset-0 bg-black opacity-50"></div>
|
|
50
|
+
</template>
|
|
51
|
+
|
|
52
|
+
<template v-else>
|
|
53
|
+
<div
|
|
54
|
+
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"
|
|
55
|
+
>
|
|
56
|
+
+
|
|
57
|
+
</div>
|
|
58
|
+
</template>
|
|
59
|
+
</div>
|
|
60
|
+
</template>
|
|
61
|
+
|
|
62
|
+
<script setup>
|
|
63
|
+
import { computed, inject, toRefs, defineAsyncComponent, markRaw } from 'vue';
|
|
64
|
+
import LockIcon from '../../icons/action/LockIcon.vue';
|
|
65
|
+
import UnlockIcon from '../../icons/action/UnlockIcon.vue';
|
|
66
|
+
|
|
67
|
+
const props = defineProps({
|
|
68
|
+
item: { type: Object, required: true },
|
|
69
|
+
polarvo: { type: Object, required: true },
|
|
70
|
+
sectionKey: { type: String, required: true },
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const { activeId } = toRefs(props.polarvo.gridDrop.state);
|
|
74
|
+
const { getElementStyle, toggleLock, handleMouseDown, detectHoverCell, startResize } = props.polarvo.gridDrop;
|
|
75
|
+
|
|
76
|
+
const activeEditMode = inject('activeEditMode');
|
|
77
|
+
function toggleEditMode() {
|
|
78
|
+
activeEditMode.value = !activeEditMode.value;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const itemStyle = computed(() => getElementStyle(props.item.id ? 'element' : 'empty', props.item));
|
|
82
|
+
|
|
83
|
+
const _componentCache = new Map();
|
|
84
|
+
const _modules = import.meta.glob('@/components/elements/*.vue');
|
|
85
|
+
|
|
86
|
+
const dynamicComponent = computed(() => {
|
|
87
|
+
const elName = props.item.type;
|
|
88
|
+
if (_componentCache.has(elName)) return _componentCache.get(elName);
|
|
89
|
+
|
|
90
|
+
const matched = Object.keys(_modules).find((path) => path.includes(elName));
|
|
91
|
+
if (!matched) {
|
|
92
|
+
const fallback = markRaw(defineAsyncComponent(async () => ({ template: '<span style="display:none"></span>' })));
|
|
93
|
+
_componentCache.set(elName, fallback);
|
|
94
|
+
return fallback;
|
|
95
|
+
}
|
|
96
|
+
const component = markRaw(defineAsyncComponent(_modules[matched]));
|
|
97
|
+
_componentCache.set(elName, component);
|
|
98
|
+
return component;
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
function setRef(el) {
|
|
102
|
+
if (el) {
|
|
103
|
+
props.polarvo.components.register('elements', `${props.sectionKey}-${props.item.id}`, el);
|
|
104
|
+
} else {
|
|
105
|
+
props.polarvo.components.unregister('elements', `${props.sectionKey}-${props.item.id}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
</script>
|
|
@@ -1,76 +1,23 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<template v-if="!preview">
|
|
3
|
-
<
|
|
3
|
+
<GridItem
|
|
4
4
|
v-for="(item, index) in mergedData"
|
|
5
|
-
:key="item.id
|
|
6
|
-
:
|
|
7
|
-
:
|
|
8
|
-
:
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
@mousedown="activeEditMode ? null : handleMouseDown($event, item)"
|
|
14
|
-
@mouseenter="item.id ? null : detectHoverCell(item)"
|
|
15
|
-
>
|
|
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>
|
|
38
|
-
</div>
|
|
39
|
-
</div>
|
|
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
|
-
/>
|
|
51
|
-
</div>
|
|
52
|
-
|
|
53
|
-
<!-- 오버레이 -->
|
|
54
|
-
<div v-if="activeEditMode && item.id !== activeId" class="absolute inset-0 bg-black opacity-50"></div>
|
|
55
|
-
</template>
|
|
56
|
-
|
|
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>
|
|
64
|
-
</div>
|
|
5
|
+
:key="item.id || `empty-${index}`"
|
|
6
|
+
:item="item"
|
|
7
|
+
:polarvo="props.polarvo"
|
|
8
|
+
:sectionKey="props.sectionKey"
|
|
9
|
+
></GridItem>
|
|
10
|
+
|
|
11
|
+
<!-- 오버레이 -->
|
|
12
|
+
<div v-if="activeEditMode" class="absolute inset-0 bg-black opacity-50"></div>
|
|
65
13
|
</template>
|
|
66
14
|
</template>
|
|
67
15
|
|
|
68
16
|
<script setup>
|
|
69
|
-
import
|
|
70
|
-
|
|
17
|
+
import GridItem from './GridItem.vue';
|
|
18
|
+
|
|
19
|
+
import { toRefs, getCurrentInstance, onMounted, onUnmounted, inject, watch, computed } from 'vue';
|
|
71
20
|
|
|
72
|
-
import { toRefs, ref, defineAsyncComponent, markRaw, getCurrentInstance, onMounted, onUnmounted, inject, watch, computed } from 'vue';
|
|
73
|
-
const emit = defineEmits(['click:element']);
|
|
74
21
|
const props = defineProps({
|
|
75
22
|
polarvo: {
|
|
76
23
|
type: Object,
|
|
@@ -121,42 +68,9 @@ const mergedData = computed(() => {
|
|
|
121
68
|
return [...filled, ...empty];
|
|
122
69
|
});
|
|
123
70
|
|
|
124
|
-
const _componentCache = new Map();
|
|
125
|
-
const modules = import.meta.glob('@/components/elements/*.vue');
|
|
126
|
-
|
|
127
|
-
function dynamicComponent(elName) {
|
|
128
|
-
if (_componentCache.has(elName)) {
|
|
129
|
-
return _componentCache.get(elName);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const matched = Object.keys(modules).find((path) => {
|
|
133
|
-
return path.includes(elName);
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
if (!matched) {
|
|
137
|
-
return markRaw(defineAsyncComponent(async () => ({ template: '<span style="display:none"></span>' })));
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
const component = markRaw(defineAsyncComponent(modules[matched]));
|
|
141
|
-
_componentCache.set(elName, component);
|
|
142
|
-
return component;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function setComponentRef(el, elementId) {
|
|
146
|
-
if (el) {
|
|
147
|
-
props.polarvo.components.register('elements', `${props.sectionKey}-${elementId}`, el);
|
|
148
|
-
} else {
|
|
149
|
-
props.polarvo.components.unregister('elements', `${props.sectionKey}-${elementId}`);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
71
|
const selectedElement = inject('selectedElement');
|
|
154
72
|
const activeEditMode = inject('activeEditMode');
|
|
155
73
|
|
|
156
|
-
function toggleEditMode() {
|
|
157
|
-
activeEditMode.value = !activeEditMode.value;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
74
|
let _isElementSwitching = false;
|
|
161
75
|
import { omit, cloneDeep } from 'lodash-es';
|
|
162
76
|
watch(
|