@mixd-id/web-scaffold 0.1.230406337 → 0.1.230406338

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.230406337",
4
+ "version": "0.1.230406338",
5
5
  "scripts": {
6
6
  "dev": "vite serve",
7
7
  "build": "vite build",
@@ -40,7 +40,7 @@
40
40
 
41
41
  <div class="flex flex-row gap-2 md:gap-3 items-center" ref="title">
42
42
  <div class="flex flex-col whitespace-nowrap text-ellipsis overflow-hidden">
43
- <div class="cursor-pointer group" @click="$refs.contextMenu.open($refs.title)">
43
+ <div class="cursor-pointer group px-2 md:px-0" @click="$refs.contextMenu.open($refs.title)">
44
44
  <small class="text-text-400 text-xs">{{ title ?? 'Untitled' }}</small>
45
45
  <div class="flex flex-row items-baseline gap-1">
46
46
  <h5 class="whitespace-nowrap relative top-[-2px] text-ellipsis overflow-hidden group-hover:text-primary">
@@ -0,0 +1,22 @@
1
+ module.exports = {
2
+
3
+ uid: "uid-1",
4
+ name: "Dashboard 1",
5
+
6
+ datasource: [
7
+ {
8
+ uid: "uid-1",
9
+ type:"mpp.hmc",
10
+ name: "Datasource 1",
11
+ columns: [
12
+ { key:"coo", label:"COO" },
13
+ { key:"mppTgtLck", label:"MPP TGT LCK" },
14
+ ]
15
+ }
16
+ ],
17
+
18
+ views: [
19
+ { uid:"uid-1", type:"List", name:"List" }
20
+ ]
21
+
22
+ }
package/src/index.js CHANGED
@@ -669,6 +669,7 @@ export default{
669
669
  app.component('TypographySetting', defineAsyncComponent(() => import("./widgets/TypographySetting.vue")))
670
670
  app.component('FiltersSetting', defineAsyncComponent(() => import("./widgets/FiltersSetting.vue")))
671
671
  app.component('MenuEditor', defineAsyncComponent(() => import("./widgets/MenuEditor.vue")))
672
+ app.component('Dashboard', defineAsyncComponent(() => import("./widgets/Dashboard.vue")))
672
673
  app.component('Text', defineAsyncComponent(() => import("./components/Text.vue")))
673
674
  }
674
675
 
@@ -243,7 +243,7 @@ const plugin = Plugin(function({ addBase, addUtilities, config, theme }) {
243
243
 
244
244
  '.openltr-enter-from, .openltr-leave-to': {
245
245
  opacity: 0,
246
- transform: 'translateX(30px)'
246
+ transform: 'translateX(100px)'
247
247
  },
248
248
 
249
249
  '.openltr-leave-active': {
@@ -0,0 +1,97 @@
1
+ <template>
2
+ <Modal ref="modal"
3
+ width="320"
4
+ height="360">
5
+ <template v-slot:head>
6
+ <div class="relative p-6">
7
+ <h3>{{ $t('Select Datasource') }}</h3>
8
+ <div class="absolute top-0 right-0 p-2">
9
+ <button type="button" class="p-2" @click="close">
10
+ <svg width="24" height="24" viewBox="0 0 24 24" class="fill-text-300 hover:fill-red-500" xmlns="http://www.w3.org/2000/svg">
11
+ <path d="M6.53034 5.46965C6.23745 5.17676 5.76257 5.17676 5.46968 5.46965C5.17679 5.76255 5.17679 6.23742 5.46968 6.53031L10.9393 12L5.46967 17.4697C5.17678 17.7626 5.17678 18.2374 5.46967 18.5303C5.76256 18.8232 6.23744 18.8232 6.53033 18.5303L12 13.0606L17.4697 18.5303C17.7626 18.8232 18.2375 18.8232 18.5303 18.5303C18.8232 18.2374 18.8232 17.7626 18.5303 17.4697L13.0607 12L18.5303 6.53032C18.8232 6.23743 18.8232 5.76256 18.5303 5.46966C18.2374 5.17677 17.7626 5.17677 17.4697 5.46966L12 10.9393L6.53034 5.46965Z"/>
12
+ </svg>
13
+ </button>
14
+ </div>
15
+ </div>
16
+ </template>
17
+ <template v-slot:foot>
18
+ <div class="p-6">
19
+
20
+ </div>
21
+ </template>
22
+ <div class="flex-1 p-6">
23
+
24
+ <button v-for="datasource in datasources"
25
+ type="button"
26
+ @click="add(datasource)">
27
+ {{ datasource.name }}
28
+ </button>
29
+
30
+ </div>
31
+ </Modal>
32
+ </template>
33
+
34
+ <script>
35
+
36
+ import Modal from "../../components/Modal.vue"
37
+ import md5 from "md5";
38
+
39
+ export default{
40
+
41
+ components: { Modal },
42
+
43
+ emits: [ 'add' ],
44
+
45
+ inject: [ 'alert', 'socket' ],
46
+
47
+ props: {
48
+
49
+ src: String
50
+
51
+ },
52
+
53
+ data(){
54
+ return {
55
+ instance: null,
56
+ datasources: null,
57
+ }
58
+ },
59
+
60
+ methods: {
61
+
62
+ add(item){
63
+ const newItem = JSON.parse(JSON.stringify(item))
64
+ newItem.uid = md5(newItem.name + Date.now())
65
+
66
+ this.close()
67
+ this.$emit('add', newItem)
68
+ },
69
+
70
+ load(){
71
+ this.socket.send(this.src, {})
72
+ .then(res => this.datasources = res.items)
73
+ .catch(err => this.alert(err))
74
+ },
75
+
76
+ open(){
77
+ this.load()
78
+ this.$refs.modal.open()
79
+ },
80
+
81
+ close(){
82
+ this.$refs.modal.close()
83
+ }
84
+
85
+ }
86
+
87
+ }
88
+
89
+ </script>
90
+
91
+ <style module>
92
+
93
+ .comp{
94
+
95
+ }
96
+
97
+ </style>
@@ -0,0 +1,99 @@
1
+ <template>
2
+ <div class="flex flex-col gap-2">
3
+
4
+ <Doughnut class="aspect-square"
5
+ :options="chartOptions"
6
+ :data="chartData" />
7
+
8
+ <div class="flex justify-center">
9
+ {{ column }}
10
+ </div>
11
+
12
+ </div>
13
+ </template>
14
+
15
+ <script>
16
+
17
+ import { Doughnut } from 'vue-chartjs'
18
+ import Chart from 'chart.js/auto'
19
+
20
+ export default{
21
+
22
+ components: {
23
+ Doughnut
24
+ },
25
+
26
+ computed: {
27
+
28
+ chartData(){
29
+ if(!Array.isArray(this.items)) return { labels:[], datasets:[] }
30
+
31
+ const labels = this.items.map(_ => _[this.rows])
32
+
33
+ const data = this.items.map(_ => _[this.column])
34
+
35
+ const backgroundColor = labels.map((_, i) => this.backgroundColors[i % this.backgroundColors.length])
36
+
37
+ return {
38
+ labels,
39
+ datasets: [{
40
+ label: this.rows,
41
+ data: data,
42
+ backgroundColor,
43
+ hoverOffset: 4
44
+ }]
45
+ }
46
+ },
47
+
48
+ chartOptions(){
49
+ return {
50
+ responsive: true,
51
+ maintainAspectRatio: true,
52
+ plugins: {
53
+ legend: {
54
+ display: false
55
+ },
56
+ },
57
+ }
58
+ },
59
+
60
+ },
61
+
62
+ data(){
63
+ return {
64
+ backgroundColors: [
65
+ '#5D9CEC',
66
+ '#A0D468',
67
+ '#FFCE54',
68
+ '#FC6E51',
69
+ '#48CFAD',
70
+ '#AC92EC',
71
+ '#4FC1E9',
72
+ '#FFCE54',
73
+ '#ED5565',
74
+ '#EC87C0'
75
+ ],
76
+ }
77
+ },
78
+
79
+ props: {
80
+
81
+ items: Array,
82
+
83
+ column: String,
84
+
85
+ rows: String
86
+
87
+ }
88
+
89
+ }
90
+
91
+ </script>
92
+
93
+ <style module>
94
+
95
+ .comp{
96
+
97
+ }
98
+
99
+ </style>
@@ -0,0 +1,80 @@
1
+ <template>
2
+ <div :class="$style.comp">
3
+
4
+ <div class="flex flex-row items-center p-3">
5
+ <label class="flex-1">Datasource</label>
6
+ <div>
7
+ <Dropdown class="min-w-[150px]"
8
+ v-model="value.datasourceUid"
9
+ @change="delete value.columns">
10
+ <option v-for="datasource in preset.datasource"
11
+ :value="datasource.uid">
12
+ {{ datasource.name }}
13
+ </option>
14
+ </Dropdown>
15
+ </div>
16
+ </div>
17
+
18
+ <div v-if="value.datasourceUid" class="flex flex-col">
19
+ <div class="flex flex-row items-center p-3">
20
+ <label class="flex-1">Column</label>
21
+ <div>
22
+ <Dropdown class="min-w-[150px]"
23
+ v-model="value.column">
24
+ <option v-for="column in datasource.columns"
25
+ :value="column.key">
26
+ {{ column.text }}
27
+ </option>
28
+ </Dropdown>
29
+ </div>
30
+ </div>
31
+
32
+ <div class="flex flex-row items-center p-3">
33
+ <label class="flex-1">Rows</label>
34
+ <div>
35
+ <Dropdown class="min-w-[150px]"
36
+ v-model="value.rows">
37
+ <option v-for="column in datasource.columns"
38
+ :value="column.key">
39
+ {{ column.text }}
40
+ </option>
41
+ </Dropdown>
42
+ </div>
43
+ </div>
44
+ </div>
45
+
46
+
47
+ </div>
48
+ </template>
49
+
50
+ <script>
51
+
52
+ export default{
53
+
54
+ computed: {
55
+
56
+ datasource(){
57
+ return this.preset.datasource.find(d => d.uid === this.value.datasourceUid)
58
+ }
59
+
60
+ },
61
+
62
+ props: {
63
+
64
+ preset: Object,
65
+
66
+ value: Object
67
+
68
+ }
69
+
70
+ }
71
+
72
+ </script>
73
+
74
+ <style module>
75
+
76
+ .comp{
77
+
78
+ }
79
+
80
+ </style>
@@ -0,0 +1,102 @@
1
+ <template>
2
+ <Modal ref="modal"
3
+ width="320"
4
+ height="360">
5
+ <template v-slot:head>
6
+ <div class="relative p-6">
7
+ <h3>{{ $t('Select View') }}</h3>
8
+ <div class="absolute top-0 right-0 p-2">
9
+ <button type="button" class="p-2" @click="close">
10
+ <svg width="24" height="24" viewBox="0 0 24 24" class="fill-text-300 hover:fill-red-500" xmlns="http://www.w3.org/2000/svg">
11
+ <path d="M6.53034 5.46965C6.23745 5.17676 5.76257 5.17676 5.46968 5.46965C5.17679 5.76255 5.17679 6.23742 5.46968 6.53031L10.9393 12L5.46967 17.4697C5.17678 17.7626 5.17678 18.2374 5.46967 18.5303C5.76256 18.8232 6.23744 18.8232 6.53033 18.5303L12 13.0606L17.4697 18.5303C17.7626 18.8232 18.2375 18.8232 18.5303 18.5303C18.8232 18.2374 18.8232 17.7626 18.5303 17.4697L13.0607 12L18.5303 6.53032C18.8232 6.23743 18.8232 5.76256 18.5303 5.46966C18.2374 5.17677 17.7626 5.17677 17.4697 5.46966L12 10.9393L6.53034 5.46965Z"/>
12
+ </svg>
13
+ </button>
14
+ </div>
15
+ </div>
16
+ </template>
17
+ <template v-slot:foot>
18
+ <div class="p-6">
19
+
20
+ </div>
21
+ </template>
22
+ <div class="flex-1 p-6">
23
+
24
+ <div class="flex flex-col">
25
+ <button v-for="component in components"
26
+ type="button"
27
+ :class="$style.button"
28
+ @click="add(component)">
29
+ {{ component.text }}
30
+ </button>
31
+ </div>
32
+
33
+ </div>
34
+ </Modal>
35
+ </template>
36
+
37
+ <script>
38
+
39
+ import Modal from "../../components/Modal.vue"
40
+ import md5 from "md5";
41
+
42
+ export default{
43
+
44
+ components: { Modal },
45
+
46
+ emits: [ 'add' ],
47
+
48
+ inject: [ 'socketEmit2' ],
49
+
50
+ props: {
51
+ },
52
+
53
+ data(){
54
+ return {
55
+ callback: null,
56
+ instance: null,
57
+
58
+ components: [
59
+ { type:"Bar", text:"Bar Chart" },
60
+ { type:"Doughnut", text:"Doughnut" },
61
+ { type:"Line", text:"Line Chart" },
62
+ { type:"Metric", text:"Metric" },
63
+ { type:"Row", text:"Row", items:[], isContainer:true },
64
+ { type:"VirtualTable", text:"Table" },
65
+ ]
66
+ }
67
+ },
68
+
69
+ methods: {
70
+
71
+ add(component){
72
+ const newComponent = JSON.parse(JSON.stringify(component))
73
+ newComponent.uid = md5(newComponent.type + Date.now())
74
+ if(typeof this.callback === 'function')
75
+ this.callback(newComponent)
76
+ this.$emit('add', newComponent)
77
+ this.close()
78
+ },
79
+
80
+ open(callback){
81
+ this.callback = callback
82
+ this.$refs.modal.open()
83
+ },
84
+
85
+ close(){
86
+ this.$refs.modal.close()
87
+ }
88
+
89
+ }
90
+
91
+ }
92
+
93
+ </script>
94
+
95
+ <style module>
96
+
97
+ .button{
98
+ @apply text-left p-3 border-[1px] border-transparent rounded-lg;
99
+ @apply hover:border-primary-300 hover:bg-primary-100;
100
+ }
101
+
102
+ </style>
@@ -0,0 +1,43 @@
1
+ <template>
2
+ <div :class="$style.comp">
3
+
4
+ <div class="flex flex-row items-center p-3">
5
+ <label class="flex-1">Datasource</label>
6
+ <div>
7
+ <Dropdown class="min-w-[150px]"
8
+ v-model="value.datasourceUid"
9
+ @change="delete value.columns">
10
+ <option v-for="datasource in preset.datasource"
11
+ :value="datasource.uid">
12
+ {{ datasource.name }}
13
+ </option>
14
+ </Dropdown>
15
+ </div>
16
+ </div>
17
+
18
+ </div>
19
+ </template>
20
+
21
+ <script>
22
+
23
+ export default{
24
+
25
+ props: {
26
+
27
+ preset: Object,
28
+
29
+ value: Object
30
+
31
+ }
32
+
33
+ }
34
+
35
+ </script>
36
+
37
+ <style module>
38
+
39
+ .comp{
40
+
41
+ }
42
+
43
+ </style>
@@ -0,0 +1,502 @@
1
+ <template>
2
+ <div v-if="cConfig" :class="$style.comp">
3
+
4
+ <div class="relative flex flex-col border-r-[1px] border-text-50 panel-400 overflow-hidden"
5
+ :style="sidebarStyle">
6
+
7
+ <TransitionGroup name="openltr" tag="div" class="flex-1 flex flex-col">
8
+
9
+ <div v-if="selectedView" class="flex-1 flex flex-col p-3">
10
+ <div class="p-3 flex flex-row gap-2">
11
+ <button type="button" @click="delete cConfig.selectedView">
12
+ <svg width="16" height="16" class="fill-text" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Pro 6.0.0-alpha3 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M447.1 256C447.1 273.7 433.7 288 416 288H109.3l105.4 105.4c12.5 12.5 12.5 32.75 0 45.25C208.4 444.9 200.2 448 192 448s-16.38-3.125-22.62-9.375l-160-160c-12.5-12.5-12.5-32.75 0-45.25l160-160c12.5-12.5 32.75-12.5 45.25 0s12.5 32.75 0 45.25L109.3 224H416C433.7 224 447.1 238.3 447.1 256z"/></svg>
13
+ </button>
14
+ <h5>{{ selectedView.type }}</h5>
15
+ </div>
16
+
17
+ <div class="flex-1">
18
+
19
+ <component :is="`${selectedView.type}Setting`"
20
+ :preset="selectedPreset"
21
+ :value="selectedView" />
22
+
23
+ </div>
24
+ </div>
25
+
26
+ <div v-else-if="selectedDatasource" class="flex-1 flex flex-col gap-3 p-3">
27
+ <div class="p-3 flex flex-row gap-2">
28
+ <button type="button" @click="delete cConfig.selectedDatasource">
29
+ <svg width="16" height="16" class="fill-text" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Pro 6.0.0-alpha3 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M447.1 256C447.1 273.7 433.7 288 416 288H109.3l105.4 105.4c12.5 12.5 12.5 32.75 0 45.25C208.4 444.9 200.2 448 192 448s-16.38-3.125-22.62-9.375l-160-160c-12.5-12.5-12.5-32.75 0-45.25l160-160c12.5-12.5 32.75-12.5 45.25 0s12.5 32.75 0 45.25L109.3 224H416C433.7 224 447.1 238.3 447.1 256z"/></svg>
30
+ </button>
31
+ <h5 @click="log(selectedDatasource)">{{ selectedDatasource.name }}</h5>
32
+ </div>
33
+
34
+ <div class="flex justify-center">
35
+ <Tabs :items="datasourceTabs" v-model="datasourceTabIndex" />
36
+ </div>
37
+
38
+ <div>
39
+
40
+ <div v-if="datasourceTabIndex === 1">
41
+ <ListItem :items="selectedDatasource.columns"
42
+ @reorder="(from, to) => { selectedDatasource.columns.splice(to, 0, selectedDatasource.columns.splice(from, 1)[0]) }"
43
+ class="mt-2 h-[25vh] overflow-y-auto border-[1px] border-text-200 bg-base-400 rounded-lg p-0.5"
44
+ container-class="divide-y divide-text-50">
45
+ <template v-slot="{ item }">
46
+ <div class="flex flex-row gap-2 items-center px-2 bg-base-500">
47
+ <div data-reorder>
48
+ <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>
49
+ </div>
50
+ <Textbox class="flex-1 border-none" :class="$style.columnTextbox" v-model="item.label2" :placeholder="item.label"
51
+ item-class="p-1" />
52
+ <button type="button" @click="selectedDatasource.columns.splice(index, 1);">
53
+ <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>
54
+ </button>
55
+ </div>
56
+ </template>
57
+ </ListItem>
58
+ </div>
59
+
60
+ <div v-else-if="datasourceTabIndex === 2">
61
+
62
+ </div>
63
+
64
+ </div>
65
+ </div>
66
+
67
+ <div v-else-if="selectedPreset" class="flex-1 flex flex-col p-3">
68
+ <div class="p-3 flex flex-row gap-2">
69
+ <button type="button" @click="$router.replace({ hash:'' })">
70
+ <svg width="16" height="16" class="fill-text" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Pro 6.0.0-alpha3 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M447.1 256C447.1 273.7 433.7 288 416 288H109.3l105.4 105.4c12.5 12.5 12.5 32.75 0 45.25C208.4 444.9 200.2 448 192 448s-16.38-3.125-22.62-9.375l-160-160c-12.5-12.5-12.5-32.75 0-45.25l160-160c12.5-12.5 32.75-12.5 45.25 0s12.5 32.75 0 45.25L109.3 224H416C433.7 224 447.1 238.3 447.1 256z"/></svg>
71
+ </button>
72
+ <Textbox v-model="selectedPreset.name"
73
+ item-class="p-0 text-lg"
74
+ @click="log(selectedPreset)"
75
+ class="border-none bg-transparent"/>
76
+ </div>
77
+
78
+ <div class="flex-1">
79
+ <div class="p-3">
80
+ <div class="flex flex-row gap-2">
81
+ <label class="flex-1">Datasource</label>
82
+ <button type="button"
83
+ class="text-primary"
84
+ @click="$refs.chooseDatasource.open()">
85
+ Add Datasource
86
+ </button>
87
+ </div>
88
+ <div class="flex flex-col mt-2">
89
+ <ListItem :items="selectedPreset.datasource">
90
+ <template v-slot="{ item, index }">
91
+ <div class="flex flex-row items-center gap-3 p-3">
92
+ <div class="flex-1" @click="cConfig.selectedDatasource = item.uid">
93
+ {{ item.name }}
94
+ </div>
95
+ <button type="button" @click="selectedPreset.datasource.splice(index, 1)">
96
+ <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>
97
+ </button>
98
+ </div>
99
+ </template>
100
+ </ListItem>
101
+ </div>
102
+ </div>
103
+
104
+ <div class="p-3">
105
+ <div class="flex flex-row gap-2">
106
+ <label class="flex-1">Views</label>
107
+ <button type="button" class="text-primary"
108
+ @click="$refs.chooseView.open(_ => selectedPreset.views.push(_))">
109
+ Add View
110
+ </button>
111
+ </div>
112
+ <div class="flex flex-col">
113
+ <TreeView :items="selectedPreset.views"
114
+ :config="componentsConfig"
115
+ v-model="selectedViewUid"
116
+ class="mt-2">
117
+ <template #item="{ item, parent }">
118
+ <div class="flex-1 p-3 overflow-hidden text-ellipsis cursor-pointer flex flex-row gap-2">
119
+ <div class="flex-1">
120
+ <strong @click="cConfig.selectedView = item.uid" class="hover:text-primary">{{ item.text }}</strong>
121
+ </div>
122
+ <button type="button"
123
+ v-if="Array.isArray(item.items)"
124
+ @click="$refs.chooseView.open(_ => item.items.push(_))">
125
+ <svg width="14" height="14" class="fill-text-300 hover:fill-primary-600" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Pro 6.0.0-alpha3 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M432 256c0 17.69-14.33 32.01-32 32.01H256v144c0 17.69-14.33 31.99-32 31.99s-32-14.3-32-31.99v-144H48c-17.67 0-32-14.32-32-32.01s14.33-31.99 32-31.99H192v-144c0-17.69 14.33-32.01 32-32.01s32 14.32 32 32.01v144h144C417.7 224 432 238.3 432 256z"/></svg>
126
+ </button>
127
+ <button type="button" @click="parent.splice(parent.indexOf(item), 1)">
128
+ <svg width="14" height="14" class="fill-text-300 hover:fill-red-600" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><!--! Font Awesome Pro 6.0.0-alpha3 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M310.6 361.4c12.5 12.5 12.5 32.75 0 45.25C304.4 412.9 296.2 416 288 416s-16.38-3.125-22.62-9.375L160 301.3L54.63 406.6C48.38 412.9 40.19 416 32 416S15.63 412.9 9.375 406.6c-12.5-12.5-12.5-32.75 0-45.25l105.4-105.4L9.375 150.6c-12.5-12.5-12.5-32.75 0-45.25s32.75-12.5 45.25 0L160 210.8l105.4-105.4c12.5-12.5 32.75-12.5 45.25 0s12.5 32.75 0 45.25l-105.4 105.4L310.6 361.4z"/></svg>
129
+ </button>
130
+ </div>
131
+ </template>
132
+ </TreeView>
133
+
134
+ </div>
135
+ </div>
136
+ </div>
137
+
138
+ </div>
139
+
140
+ <div v-else class="flex-1 flex flex-col">
141
+ <div class="p-6 flex flex-row items-start gap-2">
142
+ <div class="flex-1">
143
+ <h5 @click="log(cConfig)">{{ title }}</h5>
144
+ </div>
145
+ <button type="button" @click="addPreset">
146
+ <svg width="19" height="19" class="fill-primary" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Pro 6.0.0-alpha3 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M432 256c0 17.69-14.33 32.01-32 32.01H256v144c0 17.69-14.33 31.99-32 31.99s-32-14.3-32-31.99v-144H48c-17.67 0-32-14.32-32-32.01s14.33-31.99 32-31.99H192v-144c0-17.69 14.33-32.01 32-32.01s32 14.32 32 32.01v144h144C417.7 224 432 238.3 432 256z"/></svg>
147
+ </button>
148
+ </div>
149
+
150
+ <div v-if="(cConfig.presets ?? []).length > 0" class="flex-1 overflow-y-auto px-6">
151
+ <ListItem :items="cConfig.presets" container-class="divide-y divide-text-50">
152
+ <template v-slot="{ item, index }">
153
+ <div class="flex flex-row items-center gap-3 p-3">
154
+ <Radio v-model="cConfig.viewedUid" :value="item.uid" />
155
+ <div class="flex-1" @click="openPreset(item.uid)">
156
+ {{ item.name }}
157
+ </div>
158
+ <button type="button" @click="confirm('Remove this preset?', () => cConfig.presets.splice(index, 1))">
159
+ <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>
160
+ </button>
161
+ </div>
162
+ </template>
163
+ </ListItem>
164
+ </div>
165
+ <div v-else class="flex-1 flex items-center justify-center">
166
+ <button type="button"
167
+ class="cursor-pointer flex flex-col gap-1 items-center"
168
+ @click="addPreset">
169
+ <svg width="21" height="21" class="fill-primary" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Pro 6.0.0-alpha3 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M432 256c0 17.69-14.33 32.01-32 32.01H256v144c0 17.69-14.33 31.99-32 31.99s-32-14.3-32-31.99v-144H48c-17.67 0-32-14.32-32-32.01s14.33-31.99 32-31.99H192v-144c0-17.69 14.33-32.01 32-32.01s32 14.32 32 32.01v144h144C417.7 224 432 238.3 432 256z"/></svg>
170
+ Create new preset
171
+ </button>
172
+ </div>
173
+
174
+ </div>
175
+
176
+ </TransitionGroup>
177
+
178
+ <div :class="$style.resize1"
179
+ @mousedown="(e) => $util.dragResize(e, resize1)"></div>
180
+ </div>
181
+
182
+ <div v-if="viewedPreset" class="flex-1 px-8 py-5 overflow-y-auto flex flex-col gap-6">
183
+
184
+ <div class="flex flex-row gap-2 md:gap-2 items-center" ref="title">
185
+ <button type="button"
186
+ class="p-2 rounded-xl"
187
+ :class="sidebar.open ? 'bg-primary' : ''"
188
+ @click="sidebar.open = !sidebar.open">
189
+ <svg width="21" height="21" :class="sidebar.open ? 'fill-text' : 'fill-text hover:fill-primary'" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.0.0-alpha3 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M480 384H249.2C236.9 355.8 208.8 336 176 336S115.1 355.8 102.8 384H32c-17.67 0-32 14.33-32 32s14.33 32 32 32h70.75C115.1 476.2 143.2 496 176 496s60.89-19.77 73.25-48H480c17.67 0 32-14.33 32-32S497.7 384 480 384zM176 448c-17.64 0-32-14.36-32-32s14.36-32 32-32s32 14.36 32 32S193.6 448 176 448zM480 224h-70.75C396.9 195.8 368.8 176 336 176S275.1 195.8 262.8 224H32C14.33 224 0 238.3 0 256s14.33 32 32 32h230.8C275.1 316.2 303.2 336 336 336s60.89-19.77 73.25-48H480c17.67 0 32-14.33 32-32S497.7 224 480 224zM336 288c-17.64 0-32-14.36-32-32s14.36-32 32-32s32 14.36 32 32S353.6 288 336 288zM32 128h102.8C147.1 156.2 175.2 176 208 176s60.89-19.77 73.25-48H480c17.67 0 32-14.33 32-32s-14.33-32-32-32h-198.8C268.9 35.77 240.8 16 208 16S147.1 35.77 134.8 64H32C14.33 64 0 78.33 0 96S14.33 128 32 128zM208 64c17.64 0 32 14.36 32 32s-14.36 32-32 32s-32-14.36-32-32S190.4 64 208 64z"/></svg>
190
+ </button>
191
+ <div class="flex flex-col whitespace-nowrap text-ellipsis overflow-hidden">
192
+ <div class="cursor-pointer group px-2 md:px-0" @click="$refs.contextMenu.open($refs.title)">
193
+ <small class="text-text-400 text-xs">{{ title ?? 'Untitled' }}</small>
194
+ <div class="flex flex-row items-baseline gap-1">
195
+ <h5 class="whitespace-nowrap relative top-[-2px] text-ellipsis overflow-hidden group-hover:text-primary">
196
+ {{ viewedPreset.name ?? 'Preset Name' }}
197
+ </h5>
198
+ <svg width="13" height="13" class="fill-text group-hover:fill-primary relative top-[-1px]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><!--! Font Awesome Pro 6.0.0-alpha3 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M310.6 246.6l-127.1 128C176.4 380.9 168.2 384 160 384s-16.38-3.125-22.63-9.375l-127.1-128C.2244 237.5-2.516 223.7 2.438 211.8S19.07 192 32 192h255.1c12.94 0 24.62 7.781 29.58 19.75S319.8 237.5 310.6 246.6z"/></svg>
199
+ </div>
200
+ </div>
201
+ <ContextMenu ref="contextMenu" class="panel-400">
202
+ <div class="flex flex-col min-w-[300px] divide-y divide-text-50">
203
+
204
+ <div class="p-3 flex flex-col gap-1 bg-base-300">
205
+ <button v-for="(preset, idx) in config.presets"
206
+ class="p-2 px-5 text-left flex flex-row items-center rounded-md"
207
+ :class="config.presetIdx === idx ? 'bg-primary-200 text-white' : 'hover:bg-primary-100'"
208
+ type="button"
209
+ @click="selectPreset(idx)">
210
+ <label class="flex-1 pr-12">
211
+ {{ preset.name }}
212
+ </label>
213
+ <div class="p-1">
214
+ <div v-if="idx < 10"
215
+ class="border-[1px] border-text-300 px-2 text-mono text-xs text-text-400 rounded-lg">
216
+ Alt {{ idx + 1 }}
217
+ </div>
218
+ </div>
219
+ </button>
220
+ </div>
221
+
222
+ <div class="px-3">
223
+ <button type="button" class="text-primary p-5 text-left w-full" @click="openPreset">Edit</button>
224
+ </div>
225
+
226
+ </div>
227
+ </ContextMenu>
228
+ </div>
229
+ </div>
230
+
231
+ <div v-for="view in viewedPreset.views">
232
+
233
+ <div v-if="view.type === 'Row'" class="grid grid-cols-6 gap-5">
234
+ <div v-for="subView in view.items">
235
+
236
+ <Doughnut v-if="subView.type === 'Doughnut'"
237
+ :items="data[subView.datasourceUid]?.items"
238
+ :="subView" />
239
+
240
+ </div>
241
+ </div>
242
+
243
+ <VirtualTable v-if="view.type === 'VirtualTable'"
244
+ :columns="viewColumns(view)"
245
+ class="h-[50vh]"
246
+ :items="data[view.datasourceUid]?.items" />
247
+
248
+ </div>
249
+
250
+ </div>
251
+
252
+ <DatasourceSelector ref="chooseDatasource"
253
+ :src="datasourceSrc"
254
+ @add="addDatasource" />
255
+
256
+ <ViewSelector ref="chooseView" />
257
+
258
+ </div>
259
+ </template>
260
+
261
+ <script>
262
+
263
+ import DatasourceSelector from "./Dashboard/DatasourceSelector.vue";
264
+ import ViewSelector from "./Dashboard/ViewSelector.vue";
265
+ import md5 from "md5";
266
+ import TreeView from "./WebPageBuilder4/TreeView.vue";
267
+ import Doughnut from "./Dashboard/Doughnut.vue";
268
+ import DoughnutSetting from "./Dashboard/DoughnutSetting.vue";
269
+ import VirtualTableSetting from "./Dashboard/VirtualTableSetting.vue";
270
+
271
+ const findComponents = (items, uid) => {
272
+ if(!Array.isArray(items)) return
273
+
274
+ for(let item of items){
275
+ if(item.uid === uid)
276
+ return item
277
+
278
+ if(item.items){
279
+ const component = findComponents(item.items, uid)
280
+ if(component)
281
+ return component
282
+ }
283
+ }
284
+ }
285
+
286
+ export default{
287
+ components: {
288
+ Doughnut,
289
+ DoughnutSetting,
290
+ TreeView,
291
+ ViewSelector,
292
+ DatasourceSelector,
293
+ VirtualTableSetting,
294
+ },
295
+
296
+ computed: {
297
+
298
+ cConfig(){
299
+ return this.config ?? this._config
300
+ },
301
+
302
+ componentsConfig(){
303
+ if(!this.cConfig.components)
304
+ this.cConfig.components = {}
305
+ return this.cConfig.components
306
+ },
307
+
308
+ selectedDatasource(){
309
+ const datasource = this.selectedPreset?.datasource[this.selectedPreset.datasource.findIndex(_ => _.uid === this.cConfig.selectedDatasource)]
310
+
311
+ if(datasource){
312
+
313
+
314
+
315
+ }
316
+
317
+ return datasource
318
+ },
319
+
320
+ selectedPreset(){
321
+ const preset = this.cConfig.presets[this.cConfig.presets.findIndex(_ => _.uid === this.cConfig.selectedUid)]
322
+
323
+ if(preset){
324
+ if(!Array.isArray(preset.datasource))
325
+ preset.datasource = []
326
+
327
+ if(!Array.isArray(preset.views))
328
+ preset.views = []
329
+ }
330
+
331
+ return preset
332
+ },
333
+
334
+ selectedView(){
335
+ return findComponents(this.selectedPreset?.views, this.cConfig.selectedView)
336
+ },
337
+
338
+ sidebar(){
339
+ if(!this.cConfig.sidebar || !('open' in this.cConfig.sidebar))
340
+ this.cConfig.sidebar = {
341
+ open: true,
342
+ width: 360
343
+ }
344
+
345
+ return this.cConfig.sidebar
346
+ },
347
+
348
+ sidebarStyle(){
349
+ return {
350
+ width: !this.sidebar.open ? 0 : this.sidebar.width + 'px'
351
+ }
352
+ },
353
+
354
+ viewedPreset(){
355
+ return this.cConfig?.presets[this.cConfig.presets.findIndex(_ => _.uid === this.cConfig.viewedUid)]
356
+ },
357
+
358
+ },
359
+
360
+ data(){
361
+ return {
362
+ data: {},
363
+
364
+ datasourceTabs: [
365
+ { text:"Columns", value:1 },
366
+ { text:"Filters", value:2 },
367
+ { text:"Sorts", value:3 },
368
+ ],
369
+
370
+ datasourceTabIndex: 1,
371
+
372
+ selectedViewUid: null
373
+ }
374
+ },
375
+
376
+ inject: [ 'alert', 'confirm', 'socket' ],
377
+
378
+ methods: {
379
+
380
+ addDatasource(datasource){
381
+ this.selectedPreset.datasource.push(datasource)
382
+ },
383
+
384
+ addPreset(){
385
+ if(!Array.isArray(this.cConfig.presets))
386
+ this.cConfig.presets = []
387
+
388
+ const newPreset = {
389
+ name: 'New Preset',
390
+ uid: md5('Preset' + Date.now()),
391
+ }
392
+ this.cConfig.presets.push(newPreset)
393
+ this.openPreset(newPreset.uid)
394
+ },
395
+
396
+ viewColumns(view){
397
+
398
+ if(!view.columns){
399
+ view.columns = Object.keys(((this.data[view.datasourceUid] ?? {}).items ?? [])[0] ?? [])
400
+ .map(key => {
401
+ return {
402
+ key,
403
+ label: key
404
+ }
405
+ })
406
+ }
407
+
408
+ return view.columns
409
+ },
410
+
411
+ async load(){
412
+ for(let datasource of this.viewedPreset?.datasource ?? []){
413
+ this.socket.send(datasource.src)
414
+ .then(res => {
415
+ this.data[datasource.uid] = res
416
+ })
417
+ }
418
+ },
419
+
420
+ openPreset(uid){
421
+ this.$router.push({ hash:`#dashboard-${uid}` })
422
+ },
423
+
424
+ resize1(w){
425
+ if(this.sidebar.width + w >= 270 && this.sidebar.width + w <= 600){
426
+ this.sidebar.width += w
427
+ }
428
+ },
429
+
430
+ },
431
+
432
+ props:{
433
+
434
+ config: Object,
435
+
436
+ datasourceSrc: String,
437
+
438
+ title: String,
439
+
440
+ },
441
+
442
+ provide(){
443
+ return {
444
+ listStyle: () => {}
445
+ }
446
+ },
447
+
448
+ watch: {
449
+
450
+ 'cConfig.presets'(){
451
+ if(this.$route.hash.startsWith('#dashboard')){
452
+ this.cConfig.selectedUid = this.$route.hash.replace('#dashboard-', '')
453
+ }
454
+ else {
455
+ this.cConfig.selectedUid = false
456
+ }
457
+ },
458
+
459
+ '$route.hash': {
460
+ immediate: true,
461
+ handler(to){
462
+ if(this.cConfig){
463
+ if(to.startsWith('#dashboard')){
464
+ this.cConfig.selectedUid = to.replace('#dashboard-', '')
465
+ }
466
+ else {
467
+ this.cConfig.selectedUid = false
468
+ }
469
+ }
470
+ }
471
+ },
472
+
473
+ viewedPreset: {
474
+ immediate: true,
475
+ handler(){
476
+ this.load()
477
+ }
478
+ }
479
+
480
+ }
481
+
482
+ }
483
+
484
+ </script>
485
+
486
+ <style>
487
+
488
+
489
+
490
+ </style>
491
+
492
+ <style module>
493
+
494
+ .comp{
495
+ @apply flex flex-row;
496
+ }
497
+
498
+ .resize1{
499
+ @apply w-[3px] cursor-ew-resize absolute top-0 right-0 bottom-0;
500
+ }
501
+
502
+ </style>
@@ -57,7 +57,10 @@ export default{
57
57
  }
58
58
  },
59
59
  parent: Array,
60
- config: Object,
60
+ config: {
61
+ type: Object,
62
+ default: () => ({})
63
+ },
61
64
  value: String,
62
65
  },
63
66