glib-web 4.17.0 → 4.18.1
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/actions/windows/closeAll.js +5 -0
- package/actions/windows/closeAllWithOpen.js +5 -0
- package/actions/windows/closeWithOpen.js +7 -1
- package/actions/windows/closeWithReload.js +6 -1
- package/components/fields/_buttonDate.vue +86 -0
- package/components/fields/_patternText.vue +8 -1
- package/components/fields/richText.vue +22 -26
- package/components/icon.vue +1 -1
- package/components/mixins/styles.js +10 -1
- package/components/panels/bulkEdit2.vue +21 -17
- package/components/panels/table.vue +9 -1
- package/cypress/e2e/glib-web/dirtyState.cy.ts +2 -2
- package/nav/sheet.vue +4 -0
- package/package.json +1 -1
- package/store.js +2 -2
- package/utils/launch/popover.js +3 -3
|
@@ -1,8 +1,13 @@
|
|
|
1
|
+
import { executeGlibEvent } from "../../store";
|
|
1
2
|
import { ifObject } from "../../utils/type";
|
|
2
3
|
|
|
3
4
|
export default class {
|
|
4
5
|
execute(properties, component) {
|
|
5
6
|
|
|
7
|
+
if (!executeGlibEvent('onbeforewindowsclose')) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
|
|
6
11
|
Utils.history.destroy();
|
|
7
12
|
|
|
8
13
|
ifObject(properties["onClose"], it => {
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import Action from "../../action";
|
|
2
|
+
import { executeGlibEvent } from "../../store";
|
|
2
3
|
|
|
3
4
|
export default class {
|
|
4
5
|
execute(properties, component) {
|
|
6
|
+
if (!executeGlibEvent('onbeforewindowsclose')) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
|
|
5
10
|
Utils.history.destroy();
|
|
6
11
|
|
|
7
12
|
// Don't use nextTick so that there is no visible flicker.
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
+
import { executeGlibEvent } from "../../store";
|
|
2
|
+
|
|
1
3
|
export default class {
|
|
2
4
|
execute(properties, component) {
|
|
3
|
-
|
|
5
|
+
if (!executeGlibEvent('onbeforewindowsclose')) {
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
Utils.history.backWithoutRender();
|
|
4
10
|
|
|
5
11
|
// Don't use nextTick so that there is no visible flicker.
|
|
6
12
|
Utils.http.load(properties, component, true, true);
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
import { executeGlibEvent } from "../../store";
|
|
1
2
|
import Hash from "../../utils/hash";
|
|
2
3
|
|
|
3
4
|
export default class {
|
|
4
5
|
execute(properties, component) {
|
|
6
|
+
if (!executeGlibEvent('onbeforewindowsclose')) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
|
|
5
10
|
const fallbackUrl = new Hash(properties).remove("fallbackUrl");
|
|
6
11
|
const data = Object.assign({}, properties);
|
|
7
12
|
if (!Utils.history.back()) {
|
|
@@ -11,7 +16,7 @@ export default class {
|
|
|
11
16
|
|
|
12
17
|
// Allow time for history.back() to complete, which is important for actions that need
|
|
13
18
|
// to use window.location.href such as `windows/reload`
|
|
14
|
-
setTimeout(function() {
|
|
19
|
+
setTimeout(function () {
|
|
15
20
|
Utils.http.reload(data, component);
|
|
16
21
|
}, 100);
|
|
17
22
|
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
<!-- eslint-disable vue/no-mutating-props -->
|
|
2
|
+
<template>
|
|
3
|
+
<div class="glib date-input-container" @click="dateInput.showPicker()">
|
|
4
|
+
<v-icon icon="calendar_today" class="calendar-icon"></v-icon>
|
|
5
|
+
<span class="date-text">{{ text }}</span>
|
|
6
|
+
<input :name="fieldName" :type="type" class="date-input" ref="dateInput" @change="handleChanged"
|
|
7
|
+
:min="sanitizeValue(spec.min)" :max="sanitizeValue(spec.max)" :disabled="inputDisabled" :readonly="spec.readonly"
|
|
8
|
+
:value="model" />
|
|
9
|
+
</div>
|
|
10
|
+
</template>
|
|
11
|
+
|
|
12
|
+
<script>
|
|
13
|
+
import { computed, ref, watch } from "vue";
|
|
14
|
+
import { sanitize } from "../composable/date";
|
|
15
|
+
|
|
16
|
+
export default {
|
|
17
|
+
props: {
|
|
18
|
+
spec: {
|
|
19
|
+
type: Object,
|
|
20
|
+
default: () => ({}),
|
|
21
|
+
},
|
|
22
|
+
type: {
|
|
23
|
+
type: String
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
emits: ['datePicked'],
|
|
27
|
+
setup(props, ctx) {
|
|
28
|
+
function sanitizeValue(value) {
|
|
29
|
+
return sanitize(value, props.type);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const model = ref(sanitizeValue(props.spec.value));
|
|
33
|
+
|
|
34
|
+
const dateInput = ref(null);
|
|
35
|
+
const text = computed(() => model.value || props.spec.label);
|
|
36
|
+
|
|
37
|
+
watch(props, (val) => {
|
|
38
|
+
model.value = sanitizeValue(val.spec.value);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
function handleChanged(e) {
|
|
42
|
+
const { value } = e.srcElement;
|
|
43
|
+
model.value = value;
|
|
44
|
+
ctx.emit('datePicked', model.value);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return { text, sanitizeValue, dateInput, handleChanged, model };
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
<style>
|
|
53
|
+
.glib.date-input-container {
|
|
54
|
+
display: flex;
|
|
55
|
+
align-items: center;
|
|
56
|
+
background-color: #f3f5f7;
|
|
57
|
+
border-radius: 5px;
|
|
58
|
+
padding: 8px 12px;
|
|
59
|
+
position: relative;
|
|
60
|
+
width: 100%;
|
|
61
|
+
cursor: pointer;
|
|
62
|
+
|
|
63
|
+
.date-text {
|
|
64
|
+
color: #555;
|
|
65
|
+
font-size: 16px;
|
|
66
|
+
white-space: nowrap;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.date-input {
|
|
70
|
+
position: absolute;
|
|
71
|
+
top: 0;
|
|
72
|
+
left: 0;
|
|
73
|
+
width: 100%;
|
|
74
|
+
height: 100%;
|
|
75
|
+
opacity: 0;
|
|
76
|
+
cursor: pointer;
|
|
77
|
+
z-index: 2;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.calendar-icon {
|
|
81
|
+
font-size: 16px;
|
|
82
|
+
color: #888;
|
|
83
|
+
margin-right: 8px;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
</style>
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div :style="$styles()" :class="$classes()" v-if="loadIf">
|
|
3
|
+
<button-date v-if="spec.buttonTemplate" :type="type" :spec="spec" @datePicked="handleDatePicked"></button-date>
|
|
3
4
|
<!-- See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/date for why we need to use `pattern` -->
|
|
4
|
-
<v-text-field :color="gcolor" v-model="fieldModel" :name="fieldName" :label="spec.label" :hint="spec.hint"
|
|
5
|
+
<v-text-field v-else :color="gcolor" v-model="fieldModel" :name="fieldName" :label="spec.label" :hint="spec.hint"
|
|
5
6
|
:type="type" :readonly="spec.readOnly" :disabled="inputDisabled" :min="sanitizeValue(spec.min)"
|
|
6
7
|
:max="sanitizeValue(spec.max)" :pattern="pattern" :rules="$validation()" :style="$styles()"
|
|
7
8
|
:density="$classes().includes('compact') ? 'compact' : 'default'" :clearable="spec.clearable" @change="onChange"
|
|
@@ -13,9 +14,11 @@
|
|
|
13
14
|
import { sanitize } from "../composable/date";
|
|
14
15
|
import { useGlibInput } from "../composable/form";
|
|
15
16
|
import inputVariant from '../mixins/inputVariant';
|
|
17
|
+
import buttonDate from "./_buttonDate.vue";
|
|
16
18
|
|
|
17
19
|
export default {
|
|
18
20
|
mixins: [inputVariant],
|
|
21
|
+
components: { buttonDate },
|
|
19
22
|
props: {
|
|
20
23
|
spec: { type: Object, required: true },
|
|
21
24
|
type: { type: String, required: true },
|
|
@@ -36,6 +39,10 @@ export default {
|
|
|
36
39
|
onChange() {
|
|
37
40
|
this.$executeOnChange();
|
|
38
41
|
},
|
|
42
|
+
handleDatePicked(value) {
|
|
43
|
+
this.fieldModel = value;
|
|
44
|
+
this.$executeOnChange();
|
|
45
|
+
},
|
|
39
46
|
$registryEnabled() {
|
|
40
47
|
return false;
|
|
41
48
|
}
|
|
@@ -211,36 +211,32 @@ export default {
|
|
|
211
211
|
},
|
|
212
212
|
},
|
|
213
213
|
watch: {
|
|
214
|
-
spec: {
|
|
215
|
-
handler: function (val, oldVal) {
|
|
216
|
-
this.produce = val.produce || "markdown";
|
|
217
|
-
this.accept = val.accept || "markdown";
|
|
218
|
-
|
|
219
|
-
this.textEditor = new TextEditor(this);
|
|
220
|
-
this.imageUploader = val.imageUploader;
|
|
221
|
-
|
|
222
|
-
this.richEditorValue = this.textEditor.htmlValueWithRealImage(
|
|
223
|
-
val.value,
|
|
224
|
-
this.accept
|
|
225
|
-
);
|
|
226
|
-
|
|
227
|
-
this.rawEditorValue = this.textEditor.markdownValueWithRealImage(
|
|
228
|
-
val.value,
|
|
229
|
-
this.accept
|
|
230
|
-
);
|
|
231
|
-
this.producedValue = this.textEditor.producedValue(
|
|
232
|
-
val.value,
|
|
233
|
-
this.accept,
|
|
234
|
-
this.produce
|
|
235
|
-
);
|
|
236
|
-
},
|
|
237
|
-
immediate: true,
|
|
238
|
-
deep: true // because spec needs to be process first
|
|
239
|
-
},
|
|
240
214
|
producedValue(val, oldVal) {
|
|
241
215
|
this.fieldModel = val;
|
|
242
216
|
}
|
|
243
217
|
},
|
|
218
|
+
beforeMount() {
|
|
219
|
+
this.produce = this.spec.produce || "markdown";
|
|
220
|
+
this.accept = this.spec.accept || "markdown";
|
|
221
|
+
|
|
222
|
+
this.textEditor = new TextEditor(this);
|
|
223
|
+
this.imageUploader = this.spec.imageUploader;
|
|
224
|
+
|
|
225
|
+
this.richEditorValue = this.textEditor.htmlValueWithRealImage(
|
|
226
|
+
this.spec.value,
|
|
227
|
+
this.accept
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
this.rawEditorValue = this.textEditor.markdownValueWithRealImage(
|
|
231
|
+
this.spec.value,
|
|
232
|
+
this.accept
|
|
233
|
+
);
|
|
234
|
+
this.producedValue = this.textEditor.producedValue(
|
|
235
|
+
this.spec.value,
|
|
236
|
+
this.accept,
|
|
237
|
+
this.produce
|
|
238
|
+
);
|
|
239
|
+
},
|
|
244
240
|
mounted() {
|
|
245
241
|
if (this.spec.imageUploader) {
|
|
246
242
|
bus.$on("richText/dropOrPaste", ({ file, editor, cursorLocation }) =>
|
package/components/icon.vue
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<a v-if="$href()" :href="$href()" :rel="$rel()" @click="$onClick()">
|
|
3
3
|
<common-icon ref="delegate" :spec="iconSpec" />
|
|
4
4
|
</a>
|
|
5
|
-
<common-icon ref="delegate" v-else :spec="iconSpec" />
|
|
5
|
+
<common-icon @click="$onClick()" ref="delegate" v-else :spec="iconSpec" />
|
|
6
6
|
</template>
|
|
7
7
|
|
|
8
8
|
<script>
|
|
@@ -30,7 +30,7 @@ const jsonLogicData = (data) => {
|
|
|
30
30
|
|
|
31
31
|
export default {
|
|
32
32
|
data: function () {
|
|
33
|
-
|
|
33
|
+
let obj = {
|
|
34
34
|
// fieldName: null,
|
|
35
35
|
fieldModel: null,
|
|
36
36
|
_disabled: false,
|
|
@@ -42,6 +42,15 @@ export default {
|
|
|
42
42
|
// See https://github.com/vuetifyjs/vuetify/issues/8876
|
|
43
43
|
vuetifyEmptyString: "<EMPTY_STRING>"
|
|
44
44
|
};
|
|
45
|
+
|
|
46
|
+
// These fields use its own model variable. Delete this variable to avoid mixup,
|
|
47
|
+
// which would cause their values to get reset in certain situations,
|
|
48
|
+
// e.g. when the fields reside in a dialogs_show and the dialog gets closed and reopened.
|
|
49
|
+
if (['fields-checkGroup', 'fields-check', 'fields-select'].includes(GLib.component.vueName(this))) {
|
|
50
|
+
delete obj.fieldModel;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return obj;
|
|
45
54
|
},
|
|
46
55
|
provide() {
|
|
47
56
|
const obj = {};
|
|
@@ -92,6 +92,7 @@ import Action from "../../action";
|
|
|
92
92
|
import Hash from "../../utils/hash";
|
|
93
93
|
import Papa from "papaparse";
|
|
94
94
|
import launch from "../../utils/launch";
|
|
95
|
+
import { isPresent } from "../../utils/type";
|
|
95
96
|
|
|
96
97
|
class Row {
|
|
97
98
|
constructor({ id, columns, selected, index, status }) {
|
|
@@ -128,14 +129,22 @@ class Row {
|
|
|
128
129
|
}
|
|
129
130
|
|
|
130
131
|
class Cell {
|
|
131
|
-
constructor({
|
|
132
|
-
this.viewHeaders = viewHeaders;
|
|
132
|
+
constructor({ cellIndex, rowId, dataRow, viewCell }) {
|
|
133
133
|
this.cellIndex = cellIndex;
|
|
134
134
|
this.rowId = rowId;
|
|
135
|
-
this.
|
|
135
|
+
this.cellId = props.spec.viewHeaders[this.cellIndex].id;
|
|
136
|
+
|
|
137
|
+
let value = dataRow.columns[cellIndex].value;
|
|
138
|
+
|
|
139
|
+
// clean value
|
|
140
|
+
if (!value) value = undefined;
|
|
141
|
+
if (value && value.includes(',')) value = value.split(',');
|
|
136
142
|
this.value = value;
|
|
137
143
|
|
|
138
|
-
|
|
144
|
+
const name = viewCell.multiple ? `${this.cellId}[]` : this.cellId;
|
|
145
|
+
|
|
146
|
+
this.view = Object.assign({}, viewCell, dataRow.columns[cellIndex], { value: value, name: name });
|
|
147
|
+
|
|
139
148
|
}
|
|
140
149
|
}
|
|
141
150
|
|
|
@@ -246,17 +255,8 @@ function makeRows(dataRows, selected = false) {
|
|
|
246
255
|
const rows = dataRows.map((dataRow, index) => {
|
|
247
256
|
// const id = dataRow.rowId || Math.random().toString(36).slice(2, 7);
|
|
248
257
|
const id = dataRow.rowId || `row_${index}`;
|
|
249
|
-
const columns = props.spec.viewCells.map((
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
// clean the data
|
|
253
|
-
if (!value) value = undefined;
|
|
254
|
-
if (value && value.includes(',')) value = value.split(',');
|
|
255
|
-
|
|
256
|
-
const view = Object.assign({}, viewCells, dataRow.columns[i], { value: value });
|
|
257
|
-
const cellIndex = i;
|
|
258
|
-
const viewHeaders = props.spec.viewHeaders;
|
|
259
|
-
return new Cell({ rowId: id, view, cellIndex, viewHeaders, value });
|
|
258
|
+
const columns = props.spec.viewCells.map((viewCell, i) => {
|
|
259
|
+
return new Cell({ rowId: id, cellIndex: i, dataRow, viewCell });
|
|
260
260
|
});
|
|
261
261
|
return new Row({ selected, columns, id, index });
|
|
262
262
|
});
|
|
@@ -275,7 +275,8 @@ function handleCellChange(e, cell) {
|
|
|
275
275
|
if (e.target instanceof HTMLInputElement) {
|
|
276
276
|
value = e.target.value;
|
|
277
277
|
} else {
|
|
278
|
-
value = e.target.
|
|
278
|
+
value = Array.from(e.target.querySelectorAll(`input[name="${cell.view.name}"]`)).map((el) => el.value);
|
|
279
|
+
value = cell.view.multiple ? value : value[0];
|
|
279
280
|
}
|
|
280
281
|
|
|
281
282
|
cell.value = value;
|
|
@@ -361,8 +362,11 @@ function submitRows(rows) {
|
|
|
361
362
|
updateRow({ rowId: row.id, status: 'pending' });
|
|
362
363
|
|
|
363
364
|
const formData = row.columns.reduce((prev, curr) => {
|
|
365
|
+
let value = curr.value;
|
|
366
|
+
if (curr.view.multiple && isPresent(curr.value)) value = [value].flat();
|
|
367
|
+
if (curr.view.multiple && !isPresent(curr.value)) value = [''];
|
|
364
368
|
return Object.assign(prev, {
|
|
365
|
-
[curr.cellId]:
|
|
369
|
+
[curr.cellId]: value || '',
|
|
366
370
|
[props.spec.paramNameForRowId || 'rowId']: row.id
|
|
367
371
|
});
|
|
368
372
|
}, {});
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
<template v-if="importable">
|
|
12
12
|
<span>{{
|
|
13
13
|
rows(section).length + section.dataRows.length
|
|
14
|
-
|
|
14
|
+
}}
|
|
15
15
|
rows</span>
|
|
16
16
|
<input ref="fileInput" style="display: none;" type="file" accept=".csv"
|
|
17
17
|
@change="loadFile($event, section)" />
|
|
@@ -205,6 +205,14 @@ table.table--grid {
|
|
|
205
205
|
}
|
|
206
206
|
}
|
|
207
207
|
}
|
|
208
|
+
|
|
209
|
+
table.table--plain {
|
|
210
|
+
td {
|
|
211
|
+
>span {
|
|
212
|
+
padding: unset;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
208
216
|
</style>
|
|
209
217
|
|
|
210
218
|
<!-- Overridable -->
|
|
@@ -26,7 +26,7 @@ describe('dirtyState', () => {
|
|
|
26
26
|
|
|
27
27
|
cy.contains('multiupload').click()
|
|
28
28
|
cy.contains('Female').click()
|
|
29
|
-
cy.contains('multiupload').click()
|
|
29
|
+
cy.contains('multiupload').click() // Try to navigate to another page
|
|
30
30
|
cy.get('h2').should('contain.text', 'MultiUpload')
|
|
31
31
|
})
|
|
32
32
|
|
|
@@ -47,7 +47,7 @@ describe('dirtyState', () => {
|
|
|
47
47
|
|
|
48
48
|
cy.then(() => expect(text).to.equal('Changes you made have not been saved. Are you sure?'))
|
|
49
49
|
|
|
50
|
-
cy.contains('multiupload').click()
|
|
50
|
+
cy.contains('multiupload').click() // Try to navigate to another page
|
|
51
51
|
cy.get('h2').should('contain.text', 'MultiUpload')
|
|
52
52
|
})
|
|
53
53
|
|
package/nav/sheet.vue
CHANGED
package/package.json
CHANGED
package/store.js
CHANGED
|
@@ -34,7 +34,7 @@ export const closeAllDialog = () => {
|
|
|
34
34
|
};
|
|
35
35
|
|
|
36
36
|
export const closeAllPopover = () => {
|
|
37
|
-
popovers.value.forEach((popover) => popover.
|
|
37
|
+
popovers.value.forEach((popover) => popover.close());
|
|
38
38
|
popovers.value = [];
|
|
39
39
|
};
|
|
40
40
|
|
|
@@ -56,7 +56,7 @@ function glibEventHandler(e, closeAllFloating = true) {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
if (e) {
|
|
59
|
-
if (
|
|
59
|
+
if (ctx().isFormDirty && !vueApp.isFormSubmitted) {
|
|
60
60
|
e.preventDefault();
|
|
61
61
|
}
|
|
62
62
|
} else {
|
package/utils/launch/popover.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createApp, h } from "vue";
|
|
2
2
|
import Popover from "../../components/popover.vue";
|
|
3
3
|
import { Vue } from "../..";
|
|
4
|
-
import { autoUpdate, computePosition, shift, offset, arrow } from "@floating-ui/dom";
|
|
4
|
+
import { autoUpdate, computePosition, shift, offset, arrow, flip } from "@floating-ui/dom";
|
|
5
5
|
|
|
6
6
|
import bus from "../eventBus";
|
|
7
7
|
import { htmlElement } from "../../components/helper";
|
|
@@ -58,8 +58,8 @@ export default class LaunchPopover {
|
|
|
58
58
|
Object.assign(placeholder.style, { position: 'absolute', display: 'block', top: 0, left: 0 });
|
|
59
59
|
|
|
60
60
|
autoUpdate(reference, floating, () => {
|
|
61
|
-
computePosition(reference, floating, { placement: placement, middleware: [shift({ padding: 4 }), offset(offsetSize), arrow({ element: arrowEl })] })
|
|
62
|
-
.then(({ x, y, middlewareData }) => {
|
|
61
|
+
computePosition(reference, floating, { placement: placement, middleware: [shift({ padding: 4 }), offset(offsetSize), flip(), arrow({ element: arrowEl })] })
|
|
62
|
+
.then(({ placement, x, y, middlewareData }) => {
|
|
63
63
|
// zIndex should be larger than dialog's, which is 2400.
|
|
64
64
|
Object.assign(placeholder.style, { top: `${y}px`, left: `${x}px`, zIndex: 10001 });
|
|
65
65
|
|