quasar-ui-danx 0.4.16 → 0.4.18
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 +3940 -3930
- package/dist/danx.es.js.map +1 -1
- package/dist/danx.umd.js +60 -60
- package/dist/danx.umd.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/ActionTable/ActionTable.vue +62 -62
- package/src/components/ActionTable/Layouts/ActionTableLayout.vue +74 -69
- package/src/components/ActionTable/listControls.ts +40 -24
- package/src/helpers/routes.ts +4 -0
- package/src/helpers/utils.ts +16 -0
- package/src/types/controls.d.ts +2 -0
package/package.json
CHANGED
@@ -1,66 +1,66 @@
|
|
1
1
|
<template>
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
2
|
+
<div
|
3
|
+
class="dx-action-table overflow-hidden"
|
4
|
+
:class="{'dx-no-data': !hasData, 'dx-is-loading': loadingList || loadingSummary, 'dx-is-loading-list': loadingList}"
|
5
|
+
>
|
6
|
+
<ActionVnode />
|
7
|
+
<QTable
|
8
|
+
ref="actionTable"
|
9
|
+
:selected="selectedRows"
|
10
|
+
:pagination="pagination"
|
11
|
+
:columns="tableColumns"
|
12
|
+
:loading="loadingList || loadingSummary"
|
13
|
+
:rows="pagedItems?.data || []"
|
14
|
+
:binary-state-sort="false"
|
15
|
+
:selection="selection"
|
16
|
+
:rows-per-page-options="rowsPerPageOptions"
|
17
|
+
class="sticky-column sticky-header w-full h-full !border-0"
|
18
|
+
:color="color"
|
19
|
+
@update:selected="$emit('update:selected-rows', $event)"
|
20
|
+
@update:pagination="() => {}"
|
21
|
+
@request="(e) => $emit('update:pagination', {...e.pagination, __sort: mapSortBy(e.pagination, tableColumns)})"
|
22
|
+
>
|
23
|
+
<template #no-data>
|
24
|
+
<slot name="empty">
|
25
|
+
<EmptyTableState :text="`There are no ${label.toLowerCase()} matching the applied filter`" />
|
26
|
+
</slot>
|
27
|
+
</template>
|
28
|
+
<template #top-row>
|
29
|
+
<TableSummaryRow
|
30
|
+
v-if="hasData"
|
31
|
+
:label="label"
|
32
|
+
:item-count="summary?.count || 0"
|
33
|
+
:selected-count="selectedRows.length"
|
34
|
+
:sticky-colspan="summaryColSpan"
|
35
|
+
:loading="loadingSummary"
|
36
|
+
:summary="summary"
|
37
|
+
:columns="tableColumns"
|
38
|
+
@clear="$emit('update:selected-rows', [])"
|
39
|
+
/>
|
40
|
+
</template>
|
41
|
+
<template #header-cell="rowProps">
|
42
|
+
<ActionTableHeaderColumn
|
43
|
+
v-model="columnSettings"
|
44
|
+
:row-props="rowProps"
|
45
|
+
:name="name"
|
46
|
+
@update:model-value="onUpdateColumnSettings"
|
47
|
+
/>
|
48
|
+
</template>
|
49
|
+
<template #body-cell="rowProps">
|
50
|
+
<ActionTableColumn
|
51
|
+
:key="rowProps.key"
|
52
|
+
:row-props="rowProps"
|
53
|
+
:settings="columnSettings[rowProps.col.name]"
|
54
|
+
>
|
55
|
+
<slot
|
56
|
+
:column-name="rowProps.col.name"
|
57
|
+
:row="rowProps.row"
|
58
|
+
:value="rowProps.value"
|
59
|
+
/>
|
60
|
+
</ActionTableColumn>
|
61
|
+
</template>
|
62
|
+
</QTable>
|
63
|
+
</div>
|
64
64
|
</template>
|
65
65
|
|
66
66
|
<script setup lang="ts">
|
@@ -1,72 +1,72 @@
|
|
1
1
|
<template>
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
2
|
+
<div class="flex flex-grow flex-col flex-nowrap overflow-hidden h-full">
|
3
|
+
<slot name="top" />
|
4
|
+
<slot name="toolbar">
|
5
|
+
<ActionToolbar
|
6
|
+
v-if="!hideToolbar"
|
7
|
+
:title="title"
|
8
|
+
:refresh-button="refreshButton"
|
9
|
+
:actions="actions?.filter(a => a.batch)"
|
10
|
+
:action-target="controller.selectedRows.value"
|
11
|
+
:exporter="controller.exportList"
|
12
|
+
:loading="controller.isLoadingList.value || controller.isLoadingSummary.value"
|
13
|
+
@refresh="controller.refreshAll"
|
14
|
+
>
|
15
|
+
<template #default>
|
16
|
+
<slot name="action-toolbar" />
|
17
|
+
</template>
|
18
|
+
</ActionToolbar>
|
19
|
+
</slot>
|
20
|
+
<div class="flex flex-nowrap flex-grow overflow-hidden w-full">
|
21
|
+
<slot name="filters">
|
22
|
+
<CollapsableFiltersSidebar
|
23
|
+
v-if="activeFilter"
|
24
|
+
:name="controller.name"
|
25
|
+
:show-filters="showFilters"
|
26
|
+
:filters="filters"
|
27
|
+
:active-filter="activeFilter"
|
28
|
+
class="dx-action-table-filters"
|
29
|
+
@update:active-filter="controller.setActiveFilter"
|
30
|
+
/>
|
31
|
+
</slot>
|
32
|
+
<slot>
|
33
|
+
<ActionTable
|
34
|
+
class="flex-grow"
|
35
|
+
:pagination="controller.pagination.value"
|
36
|
+
:selected-rows="controller.selectedRows.value"
|
37
|
+
:label="controller.label"
|
38
|
+
:name="controller.name"
|
39
|
+
:class="tableClass"
|
40
|
+
:summary="controller.summary.value"
|
41
|
+
:loading-list="controller.isLoadingList.value"
|
42
|
+
:loading-summary="controller.isLoadingSummary.value"
|
43
|
+
:paged-items="controller.pagedItems.value"
|
44
|
+
:columns="columns"
|
45
|
+
:selection="selection"
|
46
|
+
@update:selected-rows="controller.setSelectedRows"
|
47
|
+
@update:pagination="controller.setPagination"
|
48
|
+
/>
|
49
|
+
</slot>
|
50
|
+
<slot name="panels">
|
51
|
+
<PanelsDrawer
|
52
|
+
v-if="activeItem && panels"
|
53
|
+
:title="panelTitle"
|
54
|
+
:model-value="activePanel"
|
55
|
+
:active-item="activeItem"
|
56
|
+
:panels="panels"
|
57
|
+
@update:model-value="panel => controller.activatePanel(activeItem, panel)"
|
58
|
+
@close="controller.setActiveItem(null)"
|
59
|
+
>
|
60
|
+
<template #controls>
|
61
|
+
<PreviousNextControls
|
62
|
+
:is-loading="controller.isLoadingList.value"
|
63
|
+
@next="controller.getNextItem"
|
64
|
+
/>
|
65
|
+
</template>
|
66
|
+
</PanelsDrawer>
|
67
|
+
</slot>
|
68
|
+
</div>
|
69
|
+
</div>
|
70
70
|
</template>
|
71
71
|
<script setup lang="ts">
|
72
72
|
import { computed } from "vue";
|
@@ -96,7 +96,12 @@ export interface ActionTableLayoutProps {
|
|
96
96
|
const props = withDefaults(defineProps<ActionTableLayoutProps>(), {
|
97
97
|
title: "",
|
98
98
|
tableClass: "",
|
99
|
-
selection: "multiple"
|
99
|
+
selection: "multiple",
|
100
|
+
filters: undefined,
|
101
|
+
panels: undefined,
|
102
|
+
actions: undefined,
|
103
|
+
exporter: undefined,
|
104
|
+
panelTitleField: ""
|
100
105
|
});
|
101
106
|
|
102
107
|
const activeFilter = computed(() => props.controller.activeFilter.value);
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import { computed, Ref, ref, shallowRef, watch } from "vue";
|
2
2
|
import { RouteParams, Router } from "vue-router";
|
3
3
|
import { danxOptions } from "../../config";
|
4
|
-
import { getItem, setItem, storeObject, waitForRef } from "../../helpers";
|
4
|
+
import { getItem, latestCallOnly, setItem, storeObject, waitForRef } from "../../helpers";
|
5
5
|
import {
|
6
6
|
ActionController,
|
7
7
|
ActionTargetItem,
|
@@ -290,10 +290,16 @@ export function useListControls(name: string, options: ListControlsOptions): Act
|
|
290
290
|
* @returns {Promise<void>}
|
291
291
|
*/
|
292
292
|
async function getActiveItemDetails() {
|
293
|
-
if (!activeItem.value || !options.routes.details) return;
|
294
|
-
|
295
293
|
try {
|
296
|
-
const
|
294
|
+
const latestResult = latestCallOnly("active-item", async () => {
|
295
|
+
if (!activeItem.value || !options.routes.details) return undefined;
|
296
|
+
return await options.routes.details(activeItem.value);
|
297
|
+
});
|
298
|
+
|
299
|
+
const result = await latestResult();
|
300
|
+
|
301
|
+
// Undefined means we were not the latest, or the request was invalid (ie: activeItem was already cleared)
|
302
|
+
if (result === undefined) return;
|
297
303
|
|
298
304
|
if (!result || !result.__type || !result.id) {
|
299
305
|
return console.error("Invalid response from details route: All responses must include a __type and id field. result =", result);
|
@@ -354,33 +360,43 @@ export function useListControls(name: string, options: ListControlsOptions): Act
|
|
354
360
|
const index = pagedItems.value.data.findIndex((i: ActionTargetItem) => i.id === activeItem.value?.id);
|
355
361
|
if (index === undefined || index === null) return;
|
356
362
|
|
357
|
-
|
363
|
+
const nextIndex = index + offset;
|
364
|
+
|
365
|
+
const latestNextIndex = latestCallOnly("getNextItem", async () => {
|
366
|
+
if (!pagedItems.value?.data) return -1;
|
367
|
+
|
368
|
+
// Load the previous page if the offset is before index 0
|
369
|
+
if (nextIndex < 0) {
|
370
|
+
if (pagination.value.page > 1) {
|
371
|
+
pagination.value = { ...pagination.value, page: pagination.value.page - 1 };
|
372
|
+
await waitForRef(isLoadingList, false);
|
373
|
+
return pagedItems.value.data.length - 1;
|
374
|
+
}
|
358
375
|
|
359
|
-
// Load the previous page if the offset is before index 0
|
360
|
-
if (nextIndex < 0) {
|
361
|
-
if (pagination.value.page > 1) {
|
362
|
-
pagination.value = { ...pagination.value, page: pagination.value.page - 1 };
|
363
|
-
await waitForRef(isLoadingList, false);
|
364
|
-
nextIndex = pagedItems.value.data.length - 1;
|
365
|
-
} else {
|
366
376
|
// There are no more previous pages
|
367
|
-
return;
|
377
|
+
return -1;
|
368
378
|
}
|
369
|
-
}
|
370
379
|
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
380
|
+
// Load the next page if the offset is past the last index
|
381
|
+
if (nextIndex >= pagedItems.value.data.length) {
|
382
|
+
if (pagination.value.page < (pagedItems.value?.meta?.last_page || 1)) {
|
383
|
+
pagination.value = { ...pagination.value, page: pagination.value.page + 1 };
|
384
|
+
await waitForRef(isLoadingList, false);
|
385
|
+
return 0;
|
386
|
+
}
|
387
|
+
|
378
388
|
// There are no more next pages
|
379
|
-
return;
|
389
|
+
return -1;
|
380
390
|
}
|
381
|
-
}
|
382
391
|
|
383
|
-
|
392
|
+
return nextIndex;
|
393
|
+
});
|
394
|
+
|
395
|
+
const resolvedNextIndex = await latestNextIndex();
|
396
|
+
|
397
|
+
if (resolvedNextIndex !== undefined && resolvedNextIndex >= 0) {
|
398
|
+
activeItem.value = pagedItems.value?.data[resolvedNextIndex];
|
399
|
+
}
|
384
400
|
}
|
385
401
|
|
386
402
|
/**
|
package/src/helpers/routes.ts
CHANGED
@@ -18,6 +18,10 @@ export function useActionRoutes(baseUrl: string): ListControlsRoutes {
|
|
18
18
|
const item = await request.get(`${baseUrl}/${target.id}/details`);
|
19
19
|
return storeObject(item);
|
20
20
|
},
|
21
|
+
async relation(target, relation) {
|
22
|
+
const item = await request.get(`${baseUrl}/${target.id}/relation/${relation}`);
|
23
|
+
return storeObject(item);
|
24
|
+
},
|
21
25
|
fieldOptions() {
|
22
26
|
return request.get(`${baseUrl}/field-options`);
|
23
27
|
},
|
package/src/helpers/utils.ts
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
import { AnyObject } from "src/types";
|
1
2
|
import { Ref, watch } from "vue";
|
2
3
|
|
3
4
|
export { useDebounceFn } from "@vueuse/core";
|
@@ -33,6 +34,21 @@ export function waitForRef(ref: Ref, value: any) {
|
|
33
34
|
});
|
34
35
|
}
|
35
36
|
|
37
|
+
const currentCalls: AnyObject = {};
|
38
|
+
export function latestCallOnly<T extends any[], R>(type: string, fn: (...args: T) => Promise<R>) {
|
39
|
+
if (!currentCalls[type]) {
|
40
|
+
currentCalls[type] = 0;
|
41
|
+
}
|
42
|
+
return async function (...args: T) {
|
43
|
+
const callId = ++currentCalls[type];
|
44
|
+
const result = await fn(...args);
|
45
|
+
if (callId === currentCalls[type]) {
|
46
|
+
return result;
|
47
|
+
}
|
48
|
+
return undefined;
|
49
|
+
};
|
50
|
+
}
|
51
|
+
|
36
52
|
/**
|
37
53
|
* Returns a number that is constrained to the given range.
|
38
54
|
*/
|
package/src/types/controls.d.ts
CHANGED
@@ -30,6 +30,8 @@ export interface ListControlsRoutes {
|
|
30
30
|
|
31
31
|
detailsAndStore?(target: ActionTargetItem): Promise<ActionTargetItem>;
|
32
32
|
|
33
|
+
relation?(target: ActionTargetItem, relation: string): Promise<ActionTargetItem>;
|
34
|
+
|
33
35
|
more?(pager: ListControlsPagination): Promise<ActionTargetItem[]>;
|
34
36
|
|
35
37
|
fieldOptions?(filter?: AnyObject): Promise<AnyObject>;
|