glib-web 3.12.2 → 3.14.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
@@ -63,6 +63,10 @@ import ActionPopoversClose from "./actions/popovers/close";
63
63
  import ActionComponentsUpdate from "./actions/components/update";
64
64
  import ActionComponentsFind from "./actions/components/find";
65
65
 
66
+ import ActionLabelsSet from "./actions/labels/set";
67
+
68
+ import ActionListsAppend from "./actions/lists/append";
69
+
66
70
  import { vueApp } from "./store";
67
71
 
68
72
  const actions = {
@@ -126,7 +130,10 @@ const actions = {
126
130
  "popovers/close": ActionPopoversClose,
127
131
 
128
132
  "components/update": ActionComponentsUpdate,
129
- "components/find": ActionComponentsFind
133
+ "components/find": ActionComponentsFind,
134
+
135
+ "labels/set": ActionLabelsSet,
136
+ "lists/append": ActionListsAppend
130
137
  };
131
138
 
132
139
  const customActions = {};
@@ -182,17 +189,18 @@ export default class Action {
182
189
  }
183
190
  }
184
191
 
185
- static handleResponse(response, component) {
192
+ static handleResponse(response, component, windowMode = true) {
193
+ // The execution goes in the following order: onResponse, page render, onLoad
186
194
  vueApp.temp.analytics = response.analytics;
187
195
  GLib.action.execute(response.onResponse, component);
188
196
  vueApp.temp.analytics = null;
189
197
 
190
- // TODO: The execution should be in the following order: onResponse, page render, onLoad
191
198
  if (response.header || response.body || response.footer) {
192
199
  Utils.http.forceComponentUpdate(() => {
193
- const dialog = component.$closest("dialog");
194
- if (dialog) {
195
- dialog.updateContent(response)
200
+ const dialog = component.$closest("dialog")
201
+ const updateDialog = windowMode ? false : Utils.type.isObject(dialog)
202
+ if (updateDialog) {
203
+ dialog.updateContent(response);
196
204
  } else {
197
205
  vueApp.page = response;
198
206
  }
@@ -1,31 +1,16 @@
1
- import { vueApp } from "../../store";
1
+ import { channels } from "../../components/composable/socket";
2
+
2
3
 
3
4
  export default class {
4
5
  execute(properties, component) {
5
- Utils.type.ifString(properties.channel, channelName => {
6
- const ws = vueApp.actionCable;
7
- const channel = ws.channels[channelName];
8
-
9
- // TODO: Use logDisabled() that reads params from server to decide whether we want to print the log or not.
10
- // This is because `push` action's logs are a bit sensitive.
11
- console.debug("Pushing to", channel);
6
+ const { event, payload, channel } = properties;
7
+ const ch = channels[channel];
12
8
 
13
- Utils.type.ifString(properties.event, eventName => {
14
- if (channel) {
15
- const payload = Object.assign({}, properties.payload, {
16
- formData: properties.formData
17
- });
18
- console.debug(`Pushing to '${channelName}/${eventName}'`, payload);
9
+ if (!event || !channel) {
10
+ console.error('No event name or channel');
11
+ return;
12
+ }
19
13
 
20
- channel.perform(eventName, payload);
21
- } else {
22
- console.error(`Channel not joined: '${channelName}'`);
23
- Utils.launch.snackbar.error(
24
- "Something went wrong and we have been notified",
25
- component
26
- );
27
- }
28
- });
29
- });
14
+ ch.perform(event, payload);
30
15
  }
31
16
  }
@@ -1,5 +1,13 @@
1
1
  export default class {
2
2
  execute(spec, component) {
3
+ let dialog = null
4
+ if (spec.updateExisting) {
5
+ dialog = component.$closest("dialog")
6
+ if (dialog) {
7
+ dialog.updateContent(spec)
8
+ return
9
+ }
10
+ }
3
11
  Utils.launch.dialog.open(spec, component);
4
12
  }
5
13
  }
@@ -0,0 +1,16 @@
1
+ import Action from "../../action";
2
+
3
+ export default class {
4
+ execute(properties, component) {
5
+ let targetComponent = component;
6
+ if (properties.targetId) {
7
+ targetComponent = GLib.component.findById(properties.targetId);
8
+ }
9
+
10
+ targetComponent.text = properties.text;
11
+
12
+ if (properties.onSet) {
13
+ Action.execute(properties.onSet, targetComponent);
14
+ }
15
+ }
16
+ }
@@ -0,0 +1,15 @@
1
+ export default class {
2
+ execute(properties, component) {
3
+ let targetComponent = component;
4
+ if (properties.targetId) {
5
+ targetComponent = GLib.component.findById(properties.targetId);
6
+ }
7
+
8
+ if (!targetComponent.sections || !targetComponent.sections[0].rows) {
9
+ console.warn('there is no sections or rows');
10
+ return null;
11
+ }
12
+
13
+ targetComponent.sections[0].rows.push(properties.row);
14
+ }
15
+ }
@@ -8,6 +8,6 @@ export default class {
8
8
  return;
9
9
  }
10
10
 
11
- Utils.http.load(properties, component);
11
+ Utils.http.load(properties, component, true);
12
12
  }
13
13
  }
package/app.vue CHANGED
@@ -34,10 +34,14 @@ import Utils from "./utils/helper";
34
34
  import FormPanel from "./components/panels/form.vue";
35
35
  import { vueApp } from "./store";
36
36
  import { useDirtyState } from "./components/composable/dirtyState";
37
+ import { useSocket } from "./components/composable/socket";
37
38
 
38
39
  const { watchDirtyState } = useDirtyState();
39
40
 
40
41
  export default {
42
+ setup() {
43
+ return { vueApp };
44
+ },
41
45
  components: {
42
46
  "nav-appbar": NavAppBar,
43
47
  "panels-form": FormPanel,
@@ -50,7 +54,7 @@ export default {
50
54
  return {
51
55
  title: "...",
52
56
  mainHeight: 0,
53
- vueApp,
57
+ actionCableCustomer: null,
54
58
  };
55
59
  },
56
60
  computed: {
@@ -84,10 +88,24 @@ export default {
84
88
  "vueApp.indicator": function (val, oldVal) {
85
89
  document.title = val ? "..." : this.page.title;
86
90
  },
91
+ "vueApp.page": {
92
+ handler(val, oldVal) {
93
+ if (!val) {
94
+ return;
95
+ }
96
+
97
+ const { actionCable } = val;
98
+ if (actionCable) {
99
+ const { customer } = useSocket(actionCable, this.actionCableCustomer);
100
+ this.actionCableCustomer = customer;
101
+ }
102
+ },
103
+ immediate: true
104
+ }
87
105
  },
88
106
  created() {
89
107
  console.debug(
90
- `Version123: ${Utils.settings.appVersion} (${Utils.settings.env})`
108
+ `Version: ${Utils.settings.appVersion} (${Utils.settings.env})`
91
109
  );
92
110
  Utils.history.saveInitialContent(this.page);
93
111
  Utils.history.restoreOnBackOrForward();
@@ -71,6 +71,7 @@ import TextAreaField from "./fields/textarea.vue";
71
71
  const RichTextField = defineAsyncComponent(() => import("./fields/richText.vue"));
72
72
  // import NewRichTextField from "./fields/newRichText.vue";
73
73
  const FileField = defineAsyncComponent(() => import("./fields/file.vue"));
74
+ const MultipleUploadField = defineAsyncComponent(() => import("./fields/multipleUpload.vue"));
74
75
  import AutocompleteField from "./fields/autocomplete.vue";
75
76
  import SelectField from "./fields/select.vue";
76
77
  import TimeZoneField from "./fields/timeZone.vue";
@@ -163,6 +164,7 @@ export default {
163
164
  "fields-richText": RichTextField,
164
165
  // "fields-newRichText": NewRichTextField,
165
166
  "fields-file": FileField,
167
+ "fields-multipleUpload": MultipleUploadField,
166
168
  "fields-autocomplete": AutocompleteField,
167
169
  "fields-select": SelectField,
168
170
  "fields-timeZone": TimeZoneField,
@@ -0,0 +1,97 @@
1
+ import { createConsumer } from '@rails/actioncable';
2
+ import { reactive } from "vue";
3
+ import Action from "../../action";
4
+
5
+ // Handler implementation
6
+ class SocketHandler {
7
+ constructor(options) {
8
+ this.targetId = options.targetId;
9
+ }
10
+
11
+ initialized() {
12
+ console.log(`${this.targetId} initialized`);
13
+ }
14
+
15
+ rejected() {
16
+ console.error(`${this.targetId} rejected`);
17
+ }
18
+
19
+ received(data) {
20
+ Action.execute(data, this.component);
21
+ }
22
+
23
+ connected() {
24
+ console.log(`${this.targetId} connected`);
25
+ }
26
+
27
+ disconnected() {
28
+ console.log(`${this.targetId} disconnected`);
29
+ }
30
+
31
+ get component() {
32
+ if (this.targetId) {
33
+ return GLib.component.findById(this.targetId);
34
+ }
35
+ }
36
+ }
37
+ class ExampleSocketHandler extends SocketHandler {
38
+ received(data) {
39
+ this.component.text = data.text;
40
+ }
41
+ }
42
+
43
+ // you can implement your own handler or just use the default one
44
+ const socketHandlers = reactive({
45
+ ExampleSocketHandler
46
+ });
47
+
48
+ const channels = reactive({});
49
+
50
+ // API
51
+ const useSocket = (config, defaultConsumer = null) => {
52
+ const consumer = defaultConsumer || createConsumer();
53
+
54
+ consumer.connection.events.error = function () {
55
+ console.error('Websocket error');
56
+ if (!config.onError) return;
57
+ Action.execute(config.onError, {});
58
+ };
59
+
60
+ config.channels.forEach(ch => {
61
+ if (!channels[ch.name]) {
62
+ let handler = null;
63
+
64
+ if (ch.handler && socketHandlers[ch.handler]) {
65
+ handler = new socketHandlers[ch.handler](ch.options);
66
+ } else {
67
+ handler = new SocketHandler(ch.options);
68
+ }
69
+
70
+ const channel = consumer.subscriptions.create(
71
+ { channel: ch.name, ...ch.options },
72
+ {
73
+ received(data) {
74
+ handler.received(data);
75
+ },
76
+ initialized() {
77
+ handler.initialized();
78
+ },
79
+ connected() {
80
+ handler.connected();
81
+ },
82
+ rejected() {
83
+ handler.rejected();
84
+ },
85
+ disconnected({ willAttemptReconnect: boolean }) {
86
+ handler.disconnected();
87
+ }
88
+ }
89
+ );
90
+ channels[ch.name] = channel;
91
+ }
92
+ });
93
+
94
+ return { consumer };
95
+ };
96
+
97
+ export { useSocket, channels };
@@ -7,7 +7,6 @@
7
7
  @update:modelValue="onChange">
8
8
 
9
9
  <template #item="{props, item}">
10
- <span :debug="console.log('DATA1', props, item)"></span>
11
10
  <v-list-subheader v-if="item.raw.header">
12
11
  {{ item.raw.header }}
13
12
  </v-list-subheader>
@@ -0,0 +1,393 @@
1
+ <template>
2
+ <div :style="$styles()" :class="$classes()">
3
+ <div ref="container" @click="handleClick" @drop="handleDrop" @dragover="handleDragOver" @dragleave="handleDragLeave"
4
+ class="gdrop-file border-[2px]">
5
+ <input ref="fileSelect" type="file" multiple style="display: none">
6
+
7
+ <div class="cloud" style="pointer-events: none;">
8
+ <v-icon ref="icon" size="48" class="icon">cloud_upload</v-icon>
9
+ <h4 class="title">{{ spec.title }}</h4>
10
+ <p class="subtitle">{{ spec.subtitle }}</p>
11
+ </div>
12
+
13
+ <input v-for="(file, index) in Object.values(files)" type="hidden" :name="spec.name" :value="file.signedId"
14
+ :disabled="inputDisabled" :key="`hidden-${index}`">
15
+ </div>
16
+
17
+ <div class="guploaded-file" v-if="showUploadedFile">
18
+ <p class="title" v-if="uploadTitle">{{ uploadTitle }} </p>
19
+
20
+ <template v-for="(file, index) in Object.entries(files)" :key="`added-${index}`">
21
+ <div :class="`file-container ${file[1].status == 'failed' ? 'opacity-50' : ''}`">
22
+ <div class="file">
23
+ <div @click.stop="handleRemoveFile(file[0])" class="close-btn">
24
+ <v-icon size="12" color="white">close</v-icon>
25
+ </div>
26
+ <div class="status">
27
+ <v-icon class="icon">{{ file[1].isImage() ? 'photo_library' : 'description' }}</v-icon>
28
+ <div class="progress">
29
+ <label class="label">{{ `${file[1].name} ${file[1].status === 'failed' ? uploadFailedText : ''}`
30
+ }}</label>
31
+ <div class="background" v-show="file[1].progress.value > 0">
32
+ <div class="value" :style="{ width: `${file[1].progress.value}%` }"></div>
33
+ </div>
34
+ </div>
35
+ </div>
36
+ </div>
37
+ </div>
38
+ </template>
39
+ </div>
40
+
41
+ </div>
42
+ </template>
43
+
44
+ <style>
45
+ .border-\[2px\] {
46
+ border-width: 2px;
47
+ }
48
+
49
+ .border-\[4px\] {
50
+ border-width: 4px;
51
+ }
52
+
53
+ .\!font-semibold {
54
+ font-weight: 600px;
55
+ }
56
+
57
+ .opacity-50 {
58
+ opacity: 0.5;
59
+ }
60
+
61
+ .guploaded-file {
62
+ /* pt-6 w-full */
63
+ padding-top: 24px;
64
+ width: 100%;
65
+
66
+ .title {
67
+ color: #6b7280;
68
+ font-weight: 600 !important;
69
+ margin-bottom: 8px;
70
+ }
71
+
72
+ .file-container {
73
+ display: flex;
74
+ align-items: center;
75
+ border-radius: 6px;
76
+ background-color: #f3f4f6;
77
+ width: 100%;
78
+ padding: 16px;
79
+ margin-bottom: 16px;
80
+ min-height: 72px;
81
+ }
82
+
83
+ .file {
84
+ display: flex;
85
+ width: 100%;
86
+ position: relative;
87
+
88
+ .status {
89
+ display: flex;
90
+ width: 100%;
91
+ align-items: center;
92
+
93
+ .icon {
94
+ margin-right: 8px;
95
+ }
96
+
97
+ .progress {
98
+ display: flex;
99
+ width: 100%;
100
+ flex-direction: column;
101
+ justify-content: space-between;
102
+
103
+ .label {
104
+ /* mb-1 */
105
+ margin-bottom: 4px;
106
+ }
107
+
108
+ .background {
109
+ width: 100%;
110
+ background-color: #e5e7eb;
111
+ border-radius: 9999px;
112
+ height: 6px;
113
+
114
+ .value {
115
+ background-color: #4b5563;
116
+ height: inherit;
117
+ border-radius: inherit;
118
+ }
119
+ }
120
+ }
121
+ }
122
+
123
+ .close-btn {
124
+ border-radius: 9999px;
125
+ background-color: #6b7280;
126
+ position: absolute;
127
+ top: -24px;
128
+ right: -24px;
129
+ display: flex;
130
+ justify-content: center;
131
+ align-items: center;
132
+ padding: 2px;
133
+ cursor: pointer;
134
+ }
135
+ }
136
+ }
137
+
138
+ .gdrop-file {
139
+ /* px-6 py-10 border-[2px] border-dashed border-gray-400 w-full rounded-2xl cursor-pointer */
140
+ padding: 40px 24px;
141
+ border-style: dashed;
142
+ border-color: #9ca3af;
143
+ width: 100%;
144
+ border-radius: 16px;
145
+ cursor: pointer;
146
+
147
+ .cloud {
148
+ /* w-full flex flex-col justify-center items-center */
149
+ width: 100%;
150
+ display: flex;
151
+ flex-direction: column;
152
+ justify-content: center;
153
+ align-items: center;
154
+
155
+ .icon {
156
+ /* mb-3 text-gray-400 */
157
+ margin-bottom: 12px;
158
+ color: #9ca3af;
159
+ }
160
+
161
+ .title {
162
+ /* text-center mb-2 */
163
+ text-align: center;
164
+ margin-bottom: 8px;
165
+ }
166
+
167
+ .subtitle {
168
+ /* text-color-light text-center */
169
+ color: #A7ADB5;
170
+ text-align: center;
171
+ }
172
+ }
173
+ }
174
+ </style>
175
+
176
+ <script>
177
+
178
+ import Uploader from "../../utils/uploader";
179
+ import { watchEffect } from 'vue';
180
+ import { ref, computed, defineComponent } from 'vue';
181
+ import { VIcon } from 'vuetify/components';
182
+ const makeKey = () => Math.random().toString(36).slice(2, 7);
183
+
184
+
185
+ class Item {
186
+ constructor(options) {
187
+ this.progress = { value: 0 };
188
+ this.status = options.status;
189
+ this.signedId = options.signedId;
190
+ this._name = options.name;
191
+ this._url = options.url;
192
+ this._type = options.type;
193
+ this.el = options.el;
194
+ }
195
+
196
+ isImage() {
197
+ return this.type.startsWith('image');
198
+ }
199
+
200
+ get type() {
201
+ if (this._type) {
202
+ return this._type;
203
+ }
204
+
205
+ if (this.el) {
206
+ return this.el.type;
207
+ }
208
+
209
+ return '';
210
+ }
211
+
212
+ get name() {
213
+ if (this._name) {
214
+ return this._name;
215
+ }
216
+
217
+ if (this.el) {
218
+ return this.el.name;
219
+ }
220
+
221
+ return '';
222
+ }
223
+
224
+ get url() {
225
+ if (this._url) {
226
+ return this._url;
227
+ }
228
+
229
+ return URL.createObjectURL(this.el);
230
+ }
231
+ }
232
+
233
+ export default defineComponent({
234
+ props: { spec: { type: Object } },
235
+ components: { VIcon },
236
+ setup(props) {
237
+ const spec = props.spec;
238
+ const fileSelect = ref(null);
239
+ const container = ref(null);
240
+ const icon = ref(null);
241
+
242
+ let files = null;
243
+ if (spec.files) {
244
+ files = ref(spec.files.reduce((prev, curr) => {
245
+ const key = makeKey();
246
+
247
+ prev[key] = new Item({
248
+ status: null, // pending, completed, failed
249
+ url: curr.url,
250
+ name: curr.name,
251
+ signedId: curr.signed_id,
252
+ type: curr.type,
253
+ el: null
254
+ });
255
+
256
+ return prev;
257
+ }, {}));
258
+ } else {
259
+ files = ref([]);
260
+ }
261
+
262
+ const setBusy = (value) => {
263
+ const event = new Event('forms/setBusy', { bubbles: true });
264
+ event.data = { value };
265
+ container.value.dispatchEvent(event);
266
+ };
267
+
268
+ const uploading = computed(() => Object.values(files.value).reduce((prev, curr) => prev || curr.status == 'pending', false));
269
+
270
+ watchEffect(() => {
271
+ if (!container.value) return null;
272
+
273
+ if (uploading.value) {
274
+ setBusy(true);
275
+ } else {
276
+ setBusy(false);
277
+ }
278
+ });
279
+
280
+ const showUploadedFile = computed(() => {
281
+ return Object.keys(files.value).length > 0;
282
+ });
283
+
284
+ function uploadFiles(droppedFiles) {
285
+ for (let index = 0; index < droppedFiles.length; index++) {
286
+ // show new dropped file and track progress
287
+ const key = makeKey();
288
+ files.value[key] = new Item({ el: droppedFiles[index], status: 'pending' });
289
+
290
+ const uploader = new Uploader(files.value[key].el, spec.directUploadUrl, files.value[key].progress);
291
+
292
+ // track progress
293
+ uploader.directUploadWillStoreFileWithXHR = (request) => {
294
+ request.upload.addEventListener("progress", event => {
295
+ files.value[key].progress.value = (event.loaded / event.total) * 100;
296
+ });
297
+
298
+ // can be use for aborting request
299
+ files.value[key].xhr = request;
300
+ };
301
+
302
+ // validate per file, skip if invalid
303
+ if (!uploader.validateFile(spec)) {
304
+ delete files.value[key];
305
+ continue;
306
+ }
307
+
308
+ // upload per file
309
+ uploader.start((error, blob) => {
310
+ if (!error) {
311
+ Object.assign(files.value[key], {
312
+ status: 'completed',
313
+ signedId: blob.signed_id
314
+ });
315
+ } else {
316
+ Object.assign(files.value[key], {
317
+ status: 'failed'
318
+ });
319
+ }
320
+ });
321
+ }
322
+ }
323
+
324
+ function handleDrop(e) {
325
+ e.preventDefault();
326
+ uploadFiles(e.dataTransfer.files);
327
+
328
+ e.currentTarget.classList.remove('border-[4px]');
329
+ container.value.classList.add('border-[2px]');
330
+ }
331
+
332
+ function handleRemoveFile(key) {
333
+ // abort upload
334
+ const xhr = files.value[key].xhr;
335
+ if (xhr) {
336
+ xhr.abort();
337
+ }
338
+
339
+ delete files.value[key];
340
+ }
341
+
342
+ function handleDragOver(e) {
343
+ e.preventDefault();
344
+ if (container.value.classList.contains('border-[2px]')) {
345
+ container.value.classList.remove('border-[2px]');
346
+ }
347
+ if (!container.value.classList.contains('border-[4px]')) {
348
+ container.value.classList.add('border-[4px]');
349
+ }
350
+ }
351
+
352
+ function handleDragLeave(e) {
353
+ e.preventDefault();
354
+ if (container.value.classList.contains('border-[4px]')) {
355
+ e.currentTarget.classList.remove('border-[4px]');
356
+ }
357
+ if (!container.value.classList.contains('border-[2px]')) {
358
+ container.value.classList.add('border-[2px]');
359
+ }
360
+ }
361
+
362
+ function handleClick(e) {
363
+ const onchange = () => {
364
+ uploadFiles(e.target.files);
365
+ };
366
+
367
+ fileSelect.value.onchange = onchange;
368
+ fileSelect.value.click();
369
+ }
370
+
371
+ const uploadFailedText = spec.uploadFailedText || '(UPLOAD FAILED)';
372
+ const uploadTitle = spec.uploadTitle || 'File added';
373
+
374
+ return {
375
+ files,
376
+ fileSelect,
377
+ container,
378
+ icon,
379
+ handleDragLeave,
380
+ handleDragOver,
381
+ handleDrop,
382
+ handleClick,
383
+ handleRemoveFile,
384
+ showUploadedFile,
385
+ uploadFailedText,
386
+ uploadTitle
387
+ };
388
+ }
389
+ })
390
+
391
+
392
+
393
+ </script>
@@ -5,7 +5,7 @@
5
5
  :disabled="inputDisabled" :type="config.type" :rules="rules" :prepend-inner-icon="leftIconName"
6
6
  :append-inner-icon="config.appendIcon" :prefix="spec.leftText" :suffix="spec.rightText" :min="spec.min"
7
7
  :max="spec.max" :autofocus="spec.autoFocus || false" validate-on="blur" @click:appendInner="onRightIconClick"
8
- @input="onChange" :variant="variant" persistent-placeholder />
8
+ @input="onChange" :variant="variant" persistent-placeholder @keyup="$onTyping({ duration: 2000 })" />
9
9
  </div>
10
10
  </template>
11
11
 
@@ -30,9 +30,9 @@ export default {
30
30
  }
31
31
  },
32
32
  methods: {
33
- $ready() {
34
- this.fieldModel = this.spec.value;
35
- },
33
+ // $ready() {
34
+ // this.fieldModel = this.spec.value;
35
+ // },
36
36
  styles() {
37
37
  const styles = this.$styles();
38
38
  this.height = styles.remove("height");
@@ -8,7 +8,7 @@ export default {
8
8
  }
9
9
  },
10
10
  $closest(name) {
11
- var parent = this.$parent;
11
+ var parent = this;
12
12
  while (parent != null) {
13
13
  if (
14
14
  (Utils.type.isObject(parent.spec) || Utils.type.isObject(parent.formSpec)) &&
package/components/p.vue CHANGED
@@ -1,12 +1,18 @@
1
1
  <template>
2
2
  <!-- <p :style="genericStyles()" :class="$classes()" v-html="compiledText" /> -->
3
- <p :style="genericStyles()" :class="$classes()">{{ spec.text }}</p>
3
+ <p :style="genericStyles()" :class="$classes()">{{ text }}</p>
4
4
  </template>
5
5
 
6
6
  <script>
7
7
  // import marked from "marked";
8
8
 
9
9
  export default {
10
+ expose: ['text'],
11
+ data: function () {
12
+ return {
13
+ text: this.spec.text
14
+ };
15
+ },
10
16
  props: {
11
17
  spec: { type: Object, required: true }
12
18
  }
package/index.js CHANGED
@@ -8,17 +8,15 @@ import { useTheme } from "vuetify";
8
8
  import merge from 'lodash.merge';
9
9
 
10
10
  const Vue = createApp({
11
- data() {
12
- return {
13
- vueApp,
14
- themeConfig: useTheme()
15
- };
11
+ setup() {
12
+ const themeConfig = useTheme();
13
+ return { themeConfig, vueApp };
16
14
  },
17
15
  created() {
18
16
  this.themeConfig.themes = merge(this.themeConfig.themes, settings.themes);
19
17
  },
20
18
  render: function () {
21
- return h(App, { page: this.$data.vueApp.page });
19
+ return h(App, { page: this.vueApp.page });
22
20
  }
23
21
  });
24
22
 
package/nav/dialog.vue CHANGED
@@ -178,17 +178,17 @@ export default {
178
178
  this.formSpec = response.fullPageForm;
179
179
  this.disableCloseButton = this.spec.disableCloseButton || false;
180
180
  this.updateContent(response)
181
-
182
- Action.execute(response.onLoad, this);
183
181
  });
184
182
  });
185
183
  }
186
184
  } else {
187
- this.title = this.spec.title;
185
+ // this.title = this.spec.title;
186
+ // this.body = this.spec.body;
187
+
188
188
  this.message = this.spec.message;
189
- this.body = this.spec.body;
190
189
  this.disableCloseButton = this.spec.disableCloseButton || false;
191
- Action.execute(spec.onLoad, this);
190
+
191
+ this.updateContent(this.spec);
192
192
  }
193
193
 
194
194
  this.model = true;
@@ -198,6 +198,11 @@ export default {
198
198
  this.header = response.header;
199
199
  this.body = response.body;
200
200
  this.footer = response.footer;
201
+
202
+ // Make sure action only executes after the content has finished populating.
203
+ this.$nextTick(() => {
204
+ Action.execute(response.onLoad, this);
205
+ })
201
206
  },
202
207
  updateMainHeight() {
203
208
  this.mainHeight = window.innerHeight - 140;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "glib-web",
3
- "version": "3.12.2",
3
+ "version": "3.14.0",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -41,4 +41,4 @@
41
41
  "prettier": "^1.18.2",
42
42
  "typescript": "^4.9.5"
43
43
  }
44
- }
44
+ }
package/store.js CHANGED
@@ -10,7 +10,7 @@ export const vueApp = reactive({
10
10
  isFormDirty: false,
11
11
  stateUpdatedAt: null,
12
12
  webSocket: { channels: {}, header: {} },
13
- actionCable: { channels: {} },
13
+ actionCable: null,
14
14
  registeredComponents: [],
15
15
  temp: {},
16
16
  richTextValues: {},
package/utils/form.js CHANGED
@@ -32,7 +32,7 @@ export default class {
32
32
  url: Utils.url.appendParams(url, formData)
33
33
  };
34
34
  Utils.http.notifyFormSubmitted();
35
- Utils.http.load(data, component);
35
+ Utils.http.load(data, component, false);
36
36
  } else {
37
37
  const data = {
38
38
  url: url,
package/utils/http.js CHANGED
@@ -59,7 +59,7 @@ export default class {
59
59
  }
60
60
  }
61
61
 
62
- static load(properties, component) {
62
+ static load(properties, component, windowMode) {
63
63
  const urlString = properties["url"];
64
64
  let url;
65
65
  try {
@@ -80,8 +80,8 @@ export default class {
80
80
  const htmlUrl = Utils.url.htmlUrl(properties["url"]);
81
81
 
82
82
  Utils.http.execute(properties, "GET", component, (data, response) => {
83
- const dialog = component.$closest("dialog");
84
- if (htmlUrl !== currentUrl && !Utils.type.isObject(dialog)) {
83
+ const pushHistory = windowMode ? true : !Utils.type.isObject(component.$closest("dialog"))
84
+ if (htmlUrl !== currentUrl && pushHistory) {
85
85
  const redirectUrl = Utils.url.htmlUrl(response.url);
86
86
  Utils.history.pushPage(data, redirectUrl);
87
87
  }
@@ -89,7 +89,7 @@ export default class {
89
89
  this.forceComponentUpdate(() => {
90
90
  Utils.history.resetScroll();
91
91
 
92
- GLib.action.handleResponse(data, component)
92
+ GLib.action.handleResponse(data, component, windowMode)
93
93
 
94
94
  Action.execute(properties["onOpen"], component);
95
95
  });
package/utils/launch.js CHANGED
@@ -5,6 +5,7 @@ import Snackbar from "../nav/snackbar.vue";
5
5
  import Popover from "../components/popover.vue";
6
6
  import { computePosition, flip, offset } from '@floating-ui/dom';
7
7
  import bus from "./eventBus";
8
+ import { nextTick } from "vue";
8
9
 
9
10
  import { createApp, h } from "vue";
10
11
 
@@ -105,7 +106,6 @@ class LaunchDialog {
105
106
  this.stack = [];
106
107
  }
107
108
 
108
-
109
109
  // https://css-tricks.com/creating-vue-js-component-instances-programmatically/
110
110
  const props = {
111
111
  spec: properties,
@@ -149,11 +149,9 @@ class LaunchDialog {
149
149
  dialog.close();
150
150
  });
151
151
 
152
- Action.execute(properties.onClose, component);
153
-
154
- // Utils.type.ifObject(properties["onClose"], it => {
155
- // Action.execute(it, component);
156
- // });
152
+ nextTick(() => {
153
+ Action.execute(properties.onClose, component);
154
+ })
157
155
  }
158
156
 
159
157
  // This is only meant to be used internally