@mixd-id/web-scaffold 0.1.240411027 → 0.1.240411029
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/ContextMenu.vue +12 -1
- package/src/components/ListContextMenu.vue +88 -0
- package/src/components/PresetSelectorFilterItem.vue +5 -2
- package/src/components/Textbox.vue +1 -1
- package/src/index.js +1 -0
- package/src/utils/helpers.mjs +21 -18
- package/src/utils/preset-selector.cjs +22 -0
- package/src/utils/preset-selector.js +22 -0
package/package.json
CHANGED
|
@@ -177,6 +177,17 @@ export default {
|
|
|
177
177
|
left = Math.round(rect.x)
|
|
178
178
|
top = Math.round(rect.y + rect.height)
|
|
179
179
|
transformOrigin = 'top left'
|
|
180
|
+
maxHeight = window.innerHeight - top - 16
|
|
181
|
+
this.transition = 'slidedown'
|
|
182
|
+
|
|
183
|
+
if(top > window.innerHeight * .8){
|
|
184
|
+
top = null
|
|
185
|
+
bottom = window.innerHeight - rect.top + 8
|
|
186
|
+
transformOrigin = 'bottom'
|
|
187
|
+
maxHeight = Math.round(rect.top - 16)
|
|
188
|
+
console.log('maxHeight', maxHeight)
|
|
189
|
+
this.transition = 'slideup'
|
|
190
|
+
}
|
|
180
191
|
break
|
|
181
192
|
}
|
|
182
193
|
|
|
@@ -234,7 +245,7 @@ export default {
|
|
|
234
245
|
|
|
235
246
|
.contextMenu{
|
|
236
247
|
@apply fixed bg-base-300 min-w-[150px] overflow-y-auto rounded-xl z-50;
|
|
237
|
-
@apply whitespace-nowrap shadow-2xl border-[1px] border-text-
|
|
248
|
+
@apply whitespace-nowrap shadow-2xl border-[1px] border-text-200 mt-[1px];
|
|
238
249
|
}
|
|
239
250
|
|
|
240
251
|
@media screen and (min-width: 640px){
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<ContextMenu ref="contextMenu" class="mt-1">
|
|
3
|
+
<div class="flex flex-col min-w-[200px]">
|
|
4
|
+
|
|
5
|
+
<div class="p-1 sticky top-0 bg-base-500" @click.stop>
|
|
6
|
+
<Textbox :clearable="true" @clear="search = ''" placeholder="Search or add..." v-model="search" />
|
|
7
|
+
</div>
|
|
8
|
+
|
|
9
|
+
<button v-if="viewedItems.length > 0" v-for="text in viewedItems" class="w-full p-3 text-left flex flex-row menu-item"
|
|
10
|
+
@click="select(text)">
|
|
11
|
+
{{ text }}
|
|
12
|
+
</button>
|
|
13
|
+
|
|
14
|
+
<button v-else-if="search.length > 0" type="button" class="w-full p-3 text-primary" @click="add(search)">
|
|
15
|
+
Tambah {{ search }}
|
|
16
|
+
</button>
|
|
17
|
+
|
|
18
|
+
</div>
|
|
19
|
+
</ContextMenu>
|
|
20
|
+
</template>
|
|
21
|
+
|
|
22
|
+
<script>
|
|
23
|
+
import ContextMenu from "./ContextMenu.vue";
|
|
24
|
+
|
|
25
|
+
export default{
|
|
26
|
+
components: {ContextMenu},
|
|
27
|
+
|
|
28
|
+
emits: [ 'select' ],
|
|
29
|
+
|
|
30
|
+
props: {
|
|
31
|
+
items: Array
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
computed: {
|
|
35
|
+
|
|
36
|
+
viewedItems(){
|
|
37
|
+
return (this.items ?? [])
|
|
38
|
+
.filter(text => {
|
|
39
|
+
return text.toLowerCase().includes(this.search.toLowerCase())
|
|
40
|
+
})
|
|
41
|
+
.sort((a, b) => {
|
|
42
|
+
return a.toLowerCase().localeCompare(b.toLowerCase())
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
data(){
|
|
49
|
+
return {
|
|
50
|
+
search: '',
|
|
51
|
+
cb: null
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
methods: {
|
|
56
|
+
|
|
57
|
+
add(text){
|
|
58
|
+
if(`${text}`.length < 1) return
|
|
59
|
+
|
|
60
|
+
this.items.push(text)
|
|
61
|
+
this.select(text)
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
select(text){
|
|
65
|
+
this.$emit('select', text)
|
|
66
|
+
if(typeof this.cb === 'function')
|
|
67
|
+
this.cb(text)
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
open(caller, cb){
|
|
71
|
+
this.search = ''
|
|
72
|
+
this.cb = cb
|
|
73
|
+
this.$refs.contextMenu.open(caller)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
</script>
|
|
81
|
+
|
|
82
|
+
<style module>
|
|
83
|
+
|
|
84
|
+
.comp{
|
|
85
|
+
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
</style>
|
|
@@ -88,7 +88,8 @@
|
|
|
88
88
|
</div>
|
|
89
89
|
|
|
90
90
|
<div v-else class="flex flex-row gap-2">
|
|
91
|
-
<Dropdown v-model="value.operator"
|
|
91
|
+
<Dropdown v-model="value.operator"
|
|
92
|
+
:class="![ 'notEmpty' ].includes(value.operator) ? 'w-[100px]' : 'w-full'"
|
|
92
93
|
:readonly="readonly"
|
|
93
94
|
@change="apply">
|
|
94
95
|
<option value="eq">Equal</option>
|
|
@@ -100,8 +101,10 @@
|
|
|
100
101
|
<option value="in">Multiple with comma</option>
|
|
101
102
|
<option value="notIn">Except with comma</option>
|
|
102
103
|
<option value="regex">Regex</option>
|
|
104
|
+
<option value="notEmpty">Not Empty</option>
|
|
103
105
|
</Dropdown>
|
|
104
|
-
<Textbox v-
|
|
106
|
+
<Textbox v-if="![ 'notEmpty' ].includes(value.operator)"
|
|
107
|
+
v-model="value.value"
|
|
105
108
|
:readonly="readonly"
|
|
106
109
|
class="flex-1"
|
|
107
110
|
@keyup.enter="apply"
|
|
@@ -74,6 +74,7 @@ export default{
|
|
|
74
74
|
this.$style.textbox,
|
|
75
75
|
this.$style['state-' + this.computedState],
|
|
76
76
|
this.readonly ? this.$style.readonly : '',
|
|
77
|
+
this.disabled ? this.$style.disabled : '',
|
|
77
78
|
this.isActive && !this.readonly ? this.$style.active : '',
|
|
78
79
|
this.align ? this.$style['align-' + this.align] : '',
|
|
79
80
|
this.size ? this.$style['size-' + this.size] : ''
|
|
@@ -186,7 +187,6 @@ export default{
|
|
|
186
187
|
}
|
|
187
188
|
|
|
188
189
|
.textbox.readonly{
|
|
189
|
-
@apply bg-text-50;
|
|
190
190
|
}
|
|
191
191
|
|
|
192
192
|
.size-sm input{ @apply text-sm; }
|
package/src/index.js
CHANGED
|
@@ -489,6 +489,7 @@ export default{
|
|
|
489
489
|
app.component('MultiDropdown', defineAsyncComponent(() => import("./components/MultiDropdown.vue")))
|
|
490
490
|
app.component('Carousel', defineAsyncComponent(() => import("./components/Carousel.vue")))
|
|
491
491
|
app.component('ContextMenu', defineAsyncComponent(() => import("./components/ContextMenu.vue")))
|
|
492
|
+
app.component('ListContextMenu', defineAsyncComponent(() => import("./components/ListContextMenu.vue")))
|
|
492
493
|
app.component('FAQ', defineAsyncComponent(() => import("./widgets/FAQ.vue")))
|
|
493
494
|
app.component('Flex', defineAsyncComponent(() => import("./components/Flex.vue")))
|
|
494
495
|
app.component('Grid', defineAsyncComponent(() => import("./components/Grid.vue")))
|
package/src/utils/helpers.mjs
CHANGED
|
@@ -560,34 +560,37 @@ const arrayRemove = (arr, items, opt = {}) => {
|
|
|
560
560
|
}
|
|
561
561
|
}
|
|
562
562
|
|
|
563
|
-
const arrayUnshift = (arr,
|
|
563
|
+
const arrayUnshift = (arr, items, opt = { update:true }) => {
|
|
564
564
|
if(!Array.isArray(arr)) return
|
|
565
|
+
if(!Array.isArray(items)) items = [ items ]
|
|
565
566
|
if(!opt.key){
|
|
566
|
-
opt.key =
|
|
567
|
+
opt.key = items[0] && items[0].uid ? 'uid' : 'id'
|
|
567
568
|
}
|
|
568
569
|
|
|
569
|
-
opt
|
|
570
|
+
if(!Array.isArray(opt.key)){
|
|
571
|
+
opt.key = [ opt.key ]
|
|
572
|
+
}
|
|
570
573
|
|
|
571
|
-
|
|
572
|
-
|
|
574
|
+
for(let item of items){
|
|
575
|
+
if(!item) continue
|
|
573
576
|
|
|
574
|
-
const
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
577
|
+
const index = arr.findIndex((_) => {
|
|
578
|
+
for(let key of opt.key){
|
|
579
|
+
if(_[key] !== item[key]){
|
|
580
|
+
return false
|
|
581
|
+
}
|
|
578
582
|
}
|
|
579
|
-
|
|
583
|
+
return true
|
|
584
|
+
})
|
|
580
585
|
|
|
581
|
-
if(
|
|
582
|
-
|
|
586
|
+
if(index >= 0){
|
|
587
|
+
if(opt.update !== false){
|
|
588
|
+
Object.assign(arr[index], item)
|
|
589
|
+
}
|
|
583
590
|
}
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
else{
|
|
587
|
-
if(opt.highlight){
|
|
588
|
-
item._highlight = true
|
|
591
|
+
else{
|
|
592
|
+
arr.unshift(item)
|
|
589
593
|
}
|
|
590
|
-
arr.unshift(item)
|
|
591
594
|
}
|
|
592
595
|
}
|
|
593
596
|
|
|
@@ -307,6 +307,17 @@ const getValue = (filter, opt) => {
|
|
|
307
307
|
}
|
|
308
308
|
break
|
|
309
309
|
|
|
310
|
+
case 'notEmpty':
|
|
311
|
+
whereObj = {
|
|
312
|
+
[key]:{
|
|
313
|
+
[Op.or]: [
|
|
314
|
+
{ [Op.ne]: null },
|
|
315
|
+
{ [Op.ne]: '' }
|
|
316
|
+
]
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
break
|
|
320
|
+
|
|
310
321
|
case 'in':
|
|
311
322
|
withoutKey ?
|
|
312
323
|
whereObj = {
|
|
@@ -676,6 +687,17 @@ const filtersToSequelizeWhere = async(filters, opt) => {
|
|
|
676
687
|
}
|
|
677
688
|
break
|
|
678
689
|
|
|
690
|
+
case 'notEmpty':
|
|
691
|
+
whereObj = {
|
|
692
|
+
[key]:{
|
|
693
|
+
[Op.or]: [
|
|
694
|
+
{ [Op.ne]: null },
|
|
695
|
+
{ [Op.ne]: '' }
|
|
696
|
+
]
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
break
|
|
700
|
+
|
|
679
701
|
case 'in':
|
|
680
702
|
whereObj = {
|
|
681
703
|
[key]:{
|
|
@@ -307,6 +307,17 @@ const getValue = (filter, opt) => {
|
|
|
307
307
|
}
|
|
308
308
|
break
|
|
309
309
|
|
|
310
|
+
case 'notEmpty':
|
|
311
|
+
whereObj = {
|
|
312
|
+
[key]:{
|
|
313
|
+
[Op.or]: [
|
|
314
|
+
{ [Op.ne]: null },
|
|
315
|
+
{ [Op.ne]: '' }
|
|
316
|
+
]
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
break
|
|
320
|
+
|
|
310
321
|
case 'in':
|
|
311
322
|
withoutKey ?
|
|
312
323
|
whereObj = {
|
|
@@ -676,6 +687,17 @@ const filtersToSequelizeWhere = async(filters, opt) => {
|
|
|
676
687
|
}
|
|
677
688
|
break
|
|
678
689
|
|
|
690
|
+
case 'notEmpty':
|
|
691
|
+
whereObj = {
|
|
692
|
+
[key]:{
|
|
693
|
+
[Op.or]: [
|
|
694
|
+
{ [Op.ne]: null },
|
|
695
|
+
{ [Op.ne]: '' }
|
|
696
|
+
]
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
break
|
|
700
|
+
|
|
679
701
|
case 'in':
|
|
680
702
|
whereObj = {
|
|
681
703
|
[key]:{
|