@mixd-id/web-scaffold 0.1.230406146 → 0.1.230406147

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,7 +1,7 @@
1
1
  {
2
2
  "name": "@mixd-id/web-scaffold",
3
3
  "private": false,
4
- "version": "0.1.230406146",
4
+ "version": "0.1.230406147",
5
5
  "scripts": {
6
6
  "dev": "vite serve",
7
7
  "build": "vite build",
@@ -69,7 +69,6 @@ export default{
69
69
  this.alert = obj
70
70
  this.isOpen = true
71
71
  this.showDetails = false
72
- console.log('alert', this.alert)
73
72
  },
74
73
 
75
74
  close(){
@@ -1,9 +1,19 @@
1
1
  <template>
2
2
 
3
- <Teleport to=".dG9h">
4
- <Transition name="dG9i">
5
- <div v-if="state" :class="$style.toast" @mouseover="onMouseOver">
6
- <slot></slot>
3
+ <Teleport to=".YWxl">
4
+ <Transition name="YWxl">
5
+ <div v-if="isOpen" :class="$style.toast">
6
+ <slot>
7
+ <div class="p-6">
8
+ <div ref="item" :class="$style.item" @mouseover="onMouseOver" @mouseout="onMouseOut">
9
+ <div></div>
10
+ <div class="flex-1 break-all whitespace-pre-line">{{ text }}</div>
11
+ <div>
12
+ <button type="button" class="text-sm" @click="close">Dismiss</button>
13
+ </div>
14
+ </div>
15
+ </div>
16
+ </slot>
7
17
  </div>
8
18
  </Transition>
9
19
  </Teleport>
@@ -14,48 +24,54 @@
14
24
 
15
25
  export default{
16
26
 
17
- props: {
18
-
19
- state: [ Boolean, Number, String ],
20
-
21
- dismissAfter: {
22
- type: [ Number, String ],
23
- default: 3000
24
- }
25
-
26
- },
27
-
28
- emits: [ 'dismiss' ],
27
+ methods: {
29
28
 
30
- data(){
31
- return {
32
- intervalId: null
33
- }
34
- },
29
+ /**
30
+ * @param obj
31
+ * @param obj.text {string} - The text of the toast.
32
+ */
33
+ open(obj){
34
+ this.toast = obj
35
+ this.isOpen = true
36
+ this.intervalId = window.setTimeout(() => this.close(), 3000)
37
+ },
35
38
 
36
- methods: {
39
+ close(){
40
+ this.isOpen = false
41
+ },
37
42
 
38
43
  onMouseOver(){
39
44
  if(this.intervalId){
40
45
  window.clearTimeout(this.intervalId)
41
46
  this.intervalId = null
42
47
  }
43
- }
48
+ },
49
+
50
+ onMouseOut(e){
51
+ this.intervalId = window.setTimeout(() => this.close(), 3000)
52
+ },
44
53
 
45
54
  },
46
55
 
47
- watch:{
56
+ computed: {
48
57
 
49
- state(to){
50
- if(to){
51
- this.intervalId = window.setTimeout(() => {
52
- if(this.state){
53
- this.$emit('dismiss')
54
- }
55
- }, parseInt(this.dismissAfter))
58
+ text(){
59
+ if(this.toast.text){
60
+ return this.toast.text
61
+ }
62
+ else if(this.toast.message){
63
+ return this.toast.message
56
64
  }
57
65
  }
58
66
 
67
+ },
68
+
69
+ data(){
70
+ return {
71
+ intervalId: null,
72
+ isOpen: false,
73
+ toast: {},
74
+ }
59
75
  }
60
76
 
61
77
  }
@@ -64,18 +80,18 @@ export default{
64
80
 
65
81
  <style>
66
82
 
67
- .dG9h{
83
+ .YWxl{
68
84
  @apply fixed top-0 left-0 right-0;
69
85
  z-index: 61;
70
86
  }
71
87
 
72
- .dG9i-enter-active,
73
- .dG9i-leave-active {
88
+ .YWxl-enter-active,
89
+ .YWxl-leave-active {
74
90
  transition: all 300ms cubic-bezier(0.25, 1, 0.5, 1);
75
91
  }
76
92
 
77
- .dG9i-enter-from,
78
- .dG9i-leave-to {
93
+ .YWxl-enter-from,
94
+ .YWxl-leave-to {
79
95
  opacity: 0;
80
96
  transform: translate3d(0, -10vh, 0);
81
97
  }
@@ -85,7 +101,11 @@ export default{
85
101
  <style module>
86
102
 
87
103
  .toast{
88
- @apply max-w-[90vw] md:max-w-[480px] bg-base-500 mt-[64px] mx-auto rounded-xl border-[1px] border-text-100;
104
+ }
105
+
106
+ .item{
107
+ @apply flex p-3 gap-4;
108
+ @apply max-w-[90vw] md:max-w-[480px] bg-base-500 mx-auto rounded-xl border-[1px] border-text-100;
89
109
  @apply min-h-[var(--h-cp)];
90
110
  z-index: 61;
91
111
  }
@@ -8,10 +8,8 @@
8
8
  @moveup="moveUp(item)"
9
9
  @movedown="moveDown(item)"
10
10
  @change="$emit('change')"
11
- @remove="confirm($t('Remove this item?'), { onConfirm: () => { modelValue.splice(index, 1);$emit('change') }})"
12
- @add="(items) => $emit('add', items)"
13
- @paste.stop.prevent="(e) => paste(e, item)"
14
- @item-paste="(p1, p2, p3) => $emit('item-paste', p1, p2, p3)">
11
+ @remove="confirm({ title:$t('Remove this item?'), onConfirm: () => { modelValue.splice(index, 1);$emit('change') }})"
12
+ @add="(items) => $emit('add', items)">
15
13
  <template #default="{ item }">
16
14
  <slot :item="item"></slot>
17
15
  </template>
@@ -23,14 +21,15 @@
23
21
  <script>
24
22
 
25
23
  import TreeViewItem from "./TreeViewItem.vue";
24
+ import {copyToClipboard, getClipboardData} from "../utils/helpers.mjs";
26
25
 
27
26
  export default{
28
27
 
29
28
  components: {TreeViewItem},
30
29
 
31
- emits: [ 'add', 'change', 'item-paste' ],
30
+ emits: [ 'add', 'change', 'paste' ],
32
31
 
33
- inject: [ 'confirm' ],
32
+ inject: [ 'confirm', 'toast' ],
34
33
 
35
34
  props: {
36
35
 
@@ -42,17 +41,21 @@ export default{
42
41
 
43
42
  methods: {
44
43
 
45
- paste(event, subItem){
46
- const clipboardData = event.clipboardData || window.clipboardData;
47
- const pastedText = clipboardData.getData('text');
48
-
49
- try{
50
- const newItem = JSON.parse(pastedText)
51
- this.$emit('item-paste', this.modelValue, subItem, newItem)
44
+ copy(){
45
+ if(this.selectedItem){
46
+ copyToClipboard(JSON.stringify(this.selectedItem))
47
+ .then(() => this.toast('Copied to clipboard'))
52
48
  }
53
- catch(e){
49
+ },
54
50
 
55
- }
51
+ paste(){
52
+ getClipboardData().then(text => {
53
+ try{
54
+ const item = JSON.parse(text)
55
+ this.$emit('paste', item)
56
+ }
57
+ catch(e){}
58
+ })
56
59
  },
57
60
 
58
61
  moveDown(item){
@@ -73,8 +76,12 @@ export default{
73
76
 
74
77
  add(){
75
78
  this.$emit('add', this.modelValue)
76
- }
79
+ },
80
+
81
+ },
77
82
 
83
+ mounted() {
84
+ window.addEventListener('keydown', this.onKeyUp)
78
85
  }
79
86
 
80
87
  }
@@ -1,6 +1,5 @@
1
1
  <template>
2
- <div @copy.stop.prevent="copy"
3
- @mouseover.stop="hoverMouseOver"
2
+ <div @mousemove.stop="hoverMouseOver"
4
3
  @mouseout.stop="hoverMouseOut">
5
4
  <div ref="item" :class="itemClass"
6
5
  @mousedown="mouseDown">
@@ -22,11 +21,9 @@
22
21
  :selected-item="selectedItem"
23
22
  @moveup="moveUp(subItem)"
24
23
  @movedown="moveDown(subItem)"
25
- @remove="confirm($t('Remove this item?'), { onConfirm: () => { item.items.splice(index, 1);$emit('change') }})"
24
+ @remove="confirm({ title:$t('Remove this item?'), onConfirm: () => { item.items.splice(index, 1);$emit('change') }})"
26
25
  @add="(items) => $emit('add', items)"
27
- @change="$emit('change')"
28
- @paste.stop.prevent="(e) => paste(e, subItem)"
29
- @item-paste="(p1, p2, p3) => $emit('item-paste', p1, p2, p3)">
26
+ @change="$emit('change')">
30
27
  <template #default="{ item }">
31
28
  <slot :item="item"></slot>
32
29
  </template>
@@ -51,7 +48,7 @@ let guide1 = null
51
48
 
52
49
  export default{
53
50
 
54
- emits: [ 'add', 'change', 'moveup', 'movedown', 'remove', 'item-paste' ],
51
+ emits: [ 'add', 'change', 'moveup', 'movedown', 'remove' ],
55
52
 
56
53
  inject: [ 'confirm', 'toast' ],
57
54
 
@@ -70,24 +67,6 @@ export default{
70
67
 
71
68
  methods: {
72
69
 
73
- copy(){
74
- copyToClipboard(JSON.stringify(this.item))
75
- this.toast(this.item.type + ' copied')
76
- },
77
-
78
- paste(event, subItem){
79
- const clipboardData = event.clipboardData || window.clipboardData;
80
- const pastedText = clipboardData.getData('text');
81
-
82
- try{
83
- const newItem = JSON.parse(pastedText)
84
- this.$emit('item-paste', this.item.items, subItem, newItem)
85
- }
86
- catch(e){
87
-
88
- }
89
- },
90
-
91
70
  moveDown(item){
92
71
  const idx = this.item.items.indexOf(item)
93
72
  if(idx < this.item.items.length - 1){
@@ -142,31 +121,45 @@ export default{
142
121
  window.removeEventListener('mousemove', mouseMove)
143
122
  window.removeEventListener('mouseup', mouseUp)
144
123
 
145
- if(guide1 && guide1.parentNode){
146
- guide1.parentNode.removeChild(guide1)
147
- }
148
-
149
124
  if(dragged && dragged.parent && dragged.targetParent){
150
125
 
151
- let targetIdx = dragged.targetParent.indexOf(dragged.target)
152
- const startIdx = dragged.parent.indexOf(dragged.item)
153
-
154
- /*if(e.clientY > dragged.startY){
155
- targetIdx++
156
- }*/
157
- //console.log(e.clientY, dragged.startY, e.clientY > dragged.startY ? 'after' : 'before', targetIdx, startIdx)
158
-
159
- if(targetIdx >= -1 && (targetIdx !== startIdx || dragged.parent !== dragged.targetParent)){
126
+ const startIdx = dragged.targetParent.indexOf(dragged.item)
127
+ const targetIdx = dragged.targetParent.indexOf(dragged.target)
128
+ const moveDirection = e.clientY < dragged.startY ? -1 : 1
160
129
 
130
+ let destIdx
131
+ if(moveDirection === -1){
161
132
  if(dragged.parent === dragged.targetParent){
162
- dragged.parent.splice(targetIdx, 0, dragged.parent.splice(startIdx, 1)[0])
133
+ destIdx = dragged.dragArea === -1 ? targetIdx : targetIdx + 1
163
134
  }
164
135
  else{
165
- dragged.targetParent.splice(targetIdx, 0, dragged.parent.splice(startIdx, 1)[0])
136
+ destIdx = dragged.dragArea === -1 ? targetIdx : targetIdx + 1
166
137
  }
167
-
168
- this.$emit('change')
169
138
  }
139
+ else{
140
+ if(dragged.parent === dragged.targetParent) {
141
+ destIdx = dragged.dragArea === -1 ? targetIdx - 1 : targetIdx
142
+ }
143
+ else{
144
+ destIdx = dragged.dragArea === -1 ? targetIdx : targetIdx + 1
145
+ }
146
+ }
147
+
148
+ /*console.log('#1', {
149
+ startIdx,
150
+ targetIdx,
151
+ destIdx,
152
+ moveDirection,
153
+ dragArea: dragged.dragArea
154
+ })*/
155
+
156
+ dragged.targetParent.splice(destIdx, 0, dragged.parent.splice(startIdx, 1)[0])
157
+
158
+ this.$emit('change')
159
+ }
160
+
161
+ if(guide1 && guide1.parentNode){
162
+ guide1.parentNode.removeChild(guide1)
170
163
  }
171
164
 
172
165
  dragged = null
@@ -179,16 +172,30 @@ export default{
179
172
  hoverMouseOver(e){
180
173
  if(!dragged) return
181
174
 
175
+ if(this.item === dragged.item) return
176
+
182
177
  if(!guide1){
183
178
  guide1 = this.createGuide()
184
179
  }
185
180
 
181
+ if(dragged.target !== this.item){
182
+ const rect = this.$refs.item.getBoundingClientRect()
183
+ dragged.centerY = rect.y + (rect.height / 2)
184
+ //console.log('#BS', dragged.centerY, this.item.name)
185
+ }
186
186
  dragged.target = this.item
187
187
  dragged.targetParent = this.parent
188
188
 
189
- guide1.addEventListener('mouseover', this.hoverMouseOver)
189
+ if(e.clientY < dragged.centerY){
190
+ this.$el.insertBefore(guide1, this.$el.firstElementChild)
191
+ dragged.dragArea = -1
192
+ }
193
+ else{
194
+ this.$el.insertBefore(guide1, null)
195
+ dragged.dragArea = 1
196
+ }
190
197
 
191
- this.$el.insertBefore(guide1, e.clientY < dragged.startY ? this.$refs.item : null)
198
+ //console.log('centerY', dragged.dragArea, e.clientY, dragged.centerY)
192
199
  },
193
200
 
194
201
  hoverMouseOut(e){
@@ -222,7 +229,8 @@ export default{
222
229
 
223
230
  data(){
224
231
  return {
225
- childCollapsed: false
232
+ childCollapsed: false,
233
+ hvm: null
226
234
  }
227
235
  }
228
236
 
@@ -179,27 +179,49 @@ const _applyClass = function(props, defaultClass = {}){
179
179
 
180
180
  const fallbackCopyTextToClipboard = function(obj){
181
181
  return new Promise((resolve, reject) => {
182
- var textArea = document.createElement("textarea");
183
- textArea.value = obj;
184
- textArea.style.top = "0";
185
- textArea.style.left = "0";
186
- textArea.style.position = "fixed";
187
- document.body.appendChild(textArea);
188
- textArea.focus();
189
- textArea.select();
190
-
191
- try {
182
+ try{
183
+ var textArea = document.createElement("textarea");
184
+ textArea.value = obj;
185
+ textArea.style.left = "-100vw";
186
+ textArea.style.position = "fixed";
187
+ document.body.appendChild(textArea);
188
+ textArea.focus();
189
+ textArea.select();
190
+
192
191
  document.execCommand('copy')
193
192
  document.body.removeChild(textArea);
193
+
194
194
  resolve()
195
- } catch (err) {
196
- reject(err)
195
+ }
196
+ catch(e){
197
+ reject(e)
197
198
  }
198
199
  })
199
200
  }
200
201
 
201
- const copyToClipboard = function(obj){
202
+ const fallbackGetClipboardData = function(obj){
203
+ return new Promise((resolve, reject) => {
204
+ try{
205
+ var textArea = document.createElement("textarea");
206
+ textArea.style.left = "-100vw";
207
+ textArea.style.position = "fixed";
208
+ document.body.appendChild(textArea);
209
+ textArea.focus();
210
+ document.execCommand('paste');
211
+
212
+ window.setTimeout(() => {
213
+ const text = textArea.value;
214
+ document.body.removeChild(textArea);
215
+ resolve(text)
216
+ }, 100)
217
+ }
218
+ catch(e){
219
+ reject(e)
220
+ }
221
+ })
222
+ }
202
223
 
224
+ const copyToClipboard = function(obj){
203
225
  if (!navigator.clipboard) {
204
226
  return fallbackCopyTextToClipboard(obj);
205
227
  }
@@ -213,6 +235,20 @@ const copyToClipboard = function(obj){
213
235
  })
214
236
  }
215
237
 
238
+ const getClipboardData = function(){
239
+ if (!navigator.clipboard) {
240
+ return fallbackGetClipboardData();
241
+ }
242
+
243
+ return new Promise((resolve, reject) => {
244
+ navigator.clipboard.readText().then((text) => {
245
+ resolve(text)
246
+ }, (err) => {
247
+ reject(err)
248
+ });
249
+ })
250
+ }
251
+
216
252
  const getComponentUids = function(components, excepts = []){
217
253
 
218
254
  const arr = []
@@ -249,8 +285,9 @@ export {
249
285
  mediaPrefixes,
250
286
  _applyClass,
251
287
  copyToClipboard,
288
+ getClipboardData,
252
289
  getComponentUids,
253
- unPascalCase
290
+ unPascalCase,
254
291
  }
255
292
 
256
293
  function observeInit(){
@@ -1,58 +1,60 @@
1
1
  <template>
2
2
  <div :class="$style.comp">
3
3
 
4
- <div class="flex flex-col gap-1">
5
- <label class="text-text-400">Title</label>
6
- <Textbox v-model="item.props.title" maxlength="40" @keyup.enter="$emit('change')" placeholder="Title" />
7
- </div>
8
-
9
- <div class="flex flex-col gap-1">
10
- <label class="text-text-400">Mode</label>
11
- <Dropdown v-model="item.props.mode" @change="item.props.items = { reviews:[] }">
12
- <option value="">Static</option>
13
- <option value="dynamic">Dynamic</option>
14
- </Dropdown>
15
- <Textbox v-if="item.props.mode === 'dynamic'"
16
- placeholder="Var" v-model="item.props.src"
17
- @blur="$emit('change')"
18
- @keyup.enter="$emit('change')" />
19
- </div>
4
+ <div v-if="viewType === ''" class="flex flex-col gap-4">
5
+ <div class="flex flex-col gap-1">
6
+ <label class="text-text-400">Title</label>
7
+ <Textbox v-model="item.props.title" maxlength="40" @keyup.enter="$emit('change')" placeholder="Title" />
8
+ </div>
20
9
 
21
- <div v-if="item.props.mode !== 'dynamic'">
22
- <div class="flex flex-row gap-2">
23
- <label class="flex-1 text-text-400">Reviews</label>
24
- <button type="button" class="text-primary" @click="$refs.reviewModal.open({ item:{} })">Add Review</button>
10
+ <div class="flex flex-col gap-1">
11
+ <label class="text-text-400">Mode</label>
12
+ <Dropdown v-model="item.props.mode" @change="item.props.items = { reviews:[] }">
13
+ <option value="">Static</option>
14
+ <option value="dynamic">Dynamic</option>
15
+ </Dropdown>
16
+ <Textbox v-if="item.props.mode === 'dynamic'"
17
+ placeholder="Var" v-model="item.props.src"
18
+ @blur="$emit('change')"
19
+ @keyup.enter="$emit('change')" />
25
20
  </div>
26
- <div class="mt-1">
27
- <ListItem :items="items.reviews"
28
- @reorder="(from, to) => { items.reviews.splice(to, 0, items.reviews.splice(from, 1)[0]); calcReview(); $emit('change') }">
29
- <template v-slot="{ item, index }">
30
- <div class="flex flex-row items-center gap-3 p-2">
31
- <div data-reorder>
32
- <svg width="14" height="14" class="fill-text-300" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M496 288H16c-8.8 0-16 7.2-16 16v32c0 8.8 7.2 16 16 16h480c8.8 0 16-7.2 16-16v-32c0-8.8-7.2-16-16-16zm0-128H16c-8.8 0-16 7.2-16 16v32c0 8.8 7.2 16 16 16h480c8.8 0 16-7.2 16-16v-32c0-8.8-7.2-16-16-16z"/></svg>
33
- </div>
34
- <div class="flex-1 cursor-pointer" @click="$refs.reviewModal.open({ item, index })">
35
- <p class="line-clamp-1">
21
+
22
+ <div v-if="item.props.mode !== 'dynamic'">
23
+ <div class="flex flex-row gap-2">
24
+ <label class="flex-1 text-text-400">Reviews</label>
25
+ <button type="button" class="text-primary" @click="$refs.reviewModal.open({ item:{} })">Add Review</button>
26
+ </div>
27
+ <div class="mt-1">
28
+ <ListItem :items="items.reviews"
29
+ @reorder="(from, to) => { items.reviews.splice(to, 0, items.reviews.splice(from, 1)[0]); calcReview(); $emit('change') }">
30
+ <template v-slot="{ item, index }">
31
+ <div class="flex flex-row items-center gap-3 p-2">
32
+ <div data-reorder>
33
+ <svg width="14" height="14" class="fill-text-300" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M496 288H16c-8.8 0-16 7.2-16 16v32c0 8.8 7.2 16 16 16h480c8.8 0 16-7.2 16-16v-32c0-8.8-7.2-16-16-16zm0-128H16c-8.8 0-16 7.2-16 16v32c0 8.8 7.2 16 16 16h480c8.8 0 16-7.2 16-16v-32c0-8.8-7.2-16-16-16z"/></svg>
34
+ </div>
35
+ <div class="flex-1 cursor-pointer" @click="$refs.reviewModal.open({ item, index })">
36
+ <p class="line-clamp-1">
36
37
  <span class="w-[19px] aspect-square rounded-full bg-primary text-white inline-flex items-center justify-center">
37
38
  {{ item.rate }}
38
39
  </span>
39
- {{ item.text }}
40
- </p>
40
+ {{ item.text }}
41
+ </p>
42
+ </div>
43
+ <button type="button" @click="confirm($t('Remove this item?'), '', () => { items.reviews.splice(index, 1); calcReview(); $emit('change') })">
44
+ <svg width="16" height="16" class="fill-text-300 hover:fill-red-500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><path d="M207.6 256l107.72-107.72c6.23-6.23 6.23-16.34 0-22.58l-25.03-25.03c-6.23-6.23-16.34-6.23-22.58 0L160 208.4 52.28 100.68c-6.23-6.23-16.34-6.23-22.58 0L4.68 125.7c-6.23 6.23-6.23 16.34 0 22.58L112.4 256 4.68 363.72c-6.23 6.23-6.23 16.34 0 22.58l25.03 25.03c6.23 6.23 16.34 6.23 22.58 0L160 303.6l107.72 107.72c6.23 6.23 16.34 6.23 22.58 0l25.03-25.03c6.23-6.23 6.23-16.34 0-22.58L207.6 256z"/></svg>
45
+ </button>
41
46
  </div>
42
- <button type="button" @click="confirm($t('Remove this item?'), '', () => { items.reviews.splice(index, 1); calcReview(); $emit('change') })">
43
- <svg width="16" height="16" class="fill-text-300 hover:fill-red-500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><path d="M207.6 256l107.72-107.72c6.23-6.23 6.23-16.34 0-22.58l-25.03-25.03c-6.23-6.23-16.34-6.23-22.58 0L160 208.4 52.28 100.68c-6.23-6.23-16.34-6.23-22.58 0L4.68 125.7c-6.23 6.23-6.23 16.34 0 22.58L112.4 256 4.68 363.72c-6.23 6.23-6.23 16.34 0 22.58l25.03 25.03c6.23 6.23 16.34 6.23 22.58 0L160 303.6l107.72 107.72c6.23 6.23 16.34 6.23 22.58 0l25.03-25.03c6.23-6.23 6.23-16.34 0-22.58L207.6 256z"/></svg>
44
- </button>
45
- </div>
46
- </template>
47
- </ListItem>
47
+ </template>
48
+ </ListItem>
49
+ </div>
48
50
  </div>
49
- </div>
50
51
 
51
- <div class="flex flex-row gap-4 items-center">
52
- <Checkbox v-model="item.props.enableGoogleReviewSD" @change="$emit('change')">
53
- Enable google structured data
54
- </Checkbox>
55
- </div>
52
+ <div class="flex flex-row gap-4 items-center">
53
+ <Checkbox v-model="item.props.enableGoogleReviewSD" @change="$emit('change')">
54
+ Enable google structured data
55
+ </Checkbox>
56
+ </div>
57
+ </div>
56
58
 
57
59
 
58
60
  <Modal ref="reviewModal" width="360" height="420">
@@ -160,8 +160,7 @@
160
160
  v-model="layout.headers"
161
161
  :selected-item="currentItem"
162
162
  @add="openSelector"
163
- @change="saveLayout"
164
- @item-paste="onLayoutItemPaste">
163
+ @change="saveLayout">
165
164
  <template #default="{ item }">
166
165
  <component :is="`${(item ?? {}).type}Item`" :item="item"
167
166
  @click="store.selectedComponent = [ item.uid ]"/>
@@ -178,8 +177,7 @@
178
177
  v-model="layout.footers"
179
178
  :selected-item="currentItem"
180
179
  @add="openSelector"
181
- @change="saveLayout"
182
- @item-paste="onLayoutItemPaste">
180
+ @change="saveLayout">
183
181
  <template #default="{ item }">
184
182
  <component :is="`${(item ?? {}).type}Item`" :item="item"
185
183
  @click="store.selectedComponent = [ item.uid ]"/>
@@ -196,14 +194,11 @@
196
194
  v-model="page.components"
197
195
  :selected-item="currentItem"
198
196
  @add="openSelector"
199
- @change="save"
200
- @item-paste="onItemPaste">
201
-
197
+ @change="save">
202
198
  <template #default="{ item }">
203
199
  <component :is="`${(item ?? {}).type}Item`" :item="item"
204
200
  @click="store.selectedComponent = [ item.uid ]"/>
205
201
  </template>
206
-
207
202
  </TreeView>
208
203
  </div>
209
204
 
@@ -261,36 +256,41 @@
261
256
  <div class="flex-1 flex flex-col bg-base-400">
262
257
 
263
258
  <div class="p-3 sticky top-0 flex justify-center gap-4 bg-base-400 dark:bg-base-300 border-b-[1px] border-text-50">
264
- <div class="flex-1 flex flex-row gap-3 items-center">
259
+ <div class="flex-1 flex flex-row gap-4 items-center">
265
260
  <Dropdown v-model="store.zoomLevel" class="w-[75px]" @change="resize()">
266
- <option value="fit">Fit</option>
267
- <option value="125%">125%</option>
268
- <option value="100%">100%</option>
269
- <option value="75%">75%</option>
270
- <option value="50%">50%</option>
261
+ <optgroup label="Zoom Level">
262
+ <option value="fit">Fit</option>
263
+ <option value="125%">125%</option>
264
+ <option value="100%">100%</option>
265
+ <option value="75%">75%</option>
266
+ <option value="50%">50%</option>
267
+ </optgroup>
271
268
  </Dropdown>
272
269
 
273
- <Textbox :readonly="1" :value="iframeSrc" class="w-full">
274
- <template #start>
275
- <button @click="reloadIframe" class="p-3">
276
- <svg width="14" height="14" viewBox="0 0 24 24" class="fill-text" xmlns="http://www.w3.org/2000/svg">
277
- <path d="M3.75 12C3.75 7.44365 7.44365 3.75 12 3.75C14.7802 3.75 17.1982 5.12612 18.6816 7.24467L16.5022 7.23828C16.088 7.23707 15.7512 7.57187 15.75 7.98608C15.7488 8.4003 16.0836 8.73706 16.4978 8.73828L19.9491 8.74839C19.9817 8.75065 20.0147 8.75076 20.0477 8.74868L20.4978 8.75C20.6971 8.75058 20.8884 8.67182 21.0296 8.53111C21.1707 8.39039 21.25 8.19929 21.25 8L21.25 4C21.25 3.58579 20.9142 3.25 20.5 3.25C20.0858 3.25 19.75 3.58579 19.75 4L19.75 6.16237C17.9894 3.79113 15.2004 2.25 12 2.25C6.61522 2.25 2.25 6.61522 2.25 12C2.25 17.3848 6.61522 21.75 12 21.75C15.8354 21.75 19.0799 19.5367 20.6716 16.3338C20.856 15.9628 20.7047 15.5127 20.3338 15.3284C19.9628 15.144 19.5127 15.2953 19.3284 15.6662C17.9747 18.3902 15.2321 20.25 12 20.25C7.44365 20.25 3.75 16.5563 3.75 12Z"/>
278
- </svg>
279
- </button>
280
- </template>
281
- <template #end>
282
- <div class="flex flex-row gap-4">
283
- <CopyToClipboard :value="iframeSrc" @copied="toast($t('Copied'))">
284
- <svg width="14" height="14" class="fill-primary" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM352 32.491a15.88 15.88 0 0 1 7.431 4.195l51.882 51.883A15.885 15.885 0 0 1 415.508 96H352V32.491zM288 464c0 8.822-7.178 16-16 16H48c-8.822 0-16-7.178-16-16V144c0-8.822 7.178-16 16-16h80v240c0 26.51 21.49 48 48 48h112v48zm128-96c0 8.822-7.178 16-16 16H176c-8.822 0-16-7.178-16-16V48c0-8.822 7.178-16 16-16h144v72c0 13.2 10.8 24 24 24h72v240z"/></svg>
285
- </CopyToClipboard>
286
- <a type="button" class="w-[21px]" :href="iframeSrc" target="_blank">
287
- <svg width="12" height="12" class="fill-primary" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M497.6,0,334.4.17A14.4,14.4,0,0,0,320,14.57V47.88a14.4,14.4,0,0,0,14.69,14.4l73.63-2.72,2.06,2.06L131.52,340.49a12,12,0,0,0,0,17l23,23a12,12,0,0,0,17,0L450.38,101.62l2.06,2.06-2.72,73.63A14.4,14.4,0,0,0,464.12,192h33.31a14.4,14.4,0,0,0,14.4-14.4L512,14.4A14.4,14.4,0,0,0,497.6,0ZM432,288H416a16,16,0,0,0-16,16V458a6,6,0,0,1-6,6H54a6,6,0,0,1-6-6V118a6,6,0,0,1,6-6H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V304A16,16,0,0,0,432,288Z"/></svg>
288
- </a>
289
- </div>
290
- </template>
291
- </Textbox>
292
- <div class="px-6">
293
- <Tabs v-model="store.viewType" variant="button" @change="resize" :items="viewTypes">
270
+ <div class="flex-1">
271
+ <Textbox :readonly="1" :value="iframeSrc" class="w-full">
272
+ <template #start>
273
+ <button @click="reloadIframe" class="p-3">
274
+ <svg width="14" height="14" viewBox="0 0 24 24" class="fill-text" xmlns="http://www.w3.org/2000/svg">
275
+ <path d="M3.75 12C3.75 7.44365 7.44365 3.75 12 3.75C14.7802 3.75 17.1982 5.12612 18.6816 7.24467L16.5022 7.23828C16.088 7.23707 15.7512 7.57187 15.75 7.98608C15.7488 8.4003 16.0836 8.73706 16.4978 8.73828L19.9491 8.74839C19.9817 8.75065 20.0147 8.75076 20.0477 8.74868L20.4978 8.75C20.6971 8.75058 20.8884 8.67182 21.0296 8.53111C21.1707 8.39039 21.25 8.19929 21.25 8L21.25 4C21.25 3.58579 20.9142 3.25 20.5 3.25C20.0858 3.25 19.75 3.58579 19.75 4L19.75 6.16237C17.9894 3.79113 15.2004 2.25 12 2.25C6.61522 2.25 2.25 6.61522 2.25 12C2.25 17.3848 6.61522 21.75 12 21.75C15.8354 21.75 19.0799 19.5367 20.6716 16.3338C20.856 15.9628 20.7047 15.5127 20.3338 15.3284C19.9628 15.144 19.5127 15.2953 19.3284 15.6662C17.9747 18.3902 15.2321 20.25 12 20.25C7.44365 20.25 3.75 16.5563 3.75 12Z"/>
276
+ </svg>
277
+ </button>
278
+ </template>
279
+ <template #end>
280
+ <div class="flex flex-row gap-4">
281
+ <CopyToClipboard :value="iframeSrc" @copied="toast($t('Copied'))">
282
+ <svg width="14" height="14" class="fill-primary" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM352 32.491a15.88 15.88 0 0 1 7.431 4.195l51.882 51.883A15.885 15.885 0 0 1 415.508 96H352V32.491zM288 464c0 8.822-7.178 16-16 16H48c-8.822 0-16-7.178-16-16V144c0-8.822 7.178-16 16-16h80v240c0 26.51 21.49 48 48 48h112v48zm128-96c0 8.822-7.178 16-16 16H176c-8.822 0-16-7.178-16-16V48c0-8.822 7.178-16 16-16h144v72c0 13.2 10.8 24 24 24h72v240z"/></svg>
283
+ </CopyToClipboard>
284
+ <a type="button" class="w-[21px]" :href="iframeSrc" target="_blank">
285
+ <svg width="12" height="12" class="fill-primary" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M497.6,0,334.4.17A14.4,14.4,0,0,0,320,14.57V47.88a14.4,14.4,0,0,0,14.69,14.4l73.63-2.72,2.06,2.06L131.52,340.49a12,12,0,0,0,0,17l23,23a12,12,0,0,0,17,0L450.38,101.62l2.06,2.06-2.72,73.63A14.4,14.4,0,0,0,464.12,192h33.31a14.4,14.4,0,0,0,14.4-14.4L512,14.4A14.4,14.4,0,0,0,497.6,0ZM432,288H416a16,16,0,0,0-16,16V458a6,6,0,0,1-6,6H54a6,6,0,0,1-6-6V118a6,6,0,0,1,6-6H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V304A16,16,0,0,0,432,288Z"/></svg>
286
+ </a>
287
+ </div>
288
+ </template>
289
+ </Textbox>
290
+ </div>
291
+
292
+ <div>
293
+ <Tabs v-model="store.previewViewType" variant="button" @change="resize" :items="viewTypes">
294
294
  <template #tab="{ item }">
295
295
  <div class="p-1 px-2" v-if="item.value === ''" v-tooltip="'Mobile'">
296
296
  <svg width="14" height="14" class="fill-text pointer-events-none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><path d="M192 416c0 17.7-14.3 32-32 32s-32-14.3-32-32 14.3-32 32-32 32 14.3 32 32zM320 48v416c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V48C0 21.5 21.5 0 48 0h224c26.5 0 48 21.5 48 48zm-32 0c0-8.8-7.2-16-16-16H48c-8.8 0-16 7.2-16 16v416c0 8.8 7.2 16 16 16h224c8.8 0 16-7.2 16-16V48z"/></svg>
@@ -304,7 +304,7 @@
304
304
  </div>
305
305
  </div>
306
306
 
307
- <div class="flex-1 p-6 bg-base-300 dark:bg-base-400" :class="previewClass" :style="previewStyle" ref="preview">
307
+ <div class="flex-1 p-6 bg-base-300 dark:bg-base-400" :class="previewClass" ref="preview">
308
308
  <iframe :src="`${iframeSrc}?edit-mode=${page.uid}`" :width="iframeSize.width" :height="iframeSize.height"
309
309
  class="mx-auto border-[2px] border-text-300" :style="iframeStyle" ref="iframe"></iframe>
310
310
  </div>
@@ -314,19 +314,35 @@
314
314
  <div v-if="currentItem" class="flex flex-col bg-base-400 dark:bg-base-300"
315
315
  :style="section3Style">
316
316
 
317
- <div class="p-6">
317
+ <div class="px-6 pt-6 pb-2 bg-base-300">
318
318
  <strong>
319
319
  {{ currentItem.type }}
320
320
  </strong>
321
321
  </div>
322
322
 
323
- <div class="flex-1 flex flex-row">
323
+ <div class="px-4 bg-base-300 pt-2 relative">
324
+ <Tabs v-model="store.viewType" :items="viewTypes" variant="minimal">
325
+ <template #tab="{ item }">
326
+ <div v-if="item.value === ''" class="px-6 p-2 border-[1px] border-b-0 relative top-[1px] rounded-t-md"
327
+ :class="store.viewType === item.value ? 'bg-base-400 border-text-50' : 'border-transparent'">
328
+ Mobile
329
+ </div>
330
+ <div v-else-if="item.value === 'md:'" class="px-6 p-2 border-[1px] border-b-0 relative top-[1px] rounded-t-md"
331
+ :class="store.viewType === item.value ? 'bg-base-400 border-text-50' : 'border-transparent'">
332
+ Tablet
333
+ </div>
334
+ </template>
335
+ </Tabs>
336
+ </div>
337
+
338
+ <div class="flex-1 flex flex-row border-t-[1px] border-text-50">
324
339
 
325
340
  <div :class="$style.resize3"
326
341
  @mousedown="(e) => $util.dragResize(e, store.width[1], resize3)"></div>
327
342
 
328
343
  <div class="flex-1 flex flex-col gap-6 overflow-y-auto p-6">
329
- <div class="flex flex-col gap-6" v-if="(store.selectedComponent ?? [])[1] !== 'style'">
344
+ <div class="flex flex-col gap-6"
345
+ v-if="(store.selectedComponent ?? [])[1] !== 'style' && store.viewType === ''">
330
346
  <div>
331
347
  <label class="text-text-400">{{ $t('Enabled') }}</label>
332
348
  <Switch v-model="currentItem.props.enabled" @change="onSave"/>
@@ -455,6 +471,7 @@
455
471
  import throttle from "lodash/throttle";
456
472
  import md5 from "md5";
457
473
  import groupBy from "lodash/groupBy";
474
+ import {copyToClipboard, getClipboardData} from "../utils/helpers.mjs";
458
475
 
459
476
  export default{
460
477
 
@@ -519,6 +536,32 @@ export default{
519
536
  this.$refs.webPageComponentSelector.close()
520
537
  },
521
538
 
539
+ copy(){
540
+ if(!this.currentItem) return
541
+
542
+ copyToClipboard(JSON.stringify(this.currentItem))
543
+ .then(() => this.toast('Copied to clipboard'))
544
+ },
545
+
546
+ paste(){
547
+ if(!this.currentItem) return
548
+
549
+ getClipboardData().then(text => {
550
+ try{
551
+ const item = JSON.parse(text)
552
+ this.setUid(item)
553
+
554
+ const comp = this.findItemByUid(this.currentItem.uid)
555
+ comp.items.splice(comp.items.indexOf(comp.item) + 1, 0, item)
556
+ this.store.selectedComponent = [ item.uid ]
557
+ comp.type === 'components' ? this.save() : this.saveLayout()
558
+ }
559
+ catch(e){
560
+ console.error(e)
561
+ }
562
+ })
563
+ },
564
+
522
565
  async load(){
523
566
  return this.socketEmit2('page.open', { uid:this.$route.params.uid })
524
567
  .then(({ page, layouts, host }) => {
@@ -582,7 +625,7 @@ export default{
582
625
 
583
626
  resize(){
584
627
 
585
- const transformOrigin = this.store.viewType === '' ? 'center top' : '0 0'
628
+ const transformOrigin = this.computedPreviewViewType === '' ? 'center top' : '0 0'
586
629
 
587
630
  switch(this.store.zoomLevel){
588
631
 
@@ -592,7 +635,7 @@ export default{
592
635
  const previewHeight = this.$refs.preview.clientHeight - 70
593
636
 
594
637
  let scale = 1
595
- if(this.store.viewType === ''){
638
+ if(this.computedPreviewViewType === ''){
596
639
  scale = (previewHeight / 844).toFixed(2)
597
640
  }
598
641
  else{
@@ -685,12 +728,9 @@ export default{
685
728
 
686
729
  createComponentInstance(component){
687
730
 
688
- if(!component.instance)
689
- component.instance = {}
690
-
691
731
  const compUid = '_' + component.uid.substring(0, 4) + ' '
692
732
 
693
- Object.assign(component.instance, {
733
+ const instance = {
694
734
  type: component.type,
695
735
  uid: component.uid,
696
736
 
@@ -711,27 +751,29 @@ export default{
711
751
  })
712
752
  .filter(_ => _)
713
753
  .join(' ')
714
- })
754
+ }
715
755
 
716
756
  for(let key in component.props){
717
757
  if(!this.compClasses.includes(key) && ![ 'enabled' ].includes(key)){
718
- component.instance[key] = component.props[key]
758
+ instance[key] = component.props[key]
719
759
  }
720
760
  }
721
761
 
722
762
  if(Array.isArray(component.items)){
723
- component.instance.items = component.items.map((_) => this.createComponentInstance(_))
763
+ instance.items = component.items.map((_) => this.createComponentInstance(_))
724
764
  }
725
765
 
726
766
  if(component.props && Array.isArray(component.props.items)){
727
- component.instance.items = component.props.items
767
+ instance.items = component.props.items
728
768
  }
729
769
 
730
- return component
770
+ return instance
731
771
  },
732
772
 
733
773
  save: throttle(function(reload = true){
734
- this.page.components = this.page.components.map((_) => this.createComponentInstance(_))
774
+ if(!this.page.instances || typeof this.page.instances !== 'object' || Array.isArray(this.page.instances))
775
+ this.page.instances = {}
776
+ this.page.instances.components = this.page.components.map((_) => this.createComponentInstance(_))
735
777
 
736
778
  this.socketEmit2('page.save', this.page)
737
779
  .then(() => {
@@ -747,8 +789,10 @@ export default{
747
789
  }, 300),
748
790
 
749
791
  saveLayout: throttle(function(reload = true){
750
- this.layout.headers = (this.layout.headers ?? []).map((_) => this.createComponentInstance(_))
751
- this.layout.footers = (this.layout.footers ?? []).map((_) => this.createComponentInstance(_))
792
+ if(!this.layout.instances || typeof this.layout.instances !== 'object' || Array.isArray(this.layout.instances))
793
+ this.layout.instances = {}
794
+ this.layout.instances.headers = (this.layout.headers ?? []).map((_) => this.createComponentInstance(_))
795
+ this.layout.instances.footers = (this.layout.footers ?? []).map((_) => this.createComponentInstance(_))
752
796
 
753
797
  this.socketEmit2('layout.save', this.layout)
754
798
  .then(() => {
@@ -863,29 +907,45 @@ export default{
863
907
  }
864
908
  },
865
909
 
866
- onLayoutItemPaste(items, currentItem, newItem){
867
- this.onItemPaste(items, currentItem, newItem, true)
868
- },
910
+ onLayoutItemPaste(item){
911
+ //console.log('layout paste', item)
869
912
 
870
- onItemPaste(items, currentItem, newItem, isLayout = false){
871
913
 
872
- const setUid = (item) => {
873
- item.uid = md5(newItem.type + Math.random())
874
914
 
875
- if(Array.isArray(item.items)){
876
- for(let subItem of item.items){
877
- setUid(subItem)
878
- }
879
- }
880
- }
915
+ },
916
+
917
+ onItemPaste(item, isLayout = false){
918
+ //console.log('page paste', item, this.store.selectedComponent)
919
+ return
881
920
 
882
- setUid(newItem)
921
+ this.setUid(newItem)
883
922
  items.splice(items.indexOf(currentItem) + 1, 0, newItem)
884
923
  this.store.selectedComponent = [ newItem.uid ]
885
924
 
886
925
  isLayout ? this.saveLayout() : this.save()
887
926
  },
888
927
 
928
+ remove(item){
929
+ this.confirm(this.$t('Remove this item?'), {
930
+ onConfirm: () => {
931
+ const { items, type } = this.findItemByUid(item.uid)
932
+ items.splice(items.indexOf(item), 1)
933
+ type === 'components' ? this.save() : this.saveLayout()
934
+ this.store.selectedComponent = null
935
+ }
936
+ })
937
+ },
938
+
939
+ setUid(item){
940
+ item.uid = md5(Math.random())
941
+
942
+ if(Array.isArray(item.items)){
943
+ for(let subItem of item.items){
944
+ this.setUid(subItem)
945
+ }
946
+ }
947
+ },
948
+
889
949
  findCompByUid(uid, components){
890
950
 
891
951
  for(let i in components){
@@ -904,6 +964,47 @@ export default{
904
964
  }
905
965
  },
906
966
 
967
+ findItemByUid(uid, items){
968
+
969
+ if(!items){
970
+ for(let type of [ 'headers', 'footers' ]){
971
+ let item = this.findItemByUid(uid, (this.layout ?? {})[type] ?? [])
972
+ if(item){
973
+ return {
974
+ ...item,
975
+ type
976
+ }
977
+ }
978
+ }
979
+
980
+ let item = this.findItemByUid(uid, (this.page ?? {}).components ?? [])
981
+ if(item){
982
+ return {
983
+ ...item,
984
+ type: 'components'
985
+ }
986
+ }
987
+ }
988
+ else{
989
+
990
+ let item
991
+ for(let _item of items){
992
+ if(_item.uid === uid){
993
+ return {
994
+ item: _item,
995
+ items
996
+ }
997
+ }
998
+ else if(_item.items){
999
+ item = this.findItemByUid(uid, _item.items)
1000
+ if(item){
1001
+ return item
1002
+ }
1003
+ }
1004
+ }
1005
+ }
1006
+ },
1007
+
907
1008
  getPage(){
908
1009
  return this.page
909
1010
  },
@@ -912,12 +1013,25 @@ export default{
912
1013
  this.resize()
913
1014
  }, 500, { leading:true }),
914
1015
 
1016
+ onKeyDown(e){
1017
+ if(e.keyCode === 67 && (e.metaKey || e.ctrlKey)){
1018
+ this.copy()
1019
+ }
1020
+ else if(e.keyCode === 86 && (e.metaKey || e.ctrlKey)){
1021
+ this.paste(e)
1022
+ }
1023
+ /*else if(e.keyCode === 8){
1024
+ if(this.currentItem){
1025
+ this.remove(this.currentItem)
1026
+ }
1027
+ }*/
1028
+ },
1029
+
915
1030
  },
916
1031
 
917
1032
  computed: {
918
1033
 
919
1034
  currentItem(){
920
-
921
1035
  let comp
922
1036
  if(this.page && Array.isArray(this.store.selectedComponent)){
923
1037
 
@@ -954,14 +1068,19 @@ export default{
954
1068
  break
955
1069
  }
956
1070
  }
957
-
958
1071
  return comp
959
1072
  },
960
1073
 
1074
+ computedPreviewViewType(){
1075
+ if(this.store.previewViewType === 'auto')
1076
+ return this.store.viewType
1077
+ return this.store.previewViewType
1078
+ },
1079
+
961
1080
  iframeSize(){
962
1081
 
963
1082
  let width, height
964
- switch(this.store.viewType){
1083
+ switch(this.computedPreviewViewType){
965
1084
  case '':
966
1085
  width = 390
967
1086
  height = 844
@@ -981,16 +1100,9 @@ export default{
981
1100
 
982
1101
  previewClass(){
983
1102
  return {
984
- 'overflow-y-auto': this.store.zoomLevel !== 'fit'
985
- }
986
- },
987
-
988
- previewStyle(){
989
-
990
- return {
991
- 'overflow-y': this.store.zoomLevel === 'fit' ? 'hidden' : 'auto'
1103
+ 'overflow-auto': this.store.zoomLevel !== 'fit',
1104
+ 'overflow-hidden': this.store.zoomLevel === 'fit',
992
1105
  }
993
-
994
1106
  },
995
1107
 
996
1108
  layout(){
@@ -1041,7 +1153,7 @@ export default{
1041
1153
  ],
1042
1154
  viewTypes: [
1043
1155
  { text:'Mobile', value:'' },
1044
- { text:'Desktop', value:'md:' }
1156
+ { text:'Tablet', value:'md:' }
1045
1157
  ],
1046
1158
 
1047
1159
  currentComponentItems: null,
@@ -1173,6 +1285,7 @@ export default{
1173
1285
 
1174
1286
  window.addEventListener('message', this.onMessage)
1175
1287
  window.addEventListener('resize', this.onResize)
1288
+ window.addEventListener('keydown', this.onKeyDown)
1176
1289
 
1177
1290
  this.listen()
1178
1291
  this.socket.onAny(this.onHooks)