polarvo-layout 1.0.20 → 1.0.22
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/DisplayFastMenu.vue +2 -2
- package/src/components/FastMenu/LayoutFastMenu.vue +12 -4
- package/src/components/Layout/BaseLayout.vue +12 -11
- package/src/components/Layout/CanvasContainer.vue +30 -6
- package/src/components/MiniMenu/LayoutMiniMenu.vue +3 -4
- package/src/components/SideBar/LayoutSettingSideBar.vue +12 -4
- package/src/components/SideBar/LayoutSideBar.vue +36 -10
- package/src/core/engines/DisplayEngine.js +39 -28
- package/src/core/engines/FreeDropEngine.js +0 -1
- package/src/core/engines/GridDropEngine.js +0 -1
- package/src/core/engines/HistoryEngine.js +0 -1
- package/src/core/engines/LayoutEngine.js +50 -41
- package/src/core/managers/EngineManager.js +23 -13
- package/src/library/DisplayLibrary.js +2 -2
- package/src/library/LayoutLibrary.js +6 -18
package/package.json
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
name="px"
|
|
23
23
|
aria-label="픽셀 단위 크기"
|
|
24
24
|
min="600"
|
|
25
|
-
max="
|
|
25
|
+
max="1200"
|
|
26
26
|
step="50"
|
|
27
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"
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
name="percent"
|
|
35
35
|
aria-label="퍼센트 단위 크기"
|
|
36
36
|
min="0"
|
|
37
|
-
max="
|
|
37
|
+
max="200"
|
|
38
38
|
step="10"
|
|
39
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"
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
<input
|
|
20
20
|
type="checkbox"
|
|
21
21
|
:checked="activeConfig?.showGuideLine"
|
|
22
|
-
@change="
|
|
22
|
+
@change="handleSectionConfigChange('guideLine', $event.target.checked)"
|
|
23
23
|
/>
|
|
24
24
|
</div>
|
|
25
25
|
</template>
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
max="50"
|
|
35
35
|
class="text-sm text-center w-6 border-b-2 border-white focus:outline-none focus:border-b-2 focus:border-blue-500"
|
|
36
36
|
:value="activeConfig?.gridRows"
|
|
37
|
-
@change="
|
|
37
|
+
@change="handleSectionConfigChange('row', $event.target.value)"
|
|
38
38
|
/>
|
|
39
39
|
</div>
|
|
40
40
|
<div class="flex items-center gap-1">
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
max="12"
|
|
47
47
|
class="text-sm text-center w-6 border-b-2 border-white focus:outline-none focus:border-b-2 focus:border-blue-500"
|
|
48
48
|
:value="activeConfig?.gridColumns"
|
|
49
|
-
@change="
|
|
49
|
+
@change="handleSectionConfigChange('column', $event.target.value)"
|
|
50
50
|
/>
|
|
51
51
|
</div>
|
|
52
52
|
<div class="flex items-center gap-1">
|
|
@@ -58,7 +58,7 @@
|
|
|
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
60
|
:value="activeConfig?.gridGap"
|
|
61
|
-
@change="
|
|
61
|
+
@change="handleSectionConfigChange('gap', $event.target.value)"
|
|
62
62
|
/>
|
|
63
63
|
</div>
|
|
64
64
|
</div>
|
|
@@ -80,6 +80,14 @@ const props = defineProps({
|
|
|
80
80
|
const { activeSection, activeMode, activeConfig } = toRefs(props.polarvo.layout.state);
|
|
81
81
|
const { setSectionMode, setSectionConfig } = props.polarvo.layout;
|
|
82
82
|
const sectionMode = computed(() => activeMode.value);
|
|
83
|
+
|
|
84
|
+
function handleSectionConfigChange(type, value) {
|
|
85
|
+
const { result, message } = setSectionConfig(type, value)
|
|
86
|
+
|
|
87
|
+
if (!result) {
|
|
88
|
+
alert(message)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
83
91
|
</script>
|
|
84
92
|
|
|
85
93
|
<style scoped lang="scss">
|
|
@@ -2,8 +2,9 @@
|
|
|
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'
|
|
5
|
+
:class="[layoutType + '-layout', `grid-cols-${gridNumber?.column}`, { 'mb-4': layoutType === 'three-column-split' }]"
|
|
6
6
|
:style="getBaseStyle"
|
|
7
|
+
@contextmenu.prevent
|
|
7
8
|
>
|
|
8
9
|
<template v-for="(section, key) in layoutData" :key="key">
|
|
9
10
|
<PolarLayout
|
|
@@ -50,9 +51,9 @@ import FreeLayout from './FreeLayout.vue';
|
|
|
50
51
|
import GridLayout from './GridLayout.vue';
|
|
51
52
|
|
|
52
53
|
// 우클릭 방지
|
|
53
|
-
window.oncontextmenu = () => {
|
|
54
|
-
|
|
55
|
-
};
|
|
54
|
+
// window.oncontextmenu = () => {
|
|
55
|
+
// return false;
|
|
56
|
+
// };
|
|
56
57
|
|
|
57
58
|
const emit = defineEmits(['click:section']);
|
|
58
59
|
const props = defineProps({
|
|
@@ -67,7 +68,7 @@ const props = defineProps({
|
|
|
67
68
|
});
|
|
68
69
|
|
|
69
70
|
const layoutType = computed(() => {
|
|
70
|
-
if (layoutName.value.eng
|
|
71
|
+
if (layoutName.value.eng === null) return 'no-split';
|
|
71
72
|
return layoutName.value.eng
|
|
72
73
|
.replace(/([A-Z])/g, '-$1')
|
|
73
74
|
.toLowerCase()
|
|
@@ -78,16 +79,16 @@ const { layoutName, layoutData, activeSection, gapSize, gridNumber, gridRatio }
|
|
|
78
79
|
|
|
79
80
|
const getBaseStyle = computed(() => {
|
|
80
81
|
return {
|
|
81
|
-
gridTemplateColumns: gridRatio.value?.column.map((ratio) => `${ratio}%`).join(' '),
|
|
82
|
-
gridTemplateRows: gridRatio.value?.row.map((ratio) => `${ratio}%`).join(' '),
|
|
83
|
-
marginRight: layoutType.value === 'no-split' ? '0' : layoutType.value === 'three-column-split' ? '14px' : '8px',
|
|
82
|
+
// gridTemplateColumns: gridRatio.value?.column.map((ratio) => `${ratio}%`).join(' '),
|
|
83
|
+
// gridTemplateRows: gridRatio.value?.row.map((ratio) => `${ratio}%`).join(' '),
|
|
84
|
+
// marginRight: layoutType.value === 'no-split' ? '0' : layoutType.value === 'three-column-split' ? '14px' : '8px',
|
|
84
85
|
gap: `${gapSize.value}px`,
|
|
85
86
|
};
|
|
86
87
|
});
|
|
87
88
|
|
|
88
89
|
const getSectionStyle = (section) => ({
|
|
89
|
-
'--grid-columns': section.config?.gridColumns
|
|
90
|
-
'--grid-gap': `${section.config?.gridGap
|
|
90
|
+
'--grid-columns': section.config?.gridColumns ?? 3,
|
|
91
|
+
'--grid-gap': `${section.config?.gridGap ?? 5}px`,
|
|
91
92
|
position: 'relative',
|
|
92
93
|
});
|
|
93
94
|
|
|
@@ -95,7 +96,7 @@ const getSectionClass = (key, section) => ({
|
|
|
95
96
|
[key]: true,
|
|
96
97
|
[section.mode]: true,
|
|
97
98
|
'section-active': activeSection.value === key,
|
|
98
|
-
disabled: activeSection.value
|
|
99
|
+
disabled: activeSection.value !== key && !props.preview,
|
|
99
100
|
});
|
|
100
101
|
</script>
|
|
101
102
|
|
|
@@ -23,10 +23,16 @@ const props = defineProps({
|
|
|
23
23
|
},
|
|
24
24
|
});
|
|
25
25
|
|
|
26
|
+
const DEFAULT_DISPLAY_SIZE = {
|
|
27
|
+
px: 1200,
|
|
28
|
+
percent: 100,
|
|
29
|
+
gridSize: 16,
|
|
30
|
+
aspectRatio: '16/9',
|
|
31
|
+
};
|
|
32
|
+
|
|
26
33
|
// height 관련 여유 공간 추가하여 계산함 (50, 200)
|
|
27
34
|
const { displaySize } = toRefs(props.polarvo.display.state);
|
|
28
35
|
const { elements } = toRefs(props.polarvo.layout.state);
|
|
29
|
-
const gridSize = computed(() => displaySize.value?.gridSize || 16);
|
|
30
36
|
const defaultHeight = computed(() => {
|
|
31
37
|
return (
|
|
32
38
|
elements.value?.reduce((max, el) => {
|
|
@@ -52,19 +58,37 @@ function expandContainer() {
|
|
|
52
58
|
}
|
|
53
59
|
|
|
54
60
|
const containerStyle = computed(() => {
|
|
61
|
+
if (!displaySize.value) {
|
|
62
|
+
return {
|
|
63
|
+
aspectRatio: DEFAULT_DISPLAY_SIZE.aspectRatio,
|
|
64
|
+
width: `${DEFAULT_DISPLAY_SIZE.px}px`,
|
|
65
|
+
height: `${displayHeight.value}px`,
|
|
66
|
+
margin: '0 auto',
|
|
67
|
+
transform: `scale(${DEFAULT_DISPLAY_SIZE.percent / 100})`,
|
|
68
|
+
transformOrigin: 'top center',
|
|
69
|
+
backgroundImage: `
|
|
70
|
+
linear-gradient(rgba(0,0,0,0.05) 1px, transparent 1px),
|
|
71
|
+
linear-gradient(90deg, rgba(0,0,0,0.05) 1px, transparent 1px)
|
|
72
|
+
`,
|
|
73
|
+
backgroundSize: `${DEFAULT_DISPLAY_SIZE.gridSize}px ${DEFAULT_DISPLAY_SIZE.gridSize}px`,
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
55
77
|
return {
|
|
56
|
-
aspectRatio: displaySize.value
|
|
57
|
-
width: `${displaySize.value
|
|
78
|
+
aspectRatio: displaySize.value.aspectRatio,
|
|
79
|
+
width: `${displaySize.value.px}px`,
|
|
58
80
|
height: `${displayHeight.value}px`,
|
|
59
|
-
margin:
|
|
60
|
-
transform: `scale(${displaySize.value
|
|
81
|
+
margin: '0 auto',
|
|
82
|
+
transform: `scale(${displaySize.value.percent / 100})`,
|
|
61
83
|
transformOrigin: 'top center',
|
|
62
84
|
backgroundImage: `
|
|
63
85
|
linear-gradient(rgba(0,0,0,0.05) 1px, transparent 1px),
|
|
64
86
|
linear-gradient(90deg, rgba(0,0,0,0.05) 1px, transparent 1px)
|
|
65
87
|
`,
|
|
66
|
-
backgroundSize: `${
|
|
88
|
+
backgroundSize: `${displaySize.value.gridSize}px ${displaySize.value.gridSize}px`,
|
|
67
89
|
};
|
|
90
|
+
|
|
91
|
+
|
|
68
92
|
});
|
|
69
93
|
|
|
70
94
|
// 전체 elements
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<div class="grid gap-1 grid-cols-1 mx-1 mb-3">
|
|
6
6
|
<div
|
|
7
7
|
class="item flex flex-col items-center gap-1 p-2 border border-gray-200 rounded-md bg-white cursor-pointer hover:border-gray-400"
|
|
8
|
-
:class="{ active: layoutName.eng
|
|
8
|
+
:class="{ active: layoutName.eng === 'NoSplit' }"
|
|
9
9
|
@click="setLayoutName('NoSplit')"
|
|
10
10
|
>
|
|
11
11
|
<component class="text-gray-600" :is="layoutSource.noSplit.icon"></component>
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
v-for="(layout, key) in layoutSource.split"
|
|
19
19
|
:key="key"
|
|
20
20
|
class="item flex flex-col items-center gap-1 p-2 border border-gray-200 rounded-md bg-white cursor-pointer hover:border-gray-400"
|
|
21
|
-
:class="{ active: layoutName.eng
|
|
21
|
+
:class="{ active: layoutName.eng === key }"
|
|
22
22
|
@click="setLayoutName(key)"
|
|
23
23
|
>
|
|
24
24
|
<component class="text-gray-600" :is="layout.icon"></component>
|
|
@@ -45,8 +45,7 @@ watch(
|
|
|
45
45
|
() => layoutName.value,
|
|
46
46
|
() => {
|
|
47
47
|
emits('click:layout');
|
|
48
|
-
}
|
|
49
|
-
{ deep: true }
|
|
48
|
+
}
|
|
50
49
|
);
|
|
51
50
|
</script>
|
|
52
51
|
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
min="1"
|
|
27
27
|
max="50"
|
|
28
28
|
:value="activeConfig.gridRows"
|
|
29
|
-
@change="
|
|
29
|
+
@change="handleSectionConfigChange('row', $event.target.value)"
|
|
30
30
|
/>
|
|
31
31
|
</div>
|
|
32
32
|
<div class="flex flex-row items-center gap-4">
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
min="1"
|
|
39
39
|
max="12"
|
|
40
40
|
:value="activeConfig.gridColumns"
|
|
41
|
-
@change="
|
|
41
|
+
@change="handleSectionConfigChange('column', $event.target.value)"
|
|
42
42
|
/>
|
|
43
43
|
</div>
|
|
44
44
|
<div class="flex flex-row items-center gap-4">
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
min="0"
|
|
51
51
|
max="12"
|
|
52
52
|
:value="activeConfig.gridGap"
|
|
53
|
-
@change="
|
|
53
|
+
@change="handleSectionConfigChange('gap', $event.target.value)"
|
|
54
54
|
/>
|
|
55
55
|
</div>
|
|
56
56
|
</div>
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
type="checkbox"
|
|
65
65
|
id="guide-line"
|
|
66
66
|
:checked="activeConfig.showGuideLine"
|
|
67
|
-
@change="
|
|
67
|
+
@change="handleSectionConfigChange('guideLine', $event.target.checked)"
|
|
68
68
|
/>
|
|
69
69
|
</div>
|
|
70
70
|
</div>
|
|
@@ -85,6 +85,14 @@ const { activeMode, activeConfig } = toRefs(props.polarvo.layout.state);
|
|
|
85
85
|
const { setSectionMode, setSectionConfig } = props.polarvo.layout;
|
|
86
86
|
|
|
87
87
|
const sectionMode = computed(() => activeMode.value);
|
|
88
|
+
|
|
89
|
+
function handleSectionConfigChange(type, value) {
|
|
90
|
+
const { result, message } = setSectionConfig(type, value)
|
|
91
|
+
|
|
92
|
+
if (!result) {
|
|
93
|
+
alert(message)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
88
96
|
</script>
|
|
89
97
|
|
|
90
98
|
<style lang="scss" scoped></style>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div>
|
|
3
|
-
<div class="aspect-[3/2] border border-gray-200 rounded-md bg-white
|
|
3
|
+
<div class="aspect-[3/2] border border-gray-200 rounded-md bg-white">
|
|
4
4
|
<BaseLayout id="preview" :polarvo="polarvo" :preview="true" @click:section="setActiveSection($event)" />
|
|
5
5
|
<button
|
|
6
6
|
class="text-sm bg-blue-100 text-center w-full py-1 hover:cursor-pointer hover:bg-blue-200 active:opacity-80"
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
</button>
|
|
11
11
|
</div>
|
|
12
12
|
|
|
13
|
-
<div v-if="layoutName.eng
|
|
13
|
+
<div v-if="layoutName.eng !== 'NoSplit'">
|
|
14
14
|
<h5 class="mt-4 block text-sm font-bold text-gray-600">레이아웃 설정</h5>
|
|
15
15
|
<div class="mt-2 border border-gray-200 rounded-md bg-gray-100">
|
|
16
16
|
<div class="px-4 py-2">
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
type="number"
|
|
22
22
|
id="section-gap"
|
|
23
23
|
:value="gapSize"
|
|
24
|
-
@change="
|
|
24
|
+
@change="handleGapSize($event)"
|
|
25
25
|
/>
|
|
26
26
|
</div>
|
|
27
27
|
</div>
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
type="number"
|
|
36
36
|
:id="`col-ratio-${column}`"
|
|
37
37
|
:value="gridRatio.column[column - 1]"
|
|
38
|
-
@change="
|
|
38
|
+
@change="handleGridRatio('column', column - 1, $event)"
|
|
39
39
|
/>
|
|
40
40
|
</template>
|
|
41
41
|
</div>
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
type="number"
|
|
49
49
|
:id="`row-ratio-${row}`"
|
|
50
50
|
:value="gridRatio.row[row - 1]"
|
|
51
|
-
@change="
|
|
51
|
+
@change="handleGridRatio('row', row - 1, $event)"
|
|
52
52
|
/>
|
|
53
53
|
</template>
|
|
54
54
|
</div>
|
|
@@ -60,11 +60,7 @@
|
|
|
60
60
|
현재 활성화 섹션:
|
|
61
61
|
<span class="text-sm text-red-400 font-medium cursor-default">{{ activeSection }}</span>
|
|
62
62
|
</h5>
|
|
63
|
-
<LayoutSetting
|
|
64
|
-
v-if="activeSection !== null"
|
|
65
|
-
:polarvo="polarvo"
|
|
66
|
-
class="mt-2 border border-gray-200 rounded-md bg-white"
|
|
67
|
-
/>
|
|
63
|
+
<LayoutSetting v-if="activeSection !== null" :polarvo="polarvo" class="mt-2 border border-gray-200 rounded-md bg-white" />
|
|
68
64
|
<div v-else>
|
|
69
65
|
<div class="text-center text-sm text-red-400 bg-red-50 mt-1 py-1 cursor-default">선택된 섹션이 없습니다.</div>
|
|
70
66
|
</div>
|
|
@@ -86,4 +82,34 @@ const props = defineProps({
|
|
|
86
82
|
});
|
|
87
83
|
const { layoutName, activeSection, gapSize, gridNumber, gridRatio } = toRefs(props.polarvo.layout.state);
|
|
88
84
|
const { setGapSize, setRatio, setActiveSection } = props.polarvo.layout;
|
|
85
|
+
|
|
86
|
+
function handleGridRatio(type, index, event) {
|
|
87
|
+
const value = event.target.value;
|
|
88
|
+
if (Number(value) > 100 || Number(value) < 0 || isNaN(Number(value))) {
|
|
89
|
+
alert('비율은 0에서 100 사이의 숫자여야 합니다.');
|
|
90
|
+
event.target.value = gridRatio.value[type][index]; // 원래 값으로 복원
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const { result, message } = setRatio(type, index, Number(value));
|
|
95
|
+
|
|
96
|
+
if (!result) {
|
|
97
|
+
alert(message);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function handleGapSize(event) {
|
|
102
|
+
const value = event.target.value;
|
|
103
|
+
if (Number(value) < 0 || isNaN(Number(value))) {
|
|
104
|
+
alert('갭 크기는 0 이상의 숫자여야 합니다.');
|
|
105
|
+
event.target.value = gapSize.value; // 원래 값으로 복원
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const { result, message } = setGapSize(Number(value));
|
|
110
|
+
|
|
111
|
+
if (!result) {
|
|
112
|
+
alert(message);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
89
115
|
</script>
|
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
// event 발행 시 'display'으로 시작하는 이름 사용
|
|
2
|
+
const DISPLAY_LIMITS = {
|
|
3
|
+
percent: { max: 200, min: 0, unit: '%', name: '배율' },
|
|
4
|
+
px: { max: 1200, min: 1, unit: 'px', name: '크기' },
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
const DEFAULT_DISPLAY_SIZE = {
|
|
8
|
+
px: 1200,
|
|
9
|
+
percent: 100,
|
|
10
|
+
gridSize: 16,
|
|
11
|
+
aspectRatio: '16/9',
|
|
12
|
+
};
|
|
13
|
+
|
|
2
14
|
class DisplayEngine {
|
|
3
15
|
constructor({ eventBus, options }) {
|
|
4
16
|
this.eventBus = eventBus;
|
|
@@ -9,6 +21,8 @@ class DisplayEngine {
|
|
|
9
21
|
this.displayMode = 'desktop'; // 현재 디스플레이 모드 (초기값 = 'desktop')
|
|
10
22
|
this.displaySize = null;
|
|
11
23
|
|
|
24
|
+
this._resetDisplaySizeByMode(this.displayMode);
|
|
25
|
+
|
|
12
26
|
this._subscriptions = []; // 구독 해제 함수 저장용
|
|
13
27
|
this._setupSubscriptions();
|
|
14
28
|
}
|
|
@@ -29,8 +43,6 @@ class DisplayEngine {
|
|
|
29
43
|
_setupSubscriptions() {
|
|
30
44
|
// 초기화 완료 이벤트 구독 - from EngineManager
|
|
31
45
|
this._subscribe('system:engineInitialized', () => {
|
|
32
|
-
this._resetDisplaySizeByMode(this.displayMode);
|
|
33
|
-
|
|
34
46
|
this.eventBus.emit('display:engineInitialized', {
|
|
35
47
|
$displaySize: this.displaySize,
|
|
36
48
|
timestamp: Date.now(),
|
|
@@ -71,8 +83,6 @@ class DisplayEngine {
|
|
|
71
83
|
this.displayMode = 'desktop';
|
|
72
84
|
this._resetDisplaySizeByMode(this.displayMode);
|
|
73
85
|
|
|
74
|
-
console.log('[DisplayEngine] 리셋 완료');
|
|
75
|
-
|
|
76
86
|
this.eventBus.emit('display:enginesReset', {
|
|
77
87
|
$displaySize: this.displaySize,
|
|
78
88
|
timestamp: Date.now(),
|
|
@@ -124,7 +134,6 @@ class DisplayEngine {
|
|
|
124
134
|
}
|
|
125
135
|
|
|
126
136
|
/** ---------------------------------- 공통 메소드 ---------------------------------- **/
|
|
127
|
-
|
|
128
137
|
/** 아이콘바 메뉴 변경
|
|
129
138
|
* @param {string} name - 메뉴 이름
|
|
130
139
|
*/
|
|
@@ -180,14 +189,19 @@ class DisplayEngine {
|
|
|
180
189
|
* @param {string} mode - 디스플레이 모드
|
|
181
190
|
*/
|
|
182
191
|
_resetDisplaySizeByMode(mode) {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
gridSize: this.resource[mode]?.gridSize || 16,
|
|
188
|
-
aspectRatio: this.resource[mode]?.aspectRatio || '16/9',
|
|
189
|
-
};
|
|
192
|
+
const defaults = DEFAULT_DISPLAY_SIZE;
|
|
193
|
+
if (!this.resource[mode]) {
|
|
194
|
+
this.displaySize = { ...defaults };
|
|
195
|
+
return;
|
|
190
196
|
}
|
|
197
|
+
|
|
198
|
+
const resource = this.resource[mode];
|
|
199
|
+
this.displaySize = {
|
|
200
|
+
px: resource.defaultWidth ?? defaults.px,
|
|
201
|
+
percent: defaults.percent,
|
|
202
|
+
gridSize: resource.gridSize ?? defaults.gridSize,
|
|
203
|
+
aspectRatio: resource.aspectRatio ?? defaults.aspectRatio,
|
|
204
|
+
};
|
|
191
205
|
}
|
|
192
206
|
|
|
193
207
|
/** displaySize 변경
|
|
@@ -196,29 +210,24 @@ class DisplayEngine {
|
|
|
196
210
|
*/
|
|
197
211
|
setDisplaySize(type, size) {
|
|
198
212
|
if (!['px', 'percent'].includes(type)) {
|
|
199
|
-
|
|
200
|
-
return;
|
|
213
|
+
return { result: false, message: '입력할 수 없는 사이즈 타입입니다.' };
|
|
201
214
|
}
|
|
202
|
-
|
|
203
215
|
const numSize = Number(size);
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
216
|
+
const limit = DISPLAY_LIMITS[type];
|
|
217
|
+
const result = {result: true, message: '사이즈가 성공적으로 변경되었습니다.'};
|
|
208
218
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
}
|
|
219
|
+
if (isNaN(numSize) || numSize <= limit.min) {
|
|
220
|
+
result.result = false;
|
|
221
|
+
result.message = `설정 가능한 최소 ${limit.name}는 ${limit.min}${limit.unit}입니다.`;
|
|
222
|
+
}
|
|
213
223
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
224
|
+
if (isNaN(numSize) || numSize > limit.max) {
|
|
225
|
+
result.result = false;
|
|
226
|
+
result.message = `설정 가능한 최대 ${limit.name}는 ${limit.max}${limit.unit}입니다.`;
|
|
217
227
|
}
|
|
218
228
|
|
|
219
229
|
const prevDisplaySize = { ...this.displaySize };
|
|
220
|
-
this.displaySize[type] = Math.min(Math.max(0, numSize),
|
|
221
|
-
|
|
230
|
+
this.displaySize[type] = Math.min(Math.max(0, numSize), limit.max);
|
|
222
231
|
this.eventBus.emit('display:displayChanged', {
|
|
223
232
|
type: 'size',
|
|
224
233
|
$displayMode: this.displayMode,
|
|
@@ -226,6 +235,8 @@ class DisplayEngine {
|
|
|
226
235
|
prev: { displaySize: prevDisplaySize },
|
|
227
236
|
timestamp: Date.now(),
|
|
228
237
|
});
|
|
238
|
+
|
|
239
|
+
return result;
|
|
229
240
|
}
|
|
230
241
|
}
|
|
231
242
|
|
|
@@ -32,7 +32,7 @@ class LayoutEngine {
|
|
|
32
32
|
/** [내부함수] 구독 설정 */
|
|
33
33
|
_setupSubscriptions() {
|
|
34
34
|
// 초기화 완료 이벤트 구독 - from EngineManager
|
|
35
|
-
this._subscribe('system:engineInitialized', () => {});
|
|
35
|
+
// this._subscribe('system:engineInitialized', () => {});
|
|
36
36
|
|
|
37
37
|
this._subscribe('system:updateActiveSectionConfig', ({ $section, changes }) => {
|
|
38
38
|
Object.keys(changes).forEach((key) => {
|
|
@@ -63,6 +63,7 @@ class LayoutEngine {
|
|
|
63
63
|
});
|
|
64
64
|
|
|
65
65
|
this._subscribe('system:restoredState', ({ stateData }) => {
|
|
66
|
+
if (!stateData?.layoutData) return;
|
|
66
67
|
Object.assign(this.layoutData, stateData.layoutData);
|
|
67
68
|
});
|
|
68
69
|
}
|
|
@@ -209,27 +210,17 @@ class LayoutEngine {
|
|
|
209
210
|
|
|
210
211
|
const newLayoutData = cloneDeep(this.resource[name].sections);
|
|
211
212
|
|
|
212
|
-
// userData 있는 경우 우선 병합
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
else if (this.layoutData) {
|
|
222
|
-
{
|
|
223
|
-
Object.keys(newLayoutData).forEach((sectionKey) => {
|
|
224
|
-
if (this.layoutData[sectionKey]) {
|
|
225
|
-
newLayoutData[sectionKey] = {
|
|
226
|
-
...newLayoutData[sectionKey],
|
|
227
|
-
...this.layoutData[sectionKey],
|
|
228
|
-
};
|
|
229
|
-
}
|
|
230
|
-
});
|
|
213
|
+
// userData 있는 경우 우선 병합 - 기존 데이터 유지하면서 userData로 덮어쓰기
|
|
214
|
+
const mergeSource = userData || this.layoutData;
|
|
215
|
+
|
|
216
|
+
Object.keys(newLayoutData).forEach((sectionKey) => {
|
|
217
|
+
if (mergeSource[sectionKey]) {
|
|
218
|
+
newLayoutData[sectionKey] = {
|
|
219
|
+
...newLayoutData[sectionKey],
|
|
220
|
+
...mergeSource[sectionKey],
|
|
221
|
+
};
|
|
231
222
|
}
|
|
232
|
-
}
|
|
223
|
+
});
|
|
233
224
|
|
|
234
225
|
this.layoutData = newLayoutData;
|
|
235
226
|
}
|
|
@@ -248,18 +239,26 @@ class LayoutEngine {
|
|
|
248
239
|
this.gridRatio.column = gridRatio.column;
|
|
249
240
|
} else {
|
|
250
241
|
this.gridRatio.column = columnNumber === 1 ? [100] : Array(columnNumber).fill(Math.floor(100 / columnNumber));
|
|
242
|
+
|
|
243
|
+
if (columnNumber === 3) {
|
|
244
|
+
this.gridRatio.column[2] = 100 - this.gridRatio.column[0] - this.gridRatio.column[1];
|
|
245
|
+
}
|
|
251
246
|
}
|
|
252
247
|
|
|
253
248
|
if (gridRatio?.row) {
|
|
254
249
|
this.gridRatio.row = gridRatio.row;
|
|
255
250
|
} else {
|
|
256
251
|
this.gridRatio.row = rowNumber === 1 ? [100] : Array(rowNumber).fill(Math.floor(100 / rowNumber));
|
|
252
|
+
|
|
253
|
+
if (rowNumber === 3) {
|
|
254
|
+
this.gridRatio.row[2] = 100 - this.gridRatio.row[0] - this.gridRatio.row[1];
|
|
255
|
+
}
|
|
257
256
|
}
|
|
258
257
|
|
|
259
258
|
this.gridNumber.column = columnNumber;
|
|
260
259
|
this.gridNumber.row = rowNumber;
|
|
261
260
|
|
|
262
|
-
if (gapSize) {
|
|
261
|
+
if (gapSize != null) {
|
|
263
262
|
this.gapSize = gapSize;
|
|
264
263
|
}
|
|
265
264
|
}
|
|
@@ -274,6 +273,8 @@ class LayoutEngine {
|
|
|
274
273
|
$gapSize: this.gapSize,
|
|
275
274
|
timestamp: Date.now(),
|
|
276
275
|
});
|
|
276
|
+
|
|
277
|
+
return { result: true, message: '갭 사이즈가 성공적으로 변경되었습니다.' };
|
|
277
278
|
}
|
|
278
279
|
|
|
279
280
|
/** 섹션 비율 변경
|
|
@@ -282,7 +283,7 @@ class LayoutEngine {
|
|
|
282
283
|
* @param {number} ratio - 새로운 비율 값
|
|
283
284
|
*/
|
|
284
285
|
setRatio(type, index, ratio) {
|
|
285
|
-
const ratioArray =
|
|
286
|
+
const ratioArray = this.gridRatio[type];
|
|
286
287
|
ratioArray[index] = Number(ratio);
|
|
287
288
|
|
|
288
289
|
const totalWithout = ratioArray.reduce((sum, val, idx) => {
|
|
@@ -295,34 +296,42 @@ class LayoutEngine {
|
|
|
295
296
|
}
|
|
296
297
|
|
|
297
298
|
const total = totalWithout + ratioArray[index];
|
|
299
|
+
const result = { result: true, message: '비율이 성공적으로 변경되었습니다.' };
|
|
300
|
+
|
|
298
301
|
if (total == 100) {
|
|
299
|
-
return;
|
|
300
|
-
}
|
|
301
|
-
if (ratioArray.length == 2) {
|
|
302
|
-
index == 0 ? (ratioArray[1] = 100 - ratioArray[0]) : (ratioArray[0] = 100 - ratioArray[1]);
|
|
303
|
-
} else if (ratioArray.length == 3) {
|
|
304
|
-
const remaining = 100 - total;
|
|
305
|
-
if (remaining < 0) {
|
|
306
|
-
alert('비율의 합은 100을 초과할 수 없습니다.');
|
|
307
|
-
}
|
|
302
|
+
return result;
|
|
303
|
+
}
|
|
308
304
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
305
|
+
if (ratioArray.length == 2) {
|
|
306
|
+
index == 0 ? (ratioArray[1] = 100 - ratioArray[0]) : (ratioArray[0] = 100 - ratioArray[1]);
|
|
307
|
+
} else if (ratioArray.length == 3) {
|
|
308
|
+
const remaining = 100 - total;
|
|
309
|
+
if (remaining < 0) {
|
|
310
|
+
result.result = false;
|
|
311
|
+
result.message = '비율의 합은 100을 초과할 수 없습니다.';
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (index == 2) {
|
|
315
|
+
ratioArray[0] = ratioArray[0] - Math.abs(remaining);
|
|
316
|
+
} else {
|
|
317
|
+
ratioArray[index + 1] = ratioArray[index + 1] - Math.abs(remaining);
|
|
318
|
+
|
|
319
|
+
if (ratioArray[index + 1] < 0) {
|
|
320
|
+
ratioArray[index + 1] = 0;
|
|
321
|
+
ratioArray[2] = 100 - ratioArray[0] - ratioArray[1];
|
|
313
322
|
}
|
|
314
323
|
}
|
|
315
324
|
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
} else if (type === 'row') {
|
|
319
|
-
this.gridRatio.row = ratioArray;
|
|
320
|
-
}
|
|
325
|
+
|
|
326
|
+
this.gridRatio[type] = ratioArray;
|
|
321
327
|
|
|
322
328
|
this.eventBus.emit('layout:updateRatio', {
|
|
329
|
+
$type: type,
|
|
323
330
|
$gridRatio: this.gridRatio,
|
|
324
331
|
timestamp: Date.now(),
|
|
325
332
|
});
|
|
333
|
+
|
|
334
|
+
return result;
|
|
326
335
|
}
|
|
327
336
|
}
|
|
328
337
|
|
|
@@ -9,6 +9,11 @@ const { dataConverter, calculateAspectRatio } = utils;
|
|
|
9
9
|
import createDefaultConfig from '../../configs/index.js';
|
|
10
10
|
const { initialState, resource } = createDefaultConfig();
|
|
11
11
|
|
|
12
|
+
const CONFIG_LIMITS = {
|
|
13
|
+
column: { min: 1, max: 12, name: '열 개수' },
|
|
14
|
+
row: { min: 1, max: 50, name: '행 개수' },
|
|
15
|
+
gap: { min: 0, max: 12, name: '갭 크기' },
|
|
16
|
+
};
|
|
12
17
|
// event 발행 시 'system'으로 시작하는 이름 사용
|
|
13
18
|
class EngineManager {
|
|
14
19
|
constructor(config = {}) {
|
|
@@ -241,7 +246,7 @@ class EngineManager {
|
|
|
241
246
|
if (!this.activeData || this.activeData.mode === mode) return;
|
|
242
247
|
const updates = {
|
|
243
248
|
mode: mode,
|
|
244
|
-
elementIds: this.state.elements.filter((x) => x.mode === mode).map((x) => x.id),
|
|
249
|
+
elementIds: this.state.elements.filter((x) => x.section === this.state.activeSection && x.mode === mode).map((x) => x.id),
|
|
245
250
|
};
|
|
246
251
|
|
|
247
252
|
if (mode === 'grid') {
|
|
@@ -267,17 +272,18 @@ class EngineManager {
|
|
|
267
272
|
*/
|
|
268
273
|
setSectionConfig(type, config) {
|
|
269
274
|
if (!['column', 'row', 'gap', 'guideLine'].includes(type)) {
|
|
270
|
-
|
|
271
|
-
return;
|
|
275
|
+
return { result: false, message: '입력할 수 없는 config 타입입니다.' };
|
|
272
276
|
}
|
|
273
277
|
|
|
274
278
|
if (!this.activeData) return;
|
|
275
279
|
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
}
|
|
280
|
+
const limit = CONFIG_LIMITS[type];
|
|
281
|
+
const result = { result: true, message: 'config가 성공적으로 변경되었습니다.' };
|
|
282
|
+
// const limits = {
|
|
283
|
+
// column: { min: 1, max: 12, name: '열 개수' },
|
|
284
|
+
// row: { min: 1, max: 50, name: '행 개수' },
|
|
285
|
+
// gap: { min: 0, max: 12, name: '갭 크기' },
|
|
286
|
+
// };
|
|
281
287
|
|
|
282
288
|
let newConfig = {};
|
|
283
289
|
let value = config;
|
|
@@ -285,13 +291,15 @@ class EngineManager {
|
|
|
285
291
|
if (type === 'guideLine') {
|
|
286
292
|
newConfig = { showGuideLine: value };
|
|
287
293
|
} else {
|
|
288
|
-
if (value <
|
|
289
|
-
|
|
294
|
+
if (value < limit.min) {
|
|
295
|
+
result.result = false;
|
|
296
|
+
result.message = `설정 가능한 최소 ${limit.name}는 ${limit.min}입니다.`;
|
|
290
297
|
}
|
|
291
|
-
if (value >
|
|
292
|
-
|
|
298
|
+
if (value > limit.max) {
|
|
299
|
+
result.result = false;
|
|
300
|
+
result.message = `설정 가능한 최대 ${limit.name}는 ${limit.max}입니다.`;
|
|
293
301
|
}
|
|
294
|
-
value = Math.min(Math.max(
|
|
302
|
+
value = Math.min(Math.max(limit.min, value), limit.max);
|
|
295
303
|
|
|
296
304
|
if (type === 'column') {
|
|
297
305
|
newConfig = { gridColumns: value };
|
|
@@ -304,6 +312,8 @@ class EngineManager {
|
|
|
304
312
|
|
|
305
313
|
const updates = { config: { ...this.activeData.config, ...newConfig } };
|
|
306
314
|
this._updateActiveSectionConfig('config', updates);
|
|
315
|
+
|
|
316
|
+
return result;
|
|
307
317
|
}
|
|
308
318
|
|
|
309
319
|
/** ---------------------------------- freeDrop Engine ---------------------------------- **/
|
|
@@ -115,9 +115,9 @@ function useDisplay(api) {
|
|
|
115
115
|
|
|
116
116
|
const setDisplaySize = (type, size) => {
|
|
117
117
|
try {
|
|
118
|
-
api.display.setDisplaySize(type, size);
|
|
118
|
+
return api.display.setDisplaySize(type, size);
|
|
119
119
|
} catch (error) {
|
|
120
|
-
console.error('[
|
|
120
|
+
console.error('[DisplayLibrary] setDisplaySize 실행 중 오류 발생:', error);
|
|
121
121
|
}
|
|
122
122
|
};
|
|
123
123
|
|
|
@@ -65,7 +65,7 @@ function useLayout(api) {
|
|
|
65
65
|
state.layoutData = $layoutData;
|
|
66
66
|
state.gridNumber = $gridNumber;
|
|
67
67
|
state.gridRatio = $gridRatio;
|
|
68
|
-
if ($gapSize) {
|
|
68
|
+
if ($gapSize != null) {
|
|
69
69
|
state.gapSize = $gapSize;
|
|
70
70
|
}
|
|
71
71
|
});
|
|
@@ -76,8 +76,8 @@ function useLayout(api) {
|
|
|
76
76
|
});
|
|
77
77
|
|
|
78
78
|
// gridRatio 변경 감지
|
|
79
|
-
_subscribe('layout:updateRatio', ({ $gridRatio }) => {
|
|
80
|
-
state.gridRatio = $
|
|
79
|
+
_subscribe('layout:updateRatio', ({ $type, $gridRatio }) => {
|
|
80
|
+
state.gridRatio[$type] = [...$gridRatio[$type]];
|
|
81
81
|
});
|
|
82
82
|
|
|
83
83
|
// activeSection 변경 감지 (activeData가 함께 초기화됨)
|
|
@@ -118,9 +118,6 @@ function useLayout(api) {
|
|
|
118
118
|
state.elements = stateData.elements;
|
|
119
119
|
state.layoutData = stateData.layoutData;
|
|
120
120
|
state.activeSection = stateData.activeSection;
|
|
121
|
-
|
|
122
|
-
// state.layoutData = cloneDeep(api.layout.getLayoutData());
|
|
123
|
-
// state.activeData = cloneDeep(api.layout.getActiveData());
|
|
124
121
|
});
|
|
125
122
|
|
|
126
123
|
_subscribe('layout:restoredState', ({ stateData }) => {
|
|
@@ -180,7 +177,7 @@ function useLayout(api) {
|
|
|
180
177
|
|
|
181
178
|
const setGapSize = (size) => {
|
|
182
179
|
try {
|
|
183
|
-
api.layout.setGapSize(size);
|
|
180
|
+
return api.layout.setGapSize(size);
|
|
184
181
|
} catch (error) {
|
|
185
182
|
console.error('[useLayout] setGapSize 실행 중 오류 발생:', error);
|
|
186
183
|
}
|
|
@@ -188,20 +185,12 @@ function useLayout(api) {
|
|
|
188
185
|
|
|
189
186
|
const setRatio = (type, index, ratio) => {
|
|
190
187
|
try {
|
|
191
|
-
api.layout.setRatio(type, index, ratio);
|
|
188
|
+
return api.layout.setRatio(type, index, ratio);
|
|
192
189
|
} catch (error) {
|
|
193
190
|
console.error('[useLayout] setRatio 실행 중 오류 발생:', error);
|
|
194
191
|
}
|
|
195
192
|
};
|
|
196
193
|
|
|
197
|
-
// const setUserLayoutData = (userData) => {
|
|
198
|
-
// try {
|
|
199
|
-
// api.layout.setUserLayoutData(userData);
|
|
200
|
-
// } catch (error) {
|
|
201
|
-
// console.error('[useLayout] setUserLayoutData 실행 중 오류 발생:', error);
|
|
202
|
-
// }
|
|
203
|
-
// };
|
|
204
|
-
|
|
205
194
|
const setActiveSection = (name) => {
|
|
206
195
|
try {
|
|
207
196
|
api.layout.setActiveSection(name);
|
|
@@ -236,7 +225,7 @@ function useLayout(api) {
|
|
|
236
225
|
|
|
237
226
|
const setSectionConfig = (type, config) => {
|
|
238
227
|
try {
|
|
239
|
-
api.layout.setSectionConfig(type, config);
|
|
228
|
+
return api.layout.setSectionConfig(type, config);
|
|
240
229
|
} catch (error) {
|
|
241
230
|
console.error('[useLayout] setSectionConfig 실행 중 오류 발생:', error);
|
|
242
231
|
}
|
|
@@ -250,7 +239,6 @@ function useLayout(api) {
|
|
|
250
239
|
setLayoutName,
|
|
251
240
|
setGapSize,
|
|
252
241
|
setRatio,
|
|
253
|
-
// setUserLayoutData,
|
|
254
242
|
setActiveSection,
|
|
255
243
|
setElements,
|
|
256
244
|
updateActiveElement,
|