glib-web 4.30.1 → 4.32.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.
@@ -1,49 +1,56 @@
1
1
 
2
2
 
3
- import HttpGet from '../http/get';
4
3
  import countries from "moment-timezone/data/meta/latest.json";
5
4
  import Action from "../../action";
5
+ import http from "../../utils/http";
6
+ import merge from 'lodash.merge';
6
7
 
7
8
  export default class {
8
9
  async execute(spec, component) {
9
10
  const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
10
11
  const countriesWithTimeZone = Object.values(countries.countries).filter((country) => country.zones.includes(timeZone));
11
12
 
12
- let data;
13
-
14
13
  if (countriesWithTimeZone.length === 1) {
15
- data = Object.assign({}, spec.onDetect, {
16
- formData: {
17
- country: countriesWithTimeZone[0].name,
18
- iso: countriesWithTimeZone[0].abbr
19
- }
20
- });
14
+ this.handleWithTimezone(countriesWithTimeZone, spec, component);
21
15
  } else {
22
- const httpGet = new HttpGet();
23
-
24
- const specIp = {
25
- url: 'http://ip-api.com/json/',
26
- };
16
+ this.handleWithIp(spec, component);
17
+ }
18
+ }
27
19
 
28
- try {
29
- const { page } = await httpGet.execute(specIp, component);
20
+ handleWithTimezone(countriesWithTimeZone, spec, component) {
21
+ const data = merge({}, spec.onDetect, {
22
+ formData: {
23
+ country: {
24
+ iso_short_name: countriesWithTimeZone[0].name,
25
+ alpha2: countriesWithTimeZone[0].abbr
26
+ }
27
+ }
28
+ });
29
+ Action.execute(data, component);
30
+ }
30
31
 
31
- data = Object.assign({}, spec.onDetect, {
32
- formData: {
33
- country: page.country,
34
- iso: page.countryCode,
32
+ handleWithIp(spec, component) {
33
+ http.execute(
34
+ { url: 'http://ip-api.com/json/' },
35
+ 'GET',
36
+ component,
37
+ (page) => {
38
+ const data = merge(
39
+ {},
40
+ spec.onDetect,
41
+ {
42
+ formData: {
43
+ country: {
44
+ iso_short_name: page.country,
45
+ alpha2: page.countryCode
46
+ }
47
+ }
35
48
  }
36
- });
37
- } catch ({ error }) {
38
- console.error('Error:', error);
39
- return;
40
- }
41
- }
49
+ );
42
50
 
43
- if (data) {
44
- Action.execute(data, component);
45
- } else {
46
- console.error('Data is undefined');
47
- }
51
+ Action.execute(data, component);
52
+ },
53
+ (err) => console.error(err)
54
+ );
48
55
  }
49
56
  }
@@ -1,16 +1,12 @@
1
1
  import { nextTick } from "vue";
2
+ import { forEachTargets } from "../util";
2
3
 
3
4
  export default class {
4
5
  execute(properties, component) {
5
- const target = GLib.component.findById(properties.targetId);
6
-
7
- if (!target) {
8
- console.warn("Component ID not found", properties.targetId);
9
- return;
10
- }
11
-
12
- target.action_resetValue();
13
-
6
+ forEachTargets(
7
+ properties,
8
+ (component) => component.action_resetValue()
9
+ );
14
10
  nextTick(() => {
15
11
  GLib.action.execute(properties["onReset"], component);
16
12
  });
@@ -1,7 +1,7 @@
1
+ import { httpExecuteWithRetry } from "./retry";
2
+
1
3
  export default class {
2
4
  execute(properties, controller) {
3
- GLib.http.execute(properties, "DELETE", controller, response =>
4
- GLib.action.handleResponse(response, controller)
5
- );
5
+ httpExecuteWithRetry('DELETE', properties, controller);
6
6
  }
7
7
  }
@@ -1,30 +1,5 @@
1
- import { settings } from "../../utils/settings";
1
+ import { retryWithDelay } from "./retry";
2
2
 
3
- const wait = ms => new Promise((resolve) => {
4
- setTimeout(() => resolve(), ms);
5
- });
6
-
7
- const retryWithDelay = async (
8
- fn, retries = settings.httpGet.retries, interval = settings.httpGet.interval,
9
- finalErr = 'Retry failed'
10
- ) => {
11
- try {
12
- // try
13
- await fn();
14
- } catch (err) {
15
- // if no retries left
16
- // throw error
17
- if (retries <= 0) {
18
- return Promise.reject(finalErr);
19
- }
20
-
21
- //delay the next call
22
- await wait(interval);
23
-
24
- //recursively call the same func
25
- return retryWithDelay(fn, (retries - 1), interval, finalErr);
26
- }
27
- };
28
3
 
29
4
  export default class {
30
5
  execute(spec, component) {
@@ -1,8 +1,7 @@
1
+ import { httpExecuteWithRetry } from "./retry";
1
2
 
2
3
  export default class {
3
4
  execute(properties, controller, params) {
4
- GLib.http.execute(properties, "PATCH", controller, response =>
5
- GLib.action.handleResponse(response, controller)
6
- );
5
+ httpExecuteWithRetry('PATCH', properties, controller);
7
6
  }
8
7
  }
@@ -1,8 +1,7 @@
1
+ import { httpExecuteWithRetry } from "./retry";
2
+
1
3
  export default class {
2
4
  execute(properties, controller, params) {
3
-
4
- GLib.http.execute(properties, "POST", controller, response =>
5
- GLib.action.handleResponse(response, controller)
6
- );
5
+ httpExecuteWithRetry('POST', properties, controller);
7
6
  }
8
7
  }
@@ -1,7 +1,7 @@
1
+ import { httpExecuteWithRetry } from "./retry";
2
+
1
3
  export default class {
2
4
  execute(properties, controller, params) {
3
- GLib.http.execute(properties, "PUT", controller, response =>
4
- GLib.action.handleResponse(response, controller)
5
- );
5
+ httpExecuteWithRetry('PUT', properties, controller);
6
6
  }
7
7
  }
@@ -0,0 +1,54 @@
1
+ import Action from "../../action";
2
+ import http from "../../utils/http";
3
+ import { settings } from "../../utils/settings";
4
+
5
+ const wait = ms => new Promise((resolve) => {
6
+ setTimeout(() => resolve(), ms);
7
+ });
8
+
9
+ const retryWithDelay = async (
10
+ fn, retries = settings.httpGet.retries, interval = settings.httpGet.interval,
11
+ finalErr = 'Retry failed'
12
+ ) => {
13
+ try {
14
+ // try
15
+ await fn();
16
+ } catch (err) {
17
+ // if no retries left
18
+ // throw error
19
+ if (retries <= 0) {
20
+ return Promise.reject(finalErr);
21
+ }
22
+
23
+ //delay the next call
24
+ await wait(interval);
25
+
26
+ //recursively call the same func
27
+ return retryWithDelay(fn, (retries - 1), interval, finalErr);
28
+ }
29
+ };
30
+
31
+ const httpExecuteWithRetry = (methodName, spec, component) => {
32
+ const func = async () => {
33
+ let err;
34
+ const { promise } = http.execute(
35
+ spec,
36
+ methodName,
37
+ component,
38
+ response => {
39
+ Action.handleResponse(response, component);
40
+ },
41
+ error => {
42
+ if (error == 'Server error') err = new Error(error);
43
+ }
44
+ );
45
+
46
+ await promise;
47
+
48
+ if (err) throw err;
49
+ };
50
+
51
+ retryWithDelay(func, spec.retryLimit);
52
+ };
53
+
54
+ export { retryWithDelay, httpExecuteWithRetry };
@@ -1,16 +1,16 @@
1
1
  import jsonLogic from 'json-logic-js';
2
- import { fieldModels } from "../../components/composable/conditional";
2
+ import { getAllFormData } from "../../components/composable/form";
3
3
 
4
4
  export default class {
5
5
  execute(spec, component) {
6
- let condition = spec.condition
6
+ let condition = spec.condition;
7
7
 
8
- const dynamicGroupEntry = component.$closest("fields/internalDynamicGroupEntry");
9
- if (dynamicGroupEntry) {
10
- condition = dynamicGroupEntry.$populateIndexes(condition);
11
- }
8
+ // const dynamicGroupEntry = component.$closest("fields/internalDynamicGroupEntry");
9
+ // if (dynamicGroupEntry) {
10
+ // condition = dynamicGroupEntry.$populateIndexes(condition);
11
+ // }
12
12
 
13
- const result = jsonLogic.apply(condition, Object.assign({}, fieldModels, spec.variables));
13
+ const result = jsonLogic.apply(condition, Object.assign({}, getAllFormData(), spec.variables));
14
14
  if (result) {
15
15
  this.executeWithPassthroughParams(spec.onTrue, spec, component);
16
16
  } else {
@@ -0,0 +1,22 @@
1
+ export function forEachTargets(spec, cb) {
2
+ if (spec.targetId) {
3
+ const comp = getTarget(spec.targetId);
4
+ cb(comp);
5
+ return;
6
+ }
7
+
8
+ const components = spec.targetIds.map((id) => getTarget(id)).filter((comp) => comp);
9
+
10
+ components.forEach((comp) => cb(comp));
11
+ }
12
+
13
+ function getTarget(id) {
14
+ const target = GLib.component.findById(id);
15
+
16
+ if (!target) {
17
+ console.warn("Component ID not found", id);
18
+ return;
19
+ }
20
+
21
+ return target;
22
+ }
package/app.vue CHANGED
@@ -391,6 +391,19 @@ body,
391
391
  }
392
392
 
393
393
  /******/
394
+
395
+ // override textarea style
396
+ .fields-textarea .v-input--disabled {
397
+ pointer-events: unset;
398
+
399
+ .v-field--disabled {
400
+ pointer-events: unset;
401
+ }
402
+ }
403
+
404
+ .fields-textarea .v-input--readonly {
405
+ pointer-events: unset;
406
+ }
394
407
  </style>
395
408
 
396
409
  <style scoped>
@@ -38,6 +38,12 @@ const getFormData = (el, ignoredFields = new Set()) => {
38
38
  return obj;
39
39
  };
40
40
 
41
+ function getAllFormData() {
42
+ return Array.from(document.querySelectorAll('form')).reduce((prev, curr) => {
43
+ return Object.assign({}, prev, getFormData(curr));
44
+ }, {});
45
+ }
46
+
41
47
  function useGlibForm({ formRef }) {
42
48
  const initFormData = ref({});
43
49
  const currentFormData = ref({});
@@ -112,4 +118,4 @@ function useGlibInput({ props, cacheValue = true }) {
112
118
 
113
119
  }
114
120
 
115
- export { setBusy, triggerOnChange, triggerOnInput, useGlibForm, useGlibInput, getFormData };
121
+ export { setBusy, triggerOnChange, triggerOnInput, useGlibForm, useGlibInput, getFormData, getAllFormData };
@@ -2,8 +2,8 @@
2
2
  <div :style="$styles()" :class="$classes()" v-if="loadIf">
3
3
  <button-date v-if="spec.template" :type="type" :spec="spec" @datePicked="handleDatePicked"></button-date>
4
4
  <!-- See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/date for why we need to use `pattern` -->
5
- <v-text-field v-else :color="gcolor" v-model="fieldModel" :name="fieldName" :label="spec.label" :hint="spec.hint"
6
- :type="type" :readonly="spec.readOnly" :disabled="inputDisabled" :min="sanitizeValue(spec.min)"
5
+ <v-text-field v-else ref="field" :color="gcolor" v-model="fieldModel" :name="fieldName" :label="spec.label"
6
+ :hint="spec.hint" :type="type" :readonly="spec.readOnly" :disabled="inputDisabled" :min="sanitizeValue(spec.min)"
7
7
  :max="sanitizeValue(spec.max)" :pattern="pattern" :rules="$validation()" :style="$styles()"
8
8
  :density="$classes().includes('compact') ? 'compact' : 'default'" :clearable="spec.clearable" @change="onChange"
9
9
  :variant="variant" validate-on="blur" persistent-placeholder />
@@ -43,6 +43,9 @@ export default {
43
43
  this.fieldModel = value;
44
44
  this.$executeOnChange();
45
45
  },
46
+ action_focus() {
47
+ this.$refs.field.focus();
48
+ },
46
49
  $registryEnabled() {
47
50
  return false;
48
51
  }
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <div :style="styles()" :class="$classes()" v-if="loadIf">
3
- <v-textarea :color="gcolor" v-model="fieldModel" :label="spec.label" :name="fieldName" :hint="spec.hint"
3
+ <v-textarea ref="field" :color="gcolor" v-model="fieldModel" :label="spec.label" :name="fieldName" :hint="spec.hint"
4
4
  :placeholder="spec.placeholder" :maxlength="spec.maxLength || 255" :readonly="spec.readOnly" :height="height"
5
5
  :rules="$validation()" counter :outlined="$classes().includes('outlined')" :disabled="inputDisabled"
6
6
  :no-resize="$classes().includes('no-resize')" validate-on="blur" :variant="variant" :density="density"
@@ -41,8 +41,11 @@ export default {
41
41
  },
42
42
  onChange: eventFiltering.debounce(function () {
43
43
  this.$executeOnChange();
44
- }, 300)
45
- }
44
+ }, 300),
45
+ action_focus() {
46
+ this.$refs.field.focus();
47
+ },
48
+ },
46
49
  };
47
50
  </script>
48
51
 
@@ -1,7 +1,5 @@
1
1
  import Hash from "../../utils/hash";
2
- import { fieldModels, watchFieldModels } from "../composable/conditional";
3
2
  import { determineColor } from "../../utils/constant";
4
- import Action from "../../action";
5
3
  import { triggerOnChange } from "../composable/form";
6
4
  import { realComponent } from "../helper";
7
5
  import set from "lodash.set";
@@ -118,7 +116,6 @@ export default {
118
116
  val = "";
119
117
  }
120
118
 
121
- Object.assign(fieldModels, { [this.fieldName]: val });
122
119
  },
123
120
  spec: {
124
121
  handler(spec, oldSpec) {
@@ -128,17 +125,11 @@ export default {
128
125
  valueChanged = false;
129
126
  }
130
127
  this._linkFieldModels(valueChanged);
131
- this._initConditional(oldSpec);
132
128
  }
133
129
  },
134
130
  immediate: true
135
131
  }
136
132
  },
137
- unmounted() {
138
- if (fieldModels[this.fieldName]) {
139
- delete fieldModels[this.fieldName];
140
- }
141
- },
142
133
  methods: {
143
134
  viewKey(item, index) {
144
135
  if (!item || !item.view) return '';
@@ -152,19 +143,6 @@ export default {
152
143
  const childViewLength = item.childViews ? item.childViews.length : 0;
153
144
  return `${item.view}_${index}_${childViewLength}`;
154
145
  },
155
- _initConditional(oldSpec) {
156
- // unreg old fieldName
157
- if (oldSpec && fieldModels[oldSpec.name] && oldSpec.name != this.spec.name) {
158
- delete fieldModels[oldSpec.name];
159
- }
160
-
161
- },
162
- setFieldModel(name, value) {
163
- fieldModels[name] = value;
164
- },
165
- getFieldModel(name) {
166
- return fieldModels[name];
167
- },
168
146
  // NOTE: Styles are dynamic, do not save it in $ready().
169
147
  $styles(spec) {
170
148
  const properties = spec || this.spec;
@@ -1,7 +1,7 @@
1
- import { fieldModels } from "../../composable/conditional";
1
+ import { getAllFormData } from "../../composable/form";
2
2
 
3
3
  export default {
4
- data: function() {
4
+ data: function () {
5
5
  return {
6
6
  importable: false,
7
7
  importSubmitUrl: null,
@@ -18,7 +18,7 @@ export default {
18
18
  });
19
19
  },
20
20
  rowSelected(sectionIndex, rowIndex) {
21
- return fieldModels[this.rowCheckId(sectionIndex, rowIndex)];
21
+ return getAllFormData()[this.rowCheckId(sectionIndex, rowIndex)];
22
22
  },
23
23
  selectedRowCount(section) {
24
24
  const sectionIndex = section.index;
@@ -57,7 +57,7 @@ export default {
57
57
  }
58
58
  },
59
59
  _submitEachRow(rows) {
60
- const url = Utils.type.string(this.importSubmitUrl)
60
+ const url = Utils.type.string(this.importSubmitUrl);
61
61
  if (!url) {
62
62
  return;
63
63
  }
@@ -96,7 +96,7 @@ export default {
96
96
  // Reset value so it will trigger again the next time the same file is selected.
97
97
  event.target.value = null;
98
98
  },
99
- _parseCsv: function(csvString) {
99
+ _parseCsv: function (csvString) {
100
100
  // https://stackoverflow.com/a/41563966/9970813
101
101
  let prevLetter = "",
102
102
  row = [""],
@@ -3,6 +3,11 @@ import launch from "../../utils/launch";
3
3
  import { htmlElement } from "../helper";
4
4
 
5
5
  export default defineComponent({
6
+ data() {
7
+ return {
8
+ key: Math.random().toString(36).slice(2, 7)
9
+ };
10
+ },
6
11
  computed: {
7
12
  properties() {
8
13
  return {
@@ -13,6 +18,18 @@ export default defineComponent({
13
18
  }
14
19
  },
15
20
  methods: {
21
+ handleMouseEnter() {
22
+ const properties = {
23
+ body: { childViews: [this.properties] },
24
+ key: this.key,
25
+ placement: this.spec.tooltip.placement || 'top',
26
+ styleClass: 'views-tooltip'
27
+ };
28
+ launch.popover.open(properties, this);
29
+ },
30
+ handleMouseLeave() {
31
+ launch.popover.close({ key: this.key });
32
+ },
16
33
  $mounted() {
17
34
  const tooltip = this.spec.tooltip;
18
35
 
@@ -20,26 +37,22 @@ export default defineComponent({
20
37
 
21
38
  this.initTooltip();
22
39
  },
23
- initTooltip() {
24
- const key = Math.random().toString(36).slice(2, 7);
40
+ $tearDown() {
41
+ const el = htmlElement(this);
42
+
43
+ if (el) {
44
+ el.removeEventListener('mouseenter', this.handleMouseEnter);
45
+ el.removeEventListener('mouseleave', this.handleMouseLeave);
46
+ }
25
47
 
26
- const handleMouseEnter = () => {
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);
34
- };
35
- const handleMouseLeave = () => {
36
- launch.popover.close({ key: key });
37
- };
38
48
 
49
+ this.handleMouseLeave();
50
+ },
51
+ initTooltip() {
39
52
  const el = htmlElement(this);
40
53
 
41
- el.addEventListener('mouseenter', handleMouseEnter);
42
- el.addEventListener('mouseleave', handleMouseLeave);
54
+ el.addEventListener('mouseenter', this.handleMouseEnter);
55
+ el.addEventListener('mouseleave', this.handleMouseLeave);
43
56
  }
44
57
  }
45
58
  });
@@ -31,14 +31,17 @@ export default {
31
31
  computed: {
32
32
  cssClasses: function () {
33
33
  const classes = this.$classes().concat("layouts-flow");
34
- // switch(this.spec.distribution) {
35
- // case 'fillEqually':
36
- // classes.push('layouts-horizontal--fill-equally')
37
- // break
38
- // case 'spaceEqually':
39
- // classes.push('layouts-horizontal--space-equally')
40
- // break
41
- // }
34
+
35
+ const configs = {
36
+ 'top': 'align-start',
37
+ 'middle': 'align-center',
38
+ 'bottom': 'align-end'
39
+ };
40
+
41
+ const alignClass = configs[this.spec.align] || 'align-start';
42
+
43
+ classes.push(alignClass);
44
+
42
45
  return classes;
43
46
  },
44
47
  cssStyles: function () {
@@ -15,6 +15,7 @@
15
15
  <script>
16
16
  import { onMounted, provide, ref } from "vue";
17
17
  import { useGlibForm } from "../composable/form";
18
+ import eventFiltering from "../../utils/eventFiltering";
18
19
 
19
20
  export default {
20
21
  props: {
@@ -138,7 +139,7 @@ export default {
138
139
  this.formCtx = { form: this.$refs.form };
139
140
  if (onChange) this.$executeOnChange();
140
141
  };
141
- this.formElement.onchange = () => onChangeHandler();
142
+ this.formElement.onchange = eventFiltering.debounce(onChangeHandler, 300);
142
143
 
143
144
  this.formElement.oninput = (event) => this.glibForm.updateDirtyState(event);
144
145
  }
@@ -24,7 +24,16 @@ export default {
24
24
  },
25
25
  methods: {
26
26
  classes: function () {
27
- return this.$classes().concat("layouts-split");
27
+ // This class is provided by Vuetify: https://vuetifyjs.com/en/styles/flex/#flex-align.
28
+ const configs = {
29
+ 'top': 'align-start',
30
+ 'middle': 'align-center',
31
+ 'bottom': 'align-end'
32
+ };
33
+
34
+ const alignClass = configs[this.spec.align] || 'align-start';
35
+
36
+ return this.$classes().concat(["layouts-split", alignClass]);
28
37
  }
29
38
  }
30
39
  };
package/nav/dialog.vue CHANGED
@@ -10,7 +10,7 @@
10
10
  </h1>
11
11
  <div v-else></div>
12
12
 
13
- <v-btn v-if="!spec.disableCloseButton" size="small" icon @click="close" variant="text">
13
+ <v-btn v-if="!spec.disableCloseButton" class="close-btn" size="small" icon @click="close" variant="text">
14
14
  <v-icon>close</v-icon>
15
15
  </v-btn>
16
16
  </div>
@@ -231,7 +231,6 @@ export default {
231
231
  }
232
232
  };
233
233
  </script>
234
-
235
234
  <style lang="scss">
236
235
  .dialog-message {
237
236
  padding: 16px 20px;
@@ -247,6 +246,12 @@ export default {
247
246
  width: 100%;
248
247
  z-index: 999;
249
248
  padding: 8px 16px;
249
+ position: relative;
250
+
251
+ .close-btn {
252
+ position: fixed;
253
+ right: 16px;
254
+ }
250
255
  }
251
256
 
252
257
  .dialog-absolute {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "glib-web",
3
- "version": "4.30.1",
3
+ "version": "4.32.0",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/store.js CHANGED
@@ -42,7 +42,6 @@ export const closeAllPopover = () => {
42
42
  popovers.value.forEach((popover) => popover.close());
43
43
  popovers.value = [];
44
44
  vueApp.sheet.show = false;
45
-
46
45
  };
47
46
 
48
47
  export const glibevent = reactive({
@@ -48,6 +48,10 @@ export default class LaunchDialog {
48
48
  Utils.type.ifObject(dialogs.value.last(), dialog => {
49
49
  dialog.reload(properties);
50
50
  });
51
+
52
+ nextTick(() => {
53
+ Action.execute(properties.onReload, component);
54
+ });
51
55
  }
52
56
 
53
57
  static close(properties, component) {
@@ -1,12 +0,0 @@
1
- import { reactive, watchEffect } from "vue";
2
- import jsonLogic from 'json-logic-js';
3
-
4
- // TODO: support multiple form
5
- const fieldModels = reactive({});
6
-
7
- const watchFieldModels = (logic, cb) => watchEffect(() => {
8
- const value = jsonLogic.apply(logic, fieldModels);
9
- cb(value);
10
- });
11
-
12
- export { fieldModels, watchFieldModels };