glib-web 3.2.0 → 3.4.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.
@@ -3,6 +3,7 @@
3
3
  export default class {
4
4
  execute(properties, component) {
5
5
  const spec = Object.assign({}, properties, {
6
+ disableCloseButton: true,
6
7
  buttons: [
7
8
  {
8
9
  text: "OK",
@@ -22,7 +22,7 @@ export default class {
22
22
  Utils.type.ifString(spec.historyUrl, historyUrl => {
23
23
  const cleanUrl = Utils.url.htmlUrl(historyUrl);
24
24
  if (cleanUrl !== currentUrl) {
25
- const data = Object.assign({}, window.vueApp.page, {
25
+ const data = Object.assign({}, page, {
26
26
  replayGetResponse: page.onResponse
27
27
  });
28
28
 
@@ -1,5 +1,7 @@
1
1
  <template>
2
- <area-chart :style="genericStyles()" :class="$classes()" :data="series"></area-chart>
2
+ <div>
3
+ <area-chart :style="genericStyles()" :class="$classes()" :data="series" :colors="color">></area-chart>
4
+ </div>
3
5
  </template>
4
6
 
5
7
  <script setup>
@@ -7,6 +9,6 @@ import { computed } from 'vue';
7
9
  import { singleDataSeries } from './series';
8
10
 
9
11
  const { spec } = defineProps({ spec: Object })
10
-
12
+ const color = spec.colors == null ? '' : [spec.colors]
11
13
  const series = computed(() => singleDataSeries(spec.dataSeries))
12
14
  </script>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <column-chart :data="series" :stacked="spec.stacked"></column-chart>
2
+ <column-chart :data="series" :stacked="spec.stacked" :colors="color"></column-chart>
3
3
  </template>
4
4
 
5
5
  <script setup>
@@ -8,6 +8,7 @@ import { computed } from 'vue';
8
8
  import { multipleDataSeries } from './series';
9
9
 
10
10
  const { spec } = defineProps({ spec: Object })
11
+ const color = spec.colors == null ? '' : spec.colors
11
12
  const series = computed(() => multipleDataSeries(spec.dataGroups))
12
13
 
13
14
  </script>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <line-chart :style="genericStyles()" :class="$classes()" :data="series"></line-chart>
2
+ <line-chart :style="genericStyles()" :class="$classes()" :data="series" :colors="color"></line-chart>
3
3
  </template>
4
4
 
5
5
  <script setup>
@@ -7,6 +7,7 @@ import { computed } from 'vue';
7
7
  import { multipleDataSeries } from './series';
8
8
 
9
9
  const { spec } = defineProps({ spec: Object })
10
+ const color = spec.colors == null ? '' : spec.colors
10
11
 
11
12
  const series = computed(() => multipleDataSeries(spec.dataSeries))
12
13
  </script>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <pie-chart :style="genericStyles()" :class="$classes()" :data="series"></pie-chart>
2
+ <pie-chart :style="genericStyles()" :class="$classes()" :data="series" :colors="color"></pie-chart>
3
3
  </template>
4
4
 
5
5
  <script setup>
@@ -7,6 +7,7 @@ import { computed } from 'vue';
7
7
  import { singleDataSeries } from './series';
8
8
 
9
9
  const { spec } = defineProps({ spec: Object })
10
+ const color = spec.colors == null ? '' : spec.colors
10
11
  const series = computed(() => singleDataSeries(spec.dataSeries))
11
12
 
12
13
  </script>
@@ -148,7 +148,7 @@ export default {
148
148
  "views-hr": Hr,
149
149
  "views-spacer": Spacer,
150
150
 
151
- "views-map": Map,
151
+ // "views-map": Map,
152
152
  "views-tabBar": TabBar,
153
153
  "views-calendar": Calendar,
154
154
  "views-shareButton": ShareButton,
@@ -221,10 +221,10 @@ export default {
221
221
  },
222
222
  computed: {
223
223
  menter() {
224
- return this.spec.onMouseEnter ? 'mouseenter' : null
224
+ return this.spec.onMouseEnter ? 'mouseenter' : null;
225
225
  },
226
226
  mleave() {
227
- return this.spec.onMouseLeave ? 'mouseleave' : null
227
+ return this.spec.onMouseLeave ? 'mouseleave' : null;
228
228
  }
229
229
  },
230
230
  watch: {
@@ -275,7 +275,7 @@ export default {
275
275
  return false;
276
276
  },
277
277
  handlePopover(spec) {
278
- GLib.action.execute(spec, this)
278
+ GLib.action.execute(spec, this);
279
279
  }
280
280
  },
281
281
  };
@@ -0,0 +1,11 @@
1
+ import { reactive, watchEffect } from "vue";
2
+ import jsonLogic from 'json-logic-js';
3
+
4
+ const fieldModels = reactive({});
5
+
6
+ const watchFieldModels = (logic, cb) => watchEffect(() => {
7
+ const value = jsonLogic.apply(logic, fieldModels);
8
+ cb(value);
9
+ });
10
+
11
+ export { fieldModels, watchFieldModels };
@@ -0,0 +1,161 @@
1
+ <template>
2
+ <div :style="$styles()" :class="$classes()">
3
+ <div v-if="spec.type === 'text'">
4
+ <input v-for="(digit, index) in digits" ref="input" :key="index" v-model="val[index]" type="text" maxlength="1"
5
+ class="otp-input" @input="handleInput($event, index)" @keydown="handleKeydown($event, index)"
6
+ @paste="handlePaste($event, index)" @focus="handleFocus($event, index)" @blur="handleBlur($event, index)"
7
+ @click="handleClick($event)" />
8
+ <label class="hint">{{ spec.hint }}</label>
9
+ </div>
10
+ <div v-else>
11
+ <input v-for="(digit, index) in digits" :key="index" ref="input" :value="val[index]" type="number"
12
+ oninput="javascript: if (this.value.length > this.maxLength) this.value = this.value.slice(0, this.maxLength);"
13
+ maxlength="1" class="otp-input" @input="handleInput($event, index)" @keydown="handleKeydown($event, index)"
14
+ @paste="handlePaste($event, index)" @focus="handleFocus($event, index)" @blur="handleBlur($event, index)"
15
+ @click="handleClick($event)" />
16
+ <label class="hint">{{ spec.hint }}</label>
17
+ </div>
18
+ <input type="hidden" :name="fieldName" v-model="fieldModel" />
19
+ </div>
20
+ </template>
21
+
22
+ <script>
23
+ import bus from '../../utils/eventBus'
24
+
25
+ export default {
26
+ props: {
27
+ spec: { type: Object, required: true }
28
+ },
29
+ data() {
30
+ return {
31
+ otp: (this.spec.value || '').toString().split(""),
32
+ alignment: this.spec.align === null ? 'left' : this.spec.align
33
+ }
34
+ },
35
+ computed: {
36
+ otpValue() {
37
+ if (!this.otp) return ''
38
+
39
+ // concatenate the digits into a single string
40
+ return this.otp.join("");
41
+ },
42
+ digits() {
43
+ return this.spec.length
44
+ },
45
+ val() {
46
+ const field = this.fieldModel == null ? [] : this.fieldModel.toString().split("")
47
+
48
+ if (this.alignment === 'right') {
49
+ if (this.spec.valueIf == null) {
50
+ if (this.digits > this.otp.length && this.otp.length != 0) {
51
+ const results = []
52
+ for (let i = 0; i < (this.digits - this.otp.length); i++) {
53
+ results.push('')
54
+ }
55
+ return Reflect.apply(Array.prototype.concat, results, this.otp)
56
+ } else {
57
+ return field
58
+ }
59
+ } else {
60
+ return field
61
+ }
62
+ } else {
63
+ if (this.spec.valueIf == null) {
64
+ return this.otp
65
+ } else {
66
+ return field
67
+ }
68
+ }
69
+ },
70
+ },
71
+ methods: {
72
+ created() {
73
+ this.fieldModel = this.otpValue;
74
+ },
75
+ handleInput(event, index) {
76
+ if (event.target.value.length === 1) {
77
+ this.focusNext(index);
78
+ }
79
+ this.val[index] = event.target.value;
80
+ this.fieldModel = this.otp.join("");
81
+ },
82
+ handleKeydown(event, index) {
83
+ if (event.key === "Backspace") {
84
+ event.preventDefault();
85
+ if (this.otp[index] !== "") {
86
+ this.otp[index] = ""
87
+ } else {
88
+ this.focusPrevious(index);
89
+ this.otp[index - 1] = ""
90
+ }
91
+ }
92
+ },
93
+ handlePaste(event, index) {
94
+ event.preventDefault();
95
+ const pasted = event.clipboardData
96
+ .getData("text")
97
+ .slice(0, this.digits - index);
98
+ for (let i = 0; i < pasted.length; i++) {
99
+ this.$set(this.otp, index + i, pasted[i]);
100
+ }
101
+ this.focusNext(index + pasted.length - 1);
102
+ },
103
+ handleFocus(event, index) {
104
+ if (index > 0 && this.otp[index - 1] === "") {
105
+ this.focusPrevious(index);
106
+ }
107
+ },
108
+ handleBlur(event, index) {
109
+ if (this.otp.filter(digit => digit === "").length === 0) {
110
+ bus.$emit("complete", this.otp.join(""));
111
+ }
112
+ },
113
+ focusNext(index) {
114
+ if (index < this.digits - 1) {
115
+ this.$refs.input[index + 1].focus();
116
+ }
117
+ },
118
+ focusPrevious(index) {
119
+ if (index > 0) {
120
+ this.$refs.input[index - 1].focus();
121
+ }
122
+ },
123
+ handleClick(event) {
124
+ event.target.select();
125
+ }
126
+ }
127
+ };
128
+ </script>
129
+ <style scoped>
130
+ .otp-input {
131
+ border: 1px solid #47495f;
132
+ text-align: center;
133
+ width: 50px;
134
+ height: 50px;
135
+ border-radius: 4px;
136
+ font-size: 24px;
137
+ margin: 8px;
138
+ }
139
+
140
+ /* Chrome, Safari, Edge, Opera */
141
+ input::-webkit-outer-spin-button,
142
+ input::-webkit-inner-spin-button {
143
+ -webkit-appearance: none;
144
+ margin: 0;
145
+ }
146
+
147
+ /* Firefox */
148
+ input[type="number"] {
149
+ -moz-appearance: textfield;
150
+ }
151
+
152
+ .hint {
153
+ display: none;
154
+ }
155
+
156
+ input:focus~.hint,
157
+ input:focus-visible~.hint {
158
+ display: block;
159
+ padding: 8px 16px;
160
+ }
161
+ </style>
@@ -4,62 +4,29 @@
4
4
  It's safer to manually implement styling than adding another dependency such as vuetify-google-autocomplete,
5
5
  which behaves differently and needs to be actively maintained.
6
6
  -->
7
- <div
8
- class="v-input theme--light v-text-field v-text-field--enclosed v-text-field--outlined"
9
- >
7
+ <div class="v-input theme--light v-text-field v-text-field--enclosed v-text-field--outlined">
10
8
  <div class="v-input__control">
11
9
  <div class="v-input__slot">
12
10
  <fieldset aria-hidden="true">
13
11
  <legend :style="`width: ${labelWidth}px;`"><span>​</span></legend>
14
12
  </fieldset>
15
13
  <div class="v-text-field__slot">
16
- <label
17
- ref="label"
18
- class="v-label v-label--active theme--light"
19
- style="left: 0px; right: auto; position: absolute; font-size: 16px;"
20
- >{{ spec.label }}</label
21
- >
14
+ <label ref="label" class="v-label v-label--active theme--light"
15
+ style="left: 0px; right: auto; position: absolute; font-size: 16px;">{{ spec.label }}</label>
22
16
  <!-- See https://developers.google.com/places/web-service/autocomplete#place_types for autocompleteOptions -->
23
- <gmap-autocomplete
24
- :name="spec.name"
25
- :placeholder="spec.placeholder"
26
- :value="address"
27
- :options="spec.autocompleteOptions"
28
- @input="onAddressChanged"
29
- @place_changed="onPlaceChanged"
30
- />
17
+ <gmap-autocomplete :name="spec.name" :placeholder="spec.placeholder" :value="address"
18
+ :options="spec.autocompleteOptions" @input="onAddressChanged" @place_changed="onPlaceChanged" />
31
19
  </div>
32
20
  </div>
33
21
  </div>
34
22
  </div>
35
23
 
36
- <gmap-map
37
- ref="map"
38
- :center="{ lat: 0, lng: 0 }"
39
- :zoom="13"
40
- class="mt-3 mb-3 map"
41
- map-type-id="roadmap"
42
- :style="mapStyles"
43
- @idle="onActionCompleted"
44
- >
45
- <gmap-marker
46
- :position="markerPos"
47
- :clickable="true"
48
- :draggable="true"
49
- @drag="onDrag"
50
- @dragend="onDrag"
51
- />
24
+ <gmap-map ref="map" :center="{ lat: 0, lng: 0 }" :zoom="13" class="mt-3 mb-3 map" map-type-id="roadmap"
25
+ :style="mapStyles" @idle="onActionCompleted">
26
+ <gmap-marker :position="markerPos" :clickable="true" :draggable="true" @drag="onDrag" @dragend="onDrag" />
52
27
  </gmap-map>
53
- <glib-component
54
- v-if="latitudeField"
55
- ref="latitudeView"
56
- :spec="latitudeField"
57
- />
58
- <glib-component
59
- v-if="longitudeField"
60
- ref="longitudeView"
61
- :spec="longitudeField"
62
- />
28
+ <glib-component v-if="latitudeField" ref="latitudeView" :spec="latitudeField" />
29
+ <glib-component v-if="longitudeField" ref="longitudeView" :spec="longitudeField" />
63
30
  <glib-component v-if="zoomField" ref="zoomView" :spec="zoomField" />
64
31
  </v-container>
65
32
  </template>
@@ -97,8 +64,8 @@ export default {
97
64
  mapStyles() {
98
65
  return this.longitude != null && this.latitude != null
99
66
  ? {
100
- display: "block"
101
- }
67
+ display: "block"
68
+ }
102
69
  : { display: "none" };
103
70
  }
104
71
  },
@@ -112,11 +79,11 @@ export default {
112
79
  this.centerInitial();
113
80
 
114
81
  setTimeout(() => {
115
- this.latitudeView = this.$refs.latitudeView.$children[0];
116
- this.longitudeView = this.$refs.longitudeView.$children[0];
82
+ this.latitudeView = this.$refs.latitudeView;
83
+ this.longitudeView = this.$refs.longitudeView;
117
84
 
118
85
  if (this.$refs.zoomView) {
119
- this.zoomView = this.$refs.zoomView.$children[0];
86
+ this.zoomView = this.$refs.zoomView;
120
87
  }
121
88
 
122
89
  const labelView = this.$refs.label;
@@ -139,10 +106,10 @@ export default {
139
106
  }
140
107
  },
141
108
  setLocation(latitudeValue, longitudeValue, dropPin) {
142
- this.$refs.map.$gmapApiPromiseLazy().then(() => {
109
+ this.$gmapApiPromiseLazy().then(() => {
143
110
  // eslint-disable-next-line no-undef
144
111
  const location = new google.maps.LatLng(latitudeValue, longitudeValue);
145
- this.$refs.map.$mapObject.setCenter(location);
112
+ this.$refs.map.mapInstance.setCenter(location);
146
113
 
147
114
  if (dropPin) {
148
115
  this.setMarkerPosition(location);
@@ -161,14 +128,15 @@ export default {
161
128
 
162
129
  const location = place.geometry.location;
163
130
  this.setMarkerPosition(location);
164
- this.$refs.map.$mapObject.setCenter(location);
131
+
132
+ this.$refs.map.mapInstance.setCenter(location);
165
133
  },
166
134
  onDrag(e) {
167
135
  this.setMarkerPosition(e.latLng);
168
136
  },
169
137
  onActionCompleted() {
170
138
  if (this.zoomView) {
171
- this.zoomView.fieldModel = this.$refs.map.$mapObject.zoom;
139
+ this.zoomView.fieldModel = this.$refs.map.mapInstance.zoom;
172
140
  }
173
141
  },
174
142
  onAddressChanged(e) {
@@ -1,130 +1,24 @@
1
1
  <template>
2
- <div>
3
- <div v-if="spec.type === 'text'">
4
- <input v-for="(digit, index) in digits" ref="input" :key="index" v-model="otp[index]" type="text" maxlength="1"
5
- class="otp-input" @input="handleInput($event, index)" @keydown="handleKeydown($event, index)"
6
- @paste="handlePaste($event, index)" @focus="handleFocus($event, index)" @blur="handleBlur($event, index)"
7
- @click="handleClick($event)" />
8
- <label class="hint">{{ spec.hint }}</label>
9
- </div>
10
- <div v-else>
11
- <input v-for="(digit, index) in digits" ref="input" :key="index" v-model="otp[index]" type="number"
12
- oninput="javascript: if (this.value.length > this.maxLength) this.value = this.value.slice(0, this.maxLength);"
13
- maxlength="1" class="otp-input" @input="handleInput($event, index)" @keydown="handleKeydown($event, index)"
14
- @paste="handlePaste($event, index)" @focus="handleFocus($event, index)" @blur="handleBlur($event, index)"
15
- @click="handleClick($event)" />
16
- <label class="hint">{{ spec.hint }}</label>
17
- </div>
18
- <input type="hidden" :name="spec.name" :value="otpValue" />
19
- </div>
2
+ <fields-otp :spec="spec" />
20
3
  </template>
21
4
 
22
5
  <script>
23
- import bus from '../../utils/eventBus'
6
+ import OtpField from "./_otp.vue";
24
7
 
25
8
  export default {
9
+ components: {
10
+ // Need to start with `fields-` to enable jsonlogic in `styles.js#_linkFieldModels`
11
+ "fields-otp": OtpField,
12
+ },
26
13
  props: {
27
- spec: { type: Object, required: true }
14
+ spec: { type: Object, required: true },
28
15
  },
29
16
  data() {
30
17
  return {
31
- otp: (this.spec.value || '').toString().split("")
32
- }
33
- },
34
- computed: {
35
- otpValue() {
36
- if (!this.otp) return ''
37
-
38
- // concatenate the digits into a single string
39
- return this.otp.join("");
40
- },
41
- digits() {
42
- return this.spec.length
43
- },
18
+ value: null,
19
+ };
44
20
  },
45
- methods: {
46
- handleInput(event, index) {
47
- if (event.target.value.length === 1) {
48
- this.focusNext(index);
49
- }
50
- },
51
- handleKeydown(event, index) {
52
- if (event.key === "Backspace") {
53
- event.preventDefault();
54
- if (this.otp[index] !== "") {
55
- this.otp[index] = ""
56
- } else {
57
- this.focusPrevious(index);
58
- this.otp[index - 1] = ""
59
- }
60
- }
61
- },
62
- handlePaste(event, index) {
63
- event.preventDefault();
64
- const pasted = event.clipboardData
65
- .getData("text")
66
- .slice(0, this.digits - index);
67
- for (let i = 0; i < pasted.length; i++) {
68
- this.$set(this.otp, index + i, pasted[i]);
69
- }
70
- this.focusNext(index + pasted.length - 1);
71
- },
72
- handleFocus(event, index) {
73
- if (index > 0 && this.otp[index - 1] === "") {
74
- this.focusPrevious(index);
75
- }
76
- },
77
- handleBlur(event, index) {
78
- if (this.otp.filter(digit => digit === "").length === 0) {
79
- bus.$emit("complete", this.otp.join(""));
80
- }
81
- },
82
- focusNext(index) {
83
- if (index < this.digits - 1) {
84
- this.$refs.input[index + 1].focus();
85
- }
86
- },
87
- focusPrevious(index) {
88
- if (index > 0) {
89
- this.$refs.input[index - 1].focus();
90
- }
91
- },
92
- handleClick(event) {
93
- event.target.select();
94
- }
95
- }
96
21
  };
97
22
  </script>
98
- <style scoped>
99
- .otp-input {
100
- border: 1px solid #47495f;
101
- text-align: center;
102
- width: 50px;
103
- height: 50px;
104
- border-radius: 4px;
105
- font-size: 24px;
106
- margin: 8px;
107
- }
108
-
109
- /* Chrome, Safari, Edge, Opera */
110
- input::-webkit-outer-spin-button,
111
- input::-webkit-inner-spin-button {
112
- -webkit-appearance: none;
113
- margin: 0;
114
- }
115
-
116
- /* Firefox */
117
- input[type="number"] {
118
- -moz-appearance: textfield;
119
- }
120
-
121
- .hint {
122
- display: none;
123
- }
124
23
 
125
- input:focus~.hint,
126
- input:focus-visible~.hint {
127
- display: block;
128
- padding: 8px 16px;
129
- }
130
- </style>
24
+ <style scoped></style>
@@ -28,6 +28,14 @@ export default {
28
28
  }
29
29
  },
30
30
  created() {
31
+ // watch components/update
32
+ if (this.spec) {
33
+ this.$watch(
34
+ () => Object.keys(this.spec).map((specKey) => this.spec[specKey]),
35
+ () => this._ready()
36
+ );
37
+ }
38
+
31
39
  this.$created();
32
40
  },
33
41
  mounted() {
@@ -37,7 +45,7 @@ export default {
37
45
  },
38
46
  beforeUpdate() {
39
47
  if (vueApp.isStale) {
40
- this._mountedUrl = null
48
+ this._mountedUrl = null;
41
49
  }
42
50
 
43
51
  if (!this._renderingTheSamePage()) {
@@ -69,7 +77,7 @@ export default {
69
77
  return UrlUtils.htmlUrl(onClick.url);
70
78
  }
71
79
  }
72
- return null;
80
+ return '';
73
81
  },
74
82
  $onClick: function (explicitEvent, spec) {
75
83
  const properties = spec || this.spec;
@@ -1,56 +1,34 @@
1
1
  import { vueApp } from "../../store";
2
2
  import Hash from "../../utils/hash";
3
+ import { fieldModels, watchFieldModels } from "../composable/conditional";
3
4
 
4
- import jsonLogic from 'json-logic-js';
5
-
6
- // jsonLogic.add_operation("add_ms", function(a, b) {
7
- // if (Utils.type.isString(a)) {
8
- // const newValue = new Date(new Date(a).getTime() + b * 1000);
9
- // return newValue.toISOString();
10
- // }
11
- // return null;
12
- // });
13
5
  export default {
14
6
  data: function () {
15
7
  return {
16
- _fieldModels: {},
17
8
  fieldName: null,
18
9
  fieldModel: null,
19
-
10
+ _show: true,
20
11
  // Some components do not support null or empty string value, so we need to use an intermediary value.
21
12
  // See https://github.com/vuetifyjs/vuetify/issues/8876
22
13
  vuetifyEmptyString: "<EMPTY_STRING>"
23
14
  };
24
15
  },
25
- computed: {
26
- display() {
27
- const conditions = this.spec.showIf;
28
- if (conditions) {
29
- return jsonLogic.apply(conditions, this.$data._fieldModels)
30
- ? this.$displayValue()
31
- : "none";
32
- } else {
33
- return this.$displayValue();
34
- }
35
- }
36
- },
37
16
  watch: {
38
17
  fieldModel: function (val, oldVal) {
39
18
  if (val === this.vuetifyEmptyString) {
40
19
  val = "";
41
20
  }
42
21
 
43
- // Make the field property observable for dynamic showing/hiding
44
- Object.assign(this.$data._fieldModels, { [this.fieldName]: this.$internalizeValue(val) })
22
+ Object.assign(fieldModels, { [this.fieldName]: this.$internalizeValue(val) });
45
23
 
46
24
  this._checkDirtyState(val, oldVal);
47
25
  },
48
26
  spec: {
49
27
  handler(spec, oldSpec) {
50
28
  if (spec) {
51
- let valueChanged = true
29
+ let valueChanged = true;
52
30
  if (oldSpec && oldSpec.value === spec.value) {
53
- valueChanged = false
31
+ valueChanged = false;
54
32
  }
55
33
  this._linkFieldModels(valueChanged);
56
34
  }
@@ -58,6 +36,22 @@ export default {
58
36
  immediate: true
59
37
  }
60
38
  },
39
+ // watch showIf and valueIf
40
+ mounted() {
41
+ if (this.spec && this.spec.valueIf) {
42
+ watchFieldModels(this.spec.valueIf, (value) => this.fieldModel = this.$sanitizeValue(this.$externalizeValue(value)));
43
+ }
44
+
45
+ if (this.spec && this.spec.showIf) {
46
+ watchFieldModels(this.spec.showIf, (value) => {
47
+ if (value) {
48
+ this._show = true;
49
+ } else {
50
+ this._show = false;
51
+ }
52
+ });
53
+ }
54
+ },
61
55
  methods: {
62
56
  // TODO: Deprecated
63
57
  genericStyles(spec) {
@@ -132,13 +126,11 @@ export default {
132
126
  val => (styles["height"] = val)
133
127
  );
134
128
 
135
- // Utils.type.ifObject(
136
- // properties.onClick,
137
- // () => (styles["cursor"] = "pointer")
138
- // );
139
-
140
- // This is the main reason styles have to be used in a dynamic way.
141
- this._updateDisplay(styles, properties);
129
+ if (this._show) {
130
+ styles['display'] = this.$displayValue();
131
+ } else {
132
+ styles['display'] = 'none';
133
+ }
142
134
 
143
135
  return new Hash(styles);
144
136
  },
@@ -166,18 +158,6 @@ export default {
166
158
  // });
167
159
  // return color;
168
160
  // },
169
- _updateDisplay(styles, properties) {
170
- Utils.type.ifObject(
171
- properties.showIf,
172
- () => {
173
- styles["display"] = this.display
174
- }
175
- );
176
- Utils.type.ifObject(properties.valueIf, logic => {
177
- const newValue = jsonLogic.apply(logic, this.$data._fieldModels);
178
- this.fieldModel = this._sanitizeValue(this.$externalizeValue(newValue));
179
- });
180
- },
181
161
  $displayValue() {
182
162
  // return "block"; Deprecated because it causes problems for multiple rows components
183
163
  // return "inherit"; // To be overridden
@@ -204,11 +184,6 @@ export default {
204
184
 
205
185
  const isField = name && name.startsWith("fields-");
206
186
  if (hasCondition || isField) {
207
- const form = this.$closest("panels-form");
208
- if (form != null) {
209
- this.$data._fieldModels = form.$data._fieldModels;
210
- }
211
-
212
187
  // Has to be executed before $ready(). This executes regardless of whether a form is found because fields
213
188
  // may be used without a form.
214
189
  this.fieldName = this.spec.name;
package/index.js CHANGED
@@ -7,42 +7,49 @@ import { useTheme } from "vuetify";
7
7
  import '@vueup/vue-quill/dist/vue-quill.snow.css';
8
8
 
9
9
  // lib for deep merge
10
- import merge from 'lodash.merge'
10
+ import merge from 'lodash.merge';
11
11
 
12
12
  const Vue = createApp({
13
13
  data() {
14
14
  return {
15
15
  vueApp,
16
16
  themeConfig: useTheme()
17
- }
17
+ };
18
18
  },
19
19
  created() {
20
- this.themeConfig.themes = merge(this.themeConfig.themes, settings.themes)
20
+ this.themeConfig.themes = merge(this.themeConfig.themes, settings.themes);
21
21
  },
22
22
  render: function () {
23
23
  return h(App, { page: this.$data.vueApp.page });
24
24
  }
25
- })
25
+ });
26
+
27
+
28
+ Vue.use(vuetify);
29
+
30
+ import { GmapVuePlugin, components } from "@gmap-vue/v3";
31
+ Vue.use(GmapVuePlugin, {
32
+ load: {
33
+ key: import.meta.env.GMAPS_API_KEY,
34
+ libraries: "places"
35
+ }
36
+ });
26
37
 
38
+ const { MapLayer, Autocomplete, Marker } = components;
39
+ Vue.component('gmap-map', MapLayer);
40
+ Vue.component('gmap-marker', Marker);
41
+ Vue.component('gmap-autocomplete', Autocomplete);
27
42
 
28
- Vue.use(vuetify)
29
43
 
30
- // import * as VueGoogleMaps from "vue2-google-maps";
31
- // Vue.use(VueGoogleMaps, {
32
- // load: {
33
- // key: process.env.GMAPS_API_KEY,
34
- // libraries: "places"
35
- // }
36
- // });
37
44
 
38
45
  import "./styles/test.scss";
39
46
  import "./styles/test.sass";
40
47
 
41
48
 
42
- import VueChartkick from 'vue-chartkick'
43
- import 'chartkick/chart.js'
49
+ import VueChartkick from 'vue-chartkick';
50
+ import 'chartkick/chart.js';
44
51
 
45
- Vue.use(VueChartkick)
52
+ Vue.use(VueChartkick);
46
53
 
47
54
  // import VueAnalytics from 'vue-analytics'
48
55
  // // TODO: Avoid hardcoding
@@ -153,10 +160,10 @@ Vue.use(updatableComponent);
153
160
  import { vueApp } from "./store";
154
161
 
155
162
  document.addEventListener("DOMContentLoaded", () => {
156
- Vue.mount("#app")
163
+ Vue.mount("#app");
157
164
  });
158
165
 
159
- export { Vue, settings, vueApp }
166
+ export { Vue, settings, vueApp };
160
167
 
161
168
  // The above code uses Vue without the compiler, which means you cannot
162
169
  // use Vue to target elements in your existing html templates. You would
package/nav/dialog.vue CHANGED
@@ -4,17 +4,18 @@
4
4
  <v-card :style="hamburgerStyles" class="hamburger">
5
5
 
6
6
  <panels-responsive v-if="header" :spec="header" />
7
- <div class="dialog-title flex flex-wrap justify-between" v-if="title">
8
- <div class="theme--light v-subheader">
7
+ <div class="dialog-title d-flex flex-wrap justify-space-between align-center" style="width: 100%; height: 100%;">
8
+ <h3 v-if="title">
9
9
  {{ title }}
10
- </div>
11
- </div>
10
+ </h3>
12
11
 
13
- <div class="dialogs-body relative">
14
- <v-btn size="small" v-if="!disableCloseButton" text icon @click="close" class="mr-3 mt-2 close-btn"
12
+ <v-btn size="small" v-if="!disableCloseButton" text icon @click="close" class="mr-4 mt-4 close-btn"
15
13
  style="z-index: 999;" variant="flat">
16
14
  <v-icon>close</v-icon>
17
15
  </v-btn>
16
+ </div>
17
+
18
+ <div class="dialogs-body relative">
18
19
  <component :is="containerComponent" :spec="formSpec">
19
20
  <div v-if="message" class="dialog-message">
20
21
  <common-message :spec="{ message: message }" />
@@ -94,14 +95,13 @@ export default {
94
95
  return null;
95
96
  }
96
97
  },
97
- // watch: {
98
- // model: function (val, oldVal) {
99
- // if (!val) {
100
- // // this.$destroy();
101
- // this.$.appContext.app.unmount()
102
- // }
103
- // },
104
- // },
98
+ watch: {
99
+ model: function (val, oldVal) {
100
+ if (!val) {
101
+ this.stack.remove(this);
102
+ }
103
+ },
104
+ },
105
105
  methods: {
106
106
  $mounted() {
107
107
  window.addEventListener(
@@ -122,10 +122,10 @@ export default {
122
122
  });
123
123
  },
124
124
  // TODO: This doesn't get called anymore after Vue3 upgrade.
125
- $tearDown() {
126
- console.debug("Dialog destroyed");
127
- this.stack.remove(this);
128
- },
125
+ // $tearDown() {
126
+ // console.debug("Dialog destroyed");
127
+ // this.stack.remove(this);
128
+ // },
129
129
  close() {
130
130
  if (Utils.http.proceedEvenWhenDirty()) {
131
131
  this.model = false;
package/nav/drawer.vue CHANGED
@@ -4,8 +4,8 @@
4
4
 
5
5
  <div :class="cssClasses" :style="style">
6
6
  <v-navigation-drawer v-model="state" app :dark="cssClasses.includes('dark') || null"
7
- :permanent="cssClasses.includes('permanent')" :expand-on-hover="expandOnHover()" v-model:mini-variant="shrunk"
8
- @input="updateState">
7
+ :permanent="mdAndUp || cssClasses.includes('permanent')" :expand-on-hover="expandOnHover()"
8
+ v-model:mini-variant="shrunk" @input="updateState">
9
9
 
10
10
  <template v-if="spec.header">
11
11
  <panels-responsive :spec="spec.header" />
@@ -39,6 +39,7 @@
39
39
  import NavDrawerButton from "./drawerButton.vue";
40
40
  import NavDrawerLabel from "./drawerLabel.vue";
41
41
  import bus from "../utils/eventBus";
42
+ import { useDisplay } from "vuetify";
42
43
 
43
44
  export default {
44
45
  components: {
@@ -51,6 +52,11 @@ export default {
51
52
  trigger: { type: [Object, Date], default: null },
52
53
  permanent: { type: Boolean, required: true },
53
54
  },
55
+ setup() {
56
+ const { mdAndUp } = useDisplay();
57
+
58
+ return { mdAndUp };
59
+ },
54
60
  data: function () {
55
61
  return {
56
62
  // Using null as the starting value for its v-model will initialize the drawer as closed on mobile and as open on desktop.
package/nav/snackbar.vue CHANGED
@@ -1,22 +1,20 @@
1
1
  <template>
2
- <v-snackbar v-model="show" :location="spec.location" :color="color" :timeout="spec.timeout">
2
+ <v-snackbar :vertical="vertical" v-model="show" :location="spec.location" :color="color" :timeout="spec.timeout"
3
+ :variant="variant">
3
4
  <common-message :spec="spec" />
4
5
 
5
- <v-btn v-for="(button, index) in spec.buttons" variant="text" @click="click(button, $event)">
6
- <span style="color: whitesmoke">{{ button.text }}</span>
7
- </v-btn>
6
+ <template v-slot:actions v-if="spec.buttons">
7
+ <v-btn v-for="(button, index) in spec.buttons" :key="index" :color="color" :variant="variant"
8
+ @click="click(button, $event)">
9
+ <span :class="`text-${color}`">{{ button.text }}</span>
10
+ </v-btn>
11
+ </template>
8
12
 
9
- <!-- <v-btn
10
- flat
11
- @click="close"
12
- >
13
- Close
14
- </v-btn> -->
15
13
  </v-snackbar>
16
14
  </template>
17
15
 
18
16
  <script>
19
- import { colors } from '../utils/constant';
17
+ import { determineColor, determineVariant } from '../utils/constant';
20
18
 
21
19
  export default {
22
20
  props: {
@@ -25,28 +23,17 @@ export default {
25
23
  data: function () {
26
24
  return {
27
25
  show: true,
28
- color: null,
29
- indicator: false
26
+ color: 'primary',
27
+ variant: 'outlined',
28
+ indicator: false,
29
+ vertical: false
30
30
  };
31
31
  },
32
- // watch: {
33
- // model: function (val, oldVal) {
34
- // if (!val) {
35
- // // this.$destroy();
36
- // this.$.appContext.app.unmount()
37
- // }
38
- // },
39
- // },
40
32
  methods: {
41
33
  $ready() {
42
- const vm = this;
43
- this.$type.ifArray(this.spec.styleClasses, val => {
44
- colors.forEach(color => {
45
- if (val.includes(color)) {
46
- vm.color = color;
47
- }
48
- });
49
- });
34
+ this.variant = determineVariant(this.spec.styleClasses, 'outlined');
35
+ this.color = determineColor(this.spec.styleClasses, 'primary');
36
+ this.vertical = [this.spec.styleClasses].flat().includes('vertical');
50
37
  },
51
38
  click: function (spec) {
52
39
  const onClick = spec["onClick"];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "glib-web",
3
- "version": "3.2.0",
3
+ "version": "3.4.0",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -10,6 +10,7 @@
10
10
  "license": "ISC",
11
11
  "dependencies": {
12
12
  "@floating-ui/dom": "^1.2.8",
13
+ "@gmap-vue/v3": "^1.0.3",
13
14
  "@rails/actioncable": "^6.0.0",
14
15
  "@rails/activestorage": "^6.0.0-alpha",
15
16
  "@vueup/vue-quill": "^1.2.0",
@@ -30,8 +31,6 @@
30
31
  "vue": "3.2.47",
31
32
  "vue-chartkick": "^1.1.0",
32
33
  "vue-social-sharing": "^4.0.0-alpha4",
33
- "vue2-gmap-custom-marker": "^6.1.1",
34
- "vue2-google-maps": "^0.10.6",
35
34
  "vuedraggable": "^4.1.0",
36
35
  "vuetify": "^3.3.2"
37
36
  },
@@ -42,4 +41,4 @@
42
41
  "prettier": "^1.18.2",
43
42
  "typescript": "^4.9.5"
44
43
  }
45
- }
44
+ }
package/utils/http.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import Type from "./type";
2
2
  import Action from "../action";
3
- import { nextTick } from 'vue'
3
+ import { nextTick } from 'vue';
4
4
  import { vueApp } from "../store";
5
5
 
6
6
  let loading = false;
@@ -89,7 +89,7 @@ export default class {
89
89
  this.forceComponentUpdate(() => {
90
90
  Utils.history.resetScroll();
91
91
 
92
- vueApp.page = data
92
+ vueApp.page = data;
93
93
 
94
94
  Action.execute(properties["onOpen"], component);
95
95
  });
@@ -143,7 +143,7 @@ export default class {
143
143
 
144
144
  Utils.http.execute(data, "GET", component, (page, response) => {
145
145
  Utils.http.forceComponentUpdate(() => {
146
- vueApp.page = page
146
+ vueApp.page = page;
147
147
  const redirectUrl = Utils.url.htmlUrl(response.url);
148
148
  Utils.history.updatePage(vueApp.page, redirectUrl);
149
149
 
@@ -244,27 +244,27 @@ export default class {
244
244
  setTimeout(() => {
245
245
  if (loading) {
246
246
  loading = false;
247
- vueApp.indicator = true
247
+ vueApp.indicator = true;
248
248
  }
249
249
  }, 200);
250
250
  }
251
251
 
252
252
  static _hideIndicator() {
253
253
  loading = false;
254
- vueApp.indicator = false
254
+ vueApp.indicator = false;
255
255
  }
256
256
 
257
257
  // See generic.js
258
258
  static forceComponentUpdate(handler) {
259
259
  // GLib.component.clearRegistry();
260
- vueApp.isStale = true
261
- vueApp.isFormSubmitted = false
262
- vueApp.isFormDirty = false
260
+ vueApp.isStale = true;
261
+ vueApp.isFormSubmitted = false;
262
+ vueApp.isFormDirty = false;
263
263
  handler();
264
264
 
265
265
  // Queue the execution so the first isStale has time to resets state before handler gets executed
266
266
  nextTick(() => {
267
- vueApp.isStale = false
267
+ vueApp.isStale = false;
268
268
  });
269
269
  }
270
270
 
@@ -275,16 +275,16 @@ export default class {
275
275
  }
276
276
 
277
277
  static clearDirtyState() {
278
- vueApp.isFormDirty = false
278
+ vueApp.isFormDirty = false;
279
279
  }
280
280
 
281
281
  static notifyFormSubmitted() {
282
- vueApp.isFormSubmitted = true
282
+ vueApp.isFormSubmitted = true;
283
283
  }
284
284
 
285
285
  // `context` can be either window or dialog.
286
286
  static proceedEvenWhenDirty() {
287
- const dirtyContext = vueApp
287
+ const dirtyContext = vueApp;
288
288
  // Don't prompt if this is a result of form submission
289
289
  if (
290
290
  dirtyContext.isFormDirty &&
package/utils/launch.js CHANGED
@@ -3,7 +3,7 @@ import Dialog from "../nav/dialog.vue";
3
3
  import Sheet from "../nav/sheet.vue";
4
4
  import Snackbar from "../nav/snackbar.vue";
5
5
  import Popover from "../components/popover.vue";
6
- import { computePosition, flip, offset } from '@floating-ui/dom'
6
+ import { computePosition, flip, offset } from '@floating-ui/dom';
7
7
  import bus from "./eventBus";
8
8
 
9
9
  import { createApp, h } from "vue";
@@ -31,17 +31,17 @@ class LaunchPopover {
31
31
  const instance = createApp(
32
32
  {
33
33
  render: function () {
34
- return h(Popover, { spec: properties })
34
+ return h(Popover, { spec: properties });
35
35
  }
36
36
  }
37
- )
38
- Object.assign(instance._context, Vue._context)
37
+ );
38
+ Object.assign(instance._context, Vue._context);
39
39
 
40
40
  if (component) {
41
- const placeholder = document.createElement('div')
41
+ const placeholder = document.createElement('div');
42
42
 
43
43
  // const pageBody = Utils.launch.dialog.closestBody(component) || Utils.history._pageBody;
44
- const dialogBody = Utils.launch.dialog.closestBody(component)
44
+ const dialogBody = Utils.launch.dialog.closestBody(component);
45
45
  if (dialogBody) {
46
46
  // Put the popover on the dialog so that clicking it doesn't close a non-persistent dialog.
47
47
  dialogBody.appendChild(placeholder);
@@ -53,11 +53,11 @@ class LaunchPopover {
53
53
 
54
54
  instance.mount(placeholder);
55
55
 
56
- const reference = component.$el instanceof HTMLElement ? component.$el : component.$el.nextElementSibling
56
+ const reference = component.$el instanceof HTMLElement ? component.$el : component.$el.nextElementSibling;
57
57
 
58
- const floating = placeholder
59
- const placement = properties.placement || 'right'
60
- const offsetSize = properties.offset || 8
58
+ const floating = placeholder;
59
+ const placement = properties.placement || 'right';
60
+ const offsetSize = properties.offset || 8;
61
61
 
62
62
  // These properties need to be set to get an accurate calculation. See https://floating-ui.com/docs/computeposition
63
63
  Object.assign(placeholder.style, { position: 'absolute', display: 'block', top: 0, left: 0 });
@@ -65,7 +65,7 @@ class LaunchPopover {
65
65
  computePosition(reference, floating, { placement: placement, middleware: [flip(), offset(offsetSize)] })
66
66
  .then(({ x, y }) => {
67
67
  Object.assign(placeholder.style, { top: `${y}px`, left: `${x}px`, zIndex: 1200 });
68
- })
68
+ });
69
69
  } else {
70
70
  console.error("A popover has to be displayed in a component");
71
71
  }
@@ -74,13 +74,13 @@ class LaunchPopover {
74
74
  }
75
75
 
76
76
  static close(properties) {
77
- bus.$emit(`popover/close-${properties.key}`, properties)
77
+ bus.$emit(`popover/close-${properties.key}`, properties);
78
78
  }
79
79
  }
80
80
 
81
81
  class LaunchDialog {
82
82
  static closestBody(component) {
83
- const element = component.$el instanceof HTMLElement ? component.$el : component.$el.nextElementSibling
83
+ const element = component.$el instanceof HTMLElement ? component.$el : component.$el.nextElementSibling;
84
84
  const dialog = element.closest(".v-dialog");
85
85
  if (dialog) {
86
86
  return dialog.querySelector(".dialogs-body");
@@ -110,20 +110,20 @@ class LaunchDialog {
110
110
  const props = {
111
111
  spec: properties,
112
112
  stack: this.stack
113
- }
113
+ };
114
114
 
115
115
  const instance = createApp(
116
116
  {
117
117
  render: function () {
118
- return h(Dialog, props)
118
+ return h(Dialog, props);
119
119
  }
120
120
  }
121
- )
122
- Object.assign(instance._context, Vue._context)
121
+ );
122
+ Object.assign(instance._context, Vue._context);
123
123
 
124
124
  if (component) {
125
- const placeholder = document.createElement('div')
126
- document.body.appendChild(placeholder)
125
+ const placeholder = document.createElement('div');
126
+ document.body.appendChild(placeholder);
127
127
  instance.mount(placeholder);
128
128
  } else {
129
129
  console.error("A dialog has to be displayed in a component");
@@ -160,6 +160,7 @@ class LaunchDialog {
160
160
  message: message
161
161
  };
162
162
  const spec = Object.assign({}, properties, {
163
+ disableCloseButton: true,
163
164
  buttons: [
164
165
  {
165
166
  text: "OK",
@@ -176,15 +177,15 @@ class LaunchSheet {
176
177
  const instance = createApp(
177
178
  {
178
179
  render: function () {
179
- return h(Sheet, { spec: properties })
180
+ return h(Sheet, { spec: properties });
180
181
  }
181
182
  }
182
- )
183
- Object.assign(instance._context, Vue._context)
183
+ );
184
+ Object.assign(instance._context, Vue._context);
184
185
 
185
186
  if (component) {
186
- const placeholder = document.createElement('div')
187
- document.body.appendChild(placeholder)
187
+ const placeholder = document.createElement('div');
188
+ document.body.appendChild(placeholder);
188
189
  instance.mount(placeholder);
189
190
  } else {
190
191
  console.error("A sheet has to be displayed in a component");
@@ -216,15 +217,15 @@ class LaunchSnackbar {
216
217
  const instance = createApp(
217
218
  {
218
219
  render: function () {
219
- return h(Snackbar, { spec: properties })
220
+ return h(Snackbar, { spec: properties });
220
221
  }
221
222
  }
222
- )
223
- Object.assign(instance._context, Vue._context)
223
+ );
224
+ Object.assign(instance._context, Vue._context);
224
225
 
225
226
  if (component) {
226
- const placeholder = document.createElement('div')
227
- document.body.appendChild(placeholder)
227
+ const placeholder = document.createElement('div');
228
+ document.body.appendChild(placeholder);
228
229
  instance.mount(placeholder);
229
230
  instance.show = true;
230
231
  } else {