polarvo-layout 1.0.8 → 1.0.10
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/ActionFastMenu.vue +1 -0
- package/src/components/FastMenu/DesignFastMenu.vue +3 -2
- package/src/components/FastMenu/DisplayFastMenu.vue +2 -2
- package/src/components/FastMenu/LayoutFastMenu.vue +4 -4
- package/src/components/IconBar/IconBar.vue +30 -29
- package/src/components/Layout/BaseLayout.vue +1 -3
- package/src/components/Layout/CanvasContainer.vue +6 -6
- package/src/components/Layout/FreeLayout.vue +31 -7
- package/src/components/Layout/GridLayout.vue +31 -7
- package/src/core/engines/DisplayEngine.js +4 -0
- package/src/core/engines/FreeDropEngine.js +2 -2
- package/src/core/managers/EngineManager.js +34 -1
- package/src/icons/action/EditIcon.vue +52 -0
- package/src/icons/action/LockIcon.vue +1 -1
- package/src/icons/action/UnlockIcon.vue +1 -1
- package/src/icons/iconHolder.vue +37 -0
package/package.json
CHANGED
|
@@ -41,6 +41,7 @@ async function saveSetting() {
|
|
|
41
41
|
const finalElements = elements.value.filter((el) => elementIdsTotal.includes(el.id));
|
|
42
42
|
|
|
43
43
|
const { width, height } = containerSize.value || { width: 1920, height: 1080 };
|
|
44
|
+
|
|
44
45
|
// 요소 데이터 정규화
|
|
45
46
|
const normalizedElements = dataConverter.normalize(finalElements, width, height);
|
|
46
47
|
|
|
@@ -52,12 +52,13 @@ const selectedElement = inject('selectedElement');
|
|
|
52
52
|
// const emits = defineEmits(['click:designBar']);
|
|
53
53
|
function clickDesignBar(designMenu) {
|
|
54
54
|
if (designMenu == 'option') {
|
|
55
|
+
// 테스트용 -> inputForm만 남기고 추후 삭제 예정
|
|
55
56
|
designMenu = activeElement.value?.type === 'inputForm' ? 'option-form' : 'option-default';
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
// selectedElement의 props 기본값 세팅
|
|
59
60
|
if (selectedElement.value) {
|
|
60
|
-
setComponentDefaults(selectedElement.value);
|
|
61
|
+
// setComponentDefaults(selectedElement.value);
|
|
61
62
|
}
|
|
62
63
|
|
|
63
64
|
setActiveDesign(designMenu);
|
|
@@ -79,7 +80,7 @@ function setComponentDefaults(element) {
|
|
|
79
80
|
// selectedElement.value[key] = typeof propInfo.default === 'function' ? propInfo.default() : propInfo.default;
|
|
80
81
|
// }
|
|
81
82
|
// });
|
|
82
|
-
selectedElement.value = element;
|
|
83
|
+
// selectedElement.value = element;
|
|
83
84
|
|
|
84
85
|
}
|
|
85
86
|
</script>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<!-- editor - title left -->
|
|
3
3
|
<div id="fast-menu" class="flex items-center gap-4">
|
|
4
|
-
<div class="flex gap-2 text-gray-400">
|
|
4
|
+
<!-- <div class="flex gap-2 text-gray-400">
|
|
5
5
|
<font-awesome-icon
|
|
6
6
|
:icon="['fas', 'display']"
|
|
7
7
|
class="cursor-pointer icon hover:opacity-70 active:text-blue-600"
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
@click="setDisplayMode('mobile')"
|
|
16
16
|
/>
|
|
17
17
|
</div>
|
|
18
|
-
<div id="divider" class="w-px bg-gray-200 h-4"></div>
|
|
18
|
+
<div id="divider" class="w-px bg-gray-200 h-4"></div> -->
|
|
19
19
|
<div class="text-sm text-gray-400">
|
|
20
20
|
<input
|
|
21
21
|
type="number"
|
|
@@ -57,8 +57,8 @@
|
|
|
57
57
|
min="0"
|
|
58
58
|
max="12"
|
|
59
59
|
class="text-sm text-center w-6 border-b-2 border-white focus:outline-none focus:border-b-2 focus:border-blue-500"
|
|
60
|
-
:value="
|
|
61
|
-
@change="
|
|
60
|
+
:value="activeData?.config?.gridGap"
|
|
61
|
+
@change="setSectionConfig('gap', $event.target.value)"
|
|
62
62
|
/>
|
|
63
63
|
</div>
|
|
64
64
|
</div>
|
|
@@ -77,8 +77,8 @@ const props = defineProps({
|
|
|
77
77
|
},
|
|
78
78
|
});
|
|
79
79
|
|
|
80
|
-
const { activeSection, activeData
|
|
81
|
-
const {
|
|
80
|
+
const { activeSection, activeData } = toRefs(props.polarvo.layout.state);
|
|
81
|
+
const { setSectionMode, setSectionConfig } = props.polarvo.layout;
|
|
82
82
|
const sectionMode = computed(() => activeData.value?.mode);
|
|
83
83
|
</script>
|
|
84
84
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<aside id="icon-bar" class="flex flex-col items-center justify-between w-16 h-fit bg-white border-r border-gray-200">
|
|
3
3
|
<div class="icon-wrapper logo border-b border-gray-200 text-2xl text-blue-800 p-0 w-full" @click="openIconBar = !openIconBar">
|
|
4
|
-
<font-awesome-icon :icon="['fas', 'p']" class="h-16" />
|
|
4
|
+
<font-awesome-icon :icon="['fas', 'p']" class="h-16 text-xs" />
|
|
5
5
|
</div>
|
|
6
6
|
|
|
7
7
|
<!-- 아이콘바 활성화 -->
|
|
@@ -10,12 +10,12 @@
|
|
|
10
10
|
<div class="top-side flex flex-col items-center gap-2 mt-4">
|
|
11
11
|
<div class="icon-group">
|
|
12
12
|
<div
|
|
13
|
-
class="icon-wrapper
|
|
13
|
+
class="icon-wrapper text-2xl text-gray-400 p-2"
|
|
14
14
|
title="프로젝트 관리"
|
|
15
15
|
:class="{ active: activeMenu === 'home' }"
|
|
16
16
|
@click="$router.push('/console/home')"
|
|
17
17
|
>
|
|
18
|
-
<font-awesome-icon :icon="['fas', 'folder']" />
|
|
18
|
+
<font-awesome-icon :icon="['fas', 'folder']" class="text-xl"/>
|
|
19
19
|
<div class="text-xs text-center col-span-1 w-max">프로젝트</div>
|
|
20
20
|
</div>
|
|
21
21
|
</div>
|
|
@@ -26,75 +26,75 @@
|
|
|
26
26
|
<div class="icon-group">
|
|
27
27
|
<!-- 👂 기본설정에 테마설정 포함 -->
|
|
28
28
|
<div
|
|
29
|
-
class="icon-wrapper text-2xl text-gray-400 p-2
|
|
29
|
+
class="icon-wrapper text-2xl text-gray-400 p-2"
|
|
30
30
|
title="기본설정"
|
|
31
31
|
:class="{ active: activeMenu === 'projectSetting' }"
|
|
32
32
|
@click="polarvo.display.setActiveMenu('projectSetting')"
|
|
33
33
|
>
|
|
34
|
-
|
|
34
|
+
<font-awesome-icon :icon="['fas', 'screwdriver-wrench']" class="text-xl"/>
|
|
35
35
|
<div class="text-xs text-center col-span-1 w-max">기본설정</div>
|
|
36
36
|
</div>
|
|
37
37
|
<div
|
|
38
|
-
class="icon-wrapper text-2xl text-gray-400 p-2
|
|
38
|
+
class="icon-wrapper text-2xl text-gray-400 p-2"
|
|
39
39
|
title="정책설정"
|
|
40
40
|
:class="{ active: activeMenu === 'policySetting' }"
|
|
41
41
|
@click="polarvo.display.setActiveMenu('policySetting')"
|
|
42
42
|
>
|
|
43
|
-
<font-awesome-icon :icon="['fas', 'shield-halved']" />
|
|
43
|
+
<font-awesome-icon :icon="['fas', 'shield-halved']" class="text-xl"/>
|
|
44
44
|
<div class="text-xs text-center col-span-1 w-max">정책설정</div>
|
|
45
45
|
</div>
|
|
46
46
|
</div>
|
|
47
47
|
<div id="divider" class="h-px bg-gray-200 w-8"></div>
|
|
48
48
|
<div class="icon-group">
|
|
49
49
|
<div
|
|
50
|
-
class="icon-wrapper text-2xl text-gray-400 p-2
|
|
50
|
+
class="icon-wrapper text-2xl text-gray-400 p-2"
|
|
51
51
|
title="DB관리"
|
|
52
52
|
:class="{ active: activeMenu === 'dbSetting' }"
|
|
53
53
|
@click="polarvo.display.setActiveMenu('dbSetting')"
|
|
54
54
|
>
|
|
55
|
-
<font-awesome-icon :icon="['fas', 'database']" />
|
|
55
|
+
<font-awesome-icon :icon="['fas', 'database']" class="text-xl"/>
|
|
56
56
|
<div class="text-xs text-center col-span-1 w-max">DB관리</div>
|
|
57
57
|
</div>
|
|
58
58
|
<div
|
|
59
|
-
class="icon-wrapper text-2xl text-gray-400 p-2
|
|
60
|
-
title="화면관리"
|
|
61
|
-
:class="{ active: activeMenu === 'screenSetting' }"
|
|
62
|
-
@click="polarvo.display.setActiveMenu('screenSetting')"
|
|
63
|
-
>
|
|
64
|
-
<font-awesome-icon :icon="['far', 'window-restore']" />
|
|
65
|
-
<div class="text-xs text-center col-span-1 w-max">화면관리</div>
|
|
66
|
-
</div>
|
|
67
|
-
<div
|
|
68
|
-
class="icon-wrapper text-2xl text-gray-400 p-2 hover"
|
|
59
|
+
class="icon-wrapper text-2xl text-gray-400 p-2"
|
|
69
60
|
title="화면이동"
|
|
70
61
|
:class="{ active: activeMenu === 'screenMove' }"
|
|
71
62
|
@click="polarvo.display.setActiveMenu('screenMove')"
|
|
72
63
|
>
|
|
73
|
-
|
|
64
|
+
<font-awesome-icon :icon="['fas', 'boxes-packing']" class="text-xl"/>
|
|
74
65
|
<div class="text-xs text-center col-span-1 w-max">화면이동</div>
|
|
75
66
|
</div>
|
|
67
|
+
<div
|
|
68
|
+
class="icon-wrapper text-2xl text-gray-400 p-2"
|
|
69
|
+
title="화면관리"
|
|
70
|
+
:class="{ active: activeMenu === 'screenSetting' }"
|
|
71
|
+
@click="polarvo.display.setActiveMenu('screenSetting')"
|
|
72
|
+
>
|
|
73
|
+
<font-awesome-icon :icon="['fas', 'computer']" class="text-xl"/>
|
|
74
|
+
<div class="text-xs text-center col-span-1 w-max">화면관리</div>
|
|
75
|
+
</div>
|
|
76
76
|
</div>
|
|
77
77
|
<!-- 화면 선택 시 아이콘 그룹 -->
|
|
78
78
|
<template v-if="isScreen">
|
|
79
79
|
<div id="divider" class="h-px bg-gray-200 w-8"></div>
|
|
80
80
|
<div class="icon-group">
|
|
81
81
|
<div
|
|
82
|
-
class="icon-wrapper text-2xl text-gray-400 p-2
|
|
82
|
+
class="icon-wrapper text-2xl text-gray-400 p-2"
|
|
83
83
|
title="레이아웃"
|
|
84
84
|
:class="{ active: activeMenu === 'layout' }"
|
|
85
85
|
@click="polarvo.display.setActiveMenu('layout')"
|
|
86
86
|
>
|
|
87
|
-
<font-awesome-icon :icon="['fas', 'table-columns']" />
|
|
87
|
+
<font-awesome-icon :icon="['fas', 'table-columns']" class="text-xl"/>
|
|
88
88
|
<div class="text-xs text-center col-span-1 w-max">레이아웃</div>
|
|
89
89
|
</div>
|
|
90
90
|
<div
|
|
91
91
|
v-if="activeSection"
|
|
92
|
-
class="icon-wrapper text-2xl text-gray-400 p-2
|
|
92
|
+
class="icon-wrapper text-2xl text-gray-400 p-2"
|
|
93
93
|
title="요소"
|
|
94
94
|
:class="{ active: activeMenu === 'element' }"
|
|
95
95
|
@click="polarvo.display.setActiveMenu('element')"
|
|
96
96
|
>
|
|
97
|
-
<font-awesome-icon :icon="['fas', 'shapes']" />
|
|
97
|
+
<font-awesome-icon :icon="['fas', 'shapes']" class="text-xl"/>
|
|
98
98
|
<div class="text-xs text-center col-span-1 w-max">요소</div>
|
|
99
99
|
</div>
|
|
100
100
|
</div>
|
|
@@ -110,7 +110,7 @@
|
|
|
110
110
|
:class="{ active: activeMenu === 'setting' }"
|
|
111
111
|
@click="$router.push('/console/setting')"
|
|
112
112
|
>
|
|
113
|
-
<font-awesome-icon :icon="['fas', 'gear']" />
|
|
113
|
+
<font-awesome-icon :icon="['fas', 'gear']" class="text-xl"/>
|
|
114
114
|
<div class="text-xs text-center col-span-1 w-max">환경설정</div>
|
|
115
115
|
</div>
|
|
116
116
|
<!-- 계정설정 -->
|
|
@@ -121,7 +121,7 @@
|
|
|
121
121
|
title="계정설정"
|
|
122
122
|
@click="toggleUser = !toggleUser"
|
|
123
123
|
>
|
|
124
|
-
<font-awesome-icon :icon="['fas', 'user']" />
|
|
124
|
+
<font-awesome-icon :icon="['fas', 'user']" class="text-xl"/>
|
|
125
125
|
</div>
|
|
126
126
|
|
|
127
127
|
<!-- 계정설정 토글메뉴 -->
|
|
@@ -185,6 +185,7 @@ async function logout() {
|
|
|
185
185
|
<style scoped lang="scss">
|
|
186
186
|
.icon-wrapper {
|
|
187
187
|
aspect-ratio: 1 / 1; /* aspect-square */
|
|
188
|
+
width: 100%;
|
|
188
189
|
display: grid; /* grid */
|
|
189
190
|
grid-template-rows: 1fr auto; /* grid-rows-[1fr_auto] */
|
|
190
191
|
place-items: center; /* place-items-center */
|
|
@@ -193,7 +194,7 @@ async function logout() {
|
|
|
193
194
|
cursor: pointer; /* cursor-pointer */
|
|
194
195
|
|
|
195
196
|
&:hover:not(.logo):not(.active) {
|
|
196
|
-
width: 100%;
|
|
197
|
+
// width: 100%;
|
|
197
198
|
background-color: #f3f4f6; /* hover:bg-gray-100 */
|
|
198
199
|
border-radius: 0.5rem; /* rounded-lg */
|
|
199
200
|
color: #4b5563; /* text-gray-600 */
|
|
@@ -219,8 +220,8 @@ async function logout() {
|
|
|
219
220
|
display: flex;
|
|
220
221
|
flex-direction: column;
|
|
221
222
|
align-items: center;
|
|
222
|
-
gap: 0.
|
|
223
|
-
padding-
|
|
223
|
+
gap: 0.25rem; /* gap-1 */
|
|
224
|
+
padding-block: 0.5rem; /* py-2 */
|
|
224
225
|
}
|
|
225
226
|
|
|
226
227
|
.bubble::before {
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
</template>
|
|
36
36
|
|
|
37
37
|
<script setup>
|
|
38
|
-
import { toRefs, computed
|
|
38
|
+
import { toRefs, computed } from 'vue';
|
|
39
39
|
|
|
40
40
|
import PolarLayout from './PolarLayout.vue';
|
|
41
41
|
import FreeLayout from './FreeLayout.vue';
|
|
@@ -89,8 +89,6 @@ const getSectionClass = (key, section) => ({
|
|
|
89
89
|
'section-active': activeSection.value === key,
|
|
90
90
|
disabled: activeSection.value != key && !props.preview,
|
|
91
91
|
});
|
|
92
|
-
|
|
93
|
-
|
|
94
92
|
</script>
|
|
95
93
|
|
|
96
94
|
<style scoped>
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
class="absolute bottom-4 left-4 right-4 bg-gray-50 flex items-center justify-center text-gray-400 rounded-lg hover:bg-gray-100 hover:text-gray-600 cursor-pointer"
|
|
7
7
|
@click="expandContainer()"
|
|
8
8
|
>
|
|
9
|
-
<font-awesome-icon :icon="['fas', 'plus']" class="h-8" />
|
|
9
|
+
<font-awesome-icon :icon="['fas', 'plus']" class="h-8 text-xs" />
|
|
10
10
|
</div>
|
|
11
11
|
</div>
|
|
12
12
|
</div>
|
|
@@ -47,6 +47,10 @@ watch(
|
|
|
47
47
|
const additionalHeight = ref(0);
|
|
48
48
|
const displayHeight = computed(() => defaultHeight.value + additionalHeight.value);
|
|
49
49
|
|
|
50
|
+
function expandContainer() {
|
|
51
|
+
additionalHeight.value += 200;
|
|
52
|
+
}
|
|
53
|
+
|
|
50
54
|
const containerStyle = computed(() => {
|
|
51
55
|
return {
|
|
52
56
|
aspectRatio: displaySize.value?.aspectRatio,
|
|
@@ -64,11 +68,7 @@ const containerStyle = computed(() => {
|
|
|
64
68
|
});
|
|
65
69
|
|
|
66
70
|
// 전체 elements
|
|
67
|
-
provide('elements', elements);
|
|
68
|
-
|
|
69
|
-
function expandContainer() {
|
|
70
|
-
additionalHeight.value += 200;
|
|
71
|
-
}
|
|
71
|
+
// provide('elements', elements);
|
|
72
72
|
</script>
|
|
73
73
|
|
|
74
74
|
<style scoped lang="scss">
|
|
@@ -7,7 +7,7 @@
|
|
|
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="startDrag($event,
|
|
10
|
+
@mousedown.stop="activeEditMode ? null : startDrag($event, item.id)"
|
|
11
11
|
>
|
|
12
12
|
<!-- 리사이즈 핸들 -->
|
|
13
13
|
<div v-if="item.id === activeId" class="absolute inset-0 pointer-events-none">
|
|
@@ -17,9 +17,21 @@
|
|
|
17
17
|
:class="`handle absolute bg-blue-400 z-10 pointer-events-auto hover:bg-blue-700 ${handle.name}`"
|
|
18
18
|
@mousedown="startResize($event, handle.name)"
|
|
19
19
|
></div>
|
|
20
|
-
<div class="
|
|
21
|
-
|
|
22
|
-
<
|
|
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>
|
|
23
35
|
</div>
|
|
24
36
|
</div>
|
|
25
37
|
<component
|
|
@@ -27,10 +39,14 @@
|
|
|
27
39
|
:id="`${sectionKey}-${index}`"
|
|
28
40
|
:ref="(el) => setComponentRef(el, item.id)"
|
|
29
41
|
v-bind="item"
|
|
30
|
-
class="hover:opacity-80
|
|
42
|
+
class="hover:opacity-80"
|
|
43
|
+
:class="activeEditMode ? `pointer-events-auto ` : `pointer-events-none`"
|
|
31
44
|
/>
|
|
32
45
|
</div>
|
|
33
|
-
|
|
46
|
+
|
|
47
|
+
<!-- 오버레이 -->
|
|
48
|
+
<div v-if="activeEditMode" class="absolute inset-0 bg-black opacity-50"></div>
|
|
49
|
+
|
|
34
50
|
<!-- 가이드라인 -->
|
|
35
51
|
<div v-if="isActive">
|
|
36
52
|
<div v-if="guides.x !== null" class="guide absolute z-10 pointer-events-none vertical" :style="{ left: `${guides.x}px` }"></div>
|
|
@@ -109,6 +125,11 @@ function setComponentRef(el, elementId) {
|
|
|
109
125
|
|
|
110
126
|
const selectedElement = inject('selectedElement');
|
|
111
127
|
|
|
128
|
+
const activeEditMode = inject('activeEditMode');
|
|
129
|
+
function toggleEditMode() {
|
|
130
|
+
activeEditMode.value = !activeEditMode.value;
|
|
131
|
+
}
|
|
132
|
+
|
|
112
133
|
import { omit, cloneDeep } from 'lodash-es';
|
|
113
134
|
watch(
|
|
114
135
|
() => activeElement.value?.id,
|
|
@@ -119,6 +140,9 @@ watch(
|
|
|
119
140
|
}
|
|
120
141
|
// selectedElement.value = structuredClone(toRaw(activeElement.value));
|
|
121
142
|
selectedElement.value = cloneDeep(activeElement.value);
|
|
143
|
+
|
|
144
|
+
// activeElement 바뀌면 편집 모드 해제
|
|
145
|
+
activeEditMode.value = false;
|
|
122
146
|
},
|
|
123
147
|
);
|
|
124
148
|
|
|
@@ -130,7 +154,7 @@ watch(
|
|
|
130
154
|
(newElement) => {
|
|
131
155
|
updateActiveElement(selectedElement.value?.id, newElement);
|
|
132
156
|
},
|
|
133
|
-
{ deep: true }
|
|
157
|
+
{ deep: true },
|
|
134
158
|
);
|
|
135
159
|
|
|
136
160
|
onMounted(() => {
|
|
@@ -10,14 +10,26 @@
|
|
|
10
10
|
'bg-white border-2 border-blue-400': activeElement?.id === item.id,
|
|
11
11
|
}"
|
|
12
12
|
:style="getElementStyle('element', item)"
|
|
13
|
-
@mousedown.stop="startDrag($event, item.id)"
|
|
13
|
+
@mousedown.stop="activeEditMode ? null : startDrag($event, item.id)"
|
|
14
14
|
>
|
|
15
15
|
<!-- 리사이즈 핸들 -->
|
|
16
16
|
<div v-if="activeElement?.id === item.id">
|
|
17
17
|
<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>
|
|
18
|
-
<div class="
|
|
19
|
-
|
|
20
|
-
<
|
|
18
|
+
<div class="flex gap-1 m-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>
|
|
21
33
|
</div>
|
|
22
34
|
</div>
|
|
23
35
|
|
|
@@ -26,10 +38,14 @@
|
|
|
26
38
|
:id="`${sectionKey}-${index}`"
|
|
27
39
|
:ref="(el) => setComponentRef(el, item.id)"
|
|
28
40
|
v-bind="item"
|
|
29
|
-
class="hover:opacity-80
|
|
41
|
+
class="hover:opacity-80"
|
|
42
|
+
:class="activeEditMode ? `pointer-events-auto ` : `pointer-events-none`"
|
|
30
43
|
/>
|
|
31
44
|
</div>
|
|
32
45
|
|
|
46
|
+
<!-- 오버레이 -->
|
|
47
|
+
<div v-if="activeEditMode" class="absolute inset-0 bg-black opacity-50"></div>
|
|
48
|
+
|
|
33
49
|
<!-- 빈 그리드 설정 -->
|
|
34
50
|
<div
|
|
35
51
|
v-for="(cell, index) in emptyData[sectionKey]"
|
|
@@ -103,6 +119,11 @@ function setComponentRef(el, elementId) {
|
|
|
103
119
|
}
|
|
104
120
|
|
|
105
121
|
const selectedElement = inject('selectedElement');
|
|
122
|
+
const activeEditMode = inject('activeEditMode');
|
|
123
|
+
|
|
124
|
+
function toggleEditMode() {
|
|
125
|
+
activeEditMode.value = !activeEditMode.value;
|
|
126
|
+
}
|
|
106
127
|
|
|
107
128
|
import { omit, cloneDeep } from 'lodash-es';
|
|
108
129
|
watch(
|
|
@@ -114,8 +135,11 @@ watch(
|
|
|
114
135
|
}
|
|
115
136
|
// selectedElement.value = structuredClone(toRaw(activeElement.value));
|
|
116
137
|
selectedElement.value = cloneDeep(activeElement.value);
|
|
138
|
+
|
|
139
|
+
// activeElement 바뀌면 편집 모드 해제
|
|
140
|
+
activeEditMode.value = false;
|
|
117
141
|
},
|
|
118
|
-
{ immediate: true }
|
|
142
|
+
{ immediate: true },
|
|
119
143
|
);
|
|
120
144
|
|
|
121
145
|
// 특정 키 값을 제외하고 변경 감지
|
|
@@ -126,7 +150,7 @@ watch(
|
|
|
126
150
|
(newElement) => {
|
|
127
151
|
updateActiveElement(selectedElement.value?.id, newElement);
|
|
128
152
|
},
|
|
129
|
-
{ deep: true }
|
|
153
|
+
{ deep: true },
|
|
130
154
|
);
|
|
131
155
|
|
|
132
156
|
onMounted(() => {
|
|
@@ -163,12 +163,14 @@ class DisplayEngine {
|
|
|
163
163
|
if (this.displayMode === mode) return;
|
|
164
164
|
|
|
165
165
|
this.displayMode = mode;
|
|
166
|
+
const prevDisplaySize = { ...this.displaySize };
|
|
166
167
|
this._resetDisplaySizeByMode(mode);
|
|
167
168
|
|
|
168
169
|
// displayMode 변경 시, displaySize도 초기화 됨
|
|
169
170
|
this.eventBus.emit('display:updateDisplayMode', {
|
|
170
171
|
displayMode: this.displayMode,
|
|
171
172
|
displaySize: this.displaySize,
|
|
173
|
+
prev: { displaySize: prevDisplaySize },
|
|
172
174
|
timestamp: Date.now(),
|
|
173
175
|
});
|
|
174
176
|
}
|
|
@@ -213,10 +215,12 @@ class DisplayEngine {
|
|
|
213
215
|
alert(`설정 가능한 최대 ${typeLimit.name}는 ${typeLimit.max}${typeLimit.unit}입니다.`);
|
|
214
216
|
}
|
|
215
217
|
|
|
218
|
+
const prevDisplaySize = { ...this.displaySize };
|
|
216
219
|
this.displaySize[type] = Math.min(Math.max(0, numSize), typeLimit.max);
|
|
217
220
|
|
|
218
221
|
this.eventBus.emit('display:updateDisplaySize', {
|
|
219
222
|
displaySize: this.displaySize,
|
|
223
|
+
prev: { displaySize: prevDisplaySize },
|
|
220
224
|
timestamp: Date.now(),
|
|
221
225
|
});
|
|
222
226
|
}
|
|
@@ -71,9 +71,9 @@ class FreeDropEngine {
|
|
|
71
71
|
});
|
|
72
72
|
|
|
73
73
|
// layoutName 변경 감지 (gridNumber가 함께 초기화됨)
|
|
74
|
-
this._subscribe('layout:setLayoutName', ({gridNumber}) => {
|
|
74
|
+
this._subscribe('layout:setLayoutName', ({ gridNumber }) => {
|
|
75
75
|
this._gridNumber = gridNumber;
|
|
76
|
-
})
|
|
76
|
+
});
|
|
77
77
|
this._subscribe('layout:updateLayoutName', ({ gridNumber }) => {
|
|
78
78
|
this._gridNumber = gridNumber;
|
|
79
79
|
});
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { cloneDeep } from 'lodash-es';
|
|
2
|
+
import { nextTick } from 'vue';
|
|
3
|
+
|
|
2
4
|
import EventBus from './EventBus.js';
|
|
3
5
|
import ApiManager from './ApiManager.js';
|
|
4
6
|
import utils from '../../utils/index.js';
|
|
@@ -180,7 +182,38 @@ class EngineManager {
|
|
|
180
182
|
|
|
181
183
|
/** ---------------------------------- display Engine ---------------------------------- **/
|
|
182
184
|
/** [내부함수] displayEngine 연결 설정 */
|
|
183
|
-
_setupDisplayEngineConnections() {
|
|
185
|
+
_setupDisplayEngineConnections() {
|
|
186
|
+
this._subscribe('display:updateDisplayMode', ({ displaySize, prev }) => {
|
|
187
|
+
this._updateElementsScale(prev.displaySize, displaySize);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
this._subscribe('display:updateDisplaySize', ({ displaySize, prev }) => {
|
|
191
|
+
this._updateElementsScale(prev.displaySize, displaySize);
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
_updateElementsScale(oldValue, newValue) {
|
|
196
|
+
const oldWidth = oldValue.px;
|
|
197
|
+
const oldHeight = oldValue.px / oldValue.aspectRatio.split('/').map(Number).reduce((a, b) => a / b);
|
|
198
|
+
|
|
199
|
+
const newWidth = newValue.px;
|
|
200
|
+
const newHeight = newValue.px / newValue.aspectRatio.split('/').map(Number).reduce((a, b) => a / b);
|
|
201
|
+
|
|
202
|
+
nextTick(() => {
|
|
203
|
+
this.setContainerSize();
|
|
204
|
+
|
|
205
|
+
const normalizeElements = dataConverter.normalize(this.state.elements, oldWidth, oldHeight);
|
|
206
|
+
const denormalizedElements = dataConverter.denormalize(normalizeElements, newWidth, newHeight);
|
|
207
|
+
|
|
208
|
+
this.state.elements = cloneDeep(denormalizedElements);
|
|
209
|
+
|
|
210
|
+
this.eventBus.emit('system:requestUpdateElements', {
|
|
211
|
+
historyEvent: true,
|
|
212
|
+
elements: cloneDeep(this.state.elements),
|
|
213
|
+
timestamp: Date.now(),
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
}
|
|
184
217
|
|
|
185
218
|
/** ---------------------------------- layout Engine ---------------------------------- **/
|
|
186
219
|
/** [내부함수] layoutEngine 연결 설정 */
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<svg
|
|
3
|
+
width="24"
|
|
4
|
+
height="24"
|
|
5
|
+
viewBox="0 0 24 24"
|
|
6
|
+
fill="none"
|
|
7
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
8
|
+
class="edit-icon"
|
|
9
|
+
>
|
|
10
|
+
<!-- 종이/문서 -->
|
|
11
|
+
<path
|
|
12
|
+
d="M12 6H7C6.73478 6 6.48043 6.10536 6.29289 6.29289C6.10536 6.48043 6 6.73478 6 7V17C6 17.2652 6.10536 17.5196 6.29289 17.7071C6.48043 17.8946 6.73478 18 7 18H17C17.2652 18 17.5196 17.8946 17.7071 17.7071C17.8946 17.5196 18 17.2652 18 17V12"
|
|
13
|
+
stroke="currentColor"
|
|
14
|
+
stroke-width="2"
|
|
15
|
+
stroke-linecap="round"
|
|
16
|
+
stroke-linejoin="round"
|
|
17
|
+
/>
|
|
18
|
+
<!-- 연필 -->
|
|
19
|
+
<path
|
|
20
|
+
d="M17.5 4.5C17.6989 4.30109 17.9687 4.18934 18.25 4.18934C18.5313 4.18934 18.8011 4.30109 19 4.5C19.1989 4.69891 19.3107 4.96866 19.3107 5.25C19.3107 5.53134 19.1989 5.80109 19 6L13 12L11 13L12 11L17.5 4.5Z"
|
|
21
|
+
stroke="currentColor"
|
|
22
|
+
stroke-width="2"
|
|
23
|
+
stroke-linecap="round"
|
|
24
|
+
stroke-linejoin="round"
|
|
25
|
+
/>
|
|
26
|
+
</svg>
|
|
27
|
+
</template>
|
|
28
|
+
|
|
29
|
+
<style scoped lang="scss">
|
|
30
|
+
.edit-icon {
|
|
31
|
+
color: #6b7280; /* 미선택 상태를 나타내는 회색 */
|
|
32
|
+
background-color: rgba(107, 114, 128, 0.1); /* 연한 회색 배경 */
|
|
33
|
+
/* margin: 4px; */
|
|
34
|
+
padding: 2px;
|
|
35
|
+
border-radius: 4px;
|
|
36
|
+
|
|
37
|
+
&.active {
|
|
38
|
+
color: #2563eb; /* 선택 상태를 나타내는 파란색 */
|
|
39
|
+
background-color: rgba(37, 99, 235, 0.1);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.edit-icon:hover {
|
|
44
|
+
color: #4b5563; /* 호버 시 더 진한 회색 */
|
|
45
|
+
background-color: rgba(107, 114, 128, 0.15); /* 호버 시 배경색 강화 */
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.edit-icon:active {
|
|
49
|
+
color: #374151; /* 클릭 시 가장 진한 회색 */
|
|
50
|
+
background-color: rgba(107, 114, 128, 0.2);
|
|
51
|
+
}
|
|
52
|
+
</style>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="icon-holder">
|
|
3
|
+
<slot></slot>
|
|
4
|
+
</div>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<style scoped lang="scss">
|
|
8
|
+
.icon-holder {
|
|
9
|
+
width: 24px;
|
|
10
|
+
height: 24px;
|
|
11
|
+
|
|
12
|
+
display: flex;
|
|
13
|
+
justify-content: center;
|
|
14
|
+
align-items: center;
|
|
15
|
+
|
|
16
|
+
color: #6b7280; /* 미선택 상태를 나타내는 회색 */
|
|
17
|
+
background-color: rgba(107, 114, 128, 0.1); /* 연한 회색 배경 */
|
|
18
|
+
/* margin: 4px; */
|
|
19
|
+
padding: 4px;
|
|
20
|
+
border-radius: 4px;
|
|
21
|
+
|
|
22
|
+
&.active {
|
|
23
|
+
color: #2563eb; /* 선택 상태를 나타내는 파란색 */
|
|
24
|
+
background-color: rgba(37, 99, 235, 0.1);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.icon-holder:hover {
|
|
29
|
+
color: #4b5563; /* 호버 시 더 진한 회색 */
|
|
30
|
+
background-color: rgba(107, 114, 128, 0.15); /* 호버 시 배경색 강화 */
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.icon-holder:active {
|
|
34
|
+
color: #374151; /* 클릭 시 가장 진한 회색 */
|
|
35
|
+
background-color: rgba(107, 114, 128, 0.2);
|
|
36
|
+
}
|
|
37
|
+
</style>
|