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 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,10 @@
1
+ import Action from "../../action";
2
+ import { vueApp } from "../../store";
3
+
4
+ export default class {
5
+ execute(spec, component) {
6
+ vueApp.sheet.show = false;
7
+
8
+ Action.execute(spec.onClose, component);
9
+ }
10
+ }
@@ -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
+ }
@@ -0,0 +1,14 @@
1
+ import { vueApp } from "../../store";
2
+
3
+ export default class {
4
+ execute(spec, component) {
5
+
6
+ const { placement, body } = spec;
7
+
8
+ vueApp.sheet = {
9
+ spec: body,
10
+ show: true,
11
+ placement
12
+ };
13
+ }
14
+ }
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.rightBanners.show" width="100%" max-width="684" height="100%" position="fixed"
34
- class="glib-rightBanner">
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
- .glib-rightBanner {
400
- z-index: 1005;
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 {
@@ -39,11 +39,6 @@ export default {
39
39
  methods: {
40
40
  $registryEnabled() {
41
41
  return false;
42
- },
43
- $ready() {
44
- nextTick(() => { // Make sure the component has been initialized and registered.
45
- GLib.action.execute(this.spec.onLoad, this);
46
- });
47
42
  }
48
43
  },
49
44
  };
@@ -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
- }}</v-icon>
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
 
@@ -141,6 +141,7 @@ export default {
141
141
  margin-right: 8px;
142
142
  }
143
143
 
144
+ /* Hide right calendar icon */
144
145
  input[type="date"]::-webkit-calendar-picker-indicator,
145
146
  input[type="datetime-local"]::-webkit-calendar-picker-indicator {
146
147
  display: none;
@@ -51,6 +51,7 @@ export default {
51
51
  </script>
52
52
 
53
53
  <style>
54
+ /* This is to ensure that the height is consistent with other fields, i.e. 56px as opposed to 58px. */
54
55
  .fields-date,
55
56
  .fields-datetime {
56
57
  .v-field__input {
@@ -75,7 +75,7 @@ export default {
75
75
  setup(props) {
76
76
  useGlibInput({ props });
77
77
 
78
- const fieldModel = ref(props.defaultValue || null);
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
- // _linkFieldModels(valueChanged) {
143
- // if (this.spec.csvMode && valueChanged) {
144
- // this.fieldModel = (this.spec.value || '').replace(/,\s+/g, ",").split(",").filter((v) => v != '');
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
- :label="spec.label" hide-selected no-filter return-object
5
- chips closable-chips :clearable="spec.clearable"
6
- :variant="variant" :density="density"
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: null,
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
- const selectedItems = this.spec.selectedOptions;
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);
@@ -187,8 +187,7 @@ export default defineComponent({
187
187
  fileSelect.value.click();
188
188
  }
189
189
 
190
- // TODO: Enable this
191
- // useGlibInput({ props });
190
+ useGlibInput({ props, cacheValue: false });
192
191
 
193
192
  return {
194
193
  files,
@@ -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
- launch.popover.open({ body: { childViews: [this.properties] }, key: key, placement: this.spec.tooltip.placement || 'top' }, this);
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="augmentedSpec" />
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
- const spec = this.spec;
44
-
45
- if (spec.autoFocus) {
46
- let firstAutoFocus = false;
47
-
48
- // TODO: Use map() instead for immutability
49
- spec.childViews.forEach(childView => {
50
- if (
51
- childView.view.startsWith("fields/") &&
52
- childView.view !== "fields/hidden-v1" &&
53
- !firstAutoFocus
54
- ) {
55
- childView.autoFocus = true;
56
- firstAutoFocus = true;
57
- }
58
- });
59
- }
60
- return spec;
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: {
@@ -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', undefined)
38
+ cy.get('input[name="user[timezone_with_current_local]"]').should('not.have.value', '')
39
39
  })
40
40
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "glib-web",
3
- "version": "4.24.1",
3
+ "version": "4.26.0",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/store.js CHANGED
@@ -17,13 +17,13 @@ export const vueApp = reactive({
17
17
  lastNavigationCount: null,
18
18
  tooltipSpec: {},
19
19
  bottomBanners: {},
20
- rightBanners: { show: false, spec: {} },
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.rightBanners.show = false;
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