polarvo-layout 1.0.7 → 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.7",
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
 
@@ -47,17 +47,18 @@ const activeElement = computed(() => {
47
47
  });
48
48
 
49
49
  const selectedElement = inject('selectedElement');
50
- const selectedChildProps = inject('selectedChildProps');
50
+ // const selectedChildProps = inject('selectedChildProps');
51
51
 
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);
@@ -65,19 +66,21 @@ function clickDesignBar(designMenu) {
65
66
 
66
67
 
67
68
  function setComponentDefaults(element) {
68
- const child = props.polarvo.components.getElement(activeSection.value, element.id);
69
- if (!child) return;
70
- selectedChildProps.value = Object.keys(child.$props);
69
+ // const child = props.polarvo.components.getElement(activeSection.value, element.id);
70
+ // if (!child) return;
71
+ // selectedChildProps.value = Object.keys(child.$props);
71
72
 
72
- //click한 컴포넌트의 props 객체 조회
73
- const childProps = child.$options.props;
73
+ // //click한 컴포넌트의 props 객체 조회
74
+ // const childProps = child.$options.props;
75
+
76
+ // // props에 default 항목이 있는 경우, 함수인지 아닌지 확인 후 default 값 설정
77
+ // Object.entries(childProps).forEach(([key, propInfo]) => {
78
+ // if (propInfo.default !== undefined && (element[key] === undefined || element[key] === null)) {
79
+ // // 함수인 경우 실행하여 기본값 설정
80
+ // selectedElement.value[key] = typeof propInfo.default === 'function' ? propInfo.default() : propInfo.default;
81
+ // }
82
+ // });
83
+ // selectedElement.value = element;
74
84
 
75
- // props에 default 항목이 있는 경우, 함수인지 아닌지 확인 후 default 값 설정
76
- Object.entries(childProps).forEach(([key, propInfo]) => {
77
- if (propInfo.default !== undefined && (element[key] === undefined || element[key] === null)) {
78
- // 함수인 경우 실행하여 기본값 설정
79
- selectedElement.value[key] = typeof propInfo.default === 'function' ? propInfo.default() : propInfo.default;
80
- }
81
- });
82
85
  }
83
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>
@@ -24,57 +24,77 @@
24
24
  <template v-if="isEditorPage">
25
25
  <div id="divider" class="h-px bg-gray-200 w-8"></div>
26
26
  <div class="icon-group">
27
- <!-- 👂 내부설정에 테마설정 포함 -->
27
+ <!-- 👂 기본설정에 테마설정 포함 -->
28
28
  <div
29
- class="icon-wrapper text-2xl text-gray-400 p-2 hover"
30
- title="내부설정"
29
+ class="icon-wrapper text-2xl text-gray-400 p-2"
30
+ title="기본설정"
31
31
  :class="{ active: activeMenu === 'projectSetting' }"
32
32
  @click="polarvo.display.setActiveMenu('projectSetting')"
33
33
  >
34
- <font-awesome-icon :icon="['fas', 'computer']" />
35
- <div class="text-xs text-center col-span-1 w-max">내부설정</div>
34
+ <font-awesome-icon :icon="['fas', 'screwdriver-wrench']" class="text-xl"/>
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
+ title="정책설정"
40
+ :class="{ active: activeMenu === 'policySetting' }"
41
+ @click="polarvo.display.setActiveMenu('policySetting')"
42
+ >
43
+ <font-awesome-icon :icon="['fas', 'shield-halved']" class="text-xl"/>
44
+ <div class="text-xs text-center col-span-1 w-max">정책설정</div>
45
+ </div>
46
+ </div>
47
+ <div id="divider" class="h-px bg-gray-200 w-8"></div>
48
+ <div class="icon-group">
49
+ <div
50
+ class="icon-wrapper text-2xl text-gray-400 p-2"
39
51
  title="DB관리"
40
52
  :class="{ active: activeMenu === 'dbSetting' }"
41
53
  @click="polarvo.display.setActiveMenu('dbSetting')"
42
54
  >
43
- <font-awesome-icon :icon="['fas', 'database']" />
55
+ <font-awesome-icon :icon="['fas', 'database']" class="text-xl"/>
44
56
  <div class="text-xs text-center col-span-1 w-max">DB관리</div>
45
57
  </div>
46
58
  <div
47
- class="icon-wrapper text-2xl text-gray-400 p-2 hover"
59
+ class="icon-wrapper text-2xl text-gray-400 p-2"
60
+ title="화면이동"
61
+ :class="{ active: activeMenu === 'screenMove' }"
62
+ @click="polarvo.display.setActiveMenu('screenMove')"
63
+ >
64
+ <font-awesome-icon :icon="['fas', 'boxes-packing']" class="text-xl"/>
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"
48
69
  title="화면관리"
49
70
  :class="{ active: activeMenu === 'screenSetting' }"
50
71
  @click="polarvo.display.setActiveMenu('screenSetting')"
51
72
  >
52
- <font-awesome-icon :icon="['far', 'window-restore']" />
73
+ <font-awesome-icon :icon="['fas', 'computer']" class="text-xl"/>
53
74
  <div class="text-xs text-center col-span-1 w-max">화면관리</div>
54
75
  </div>
55
76
  </div>
56
-
57
77
  <!-- 화면 선택 시 아이콘 그룹 -->
58
78
  <template v-if="isScreen">
59
79
  <div id="divider" class="h-px bg-gray-200 w-8"></div>
60
80
  <div class="icon-group">
61
81
  <div
62
- class="icon-wrapper text-2xl text-gray-400 p-2 hover"
82
+ class="icon-wrapper text-2xl text-gray-400 p-2"
63
83
  title="레이아웃"
64
84
  :class="{ active: activeMenu === 'layout' }"
65
85
  @click="polarvo.display.setActiveMenu('layout')"
66
86
  >
67
- <font-awesome-icon :icon="['fas', 'table-columns']" />
87
+ <font-awesome-icon :icon="['fas', 'table-columns']" class="text-xl"/>
68
88
  <div class="text-xs text-center col-span-1 w-max">레이아웃</div>
69
89
  </div>
70
90
  <div
71
91
  v-if="activeSection"
72
- class="icon-wrapper text-2xl text-gray-400 p-2 hover"
92
+ class="icon-wrapper text-2xl text-gray-400 p-2"
73
93
  title="요소"
74
94
  :class="{ active: activeMenu === 'element' }"
75
95
  @click="polarvo.display.setActiveMenu('element')"
76
96
  >
77
- <font-awesome-icon :icon="['fas', 'shapes']" />
97
+ <font-awesome-icon :icon="['fas', 'shapes']" class="text-xl"/>
78
98
  <div class="text-xs text-center col-span-1 w-max">요소</div>
79
99
  </div>
80
100
  </div>
@@ -90,7 +110,7 @@
90
110
  :class="{ active: activeMenu === 'setting' }"
91
111
  @click="$router.push('/console/setting')"
92
112
  >
93
- <font-awesome-icon :icon="['fas', 'gear']" />
113
+ <font-awesome-icon :icon="['fas', 'gear']" class="text-xl"/>
94
114
  <div class="text-xs text-center col-span-1 w-max">환경설정</div>
95
115
  </div>
96
116
  <!-- 계정설정 -->
@@ -101,7 +121,7 @@
101
121
  title="계정설정"
102
122
  @click="toggleUser = !toggleUser"
103
123
  >
104
- <font-awesome-icon :icon="['fas', 'user']" />
124
+ <font-awesome-icon :icon="['fas', 'user']" class="text-xl"/>
105
125
  </div>
106
126
 
107
127
  <!-- 계정설정 토글메뉴 -->
@@ -165,6 +185,7 @@ async function logout() {
165
185
  <style scoped lang="scss">
166
186
  .icon-wrapper {
167
187
  aspect-ratio: 1 / 1; /* aspect-square */
188
+ width: 100%;
168
189
  display: grid; /* grid */
169
190
  grid-template-rows: 1fr auto; /* grid-rows-[1fr_auto] */
170
191
  place-items: center; /* place-items-center */
@@ -173,7 +194,7 @@ async function logout() {
173
194
  cursor: pointer; /* cursor-pointer */
174
195
 
175
196
  &:hover:not(.logo):not(.active) {
176
- width: 100%;
197
+ // width: 100%;
177
198
  background-color: #f3f4f6; /* hover:bg-gray-100 */
178
199
  border-radius: 0.5rem; /* rounded-lg */
179
200
  color: #4b5563; /* text-gray-600 */
@@ -199,8 +220,8 @@ async function logout() {
199
220
  display: flex;
200
221
  flex-direction: column;
201
222
  align-items: center;
202
- gap: 0.5rem; /* gap-2 */
203
- padding-top: 0.5rem; /* py-2 */
223
+ gap: 0.25rem; /* gap-1 */
224
+ padding-block: 0.5rem; /* py-2 */
204
225
  }
205
226
 
206
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>