polarvo-layout 1.0.2 → 1.0.3

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.2",
3
+ "version": "1.0.3",
4
4
  "type": "module",
5
5
  "author": "unigence <unigencelab@gmail.com>",
6
6
  "repository": {
@@ -24,7 +24,7 @@
24
24
  min="600"
25
25
  max="2400"
26
26
  step="50"
27
- :value="displaySize.px"
27
+ :value="displaySize?.px"
28
28
  class="border-b-2 pr-1 border-white focus:outline-none focus:border-b-2 focus:border-blue-500"
29
29
  @change="setDisplaySize('px', $event.target.value)"
30
30
  />
@@ -36,7 +36,7 @@
36
36
  min="0"
37
37
  max="100"
38
38
  step="10"
39
- :value="displaySize.percent"
39
+ :value="displaySize?.percent"
40
40
  class="border-b-2 pr-1 border-white focus:outline-none focus:border-b-2 focus:border-blue-500"
41
41
  @change="setDisplaySize('percent', $event.target.value)"
42
42
  />
@@ -46,7 +46,7 @@
46
46
  </template>
47
47
 
48
48
  <script setup>
49
- import { toRefs } from 'vue';
49
+ import { toRefs, onMounted } from 'vue';
50
50
 
51
51
  const props = defineProps({
52
52
  polarvo: {
@@ -56,6 +56,7 @@ const props = defineProps({
56
56
  });
57
57
  const { displayMode, displaySize } = toRefs(props.polarvo.display.state);
58
58
  const { setDisplayMode, setDisplaySize } = props.polarvo.display;
59
+
59
60
  </script>
60
61
 
61
62
  <style scoped>
@@ -1,12 +1,27 @@
1
1
  <template>
2
2
  <!-- editor - title right -->
3
3
  <div id="fast-menu" class="flex gap-2 text-gray-400">
4
- <UndoIcon class="size-5 cursor-pointer hover:opacity-70 active:text-blue-600" />
5
- <RedoIcon class="size-5 cursor-pointer hover:opacity-70 active:text-blue-600" />
4
+ <button @click="undo" :class="undoable ? 'cursor-pointer hover:opacity-70 ' : 'cursor-not-allowed opacity-50'" :disabled="!undoable">
5
+ <UndoIcon class="size-5 active:text-blue-600" />
6
+ </button>
7
+ <button @click="redo" :class="redoable ? 'cursor-pointer hover:opacity-70' : 'cursor-not-allowed opacity-50'" :disabled="!redoable">
8
+ <RedoIcon class="size-5 active:text-blue-600" />
9
+ </button>
6
10
  </div>
7
11
  </template>
8
12
 
9
13
  <script setup>
10
- import UndoIcon from '../../icons//history/UndoIcon.vue';
14
+ import { toRefs } from 'vue';
15
+ import UndoIcon from '../../icons/history/UndoIcon.vue';
11
16
  import RedoIcon from '../../icons/history/RedoIcon.vue';
17
+
18
+ const props = defineProps({
19
+ polarvo: {
20
+ type: Object,
21
+ required: true,
22
+ },
23
+ });
24
+
25
+ const { undoable, redoable } = toRefs(props.polarvo.history.state);
26
+ const { undo, redo } = props.polarvo.history;
12
27
  </script>
@@ -2,7 +2,7 @@
2
2
  <div
3
3
  :id="preview ? 'previewLayout' : 'baseLayout'"
4
4
  class="grid h-full p-2 bg-transparent rounded-lg"
5
- :class="[layoutType + '-layout', `grid-cols-${gridNumber.column}`, 'mb-4' ? layoutType === 'three-column-split' : '']"
5
+ :class="[layoutType + '-layout', `grid-cols-${gridNumber?.column}`, 'mb-4' ? layoutType === 'three-column-split' : '']"
6
6
  :style="getBaseStyle"
7
7
  >
8
8
  <template v-for="(section, key) in layoutData" :key="key">
@@ -34,7 +34,7 @@
34
34
  </template>
35
35
 
36
36
  <script setup>
37
- import { toRefs, computed } from 'vue';
37
+ import { toRefs, computed, onUnmounted } from 'vue';
38
38
 
39
39
  import PolarLayout from './PolarLayout.vue';
40
40
  import FreeLayout from './FreeLayout.vue';
@@ -67,8 +67,8 @@ const layoutType = computed(() => {
67
67
 
68
68
  const getBaseStyle = computed(() => {
69
69
  return {
70
- gridTemplateColumns: gridRatio.value.column.map((ratio) => `${ratio}%`).join(' '),
71
- gridTemplateRows: gridRatio.value.row.map((ratio) => `${ratio}%`).join(' '),
70
+ gridTemplateColumns: gridRatio.value?.column.map((ratio) => `${ratio}%`).join(' '),
71
+ gridTemplateRows: gridRatio.value?.row.map((ratio) => `${ratio}%`).join(' '),
72
72
  marginRight: layoutType.value === 'no-split' ? '0' : layoutType.value === 'three-column-split' ? '14px' : '8px',
73
73
  gap: `${gapSize.value}px`,
74
74
  };
@@ -88,6 +88,8 @@ const getSectionClass = (key, section) => ({
88
88
  });
89
89
 
90
90
  const { layoutName, layoutData, activeSection, gapSize, gridNumber, gridRatio } = toRefs(props.polarvo.layout.state);
91
+
92
+
91
93
  </script>
92
94
 
93
95
  <style scoped>
@@ -7,7 +7,7 @@
7
7
  </template>
8
8
 
9
9
  <script setup>
10
- import { toRefs, inject, computed, provide } from 'vue';
10
+ import { toRefs, computed, provide } from 'vue';
11
11
  import BaseLayout from './BaseLayout.vue';
12
12
 
13
13
  const props = defineProps({
@@ -18,14 +18,14 @@ const props = defineProps({
18
18
  });
19
19
 
20
20
  const { displaySize } = toRefs(props.polarvo.display.state);
21
- const gridSize = computed(() => displaySize.value.gridSize || 16);
21
+ const gridSize = computed(() => displaySize.value?.gridSize || 16);
22
22
 
23
23
  const containerStyle = computed(() => {
24
24
  return {
25
- aspectRatio: displaySize.value.aspectRatio,
26
- width: `${displaySize.value.px}px`,
25
+ aspectRatio: displaySize.value?.aspectRatio,
26
+ width: `${displaySize.value?.px}px`,
27
27
  margin: `0 auto`,
28
- transform: `scale(${displaySize.value.percent / 100})`,
28
+ transform: `scale(${displaySize.value?.percent / 100})`,
29
29
  transformOrigin: 'top center',
30
30
  backgroundImage: `
31
31
  linear-gradient(rgba(0,0,0,0.05) 1px, transparent 1px),
@@ -38,8 +38,7 @@ const containerStyle = computed(() => {
38
38
  const { elements } = toRefs(props.polarvo.layout.state);
39
39
 
40
40
  // 전체 elements
41
- provide('elements', elements)
42
-
41
+ provide('elements', elements);
43
42
  </script>
44
43
 
45
44
  <style scoped lang="scss"></style>
@@ -1,51 +1,51 @@
1
1
  <template>
2
- <div
3
- v-for="(item, index) in filteredElements"
4
- :key="index"
5
- :id="item.id"
6
- class="relative bg-gray-200"
7
- :class="{
8
- 'z-50': activeElement?.id === item.id,
9
- 'bg-white border-2 border-blue-400': activeElement?.id === item.id && !preview,
10
- }"
11
- :style="getElementStyle('element', item)"
12
- @mousedown.stop="startDrag($event, item.id)"
13
- >
14
- <!-- 리사이즈 핸들 -->
15
- <div v-if="activeElement?.id === item.id && !preview">
16
- <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>
17
- <div class="absolute z-10 pointer-events-auto cursor-pointer" @click="toggleLock()">
18
- <LockIcon v-if="item.isLocked" />
19
- <UnlockIcon v-else />
2
+ <template v-if="!preview">
3
+ <div
4
+ v-for="(item, index) in filteredElements"
5
+ :key="index"
6
+ :id="item.id"
7
+ class="relative bg-gray-200"
8
+ :class="{
9
+ 'z-50': activeElement?.id === item.id,
10
+ 'bg-white border-2 border-blue-400': activeElement?.id === item.id,
11
+ }"
12
+ :style="getElementStyle('element', item)"
13
+ @mousedown.stop="startDrag($event, item.id)"
14
+ >
15
+ <!-- 리사이즈 핸들 -->
16
+ <div v-if="activeElement?.id === item.id">
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 />
21
+ </div>
20
22
  </div>
21
- </div>
22
23
 
23
- <component
24
- v-if="!preview"
25
- :is="dynamicComponent(item.type)"
26
- :id="`${sectionKey}-${index}`"
27
- :ref="(el) => setComponentRef(el, item.id)"
28
- v-bind="item"
29
- class="hover:opacity-80 pointer-events-none"
30
- />
31
- </div>
24
+ <component
25
+ :is="dynamicComponent(item.type)"
26
+ :id="`${sectionKey}-${index}`"
27
+ :ref="(el) => setComponentRef(el, item.id)"
28
+ v-bind="item"
29
+ class="hover:opacity-80 pointer-events-none"
30
+ />
31
+ </div>
32
32
 
33
- <!-- 빈 그리드 설정 -->
34
- <div
35
- v-for="(cell, index) in emptyData[sectionKey]"
36
- :key="index"
37
- class="flex items-center justify-center bg-gray-100 border border-gray-300"
38
- :style="getElementStyle('empty', cell)"
39
- @mouseenter="detectHoverCell(cell)"
40
- >
33
+ <!-- 빈 그리드 설정 -->
41
34
  <div
42
- v-show="!preview"
43
- 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"
44
- @click="setActiveCell('new', cell)"
35
+ v-for="(cell, index) in emptyData[sectionKey]"
36
+ :key="index"
37
+ class="flex items-center justify-center bg-gray-100 border border-gray-300"
38
+ :style="getElementStyle('empty', cell)"
39
+ @mouseenter="detectHoverCell(cell)"
45
40
  >
46
- +
41
+ <div
42
+ 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"
43
+ @click="setActiveCell('new', cell)"
44
+ >
45
+ +
46
+ </div>
47
47
  </div>
48
- </div>
48
+ </template>
49
49
  </template>
50
50
 
51
51
  <script setup>
@@ -70,7 +70,7 @@ const props = defineProps({
70
70
  elementIds: {
71
71
  type: Array,
72
72
  required: true,
73
- }
73
+ },
74
74
  });
75
75
 
76
76
  const { setActiveDesign } = props.polarvo.display;
@@ -49,8 +49,8 @@
49
49
  id="gap-size"
50
50
  min="0"
51
51
  max="12"
52
- :value="gapSize"
53
- @change="setGapSize($event.target.value)"
52
+ :value="activeData?.config?.gridGap"
53
+ @change="setSectionConfig('gap', $event.target.value)"
54
54
  />
55
55
  </div>
56
56
  </div>
@@ -81,8 +81,8 @@ const props = defineProps({
81
81
  required: true,
82
82
  },
83
83
  });
84
- const { activeData, gapSize, gridNumber, gridRatio } = toRefs(props.polarvo.layout.state);
85
- const { setGapSize, setRatio, setSectionMode, setSectionConfig } = props.polarvo.layout;
84
+ const { activeData } = toRefs(props.polarvo.layout.state);
85
+ const { setSectionMode, setSectionConfig } = props.polarvo.layout;
86
86
 
87
87
  const sectionMode = computed(() => activeData.value?.mode);
88
88
  </script>
@@ -27,8 +27,8 @@
27
27
  </div>
28
28
 
29
29
  <div class="px-4 py-0">
30
- <div class="flex flex-row items-center gap-2 mb-2" v-if="gridNumber.column > 1">
31
- <template v-for="column in gridNumber.column" :key="column">
30
+ <div class="flex flex-row items-center gap-2 mb-2" v-if="gridNumber?.column > 1">
31
+ <template v-for="column in gridNumber?.column" :key="column">
32
32
  <label class="text-sm w-12 text-gray-500" :for="`col-ratio-${column}`" v-if="column === 1">열비율</label>
33
33
  <input
34
34
  class="text-sm w-12 border-b-2 border-white focus:outline-none focus:border-b-2 focus:border-blue-500"
@@ -40,8 +40,8 @@
40
40
  </template>
41
41
  </div>
42
42
 
43
- <div class="flex flex-row items-center gap-2 mb-2" v-if="gridNumber.row > 1">
44
- <template v-for="row in gridNumber.row" :key="row">
43
+ <div class="flex flex-row items-center gap-2 mb-2" v-if="gridNumber?.row > 1">
44
+ <template v-for="row in gridNumber?.row" :key="row">
45
45
  <label class="text-sm w-12 text-gray-500" :for="`row-ratio-${row}`" v-if="row === 1">행비율</label>
46
46
  <input
47
47
  class="text-sm w-12 border-b-2 border-white focus:outline-none focus:border-b-2 focus:border-blue-500"
@@ -3,13 +3,8 @@ import { resources } from './resources/index.js';
3
3
  const createDefaultConfig = (overrides = {}) => {
4
4
  return {
5
5
  initialState: {
6
- gridSize: 16,
7
- gridNumber: { column: 1, row: 1 },
8
6
  elements: [], // 요소들
9
- // layoutData: null, // 레이아웃 데이터 - 캐시용
10
7
  activeSection: null,
11
- // activeData: null,
12
- activeId: null,
13
8
  },
14
9
  resource: {
15
10
  display: resources.display,
@@ -5,8 +5,9 @@ class DisplayEngine {
5
5
 
6
6
  this.activeMenu = null; // 현재 활성화된 메뉴
7
7
  this.activeDesign = null; // 현재 활성화된 디자인 메뉴
8
- this.displayMode = 'desktop'; // 현재 디스플레이 모드
9
- this.displaySize = { px: 1200, percent: 100, gridSize: 16, aspectRatio: '16/9' }; // 현재 디스플레이 사이즈
8
+ this.displayMode = 'desktop'; // 현재 디스플레이 모드 (초기값 = 'desktop')
9
+ // this.displaySize = { px: 1200, percent: 100, gridSize: 16, aspectRatio: '16/9' }; // 현재 디스플레이 사이즈
10
+ this.displaySize = null
10
11
 
11
12
  this._subscriptions = []; // 구독 해제 함수 저장용
12
13
  this._setupSubscriptions();
@@ -27,29 +28,46 @@ class DisplayEngine {
27
28
  /** 구독 설정 */
28
29
  _setupSubscriptions() {
29
30
  // 초기화 완료 이벤트 구독 - from EngineManager
30
- this._subscribe('system:engineInitialized', (data) => {
31
- this._setDisplaySizeByMode(this.displayMode);
32
- });
33
-
34
- this._subscribe('freeDrop:closeActiveMenu', () => {
35
- this.setActiveMenu(null);
36
- });
31
+ this._subscribe('system:engineInitialized', () => {
32
+ console.log('1. displayEngine')
33
+ this._resetDisplaySizeByMode(this.displayMode);
37
34
 
38
- this._subscribe('freeDrop:closeDesignMenu', () => {
39
- this.setActiveDesign(null);
35
+ this.eventBus.emit('display:engineInitialized', {
36
+ displaySize: this.displaySize,
37
+ timestamp: Date.now(),
38
+ })
40
39
  });
41
40
 
42
- this._subscribe('system:closeActiveMenu', () => {
43
- this.setActiveMenu(null);
44
- });
41
+ // this._subscribe('freeDrop:closeActiveMenu', () => {
42
+ // this.setActiveMenu(null);
43
+ // });
44
+
45
+ // this._subscribe('freeDrop:closeDesignMenu', () => {
46
+ // this.setActiveDesign(null);
47
+ // });
48
+
49
+ // this._subscribe('system:closeActiveMenu', () => {
50
+ // this.setActiveMenu(null);
51
+ // });
52
+
53
+ // this._subscribe('gridDrop:updateActiveCell', ({ type, activeCell }) => {
54
+ // if (type === 'new' && activeCell != null) {
55
+ // this.setActiveMenu('element');
56
+ // } else {
57
+ // this.setActiveMenu(activeCell);
58
+ // }
59
+ // });
60
+
61
+ // this._subscribe('system:enginesReset', () => {
62
+ // this.reset();
63
+ // })
64
+ }
45
65
 
46
- this._subscribe('gridDrop:updateActiveCell', ({ type, activeCell }) => {
47
- if (type === 'new' && activeCell != null) {
48
- this.setActiveMenu('element');
49
- } else {
50
- this.setActiveMenu(activeCell);
51
- }
52
- });
66
+ reset() {
67
+ this.activeMenu = null;
68
+ this.activeDesign = null;
69
+ this.displayMode = 'desktop';
70
+ this.displaySize = { px: 1200, percent: 100, gridSize: 16, aspectRatio: '16/9' }; // 현재 디스플레이 사이즈
53
71
  }
54
72
 
55
73
  /** 삭제 및 정리 */
@@ -67,6 +85,27 @@ class DisplayEngine {
67
85
  console.log('[DisplayEngine] 삭제 완료');
68
86
  }
69
87
 
88
+ getState() {
89
+ return {
90
+ displayMode: this.displayMode,
91
+ displaySize: { ...this.displaySize },
92
+ activeMenu: this.activeMenu,
93
+ activeDesign: this.activeDesign,
94
+ };
95
+ }
96
+
97
+ setState(state) {
98
+ this.displayMode = state.displayMode;
99
+ this.displaySize = { ...state.displaySize };
100
+ this.activeMenu = state.activeMenu;
101
+ this.activeDesign = state.activeDesign;
102
+
103
+ // this.eventBus.emit('display:restoredState', {
104
+ // prev: state,
105
+ // timestamp: Date.now(),
106
+ // });
107
+ }
108
+
70
109
  /** ---------------------------------- 공통 메소드 ---------------------------------- **/
71
110
 
72
111
  /** 아이콘바 메뉴 변경
@@ -105,19 +144,30 @@ class DisplayEngine {
105
144
  }
106
145
 
107
146
  if (this.displayMode === mode) return;
147
+
148
+ const prevDisplayMode = this.displayMode;
149
+ const prevDisplaySize = { ...this.displaySize };
150
+
108
151
  this.displayMode = mode;
152
+ this._resetDisplaySizeByMode(mode);
109
153
 
154
+ // displayMode 변경 시, displaySize도 초기화 됨
110
155
  this.eventBus.emit('display:updateDisplayMode', {
111
- displayMode: mode,
156
+ displayMode: this.displayMode,
157
+ displaySize: this.displaySize,
158
+ prev: {
159
+ displayMode: prevDisplayMode,
160
+ displaySize: prevDisplaySize,
161
+ },
112
162
  timestamp: Date.now(),
113
163
  });
114
- this._setDisplaySizeByMode(mode);
164
+
115
165
  }
116
166
 
117
167
  /** 사이즈 설정
118
168
  * @param {string} mode - 디스플레이 모드
119
169
  */
120
- _setDisplaySizeByMode(mode) {
170
+ _resetDisplaySizeByMode(mode) {
121
171
  if (this.resource[mode]) {
122
172
  this.displaySize = {
123
173
  px: this.resource[mode]?.defaultWidth || 1200,
@@ -125,11 +175,6 @@ class DisplayEngine {
125
175
  gridSize: this.resource[mode]?.gridSize || 16,
126
176
  aspectRatio: this.resource[mode]?.aspectRatio || '16/9',
127
177
  };
128
-
129
- this.eventBus.emit('display:updateDisplaySizeByMode', {
130
- displaySize: this.displaySize,
131
- timestamp: Date.now(),
132
- });
133
178
  }
134
179
  }
135
180
 
@@ -159,10 +204,12 @@ class DisplayEngine {
159
204
  alert(`설정 가능한 최대 ${typeLimit.name}는 ${typeLimit.max}${typeLimit.unit}입니다.`);
160
205
  }
161
206
 
207
+ const prevDisplaySize = { ...this.displaySize };
162
208
  this.displaySize[type] = Math.min(Math.max(0, numSize), typeLimit.max);
163
209
 
164
210
  this.eventBus.emit('display:updateDisplaySize', {
165
211
  displaySize: this.displaySize,
212
+ prev: prevDisplaySize,
166
213
  timestamp: Date.now(),
167
214
  });
168
215
  }