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,92 @@
1
+ import { download } from "danx/src/helpers/download";
2
+
3
+ /**
4
+ * Asynchronously load a file from the URL and trigger a download in the browser
5
+ *
6
+ * @param url
7
+ * @param filename
8
+ * @param postParams
9
+ * @returns {Promise<void>}
10
+ */
11
+ export async function downloadFile(url, filename = "", postParams = null) {
12
+ let fetchOptions = undefined;
13
+
14
+ if (postParams) {
15
+ fetchOptions = {
16
+ method: "POST",
17
+ "Content-Type": "application/json",
18
+ body: JSON.stringify(postParams)
19
+ };
20
+ }
21
+
22
+ const response = await fetch(url, fetchOptions);
23
+
24
+ if (!response.ok) {
25
+ throw Error("File download failed: invalid response from server");
26
+ }
27
+
28
+ let errorMessage;
29
+
30
+ // Handle a JSON response (which indicates an error occurred)
31
+ try {
32
+ // @ts-expect-error data is defined on response
33
+ const jsonResponse = JSON.parse(new TextDecoder().decode(response.data));
34
+ console.error("Error downloading file:", jsonResponse);
35
+ errorMessage = jsonResponse.message;
36
+ if (jsonResponse.errors) {
37
+ errorMessage = jsonResponse.errors[0].message;
38
+ }
39
+ } catch (e) {
40
+ // we expect an error thrown for invalid JSON when the response is a file
41
+ }
42
+
43
+ if (errorMessage) {
44
+ throw new Error("Failed to download file: " + errorMessage);
45
+ }
46
+
47
+ await downloadFileResponse(response, filename);
48
+ }
49
+
50
+ /**
51
+ * Downloads a file from a response object w/ a file attachment
52
+ *
53
+ * @param response
54
+ * @param filename
55
+ */
56
+ export async function downloadFileResponse(response, filename = "") {
57
+ const contentDisposition = getResponseHeader(
58
+ response,
59
+ "content-disposition",
60
+ ""
61
+ );
62
+
63
+ const contentType = getResponseHeader(response, "content-type", "");
64
+
65
+ const match = contentDisposition.match(/filename="([^"]+)"/);
66
+
67
+ filename = filename || (match && match[1]) || "download.pdf";
68
+
69
+ let data = response.data;
70
+ if (!data) {
71
+ data = await response.blob();
72
+ }
73
+
74
+ download(data, filename, contentType);
75
+ }
76
+
77
+ /**
78
+ * Get a header from a response object
79
+ * @param response
80
+ * @param header
81
+ * @param defaultValue
82
+ * @returns {*}
83
+ */
84
+ export function getResponseHeader(response, header, defaultValue) {
85
+ if (response.headers) {
86
+ if (typeof response.headers.get === "function") {
87
+ return response.headers.get(header) || defaultValue;
88
+ } else {
89
+ return response.headers[header] || defaultValue;
90
+ }
91
+ }
92
+ }
@@ -0,0 +1,52 @@
1
+ import { FlashMessages, useCompatibility } from "danx/src/helpers";
2
+ import ExifReader from "exifreader";
3
+
4
+ export async function resolveFileLocation(file, waitMessage = null) {
5
+ if (file.location) {
6
+ return file.location;
7
+ }
8
+
9
+ try {
10
+ const tags = await ExifReader.load(file.blobUrl || file.url, {
11
+ expanded: true
12
+ });
13
+ if (tags.gps) {
14
+ return {
15
+ latitude: tags.gps.Latitude,
16
+ longitude: tags.gps.Longitude
17
+ };
18
+ }
19
+
20
+ const { waitForLocation, location } = useCompatibility();
21
+
22
+ // Show a waiting for location message if we have not returned within 1 second
23
+ if (waitMessage) {
24
+ setTimeout(() => {
25
+ if (!location.value && waitMessage) {
26
+ FlashMessages.warning(waitMessage);
27
+ }
28
+ }, 1000);
29
+ }
30
+
31
+ // Wait for the browser to return the location (https only as http will not return a location)
32
+ if (window.location.protocol === "https:") {
33
+ await waitForLocation();
34
+ }
35
+ // Ignore the wait message if we already returned
36
+ waitMessage = false;
37
+ if (!location.value) {
38
+ return null;
39
+ }
40
+
41
+ return {
42
+ latitude: location.value.latitude,
43
+ longitude: location.value.longitude,
44
+ accuracy: location.value.accuracy,
45
+ altitude: location.value.altitude,
46
+ altitudeAccuracy: location.value.altitudeAccuracy
47
+ };
48
+ } catch (error) {
49
+ console.error(error);
50
+ return null;
51
+ }
52
+ }
@@ -0,0 +1,183 @@
1
+ import { DateTime, IANAZone } from "luxon";
2
+
3
+ const SERVER_TZ = new IANAZone("America/Chicago");
4
+
5
+ /**
6
+ * Converts a date string from the server's time zone to the user's time zone.
7
+ * @param {String} dateTimeString
8
+ * @returns {DateTime}
9
+ */
10
+ export function localizedDateTime(dateTimeString) {
11
+ dateTimeString = dateTimeString?.replace("T", " ");
12
+ // noinspection JSCheckFunctionSignatures
13
+ return DateTime.fromSQL(dateTimeString, { zone: SERVER_TZ }).setZone("local");
14
+ }
15
+
16
+ /**
17
+ * Converts a date string from the user's time zone to the server's time zone.
18
+ * @param dateTimeString
19
+ * @returns {DateTime}
20
+ */
21
+ export function remoteDateTime(dateTimeString) {
22
+ dateTimeString = dateTimeString?.replace("T", " ");
23
+ // noinspection JSCheckFunctionSignatures
24
+ return DateTime.fromSQL(dateTimeString, { zone: "local" }).setZone(SERVER_TZ);
25
+ }
26
+
27
+ /**
28
+ * @param {DateTime|String} dateTime
29
+ * @returns {DateTime|*}
30
+ */
31
+ export function parseDateTime(dateTime) {
32
+ if (typeof dateTime === "string") {
33
+ dateTime = dateTime.replace("T", " ").replace(/\//g, "-");
34
+ return DateTime.fromSQL(dateTime);
35
+ }
36
+ return dateTime || DateTime.fromSQL("0000-00-00 00:00:00");
37
+ }
38
+
39
+ /**
40
+ * Parses a Quasar formatted date string into a Luxon DateTime object
41
+ * @param date
42
+ * @param format
43
+ * @returns {DateTime}
44
+ */
45
+ export function parseQDate(date, format = "yyyy/MM/dd") {
46
+ return DateTime.fromFormat(date, format);
47
+ }
48
+
49
+ /**
50
+ * Parses a Quasar formatted date/time string into a Luxon DateTime object
51
+ * @param date
52
+ * @param format
53
+ * @returns {DateTime}
54
+ */
55
+ export function parseQDateTime(date, format = "yyyy/MM/dd HH:mm:ss") {
56
+ return DateTime.fromFormat(date, format);
57
+ }
58
+
59
+ /**
60
+ * Formats a Luxon DateTime object into a Quasar formatted date string
61
+ * @param date
62
+ * @returns {string}
63
+ */
64
+ export function fQDate(date) {
65
+ return fDate(date, { format: "yyyy/MM/dd" });
66
+ }
67
+
68
+ /**
69
+ *
70
+ * @param {String} dateTimeString
71
+ * @param options
72
+ * @returns {string}
73
+ */
74
+ export function fLocalizedDateTime(dateTimeString, options = {}) {
75
+ return fDateTime(localizedDateTime(dateTimeString), options);
76
+ }
77
+
78
+ /**
79
+ * Formats a date/time object or string into a human-readable format
80
+ *
81
+ * @param {String|Object} dateTime
82
+ * @param format
83
+ * @param {String|null} empty
84
+ * @returns {string}
85
+ */
86
+ export function fDateTime(
87
+ dateTime = null,
88
+ { format = "M/d/yy h:mma", empty = "- -" } = {}
89
+ ) {
90
+ const formatted = (dateTime ? parseDateTime(dateTime) : DateTime.now()).toFormat(format).toLowerCase();
91
+ return formatted === "invalid datetime" ? empty : formatted;
92
+ }
93
+
94
+ /**
95
+ * Formats a date/time object or string into the best format for DB input
96
+ * @param dateTime
97
+ * @returns {string}
98
+ */
99
+ export function dbDateTime(dateTime = null) {
100
+ return fDateTime(dateTime, { format: "yyyy-MM-dd HH:mm:ss", empty: null });
101
+ }
102
+
103
+ /**
104
+ * Formats a date object or string into a human-readable format
105
+ * @param {String|Object} dateTime
106
+ * @param {String|null} empty
107
+ * @param format
108
+ * @returns {string}
109
+ */
110
+ export function fDate(dateTime, { empty = "--", format = "M/d/yy" } = {}) {
111
+ const formatted = parseDateTime(dateTime).toFormat(format);
112
+ return ["Invalid DateTime", "invalid datetime"].includes(formatted) ? empty : formatted;
113
+ }
114
+
115
+ /**
116
+ * Formats a number of seconds into Hours / Minutes / Seconds or just Minutes and Seconds
117
+ *
118
+ * @param second
119
+ * @returns {string}
120
+ */
121
+ export function fSecondsToTime(second) {
122
+ const time = DateTime.now().setZone("UTC").startOf("year").set({ second });
123
+ const hours = Math.floor(second / 3600);
124
+ return (hours ? hours + ":" : "") + time.toFormat("mm:ss");
125
+ }
126
+
127
+ /**
128
+ * Formats an amount into USD currency format
129
+ * @param amount
130
+ * @returns {string}
131
+ */
132
+ export function fCurrency(amount) {
133
+ return new Intl.NumberFormat("en-US", {
134
+ style: "currency",
135
+ currency: "USD"
136
+ }).format(amount);
137
+ }
138
+
139
+ /**
140
+ * Formats a number into a human-readable format
141
+ * @param number
142
+ * @param options
143
+ * @returns {string}
144
+ */
145
+ export function fNumber(number, options = {}) {
146
+ return new Intl.NumberFormat("en-US", options).format(number);
147
+ }
148
+
149
+ /**
150
+ * Truncates the string by removing chars from the middle of the string
151
+ * @param str
152
+ * @param maxLength
153
+ * @returns {string|*}
154
+ */
155
+ export function centerTruncate(str, maxLength) {
156
+ if (str.length > maxLength) {
157
+ const frontCharCount = Math.floor((maxLength - 3) / 2);
158
+ const backCharCount = maxLength - frontCharCount - 3;
159
+ return (
160
+ str.substring(0, frontCharCount) +
161
+ "..." +
162
+ str.substring(str.length - backCharCount)
163
+ );
164
+ } else {
165
+ return str;
166
+ }
167
+ }
168
+
169
+ /**
170
+ * Formats a number into a percentage
171
+ * @param num
172
+ * @param options
173
+ * @returns {string}
174
+ */
175
+ export function fPercent(num, options = { multiplier: 100, maximumFractionDigits: 1, NaN: "N/A" }) {
176
+ num = parseFloat(num);
177
+
178
+ if (isNaN(num)) {
179
+ return options.NaN;
180
+ }
181
+
182
+ return fNumber(num * options.multiplier, options) + "%";
183
+ }
@@ -0,0 +1,62 @@
1
+ export const request = {
2
+ async get(url, options = {}) {
3
+ return fetch(url, {
4
+ method: "get",
5
+ headers: {
6
+ Accept: "application/json",
7
+ "Content-Type": "application/json"
8
+ },
9
+ ...options
10
+ });
11
+ },
12
+
13
+ async post(url, data = {}, options = {}) {
14
+ return fetch(url, {
15
+ method: "post",
16
+ body: JSON.stringify(data),
17
+ headers: {
18
+ Accept: "application/json",
19
+ "Content-Type": "application/json"
20
+ },
21
+ ...options
22
+ }).then((r) => r.json());
23
+ }
24
+ };
25
+
26
+ /**
27
+ * Fetches a resource list applying the filter. If there is a selected resource,
28
+ * stores that resource from the already populated list. If that resource does not exist
29
+ * also fetches that resource record from the endpoint, then adds it to the list if it
30
+ * does not exist in the filtered list
31
+ *
32
+ * @param fetchFn
33
+ * @param list
34
+ * @param id
35
+ * @param filter
36
+ * @returns {Promise<void>}
37
+ */
38
+ export async function fetchResourceListWithSelected(fetchFn, list, id, filter) {
39
+ // First make sure we have the selected record, so we can always add it to the list
40
+ let selectedResource;
41
+ if (id) {
42
+ selectedResource = list.value.find((c) => c.id === id) || (await fetchFn({ id }))[0];
43
+ }
44
+
45
+ // Get the filtered campaign list
46
+ list.value = await fetchFn(filter);
47
+
48
+ // If our selected campaign is not in the filtered list, add it
49
+ if (selectedResource && !list.value.find((c) => c.id === id)) {
50
+ list.value.push(selectedResource);
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Returns the value of the URL parameter (if it is set)
56
+ * @param key
57
+ * @param url
58
+ */
59
+ export function getUrlParam(key, url = undefined) {
60
+ const params = new URLSearchParams(url?.replace(/.*\?/, "") || window.location.search);
61
+ return params.get(key);
62
+ }
@@ -1 +1,10 @@
1
- export * from './utils';
1
+ export * from "./array";
2
+ export * from "./compatibility";
3
+ export * from "./files";
4
+ export * from "./FileUpload";
5
+ export * from "./FlashMessages";
6
+ export * from "./http";
7
+ export * from "./multiFileUpload";
8
+ export * from "./singleFileUpload";
9
+ export * from "./storage";
10
+ export * from "./utils";
@@ -0,0 +1,68 @@
1
+ import { FileUpload, FileUploadOptions } from "danx/src/helpers/FileUpload";
2
+ import { ref } from "vue";
3
+
4
+ export function useMultiFileUpload(options: FileUploadOptions) {
5
+ const uploadedFiles = ref([]);
6
+ const onCompleteCb = ref(null);
7
+ const onFilesChangeCb = ref(null);
8
+ const onFilesSelected = (e) => {
9
+ uploadedFiles.value = [...uploadedFiles.value, ...e.target.files];
10
+ new FileUpload(e.target.files, options)
11
+ .onProgress(({ file }) => {
12
+ updateFileInList(file);
13
+ })
14
+ .onComplete(({ file, uploadedFile }) => {
15
+ updateFileInList(file, uploadedFile);
16
+ })
17
+ .onAllComplete(() => {
18
+ onCompleteCb.value && onCompleteCb.value();
19
+ onFilesChangeCb.value && onFilesChangeCb.value(uploadedFiles.value);
20
+ })
21
+ .upload();
22
+ };
23
+
24
+ function updateFileInList(file, replace = null) {
25
+ const index = uploadedFiles.value.findIndex(f => f.id === file.id);
26
+ if (index !== -1) {
27
+ uploadedFiles.value.splice(index, 1, replace || file);
28
+ }
29
+ onFilesChangeCb.value && onFilesChangeCb.value(uploadedFiles.value);
30
+ }
31
+
32
+ const onDrop = (e) => {
33
+ onFilesSelected({ target: { files: e.dataTransfer.files } });
34
+ };
35
+
36
+ const onFilesChange = (cb) => {
37
+ onFilesChangeCb.value = cb;
38
+ };
39
+
40
+ const onComplete = (cb) => {
41
+ onCompleteCb.value = cb;
42
+ };
43
+
44
+ const onClear = () => {
45
+ uploadedFiles.value = [];
46
+ onFilesChangeCb.value && onFilesChangeCb.value(uploadedFiles.value);
47
+ onCompleteCb.value && onCompleteCb.value();
48
+ };
49
+
50
+ const onRemove = (file) => {
51
+ const index = uploadedFiles.value.findIndex(f => f.id === file.id);
52
+ if (index !== -1) {
53
+ uploadedFiles.value.splice(index, 1);
54
+ }
55
+ onFilesChangeCb.value && onFilesChangeCb.value(uploadedFiles.value);
56
+ onCompleteCb.value && onCompleteCb.value();
57
+ };
58
+
59
+ return {
60
+ onClear,
61
+ onRemove,
62
+ onComplete,
63
+ onFilesChange,
64
+ onDrop,
65
+ onFilesSelected,
66
+ uploadedFiles
67
+ };
68
+ }
@@ -0,0 +1,54 @@
1
+ import { FileUpload, FileUploadOptions } from "danx/src/helpers";
2
+ import { computed, ref } from "vue";
3
+
4
+ export function useSingleFileUpload(options: FileUploadOptions) {
5
+ const uploadedFile = ref(null);
6
+ const onCompleteCb = ref(null);
7
+ const onFileChangeCb = ref(null);
8
+
9
+ const onFileSelected = (e) => {
10
+ uploadedFile.value = null;
11
+ new FileUpload(e.target.files[0], options)
12
+ .onProgress(({ file }) => {
13
+ uploadedFile.value = file;
14
+ onFileChangeCb.value && onFileChangeCb.value(uploadedFile.value);
15
+ })
16
+ .onComplete(({ uploadedFile: completedFile }) => {
17
+ uploadedFile.value = completedFile;
18
+ onCompleteCb.value && onCompleteCb.value(uploadedFile.value);
19
+ onFileChangeCb.value && onFileChangeCb.value(uploadedFile.value);
20
+ })
21
+ .upload();
22
+ };
23
+
24
+ const onDrop = (e) => {
25
+ onFileSelected({ target: { files: e.dataTransfer.files } });
26
+ };
27
+
28
+ const isFileUploaded = computed(() => {
29
+ return uploadedFile.value && uploadedFile.value.url;
30
+ });
31
+
32
+ const onFileChange = (cb) => {
33
+ onFileChangeCb.value = cb;
34
+ };
35
+
36
+ const onComplete = (cb) => {
37
+ onCompleteCb.value = cb;
38
+ };
39
+
40
+ const onClear = () => {
41
+ uploadedFile.value = null;
42
+ onFileChangeCb.value && onFileChangeCb.value(uploadedFile.value);
43
+ };
44
+
45
+ return {
46
+ isFileUploaded,
47
+ onClear,
48
+ onComplete,
49
+ onFileChange,
50
+ onDrop,
51
+ onFileSelected,
52
+ uploadedFile
53
+ };
54
+ }
@@ -0,0 +1,8 @@
1
+ export function setItem(key, value) {
2
+ localStorage.setItem(key, JSON.stringify(value));
3
+ }
4
+
5
+ export function getItem(key, defaultValue = null) {
6
+ const item = localStorage.getItem(key);
7
+ return item ? JSON.parse(item) : defaultValue;
8
+ }