glib-web 4.24.1 → 4.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/action.js +8 -5
- package/actions/sheets/close.js +10 -0
- package/actions/sheets/open.js +35 -0
- package/actions/sheets/show.js +14 -0
- package/app.vue +55 -15
- package/components/_chip.vue +0 -5
- package/components/_icon.vue +1 -6
- package/components/_internal_button.vue +0 -5
- package/components/composable/form.js +2 -2
- package/components/fields/_buttonDate.vue +1 -0
- package/components/fields/_patternText.vue +1 -0
- package/components/fields/_select.vue +4 -8
- package/components/fields/dynamicGroup2.vue +16 -1
- package/components/fields/dynamicSelect.vue +17 -24
- package/components/fields/multiUpload.vue +1 -2
- package/components/fields/sign.vue +2 -2
- package/components/mixins/events.js +10 -1
- package/components/mixins/tooltip.js +7 -1
- package/components/panels/form.vue +44 -23
- package/components/popover.vue +2 -2
- package/cypress/e2e/glib-web/lifecycle.cy.ts +28 -0
- package/cypress/e2e/glib-web/multiupload.cy.ts +2 -0
- package/cypress/e2e/glib-web/selectable.cy.ts +1 -1
- package/package.json +1 -1
- package/store.js +8 -3
- package/utils/history.js +5 -1
- package/utils/http.js +2 -1
package/action.js
CHANGED
|
@@ -22,6 +22,9 @@ import ActionsSnackbarsAlert from "./actions/snackbars/alert";
|
|
|
22
22
|
import ActionsSnackbarsSelect from "./actions/snackbars/select";
|
|
23
23
|
|
|
24
24
|
import ActionsSheetsSelect from "./actions/sheets/select";
|
|
25
|
+
import ActionsSheetsOpen from "./actions/sheets/open";
|
|
26
|
+
import ActionsSheetsShow from "./actions/sheets/show";
|
|
27
|
+
import ActionsSheetsClose from "./actions/sheets/close";
|
|
25
28
|
|
|
26
29
|
import ActionsWindowsClose from "./actions/windows/close";
|
|
27
30
|
import ActionsWindowsCloseAll from "./actions/windows/closeAll";
|
|
@@ -80,8 +83,6 @@ import ActionListsAppend from "./actions/lists/append";
|
|
|
80
83
|
|
|
81
84
|
import ActionBottomBannersOpen from "./actions/bottom_banners/open";
|
|
82
85
|
import ActionBottomBannersClose from "./actions/bottom_banners/close";
|
|
83
|
-
import ActionRightBannersOpen from "./actions/right_banners/open";
|
|
84
|
-
import ActionRightBannersClose from "./actions/right_banners/close";
|
|
85
86
|
|
|
86
87
|
import ActionGlobalStatesWatch from "./actions/global_states/watch";
|
|
87
88
|
import ActionGlobalStatesSet from "./actions/global_states/set";
|
|
@@ -124,6 +125,9 @@ const actions = {
|
|
|
124
125
|
"snackbars/select": ActionsSnackbarsSelect,
|
|
125
126
|
|
|
126
127
|
"sheets/select": ActionsSheetsSelect,
|
|
128
|
+
"sheets/open": ActionsSheetsOpen,
|
|
129
|
+
"sheets/show": ActionsSheetsShow,
|
|
130
|
+
"sheets/close": ActionsSheetsClose,
|
|
127
131
|
|
|
128
132
|
"windows/close": ActionsWindowsClose,
|
|
129
133
|
"windows/closeAll": ActionsWindowsCloseAll,
|
|
@@ -179,12 +183,10 @@ const actions = {
|
|
|
179
183
|
|
|
180
184
|
"lists/append": ActionListsAppend,
|
|
181
185
|
|
|
186
|
+
// deprecated
|
|
182
187
|
"bottomBanners/open": ActionBottomBannersOpen,
|
|
183
188
|
"bottomBanners/close": ActionBottomBannersClose,
|
|
184
189
|
|
|
185
|
-
"rightBanners/open": ActionRightBannersOpen,
|
|
186
|
-
"rightBanners/close": ActionRightBannersClose,
|
|
187
|
-
|
|
188
190
|
"globalStates/watch": ActionGlobalStatesWatch,
|
|
189
191
|
"globalStates/set": ActionGlobalStatesSet,
|
|
190
192
|
|
|
@@ -327,6 +329,7 @@ export default class Action {
|
|
|
327
329
|
dialog.updateContent(response);
|
|
328
330
|
} else {
|
|
329
331
|
jsonView.page = response;
|
|
332
|
+
jsonView.source = 'server';
|
|
330
333
|
}
|
|
331
334
|
});
|
|
332
335
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { vueApp } from "../../store";
|
|
2
|
+
import http from "../../utils/http";
|
|
3
|
+
|
|
4
|
+
export default class {
|
|
5
|
+
execute(spec, component) {
|
|
6
|
+
|
|
7
|
+
const { placement } = spec;
|
|
8
|
+
|
|
9
|
+
http.execute(
|
|
10
|
+
spec,
|
|
11
|
+
'GET',
|
|
12
|
+
component,
|
|
13
|
+
(response) => {
|
|
14
|
+
vueApp.sheet = {
|
|
15
|
+
spec: response.body,
|
|
16
|
+
show: true,
|
|
17
|
+
placement
|
|
18
|
+
};
|
|
19
|
+
},
|
|
20
|
+
() => {
|
|
21
|
+
vueApp.sheet = {
|
|
22
|
+
spec: {
|
|
23
|
+
childViews: [
|
|
24
|
+
{ view: 'p', text: 'Failed to load' },
|
|
25
|
+
{ view: 'spacer', height: 8 },
|
|
26
|
+
{ view: 'button', text: 'close', onClick: { action: 'sheets/close' } }
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
show: true,
|
|
30
|
+
placement
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
}
|
package/app.vue
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<v-app :class="page.styleClasses" ref="appRef">
|
|
3
3
|
<component :is="containerComponent" :spec="formSpec" :style="'height: 100%;'">
|
|
4
|
-
<nav-layout ref="navBar" @mounted="updateMainHeight()" :page="page" />
|
|
4
|
+
<nav-layout ref="navBar" @mounted="updateMainHeight()" :page="page" :key="`navBar-${page.key}`" />
|
|
5
5
|
|
|
6
6
|
<v-progress-linear color="primary" v-if="vueApp.indicator" :indeterminate="true" height="5"
|
|
7
7
|
style="position: fixed; z-index: 4">
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
<v-main :style="`height: ${mainHeight}px;`">
|
|
11
11
|
<v-container :fluid="page.template == 'fullWidth'" :class="containerClasses">
|
|
12
|
-
<div class="pages-header">
|
|
12
|
+
<div class="pages-header" :key="`page-header-${page.key}`">
|
|
13
13
|
<panels-responsive :spec="header" />
|
|
14
14
|
</div>
|
|
15
15
|
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
<panels-responsive :key="`footer-${page.key}`" class="body-footer" :spec="bodyFooter" />
|
|
20
20
|
</div>
|
|
21
21
|
|
|
22
|
-
<div class="pages-footer">
|
|
22
|
+
<div class="pages-footer" :key="`page-footer-${page.key}`">
|
|
23
23
|
<panels-responsive :spec="footer" />
|
|
24
24
|
</div>
|
|
25
25
|
</v-container>
|
|
@@ -30,9 +30,8 @@
|
|
|
30
30
|
<panels-responsive :spec="vueApp.tooltipSpec"></panels-responsive>
|
|
31
31
|
</div>
|
|
32
32
|
<Transition name="slide-fade">
|
|
33
|
-
<v-sheet v-if="vueApp.
|
|
34
|
-
|
|
35
|
-
<glib-component v-for="(rbSpec, index) in vueApp.rightBanners.spec.childViews" :spec="rbSpec"
|
|
33
|
+
<v-sheet v-if="vueApp.sheet.show" position="fixed" :class="`views-sheet ${vueApp.sheet.placement}`">
|
|
34
|
+
<glib-component v-for="(rbSpec, index) in vueApp.sheet.spec.childViews" :spec="rbSpec"
|
|
36
35
|
:key="index"></glib-component>
|
|
37
36
|
</v-sheet>
|
|
38
37
|
</Transition>
|
|
@@ -49,7 +48,7 @@ import Utils from "./utils/helper";
|
|
|
49
48
|
// import phoenixSocketMixin from "./components/mixins/ws/phoenixSocket.js";
|
|
50
49
|
// import actionCableMixin from "./components/mixins/ws/actionCable.js";
|
|
51
50
|
import FormPanel from "./components/panels/form.vue";
|
|
52
|
-
import { vueApp } from "./store";
|
|
51
|
+
import { isRerender, vueApp } from "./store";
|
|
53
52
|
import { useSocket } from "./components/composable/socket";
|
|
54
53
|
import { usePasteable } from "./components/composable/pasteable";
|
|
55
54
|
import { computed, onMounted, provide, ref } from "vue";
|
|
@@ -141,7 +140,11 @@ export default {
|
|
|
141
140
|
`Version: ${Utils.settings.appVersion} (${Utils.settings.env})`
|
|
142
141
|
);
|
|
143
142
|
Utils.history.saveInitialContent(this.page);
|
|
144
|
-
Utils.history.restoreOnBackOrForward(
|
|
143
|
+
Utils.history.restoreOnBackOrForward({
|
|
144
|
+
onAfterBack: () => {
|
|
145
|
+
if (isRerender) GLib.action.execute(this.page.onRerender, this);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
145
148
|
watchGlibEvent();
|
|
146
149
|
},
|
|
147
150
|
methods: {
|
|
@@ -167,6 +170,12 @@ export default {
|
|
|
167
170
|
},
|
|
168
171
|
true
|
|
169
172
|
);
|
|
173
|
+
|
|
174
|
+
if (isRerender()) {
|
|
175
|
+
GLib.action.execute(this.page.onRerender, this);
|
|
176
|
+
} else {
|
|
177
|
+
GLib.action.execute(this.page.onLoad, this);
|
|
178
|
+
}
|
|
170
179
|
},
|
|
171
180
|
$ready() {
|
|
172
181
|
this.$type.ifString(this.page.title, (title) => {
|
|
@@ -195,10 +204,6 @@ export default {
|
|
|
195
204
|
GLib.action.execute(this.page.onRefocus, this);
|
|
196
205
|
GLib.action.execute(this.page.replayGetResponse, this);
|
|
197
206
|
});
|
|
198
|
-
} else {
|
|
199
|
-
setTimeout(() => {
|
|
200
|
-
GLib.action.execute(this.page.onLoad, this);
|
|
201
|
-
});
|
|
202
207
|
}
|
|
203
208
|
|
|
204
209
|
// Use nextTick() to allow time for the navBar to complete initialization.
|
|
@@ -370,6 +375,14 @@ body,
|
|
|
370
375
|
}
|
|
371
376
|
}
|
|
372
377
|
|
|
378
|
+
.v-btn--disabled {
|
|
379
|
+
pointer-events: auto;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
.v-btn--disabled:active {
|
|
383
|
+
pointer-events: none;
|
|
384
|
+
}
|
|
385
|
+
|
|
373
386
|
/******/
|
|
374
387
|
</style>
|
|
375
388
|
|
|
@@ -396,11 +409,38 @@ body,
|
|
|
396
409
|
z-index: 99;
|
|
397
410
|
}
|
|
398
411
|
|
|
399
|
-
.
|
|
400
|
-
z-index:
|
|
401
|
-
right: 0;
|
|
412
|
+
.views-sheet {
|
|
413
|
+
z-index: 1007;
|
|
402
414
|
box-shadow: 0px 4px 12px 0px #0000001A;
|
|
403
415
|
overflow-y: auto;
|
|
416
|
+
|
|
417
|
+
&.top {
|
|
418
|
+
top: 0;
|
|
419
|
+
height: 100%;
|
|
420
|
+
max-height: 160px;
|
|
421
|
+
width: 100%;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
&.right {
|
|
425
|
+
right: 0;
|
|
426
|
+
height: 100%;
|
|
427
|
+
width: 100%;
|
|
428
|
+
max-width: 684px;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
&.bottom {
|
|
432
|
+
bottom: 0;
|
|
433
|
+
height: 100%;
|
|
434
|
+
max-height: 160px;
|
|
435
|
+
width: 100%;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
&.left {
|
|
439
|
+
left: 0;
|
|
440
|
+
height: 100%;
|
|
441
|
+
width: 100%;
|
|
442
|
+
max-width: 684px;
|
|
443
|
+
}
|
|
404
444
|
}
|
|
405
445
|
|
|
406
446
|
.slide-fade-enter-active {
|
package/components/_chip.vue
CHANGED
package/components/_icon.vue
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<v-icon class="aligner" :style="$styles()" :class="cssClasses" :size="icon.size" :color="color">{{
|
|
3
3
|
icon.name
|
|
4
|
-
|
|
4
|
+
}}</v-icon>
|
|
5
5
|
</template>
|
|
6
6
|
|
|
7
7
|
<script>
|
|
@@ -48,11 +48,6 @@ export default {
|
|
|
48
48
|
}
|
|
49
49
|
},
|
|
50
50
|
methods: {
|
|
51
|
-
$ready() {
|
|
52
|
-
nextTick(() => { // Make sure the component has been initialized and registered.
|
|
53
|
-
GLib.action.execute(this.spec.onLoad, this);
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
51
|
|
|
57
52
|
// $ready() {
|
|
58
53
|
// // this.badge = this.spec.badge || {};
|
|
@@ -42,11 +42,6 @@ export default {
|
|
|
42
42
|
}
|
|
43
43
|
},
|
|
44
44
|
methods: {
|
|
45
|
-
$ready() {
|
|
46
|
-
nextTick(() => { // Make sure the component has been initialized and registered.
|
|
47
|
-
GLib.action.execute(this.spec.onLoad, this);
|
|
48
|
-
});
|
|
49
|
-
},
|
|
50
45
|
// $ready() {
|
|
51
46
|
// this.$type.ifArray(this.spec.styleClasses, val => {
|
|
52
47
|
// this.linkStyling = val.includes("link");
|
|
@@ -89,7 +89,7 @@ function useGlibForm({ formRef }) {
|
|
|
89
89
|
return { updateDirtyState };
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
function useGlibInput({ props }) {
|
|
92
|
+
function useGlibInput({ props, cacheValue = true }) {
|
|
93
93
|
// setup dirty state
|
|
94
94
|
const registeredInputs = inject('registeredInputs', null);
|
|
95
95
|
const ignoredDirtyCheckFields = inject('ignoredDirtyCheckFields', null);
|
|
@@ -106,7 +106,7 @@ function useGlibInput({ props }) {
|
|
|
106
106
|
|
|
107
107
|
// save fieldModel to spec so data still intact even if component unmounted
|
|
108
108
|
onBeforeUpdate(() => {
|
|
109
|
-
if (!instance.ctx.spec) return;
|
|
109
|
+
if (!instance.ctx.spec || !cacheValue) return;
|
|
110
110
|
instance.ctx.spec.value = instance.ctx.fieldModel;
|
|
111
111
|
});
|
|
112
112
|
|
|
@@ -75,7 +75,7 @@ export default {
|
|
|
75
75
|
setup(props) {
|
|
76
76
|
useGlibInput({ props });
|
|
77
77
|
|
|
78
|
-
const fieldModel = ref(props.
|
|
78
|
+
const fieldModel = ref(props.spec.value || props.defaultValue);
|
|
79
79
|
const options = ref(props.spec.options);
|
|
80
80
|
const append = props.spec.append || {};
|
|
81
81
|
|
|
@@ -139,13 +139,9 @@ export default {
|
|
|
139
139
|
}
|
|
140
140
|
},
|
|
141
141
|
methods: {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
// return;
|
|
146
|
-
// }
|
|
147
|
-
// this.fieldModel = this.spec.value;
|
|
148
|
-
// },
|
|
142
|
+
_linkFieldModels(valueChanged) {
|
|
143
|
+
this.fieldModel = this.spec.value || this.defaultValue;
|
|
144
|
+
},
|
|
149
145
|
onChange() {
|
|
150
146
|
this.$executeOnChange();
|
|
151
147
|
const containerEl = this.$refs.container;
|
|
@@ -63,14 +63,29 @@ export default defineComponent({
|
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
function processChildViewsIfExists(index, view, properties) {
|
|
67
|
+
if (view && view.childViews) {
|
|
68
|
+
view.childViews.forEach((childView) => processView(index, childView, properties));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function processViewPanelsSplit(index, view, properties) {
|
|
73
|
+
processChildViewsIfExists(index, view.left, properties);
|
|
74
|
+
processChildViewsIfExists(index, view.center, properties);
|
|
75
|
+
processChildViewsIfExists(index, view.right, properties);
|
|
76
|
+
}
|
|
77
|
+
|
|
66
78
|
function processView(index, view, properties) {
|
|
67
79
|
if (view.name && view.view.startsWith('fields')) {
|
|
68
80
|
Object.assign(view, properties[view.name]);
|
|
69
81
|
view.name = prefixFieldName(index, view.name);
|
|
70
82
|
}
|
|
71
|
-
if (view.childViews) {
|
|
83
|
+
else if (view.childViews) {
|
|
72
84
|
view.childViews.forEach((childView) => processView(index, childView, properties));
|
|
73
85
|
}
|
|
86
|
+
else if (view.center || view.left || view.right) {
|
|
87
|
+
processViewPanelsSplit(index, view, properties);
|
|
88
|
+
}
|
|
74
89
|
}
|
|
75
90
|
|
|
76
91
|
function removeGroupEntry(groupSpec) {
|
|
@@ -1,29 +1,19 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div :style="$styles()" v-if="loadIf">
|
|
3
|
-
<v-autocomplete ref="autocomplete" v-model="model" :items="allItems" v-model:search="search"
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
:
|
|
7
|
-
:placeholder="spec.placeholder" persistent-placeholder
|
|
8
|
-
:multiple="spec.multiple" @update:focused="focused = $event"
|
|
9
|
-
no-data-text="No result"
|
|
10
|
-
>
|
|
3
|
+
<v-autocomplete ref="autocomplete" v-model="model" :items="allItems" v-model:search="search" :label="spec.label"
|
|
4
|
+
hide-selected no-filter return-object chips closable-chips :clearable="spec.clearable" :variant="variant"
|
|
5
|
+
:density="density" :placeholder="spec.placeholder" persistent-placeholder :multiple="spec.multiple"
|
|
6
|
+
@update:focused="focused = $event" no-data-text="No result">
|
|
11
7
|
|
|
12
8
|
<template #item="{ item, index, props }">
|
|
13
|
-
<v-list-item
|
|
14
|
-
v-bind="props"
|
|
15
|
-
dense
|
|
16
|
-
>
|
|
9
|
+
<v-list-item v-bind="props" dense>
|
|
17
10
|
<template #title>
|
|
18
11
|
<div>
|
|
19
12
|
{{ item.title ?? item.raw.title }}
|
|
20
13
|
</div>
|
|
21
14
|
</template>
|
|
22
15
|
<template #default>
|
|
23
|
-
<div
|
|
24
|
-
v-if="item.raw.subtitle"
|
|
25
|
-
style="opacity: 0.8"
|
|
26
|
-
>
|
|
16
|
+
<div v-if="item.raw.subtitle" style="opacity: 0.8">
|
|
27
17
|
{{ item.raw.subtitle }}
|
|
28
18
|
</div>
|
|
29
19
|
<!-- <div
|
|
@@ -95,8 +85,16 @@ export default {
|
|
|
95
85
|
spec: { type: Object, required: true }
|
|
96
86
|
},
|
|
97
87
|
data() {
|
|
88
|
+
let model;
|
|
89
|
+
const selectedItems = this.spec.selectedOptions;
|
|
90
|
+
if (selectedItems) {
|
|
91
|
+
model = this.spec.multiple ? selectedItems : selectedItems[0];
|
|
92
|
+
} else {
|
|
93
|
+
model = this.spec.multiple ? [] : null;
|
|
94
|
+
}
|
|
95
|
+
|
|
98
96
|
return {
|
|
99
|
-
model
|
|
97
|
+
model,
|
|
100
98
|
search: null,
|
|
101
99
|
nextPageUrl: null,
|
|
102
100
|
keyword: "",
|
|
@@ -111,7 +109,7 @@ export default {
|
|
|
111
109
|
if (this.model) {
|
|
112
110
|
// Depends on whether the field is single or multiple
|
|
113
111
|
if (this.$type.isArray(this.model)) {
|
|
114
|
-
return this.model
|
|
112
|
+
return this.model;
|
|
115
113
|
} else {
|
|
116
114
|
return [this.model];
|
|
117
115
|
}
|
|
@@ -173,12 +171,7 @@ export default {
|
|
|
173
171
|
},
|
|
174
172
|
methods: {
|
|
175
173
|
$ready() {
|
|
176
|
-
|
|
177
|
-
if (selectedItems) {
|
|
178
|
-
this.model = this.spec.multiple ? selectedItems : selectedItems[0];
|
|
179
|
-
} else {
|
|
180
|
-
this.model = this.spec.multiple ? [] : null;
|
|
181
|
-
}
|
|
174
|
+
|
|
182
175
|
|
|
183
176
|
this.updateAllItems();
|
|
184
177
|
this.loadItems(this.spec.url, false);
|
|
@@ -49,11 +49,11 @@ import * as uploader from "../composable/upload";
|
|
|
49
49
|
import { useFileUtils } from "../composable/file";
|
|
50
50
|
import { useGlibInput } from "../composable/form";
|
|
51
51
|
|
|
52
|
-
useGlibInput({ props });
|
|
53
|
-
|
|
54
52
|
const { makeKey, Item } = useFileUtils();
|
|
55
53
|
const props = defineProps(['spec']);
|
|
56
54
|
|
|
55
|
+
useGlibInput({ props });
|
|
56
|
+
|
|
57
57
|
const canvas = ref(null);
|
|
58
58
|
const context = computed(() => {
|
|
59
59
|
if (!canvas.value) return;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import Action from "../../action";
|
|
2
2
|
import UrlUtils from "../../utils/url";
|
|
3
3
|
import * as TypeUtils from "../../utils/type";
|
|
4
|
-
import { vueApp } from "../../store";
|
|
4
|
+
import { isRerender, vueApp } from "../../store";
|
|
5
5
|
|
|
6
6
|
export default {
|
|
7
7
|
data() {
|
|
@@ -41,6 +41,15 @@ export default {
|
|
|
41
41
|
if (this.spec && this.spec.onChangeAndLoad && this.$registryEnabled()) {
|
|
42
42
|
this.$executeOnChange();
|
|
43
43
|
}
|
|
44
|
+
|
|
45
|
+
if (this.spec && this.$registryEnabled()) {
|
|
46
|
+
if (isRerender()) {
|
|
47
|
+
GLib.action.execute(this.spec.onRerender, this);
|
|
48
|
+
} else {
|
|
49
|
+
GLib.action.execute(this.spec.onLoad, this);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
44
53
|
},
|
|
45
54
|
beforeUpdate() {
|
|
46
55
|
if (vueApp.isStale) {
|
|
@@ -24,7 +24,13 @@ export default defineComponent({
|
|
|
24
24
|
const key = Math.random().toString(36).slice(2, 7);
|
|
25
25
|
|
|
26
26
|
const handleMouseEnter = () => {
|
|
27
|
-
|
|
27
|
+
const properties = {
|
|
28
|
+
body: { childViews: [this.properties] },
|
|
29
|
+
key: key,
|
|
30
|
+
placement: this.spec.tooltip.placement || 'top',
|
|
31
|
+
styleClass: 'views-tooltip'
|
|
32
|
+
};
|
|
33
|
+
launch.popover.open(properties, this);
|
|
28
34
|
};
|
|
29
35
|
const handleMouseLeave = () => {
|
|
30
36
|
launch.popover.close({ key: key });
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<input type="hidden" :name="key" :value="value" />
|
|
6
6
|
</template>
|
|
7
7
|
|
|
8
|
-
<panels-responsive :spec="
|
|
8
|
+
<panels-responsive :spec="spec" />
|
|
9
9
|
|
|
10
10
|
<!-- Used by app.vue -->
|
|
11
11
|
<slot />
|
|
@@ -13,14 +13,14 @@
|
|
|
13
13
|
</template>
|
|
14
14
|
|
|
15
15
|
<script>
|
|
16
|
-
import { provide, ref } from "vue";
|
|
16
|
+
import { onMounted, provide, ref } from "vue";
|
|
17
17
|
import { useGlibForm } from "../composable/form";
|
|
18
18
|
|
|
19
19
|
export default {
|
|
20
20
|
props: {
|
|
21
21
|
spec: { type: Object, required: true },
|
|
22
22
|
},
|
|
23
|
-
setup() {
|
|
23
|
+
setup(props) {
|
|
24
24
|
const formCtx = ref({ form: null });
|
|
25
25
|
|
|
26
26
|
const form = ref(null);
|
|
@@ -28,6 +28,27 @@ export default {
|
|
|
28
28
|
|
|
29
29
|
provide('formCtx', formCtx);
|
|
30
30
|
|
|
31
|
+
function firstInput() {
|
|
32
|
+
return form.value.$el.querySelector('input:not([type="hidden"]):not([disabled]):not([readonly]),textarea,select');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function firstInputPromise() {
|
|
36
|
+
return new Promise((resolve) => {
|
|
37
|
+
let intervalId = setInterval(() => {
|
|
38
|
+
if (firstInput()) {
|
|
39
|
+
clearInterval(intervalId);
|
|
40
|
+
resolve(firstInput());
|
|
41
|
+
}
|
|
42
|
+
}, 50);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
onMounted(() => {
|
|
47
|
+
if (props.spec.autofocus) {
|
|
48
|
+
firstInputPromise().then((firstInput) => firstInput.focus());
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
31
52
|
return { formCtx, glibForm, form };
|
|
32
53
|
},
|
|
33
54
|
data: () => ({
|
|
@@ -39,26 +60,26 @@ export default {
|
|
|
39
60
|
// modifiedSpec: {},
|
|
40
61
|
}),
|
|
41
62
|
computed: {
|
|
42
|
-
augmentedSpec() {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
63
|
+
// augmentedSpec() {
|
|
64
|
+
// const spec = this.spec;
|
|
65
|
+
|
|
66
|
+
// if (spec.autoFocus) {
|
|
67
|
+
// let firstAutoFocus = false;
|
|
68
|
+
|
|
69
|
+
// // TODO: Use map() instead for immutability
|
|
70
|
+
// spec.childViews.forEach(childView => {
|
|
71
|
+
// if (
|
|
72
|
+
// childView.view.startsWith("fields/") &&
|
|
73
|
+
// childView.view !== "fields/hidden-v1" &&
|
|
74
|
+
// !firstAutoFocus
|
|
75
|
+
// ) {
|
|
76
|
+
// childView.autoFocus = true;
|
|
77
|
+
// firstAutoFocus = true;
|
|
78
|
+
// }
|
|
79
|
+
// });
|
|
80
|
+
// }
|
|
81
|
+
// return spec;
|
|
82
|
+
// }
|
|
62
83
|
},
|
|
63
84
|
watch: {
|
|
64
85
|
spec: {
|
package/components/popover.vue
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
| 'left-end'
|
|
15
15
|
-->
|
|
16
16
|
|
|
17
|
-
<common-responsive v-if="toggle" ref="container" :spec="spec.body" />
|
|
17
|
+
<common-responsive :class="`views-popovers ${spec.styleClass}`" v-if="toggle" ref="container" :spec="spec.body" />
|
|
18
18
|
|
|
19
19
|
</template>
|
|
20
20
|
|
|
@@ -25,7 +25,7 @@ import { driver } from "driver.js";
|
|
|
25
25
|
import 'driver.js/dist/driver.css';
|
|
26
26
|
|
|
27
27
|
export default {
|
|
28
|
-
props: ['spec', 'reference', 'placeholder'],
|
|
28
|
+
props: ['spec', 'reference', 'placeholder', 'styleClass'],
|
|
29
29
|
data() {
|
|
30
30
|
const allowClose = this.spec.overlay ? this.spec.overlay.closeOnFocus : true;
|
|
31
31
|
return {
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { testPageUrl } from "../../helper"
|
|
2
|
+
const url = testPageUrl('lifecycle')
|
|
3
|
+
|
|
4
|
+
describe('glib lifecycle hooks', () => {
|
|
5
|
+
it('execute onLoad and onRerender', () => {
|
|
6
|
+
cy.visit(url)
|
|
7
|
+
|
|
8
|
+
cy.contains('page.onLoad').should('be.exist')
|
|
9
|
+
|
|
10
|
+
// check if onLoad executed
|
|
11
|
+
cy.get('#button').should('have.text', 'onLoad')
|
|
12
|
+
cy.get('#chip').trigger('mouseenter')
|
|
13
|
+
cy.get('.views-tooltip p').should('have.text', 'onLoad')
|
|
14
|
+
cy.get('#icon').should('contain.text', 'home')
|
|
15
|
+
|
|
16
|
+
// navigate to other page and close it
|
|
17
|
+
cy.contains('navigate to other page').click()
|
|
18
|
+
cy.contains('windows/close').click()
|
|
19
|
+
|
|
20
|
+
// check if onRerender executed
|
|
21
|
+
cy.contains('page.onRerender').should('be.exist')
|
|
22
|
+
|
|
23
|
+
cy.get('#button').should('have.text', 'onRerender')
|
|
24
|
+
cy.get('#chip').trigger('mouseenter')
|
|
25
|
+
cy.get('.views-tooltip p').should('have.text', 'onRerender')
|
|
26
|
+
cy.get('#icon').should('contain.text', 'person')
|
|
27
|
+
})
|
|
28
|
+
})
|
|
@@ -6,7 +6,9 @@ describe('multiUpload', () => {
|
|
|
6
6
|
cy.visit(url)
|
|
7
7
|
|
|
8
8
|
cy.contains('clear files').click()
|
|
9
|
+
cy.contains('File (Example)').should('not.exist')
|
|
9
10
|
cy.contains('populate files').click()
|
|
11
|
+
cy.contains('File (Example)').should('exist')
|
|
10
12
|
|
|
11
13
|
cy.contains('submit').click()
|
|
12
14
|
|
|
@@ -35,6 +35,6 @@ describe("selectable", () => {
|
|
|
35
35
|
it('can detect timezone', () => {
|
|
36
36
|
cy.visit(url)
|
|
37
37
|
cy.get('input[name="user[timezone]"]').should('have.value', 'Asia/Shanghai')
|
|
38
|
-
cy.get('input[name="user[timezone_with_current_local]"]').should('not.have.value',
|
|
38
|
+
cy.get('input[name="user[timezone_with_current_local]"]').should('not.have.value', '')
|
|
39
39
|
})
|
|
40
40
|
})
|
package/package.json
CHANGED
package/store.js
CHANGED
|
@@ -17,13 +17,13 @@ export const vueApp = reactive({
|
|
|
17
17
|
lastNavigationCount: null,
|
|
18
18
|
tooltipSpec: {},
|
|
19
19
|
bottomBanners: {},
|
|
20
|
-
|
|
20
|
+
sheet: { show: false, placement: 'right', spec: {} },
|
|
21
21
|
uploader: {},
|
|
22
22
|
confirmationDialog: {},
|
|
23
23
|
mobile: undefined
|
|
24
24
|
});
|
|
25
25
|
|
|
26
|
-
export const jsonView = reactive({ page: window.__page });
|
|
26
|
+
export const jsonView = reactive({ page: window.__page, source: 'server' }); // source can be 'server' or 'history'
|
|
27
27
|
export const jsonSettings = reactive(window.__settings);
|
|
28
28
|
|
|
29
29
|
export const dialogs = ref([]);
|
|
@@ -39,7 +39,8 @@ export const closeAllDialog = () => {
|
|
|
39
39
|
export const closeAllPopover = () => {
|
|
40
40
|
popovers.value.forEach((popover) => popover.close());
|
|
41
41
|
popovers.value = [];
|
|
42
|
-
vueApp.
|
|
42
|
+
vueApp.sheet.show = false;
|
|
43
|
+
|
|
43
44
|
};
|
|
44
45
|
|
|
45
46
|
export const glibevent = reactive({
|
|
@@ -99,4 +100,8 @@ export function executeGlibEvent(name) {
|
|
|
99
100
|
}
|
|
100
101
|
|
|
101
102
|
return glibevent[name]();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function isRerender() {
|
|
106
|
+
return jsonView.source == 'history';
|
|
102
107
|
}
|
package/utils/history.js
CHANGED
|
@@ -44,7 +44,7 @@ export default class {
|
|
|
44
44
|
this._pageBody.scrollTop = 0;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
static restoreOnBackOrForward() {
|
|
47
|
+
static restoreOnBackOrForward({ onAfterBack }) {
|
|
48
48
|
const vm = this;
|
|
49
49
|
let skipOnce = false;
|
|
50
50
|
window.onpopstate = event => {
|
|
@@ -75,12 +75,16 @@ export default class {
|
|
|
75
75
|
data.content.__poppedState = true;
|
|
76
76
|
|
|
77
77
|
jsonView.page = data.content;
|
|
78
|
+
jsonView.source = 'history';
|
|
79
|
+
|
|
78
80
|
vueApp.lastNavigationCount = data.navigationCount;
|
|
79
81
|
vm.navigationCount = data.navigationCount;
|
|
80
82
|
|
|
81
83
|
nextTick(() => {
|
|
82
84
|
const scrollTop = this.bodyScrollTops[this.navigationCount];
|
|
83
85
|
this._pageBody.scrollTop = scrollTop;
|
|
86
|
+
|
|
87
|
+
if (onAfterBack) { onAfterBack(); }
|
|
84
88
|
});
|
|
85
89
|
};
|
|
86
90
|
}
|
package/utils/http.js
CHANGED
|
@@ -4,7 +4,7 @@ import { nextTick } from 'vue';
|
|
|
4
4
|
import { ctx, dialogs, jsonView, vueApp } from "../store";
|
|
5
5
|
import Hash from "./hash";
|
|
6
6
|
|
|
7
|
-
const strandom = () => (Math.random() + 1).toString(36).substring(7);
|
|
7
|
+
const strandom = () => (Math.random() + 1).toString(36).substring(7) + Date.now().toString();
|
|
8
8
|
|
|
9
9
|
let loading = false;
|
|
10
10
|
|
|
@@ -183,6 +183,7 @@ export default class {
|
|
|
183
183
|
Utils.http.forceComponentUpdate(() => {
|
|
184
184
|
page.key = strandom();
|
|
185
185
|
jsonView.page = page;
|
|
186
|
+
jsonView.source = 'server';
|
|
186
187
|
const redirectUrl = Utils.url.htmlUrl(response.url);
|
|
187
188
|
Utils.history.updatePage(jsonView.page, redirectUrl);
|
|
188
189
|
|