quasar-ui-danx 0.4.37 → 0.4.40
Sign up to get free protection for your applications and to get access to all the features.
- package/dist/danx.es.js +6895 -6263
- package/dist/danx.es.js.map +1 -1
- package/dist/danx.umd.js +83 -83
- package/dist/danx.umd.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/ActionTable/Form/Fields/MultiFileField.vue +1 -2
- package/src/components/ActionTable/Form/Fields/SelectionMenuField.vue +158 -0
- package/src/components/ActionTable/Form/Fields/TextField.vue +1 -0
- package/src/components/ActionTable/Form/Fields/index.ts +1 -0
- package/src/components/DragAndDrop/ListItemDraggable.vue +1 -1
- package/src/components/Utility/Buttons/ActionButton.vue +163 -0
- package/src/components/Utility/Buttons/ShowHideButton.vue +5 -0
- package/src/components/Utility/Dialogs/InfoDialog.vue +1 -0
- package/src/helpers/actions.ts +8 -0
- package/src/helpers/formats.ts +16 -5
- package/src/helpers/routes.ts +18 -14
- package/src/types/controls.d.ts +9 -8
- package/src/types/fields.d.ts +1 -0
package/package.json
CHANGED
@@ -0,0 +1,158 @@
|
|
1
|
+
<template>
|
2
|
+
<div class="group flex items-center flex-nowrap gap-x-1 relative">
|
3
|
+
<QInnerLoading
|
4
|
+
v-if="loading"
|
5
|
+
showing
|
6
|
+
class="bg-sky-900 opacity-50 z-10 rounded"
|
7
|
+
color="teal"
|
8
|
+
/>
|
9
|
+
<ShowHideButton
|
10
|
+
v-if="selectable"
|
11
|
+
v-model="isSelecting"
|
12
|
+
:disable="disable"
|
13
|
+
:label="selectText"
|
14
|
+
:class="selectClass"
|
15
|
+
:show-icon="selectIcon || DefaultSelectIcon"
|
16
|
+
class="mr-2"
|
17
|
+
>
|
18
|
+
<template #label>
|
19
|
+
<QMenu
|
20
|
+
:model-value="isSelecting"
|
21
|
+
@before-hide="isSelecting = false"
|
22
|
+
@hide="isSelecting = false"
|
23
|
+
>
|
24
|
+
<div>
|
25
|
+
<div
|
26
|
+
v-for="option in options"
|
27
|
+
:key="option.id"
|
28
|
+
v-ripple
|
29
|
+
class="cursor-pointer flex items-center relative"
|
30
|
+
:class="{'bg-sky-900 hover:bg-sky-800': selected?.id === option.id, 'hover:bg-slate-600': selected?.id !== option.id}"
|
31
|
+
@click="selected = option"
|
32
|
+
>
|
33
|
+
<div class="flex-grow px-4 py-2">
|
34
|
+
{{ option.name }}
|
35
|
+
</div>
|
36
|
+
<ActionButton
|
37
|
+
v-if="deletable"
|
38
|
+
type="trash"
|
39
|
+
class="ml-4 mr-2"
|
40
|
+
@click.stop.prevent="$emit('delete', option)"
|
41
|
+
/>
|
42
|
+
</div>
|
43
|
+
<template v-if="creatable">
|
44
|
+
<QSeparator class="bg-slate-400 my-2" />
|
45
|
+
<div class="px-4 mb-2">
|
46
|
+
<QBtn
|
47
|
+
:class="createClass"
|
48
|
+
:loading="loading"
|
49
|
+
@click="$emit('create')"
|
50
|
+
>
|
51
|
+
<CreateIcon
|
52
|
+
class="w-3"
|
53
|
+
:class="createText ? 'mr-2' : ''"
|
54
|
+
/>
|
55
|
+
{{ createText }}
|
56
|
+
</QBtn>
|
57
|
+
</div>
|
58
|
+
</template>
|
59
|
+
</div>
|
60
|
+
</QMenu>
|
61
|
+
</template>
|
62
|
+
</ShowHideButton>
|
63
|
+
|
64
|
+
<div :class="labelClass">
|
65
|
+
<template v-if="selected">
|
66
|
+
<EditableDiv
|
67
|
+
v-if="nameEditable"
|
68
|
+
:model-value="selected.name"
|
69
|
+
color="slate-600"
|
70
|
+
@update:model-value="name => $emit('update', {name})"
|
71
|
+
/>
|
72
|
+
<template v-else>
|
73
|
+
{{ selected.name }}
|
74
|
+
</template>
|
75
|
+
</template>
|
76
|
+
<template v-else>
|
77
|
+
<slot name="no-selection">
|
78
|
+
{{ placeholder }}
|
79
|
+
</slot>
|
80
|
+
</template>
|
81
|
+
</div>
|
82
|
+
|
83
|
+
<ShowHideButton
|
84
|
+
v-if="editable && selected"
|
85
|
+
v-model="editing"
|
86
|
+
:label="editText"
|
87
|
+
:class="editClass"
|
88
|
+
class="opacity-0 group-hover:opacity-100 transition-all"
|
89
|
+
:show-icon="EditIcon"
|
90
|
+
:hide-icon="DoneEditingIcon"
|
91
|
+
/>
|
92
|
+
|
93
|
+
<QBtn
|
94
|
+
v-if="clearable && selected"
|
95
|
+
:label="clearText"
|
96
|
+
:class="clearClass"
|
97
|
+
class="opacity-0 group-hover:opacity-100 transition-all"
|
98
|
+
@click="selected = null"
|
99
|
+
>
|
100
|
+
<ClearIcon class="w-4" />
|
101
|
+
</QBtn>
|
102
|
+
</div>
|
103
|
+
</template>
|
104
|
+
<script setup lang="ts">
|
105
|
+
import {
|
106
|
+
FaSolidCheck as DoneEditingIcon,
|
107
|
+
FaSolidCircleXmark as ClearIcon,
|
108
|
+
FaSolidListCheck as DefaultSelectIcon,
|
109
|
+
FaSolidPencil as EditIcon,
|
110
|
+
FaSolidPlus as CreateIcon
|
111
|
+
} from "danx-icon";
|
112
|
+
import { ref } from "vue";
|
113
|
+
import { ActionTargetItem } from "../../../../types";
|
114
|
+
import { ShowHideButton } from "../../../Utility/Buttons";
|
115
|
+
import ActionButton from "../../../Utility/Buttons/ActionButton";
|
116
|
+
import EditableDiv from "./EditableDiv";
|
117
|
+
|
118
|
+
defineEmits(["create", "update", "delete"]);
|
119
|
+
const selected = defineModel<ActionTargetItem | null>("selected");
|
120
|
+
const editing = defineModel<boolean>("editing");
|
121
|
+
withDefaults(defineProps<{
|
122
|
+
options: ActionTargetItem[];
|
123
|
+
showEdit?: boolean;
|
124
|
+
loading?: boolean;
|
125
|
+
selectText?: string;
|
126
|
+
createText?: string;
|
127
|
+
editText?: string;
|
128
|
+
clearText?: string;
|
129
|
+
placeholder?: string;
|
130
|
+
selectClass?: string;
|
131
|
+
createClass?: string;
|
132
|
+
editClass?: string;
|
133
|
+
clearClass?: string;
|
134
|
+
labelClass?: string;
|
135
|
+
selectIcon?: object | string;
|
136
|
+
selectable?: boolean;
|
137
|
+
creatable?: boolean;
|
138
|
+
editable?: boolean;
|
139
|
+
deletable?: boolean;
|
140
|
+
nameEditable?: boolean;
|
141
|
+
clearable?: boolean;
|
142
|
+
disable?: boolean;
|
143
|
+
}>(), {
|
144
|
+
selectText: "",
|
145
|
+
createText: "",
|
146
|
+
editText: "",
|
147
|
+
clearText: "",
|
148
|
+
placeholder: "(No selection)",
|
149
|
+
selectClass: "bg-sky-800",
|
150
|
+
createClass: "bg-green-900",
|
151
|
+
editClass: "",
|
152
|
+
clearClass: "rounded-full",
|
153
|
+
labelClass: "text-slate-600",
|
154
|
+
selectIcon: null
|
155
|
+
});
|
156
|
+
|
157
|
+
const isSelecting = ref(false);
|
158
|
+
</script>
|
@@ -19,6 +19,7 @@ export { default as NumberField } from "./NumberField.vue";
|
|
19
19
|
export { default as NumberRangeField } from "./NumberRangeField.vue";
|
20
20
|
export { default as SelectDrawer } from "./SelectDrawer.vue";
|
21
21
|
export { default as SelectField } from "./SelectField.vue";
|
22
|
+
export { default as SelectionMenuField } from "./SelectionMenuField.vue";
|
22
23
|
export { default as SelectOrCreateField } from "./SelectOrCreateField.vue";
|
23
24
|
export { default as SelectWithChildrenField } from "./SelectWithChildrenField.vue";
|
24
25
|
export { default as SingleFileField } from "./SingleFileField.vue";
|
@@ -28,10 +28,10 @@
|
|
28
28
|
</div>
|
29
29
|
</template>
|
30
30
|
<script setup lang="ts">
|
31
|
-
import { DropZoneResolver } from "src/components/DragAndDrop/dragAndDrop";
|
32
31
|
import { computed, watch } from "vue";
|
33
32
|
import { DragHandleDotsIcon as DragHandleIcon } from "../../svg";
|
34
33
|
import { SvgImg } from "../Utility";
|
34
|
+
import { DropZoneResolver } from "./dragAndDrop";
|
35
35
|
import { ListDragAndDrop } from "./listDragAndDrop";
|
36
36
|
|
37
37
|
const emit = defineEmits(["position", "update:list-items", "drop-zone"]);
|
@@ -0,0 +1,163 @@
|
|
1
|
+
<template>
|
2
|
+
<QBtn
|
3
|
+
:loading="isSaving"
|
4
|
+
class="shadow-none"
|
5
|
+
:class="colorClass"
|
6
|
+
@click="onAction"
|
7
|
+
>
|
8
|
+
<div class="flex items-center flex-nowrap">
|
9
|
+
<component
|
10
|
+
:is="icon || typeOptions.icon"
|
11
|
+
class="transition-all"
|
12
|
+
:class="iconClass + ' ' + typeOptions.iconClass"
|
13
|
+
/>
|
14
|
+
<slot>
|
15
|
+
<div
|
16
|
+
v-if="label"
|
17
|
+
class="ml-2"
|
18
|
+
>
|
19
|
+
{{ label }}
|
20
|
+
</div>
|
21
|
+
</slot>
|
22
|
+
</div>
|
23
|
+
<QTooltip
|
24
|
+
v-if="tooltip"
|
25
|
+
class="whitespace-nowrap"
|
26
|
+
>
|
27
|
+
{{ tooltip }}
|
28
|
+
</QTooltip>
|
29
|
+
</QBtn>
|
30
|
+
</template>
|
31
|
+
<script setup lang="ts">
|
32
|
+
import {
|
33
|
+
FaSolidArrowsRotate as RefreshIcon,
|
34
|
+
FaSolidPause as PauseIcon,
|
35
|
+
FaSolidPencil as EditIcon,
|
36
|
+
FaSolidPlay as PlayIcon,
|
37
|
+
FaSolidPlus as CreateIcon,
|
38
|
+
FaSolidStop as StopIcon,
|
39
|
+
FaSolidTrash as TrashIcon
|
40
|
+
} from "danx-icon";
|
41
|
+
import { computed } from "vue";
|
42
|
+
import { ActionTarget, ResourceAction } from "../../../types";
|
43
|
+
|
44
|
+
export interface ActionButtonProps {
|
45
|
+
type?: "trash" | "trash-red" | "create" | "edit" | "play" | "stop" | "pause" | "refresh";
|
46
|
+
color?: "red" | "blue" | "sky" | "green" | "green-invert" | "lime" | "white" | "gray";
|
47
|
+
icon?: object | string;
|
48
|
+
iconClass?: string;
|
49
|
+
label?: string;
|
50
|
+
tooltip?: string;
|
51
|
+
saving?: boolean;
|
52
|
+
action?: ResourceAction;
|
53
|
+
target?: ActionTarget;
|
54
|
+
input?: object;
|
55
|
+
}
|
56
|
+
|
57
|
+
const emit = defineEmits(["success", "error", "always"]);
|
58
|
+
const props = withDefaults(defineProps<ActionButtonProps>(), {
|
59
|
+
type: null,
|
60
|
+
color: null,
|
61
|
+
icon: null,
|
62
|
+
iconClass: "",
|
63
|
+
label: "",
|
64
|
+
tooltip: "",
|
65
|
+
action: null,
|
66
|
+
target: null,
|
67
|
+
input: null
|
68
|
+
});
|
69
|
+
|
70
|
+
const colorClass = computed(() => {
|
71
|
+
switch (props.color) {
|
72
|
+
case "red":
|
73
|
+
return "text-red-900 bg-red-300 hover:bg-red-400";
|
74
|
+
case "lime":
|
75
|
+
return "text-lime-900 bg-lime-300 hover:bg-lime-400";
|
76
|
+
case "green":
|
77
|
+
return "text-green-900 bg-green-300 hover:bg-green-400";
|
78
|
+
case "green-invert":
|
79
|
+
return "text-lime-800 bg-green-200 hover:bg-lime-800 hover:text-green-200";
|
80
|
+
case "blue":
|
81
|
+
return "text-blue-900 bg-blue-300 hover:bg-blue-400";
|
82
|
+
case "sky":
|
83
|
+
return "text-sky-900 bg-sky-300 hover:bg-sky-400";
|
84
|
+
case "white":
|
85
|
+
return "text-white bg-gray-800 hover:bg-gray-200";
|
86
|
+
case "gray":
|
87
|
+
return "text-slate-200 bg-slate-800 hover:bg-slate-900";
|
88
|
+
default:
|
89
|
+
return "text-slate-200 hover:bg-slate-800";
|
90
|
+
}
|
91
|
+
});
|
92
|
+
const typeOptions = computed(() => {
|
93
|
+
switch (props.type) {
|
94
|
+
case "trash":
|
95
|
+
return {
|
96
|
+
icon: TrashIcon,
|
97
|
+
iconClass: "w-3"
|
98
|
+
};
|
99
|
+
case "create":
|
100
|
+
return {
|
101
|
+
icon: CreateIcon,
|
102
|
+
iconClass: "w-3"
|
103
|
+
};
|
104
|
+
case "edit":
|
105
|
+
return {
|
106
|
+
icon: EditIcon,
|
107
|
+
iconClass: "w-3"
|
108
|
+
};
|
109
|
+
case "play":
|
110
|
+
return {
|
111
|
+
icon: PlayIcon,
|
112
|
+
iconClass: "w-3"
|
113
|
+
};
|
114
|
+
case "stop":
|
115
|
+
return {
|
116
|
+
icon: StopIcon,
|
117
|
+
iconClass: "w-3"
|
118
|
+
};
|
119
|
+
case "pause":
|
120
|
+
return {
|
121
|
+
icon: PauseIcon,
|
122
|
+
iconClass: "w-3"
|
123
|
+
};
|
124
|
+
case "refresh":
|
125
|
+
return {
|
126
|
+
icon: RefreshIcon,
|
127
|
+
iconClass: "w-4"
|
128
|
+
};
|
129
|
+
default:
|
130
|
+
return {
|
131
|
+
icon: EditIcon,
|
132
|
+
iconClass: "w-3"
|
133
|
+
};
|
134
|
+
}
|
135
|
+
});
|
136
|
+
|
137
|
+
const isSaving = computed(() => {
|
138
|
+
if (props.saving) return true;
|
139
|
+
if (props.target) {
|
140
|
+
if (Array.isArray(props.target)) {
|
141
|
+
return props.target.some((t) => t.isSaving);
|
142
|
+
}
|
143
|
+
return props.target.isSaving;
|
144
|
+
}
|
145
|
+
if (props.action) {
|
146
|
+
return props.action.isApplying;
|
147
|
+
}
|
148
|
+
return false;
|
149
|
+
});
|
150
|
+
|
151
|
+
function onAction() {
|
152
|
+
if (props.action) {
|
153
|
+
props.action.trigger(props.target, props.input).then(async (response) => {
|
154
|
+
emit("success", typeof response.json === "function" ? await response.json() : response);
|
155
|
+
}).catch((e) => {
|
156
|
+
console.error(`Action emitted an error: ${props.action.name}`, e, props.target);
|
157
|
+
emit("error", e);
|
158
|
+
}).finally(() => {
|
159
|
+
emit("always");
|
160
|
+
});
|
161
|
+
}
|
162
|
+
}
|
163
|
+
</script>
|
package/src/helpers/actions.ts
CHANGED
@@ -308,6 +308,10 @@ async function onConfirmAction(action: ActionOptions, target: ActionTarget, inpu
|
|
308
308
|
|
309
309
|
export function withDefaultActions(label: string, listController?: ListController): ActionOptions[] {
|
310
310
|
return [
|
311
|
+
{
|
312
|
+
name: "quick-create",
|
313
|
+
alias: "create"
|
314
|
+
},
|
311
315
|
{
|
312
316
|
name: "create",
|
313
317
|
label: "Create " + label,
|
@@ -339,6 +343,10 @@ export function withDefaultActions(label: string, listController?: ListControlle
|
|
339
343
|
icon: EditIcon,
|
340
344
|
onAction: (action, target) => listController?.activatePanel(target, "edit")
|
341
345
|
},
|
346
|
+
{
|
347
|
+
name: "quick-delete",
|
348
|
+
alias: "delete"
|
349
|
+
},
|
342
350
|
{
|
343
351
|
name: "delete",
|
344
352
|
label: "Delete",
|
package/src/helpers/formats.ts
CHANGED
@@ -180,16 +180,27 @@ export function fSecondsToTime(second: number) {
|
|
180
180
|
return (hours ? hours + ":" : "") + time.toFormat("mm:ss");
|
181
181
|
}
|
182
182
|
|
183
|
-
|
183
|
+
/**
|
184
|
+
* Formats a number of seconds into a duration string in 00h 00m 00s format
|
185
|
+
*/
|
186
|
+
export function fSecondsToDuration(seconds: number) {
|
187
|
+
const hours = Math.floor(seconds / 3600);
|
188
|
+
const minutes = Math.floor((seconds % 3600) / 60);
|
189
|
+
const secs = Math.floor(seconds % 60);
|
190
|
+
return `${hours ? hours + "h " : ""}${minutes ? minutes + "m " : ""}${secs}s`;
|
191
|
+
}
|
192
|
+
|
193
|
+
/**
|
194
|
+
* Formats a duration between two date strings in 00h 00m 00s format
|
195
|
+
*/
|
196
|
+
export function fDuration(start: string, end?: string) {
|
184
197
|
const endDateTime = end ? parseDateTime(end) : DateTime.now();
|
185
198
|
const diff = endDateTime?.diff(parseDateTime(start) || DateTime.now(), ["hours", "minutes", "seconds"]);
|
186
199
|
if (!diff?.isValid) {
|
187
200
|
return "-";
|
188
201
|
}
|
189
|
-
const
|
190
|
-
|
191
|
-
const seconds = Math.floor(diff.seconds);
|
192
|
-
return `${hours ? hours + "h " : ""}${minutes ? minutes + "m " : ""}${seconds}s`;
|
202
|
+
const totalSeconds = diff.as("seconds");
|
203
|
+
return fSecondsToDuration(totalSeconds);
|
193
204
|
}
|
194
205
|
|
195
206
|
/**
|
package/src/helpers/routes.ts
CHANGED
@@ -5,27 +5,31 @@ import { request } from "./request";
|
|
5
5
|
|
6
6
|
export function useActionRoutes(baseUrl: string, extend?: object): ListControlsRoutes {
|
7
7
|
return {
|
8
|
-
list(pager?) {
|
9
|
-
return request.post(`${baseUrl}/list`, pager);
|
8
|
+
list(pager?, options?) {
|
9
|
+
return request.post(`${baseUrl}/list`, pager, options);
|
10
10
|
},
|
11
|
-
summary(filter) {
|
12
|
-
return request.post(`${baseUrl}/summary`, { filter });
|
11
|
+
summary(filter, options?) {
|
12
|
+
return request.post(`${baseUrl}/summary`, { filter }, options);
|
13
13
|
},
|
14
|
-
details(target, fields) {
|
15
|
-
|
14
|
+
details(target, fields, options?) {
|
15
|
+
options = options || {};
|
16
|
+
fields && (options.params = { fields });
|
17
|
+
return request.get(`${baseUrl}/${target.id}/details`, options);
|
16
18
|
},
|
17
|
-
async detailsAndStore(target, fields) {
|
18
|
-
|
19
|
+
async detailsAndStore(target, fields, options?) {
|
20
|
+
options = options || {};
|
21
|
+
fields && (options.params = { fields });
|
22
|
+
const item = await request.get(`${baseUrl}/${target.id}/details`, options);
|
19
23
|
return storeObject(item);
|
20
24
|
},
|
21
|
-
fieldOptions() {
|
22
|
-
return request.get(`${baseUrl}/field-options
|
25
|
+
fieldOptions(options?) {
|
26
|
+
return request.get(`${baseUrl}/field-options`, options);
|
23
27
|
},
|
24
|
-
applyAction(action, target, data) {
|
25
|
-
return request.post(`${baseUrl}/${target ? target.id : "new"}/apply-action`, { action, data });
|
28
|
+
applyAction(action, target, data, options?) {
|
29
|
+
return request.post(`${baseUrl}/${target ? target.id : "new"}/apply-action`, { action, data }, options);
|
26
30
|
},
|
27
|
-
batchAction(action, targets, data) {
|
28
|
-
return request.post(`${baseUrl}/batch-action`, { action, filter: { id: targets.map(r => r.id) }, data });
|
31
|
+
batchAction(action, targets, data, options?) {
|
32
|
+
return request.post(`${baseUrl}/batch-action`, { action, filter: { id: targets.map(r => r.id) }, data }, options);
|
29
33
|
},
|
30
34
|
export(filter, name) {
|
31
35
|
return downloadFile(`${baseUrl}/export`, name || "export.csv", { filter });
|
package/src/types/controls.d.ts
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
import { RequestCallOptions } from "src/types/requests";
|
1
2
|
import { ActionOptions, ActionTargetItem, ResourceAction } from "./actions";
|
2
3
|
import { AnyObject, ComputedRef, LabelValueItem, Ref } from "./shared";
|
3
4
|
|
@@ -20,21 +21,21 @@ export interface FilterGroup {
|
|
20
21
|
}
|
21
22
|
|
22
23
|
export interface ListControlsRoutes<T = ActionTargetItem> {
|
23
|
-
list(pager?: ListControlsPagination): Promise<PagedItems>;
|
24
|
+
list(pager?: ListControlsPagination, options?: RequestCallOptions): Promise<PagedItems>;
|
24
25
|
|
25
|
-
summary?(filter?: ListControlsFilter): Promise<AnyObject>;
|
26
|
+
summary?(filter?: ListControlsFilter, options?: RequestCallOptions): Promise<AnyObject>;
|
26
27
|
|
27
|
-
details?(target: T, fields
|
28
|
+
details?(target: T, fields?: ControlsFieldsList, options?: RequestCallOptions): Promise<T>;
|
28
29
|
|
29
|
-
detailsAndStore?(target: T, fields
|
30
|
+
detailsAndStore?(target: T, fields?: ControlsFieldsList, options?: RequestCallOptions): Promise<T>;
|
30
31
|
|
31
|
-
more?(pager: ListControlsPagination): Promise<T[]>;
|
32
|
+
more?(pager: ListControlsPagination, options?: RequestCallOptions): Promise<T[]>;
|
32
33
|
|
33
|
-
fieldOptions?(filter?: AnyObject): Promise<AnyObject>;
|
34
|
+
fieldOptions?(filter?: AnyObject, options?: RequestCallOptions): Promise<AnyObject>;
|
34
35
|
|
35
|
-
applyAction?(action: string | ResourceAction | ActionOptions, target: T | null, data?: object): Promise<AnyObject>;
|
36
|
+
applyAction?(action: string | ResourceAction | ActionOptions, target: T | null, data?: object, options?: RequestCallOptions): Promise<AnyObject>;
|
36
37
|
|
37
|
-
batchAction?(action: string | ResourceAction | ActionOptions, targets: T[], data: object): Promise<AnyObject>;
|
38
|
+
batchAction?(action: string | ResourceAction | ActionOptions, targets: T[], data: object, options?: RequestCallOptions): Promise<AnyObject>;
|
38
39
|
|
39
40
|
export?(filter?: ListControlsFilter, name?: string): Promise<void>;
|
40
41
|
}
|