quasar-ui-danx 0.0.10 → 0.0.12

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.
Files changed (89) hide show
  1. package/package.json +8 -2
  2. package/src/components/ActionTable/ActionTable.vue +143 -0
  3. package/src/components/ActionTable/BatchActionMenu.vue +60 -0
  4. package/src/components/ActionTable/EmptyTableState.vue +33 -0
  5. package/src/components/ActionTable/Filters/CollapsableFiltersSidebar.vue +36 -0
  6. package/src/components/ActionTable/Filters/FilterGroupItem.vue +28 -0
  7. package/src/components/ActionTable/Filters/FilterGroupList.vue +76 -0
  8. package/src/components/ActionTable/Filters/FilterListToggle.vue +50 -0
  9. package/src/components/ActionTable/Filters/FilterableField.vue +143 -0
  10. package/src/components/ActionTable/Filters/index.ts +5 -0
  11. package/src/components/ActionTable/Form/Fields/BooleanField.vue +37 -0
  12. package/src/components/ActionTable/Form/Fields/ConfirmPasswordField.vue +46 -0
  13. package/src/components/ActionTable/Form/Fields/DateField.vue +59 -0
  14. package/src/components/ActionTable/Form/Fields/DateRangeField.vue +110 -0
  15. package/src/components/ActionTable/Form/Fields/DateTimeField.vue +50 -0
  16. package/src/components/ActionTable/Form/Fields/DateTimePicker.vue +59 -0
  17. package/src/components/ActionTable/Form/Fields/EditableDiv.vue +39 -0
  18. package/src/components/ActionTable/Form/Fields/FieldLabel.vue +32 -0
  19. package/src/components/ActionTable/Form/Fields/FileUploadButton.vue +78 -0
  20. package/src/components/ActionTable/Form/Fields/InlineDateTimeField.vue +44 -0
  21. package/src/components/ActionTable/Form/Fields/IntegerField.vue +26 -0
  22. package/src/components/ActionTable/Form/Fields/LabelValueBlock.vue +22 -0
  23. package/src/components/ActionTable/Form/Fields/LabeledInput.vue +63 -0
  24. package/src/components/ActionTable/Form/Fields/MultiFileField.vue +91 -0
  25. package/src/components/ActionTable/Form/Fields/MultiKeywordField.vue +57 -0
  26. package/src/components/ActionTable/Form/Fields/NewPasswordField.vue +39 -0
  27. package/src/components/ActionTable/Form/Fields/NumberField.vue +94 -0
  28. package/src/components/ActionTable/Form/Fields/NumberRangeField.vue +140 -0
  29. package/src/components/ActionTable/Form/Fields/SelectDrawer.vue +136 -0
  30. package/src/components/ActionTable/Form/Fields/SelectField.vue +318 -0
  31. package/src/components/ActionTable/Form/Fields/SelectWithChildrenField.vue +81 -0
  32. package/src/components/ActionTable/Form/Fields/SingleFileField.vue +78 -0
  33. package/src/components/ActionTable/Form/Fields/TextField.vue +82 -0
  34. package/src/components/ActionTable/Form/Fields/WysiwygField.vue +46 -0
  35. package/src/components/ActionTable/Form/Fields/index.ts +23 -0
  36. package/src/components/ActionTable/Form/RenderedForm.vue +76 -0
  37. package/src/components/ActionTable/Form/index.ts +2 -0
  38. package/src/components/ActionTable/RenderComponentColumn.vue +22 -0
  39. package/src/components/ActionTable/TableSummaryRow.vue +95 -0
  40. package/src/components/ActionTable/index.ts +10 -0
  41. package/src/components/ActionTable/listActions.ts +362 -0
  42. package/src/components/ActionTable/listHelpers.ts +74 -0
  43. package/src/components/ActionTable/tableColumns.ts +72 -0
  44. package/src/components/DragAndDrop/HandleDraggable.vue +29 -29
  45. package/src/components/DragAndDrop/ListItemDraggable.vue +10 -10
  46. package/src/components/DragAndDrop/index.ts +0 -1
  47. package/src/components/DragAndDrop/listDragAndDrop.ts +1 -1
  48. package/src/components/Utility/CollapsableSidebar.vue +119 -0
  49. package/src/components/Utility/ContentDrawer.vue +70 -0
  50. package/src/components/Utility/Dialogs/ConfirmDialog.vue +132 -0
  51. package/src/components/Utility/Dialogs/FullScreenDialog.vue +46 -0
  52. package/src/components/Utility/Dialogs/FullscreenCarouselDialog.vue +105 -0
  53. package/src/components/Utility/Dialogs/InfoDialog.vue +92 -0
  54. package/src/components/Utility/Dialogs/InputDialog.vue +35 -0
  55. package/src/components/Utility/ImagePreview.vue +192 -0
  56. package/src/components/Utility/Popover/PopoverMenu.vue +64 -0
  57. package/src/components/Utility/Transitions/ListTransition.vue +50 -0
  58. package/src/components/Utility/Transitions/SlideTransition.vue +63 -0
  59. package/src/components/Utility/Transitions/StaggeredListTransition.vue +97 -0
  60. package/src/components/Utility/index.ts +11 -0
  61. package/src/components/index.ts +3 -0
  62. package/src/helpers/FileUpload.ts +295 -0
  63. package/src/helpers/FlashMessages.ts +79 -0
  64. package/src/helpers/array.ts +37 -0
  65. package/src/helpers/compatibility.ts +64 -0
  66. package/src/helpers/date.ts +5 -0
  67. package/src/helpers/download.ts +200 -0
  68. package/src/helpers/downloadPdf.ts +92 -0
  69. package/src/helpers/files.ts +52 -0
  70. package/src/helpers/formats.ts +183 -0
  71. package/src/helpers/http.ts +62 -0
  72. package/src/helpers/index.ts +12 -1
  73. package/src/helpers/multiFileUpload.ts +68 -0
  74. package/src/helpers/singleFileUpload.ts +54 -0
  75. package/src/helpers/storage.ts +8 -0
  76. package/src/index.esm.js +3 -4
  77. package/src/svg/FilterIcon.svg +7 -0
  78. package/src/svg/ImageIcon.svg +30 -0
  79. package/src/svg/PdfIcon.svg +21 -0
  80. package/src/svg/PercentIcon.svg +13 -0
  81. package/src/svg/TrashIcon.svg +15 -0
  82. package/src/svg/XIcon.svg +18 -0
  83. package/src/svg/index.ts +8 -0
  84. package/src/vendor/tinymce-config.ts +1 -0
  85. package/src/vue-plugin.js +7 -4
  86. package/tsconfig.json +14 -13
  87. package/src/components/DragAndDrop/Icons/index.ts +0 -2
  88. /package/src/{components/DragAndDrop/Icons → svg}/DragHandleDotsIcon.svg +0 -0
  89. /package/src/{components/DragAndDrop/Icons → svg}/DragHandleIcon.svg +0 -0
@@ -0,0 +1,295 @@
1
+ import { FlashMessages, resolveFileLocation } from "@ui/helpers";
2
+ import { uid } from "quasar";
3
+
4
+ export type FileUploadOptions = {
5
+ directory: string,
6
+ presignedUploadUrl: (...params) => "",
7
+ uploadCompletedUrl: (...params) => "",
8
+ };
9
+
10
+ export class FileUpload {
11
+ files: { id: string, blobUrl: string }[] = [];
12
+ fileUploads = [];
13
+ onErrorCb = null;
14
+ onProgressCb = null;
15
+ onCompleteCb = null;
16
+ onAllCompleteCb = null;
17
+ options: FileUploadOptions = {
18
+ directory: "file-upload",
19
+ presignedUploadUrl: null,
20
+ uploadCompletedUrl: null
21
+ };
22
+
23
+ constructor(files, options: FileUploadOptions) {
24
+ if (!Array.isArray(files) && !(files instanceof FileList)) {
25
+ files = [files];
26
+ }
27
+ this.files = files;
28
+ this.fileUploads = [];
29
+ this.onErrorCb = null;
30
+ this.onProgressCb = null;
31
+ this.onCompleteCb = null;
32
+ this.onAllCompleteCb = null;
33
+
34
+ this.options = {
35
+ ...this.options,
36
+ ...options
37
+ };
38
+ this.prepare();
39
+ }
40
+
41
+ /**
42
+ * Prepares all files for upload and provides an id and blobUrl for each file
43
+ */
44
+ prepare() {
45
+ // Prepare required attributes
46
+ for (const file of this.files) {
47
+ if (!(file instanceof File)) {
48
+ throw Error(
49
+ "FileUpload constructor requires a File object or a list of File objects"
50
+ );
51
+ }
52
+
53
+ file.id = uid();
54
+ file.blobUrl = URL.createObjectURL(file);
55
+
56
+ // Prepare FormData
57
+ const formData = new FormData();
58
+ formData.append("file", file);
59
+
60
+ this.fileUploads.push({
61
+ file,
62
+ xhr: null, // NOTE: The XHR will be setup asynchronously right before sending file uploads
63
+ formData,
64
+ isComplete: false
65
+ });
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Callback for when all files have been uploaded
71
+ */
72
+ onAllComplete(cb) {
73
+ this.onAllCompleteCb = cb;
74
+ return this;
75
+ }
76
+
77
+ /**
78
+ * Callback fired once for each file upon successful completion of upload
79
+ * @param cb
80
+ * @returns {FileUpload}
81
+ */
82
+ onComplete(cb) {
83
+ this.onCompleteCb = cb;
84
+ return this;
85
+ }
86
+
87
+ /**
88
+ * Callback fired each time there is an upload progress update for a file
89
+ * @param cb
90
+ * @returns {FileUpload}
91
+ */
92
+ onProgress(cb) {
93
+ this.onProgressCb = cb;
94
+ return this;
95
+ }
96
+
97
+ /**
98
+ * Callback fired when an error occurs during upload
99
+ * @param cb
100
+ * @returns {FileUpload}
101
+ */
102
+ onError(cb) {
103
+ this.onErrorCb = cb;
104
+ return this;
105
+ }
106
+
107
+ /**
108
+ * Handles the error events / fires the callback if it is set
109
+ * @param e
110
+ * @param file
111
+ * @param error
112
+ */
113
+ errorHandler(e, file, error = null) {
114
+ if (this.onErrorCb) {
115
+ this.onErrorCb({ e, file, error });
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Resolve the locations of all the files
121
+ * @returns {Promise<FileUpload>}
122
+ */
123
+ async resolveLocation(waitMessage = null) {
124
+ for (const fileUpload of this.fileUploads) {
125
+ fileUpload.file.location = await resolveFileLocation(
126
+ fileUpload.file,
127
+ waitMessage
128
+ );
129
+ fileUpload.formData.append(
130
+ "meta",
131
+ JSON.stringify(fileUpload.file.location)
132
+ );
133
+ }
134
+ return this;
135
+ }
136
+
137
+ /**
138
+ * Fires the progress callback
139
+ * @param fileUpload
140
+ * @param progress
141
+ */
142
+ fireProgressCallback(fileUpload, progress) {
143
+ fileUpload.file.progress = progress;
144
+ this.onProgressCb && this.onProgressCb({ file: this.wrapFile(fileUpload.file), progress });
145
+ }
146
+
147
+ /**
148
+ * Fires the complete callback
149
+ * @param fileUpload
150
+ * @param uploadedFile
151
+ */
152
+ fireCompleteCallback(fileUpload, uploadedFile) {
153
+ fileUpload.isComplete = true;
154
+ fileUpload.file.progress = 1;
155
+ this.onCompleteCb && this.onCompleteCb({ file: this.wrapFile(fileUpload.file), uploadedFile });
156
+ }
157
+
158
+ /**
159
+ * Check if all files have been uploaded and call the callback if they have
160
+ */
161
+ checkAllComplete() {
162
+ if (this.onAllCompleteCb) {
163
+ if (this.fileUploads.every((fileUpload) => fileUpload.isComplete)) {
164
+ this.onAllCompleteCb({ files: this.fileUploads });
165
+ }
166
+ }
167
+ }
168
+
169
+ /**
170
+ * Returns a native JS object that is easier to work with than the File objects (no weird behavior of missing
171
+ * properties, easily printable, etc.)
172
+ * @param file
173
+ * @returns {{size, name, progress, location, blobUrl: *, id, type}}
174
+ */
175
+ wrapFile(file) {
176
+ return {
177
+ id: file.id,
178
+ name: file.name,
179
+ size: file.size,
180
+ type: file.type,
181
+ progress: file.progress,
182
+ location: file.location,
183
+ blobUrl: file.blobUrl
184
+ };
185
+ }
186
+
187
+ /**
188
+ * Registers all the callbacks requested for the XHR / post-processing of file uploads
189
+ */
190
+ setXhrCallbacks() {
191
+ // Set the error callbacks
192
+ for (const fileUpload of this.fileUploads) {
193
+ fileUpload.xhr.addEventListener(
194
+ "error",
195
+ (e) => this.errorHandler(e, fileUpload.file),
196
+ false
197
+ );
198
+ }
199
+
200
+ // Set the progress callbacks
201
+ if (this.onProgressCb) {
202
+ for (const fileUpload of this.fileUploads) {
203
+ fileUpload.xhr.upload.addEventListener(
204
+ "progress",
205
+ (e) => {
206
+ // Max of 95%, so we can indicate we are completing the signed URL process
207
+ const progress = Math.min(.95, e.loaded / e.total);
208
+ this.fireProgressCallback(fileUpload, progress);
209
+ },
210
+ false
211
+ );
212
+ }
213
+ }
214
+
215
+ // Set the load callbacks which registers the Complete / All Complete callbacks and handles non-xhr related
216
+ // errors
217
+ for (const fileUpload of this.fileUploads) {
218
+ fileUpload.xhr.addEventListener(
219
+ "load",
220
+ async (e) => {
221
+ try {
222
+ // First complete the presigned upload to get the updated file resource data
223
+ const uploadedFile = await this.completePresignedUpload(fileUpload);
224
+
225
+ // Fire the file complete callbacks
226
+ this.fireCompleteCallback(fileUpload, uploadedFile);
227
+ this.checkAllComplete();
228
+ } catch (error) {
229
+ this.errorHandler(e, fileUpload.file, error);
230
+ }
231
+ },
232
+ false
233
+ );
234
+ }
235
+ }
236
+
237
+ /**
238
+ * Mark the presigned upload as completed and return the file resource from the platform server
239
+ * @param fileUpload
240
+ * @returns {Promise<void>}
241
+ */
242
+ async completePresignedUpload(fileUpload) {
243
+ // Show 95% as the last 5% will be to complete the presigned upload
244
+ this.fireProgressCallback(fileUpload, .95);
245
+
246
+ // Let the platform know the presigned upload is complete
247
+ return await fetch(this.options.uploadCompletedUrl(fileUpload.file.resource_id), { method: "POST" }).then(r => r.json());
248
+ }
249
+
250
+ /**
251
+ * Start uploading all files
252
+ */
253
+ async upload() {
254
+ for (const fileUpload of this.fileUploads) {
255
+ const mimeType = fileUpload.file.mimeType || fileUpload.file.type;
256
+ const presignedUrl = this.options.presignedUploadUrl(this.options.directory, fileUpload.file.name, mimeType);
257
+
258
+ // Fetch presigned upload URL
259
+ const fileResource = await fetch(presignedUrl).then(r => r.json());
260
+
261
+ if (!fileResource.url) {
262
+ FlashMessages.error("Could not fetch presigned upload URL for file " + fileUpload.file.name);
263
+ continue;
264
+ }
265
+
266
+ const isS3Upload = !fileResource.url.match("upload-presigned-url-contents");
267
+
268
+ // We need the file resource ID to complete the presigned upload
269
+ fileUpload.file.resource_id = fileResource.id;
270
+
271
+ // Prepare XHR request
272
+ const xhr = new XMLHttpRequest();
273
+
274
+ // The XHR request is different based on weather we're sending to S3 or the platform server
275
+ if (isS3Upload) {
276
+ xhr.open("PUT", fileResource.url);
277
+ xhr.setRequestHeader("Content-Type", mimeType);
278
+ fileUpload.body = fileUpload.file;
279
+ } else {
280
+ xhr.open("POST", fileResource.url);
281
+ fileUpload.body = fileUpload.formData;
282
+ }
283
+
284
+ fileUpload.xhr = xhr;
285
+ }
286
+
287
+ // Set all the callbacks on the XHR requests
288
+ this.setXhrCallbacks();
289
+
290
+ // Send all the XHR file uploads
291
+ for (const fileUpload of this.fileUploads) {
292
+ fileUpload.xhr.send(fileUpload.body);
293
+ }
294
+ }
295
+ }
@@ -0,0 +1,79 @@
1
+ import { watch } from "vue";
2
+
3
+ export class FlashMessages {
4
+ static notify;
5
+
6
+ static PROP_DEFINITIONS = {
7
+ successMsg: {
8
+ type: String,
9
+ default: ""
10
+ },
11
+ errorMsg: {
12
+ type: String,
13
+ default: ""
14
+ },
15
+ warningMsg: {
16
+ type: String,
17
+ default: ""
18
+ }
19
+ };
20
+
21
+ static enable(msgProps) {
22
+ FlashMessages.success(msgProps.successMsg);
23
+ FlashMessages.error(msgProps.errorMsg);
24
+ FlashMessages.warning(msgProps.warningMsg);
25
+ watch(() => msgProps.successMsg, FlashMessages.success);
26
+ watch(() => msgProps.errorMsg, FlashMessages.error);
27
+ watch(() => msgProps.warningMsg, FlashMessages.warning);
28
+ }
29
+
30
+ static send(message, options = {}) {
31
+ if (message) {
32
+ FlashMessages.notify({
33
+ message,
34
+ timeout: 2500,
35
+ color: "gray-base",
36
+ textColor: "white",
37
+ position: "top",
38
+ closeBtn: "X",
39
+ ...options
40
+ });
41
+ }
42
+ }
43
+
44
+ static success(message, options = {}) {
45
+ FlashMessages.send(message, {
46
+ color: "green-light",
47
+ textColor: "green-dark",
48
+ icon: "hero:check-circle",
49
+ ...options
50
+ });
51
+ }
52
+
53
+ static error(message, options = {}) {
54
+ FlashMessages.send(message, {
55
+ color: "red-light",
56
+ textColor: "red-dark",
57
+ icon: "hero:alert",
58
+ ...options
59
+ });
60
+ }
61
+
62
+ static warning(message, options = {}) {
63
+ FlashMessages.send(message, {
64
+ color: "yellow-lighter",
65
+ textColor: "yellow-base",
66
+ icon: "hero:alert",
67
+ ...options
68
+ });
69
+ }
70
+
71
+ static combine(type, messages, options = {}) {
72
+ FlashMessages[type](messages.map(m => typeof m === "string" ? m : (m.message || m.Message)).join("<br/>"), {
73
+ ...options,
74
+ html: true
75
+ });
76
+ }
77
+ }
78
+
79
+ export const notify = new FlashMessages();
@@ -0,0 +1,37 @@
1
+ /**
2
+ *
3
+ * @param array
4
+ * @param item
5
+ * @param newItem
6
+ * @returns {*[]}
7
+ */
8
+ export function replace(array, item, newItem = undefined) {
9
+ const index =
10
+ typeof item === "function" ? array.findIndex(item) : array.indexOf(item);
11
+ if (index === false) {
12
+ console.error("Item not found in array", item, array);
13
+ throw new Error("Item not found in array");
14
+ }
15
+ const newArray = [...array];
16
+ newItem !== undefined
17
+ ? newArray.splice(index, 1, newItem)
18
+ : newArray.splice(index, 1);
19
+ return newArray;
20
+ }
21
+
22
+ export function remove(array, item) {
23
+ return replace(array, item);
24
+ }
25
+
26
+ /**
27
+ * Remove duplicate items from an array using a callback to compare 2 elements
28
+ * @param array
29
+ * @param cb
30
+ * @returns {*}
31
+ */
32
+ export function uniqueBy(array, cb) {
33
+ return array.filter((a, index, self) => {
34
+ // Check if the current element 'a' is the first occurrence in the array
35
+ return index === self.findIndex((b) => cb(a, b));
36
+ });
37
+ }
@@ -0,0 +1,64 @@
1
+ import { sleep } from "@ui/helpers";
2
+ import { useGeolocation } from "@vueuse/core";
3
+ import { computed } from "vue";
4
+
5
+ let isLoaded = false;
6
+ let hasAlreadyWaited = false;
7
+ let geolocationError = null;
8
+ let hasLocation = null;
9
+ let geolocation = null;
10
+
11
+ export function useCompatibility(requestLocation = true) {
12
+ if (!isLoaded && requestLocation) {
13
+ const { coords, error, locatedAt } = useGeolocation();
14
+ geolocationError = error;
15
+ hasLocation = locatedAt;
16
+ geolocation = coords;
17
+ isLoaded = true;
18
+ }
19
+
20
+ const isLocationSupported = "geolocation" in navigator;
21
+
22
+ const location = computed(() => {
23
+ if (hasLocation?.value) {
24
+ return geolocation?.value;
25
+ }
26
+ return null;
27
+ });
28
+
29
+ const isCompatible = computed(() => !geolocationError?.value && !!hasLocation?.value);
30
+
31
+ /**
32
+ * Wait for location to be available and returns the location when it is or null after the wait period times out.
33
+ * @param maxWait
34
+ */
35
+ const waitForLocation = async (maxWait = 3000) => {
36
+ // We only should wait once, if we already waited and failed, its unlikely the location will be available at a
37
+ // later time
38
+ if (hasAlreadyWaited) {
39
+ return location;
40
+ }
41
+
42
+ hasAlreadyWaited = true;
43
+ let waitTime = 0;
44
+ while (!location.value) {
45
+ await sleep(100);
46
+ waitTime += 100;
47
+
48
+ if (waitTime > maxWait) {
49
+ return null;
50
+ }
51
+ }
52
+
53
+ return location;
54
+ };
55
+
56
+ return {
57
+ isLocationSupported,
58
+ isCompatible,
59
+ geolocationError,
60
+ hasLocation,
61
+ location,
62
+ waitForLocation
63
+ };
64
+ }
@@ -0,0 +1,5 @@
1
+ import { parseDateTime } from "@ui/helpers/formats";
2
+
3
+ export function diffInDays(date1, date2) {
4
+ return parseDateTime(date2).diff(parseDateTime(date1), ["days"]).days;
5
+ }
@@ -0,0 +1,200 @@
1
+ // download.js v4.2, by dandavis; 2008-2016. [CCBY2] see http://danml.com/download.html for tests/usage
2
+ // v1 landed a FF+Chrome compat way of downloading strings to local un-named files, upgraded to use a hidden frame and
3
+ // optional mime v2 added named files via a[download], msSaveBlob, IE (10+) support, and window.URL support for
4
+ // larger+faster saves than dataURLs v3 added dataURL and Blob Input, bind-toggle arity, and legacy dataURL fallback
5
+ // was improved with force-download mime and base64 support. 3.1 improved safari handling. v4 adds AMD/UMD, commonJS,
6
+ // and plain browser support v4.1 adds url download capability via solo URL argument (same domain/CORS only) v4.2 adds
7
+ // semantic variable names, long (over 2MB) dataURL support, and hidden by default temp anchors
8
+ // https://github.com/rndme/download
9
+
10
+ /* eslint-disable */
11
+ export function download(data, strFileName, strMimeType) {
12
+ var self = window;
13
+ // this script is only for browsers anyway...
14
+
15
+ var defaultMime = "application/octet-stream";
16
+ // this default mime also triggers iframe downloads
17
+
18
+ var mimeType = strMimeType || defaultMime;
19
+
20
+ var payload = data;
21
+
22
+ var url = !strFileName && !strMimeType && payload;
23
+
24
+ var anchor = document.createElement("a");
25
+
26
+ var toString = function (a) {
27
+ return String(a);
28
+ };
29
+
30
+ // @ts-ignore
31
+ var myBlob = self.Blob || self.MozBlob || self.WebKitBlob || toString;
32
+
33
+ var fileName = strFileName || "download";
34
+
35
+ var blob;
36
+
37
+ var reader;
38
+ myBlob = myBlob.call ? myBlob.bind(self) : Blob;
39
+
40
+ if (String(this) === "true") {
41
+ // reverse arguments, allowing download.bind(true, "text/xml", "export.xml") to act as a callback
42
+ payload = [payload, mimeType];
43
+ mimeType = payload[0];
44
+ payload = payload[1];
45
+ }
46
+
47
+ if (url && url.length < 2048) {
48
+ // if no filename and no mime, assume a url was passed as the only argument
49
+ fileName = url.split("/").pop().split("?")[0];
50
+ anchor.href = url; // assign href prop to temp anchor
51
+
52
+ // if the browser determines that it's a potentially valid url path:
53
+ if (
54
+ anchor.href.indexOf(url) !== -1 ||
55
+ anchor.href.indexOf(encodeURI(url)) !== -1 ||
56
+ anchor.href === encodeURI(url)
57
+ ) {
58
+ var ajax = new XMLHttpRequest();
59
+ ajax.open("GET", url + "?no-cache=" + Date.now(), true);
60
+ ajax.responseType = "blob";
61
+ ajax.onload = function (e) {
62
+ // @ts-ignore
63
+ download(e.target.response, fileName, defaultMime);
64
+ };
65
+ ajax.onerror = function (e) {
66
+ // As a fallback, just open the request in a new tab
67
+ window.open(url, "_blank").focus();
68
+ };
69
+ setTimeout(function () {
70
+ ajax.send();
71
+ }, 0); // allows setting custom ajax headers using the return:
72
+ return ajax;
73
+ } else {
74
+ throw new Error("Invalid URL given, cannot download file: " + url);
75
+ }
76
+ } // end if url?
77
+
78
+ // go ahead and download dataURLs right away
79
+ if (/^data:[\w+-]+\/[\w+-]+[,;]/.test(payload)) {
80
+ if (payload.length > 1024 * 1024 * 1.999 && myBlob !== toString) {
81
+ payload = dataUrlToBlob(payload);
82
+ mimeType = payload.type || defaultMime;
83
+ } else {
84
+ // IE10 can't do a[download], only Blobs
85
+ // everyone else can save dataURLs un-processed
86
+ // @ts-ignore
87
+ return navigator.msSaveBlob ? navigator.msSaveBlob(dataUrlToBlob(payload), fileName) : saver(payload);
88
+ }
89
+ } // end if dataURL passed?
90
+
91
+ blob =
92
+ payload instanceof myBlob
93
+ ? payload
94
+ : new myBlob([payload], { type: mimeType });
95
+
96
+ function dataUrlToBlob(strUrl) {
97
+ var parts = strUrl.split(/[:;,]/);
98
+
99
+ var type = parts[1];
100
+
101
+ var decoder = parts[2] === "base64" ? atob : decodeURIComponent;
102
+
103
+ var binData = decoder(parts.pop());
104
+
105
+ var mx = binData.length;
106
+
107
+ var i = 0;
108
+
109
+ var uiArr = new Uint8Array(mx);
110
+
111
+ for (i; i < mx; ++i) uiArr[i] = binData.charCodeAt(i);
112
+
113
+ return new myBlob([uiArr], { type: type });
114
+ }
115
+
116
+ function saver(url, winMode) {
117
+ if ("download" in anchor) {
118
+ // html5 A[download]
119
+ anchor.href = url;
120
+ anchor.setAttribute("download", fileName);
121
+ anchor.className = "download-js-link";
122
+ anchor.innerHTML = "downloading...";
123
+ anchor.style.display = "none";
124
+ document.body.appendChild(anchor);
125
+ setTimeout(function () {
126
+ anchor.click();
127
+ document.body.removeChild(anchor);
128
+ if (winMode === true) {
129
+ setTimeout(function () {
130
+ self.URL.revokeObjectURL(anchor.href);
131
+ }, 250);
132
+ }
133
+ }, 66);
134
+ return true;
135
+ }
136
+
137
+ // handle non-a[download] safari as best we can:
138
+ if (
139
+ /(Version)\/(\d+)\.(\d+)(?:\.(\d+))?.*Safari\//.test(navigator.userAgent)
140
+ ) {
141
+ url = url.replace(/^data:([\w/\-+]+)/, defaultMime);
142
+ if (!window.open(url)) {
143
+ // popup blocked, offer direct download:
144
+ if (
145
+ confirm(
146
+ "Displaying New Document\n\nUse Save As... to download, then click back to return to this page."
147
+ )
148
+ ) {
149
+ location.href = url;
150
+ }
151
+ }
152
+ return true;
153
+ }
154
+
155
+ // do iframe dataURL download (old ch+FF):
156
+ var f = document.createElement("iframe");
157
+ document.body.appendChild(f);
158
+
159
+ if (!winMode) {
160
+ // force a mime that will download:
161
+ url = "data:" + url.replace(/^data:([\w/\-+]+)/, defaultMime);
162
+ }
163
+ f.src = url;
164
+ setTimeout(function () {
165
+ document.body.removeChild(f);
166
+ }, 333);
167
+ } // end saver
168
+
169
+ // @ts-ignore
170
+ if (navigator.msSaveBlob) {
171
+ // IE10+ : (has Blob, but not a[download] or URL)
172
+ // @ts-ignore
173
+ return navigator.msSaveBlob(blob, fileName);
174
+ }
175
+
176
+ if (self.URL) {
177
+ // simple fast and modern way using Blob and URL:
178
+ saver(self.URL.createObjectURL(blob), true);
179
+ } else {
180
+ // handle non-Blob()+non-URL browsers:
181
+ if (typeof blob === "string" || blob.constructor === toString) {
182
+ try {
183
+ // @ts-ignore
184
+ return saver("data:" + mimeType + ";base64," + self.btoa(blob));
185
+ } catch (y) {
186
+ // @ts-ignore
187
+ return saver("data:" + mimeType + "," + encodeURIComponent(blob));
188
+ }
189
+ }
190
+
191
+ // Blob but not URL support:
192
+ reader = new FileReader();
193
+ reader.onload = function (e) {
194
+ // @ts-ignore
195
+ saver(this.result);
196
+ };
197
+ reader.readAsDataURL(blob);
198
+ }
199
+ return true;
200
+ }