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.
@@ -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
- Utils.history.backWithoutRender()
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 }) =>
@@ -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
- return {
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({ viewHeaders, cellIndex, rowId, view, value }) {
132
- this.viewHeaders = viewHeaders;
132
+ constructor({ cellIndex, rowId, dataRow, viewCell }) {
133
133
  this.cellIndex = cellIndex;
134
134
  this.rowId = rowId;
135
- this.view = view;
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
- this.cellId = this.viewHeaders[this.cellIndex].id;
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((viewCells, i) => {
250
- let value = dataRow.columns[i].value;
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.querySelector('input').value;
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]: curr.value,
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
@@ -57,4 +57,8 @@ export default {
57
57
  .v-bottom-sheet .v-list-subheader__text {
58
58
  white-space: unset;
59
59
  }
60
+
61
+ .v-bottom-sheet .v-list-item__content .v-list-item-title {
62
+ white-space: unset;
63
+ }
60
64
  </style>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "glib-web",
3
- "version": "4.17.0",
3
+ "version": "4.18.1",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
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.toggle = false);
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 (vueApp.isFormDirty && !vueApp.isFormSubmitted) {
59
+ if (ctx().isFormDirty && !vueApp.isFormSubmitted) {
60
60
  e.preventDefault();
61
61
  }
62
62
  } else {
@@ -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