quasar-ui-danx 0.3.21 → 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- package/.eslintrc.cjs +32 -30
- package/danx-local.sh +1 -1
- package/dist/danx.es.js +7490 -7520
- package/dist/danx.es.js.map +1 -1
- package/dist/danx.umd.js +5 -5
- package/dist/danx.umd.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/ActionTable/ActionMenu.vue +1 -1
- package/src/components/ActionTable/ActionTable.vue +64 -45
- package/src/components/ActionTable/{ActionTableColumn.vue → Columns/ActionTableColumn.vue} +4 -3
- package/src/components/ActionTable/{ActionTableHeaderColumn.vue → Columns/ActionTableHeaderColumn.vue} +2 -2
- package/src/components/ActionTable/Columns/index.ts +2 -0
- package/src/components/ActionTable/Filters/CollapsableFiltersSidebar.vue +22 -21
- package/src/components/ActionTable/Form/Fields/DateRangeField.vue +3 -5
- package/src/components/ActionTable/Form/Fields/FileUploadButton.vue +33 -35
- package/src/components/ActionTable/Form/Fields/TextField.vue +36 -36
- package/src/components/ActionTable/Form/RenderedForm.vue +137 -112
- package/src/components/ActionTable/Form/form.d.ts +31 -0
- package/src/components/ActionTable/Layouts/ActionTableLayout.vue +88 -4
- package/src/components/ActionTable/TableSummaryRow.vue +4 -4
- package/src/components/ActionTable/Toolbars/ActionToolbar.vue +46 -0
- package/src/components/ActionTable/Toolbars/index.ts +1 -0
- package/src/components/ActionTable/index.ts +1 -2
- package/src/components/ActionTable/listControls.ts +512 -385
- package/src/components/ActionTable/listHelpers.ts +46 -44
- package/src/components/PanelsDrawer/PanelsDrawer.vue +37 -26
- package/src/components/PanelsDrawer/PanelsDrawerPanels.vue +1 -1
- package/src/components/PanelsDrawer/PanelsDrawerTabs.vue +1 -6
- package/src/components/Utility/Buttons/ExportButton.vue +1 -1
- package/src/components/Utility/Buttons/RefreshButton.vue +5 -5
- package/src/components/Utility/Controls/PreviousNextControls.vue +4 -4
- package/src/components/Utility/Layouts/CollapsableSidebar.vue +2 -8
- package/src/components/Utility/Popovers/PopoverMenu.vue +3 -3
- package/src/helpers/actions.ts +197 -187
- package/src/styles/general.scss +12 -11
- package/src/styles/quasar-reset.scss +59 -11
- package/src/styles/themes/danx/action-table.scss +19 -0
- package/src/styles/themes/danx/buttons.scss +13 -0
- package/src/styles/themes/danx/forms.scss +5 -0
- package/src/styles/themes/danx/index.scss +3 -0
- package/src/styles/themes/danx/panels.scss +19 -0
- package/src/styles/themes/danx/sidebar.scss +3 -0
- package/src/styles/themes/danx/toolbar.scss +3 -0
- package/types/index.d.ts +1 -0
- package/src/styles/actions.scss +0 -10
@@ -67,6 +67,16 @@
|
|
67
67
|
:key="field.id"
|
68
68
|
:class="{ 'mt-4': index > 0 }"
|
69
69
|
>
|
70
|
+
<RenderVnode
|
71
|
+
v-if="field.vnode"
|
72
|
+
:vnode="field.vnode"
|
73
|
+
:readonly="readonly"
|
74
|
+
:disable="disable"
|
75
|
+
:show-name="showName"
|
76
|
+
:no-label="noLabel"
|
77
|
+
:model-value="getFieldValue(field.name)"
|
78
|
+
@update:model-value="onInput(field.name, $event)"
|
79
|
+
/>
|
70
80
|
<Component
|
71
81
|
:is="field.component"
|
72
82
|
:key="field.name + '-' + currentVariation"
|
@@ -104,72 +114,75 @@
|
|
104
114
|
/>
|
105
115
|
</div>
|
106
116
|
</template>
|
107
|
-
<script setup>
|
117
|
+
<script setup lang="ts">
|
108
118
|
import { ExclamationCircleIcon as MissingIcon, PencilIcon as EditIcon } from "@heroicons/vue/solid";
|
109
119
|
import { computed, ref } from "vue";
|
110
120
|
import { FlashMessages, incrementName, replace } from "../../../helpers";
|
111
121
|
import { TrashIcon as RemoveIcon } from "../../../svg";
|
112
|
-
import { ConfirmDialog } from "../../Utility";
|
122
|
+
import { ConfirmDialog, RenderVnode } from "../../Utility";
|
113
123
|
import {
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
124
|
+
BooleanField,
|
125
|
+
DateField,
|
126
|
+
DateRangeField,
|
127
|
+
IntegerField,
|
128
|
+
MultiFileField,
|
129
|
+
NumberField,
|
130
|
+
SingleFileField,
|
131
|
+
TextField,
|
132
|
+
WysiwygField
|
123
133
|
} from "./Fields";
|
134
|
+
import { Form, FormFieldValue } from "./form.d.ts";
|
124
135
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
emptyValue: {
|
141
|
-
type: [String, Number, Boolean],
|
142
|
-
default: undefined
|
143
|
-
},
|
144
|
-
canModifyVariations: Boolean
|
136
|
+
export interface Props {
|
137
|
+
values?: FormFieldValue[] | object;
|
138
|
+
form: Form;
|
139
|
+
noLabel?: boolean;
|
140
|
+
showName?: boolean;
|
141
|
+
disable?: boolean;
|
142
|
+
readonly?: boolean;
|
143
|
+
saving?: boolean;
|
144
|
+
emptyValue?: string | number | boolean;
|
145
|
+
canModifyVariations?: boolean;
|
146
|
+
}
|
147
|
+
|
148
|
+
const props = withDefaults(defineProps<Props>(), {
|
149
|
+
values: null,
|
150
|
+
emptyValue: undefined
|
145
151
|
});
|
146
152
|
|
153
|
+
const emit = defineEmits(["update:values"]);
|
154
|
+
|
147
155
|
const FORM_FIELD_MAP = {
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
156
|
+
BOOLEAN: BooleanField,
|
157
|
+
DATE: DateField,
|
158
|
+
DATE_RANGE: DateRangeField,
|
159
|
+
INTEGER: IntegerField,
|
160
|
+
NUMBER: NumberField,
|
161
|
+
TEXT: TextField,
|
162
|
+
SINGLE_FILE: SingleFileField,
|
163
|
+
MULTI_FILE: MultiFileField,
|
164
|
+
WYSIWYG: WysiwygField
|
157
165
|
};
|
158
166
|
|
159
167
|
const mappedFields = props.form.fields.map((field) => ({
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
168
|
+
placeholder: `Enter ${field.label}`,
|
169
|
+
...field,
|
170
|
+
component: field.component || FORM_FIELD_MAP[field.type],
|
171
|
+
default: field.type === "BOOLEAN" ? false : ""
|
164
172
|
}));
|
165
173
|
|
174
|
+
const fieldResponses = computed(() => {
|
175
|
+
if (!props.values) return [];
|
176
|
+
if (Array.isArray(props.values)) return props.values;
|
177
|
+
return Object.entries(props.values).map(([name, value]) => ({ name, value, variation: "" }));
|
178
|
+
});
|
166
179
|
const variationNames = computed(() => {
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
180
|
+
const names = [...new Set(fieldResponses.value.map(v => v.variation))].sort();
|
181
|
+
// Always guarantee that we show the default variation
|
182
|
+
if (names.length === 0) {
|
183
|
+
names.push("");
|
184
|
+
}
|
185
|
+
return names;
|
173
186
|
});
|
174
187
|
|
175
188
|
const currentVariation = ref(variationNames.value[0] || "");
|
@@ -178,89 +191,101 @@ const variationToEdit = ref(false);
|
|
178
191
|
const variationToDelete = ref("");
|
179
192
|
const canAddVariation = computed(() => props.canModifyVariations && !props.readonly && !props.disable && variationNames.value.length < props.form.variations);
|
180
193
|
|
181
|
-
function getFieldResponse(name, variation) {
|
182
|
-
|
183
|
-
|
194
|
+
function getFieldResponse(name, variation: string = undefined) {
|
195
|
+
if (!fieldResponses.value) return undefined;
|
196
|
+
return fieldResponses.value.find((fr: FormFieldValue) => fr.variation === (variation !== undefined ? variation : currentVariation.value) && fr.name === name);
|
184
197
|
}
|
185
198
|
function getFieldValue(name) {
|
186
|
-
|
199
|
+
return getFieldResponse(name)?.value;
|
187
200
|
}
|
188
201
|
function onInput(name, value) {
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
202
|
+
const fieldResponse = getFieldResponse(name);
|
203
|
+
const newFieldResponse = {
|
204
|
+
name,
|
205
|
+
variation: currentVariation.value || "",
|
206
|
+
value: value === undefined ? props.emptyValue : value
|
207
|
+
};
|
208
|
+
const newValues = replace(fieldResponses.value, fieldResponse, newFieldResponse, true);
|
209
|
+
updateValues(newValues);
|
197
210
|
}
|
198
211
|
|
199
212
|
function createVariation(variation) {
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
213
|
+
return props.form.fields.map((field) => ({
|
214
|
+
variation,
|
215
|
+
name: field.name,
|
216
|
+
value: field.type === "BOOLEAN" ? false : null
|
217
|
+
}));
|
205
218
|
}
|
206
219
|
|
207
220
|
function onAddVariation() {
|
208
|
-
|
209
|
-
|
221
|
+
if (props.saving) return;
|
222
|
+
let newValues = [...fieldResponses.value];
|
210
223
|
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
224
|
+
if (newValues.length === 0) {
|
225
|
+
newValues = createVariation("");
|
226
|
+
}
|
227
|
+
const previousName = variationNames.value[variationNames.value.length - 1];
|
228
|
+
const newName = incrementName(!previousName ? "1" : previousName);
|
229
|
+
const newVariation = createVariation(newName);
|
230
|
+
updateValues([...newValues, ...newVariation]);
|
231
|
+
currentVariation.value = newName;
|
219
232
|
}
|
220
233
|
|
221
234
|
function onChangeVariationName() {
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
235
|
+
if (!newVariationName.value) return;
|
236
|
+
if (variationNames.value.includes(newVariationName.value)) {
|
237
|
+
FlashMessages.error("Variation name already exists");
|
238
|
+
return;
|
239
|
+
}
|
240
|
+
const newValues = fieldResponses.value.map((v) => {
|
241
|
+
if (v.variation === variationToEdit.value) {
|
242
|
+
return { ...v, variation: newVariationName.value };
|
243
|
+
}
|
244
|
+
return v;
|
245
|
+
});
|
246
|
+
updateValues(newValues);
|
247
|
+
|
248
|
+
currentVariation.value = newVariationName.value;
|
249
|
+
variationToEdit.value = false;
|
250
|
+
newVariationName.value = "";
|
251
|
+
}
|
252
|
+
|
253
|
+
function updateValues(values: FormFieldValue[]) {
|
254
|
+
let updatedValues: FormFieldValue[] | object = values;
|
234
255
|
|
235
|
-
|
236
|
-
|
237
|
-
|
256
|
+
if (!Array.isArray(props.values)) {
|
257
|
+
updatedValues = values.reduce((acc, v) => {
|
258
|
+
acc[v.name] = v.value;
|
259
|
+
return acc;
|
260
|
+
}, {});
|
261
|
+
}
|
262
|
+
emit("update:values", updatedValues);
|
238
263
|
}
|
239
264
|
|
240
|
-
function onRemoveVariation(name) {
|
241
|
-
|
265
|
+
function onRemoveVariation(name: string) {
|
266
|
+
if (!name) return;
|
242
267
|
|
243
|
-
|
244
|
-
|
268
|
+
const newValues = fieldResponses.value.filter((v) => v.variation !== name);
|
269
|
+
updateValues(newValues);
|
245
270
|
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
271
|
+
if (currentVariation.value === name) {
|
272
|
+
currentVariation.value = variationNames.value[0];
|
273
|
+
}
|
274
|
+
variationToDelete.value = "";
|
250
275
|
}
|
251
276
|
|
252
277
|
function isVariationFormComplete(variation) {
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
278
|
+
const requiredGroups = {};
|
279
|
+
return props.form.fields.filter(r => r.required || r.required_group).every((field) => {
|
280
|
+
const fieldResponse = getFieldResponse(field.name, variation);
|
281
|
+
const hasValue = !!fieldResponse && fieldResponse.value !== null;
|
282
|
+
if (field.required_group) {
|
283
|
+
// This required group has already been satisfied
|
284
|
+
if (requiredGroups[field.required_group]) return true;
|
285
|
+
return requiredGroups[field.required_group] = hasValue;
|
286
|
+
} else {
|
287
|
+
return hasValue;
|
288
|
+
}
|
289
|
+
});
|
265
290
|
}
|
266
291
|
</script>
|
@@ -0,0 +1,31 @@
|
|
1
|
+
import { VNode } from "vue";
|
2
|
+
|
3
|
+
export interface FormFieldOption {
|
4
|
+
value: string;
|
5
|
+
label: string;
|
6
|
+
}
|
7
|
+
|
8
|
+
export interface FormField {
|
9
|
+
id?: string;
|
10
|
+
type?: string;
|
11
|
+
name: string;
|
12
|
+
label: string;
|
13
|
+
vnode?: ((props) => VNode | any);
|
14
|
+
component?: any;
|
15
|
+
required?: boolean;
|
16
|
+
required_group?: string;
|
17
|
+
options?: FormFieldOption[];
|
18
|
+
}
|
19
|
+
|
20
|
+
export interface Form {
|
21
|
+
id?: string;
|
22
|
+
name?: string;
|
23
|
+
variations?: number;
|
24
|
+
fields: FormField[];
|
25
|
+
}
|
26
|
+
|
27
|
+
export interface FormFieldValue {
|
28
|
+
name: string,
|
29
|
+
value: any,
|
30
|
+
variation?: string
|
31
|
+
}
|
@@ -1,10 +1,94 @@
|
|
1
1
|
<template>
|
2
|
-
<div class="flex flex-grow flex-col flex-nowrap overflow-hidden h-full
|
2
|
+
<div class="flex flex-grow flex-col flex-nowrap overflow-hidden h-full">
|
3
3
|
<slot name="top" />
|
4
|
-
<slot name="toolbar"
|
4
|
+
<slot name="toolbar">
|
5
|
+
<ActionToolbar
|
6
|
+
:refresh-button="refreshButton"
|
7
|
+
:actions="actions?.filter(a => a.batch)"
|
8
|
+
:action-target="controller.selectedRows.value"
|
9
|
+
:exporter="controller.exportList"
|
10
|
+
@refresh="controller.refreshAll"
|
11
|
+
/>
|
12
|
+
</slot>
|
5
13
|
<div class="flex flex-nowrap flex-grow overflow-hidden w-full">
|
6
|
-
<slot name="
|
7
|
-
|
14
|
+
<slot name="filter-fields">
|
15
|
+
<CollapsableFiltersSidebar
|
16
|
+
v-if="activeFilter"
|
17
|
+
:name="controller.name"
|
18
|
+
:show-filters="showFilters"
|
19
|
+
:filters="filters"
|
20
|
+
:active-filter="activeFilter"
|
21
|
+
@update:active-filter="controller.setActiveFilter"
|
22
|
+
/>
|
23
|
+
</slot>
|
24
|
+
<slot>
|
25
|
+
<ActionTable
|
26
|
+
class="flex-grow"
|
27
|
+
:pagination="controller.pagination.value"
|
28
|
+
:selected-rows="controller.selectedRows.value"
|
29
|
+
:label="controller.label"
|
30
|
+
:name="controller.name"
|
31
|
+
:class="tableClass"
|
32
|
+
:summary="controller.summary.value"
|
33
|
+
:loading-list="controller.isLoadingList.value"
|
34
|
+
:loading-summary="controller.isLoadingSummary.value"
|
35
|
+
:paged-items="controller.pagedItems.value"
|
36
|
+
:columns="columns"
|
37
|
+
@update:selected-rows="controller.setSelectedRows"
|
38
|
+
@update:pagination="controller.setPagination"
|
39
|
+
/>
|
40
|
+
</slot>
|
41
|
+
<slot name="panels">
|
42
|
+
<PanelsDrawer
|
43
|
+
v-if="activeItem && panels"
|
44
|
+
:title="panelTitle"
|
45
|
+
:model-value="activePanel"
|
46
|
+
:panels="panels"
|
47
|
+
@update:model-value="panel => controller.activatePanel(activeItem, panel)"
|
48
|
+
@close="controller.setActiveItem(null)"
|
49
|
+
>
|
50
|
+
<template #controls>
|
51
|
+
<PreviousNextControls
|
52
|
+
:is-loading="controller.isLoadingList.value"
|
53
|
+
@next="controller.getNextItem"
|
54
|
+
/>
|
55
|
+
</template>
|
56
|
+
</PanelsDrawer>
|
57
|
+
</slot>
|
8
58
|
</div>
|
9
59
|
</div>
|
10
60
|
</template>
|
61
|
+
<script setup lang="ts">
|
62
|
+
import { computed } from "vue";
|
63
|
+
import { ActionOptions } from "../../../helpers";
|
64
|
+
import { PanelsDrawer } from "../../PanelsDrawer";
|
65
|
+
import { PreviousNextControls } from "../../Utility";
|
66
|
+
import ActionTable from "../ActionTable";
|
67
|
+
import { CollapsableFiltersSidebar } from "../Filters";
|
68
|
+
import { ActionController, ActionPanel, FilterField } from "../listControls";
|
69
|
+
import { TableColumn } from "../tableColumns";
|
70
|
+
import { ActionToolbar } from "../Toolbars";
|
71
|
+
|
72
|
+
const props = defineProps<{
|
73
|
+
refreshButton: boolean,
|
74
|
+
showFilters: boolean,
|
75
|
+
controller: ActionController,
|
76
|
+
columns: TableColumn[],
|
77
|
+
filters?: FilterField[],
|
78
|
+
panels?: ActionPanel[],
|
79
|
+
actions?: ActionOptions[],
|
80
|
+
exporter?: () => Promise<void>,
|
81
|
+
titleField?: string,
|
82
|
+
tableClass?: string
|
83
|
+
}>();
|
84
|
+
|
85
|
+
const activeFilter = computed(() => props.controller.activeFilter.value);
|
86
|
+
const activeItem = computed(() => props.controller.activeItem.value);
|
87
|
+
const activePanel = computed(() => props.controller.activePanel.value || "");
|
88
|
+
const panelTitle = computed(() => {
|
89
|
+
if (activeItem.value) {
|
90
|
+
return activeItem.value[props.titleField || "title"] || activeItem.value.label || activeItem.value.name;
|
91
|
+
}
|
92
|
+
return null;
|
93
|
+
});
|
94
|
+
</script>
|
@@ -1,12 +1,12 @@
|
|
1
1
|
<template>
|
2
2
|
<QTr
|
3
|
-
class="sticky-column-1 transition-all sticky-row"
|
4
|
-
:class="{'
|
3
|
+
class="dx-table-summary-tr sticky-column-1 transition-all sticky-row"
|
4
|
+
:class="{'has-selection': selectedCount, 'is-loading': loading}"
|
5
5
|
>
|
6
6
|
<QTd
|
7
7
|
:colspan="stickyColspan"
|
8
|
-
class="
|
9
|
-
:class="{'
|
8
|
+
class="dx-table-summary-td transition-all"
|
9
|
+
:class="{'has-selection': selectedCount}"
|
10
10
|
>
|
11
11
|
<div class="flex flex-nowrap items-center">
|
12
12
|
<div
|
@@ -0,0 +1,46 @@
|
|
1
|
+
<template>
|
2
|
+
<div class="flex items-center">
|
3
|
+
<div class="flex-grow px-6">
|
4
|
+
<slot name="title">
|
5
|
+
<h2 v-if="title">
|
6
|
+
{{ title }}
|
7
|
+
</h2>
|
8
|
+
</slot>
|
9
|
+
</div>
|
10
|
+
<div class="py-3 px-6 flex items-center flex-nowrap">
|
11
|
+
<slot />
|
12
|
+
<RefreshButton
|
13
|
+
v-if="refreshButton"
|
14
|
+
:loading="loading"
|
15
|
+
@click="$emit('refresh')"
|
16
|
+
/>
|
17
|
+
<ExportButton
|
18
|
+
v-if="exporter"
|
19
|
+
:exporter="exporter"
|
20
|
+
class="ml-4"
|
21
|
+
/>
|
22
|
+
<ActionMenu
|
23
|
+
v-if="actions.length > 0"
|
24
|
+
class="ml-4 dx-batch-actions"
|
25
|
+
:target="actionTarget"
|
26
|
+
:actions="actions"
|
27
|
+
/>
|
28
|
+
<slot name="after" />
|
29
|
+
</div>
|
30
|
+
</div>
|
31
|
+
</template>
|
32
|
+
<script setup lang="ts">
|
33
|
+
import { ActionOptions, ActionTargetItem } from "../../../helpers";
|
34
|
+
import { ExportButton, RefreshButton } from "../../Utility";
|
35
|
+
import ActionMenu from "../ActionMenu";
|
36
|
+
|
37
|
+
defineEmits(["refresh"]);
|
38
|
+
defineProps<{
|
39
|
+
title?: string,
|
40
|
+
actions: ActionOptions[],
|
41
|
+
actionTarget?: ActionTargetItem[],
|
42
|
+
refreshButton?: boolean,
|
43
|
+
loading?: boolean,
|
44
|
+
exporter?: () => void
|
45
|
+
}>();
|
46
|
+
</script>
|
@@ -0,0 +1 @@
|
|
1
|
+
export { default as ActionToolbar } from "./ActionToolbar.vue";
|
@@ -5,9 +5,8 @@ export * from "./Layouts";
|
|
5
5
|
export * from "./listControls";
|
6
6
|
export * from "./listHelpers";
|
7
7
|
export * from "./tableColumns";
|
8
|
+
export * from "./Toolbars";
|
8
9
|
export { default as ActionMenu } from "./ActionMenu.vue";
|
9
10
|
export { default as ActionTable } from "./ActionTable.vue";
|
10
|
-
export { default as ActionTableColumn } from "./ActionTableColumn.vue";
|
11
|
-
export { default as ActionTableHeaderColumn } from "./ActionTableHeaderColumn.vue";
|
12
11
|
export { default as EmptyTableState } from "./EmptyTableState.vue";
|
13
12
|
export { default as TableSummaryRow } from "./TableSummaryRow.vue";
|