polarvo-layout 1.0.8 → 1.0.9

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polarvo-layout",
3
- "version": "1.0.8",
3
+ "version": "1.0.9",
4
4
  "type": "module",
5
5
  "author": "unigence <unigencelab@gmail.com>",
6
6
  "repository": {
@@ -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="gapSize"
61
- @change="setGapSize($event.target.value)"
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, gapSize } = toRefs(props.polarvo.layout.state);
81
- const { setGapSize, setSectionMode, setSectionConfig } = props.polarvo.layout;
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 w-14 text-2xl text-gray-400 p-2"
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 hover"
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
- <font-awesome-icon :icon="['fas', 'computer']" />
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 hover"
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 hover"
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 hover"
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
- <font-awesome-icon :icon="['far', 'copy']" />
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 hover"
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 hover"
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.5rem; /* gap-2 */
223
- padding-top: 0.5rem; /* py-2 */
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, onUnmounted } from 'vue';
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, item.id)"
10
+ @mousedown.stop="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="absolute z-10 pointer-events-auto cursor-pointer" @click="toggleLock()">
21
- <LockIcon v-if="item.isLocked" />
22
- <UnlockIcon v-else />
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 pointer-events-none"
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(() => {
@@ -15,9 +15,21 @@
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="absolute z-10 pointer-events-auto cursor-pointer" @click="toggleLock()">
19
- <LockIcon v-if="item.isLocked" />
20
- <UnlockIcon v-else />
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 pointer-events-none"
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>
@@ -35,7 +35,7 @@
35
35
  .lock-icon {
36
36
  color: #dc2626; /* 잠긴 상태를 나타내는 빨간색 */
37
37
  background-color: rgba(239, 68, 68, 0.1); /* 연한 빨간색 배경 */
38
- margin: 4px;
38
+ /* margin: 4px; */
39
39
  padding: 2px;
40
40
  border-radius: 4px;
41
41
  }
@@ -35,7 +35,7 @@
35
35
  .unlock-icon {
36
36
  color: #6b7280; /* 열린 상태를 나타내는 회색 */
37
37
  background-color: rgba(107, 114, 128, 0.1); /* 연한 회색 배경 */
38
- margin: 4px;
38
+ /* margin: 4px; */
39
39
  padding: 2px;
40
40
  border-radius: 4px;
41
41
  }
@@ -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>