quasar-ui-danx 0.0.10 → 0.0.11

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. package/package.json +3 -2
  2. package/src/components/ActionTable/ActionTable.vue +135 -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 +141 -0
  10. package/src/components/ActionTable/Form/Fields/BooleanField.vue +37 -0
  11. package/src/components/ActionTable/Form/Fields/ConfirmPasswordField.vue +46 -0
  12. package/src/components/ActionTable/Form/Fields/DateField.vue +59 -0
  13. package/src/components/ActionTable/Form/Fields/DateRangeField.vue +110 -0
  14. package/src/components/ActionTable/Form/Fields/DateTimeField.vue +50 -0
  15. package/src/components/ActionTable/Form/Fields/DateTimePicker.vue +59 -0
  16. package/src/components/ActionTable/Form/Fields/EditableDiv.vue +39 -0
  17. package/src/components/ActionTable/Form/Fields/FieldLabel.vue +32 -0
  18. package/src/components/ActionTable/Form/Fields/FileUploadButton.vue +78 -0
  19. package/src/components/ActionTable/Form/Fields/InlineDateTimeField.vue +44 -0
  20. package/src/components/ActionTable/Form/Fields/IntegerField.vue +26 -0
  21. package/src/components/ActionTable/Form/Fields/LabeledInput.vue +63 -0
  22. package/src/components/ActionTable/Form/Fields/MultiFileField.vue +91 -0
  23. package/src/components/ActionTable/Form/Fields/MultiKeywordField.vue +57 -0
  24. package/src/components/ActionTable/Form/Fields/NewPasswordField.vue +39 -0
  25. package/src/components/ActionTable/Form/Fields/NumberField.vue +94 -0
  26. package/src/components/ActionTable/Form/Fields/NumberRangeField.vue +140 -0
  27. package/src/components/ActionTable/Form/Fields/SelectDrawer.vue +136 -0
  28. package/src/components/ActionTable/Form/Fields/SelectField.vue +318 -0
  29. package/src/components/ActionTable/Form/Fields/SelectWithChildrenField.vue +81 -0
  30. package/src/components/ActionTable/Form/Fields/SingleFileField.vue +78 -0
  31. package/src/components/ActionTable/Form/Fields/TextField.vue +82 -0
  32. package/src/components/ActionTable/Form/Fields/WysiwygField.vue +46 -0
  33. package/src/components/ActionTable/Form/Fields/index.ts +23 -0
  34. package/src/components/ActionTable/Form/RenderedForm.vue +74 -0
  35. package/src/components/ActionTable/RenderComponentColumn.vue +22 -0
  36. package/src/components/ActionTable/TableSummaryRow.vue +95 -0
  37. package/src/components/ActionTable/index.ts +15 -0
  38. package/src/components/ActionTable/listActions.ts +361 -0
  39. package/src/components/ActionTable/tableColumns.ts +72 -0
  40. package/src/components/ActionTable/tableHelpers.ts +83 -0
  41. package/src/components/Utility/CollapsableSidebar.vue +119 -0
  42. package/src/components/Utility/ContentDrawer.vue +70 -0
  43. package/src/components/Utility/Dialogs/ConfirmDialog.vue +132 -0
  44. package/src/components/Utility/Dialogs/FullScreenDialog.vue +46 -0
  45. package/src/components/Utility/Dialogs/InfoDialog.vue +92 -0
  46. package/src/components/Utility/Dialogs/InputDialog.vue +35 -0
  47. package/src/components/Utility/Transitions/ListTransition.vue +50 -0
  48. package/src/components/Utility/Transitions/SlideTransition.vue +63 -0
  49. package/src/components/Utility/Transitions/StaggeredListTransition.vue +97 -0
  50. package/src/components/Utility/index.ts +9 -0
  51. package/src/components/index.ts +3 -0
  52. package/src/helpers/FileUpload.ts +294 -0
  53. package/src/helpers/FlashMessages.ts +79 -0
  54. package/src/helpers/array.ts +37 -0
  55. package/src/helpers/compatibility.ts +64 -0
  56. package/src/helpers/date.ts +5 -0
  57. package/src/helpers/download.ts +192 -0
  58. package/src/helpers/downloadPdf.ts +92 -0
  59. package/src/helpers/files.ts +52 -0
  60. package/src/helpers/formats.ts +183 -0
  61. package/src/helpers/http.ts +62 -0
  62. package/src/helpers/index.ts +10 -1
  63. package/src/helpers/multiFileUpload.ts +68 -0
  64. package/src/helpers/singleFileUpload.ts +54 -0
  65. package/src/helpers/storage.ts +8 -0
@@ -0,0 +1,9 @@
1
+ export { default as CollapsableSidebar } from "./CollapsableSidebar";
2
+ export { default as ConfirmDialog } from "./Dialogs/ConfirmDialog";
3
+ export { default as ContentDrawer } from "./ContentDrawer";
4
+ export { default as FullScreenDialog } from "./Dialogs/FullScreenDialog";
5
+ export { default as InfoDialog } from "./Dialogs/InfoDialog";
6
+ export { default as InputDialog } from "./Dialogs/InputDialog";
7
+ export { default as ListTransition } from "./Transitions/ListTransition";
8
+ export { default as SlideTransition } from "./Transitions/SlideTransition";
9
+ export { default as StaggeredListTransition } from "./Transitions/StaggeredListTransition";
@@ -0,0 +1,3 @@
1
+ export * from "./ActionTable";
2
+ export * from "./Utility";
3
+
@@ -0,0 +1,294 @@
1
+ import { FlashMessages, resolveFileLocation } from "danx/src/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 errors
216
+ for (const fileUpload of this.fileUploads) {
217
+ fileUpload.xhr.addEventListener(
218
+ "load",
219
+ async (e) => {
220
+ try {
221
+ // First complete the presigned upload to get the updated file resource data
222
+ const uploadedFile = await this.completePresignedUpload(fileUpload);
223
+
224
+ // Fire the file complete callbacks
225
+ this.fireCompleteCallback(fileUpload, uploadedFile);
226
+ this.checkAllComplete();
227
+ } catch (error) {
228
+ this.errorHandler(e, fileUpload.file, error);
229
+ }
230
+ },
231
+ false
232
+ );
233
+ }
234
+ }
235
+
236
+ /**
237
+ * Mark the presigned upload as completed and return the file resource from the platform server
238
+ * @param fileUpload
239
+ * @returns {Promise<void>}
240
+ */
241
+ async completePresignedUpload(fileUpload) {
242
+ // Show 95% as the last 5% will be to complete the presigned upload
243
+ this.fireProgressCallback(fileUpload, .95);
244
+
245
+ // Let the platform know the presigned upload is complete
246
+ return await fetch(this.options.uploadCompletedUrl(fileUpload.file.resource_id), { method: "POST" }).then(r => r.json());
247
+ }
248
+
249
+ /**
250
+ * Start uploading all files
251
+ */
252
+ async upload() {
253
+ for (const fileUpload of this.fileUploads) {
254
+ const mimeType = fileUpload.file.mimeType || fileUpload.file.type;
255
+ const presignedUrl = this.options.presignedUploadUrl(this.options.directory, fileUpload.file.name, mimeType);
256
+
257
+ // Fetch presigned upload URL
258
+ const fileResource = await fetch(presignedUrl).then(r => r.json());
259
+
260
+ if (!fileResource.url) {
261
+ FlashMessages.error("Could not fetch presigned upload URL for file " + fileUpload.file.name);
262
+ continue;
263
+ }
264
+
265
+ const isS3Upload = !fileResource.url.match("upload-presigned-url-contents");
266
+
267
+ // We need the file resource ID to complete the presigned upload
268
+ fileUpload.file.resource_id = fileResource.id;
269
+
270
+ // Prepare XHR request
271
+ const xhr = new XMLHttpRequest();
272
+
273
+ // The XHR request is different based on weather we're sending to S3 or the platform server
274
+ if (isS3Upload) {
275
+ xhr.open("PUT", fileResource.url);
276
+ xhr.setRequestHeader("Content-Type", mimeType);
277
+ fileUpload.body = fileUpload.file;
278
+ } else {
279
+ xhr.open("POST", fileResource.url);
280
+ fileUpload.body = fileUpload.formData;
281
+ }
282
+
283
+ fileUpload.xhr = xhr;
284
+ }
285
+
286
+ // Set all the callbacks on the XHR requests
287
+ this.setXhrCallbacks();
288
+
289
+ // Send all the XHR file uploads
290
+ for (const fileUpload of this.fileUploads) {
291
+ fileUpload.xhr.send(fileUpload.body);
292
+ }
293
+ }
294
+ }
@@ -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 { useGeolocation } from "@vueuse/core";
2
+ import { sleep } from "quasar-ui-danx/src/helpers";
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 "danx/src/helpers/formats";
2
+
3
+ export function diffInDays(date1, date2) {
4
+ return parseDateTime(date2).diff(parseDateTime(date1), ["days"]).days;
5
+ }
@@ -0,0 +1,192 @@
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
+ var myBlob = self.Blob || self.MozBlob || self.WebKitBlob || toString;
31
+
32
+ var fileName = strFileName || "download";
33
+
34
+ var blob;
35
+
36
+ var reader;
37
+ myBlob = myBlob.call ? myBlob.bind(self) : Blob;
38
+
39
+ if (String(this) === "true") {
40
+ // reverse arguments, allowing download.bind(true, "text/xml", "export.xml") to act as a callback
41
+ payload = [payload, mimeType];
42
+ mimeType = payload[0];
43
+ payload = payload[1];
44
+ }
45
+
46
+ if (url && url.length < 2048) {
47
+ // if no filename and no mime, assume a url was passed as the only argument
48
+ fileName = url.split("/").pop().split("?")[0];
49
+ anchor.href = url; // assign href prop to temp anchor
50
+
51
+ // if the browser determines that it's a potentially valid url path:
52
+ if (
53
+ anchor.href.indexOf(url) !== -1 ||
54
+ anchor.href.indexOf(encodeURI(url)) !== -1 ||
55
+ anchor.href === encodeURI(url)
56
+ ) {
57
+ var ajax = new XMLHttpRequest();
58
+ ajax.open("GET", url + "?no-cache=" + Date.now(), true);
59
+ ajax.responseType = "blob";
60
+ ajax.onload = function(e) {
61
+ download(e.target.response, fileName, defaultMime);
62
+ };
63
+ ajax.onerror = function(e) {
64
+ // As a fallback, just open the request in a new tab
65
+ window.open(url, "_blank").focus();
66
+ };
67
+ setTimeout(function() {
68
+ ajax.send();
69
+ }, 0); // allows setting custom ajax headers using the return:
70
+ return ajax;
71
+ } else {
72
+ throw new Error("Invalid URL given, cannot download file: " + url);
73
+ }
74
+ } // end if url?
75
+
76
+ // go ahead and download dataURLs right away
77
+ if (/^data:[\w+-]+\/[\w+-]+[,;]/.test(payload)) {
78
+ if (payload.length > 1024 * 1024 * 1.999 && myBlob !== toString) {
79
+ payload = dataUrlToBlob(payload);
80
+ mimeType = payload.type || defaultMime;
81
+ } else {
82
+ return navigator.msSaveBlob // IE10 can't do a[download], only Blobs:
83
+ ? navigator.msSaveBlob(dataUrlToBlob(payload), fileName)
84
+ : saver(payload); // everyone else can save dataURLs un-processed
85
+ }
86
+ } // end if dataURL passed?
87
+
88
+ blob =
89
+ payload instanceof myBlob
90
+ ? payload
91
+ : new myBlob([payload], { type: mimeType });
92
+
93
+ function dataUrlToBlob(strUrl) {
94
+ var parts = strUrl.split(/[:;,]/);
95
+
96
+ var type = parts[1];
97
+
98
+ var decoder = parts[2] === "base64" ? atob : decodeURIComponent;
99
+
100
+ var binData = decoder(parts.pop());
101
+
102
+ var mx = binData.length;
103
+
104
+ var i = 0;
105
+
106
+ var uiArr = new Uint8Array(mx);
107
+
108
+ for (i; i < mx; ++i) uiArr[i] = binData.charCodeAt(i);
109
+
110
+ return new myBlob([uiArr], { type: type });
111
+ }
112
+
113
+ function saver(url, winMode) {
114
+ if ("download" in anchor) {
115
+ // html5 A[download]
116
+ anchor.href = url;
117
+ anchor.setAttribute("download", fileName);
118
+ anchor.className = "download-js-link";
119
+ anchor.innerHTML = "downloading...";
120
+ anchor.style.display = "none";
121
+ document.body.appendChild(anchor);
122
+ setTimeout(function() {
123
+ anchor.click();
124
+ document.body.removeChild(anchor);
125
+ if (winMode === true) {
126
+ setTimeout(function() {
127
+ self.URL.revokeObjectURL(anchor.href);
128
+ }, 250);
129
+ }
130
+ }, 66);
131
+ return true;
132
+ }
133
+
134
+ // handle non-a[download] safari as best we can:
135
+ if (
136
+ /(Version)\/(\d+)\.(\d+)(?:\.(\d+))?.*Safari\//.test(navigator.userAgent)
137
+ ) {
138
+ url = url.replace(/^data:([\w/\-+]+)/, defaultMime);
139
+ if (!window.open(url)) {
140
+ // popup blocked, offer direct download:
141
+ if (
142
+ confirm(
143
+ "Displaying New Document\n\nUse Save As... to download, then click back to return to this page."
144
+ )
145
+ ) {
146
+ location.href = url;
147
+ }
148
+ }
149
+ return true;
150
+ }
151
+
152
+ // do iframe dataURL download (old ch+FF):
153
+ var f = document.createElement("iframe");
154
+ document.body.appendChild(f);
155
+
156
+ if (!winMode) {
157
+ // force a mime that will download:
158
+ url = "data:" + url.replace(/^data:([\w/\-+]+)/, defaultMime);
159
+ }
160
+ f.src = url;
161
+ setTimeout(function() {
162
+ document.body.removeChild(f);
163
+ }, 333);
164
+ } // end saver
165
+
166
+ if (navigator.msSaveBlob) {
167
+ // IE10+ : (has Blob, but not a[download] or URL)
168
+ return navigator.msSaveBlob(blob, fileName);
169
+ }
170
+
171
+ if (self.URL) {
172
+ // simple fast and modern way using Blob and URL:
173
+ saver(self.URL.createObjectURL(blob), true);
174
+ } else {
175
+ // handle non-Blob()+non-URL browsers:
176
+ if (typeof blob === "string" || blob.constructor === toString) {
177
+ try {
178
+ return saver("data:" + mimeType + ";base64," + self.btoa(blob));
179
+ } catch (y) {
180
+ return saver("data:" + mimeType + "," + encodeURIComponent(blob));
181
+ }
182
+ }
183
+
184
+ // Blob but not URL support:
185
+ reader = new FileReader();
186
+ reader.onload = function(e) {
187
+ saver(this.result);
188
+ };
189
+ reader.readAsDataURL(blob);
190
+ }
191
+ return true;
192
+ }