quasar-ui-danx 0.4.10 → 0.4.13

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. package/dist/danx.es.js +12389 -7677
  2. package/dist/danx.es.js.map +1 -1
  3. package/dist/danx.umd.js +137 -7
  4. package/dist/danx.umd.js.map +1 -1
  5. package/dist/style.css +1 -1
  6. package/index.d.ts +7 -0
  7. package/index.ts +1 -0
  8. package/package.json +10 -4
  9. package/src/components/ActionTable/ActionMenu.vue +26 -31
  10. package/src/components/ActionTable/ActionTable.vue +4 -1
  11. package/src/components/ActionTable/Columns/ActionTableColumn.vue +14 -6
  12. package/src/components/ActionTable/Columns/ActionTableHeaderColumn.vue +63 -42
  13. package/src/components/ActionTable/Form/ActionForm.vue +55 -0
  14. package/src/components/ActionTable/Form/Fields/EditOnClickTextField.vue +11 -5
  15. package/src/components/ActionTable/Form/Fields/FileUploadButton.vue +1 -0
  16. package/src/components/ActionTable/Form/Fields/MultiFileField.vue +1 -1
  17. package/src/components/ActionTable/Form/Fields/NumberField.vue +0 -1
  18. package/src/components/ActionTable/Form/RenderedForm.vue +57 -50
  19. package/src/components/ActionTable/Form/index.ts +1 -0
  20. package/src/components/ActionTable/Layouts/ActionTableLayout.vue +3 -3
  21. package/src/components/ActionTable/TableSummaryRow.vue +48 -37
  22. package/src/components/ActionTable/Toolbars/ActionToolbar.vue +2 -2
  23. package/src/components/ActionTable/listControls.ts +3 -2
  24. package/src/components/PanelsDrawer/PanelsDrawer.vue +15 -5
  25. package/src/components/PanelsDrawer/PanelsDrawerPanels.vue +3 -1
  26. package/src/components/PanelsDrawer/PanelsDrawerTabs.vue +17 -4
  27. package/src/components/Utility/Dialogs/FullscreenCarouselDialog.vue +30 -5
  28. package/src/components/Utility/Files/FilePreview.vue +72 -12
  29. package/src/components/Utility/Popovers/PopoverMenu.vue +34 -29
  30. package/src/components/Utility/Tools/RenderVnode.vue +5 -1
  31. package/src/config/index.ts +2 -1
  32. package/src/helpers/FileUpload.ts +59 -8
  33. package/src/helpers/actions.ts +27 -27
  34. package/src/helpers/date.ts +2 -2
  35. package/src/helpers/download.ts +8 -2
  36. package/src/helpers/formats.ts +52 -5
  37. package/src/helpers/multiFileUpload.ts +6 -4
  38. package/src/helpers/objectStore.ts +14 -17
  39. package/src/helpers/request.ts +12 -0
  40. package/src/helpers/singleFileUpload.ts +63 -55
  41. package/src/helpers/utils.ts +12 -3
  42. package/src/index.ts +1 -0
  43. package/src/styles/danx.scss +5 -0
  44. package/src/styles/index.scss +1 -0
  45. package/src/styles/themes/danx/action-table.scss +24 -13
  46. package/src/types/actions.d.ts +16 -7
  47. package/src/types/controls.d.ts +4 -4
  48. package/src/types/files.d.ts +10 -5
  49. package/src/types/forms.d.ts +19 -1
  50. package/src/types/index.d.ts +0 -1
  51. package/src/types/requests.d.ts +2 -0
  52. package/src/types/tables.d.ts +28 -22
  53. package/src/{vue-plugin.js → vue-plugin.ts} +5 -4
  54. package/tsconfig.json +1 -0
  55. package/types/index.d.ts +2 -0
@@ -11,6 +11,8 @@ import {
11
11
  } from "../types";
12
12
  import { resolveFileLocation } from "./files";
13
13
  import { FlashMessages } from "./FlashMessages";
14
+ import { storeObject } from "./objectStore";
15
+ import { sleep } from "./utils";
14
16
 
15
17
 
16
18
  export class FileUpload {
@@ -22,7 +24,8 @@ export class FileUpload {
22
24
  onAllCompleteCb: FileUploadAllCompleteCallback | null = null;
23
25
  options: FileUploadOptions;
24
26
 
25
- constructor(files: UploadedFile[] | UploadedFile, options?: FileUploadOptions) {
27
+ constructor(files: UploadedFile[] | UploadedFile, options?: FileUploadOptions | null) {
28
+ /* @ts-expect-error Files is an array */
26
29
  this.files = !Array.isArray(files) && !(files instanceof FileList) ? [files] : files;
27
30
  this.fileUploads = [];
28
31
  this.onErrorCb = null;
@@ -33,6 +36,7 @@ export class FileUpload {
33
36
  this.options = {
34
37
  createPresignedUpload: null,
35
38
  completePresignedUpload: null,
39
+ refreshFile: null,
36
40
  ...danxOptions.value.fileUpload,
37
41
  ...options
38
42
  };
@@ -40,7 +44,6 @@ export class FileUpload {
40
44
  if (!this.options.createPresignedUpload || !this.options.completePresignedUpload) {
41
45
  throw new Error("Please configure danxOptions.fileUpload: import { configure } from 'quasar-ui-danx';");
42
46
  }
43
- this.prepare();
44
47
  }
45
48
 
46
49
  /**
@@ -69,6 +72,8 @@ export class FileUpload {
69
72
  isComplete: false
70
73
  });
71
74
  }
75
+
76
+ return this;
72
77
  }
73
78
 
74
79
  /**
@@ -110,7 +115,7 @@ export class FileUpload {
110
115
  /**
111
116
  * Handles the error events / fires the callback if it is set
112
117
  */
113
- errorHandler(e: InputEvent, file: UploadedFile, error = null) {
118
+ errorHandler(e: InputEvent | ProgressEvent, file: UploadedFile, error = null) {
114
119
  if (this.onErrorCb) {
115
120
  this.onErrorCb({ e, file, error });
116
121
  }
@@ -179,7 +184,8 @@ export class FileUpload {
179
184
  progress: file.progress,
180
185
  location: file.location,
181
186
  blobUrl: file.blobUrl,
182
- url: ""
187
+ url: "",
188
+ __type: "BrowserFile"
183
189
  };
184
190
  }
185
191
 
@@ -219,12 +225,13 @@ export class FileUpload {
219
225
  async (e) => {
220
226
  try {
221
227
  // First complete the presigned upload to get the updated file resource data
222
- const uploadedFile = await this.completePresignedUpload(fileUpload);
223
-
228
+ let storedFile = await this.completePresignedUpload(fileUpload);
229
+ storedFile = storeObject(storedFile);
224
230
  // Fire the file complete callbacks
225
- this.fireCompleteCallback(fileUpload, uploadedFile);
231
+ this.fireCompleteCallback(fileUpload, storedFile);
226
232
  this.checkAllComplete();
227
- } catch (error) {
233
+ await this.waitForTranscode(storedFile);
234
+ } catch (error: any) {
228
235
  this.errorHandler(e, fileUpload.file, error);
229
236
  }
230
237
  },
@@ -252,6 +259,49 @@ export class FileUpload {
252
259
  return await this.options.completePresignedUpload(fileUpload.file.resource_id);
253
260
  }
254
261
 
262
+ /**
263
+ * Refresh the file data, in case transcoding or some transient state is needed to be refreshed on the file
264
+ */
265
+ async refreshFile(file: UploadedFile): Promise<UploadedFile | null> {
266
+ if (!this.options.refreshFile) return null;
267
+
268
+ const storedFile = await this.options.refreshFile(file.id);
269
+
270
+ if (storedFile) {
271
+ return storeObject(storedFile);
272
+ }
273
+ return storedFile;
274
+ }
275
+
276
+ /**
277
+ * Checks if the file has a transcode in progress or pending
278
+ */
279
+ isTranscoding(file: UploadedFile) {
280
+ const metaTranscodes = file?.meta?.transcodes || [];
281
+
282
+ for (const transcodeName of Object.keys(metaTranscodes)) {
283
+ const transcode = metaTranscodes[transcodeName];
284
+ if (transcode.status === "Pending" || transcode.status === "In Progress") {
285
+ return true;
286
+ }
287
+ }
288
+ return false;
289
+ }
290
+
291
+ /**
292
+ * Keeps refreshing the file while there is transcoding in progress
293
+ */
294
+ async waitForTranscode(file: UploadedFile) {
295
+ // Only allow waiting for transcode 1 time per file
296
+ if (!file.meta || file.meta.is_waiting_transcode) return;
297
+ file.meta.is_waiting_transcode = true;
298
+ let currentFile: UploadedFile | null = file;
299
+ while (currentFile && this.isTranscoding(currentFile)) {
300
+ await sleep(1000);
301
+ currentFile = await this.refreshFile(currentFile);
302
+ }
303
+ }
304
+
255
305
  /**
256
306
  * Start uploading all files
257
307
  */
@@ -297,6 +347,7 @@ export class FileUpload {
297
347
 
298
348
  // Send all the XHR file uploads
299
349
  for (const fileUpload of this.fileUploads) {
350
+ // @ts-expect-error XHRFileUpload has a xhr property
300
351
  fileUpload.xhr?.send(fileUpload.body);
301
352
  }
302
353
  }
@@ -1,7 +1,7 @@
1
1
  import { useDebounceFn } from "@vueuse/core";
2
2
  import { uid } from "quasar";
3
3
  import { isReactive, Ref, shallowRef } from "vue";
4
- import { ActionOptions, ActionTarget, AnyObject } from "../types";
4
+ import type { ActionOptions, ActionOptionsPartial, ActionTarget, AnyObject, ResourceAction } from "../types";
5
5
  import { FlashMessages } from "./FlashMessages";
6
6
  import { storeObject } from "./objectStore";
7
7
 
@@ -10,39 +10,40 @@ export const activeActionVnode: Ref = shallowRef(null);
10
10
  /**
11
11
  * Hook to perform an action on a set of targets
12
12
  * This helper allows you to perform actions by name on a set of targets using a provided list of actions
13
- *
14
- * @param actions
15
- * @param {ActionOptions | null} globalOptions
16
13
  */
17
- export function useActions(actions: ActionOptions[], globalOptions: ActionOptions | null = null) {
14
+ export function useActions(actions: ActionOptions[], globalOptions: ActionOptionsPartial | null = null) {
18
15
  const namespace = uid();
19
16
 
20
17
  /**
21
18
  * Resolve the action object based on the provided name (or return the object if the name is already an object)
22
19
  */
23
- function getAction(action: string | ActionOptions): ActionOptions {
24
- if (typeof action === "string") {
25
- action = actions.find(a => a.name === action) || { name: action };
20
+ function getAction(actionName: string | ActionOptions | ResourceAction): ResourceAction {
21
+ let actionOptions: ActionOptions | ResourceAction;
22
+
23
+ /// Resolve the action options or resource action based on the provided input
24
+ if (typeof actionName === "string") {
25
+ actionOptions = actions.find(a => a.name === actionName) || { name: actionName };
26
+ } else {
27
+ actionOptions = actionName;
26
28
  }
27
29
 
28
30
  // If the action is already reactive, return it
29
- if (isReactive(action) && action.__type) return action;
31
+ if (isReactive(actionOptions) && "__type" in actionOptions) return actionOptions as ResourceAction;
30
32
 
31
- action = { ...globalOptions, ...action };
33
+ const resourceAction: ResourceAction = storeObject({
34
+ ...globalOptions,
35
+ ...actionOptions,
36
+ trigger: (target, input) => performAction(resourceAction, target, input),
37
+ isApplying: false,
38
+ __type: "__Action:" + namespace
39
+ });
32
40
 
33
41
  // Assign Trigger function if it doesn't exist
34
- if (!action.trigger) {
35
- if (action.debounce) {
36
- action.trigger = useDebounceFn((target, input) => performAction(action, target, input), action.debounce);
37
- } else {
38
- action.trigger = (target, input) => performAction(action, target, input);
39
- }
42
+ if (actionOptions.debounce) {
43
+ resourceAction.trigger = useDebounceFn((target, input) => performAction(resourceAction, target, input), actionOptions.debounce);
40
44
  }
41
45
 
42
- // Set the initial state for the action
43
- action.isApplying = false;
44
-
45
- return storeObject({ ...action, __type: "__Action:" + namespace });
46
+ return resourceAction;
46
47
  }
47
48
 
48
49
  /**
@@ -52,17 +53,17 @@ export function useActions(actions: ActionOptions[], globalOptions: ActionOption
52
53
  * @param filters
53
54
  * @returns {ActionOptions[]}
54
55
  */
55
- function getActions(filters?: AnyObject): ActionOptions[] {
56
+ function getActions(filters?: AnyObject): ResourceAction[] {
56
57
  let filteredActions = [...actions];
57
58
 
58
59
  if (filters) {
59
- for (const filter of Object.keys(filters)) {
60
- const filterValue = filters[filter];
61
- filteredActions = filteredActions.filter((a: AnyObject) => a[filter] === filterValue || (Array.isArray(filterValue) && filterValue.includes(a[filter])));
60
+ for (const filterKey of Object.keys(filters)) {
61
+ const filterValue = filters[filterKey];
62
+ filteredActions = filteredActions.filter((a: AnyObject) => a[filterKey] === filterValue || (Array.isArray(filterValue) && filterValue.includes(a[filterKey])));
62
63
  }
63
64
  }
64
65
 
65
- return filteredActions.map((a: AnyObject) => getAction(a));
66
+ return filteredActions.map((a: ActionOptions) => getAction(a));
66
67
  }
67
68
 
68
69
  /**
@@ -72,8 +73,7 @@ export function useActions(actions: ActionOptions[], globalOptions: ActionOption
72
73
  * @param {object[]|object} target - an array of targets or a single target object
73
74
  * @param {any} input - The input data to pass to the action handler
74
75
  */
75
- async function performAction(action: string | ActionOptions, target: ActionTarget = null, input: any = null) {
76
- action = getAction(action);
76
+ async function performAction(action: ResourceAction, target: ActionTarget = null, input: any = null) {
77
77
  // Resolve the original action, if the current action is an alias
78
78
  const aliasedAction = action.alias ? getAction(action.alias) : null;
79
79
 
@@ -1,5 +1,5 @@
1
1
  import { parseDateTime } from "./formats";
2
2
 
3
- export function diffInDays(date1, date2) {
4
- return parseDateTime(date2).diff(parseDateTime(date1), ["days"]).days;
3
+ export function diffInDays(date1: string, date2: string) {
4
+ return parseDateTime(date2).diff(parseDateTime(date1), ["days"]).days;
5
5
  }
@@ -23,6 +23,7 @@ export function download(data: any, strFileName?: string, strMimeType?: string)
23
23
 
24
24
  var anchor = document.createElement("a");
25
25
 
26
+ // @ts-ignore
26
27
  var toString = function (a) {
27
28
  return String(a);
28
29
  };
@@ -35,8 +36,10 @@ export function download(data: any, strFileName?: string, strMimeType?: string)
35
36
  var blob;
36
37
 
37
38
  var reader;
39
+ // @ts-ignore
38
40
  myBlob = myBlob.call ? myBlob.bind(self) : Blob;
39
41
 
42
+ // @ts-ignore
40
43
  if (String(this) === "true") {
41
44
  // reverse arguments, allowing download.bind(true, "text/xml", "export.xml") to act as a callback
42
45
  payload = [payload, mimeType];
@@ -64,6 +67,7 @@ export function download(data: any, strFileName?: string, strMimeType?: string)
64
67
  };
65
68
  ajax.onerror = function (e) {
66
69
  // As a fallback, just open the request in a new tab
70
+ // @ts-ignore
67
71
  window.open(url, "_blank").focus();
68
72
  };
69
73
  setTimeout(function () {
@@ -77,6 +81,7 @@ export function download(data: any, strFileName?: string, strMimeType?: string)
77
81
 
78
82
  // go ahead and download dataURLs right away
79
83
  if (/^data:[\w+-]+\/[\w+-]+[,;]/.test(payload)) {
84
+ // @ts-ignore
80
85
  if (payload.length > 1024 * 1024 * 1.999 && myBlob !== toString) {
81
86
  payload = dataUrlToBlob(payload);
82
87
  mimeType = payload.type || defaultMime;
@@ -93,13 +98,14 @@ export function download(data: any, strFileName?: string, strMimeType?: string)
93
98
  ? payload
94
99
  : new myBlob([payload], { type: mimeType });
95
100
 
96
- function dataUrlToBlob(strUrl) {
101
+ function dataUrlToBlob(strUrl: string) {
97
102
  var parts = strUrl.split(/[:;,]/);
98
103
 
99
104
  var type = parts[1];
100
105
 
101
106
  var decoder = parts[2] === "base64" ? atob : decodeURIComponent;
102
107
 
108
+ // @ts-ignore
103
109
  var binData = decoder(parts.pop());
104
110
 
105
111
  var mx = binData.length;
@@ -113,7 +119,7 @@ export function download(data: any, strFileName?: string, strMimeType?: string)
113
119
  return new myBlob([uiArr], { type: type });
114
120
  }
115
121
 
116
- function saver(url, winMode) {
122
+ function saver(url: string, winMode: boolean | string) {
117
123
  if ("download" in anchor) {
118
124
  // html5 A[download]
119
125
  anchor.href = url;
@@ -1,4 +1,5 @@
1
1
  import { DateTime, IANAZone } from "luxon";
2
+ import { parse as parseYAML, stringify as stringifyYAML } from "yaml";
2
3
  import { ActionTargetItem, fDateOptions } from "../types";
3
4
  import { isJSON } from "./utils";
4
5
 
@@ -335,10 +336,56 @@ export function fJSON(string: string | object) {
335
336
  }
336
337
  }
337
338
 
338
- export function fMarkdownJSON(string: string | object): string {
339
- if (isJSON(string)) {
340
- return `\`\`\`json\n${fJSON(string)}\n\`\`\``;
339
+ /**
340
+ * Convert markdown formatted string into a valid JSON object
341
+ */
342
+ export function parseMarkdownJSON(string: string | object): object | null | undefined {
343
+ if (typeof string === "object") return string as object;
344
+
345
+ try {
346
+ return JSON.parse(parseMarkdownCode(string));
347
+ } catch (e) {
348
+ return undefined;
341
349
  }
342
- // @ts-expect-error Guaranteed to only allow strings here using isJSON check
343
- return string;
350
+ }
351
+
352
+ export function parseMarkdownYAML(string: string): object | null | undefined {
353
+ try {
354
+ return parseYAML(parseMarkdownCode(string)) || (string ? undefined : null);
355
+ } catch (e) {
356
+ return undefined;
357
+ }
358
+ }
359
+
360
+ /**
361
+ * Parse a markdown formatted string and return the code block content
362
+ */
363
+ export function parseMarkdownCode(string: string): string {
364
+ return string.replace(/^```[a-z0-9]{1,6}\s/, "").replace(/```$/, "");
365
+ }
366
+
367
+ /**
368
+ * Convert a JSON object or string of code into a markdown formatted JSON string
369
+ * ie: a valid JSON string with a ```json prefix and ``` postfix
370
+ */
371
+ export function fMarkdownCode(type: string, string: string | object): string {
372
+ if (typeof string === "object" || isJSON(string)) {
373
+ switch (type) {
374
+ case "yaml":
375
+ string = stringifyYAML(string);
376
+ break;
377
+ case "ts":
378
+ string = "";
379
+ break;
380
+ default:
381
+ string = fJSON(string);
382
+ }
383
+ }
384
+
385
+ const regex = new RegExp(`\`\`\`${type}`, "g");
386
+ if (!((string || "") as string).match(regex)) {
387
+ return `\`\`\`${type}\n${string}\n\`\`\``;
388
+ }
389
+
390
+ return string as string;
344
391
  }
@@ -16,14 +16,16 @@ export function useMultiFileUpload(options?: FileUploadOptions) {
16
16
  const onFilesSelected = (e: any) => {
17
17
  uploadedFiles.value = [...uploadedFiles.value, ...e.target.files];
18
18
  new FileUpload(e.target.files, options)
19
- .onProgress(({ file }: { file: UploadedFile }) => {
20
- updateFileInList(file);
19
+ .prepare()
20
+ .onProgress(({ file }) => {
21
+ file && updateFileInList(file);
21
22
  })
22
23
  .onComplete(({ file, uploadedFile }) => {
23
24
  file && updateFileInList(file, uploadedFile);
24
25
  })
25
- .onError(({ file }: { file: UploadedFile }) => {
26
- FlashMessages.error(`Failed to upload ${file.name}`);
26
+ .onError(({ file, error }) => {
27
+ console.error("Failed to upload", file, error);
28
+ FlashMessages.error(`Failed to upload ${file.name}: ${error}`);
27
29
  })
28
30
  .onAllComplete(() => {
29
31
  onCompleteCb.value && onCompleteCb.value({
@@ -1,4 +1,4 @@
1
- import { reactive, UnwrapNestedRefs } from "vue";
1
+ import { ShallowReactive, shallowReactive } from "vue";
2
2
  import { TypedObject } from "../types";
3
3
 
4
4
  const store = new Map<string, any>();
@@ -6,27 +6,25 @@ const store = new Map<string, any>();
6
6
  /**
7
7
  * Store an object in the object store via type + id
8
8
  * Returns the stored object that should be used instead of the passed object as the returned object is shared across the system
9
- *
10
- * @param {TypedObject} newObject
11
- * @returns {TypedObject}
12
9
  */
13
- export function storeObject<T extends TypedObject>(newObject: T): UnwrapNestedRefs<T> {
10
+ export function storeObject<T extends TypedObject>(newObject: T): ShallowReactive<T> {
14
11
  const id = newObject.id || newObject.name;
15
12
  const type = newObject.__type;
16
- if (!id || !type) return reactive(newObject);
13
+ if (!id || !type) return shallowReactive(newObject);
14
+
15
+ if (!newObject.__timestamp) {
16
+ newObject.__timestamp = newObject.updated_at || 0;
17
+ }
17
18
 
18
19
  const objectKey = `${type}:${id}`;
19
20
 
20
- const oldObject: UnwrapNestedRefs<T> | undefined = store.get(objectKey);
21
+ // Retrieve the existing object if it already exists in the store
22
+ const oldObject = store.get(objectKey);
21
23
 
22
- // Apply all properties from newObject to oldObject then store and return the updated object
23
- if (oldObject) {
24
- const oldTimestamp = oldObject.__timestamp || oldObject.updated_at;
25
- const newTimestamp = newObject.__timestamp || newObject.updated_at;
26
- // If the old object is newer, do not store the new object, just return the old
27
- if (oldTimestamp && newTimestamp && newTimestamp < oldTimestamp) {
28
- return oldObject;
29
- }
24
+ // If an old object exists, and it is newer than the new object, do not store the new object, just return the old
25
+ // @ts-expect-error __timestamp is guaranteed to be set in this case on both old and new
26
+ if (oldObject && newObject.__timestamp <= oldObject.__timestamp) {
27
+ return oldObject;
30
28
  }
31
29
 
32
30
  // Recursively store all the children of the object as well
@@ -41,12 +39,11 @@ export function storeObject<T extends TypedObject>(newObject: T): UnwrapNestedRe
41
39
 
42
40
  // Update the old object with the new object properties
43
41
  if (oldObject) {
44
- // If the new object is newer, apply all properties from the new object to the old object
45
42
  Object.assign(oldObject, newObject);
46
43
  return oldObject;
47
44
  }
48
45
 
49
- const reactiveObject = reactive(newObject);
46
+ const reactiveObject = shallowReactive(newObject);
50
47
  store.set(objectKey, reactiveObject);
51
48
  return reactiveObject;
52
49
  }
@@ -1,6 +1,7 @@
1
1
  import { Ref } from "vue";
2
2
  import { danxOptions } from "../config";
3
3
  import { HttpResponse, RequestApi } from "../types";
4
+ import { sleep } from "./utils";
4
5
 
5
6
  /**
6
7
  * A simple request helper that wraps the fetch API
@@ -70,6 +71,17 @@ export const request: RequestApi = {
70
71
 
71
72
  return result;
72
73
  },
74
+
75
+ async poll(url: string, options, interval, fnUntil) {
76
+ let response;
77
+ do {
78
+ response = await request.call(url, options);
79
+ await sleep(interval);
80
+ } while (!fnUntil(response));
81
+
82
+ return response;
83
+ },
84
+
73
85
  async get(url, options) {
74
86
  return await request.call(url, {
75
87
  method: "get",
@@ -1,60 +1,68 @@
1
1
  import { computed, Ref, ref } from "vue";
2
- import { FileUpload, FileUploadOptions, UploadedFile } from "./FileUpload";
2
+ import { FileUploadOptions, UploadedFile } from "../types";
3
+ import { FileUpload } from "./FileUpload";
4
+ import { FlashMessages } from "./FlashMessages";
3
5
 
4
- interface FileUploadCallbackParams {
5
- file: UploadedFile;
6
- uploadedFile: UploadedFile;
7
- }
6
+ type FileUploadCallback = ((file: UploadedFile | null) => void) | null;
8
7
 
9
8
  export function useSingleFileUpload(options: FileUploadOptions | null = null) {
10
- const uploadedFile: Ref<UploadedFile | null> = ref(null);
11
- const onCompleteCb: Ref<Function | null> = ref(null);
12
- const onFileChangeCb: Ref<Function | null> = ref(null);
13
-
14
- const onFileSelected = (e: any) => {
15
- uploadedFile.value = null;
16
- new FileUpload(e.target?.files[0], options)
17
- .onProgress(({ file }: FileUploadCallbackParams) => {
18
- uploadedFile.value = file;
19
- onFileChangeCb.value && onFileChangeCb.value(uploadedFile.value);
20
- })
21
- .onComplete(({ uploadedFile: completedFile }: FileUploadCallbackParams) => {
22
- uploadedFile.value = completedFile;
23
- onCompleteCb.value && onCompleteCb.value(uploadedFile.value);
24
- onFileChangeCb.value && onFileChangeCb.value(uploadedFile.value);
25
- })
26
- .upload();
27
- };
28
-
29
- const onDrop = (e: InputEvent) => {
30
- onFileSelected({ target: { files: e.dataTransfer?.files } });
31
- };
32
-
33
- const isFileUploaded = computed(() => {
34
- return uploadedFile.value && uploadedFile.value.url;
35
- });
36
-
37
- const onFileChange = (cb: Function) => {
38
- onFileChangeCb.value = cb;
39
- };
40
-
41
- const onComplete = (cb: Function) => {
42
- onCompleteCb.value = cb;
43
- };
44
-
45
- const clearUploadedFile = () => {
46
- uploadedFile.value = null;
47
- onFileChangeCb.value && onFileChangeCb.value(uploadedFile.value);
48
- onCompleteCb.value && onCompleteCb.value(uploadedFile.value);
49
- };
50
-
51
- return {
52
- isFileUploaded,
53
- clearUploadedFile,
54
- onComplete,
55
- onFileChange,
56
- onDrop,
57
- onFileSelected,
58
- uploadedFile
59
- };
9
+ const uploadedFile: Ref<UploadedFile | null | undefined> = ref(null);
10
+ const onCompleteCb: Ref<FileUploadCallback> = ref(null);
11
+ const onFileChangeCb: Ref<FileUploadCallback> = ref(null);
12
+
13
+ const onFileSelected = (e: any) => {
14
+ uploadedFile.value = null;
15
+ new FileUpload(e.target?.files[0], options)
16
+ .prepare()
17
+ .onProgress(({ file }) => {
18
+ if (file) {
19
+ uploadedFile.value = file;
20
+ onFileChangeCb.value && onFileChangeCb.value(uploadedFile.value);
21
+ }
22
+ })
23
+ .onComplete(({ uploadedFile: completedFile }) => {
24
+ if (completedFile) {
25
+ uploadedFile.value = completedFile;
26
+ onCompleteCb.value && onCompleteCb.value(uploadedFile.value);
27
+ onFileChangeCb.value && onFileChangeCb.value(uploadedFile.value);
28
+ }
29
+ })
30
+ .onError(({ file, error }) => {
31
+ console.error("Failed to upload", file, error);
32
+ FlashMessages.error(`Failed to upload ${file.name}: ${error}`);
33
+ })
34
+ .upload();
35
+ };
36
+
37
+ const onDrop = (e: InputEvent) => {
38
+ onFileSelected({ target: { files: e.dataTransfer?.files } });
39
+ };
40
+
41
+ const isFileUploaded = computed(() => {
42
+ return uploadedFile.value && uploadedFile.value.url;
43
+ });
44
+
45
+ const onFileChange = (cb: FileUploadCallback) => {
46
+ onFileChangeCb.value = cb;
47
+ };
48
+
49
+ const onComplete = (cb: FileUploadCallback) => {
50
+ onCompleteCb.value = cb;
51
+ };
52
+
53
+ const clearUploadedFile = () => {
54
+ uploadedFile.value = null;
55
+ onFileChangeCb.value && onFileChangeCb.value(uploadedFile.value);
56
+ onCompleteCb.value && onCompleteCb.value(uploadedFile.value);
57
+ };
58
+
59
+ return {
60
+ isFileUploaded,
61
+ clearUploadedFile,
62
+ onComplete,
63
+ onFileChange,
64
+ onDrop,
65
+ onFileSelected,
66
+ uploadedFile
67
+ };
60
68
  }
@@ -11,6 +11,15 @@ export function sleep(delay: number) {
11
11
  return new Promise((resolve) => setTimeout(resolve, delay));
12
12
  }
13
13
 
14
+ /**
15
+ * Poll a callback function until the result is true
16
+ */
17
+ export async function pollUntil(callback: () => any, interval = 1000) {
18
+ while (!(await callback())) {
19
+ await sleep(interval);
20
+ }
21
+ }
22
+
14
23
  /**
15
24
  * Wait for a ref to have a value and then resolve the promise
16
25
  */
@@ -82,12 +91,12 @@ export function incrementName(name: string) {
82
91
  * Check if a string is a valid JSON object. If an object is passed, always return true
83
92
  */
84
93
  export function isJSON(string: string | object) {
85
- if (!string) {
86
- return false;
87
- }
88
94
  if (typeof string === "object") {
89
95
  return true;
90
96
  }
97
+ if (!string) {
98
+ return false;
99
+ }
91
100
  try {
92
101
  JSON.parse(string);
93
102
  return true;
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export * from "./vue-plugin";
@@ -0,0 +1,5 @@
1
+ .dx-action-table {
2
+ .dx-column-shrink {
3
+ width: 1px;
4
+ }
5
+ }
@@ -5,3 +5,4 @@
5
5
  @import "quasar-reset";
6
6
  @import "general";
7
7
  @import "transitions";
8
+ @import "danx";