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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "quasar-ui-danx",
3
- "version": "0.4.16",
3
+ "version": "0.4.18",
4
4
  "author": "Dan <dan@flytedesk.com>",
5
5
  "description": "DanX Vue / Quasar component library",
6
6
  "license": "MIT",
@@ -1,66 +1,66 @@
1
1
  <template>
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>
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
- <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>
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 result = await options.routes.details(activeItem.value);
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
- let nextIndex = index + offset;
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
- // Load the next page if the offset is past the last index
372
- if (nextIndex >= pagedItems.value.data.length) {
373
- if (pagination.value.page < (pagedItems.value?.meta?.last_page || 1)) {
374
- pagination.value = { ...pagination.value, page: pagination.value.page + 1 };
375
- await waitForRef(isLoadingList, false);
376
- nextIndex = 0;
377
- } else {
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
- activeItem.value = pagedItems.value?.data[nextIndex];
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
  /**
@@ -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
  },
@@ -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
  */
@@ -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>;