quasar-ui-danx 0.4.43 → 0.4.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/danx.es.js +6507 -6290
- package/dist/danx.es.js.map +1 -1
- package/dist/danx.umd.js +81 -81
- package/dist/danx.umd.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/ActionTable/controls.ts +1 -1
- package/src/components/Utility/Buttons/ActionButton.vue +53 -15
- package/src/components/Utility/Buttons/ShowHideButton.vue +15 -35
- package/src/components/Utility/Files/FilePreview.vue +9 -3
- package/src/components/Utility/Widgets/LabelPillWidget.vue +40 -0
- package/src/components/Utility/Widgets/LabelValuePillWidget.vue +26 -0
- package/src/components/Utility/Widgets/index.ts +2 -0
- package/src/components/Utility/index.ts +1 -0
- package/src/helpers/array.ts +15 -15
- package/src/helpers/objectStore.ts +9 -7
- package/src/helpers/utils.ts +3 -0
- package/src/types/controls.d.ts +4 -2
- package/src/types/index.d.ts +1 -0
- package/src/types/widgets.d.ts +5 -0
package/package.json
CHANGED
@@ -115,7 +115,7 @@ export function useControls(name: string, options: ListControlsOptions): ListCon
|
|
115
115
|
* Loads the filter field options for the current filter.
|
116
116
|
*/
|
117
117
|
async function loadFieldOptions() {
|
118
|
-
if (!options.routes.fieldOptions || options.isFieldOptionsEnabled === false) return;
|
118
|
+
if (isLoadingFilters.value || !options.routes.fieldOptions || options.isFieldOptionsEnabled === false) return;
|
119
119
|
|
120
120
|
isLoadingFilters.value = true;
|
121
121
|
try {
|
@@ -1,8 +1,8 @@
|
|
1
1
|
<template>
|
2
2
|
<QBtn
|
3
3
|
:loading="isSaving"
|
4
|
-
class="shadow-none"
|
5
|
-
:class="
|
4
|
+
class="shadow-none py-0"
|
5
|
+
:class="buttonClass"
|
6
6
|
:disable="disabled"
|
7
7
|
@click="()=> onAction()"
|
8
8
|
>
|
@@ -16,6 +16,7 @@
|
|
16
16
|
<div
|
17
17
|
v-if="label"
|
18
18
|
class="ml-2"
|
19
|
+
:class="labelClass"
|
19
20
|
>
|
20
21
|
{{ label }}
|
21
22
|
</div>
|
@@ -25,7 +26,9 @@
|
|
25
26
|
v-if="tooltip"
|
26
27
|
class="whitespace-nowrap"
|
27
28
|
>
|
28
|
-
|
29
|
+
<slot name="tooltip">
|
30
|
+
{{ tooltip }}
|
31
|
+
</slot>
|
29
32
|
</QTooltip>
|
30
33
|
<QMenu
|
31
34
|
v-if="isConfirming"
|
@@ -70,9 +73,11 @@ import { ActionTarget, ResourceAction } from "../../../types";
|
|
70
73
|
export interface ActionButtonProps {
|
71
74
|
type?: "trash" | "trash-red" | "create" | "edit" | "copy" | "play" | "stop" | "pause" | "refresh" | "confirm" | "cancel";
|
72
75
|
color?: "red" | "blue" | "sky" | "green" | "green-invert" | "lime" | "white" | "gray";
|
76
|
+
size?: "xxs" | "xs" | "sm" | "md" | "lg";
|
73
77
|
icon?: object | string;
|
74
78
|
iconClass?: string;
|
75
|
-
label?: string;
|
79
|
+
label?: string | number;
|
80
|
+
labelClass?: string;
|
76
81
|
tooltip?: string;
|
77
82
|
saving?: boolean;
|
78
83
|
action?: ResourceAction;
|
@@ -90,7 +95,9 @@ const props = withDefaults(defineProps<ActionButtonProps>(), {
|
|
90
95
|
color: null,
|
91
96
|
icon: null,
|
92
97
|
iconClass: "",
|
98
|
+
size: "md",
|
93
99
|
label: "",
|
100
|
+
labelClass: "",
|
94
101
|
tooltip: "",
|
95
102
|
action: null,
|
96
103
|
target: null,
|
@@ -99,6 +106,36 @@ const props = withDefaults(defineProps<ActionButtonProps>(), {
|
|
99
106
|
disabledClass: "text-slate-800 bg-slate-500 opacity-50"
|
100
107
|
});
|
101
108
|
|
109
|
+
const mappedSizeClass = {
|
110
|
+
xxs: {
|
111
|
+
icon: "w-2",
|
112
|
+
button: "px-.5 h-5"
|
113
|
+
},
|
114
|
+
xs: {
|
115
|
+
icon: "w-3",
|
116
|
+
button: "px-1.5 h-6"
|
117
|
+
},
|
118
|
+
sm: {
|
119
|
+
icon: "w-4",
|
120
|
+
button: "px-2 h-8"
|
121
|
+
},
|
122
|
+
md: {
|
123
|
+
icon: "w-5",
|
124
|
+
button: "px-2.5 h-10"
|
125
|
+
},
|
126
|
+
lg: {
|
127
|
+
icon: "w-6",
|
128
|
+
button: "px-3 h-12"
|
129
|
+
}
|
130
|
+
};
|
131
|
+
|
132
|
+
const buttonClass = computed(() => {
|
133
|
+
return {
|
134
|
+
[props.disabled ? props.disabledClass : colorClass.value]: true,
|
135
|
+
[mappedSizeClass[props.size].button]: true
|
136
|
+
};
|
137
|
+
});
|
138
|
+
|
102
139
|
const colorClass = computed(() => {
|
103
140
|
switch (props.color) {
|
104
141
|
case "red":
|
@@ -122,61 +159,62 @@ const colorClass = computed(() => {
|
|
122
159
|
}
|
123
160
|
});
|
124
161
|
const typeOptions = computed(() => {
|
162
|
+
const iconClass = mappedSizeClass[props.size].icon;
|
125
163
|
switch (props.type) {
|
126
164
|
case "trash":
|
127
165
|
return {
|
128
166
|
icon: TrashIcon,
|
129
|
-
iconClass
|
167
|
+
iconClass
|
130
168
|
};
|
131
169
|
case "create":
|
132
170
|
return {
|
133
171
|
icon: CreateIcon,
|
134
|
-
iconClass
|
172
|
+
iconClass
|
135
173
|
};
|
136
174
|
case "confirm":
|
137
175
|
return {
|
138
176
|
icon: ConfirmIcon,
|
139
|
-
iconClass
|
177
|
+
iconClass
|
140
178
|
};
|
141
179
|
case "cancel":
|
142
180
|
return {
|
143
181
|
icon: CancelIcon,
|
144
|
-
iconClass
|
182
|
+
iconClass
|
145
183
|
};
|
146
184
|
case "edit":
|
147
185
|
return {
|
148
186
|
icon: EditIcon,
|
149
|
-
iconClass
|
187
|
+
iconClass
|
150
188
|
};
|
151
189
|
case "copy":
|
152
190
|
return {
|
153
191
|
icon: CopyIcon,
|
154
|
-
iconClass
|
192
|
+
iconClass
|
155
193
|
};
|
156
194
|
case "play":
|
157
195
|
return {
|
158
196
|
icon: PlayIcon,
|
159
|
-
iconClass
|
197
|
+
iconClass
|
160
198
|
};
|
161
199
|
case "stop":
|
162
200
|
return {
|
163
201
|
icon: StopIcon,
|
164
|
-
iconClass
|
202
|
+
iconClass
|
165
203
|
};
|
166
204
|
case "pause":
|
167
205
|
return {
|
168
206
|
icon: PauseIcon,
|
169
|
-
iconClass
|
207
|
+
iconClass
|
170
208
|
};
|
171
209
|
case "refresh":
|
172
210
|
return {
|
173
211
|
icon: RefreshIcon,
|
174
|
-
iconClass
|
212
|
+
iconClass
|
175
213
|
};
|
176
214
|
default:
|
177
215
|
return {
|
178
216
|
icon: EditIcon,
|
179
|
-
iconClass
|
217
|
+
iconClass
|
180
218
|
};
|
181
219
|
}
|
182
220
|
});
|
@@ -1,37 +1,21 @@
|
|
1
1
|
<template>
|
2
|
-
<
|
3
|
-
|
4
|
-
:
|
2
|
+
<ActionButton
|
3
|
+
:icon="isShowing ? (hideIcon || DefaultHideIcon) : (showIcon || DefaultShowIcon)"
|
4
|
+
:label="(isShowing ? hideLabel : showLabel) || label"
|
5
|
+
:label-class="labelClass"
|
5
6
|
@click="onToggle"
|
6
7
|
>
|
7
|
-
<
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
/>
|
13
|
-
</slot>
|
14
|
-
<slot
|
15
|
-
name="label"
|
16
|
-
:is-showing="isShowing"
|
17
|
-
>
|
18
|
-
<div
|
19
|
-
v-if="label"
|
20
|
-
:class="labelClass"
|
21
|
-
>
|
22
|
-
{{ (isShowing ? hideLabel : showLabel) || label }}
|
23
|
-
</div>
|
24
|
-
</slot>
|
25
|
-
</div>
|
26
|
-
<QTooltip v-if="tooltip">
|
27
|
-
{{ tooltip }}
|
28
|
-
</QTooltip>
|
29
|
-
</QBtn>
|
8
|
+
<slot />
|
9
|
+
<template #tooltip>
|
10
|
+
<slot name="tooltip" />
|
11
|
+
</template>
|
12
|
+
</ActionButton>
|
30
13
|
</template>
|
31
14
|
<script lang="ts" setup>
|
32
15
|
import { FaSolidEye as DefaultShowIcon, FaSolidEyeSlash as DefaultHideIcon } from "danx-icon";
|
33
16
|
import { nextTick } from "vue";
|
34
17
|
import { getItem, setItem } from "../../../helpers";
|
18
|
+
import ActionButton from "./ActionButton";
|
35
19
|
|
36
20
|
export interface Props {
|
37
21
|
name?: string;
|
@@ -39,14 +23,11 @@ export interface Props {
|
|
39
23
|
hideLabel?: string;
|
40
24
|
showIcon?: object | string;
|
41
25
|
hideIcon?: object | string;
|
42
|
-
iconClass?: string;
|
43
26
|
labelClass?: string;
|
44
|
-
label?: string;
|
45
|
-
tooltip?: string;
|
46
|
-
disable?: boolean;
|
27
|
+
label?: string | number;
|
47
28
|
}
|
48
29
|
|
49
|
-
const emit = defineEmits
|
30
|
+
const emit = defineEmits<{ show: void, hide: void }>();
|
50
31
|
const isShowing = defineModel<boolean>();
|
51
32
|
const props = withDefaults(defineProps<Props>(), {
|
52
33
|
name: "",
|
@@ -54,25 +35,24 @@ const props = withDefaults(defineProps<Props>(), {
|
|
54
35
|
hideLabel: "",
|
55
36
|
showIcon: null,
|
56
37
|
hideIcon: null,
|
57
|
-
iconClass: "w-4 h-6",
|
58
38
|
labelClass: "ml-2",
|
59
|
-
label: ""
|
60
|
-
tooltip: ""
|
39
|
+
label: ""
|
61
40
|
});
|
62
41
|
|
42
|
+
console.log("isShowing", isShowing.value);
|
63
43
|
const SETTINGS_KEY = "show-hide-button";
|
64
44
|
const settings = getItem(SETTINGS_KEY, {});
|
65
45
|
|
66
46
|
if (props.name) {
|
67
47
|
if (settings[props.name] !== undefined) {
|
68
48
|
isShowing.value = settings[props.name];
|
49
|
+
console.log("setting is sowing to", isShowing.value);
|
69
50
|
}
|
70
51
|
}
|
71
52
|
|
72
53
|
function onToggle() {
|
73
54
|
isShowing.value = !isShowing.value;
|
74
55
|
|
75
|
-
|
76
56
|
// NOTE: use nextTick to ensure the value is updated before saving (if the parent does not pass a value for modelValue, this can cause a desync)
|
77
57
|
nextTick(() => {
|
78
58
|
if (isShowing.value) {
|
@@ -138,7 +138,7 @@
|
|
138
138
|
<script setup lang="ts">
|
139
139
|
import { DocumentTextIcon as TextFileIcon, DownloadIcon, PlayIcon } from "@heroicons/vue/outline";
|
140
140
|
import { computed, ComputedRef, onMounted, ref } from "vue";
|
141
|
-
import { download, FileUpload } from "../../../helpers";
|
141
|
+
import { download, FileUpload, uniqueBy } from "../../../helpers";
|
142
142
|
import { ImageIcon, PdfIcon, TrashIcon as RemoveIcon } from "../../../svg";
|
143
143
|
import { UploadedFile } from "../../../types";
|
144
144
|
import { FullScreenCarouselDialog } from "../Dialogs";
|
@@ -202,10 +202,16 @@ const computedImage: ComputedRef<UploadedFile | null> = computed(() => {
|
|
202
202
|
});
|
203
203
|
|
204
204
|
const isUploading = computed(() => !props.file || props.file?.progress !== undefined);
|
205
|
-
const previewableFiles: ComputedRef<
|
206
|
-
return props.relatedFiles?.length > 0 ? props.relatedFiles : [computedImage.value];
|
205
|
+
const previewableFiles: ComputedRef<(UploadedFile | null)[] | null> = computed(() => {
|
206
|
+
return props.relatedFiles?.length > 0 ? uniqueBy([computedImage.value, ...props.relatedFiles], filesHaveSameUrl) : [computedImage.value];
|
207
207
|
});
|
208
208
|
|
209
|
+
function filesHaveSameUrl(a: UploadedFile, b: UploadedFile) {
|
210
|
+
return a.id === b.id ||
|
211
|
+
[b.url, b.optimized?.url, b.thumb?.url].includes(a.url) ||
|
212
|
+
[a.url, a.optimized?.url, a.thumb?.url].includes(b.url);
|
213
|
+
}
|
214
|
+
|
209
215
|
const filename = computed(() => computedImage.value?.name || computedImage.value?.filename || "");
|
210
216
|
const mimeType = computed(
|
211
217
|
() => computedImage.value?.type || computedImage.value?.mime || ""
|
@@ -0,0 +1,40 @@
|
|
1
|
+
<template>
|
2
|
+
<div :class="{[colorClass]: true, [sizeClass]: true, 'rounded-full': true}">
|
3
|
+
<slot>{{ label }}</slot>
|
4
|
+
</div>
|
5
|
+
</template>
|
6
|
+
<script setup lang="ts">
|
7
|
+
import { computed } from "vue";
|
8
|
+
import { LabelPillWidgetProps } from "../../../types";
|
9
|
+
|
10
|
+
const props = withDefaults(defineProps<LabelPillWidgetProps>(), {
|
11
|
+
label: "",
|
12
|
+
color: "none",
|
13
|
+
size: "md"
|
14
|
+
});
|
15
|
+
|
16
|
+
const colorClasses = {
|
17
|
+
sky: "bg-sky-950 text-sky-400",
|
18
|
+
green: "bg-green-950 text-green-400",
|
19
|
+
red: "bg-red-950 text-red-400",
|
20
|
+
amber: "bg-amber-950 text-amber-400",
|
21
|
+
yellow: "bg-yellow-950 text-yellow-400",
|
22
|
+
blue: "bg-blue-950 text-blue-400",
|
23
|
+
slate: "bg-slate-950 text-slate-400",
|
24
|
+
gray: "bg-slate-700 text-gray-300",
|
25
|
+
none: ""
|
26
|
+
};
|
27
|
+
|
28
|
+
const sizeClasses = {
|
29
|
+
xxs: "text-xs px-1 py-.5",
|
30
|
+
xs: "text-xs px-2 py-1",
|
31
|
+
sm: "text-sm px-3 py-1.5",
|
32
|
+
md: "text-base px-3 py-2",
|
33
|
+
lg: "text-lg px-4 py-2"
|
34
|
+
};
|
35
|
+
|
36
|
+
const colorClass = computed(() => {
|
37
|
+
return colorClasses[props.color];
|
38
|
+
});
|
39
|
+
const sizeClass = computed(() => sizeClasses[props.size]);
|
40
|
+
</script>
|
@@ -0,0 +1,26 @@
|
|
1
|
+
<template>
|
2
|
+
<div class="flex items-stretch">
|
3
|
+
<div
|
4
|
+
class="rounded-l-lg bg-slate-400 text-slate-900 text-xs px-2 flex items-center justify-center"
|
5
|
+
:class="labelClass"
|
6
|
+
>
|
7
|
+
<slot name="label">
|
8
|
+
{{ label }}
|
9
|
+
</slot>
|
10
|
+
</div>
|
11
|
+
<div class="rounded-r-lg bg-slate-900 text-slate-400 px-2 py-1">
|
12
|
+
<slot name="value">
|
13
|
+
{{ value }}
|
14
|
+
</slot>
|
15
|
+
</div>
|
16
|
+
</div>
|
17
|
+
</template>
|
18
|
+
<script setup lang="ts">
|
19
|
+
withDefaults(defineProps<{
|
20
|
+
label: string;
|
21
|
+
value: string;
|
22
|
+
labelClass?: string;
|
23
|
+
}>(), {
|
24
|
+
labelClass: ""
|
25
|
+
});
|
26
|
+
</script>
|
package/src/helpers/array.ts
CHANGED
@@ -2,31 +2,31 @@
|
|
2
2
|
* Replace an item in an array with a new item
|
3
3
|
*/
|
4
4
|
export function replace(array: any[], item: any, newItem = undefined, appendIfMissing = false) {
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
5
|
+
const index: any = typeof item === "function" ? array.findIndex(item) : array.indexOf(item);
|
6
|
+
if (index === false || index === -1) {
|
7
|
+
return appendIfMissing ? [...array, newItem] : array;
|
8
|
+
}
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
10
|
+
const newArray = [...array];
|
11
|
+
newItem !== undefined
|
12
|
+
? newArray.splice(index, 1, newItem)
|
13
|
+
: newArray.splice(index, 1);
|
14
|
+
return newArray;
|
15
15
|
}
|
16
16
|
|
17
17
|
/**
|
18
18
|
* Remove an item from an array
|
19
19
|
*/
|
20
20
|
export function remove(array: any[], item: any) {
|
21
|
-
|
21
|
+
return replace(array, item);
|
22
22
|
}
|
23
23
|
|
24
24
|
/**
|
25
25
|
* Remove duplicate items from an array using a callback to compare 2 elements
|
26
26
|
*/
|
27
|
-
export function uniqueBy(array: any[], cb:
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
27
|
+
export function uniqueBy(array: any[], cb: (a: any, b: any) => boolean) {
|
28
|
+
return array.filter((a, index, self) => {
|
29
|
+
// Check if the current element 'a' is the first occurrence in the array
|
30
|
+
return index === self.findIndex((b) => cb(a, b));
|
31
|
+
});
|
32
32
|
}
|
@@ -92,6 +92,7 @@ function storeObjectChildren<T extends TypedObject>(object: T, recentlyStoredObj
|
|
92
92
|
for (const index in value) {
|
93
93
|
if (value[index] && typeof value[index] === "object") {
|
94
94
|
if (!applyToObject[key]) {
|
95
|
+
// @ts-expect-error this is fine... T is generic, but not sure why the matter to write to an object?
|
95
96
|
applyToObject[key] = [];
|
96
97
|
}
|
97
98
|
applyToObject[key][index] = storeObject(value[index], recentlyStoredObjects);
|
@@ -128,7 +129,10 @@ function removeObjectFromLists<T extends TypedObject>(object: T) {
|
|
128
129
|
*/
|
129
130
|
const registeredAutoRefreshes: AnyObject = {};
|
130
131
|
|
131
|
-
export async function autoRefreshObject<T extends TypedObject>(object: T, condition: (object: T) => boolean, callback: (object: T) => Promise<T>, interval = 3000) {
|
132
|
+
export async function autoRefreshObject<T extends TypedObject>(name: string, object: T, condition: (object: T) => boolean, callback: (object: T) => Promise<T>, interval = 3000) {
|
133
|
+
// Always clear any previously registered auto refreshes before creating a new timeout
|
134
|
+
stopAutoRefreshObject(name);
|
135
|
+
|
132
136
|
if (!object?.id || !object?.__type) {
|
133
137
|
throw new Error("Invalid stored object. Cannot auto-refresh");
|
134
138
|
}
|
@@ -144,13 +148,11 @@ export async function autoRefreshObject<T extends TypedObject>(object: T, condit
|
|
144
148
|
storeObject(refreshedObject);
|
145
149
|
}
|
146
150
|
|
147
|
-
// Save the
|
148
|
-
|
149
|
-
registeredAutoRefreshes[object.__type + ":" + object.id] = timeoutId;
|
151
|
+
// Save the autoRefreshId for the object so it can be cleared when the object refresh is no longer needed
|
152
|
+
registeredAutoRefreshes[name] = setTimeout(() => autoRefreshObject(name, object, condition, callback, interval), interval);
|
150
153
|
}
|
151
154
|
|
152
|
-
export
|
153
|
-
const timeoutId = registeredAutoRefreshes[
|
154
|
-
|
155
|
+
export function stopAutoRefreshObject(name: string) {
|
156
|
+
const timeoutId = registeredAutoRefreshes[name];
|
155
157
|
timeoutId && clearTimeout(timeoutId);
|
156
158
|
}
|
package/src/helpers/utils.ts
CHANGED
@@ -33,6 +33,9 @@ export async function pollUntil(callback: () => any, interval = 1000) {
|
|
33
33
|
*/
|
34
34
|
export function waitForRef(ref: Ref, value: any) {
|
35
35
|
return new Promise<void>((resolve) => {
|
36
|
+
if (ref.value === value) {
|
37
|
+
resolve();
|
38
|
+
}
|
36
39
|
watch(ref, (newValue) => {
|
37
40
|
if (newValue === value) {
|
38
41
|
resolve();
|
package/src/types/controls.d.ts
CHANGED
@@ -31,7 +31,7 @@ export interface ListControlsRoutes<T = ActionTargetItem> {
|
|
31
31
|
|
32
32
|
fieldOptions?(options?: RequestCallOptions): Promise<AnyObject>;
|
33
33
|
|
34
|
-
applyAction?(action: string | ResourceAction | ActionOptions, target: T | null, data?: object, options?: RequestCallOptions): Promise<
|
34
|
+
applyAction?(action: string | ResourceAction | ActionOptions, target: T | null, data?: object, options?: RequestCallOptions): Promise<ApplyActionResponse>;
|
35
35
|
|
36
36
|
batchAction?(action: string | ResourceAction | ActionOptions, targets: T[], data: object, options?: RequestCallOptions): Promise<AnyObject>;
|
37
37
|
|
@@ -123,5 +123,7 @@ export interface ListController<T = ActionTargetItem> {
|
|
123
123
|
export interface ApplyActionResponse<T = ActionTargetItem> {
|
124
124
|
item?: T;
|
125
125
|
result?: T | AnyObject;
|
126
|
-
success
|
126
|
+
success?: boolean;
|
127
|
+
error?: boolean;
|
128
|
+
message?: string;
|
127
129
|
}
|
package/src/types/index.d.ts
CHANGED