glib-web 3.25.2 → 3.26.0
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/.eslintrc.js +6 -3
- package/action.js +14 -1
- package/actions/auth/saveCsrfToken.js +2 -0
- package/actions/bottom_banners/close.js +9 -0
- package/actions/bottom_banners/open.js +11 -0
- package/actions/components/find.js +2 -0
- package/actions/components/findClosest.js +2 -0
- package/actions/components/replace.js +2 -0
- package/actions/fields/blur.js +1 -0
- package/actions/fields/focus.js +1 -0
- package/actions/global_states/set.js +13 -0
- package/actions/global_states/watch.js +19 -0
- package/actions/windows/close.js +1 -1
- package/actions/windows/closeAll.js +2 -2
- package/app.vue +19 -1
- package/components/charts/series.js +12 -16
- package/components/composable/dirtyState.js +2 -2
- package/components/composable/dropable.js +84 -67
- package/components/composable/gmap.js +1 -1
- package/components/composable/pasteable.js +15 -21
- package/components/composable/socket.js +4 -3
- package/components/composable/upload_delegator.js +24 -0
- package/components/fields/multiUpload.vue +17 -15
- package/components/mixins/events.js +0 -15
- package/components/mixins/styles.js +3 -1
- package/components/tabBar.vue +11 -8
- package/components/treeView.vue +17 -21
- package/constant.js +1 -0
- package/package.json +2 -2
- package/store.js +4 -1
- package/utils/history.js +10 -1
- package/components/mixins/chart/annotation.js +0 -73
- package/components/mixins/chart/tooltip.js +0 -31
package/.eslintrc.js
CHANGED
|
@@ -3,7 +3,7 @@ module.exports = {
|
|
|
3
3
|
"browser": true,
|
|
4
4
|
"es2021": true
|
|
5
5
|
},
|
|
6
|
-
"extends": "plugin:vue/vue3-essential",
|
|
6
|
+
"extends": ["plugin:vue/vue3-essential", "eslint:recommended"],
|
|
7
7
|
"overrides": [
|
|
8
8
|
{
|
|
9
9
|
"env": {
|
|
@@ -25,10 +25,13 @@ module.exports = {
|
|
|
25
25
|
"vue"
|
|
26
26
|
],
|
|
27
27
|
"rules": {
|
|
28
|
-
"vue/multi-word-component-names": ["off"]
|
|
28
|
+
"vue/multi-word-component-names": ["off"],
|
|
29
|
+
"no-unused-vars": "warn"
|
|
29
30
|
},
|
|
30
31
|
"globals": {
|
|
31
32
|
"GLib": "readonly",
|
|
32
|
-
"Utils": "readonly"
|
|
33
|
+
"Utils": "readonly",
|
|
34
|
+
"google": "readonly",
|
|
35
|
+
"Stripe": "readonly"
|
|
33
36
|
}
|
|
34
37
|
};
|
package/action.js
CHANGED
|
@@ -77,6 +77,12 @@ import ActionLogicsSet from "./actions/logics/set";
|
|
|
77
77
|
|
|
78
78
|
import ActionListsAppend from "./actions/lists/append";
|
|
79
79
|
|
|
80
|
+
import ActionBottomBannersOpen from "./actions/bottom_banners/open";
|
|
81
|
+
import ActionBottomBannersClose from "./actions/bottom_banners/close";
|
|
82
|
+
|
|
83
|
+
import ActionGlobalStatesWatch from "./actions/global_states/watch";
|
|
84
|
+
import ActionGlobalStatesSet from "./actions/global_states/set";
|
|
85
|
+
|
|
80
86
|
import { dialogs, vueApp } from "./store";
|
|
81
87
|
|
|
82
88
|
const actions = {
|
|
@@ -154,7 +160,13 @@ const actions = {
|
|
|
154
160
|
|
|
155
161
|
"logics/set": ActionLogicsSet,
|
|
156
162
|
|
|
157
|
-
"lists/append": ActionListsAppend
|
|
163
|
+
"lists/append": ActionListsAppend,
|
|
164
|
+
|
|
165
|
+
"bottomBanners/open": ActionBottomBannersOpen,
|
|
166
|
+
"bottomBanners/close": ActionBottomBannersClose,
|
|
167
|
+
|
|
168
|
+
"globalStates/watch": ActionGlobalStatesWatch,
|
|
169
|
+
"globalStates/set": ActionGlobalStatesSet
|
|
158
170
|
};
|
|
159
171
|
|
|
160
172
|
const customActions = {};
|
|
@@ -242,4 +254,5 @@ export default class Action {
|
|
|
242
254
|
}
|
|
243
255
|
}
|
|
244
256
|
|
|
257
|
+
// TODO: not recommended
|
|
245
258
|
window.Action = Action;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { vueApp } from "../../store";
|
|
2
|
+
|
|
3
|
+
export default class {
|
|
4
|
+
execute(properties, component) {
|
|
5
|
+
const { targetId, newView } = properties;
|
|
6
|
+
|
|
7
|
+
if (!targetId) console.error('targetId is required');
|
|
8
|
+
|
|
9
|
+
vueApp.bottomBanners[targetId] ||= newView;
|
|
10
|
+
}
|
|
11
|
+
}
|
package/actions/fields/blur.js
CHANGED
package/actions/fields/focus.js
CHANGED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { vueApp } from "../../store";
|
|
2
|
+
import merge from "lodash.merge";
|
|
3
|
+
|
|
4
|
+
export default class {
|
|
5
|
+
execute(properties) {
|
|
6
|
+
const { state, data } = properties;
|
|
7
|
+
vueApp[state] = merge(vueApp[state], data);
|
|
8
|
+
|
|
9
|
+
// workaround to trigger watcher
|
|
10
|
+
const random = (Math.random() + 1).toString(36).substring(7);
|
|
11
|
+
vueApp[state][random] = 'update!';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { watch } from "vue";
|
|
2
|
+
import { vueApp } from "../../store";
|
|
3
|
+
import merge from "lodash.merge";
|
|
4
|
+
import Action from "../../action";
|
|
5
|
+
|
|
6
|
+
export default class {
|
|
7
|
+
execute(properties, component) {
|
|
8
|
+
const { state, onChange } = properties;
|
|
9
|
+
|
|
10
|
+
watch(vueApp[state], (value) => {
|
|
11
|
+
const formData = {
|
|
12
|
+
glib: { globalState: value }
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const data = merge({}, onChange, { formData: formData });
|
|
16
|
+
Action.execute(data, component);
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
}
|
package/actions/windows/close.js
CHANGED
|
@@ -4,8 +4,8 @@ import Type from "../../utils/type";
|
|
|
4
4
|
export default class {
|
|
5
5
|
execute(properties, component) {
|
|
6
6
|
vueApp.richTextValues = {};
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
|
|
8
|
+
Utils.history.destroy();
|
|
9
9
|
|
|
10
10
|
Type.ifObject(properties["onClose"], it => {
|
|
11
11
|
// Allow time for history.back() to complete, which is important for actions that need
|
package/app.vue
CHANGED
|
@@ -25,6 +25,13 @@
|
|
|
25
25
|
</v-container>
|
|
26
26
|
</v-main>
|
|
27
27
|
</component>
|
|
28
|
+
<div :id="tooltipId">
|
|
29
|
+
<panels-responsive :spec="vueApp.tooltipSpec"></panels-responsive>
|
|
30
|
+
</div>
|
|
31
|
+
<div class="glib-bottomBanner">
|
|
32
|
+
<glib-component v-for="(bottomBanner, index) in Object.values(vueApp.bottomBanners)" :spec="bottomBanner"
|
|
33
|
+
:key="index"></glib-component>
|
|
34
|
+
</div>
|
|
28
35
|
</v-app>
|
|
29
36
|
</template>
|
|
30
37
|
|
|
@@ -39,6 +46,7 @@ import { useDirtyState } from "./components/composable/dirtyState";
|
|
|
39
46
|
import { useSocket } from "./components/composable/socket";
|
|
40
47
|
import { usePasteable } from "./components/composable/pasteable";
|
|
41
48
|
import { computed } from "vue";
|
|
49
|
+
import { TOOLTIP_ID } from "./constant";
|
|
42
50
|
|
|
43
51
|
const { watchDirtyState } = useDirtyState();
|
|
44
52
|
|
|
@@ -46,8 +54,9 @@ export default {
|
|
|
46
54
|
setup(props) {
|
|
47
55
|
const filePaster = computed(() => props.page.filePaster);
|
|
48
56
|
usePasteable(filePaster);
|
|
57
|
+
const tooltipId = TOOLTIP_ID;
|
|
49
58
|
|
|
50
|
-
return { vueApp };
|
|
59
|
+
return { vueApp, tooltipId };
|
|
51
60
|
},
|
|
52
61
|
components: {
|
|
53
62
|
"nav-layout": NavLayout,
|
|
@@ -74,6 +83,9 @@ export default {
|
|
|
74
83
|
footer() {
|
|
75
84
|
return this.page.footer || {};
|
|
76
85
|
},
|
|
86
|
+
bodyHeader() {
|
|
87
|
+
return this.page.bodyHeader || {};
|
|
88
|
+
},
|
|
77
89
|
bodyFooter() {
|
|
78
90
|
return this.page.bodyFooter || {};
|
|
79
91
|
},
|
|
@@ -338,6 +350,12 @@ body,
|
|
|
338
350
|
overflow-y: overlay;
|
|
339
351
|
}
|
|
340
352
|
|
|
353
|
+
.glib-bottomBanner {
|
|
354
|
+
position: fixed;
|
|
355
|
+
bottom: 0;
|
|
356
|
+
right: 0;
|
|
357
|
+
z-index: 99;
|
|
358
|
+
}
|
|
341
359
|
|
|
342
360
|
/******/
|
|
343
361
|
</style>
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { Chart, Colors } from "chart.js";
|
|
2
2
|
import chartDataLabels from 'chartjs-plugin-datalabels';
|
|
3
3
|
import doughnutLabel from 'chartjs-plugin-doughnutlabel-v3';
|
|
4
|
-
import { Vue } from "../..";
|
|
4
|
+
import { Vue, vueApp } from "../..";
|
|
5
5
|
import { computePosition, flip, offset } from '@floating-ui/dom';
|
|
6
6
|
|
|
7
7
|
import 'chartkick/chart.js';
|
|
8
8
|
|
|
9
|
+
import { TOOLTIP_ID } from "../../constant";
|
|
10
|
+
|
|
9
11
|
Chart.register(chartDataLabels);
|
|
10
12
|
Chart.register(doughnutLabel);
|
|
11
13
|
Chart.register(Colors);
|
|
@@ -14,7 +16,6 @@ import VueChartkick from 'vue-chartkick';
|
|
|
14
16
|
import { computed } from "vue";
|
|
15
17
|
Vue.use(VueChartkick);
|
|
16
18
|
|
|
17
|
-
|
|
18
19
|
const multipleDataSeries = (dataSeries) => {
|
|
19
20
|
return dataSeries.map((value) => {
|
|
20
21
|
let points = null;
|
|
@@ -32,25 +33,20 @@ const multipleDataSeries = (dataSeries) => {
|
|
|
32
33
|
const singleDataSeries = (dataSeries) => dataSeries.reduce((prev, curr) => Object.assign(prev, { [curr.title]: curr.value }), {});
|
|
33
34
|
|
|
34
35
|
const getOrCreateTooltip = (chart) => {
|
|
35
|
-
|
|
36
|
+
const tooltipEl = document.getElementById(TOOLTIP_ID);
|
|
36
37
|
let referenceEl = chart.canvas.parentNode.querySelector('#_glib-chart-reference');
|
|
37
38
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
tooltipEl.style.position = 'absolute';
|
|
44
|
-
tooltipEl.style.transition = 'all .1s ease';
|
|
45
|
-
tooltipEl.style.lineHeight = 1;
|
|
39
|
+
tooltipEl.style.opacity = 1;
|
|
40
|
+
tooltipEl.style.pointerEvents = 'none';
|
|
41
|
+
tooltipEl.style.position = 'fixed';
|
|
42
|
+
tooltipEl.style.transition = 'all .1s ease';
|
|
43
|
+
tooltipEl.style.lineHeight = 1;
|
|
46
44
|
|
|
47
|
-
chart.canvas.parentNode.appendChild(tooltipEl);
|
|
48
|
-
}
|
|
49
45
|
|
|
50
46
|
if (!referenceEl) {
|
|
51
47
|
referenceEl = document.createElement('div');
|
|
52
48
|
referenceEl.id = '_glib-chart-reference';
|
|
53
|
-
referenceEl.style.position = '
|
|
49
|
+
referenceEl.style.position = 'fixed';
|
|
54
50
|
referenceEl.style.width = '1px';
|
|
55
51
|
referenceEl.style.height = '1px';
|
|
56
52
|
referenceEl.style.opacity = 0;
|
|
@@ -67,7 +63,7 @@ const externalTooltipHandler = (multiple, dataSeries) => {
|
|
|
67
63
|
|
|
68
64
|
if (!tooltipData) return;
|
|
69
65
|
|
|
70
|
-
const
|
|
66
|
+
const jsonView = tooltipData.tooltip;
|
|
71
67
|
|
|
72
68
|
// Tooltip Element
|
|
73
69
|
const { chart, tooltip } = context;
|
|
@@ -80,7 +76,7 @@ const externalTooltipHandler = (multiple, dataSeries) => {
|
|
|
80
76
|
}
|
|
81
77
|
|
|
82
78
|
if (tooltip.body) {
|
|
83
|
-
|
|
79
|
+
vueApp.tooltipSpec = jsonView;
|
|
84
80
|
}
|
|
85
81
|
|
|
86
82
|
const position = chart.canvas.getBoundingClientRect();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { reactive,
|
|
2
|
-
import { glibevent, ctx
|
|
1
|
+
import { reactive, watchEffect } from "vue";
|
|
2
|
+
import { glibevent, ctx } from "../../store";
|
|
3
3
|
import { fieldModels } from "./conditional";
|
|
4
4
|
|
|
5
5
|
export const dirtySpecs = reactive({});
|
|
@@ -60,16 +60,41 @@ function useDropableUtils() {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
function useFilesState(files) {
|
|
63
|
-
const uploading = computed(() =>
|
|
64
|
-
|
|
63
|
+
const uploading = computed(() => {
|
|
64
|
+
return Object.values(files.value).length > 0 ? Object.values(files.value).reduce((prev, curr) => prev || curr.status == 'pending', false) : true;
|
|
65
|
+
});
|
|
66
|
+
const uploaded = computed(() => {
|
|
67
|
+
return Object.values(files.value).length > 0 ? Object.values(files.value).reduce((prev, curr) => prev && !!curr.signedId, true) : false;
|
|
68
|
+
});
|
|
69
|
+
const uploadingFileLength = computed(() => Object.values(files.value).reduce((prev, curr) => {
|
|
70
|
+
if (curr.status == 'pending') prev = prev + 1;
|
|
71
|
+
return prev;
|
|
72
|
+
}, 0));
|
|
73
|
+
const uploadedFileLength = computed(() => Object.values(files.value).reduce((prev, curr) => {
|
|
74
|
+
if (!!curr.signedId && curr.status == 'completed') prev = prev + 1;
|
|
75
|
+
return prev;
|
|
76
|
+
}, 0));
|
|
77
|
+
|
|
78
|
+
return { uploading, uploaded, uploadingFileLength, uploadedFileLength };
|
|
79
|
+
}
|
|
65
80
|
|
|
66
|
-
|
|
81
|
+
function submitOnAllUploaded({ url, formData, files }) {
|
|
82
|
+
const { uploaded } = useFilesState(files);
|
|
83
|
+
return watch(uploaded, (val) => {
|
|
84
|
+
if (val) {
|
|
85
|
+
GLib.action.execute({
|
|
86
|
+
action: 'http/post',
|
|
87
|
+
url: url.value,
|
|
88
|
+
formData: formData.value
|
|
89
|
+
}, {});
|
|
90
|
+
}
|
|
91
|
+
});
|
|
67
92
|
}
|
|
68
93
|
|
|
69
94
|
function setBusyWhenUploading({ files }) {
|
|
70
95
|
const { uploading } = useFilesState(files);
|
|
71
96
|
|
|
72
|
-
watch(uploading, (val
|
|
97
|
+
watch(uploading, (val) => {
|
|
73
98
|
if (val) {
|
|
74
99
|
vueApp.indicator = true;
|
|
75
100
|
} else {
|
|
@@ -90,15 +115,10 @@ const showError = (options) => {
|
|
|
90
115
|
{}
|
|
91
116
|
);
|
|
92
117
|
};
|
|
93
|
-
function uploadFiles({ droppedFiles, files,
|
|
118
|
+
function uploadFiles({ droppedFiles, files, spec, container }) {
|
|
119
|
+
let { responseMessages, accepts } = spec;
|
|
120
|
+
responseMessages ||= {};
|
|
94
121
|
const { maxFileLength } = accepts;
|
|
95
|
-
// const droppedFilesSizeInByte = Array.from(droppedFiles).reduce((prev, curr) => prev + curr.size, 0);
|
|
96
|
-
|
|
97
|
-
// if (droppedFilesSizeInByte > maxFileSize * 1000) {
|
|
98
|
-
// showError(accepts.maxFileSizeErrorText);
|
|
99
|
-
|
|
100
|
-
// return;
|
|
101
|
-
// }
|
|
102
122
|
|
|
103
123
|
if (droppedFiles.length > maxFileLength) {
|
|
104
124
|
showError(accepts.maxFileLengthErrorText);
|
|
@@ -112,51 +132,60 @@ function uploadFiles({ droppedFiles, files, directUploadUrl, accepts = {}, respo
|
|
|
112
132
|
// show new dropped file and track progress
|
|
113
133
|
const key = makeKey();
|
|
114
134
|
files.value[key] = new Item({ el: droppedFiles[index], status: 'pending' });
|
|
135
|
+
uploadOneFile({ files, key, spec, container });
|
|
136
|
+
}
|
|
137
|
+
}
|
|
115
138
|
|
|
116
|
-
|
|
139
|
+
function uploadOneFile({ files, key, spec, container, onAfterUploaded }) {
|
|
140
|
+
const { directUploadUrl, accepts } = spec;
|
|
141
|
+
let responseMessages = spec.responseMessages || {};
|
|
142
|
+
const uploader = new Uploader(files.value[key].el, directUploadUrl, files.value[key].progress);
|
|
117
143
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
144
|
+
// track progress
|
|
145
|
+
uploader.directUploadWillStoreFileWithXHR = (request) => {
|
|
146
|
+
request.upload.addEventListener("progress", event => {
|
|
147
|
+
files.value[key].progress.value = (event.loaded / event.total) * 100;
|
|
148
|
+
});
|
|
123
149
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
150
|
+
// can be use for aborting request
|
|
151
|
+
files.value[key].xhr = request;
|
|
152
|
+
};
|
|
127
153
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
154
|
+
// validate per file, skip if invalid
|
|
155
|
+
if (!uploader.validateFile({ accepts: accepts })) {
|
|
156
|
+
delete files.value[key];
|
|
157
|
+
}
|
|
133
158
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
triggerOnChange(container.value);
|
|
145
|
-
}
|
|
146
|
-
} else {
|
|
147
|
-
const message = responseMessages[error.slice(-3) || 'else'];
|
|
148
|
-
Object.assign(files.value[key], {
|
|
149
|
-
status: 'failed',
|
|
150
|
-
message: message
|
|
151
|
-
});
|
|
159
|
+
// upload per file
|
|
160
|
+
uploader.start((error, blob) => {
|
|
161
|
+
if (!error) {
|
|
162
|
+
Object.assign(files.value[key], {
|
|
163
|
+
status: 'completed',
|
|
164
|
+
signedId: blob.signed_id,
|
|
165
|
+
message: responseMessages['200']
|
|
166
|
+
});
|
|
167
|
+
if (onAfterUploaded) {
|
|
168
|
+
onAfterUploaded(files.value[key]);
|
|
152
169
|
}
|
|
153
|
-
|
|
154
|
-
|
|
170
|
+
|
|
171
|
+
// trigger form.vue onchange
|
|
172
|
+
if (container && container.value) {
|
|
173
|
+
triggerOnChange(container.value);
|
|
174
|
+
}
|
|
175
|
+
} else {
|
|
176
|
+
const message = responseMessages[error.slice(-3) || 'else'];
|
|
177
|
+
Object.assign(files.value[key], {
|
|
178
|
+
status: 'failed',
|
|
179
|
+
message: message
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
});
|
|
155
183
|
}
|
|
156
184
|
|
|
157
|
-
function useDropUpload({
|
|
185
|
+
function useDropUpload({ container, fileSelect, files, spec, uploader }) {
|
|
186
|
+
const { onDragStyle } = spec;
|
|
158
187
|
onMounted(() => {
|
|
159
|
-
setBusyWhenUploading({ files });
|
|
188
|
+
uploader.setBusyWhenUploading({ files });
|
|
160
189
|
|
|
161
190
|
// handle style changes
|
|
162
191
|
let dragEl = null;
|
|
@@ -174,26 +203,22 @@ function useDropUpload({ accepts, onFinishUpload, onDragStyle, container, direct
|
|
|
174
203
|
container.value.ondragover = (event) => event.preventDefault();
|
|
175
204
|
container.value.ondrop = (event) => {
|
|
176
205
|
event.preventDefault();
|
|
177
|
-
uploadFiles({
|
|
206
|
+
uploader.uploadFiles({
|
|
178
207
|
droppedFiles: event.dataTransfer.files,
|
|
179
|
-
accepts,
|
|
180
208
|
container,
|
|
181
|
-
directUploadUrl,
|
|
182
209
|
files,
|
|
183
|
-
|
|
210
|
+
spec
|
|
184
211
|
});
|
|
185
212
|
container.value.classList.remove(...onDragStyle);
|
|
186
213
|
};
|
|
187
214
|
|
|
188
215
|
container.value.onclick = (event) => {
|
|
189
216
|
const onchange = () => {
|
|
190
|
-
uploadFiles(
|
|
217
|
+
uploader.uploadFiles(
|
|
191
218
|
{
|
|
192
219
|
droppedFiles: event.target.files,
|
|
193
|
-
files
|
|
194
|
-
|
|
195
|
-
accepts,
|
|
196
|
-
responseMessages: {},
|
|
220
|
+
files,
|
|
221
|
+
spec,
|
|
197
222
|
container
|
|
198
223
|
}
|
|
199
224
|
);
|
|
@@ -202,16 +227,8 @@ function useDropUpload({ accepts, onFinishUpload, onDragStyle, container, direct
|
|
|
202
227
|
fileSelect.value.onchange = onchange;
|
|
203
228
|
fileSelect.value.click();
|
|
204
229
|
};
|
|
205
|
-
|
|
206
|
-
// handle when all file uploaded, all files have signedIds
|
|
207
|
-
const { uploaded } = useFilesState(files);
|
|
208
|
-
watch(uploaded, (val, oldVal) => {
|
|
209
|
-
if (val) {
|
|
210
|
-
nextTick(() => onFinishUpload(Object.values(files.value).map((file) => file.signedId)));
|
|
211
|
-
}
|
|
212
|
-
});
|
|
213
230
|
});
|
|
214
231
|
|
|
215
|
-
}
|
|
232
|
+
}
|
|
216
233
|
|
|
217
|
-
export { useDropUpload, useDropableUtils, setBusyWhenUploading, uploadFiles, useFilesState };
|
|
234
|
+
export { useDropUpload, useDropableUtils, setBusyWhenUploading, submitOnAllUploaded, uploadFiles, uploadOneFile, useFilesState };
|
|
@@ -32,7 +32,7 @@ export function useAutocomplete({ inputRef, options, onPlaceChanged }) {
|
|
|
32
32
|
return { autocompleteInstance };
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
export function useGmap({ mapRef, infoWindowRef, options, locations, showCluster = false
|
|
35
|
+
export function useGmap({ mapRef, infoWindowRef, options, locations, showCluster = false }) {
|
|
36
36
|
let mapInstance = null;
|
|
37
37
|
const markers = ref([]);
|
|
38
38
|
const cluster = ref({});
|
|
@@ -1,32 +1,26 @@
|
|
|
1
|
-
import { onMounted, ref, watch } from "vue";
|
|
2
|
-
import
|
|
1
|
+
import { computed, onMounted, ref, watch } from "vue";
|
|
2
|
+
import * as dropUploader from "./dropable";
|
|
3
|
+
import * as delegateUploader from "./upload_delegator";
|
|
3
4
|
|
|
4
|
-
const { signedIds } = useDropableUtils();
|
|
5
|
+
const { signedIds } = dropUploader.useDropableUtils();
|
|
5
6
|
|
|
6
7
|
const onPaste = async (event, filePaster) => {
|
|
7
|
-
const {
|
|
8
|
+
const { url, inputName, strategy } = filePaster;
|
|
8
9
|
const pastedFiles = event.clipboardData.files;
|
|
9
10
|
if (pastedFiles.length <= 0) return;
|
|
10
11
|
|
|
11
12
|
const files = ref({});
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}, {});
|
|
21
|
-
files.value = {};
|
|
22
|
-
}
|
|
23
|
-
});
|
|
24
|
-
uploadFiles({
|
|
13
|
+
files.value = {};
|
|
14
|
+
|
|
15
|
+
const uploader = strategy == 'delegate' ? delegateUploader : dropUploader;
|
|
16
|
+
|
|
17
|
+
uploader.setBusyWhenUploading({ files });
|
|
18
|
+
const formData = computed(() => inputName ? { [inputName]: { signed_ids: signedIds(files) } } : { signed_ids: signedIds(files) });
|
|
19
|
+
uploader.submitOnAllUploaded({ files, formData, url: { value: url } });
|
|
20
|
+
uploader.uploadFiles({
|
|
25
21
|
droppedFiles: pastedFiles,
|
|
26
22
|
files,
|
|
27
|
-
|
|
28
|
-
directUploadUrl,
|
|
29
|
-
responseMessages: {}
|
|
23
|
+
spec: filePaster
|
|
30
24
|
});
|
|
31
25
|
|
|
32
26
|
};
|
|
@@ -36,7 +30,7 @@ let handler = () => { };
|
|
|
36
30
|
function usePasteable(filePaster) {
|
|
37
31
|
handler = (event) => onPaste(event, filePaster.value);
|
|
38
32
|
onMounted(() => {
|
|
39
|
-
watch(filePaster, (val
|
|
33
|
+
watch(filePaster, (val) => {
|
|
40
34
|
if (val) {
|
|
41
35
|
document.addEventListener('paste', handler);
|
|
42
36
|
} else {
|
|
@@ -29,9 +29,10 @@ class SocketHandler {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
get component() {
|
|
32
|
-
if (this.targetId) {
|
|
33
|
-
return
|
|
32
|
+
if (!this.targetId) {
|
|
33
|
+
return null;
|
|
34
34
|
}
|
|
35
|
+
return GLib.component.findById(this.targetId);
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
38
|
class ExampleSocketHandler extends SocketHandler {
|
|
@@ -109,7 +110,7 @@ const useSocket = (config, defaultConsumer = null) => {
|
|
|
109
110
|
rejected() {
|
|
110
111
|
handler.rejected();
|
|
111
112
|
},
|
|
112
|
-
disconnected({ willAttemptReconnect
|
|
113
|
+
disconnected({ willAttemptReconnect }) {
|
|
113
114
|
handler.disconnected();
|
|
114
115
|
}
|
|
115
116
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { vueApp } from "../../store";
|
|
2
|
+
import { useDropableUtils } from "./dropable";
|
|
3
|
+
|
|
4
|
+
const { makeKey } = useDropableUtils();
|
|
5
|
+
|
|
6
|
+
function uploadFiles(obj) {
|
|
7
|
+
const { droppedFiles, spec } = obj;
|
|
8
|
+
Object.assign(
|
|
9
|
+
vueApp.uploader,
|
|
10
|
+
{
|
|
11
|
+
files: Array.from(droppedFiles).reduce((prev, curr) => {
|
|
12
|
+
prev[makeKey()] = curr;
|
|
13
|
+
|
|
14
|
+
return prev;
|
|
15
|
+
}, {}),
|
|
16
|
+
spec: spec
|
|
17
|
+
}
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function submitOnAllUploaded(obj) { }
|
|
22
|
+
function setBusyWhenUploading(obj) { }
|
|
23
|
+
|
|
24
|
+
export { uploadFiles, submitOnAllUploaded, setBusyWhenUploading };
|
|
@@ -196,21 +196,23 @@
|
|
|
196
196
|
<script>
|
|
197
197
|
import { ref, computed, defineComponent, watch, getCurrentInstance } from 'vue';
|
|
198
198
|
import { VIcon } from 'vuetify/components';
|
|
199
|
-
import
|
|
199
|
+
import * as dropUploader from "../composable/dropable";
|
|
200
|
+
import * as delegateUploader from "../composable/upload_delegator";
|
|
200
201
|
import { triggerOnChange } from "../composable/form";
|
|
201
202
|
import { nextTick } from "vue";
|
|
202
|
-
const { makeKey, Item } = useDropableUtils();
|
|
203
|
+
const { makeKey, Item, signedIds } = dropUploader.useDropableUtils();
|
|
203
204
|
|
|
204
205
|
export default defineComponent({
|
|
205
206
|
props: { spec: { type: Object } },
|
|
206
207
|
components: { VIcon },
|
|
207
208
|
setup(props) {
|
|
209
|
+
const uploader = props.spec.strategy == 'delegate' ? delegateUploader : dropUploader;
|
|
210
|
+
|
|
208
211
|
const fileSelect = ref(null);
|
|
209
212
|
const container = ref(null);
|
|
210
213
|
const icon = ref(null);
|
|
211
214
|
const { ctx } = getCurrentInstance();
|
|
212
215
|
|
|
213
|
-
const responseMessages = props.spec.responseMessages;
|
|
214
216
|
const uploadTitle = props.spec.uploadTitle || 'File added';
|
|
215
217
|
|
|
216
218
|
const files = ref({});
|
|
@@ -232,15 +234,20 @@ export default defineComponent({
|
|
|
232
234
|
}
|
|
233
235
|
|
|
234
236
|
if (props.spec.onFinishUpload) {
|
|
235
|
-
const { uploaded } = useFilesState(files);
|
|
236
|
-
watch(uploaded, (val
|
|
237
|
+
const { uploaded } = dropUploader.useFilesState(files);
|
|
238
|
+
watch(uploaded, (val) => {
|
|
237
239
|
if (val) {
|
|
238
240
|
nextTick(() => GLib.action.execute(props.spec.onFinishUpload, ctx));
|
|
239
241
|
}
|
|
240
242
|
});
|
|
241
243
|
}
|
|
242
244
|
|
|
243
|
-
setBusyWhenUploading({ files });
|
|
245
|
+
uploader.setBusyWhenUploading({ files });
|
|
246
|
+
if (props.spec.url) {
|
|
247
|
+
const formData = computed(() => ({ signed_ids: signedIds(files) }));
|
|
248
|
+
const url = computed(() => props.spec.url);
|
|
249
|
+
uploader.submitOnAllUploaded({ files, url, formData });
|
|
250
|
+
}
|
|
244
251
|
|
|
245
252
|
const showUploadedFile = computed(() => {
|
|
246
253
|
return Object.keys(files.value).length > 0;
|
|
@@ -248,13 +255,11 @@ export default defineComponent({
|
|
|
248
255
|
|
|
249
256
|
function handleDrop(e) {
|
|
250
257
|
e.preventDefault();
|
|
251
|
-
uploadFiles(
|
|
258
|
+
uploader.uploadFiles(
|
|
252
259
|
{
|
|
253
260
|
droppedFiles: e.dataTransfer.files,
|
|
254
261
|
files: files,
|
|
255
|
-
|
|
256
|
-
accepts: props.spec.accepts,
|
|
257
|
-
responseMessages: responseMessages,
|
|
262
|
+
spec: props.spec,
|
|
258
263
|
container: container
|
|
259
264
|
}
|
|
260
265
|
);
|
|
@@ -296,13 +301,11 @@ export default defineComponent({
|
|
|
296
301
|
|
|
297
302
|
function handleClick(e) {
|
|
298
303
|
const onchange = () => {
|
|
299
|
-
uploadFiles(
|
|
304
|
+
uploader.uploadFiles(
|
|
300
305
|
{
|
|
301
306
|
droppedFiles: e.target.files,
|
|
302
307
|
files: files,
|
|
303
|
-
|
|
304
|
-
accepts: props.spec.accepts,
|
|
305
|
-
responseMessages: responseMessages,
|
|
308
|
+
spec: props.spec,
|
|
306
309
|
container: container
|
|
307
310
|
}
|
|
308
311
|
);
|
|
@@ -323,7 +326,6 @@ export default defineComponent({
|
|
|
323
326
|
handleClick,
|
|
324
327
|
handleRemoveFile,
|
|
325
328
|
showUploadedFile,
|
|
326
|
-
responseMessages,
|
|
327
329
|
uploadTitle,
|
|
328
330
|
props
|
|
329
331
|
};
|
|
@@ -166,21 +166,6 @@ export default {
|
|
|
166
166
|
$update() {
|
|
167
167
|
this.$ready();
|
|
168
168
|
},
|
|
169
|
-
async $recursiveUpdate(stack) {
|
|
170
|
-
// const children = this.$children;
|
|
171
|
-
if (this.spec) {
|
|
172
|
-
// this.$update();
|
|
173
|
-
// this.$forceUpdate();
|
|
174
|
-
|
|
175
|
-
// Execute on next tick to ensure that the child has received the updated spec.
|
|
176
|
-
// $children is removed in vue 3
|
|
177
|
-
// this.$nextTick(() => {
|
|
178
|
-
// this.$children.find(child => {
|
|
179
|
-
// child.$recursiveUpdate();
|
|
180
|
-
// });
|
|
181
|
-
// });
|
|
182
|
-
}
|
|
183
|
-
},
|
|
184
169
|
$dispatchEvent(name, data) {
|
|
185
170
|
const event = new Event(name, { bubbles: true });
|
|
186
171
|
|
|
@@ -2,6 +2,7 @@ import Hash from "../../utils/hash";
|
|
|
2
2
|
import { fieldModels, watchFieldModels } from "../composable/conditional";
|
|
3
3
|
import { dirtySpecs } from "../composable/dirtyState";
|
|
4
4
|
import { determineColor } from "../../utils/constant";
|
|
5
|
+
import Action from "../../action";
|
|
5
6
|
|
|
6
7
|
const NUMBER_PRECISION = 2;
|
|
7
8
|
const isNeedToBeFixed = (val, component) => {
|
|
@@ -54,7 +55,7 @@ export default {
|
|
|
54
55
|
}
|
|
55
56
|
},
|
|
56
57
|
watch: {
|
|
57
|
-
fieldModel: function (val
|
|
58
|
+
fieldModel: function (val) {
|
|
58
59
|
if (val === this.vuetifyEmptyString) {
|
|
59
60
|
val = "";
|
|
60
61
|
}
|
|
@@ -140,6 +141,7 @@ export default {
|
|
|
140
141
|
if (oldValue != value) {
|
|
141
142
|
this.$nextTick(() => {
|
|
142
143
|
// Call either onIfTrue or onIfFalse.
|
|
144
|
+
if (value == null) return;
|
|
143
145
|
Action.execute(this.spec[`onIf${value.toString().capitalize()}`], this);
|
|
144
146
|
});
|
|
145
147
|
}
|
package/components/tabBar.vue
CHANGED
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
<div :style="'width: 100%'" :class="$classes()">
|
|
3
3
|
<v-tabs v-model="tab" :bg-color="spec.backgroundColor || 'white'" slider-color="primary" color="primary" show-arrows
|
|
4
4
|
:height="spec.height || 60" :grow="$classes().includes('no-grow') ? false : true">
|
|
5
|
-
<v-tab v-for="(item, index) in
|
|
5
|
+
<v-tab v-for="(item, index) in spec.buttons" @click="handleClick(index)" :key="index" height="100%"
|
|
6
|
+
:value="index">
|
|
6
7
|
<common-badge :spec="item">
|
|
7
8
|
<div class="tab-content">
|
|
8
|
-
<common-icon :class="index == spec.activeIndex ? 'text-primary' : null" :spec="item.icon"
|
|
9
|
-
|
|
9
|
+
<common-icon :class="index == spec.activeIndex ? 'text-primary' : null" :spec="item.icon"
|
|
10
|
+
v-if="item.icon" />
|
|
11
|
+
<span :style="{ marginLeft: '2px' }" :class="index == spec.activeIndex ? 'text-primary' : null">{{ item.text
|
|
10
12
|
}}</span>
|
|
11
13
|
</div>
|
|
12
14
|
</common-badge>
|
|
@@ -46,14 +48,14 @@ export default {
|
|
|
46
48
|
methods: {
|
|
47
49
|
handleClick(index) {
|
|
48
50
|
this.$nextTick(function () {
|
|
49
|
-
const activeTab = this.buttons[index]
|
|
50
|
-
const onClick = activeTab?.onClick
|
|
51
|
+
const activeTab = this.buttons[index];
|
|
52
|
+
const onClick = activeTab?.onClick;
|
|
51
53
|
|
|
52
54
|
if (onClick) {
|
|
53
|
-
Action.execute(onClick, this)
|
|
55
|
+
Action.execute(onClick, this);
|
|
54
56
|
}
|
|
55
|
-
this.tab = index
|
|
56
|
-
})
|
|
57
|
+
this.tab = index;
|
|
58
|
+
});
|
|
57
59
|
},
|
|
58
60
|
$ready() {
|
|
59
61
|
if (this.spec.activeIndex) {
|
|
@@ -84,6 +86,7 @@ export default {
|
|
|
84
86
|
<!-- Overridable -->
|
|
85
87
|
<style lang="scss" scoped>
|
|
86
88
|
.tab-content {
|
|
89
|
+
display: flex;
|
|
87
90
|
padding: 5px;
|
|
88
91
|
}
|
|
89
92
|
</style>
|
package/components/treeView.vue
CHANGED
|
@@ -91,18 +91,20 @@
|
|
|
91
91
|
<script>
|
|
92
92
|
import { computed, getCurrentInstance } from "vue";
|
|
93
93
|
import { defineComponent, ref } from "vue";
|
|
94
|
-
import
|
|
94
|
+
import * as dropUploader from "./composable/dropable";
|
|
95
|
+
import * as delegateUploader from "./composable/upload_delegator";
|
|
95
96
|
import { watch } from "vue";
|
|
96
97
|
import merge from 'lodash.merge';
|
|
97
|
-
const { signedIds } = useDropableUtils();
|
|
98
|
+
const { signedIds } = dropUploader.useDropableUtils();
|
|
98
99
|
|
|
99
100
|
export default defineComponent({
|
|
100
101
|
props: { spec: { type: Object, required: true } },
|
|
101
102
|
setup(props) {
|
|
103
|
+
|
|
102
104
|
const selected = computed(() => props.spec.selected);
|
|
103
105
|
const { ctx } = getCurrentInstance();
|
|
104
106
|
const files = ref({});
|
|
105
|
-
|
|
107
|
+
|
|
106
108
|
let dropData = null;
|
|
107
109
|
|
|
108
110
|
const makeFormData = (dragData) => {
|
|
@@ -116,20 +118,15 @@ export default defineComponent({
|
|
|
116
118
|
return merge(dropData, dragData);
|
|
117
119
|
};
|
|
118
120
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
+
const uploader = props.spec.strategy == 'delegate' ? delegateUploader : dropUploader;
|
|
122
|
+
uploader.setBusyWhenUploading({ files });
|
|
121
123
|
// if all file uploaded send http post
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}, ctx);
|
|
129
|
-
dropData = null;
|
|
130
|
-
files.value = {};
|
|
131
|
-
}
|
|
132
|
-
});
|
|
124
|
+
if (props.spec.url) {
|
|
125
|
+
const formData = computed(() => makeFormData({ signed_ids: signedIds(files) }));
|
|
126
|
+
const url = computed(() => props.spec.url);
|
|
127
|
+
uploader.submitOnAllUploaded({ url, formData, files });
|
|
128
|
+
}
|
|
129
|
+
|
|
133
130
|
|
|
134
131
|
const makeItems = () => (props.spec.items.map((item) => {
|
|
135
132
|
if (selected.value == item.id) {
|
|
@@ -161,7 +158,7 @@ export default defineComponent({
|
|
|
161
158
|
}
|
|
162
159
|
});
|
|
163
160
|
|
|
164
|
-
const handleDragLeave = (event
|
|
161
|
+
const handleDragLeave = (event) => {
|
|
165
162
|
if (!event.target || !event.relatedTarget) return false;
|
|
166
163
|
if (event.target.contains(event.relatedTarget) || event.relatedTarget.contains(event.target)) return false;
|
|
167
164
|
lastDragItem.value = null;
|
|
@@ -178,12 +175,11 @@ export default defineComponent({
|
|
|
178
175
|
// 1. files
|
|
179
176
|
// 2. other components
|
|
180
177
|
if (event.dataTransfer.files.length > 0) {
|
|
181
|
-
|
|
178
|
+
files.value = {};
|
|
179
|
+
uploader.uploadFiles({
|
|
182
180
|
droppedFiles: event.dataTransfer.files,
|
|
183
|
-
|
|
184
|
-
directUploadUrl: props.spec.directUploadUrl,
|
|
181
|
+
spec: props.spec,
|
|
185
182
|
files: files,
|
|
186
|
-
responseMessages: {}
|
|
187
183
|
});
|
|
188
184
|
} else {
|
|
189
185
|
const dragData = JSON.parse(event.dataTransfer.getData('text'));
|
package/constant.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const TOOLTIP_ID = '_glib-tooltip';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "glib-web",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.26.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@types/chart.js": "^2.9.34",
|
|
40
40
|
"eslint": "^8.36.0",
|
|
41
|
-
"eslint-plugin-vue": "^9.
|
|
41
|
+
"eslint-plugin-vue": "^9.26.0",
|
|
42
42
|
"prettier": "^1.18.2",
|
|
43
43
|
"typescript": "^4.9.5"
|
|
44
44
|
}
|
package/store.js
CHANGED
|
@@ -15,7 +15,10 @@ export const vueApp = reactive({
|
|
|
15
15
|
temp: {},
|
|
16
16
|
richTextValues: {},
|
|
17
17
|
draggedComponent: null,
|
|
18
|
-
lastNavigationCount: null
|
|
18
|
+
lastNavigationCount: null,
|
|
19
|
+
tooltipSpec: {},
|
|
20
|
+
bottomBanners: {},
|
|
21
|
+
uploader: {}
|
|
19
22
|
});
|
|
20
23
|
|
|
21
24
|
export const dialogs = reactive([]);
|
package/utils/history.js
CHANGED
|
@@ -69,7 +69,6 @@ export default class {
|
|
|
69
69
|
|
|
70
70
|
const data = event.state;
|
|
71
71
|
data.content.__poppedState = true;
|
|
72
|
-
//
|
|
73
72
|
|
|
74
73
|
vueApp.page = data.content;
|
|
75
74
|
vueApp.lastNavigationCount = data.navigationCount;
|
|
@@ -100,4 +99,14 @@ export default class {
|
|
|
100
99
|
return false;
|
|
101
100
|
}
|
|
102
101
|
}
|
|
102
|
+
|
|
103
|
+
static destroy() {
|
|
104
|
+
if (this.navigationCount <= 0) return false;
|
|
105
|
+
|
|
106
|
+
window.onpopstate = () => { };
|
|
107
|
+
window.history.go(-this.navigationCount);
|
|
108
|
+
this.restoreOnBackOrForward();
|
|
109
|
+
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
103
112
|
}
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
export default {
|
|
2
|
-
data() {
|
|
3
|
-
return {
|
|
4
|
-
options: {
|
|
5
|
-
annotation: {
|
|
6
|
-
drawTime: "afterDraw",
|
|
7
|
-
events: ["mouseleave", "mouseenter"],
|
|
8
|
-
annotations: []
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
};
|
|
12
|
-
},
|
|
13
|
-
created() {
|
|
14
|
-
if (this.spec.lineVertical) {
|
|
15
|
-
this.options.annotation.annotations = this.spec.lineVertical.map(
|
|
16
|
-
(element, index) => this.annotation(element, index)
|
|
17
|
-
);
|
|
18
|
-
}
|
|
19
|
-
},
|
|
20
|
-
methods: {
|
|
21
|
-
annotation(options, index) {
|
|
22
|
-
const { text, x } = options;
|
|
23
|
-
|
|
24
|
-
return {
|
|
25
|
-
type: "line",
|
|
26
|
-
mode: "vertical",
|
|
27
|
-
scaleID: "x-axis-0",
|
|
28
|
-
value: new Date(x).getTime(),
|
|
29
|
-
borderColor: "rgba(0,0,255,0.3)",
|
|
30
|
-
borderWidth: 3,
|
|
31
|
-
borderDash: [5, 10],
|
|
32
|
-
label: {
|
|
33
|
-
content: text,
|
|
34
|
-
enabled: true,
|
|
35
|
-
position: "top",
|
|
36
|
-
backgroundColor: "rgba(0,0,0,0.3)"
|
|
37
|
-
},
|
|
38
|
-
onMouseenter: function(e) {
|
|
39
|
-
Object.assign(
|
|
40
|
-
this.chartInstance.options.annotation.annotations[index],
|
|
41
|
-
{ borderColor: "rgba(0,0,255,0.8)" },
|
|
42
|
-
{
|
|
43
|
-
label: {
|
|
44
|
-
content: text,
|
|
45
|
-
enabled: true,
|
|
46
|
-
position: "top",
|
|
47
|
-
backgroundColor: "rgba(0,0,0,0.8)"
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
);
|
|
51
|
-
|
|
52
|
-
this.chartInstance.update();
|
|
53
|
-
},
|
|
54
|
-
onMouseleave: function(e) {
|
|
55
|
-
Object.assign(
|
|
56
|
-
this.chartInstance.options.annotation.annotations[index],
|
|
57
|
-
{ borderColor: "rgba(0,0,255,0.3)" },
|
|
58
|
-
{
|
|
59
|
-
label: {
|
|
60
|
-
content: text,
|
|
61
|
-
enabled: true,
|
|
62
|
-
position: "top",
|
|
63
|
-
backgroundColor: "rgba(0,0,0,0.3)"
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
);
|
|
67
|
-
|
|
68
|
-
this.chartInstance.update();
|
|
69
|
-
}
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
};
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
export default {
|
|
2
|
-
data() {
|
|
3
|
-
return {
|
|
4
|
-
options: {
|
|
5
|
-
dataName: "",
|
|
6
|
-
tooltips: {
|
|
7
|
-
intersect: false,
|
|
8
|
-
callbacks: {
|
|
9
|
-
afterLabel: this.afterLabel
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
};
|
|
14
|
-
},
|
|
15
|
-
methods: {
|
|
16
|
-
afterLabel(tooltipItem) {
|
|
17
|
-
if (!this.dataName) {
|
|
18
|
-
console.error("dataName is empty");
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const selectedSeries = this.spec[this.dataName][tooltipItem.datasetIndex];
|
|
22
|
-
const point = selectedSeries.points.find(
|
|
23
|
-
point => point.x == tooltipItem.xLabel
|
|
24
|
-
);
|
|
25
|
-
const tooltip = point ? point.tooltip : null;
|
|
26
|
-
if (tooltip) {
|
|
27
|
-
return tooltip;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
};
|