quasar-ui-danx 0.4.4 → 0.4.6
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 +5823 -5792
- package/dist/danx.es.js.map +1 -1
- package/dist/danx.umd.js +7 -11
- package/dist/danx.umd.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +2 -2
- package/src/components/ActionTable/ActionTable.vue +2 -2
- package/src/components/ActionTable/Form/Fields/SelectField.vue +7 -0
- package/src/components/ActionTable/Form/Fields/TextField.vue +10 -18
- package/src/components/ActionTable/Layouts/ActionTableLayout.vue +1 -0
- package/src/components/ActionTable/TableSummaryRow.vue +56 -52
- package/src/components/ActionTable/listControls.ts +67 -41
- package/src/helpers/app.ts +20 -0
- package/src/helpers/index.ts +1 -0
- package/src/helpers/request.ts +72 -26
- package/src/helpers/routes.ts +8 -8
- package/src/styles/quasar-reset.scss +1 -1
- package/src/types/controls.d.ts +25 -8
- package/src/types/requests.d.ts +21 -1
- package/tsconfig.json +0 -1
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "quasar-ui-danx",
|
3
|
-
"version": "0.4.
|
3
|
+
"version": "0.4.6",
|
4
4
|
"author": "Dan <dan@flytedesk.com>",
|
5
5
|
"description": "DanX Vue / Quasar component library",
|
6
6
|
"license": "MIT",
|
@@ -43,7 +43,7 @@
|
|
43
43
|
"vite-svg-loader": "^5.1.0",
|
44
44
|
"vue": "^3.4.21",
|
45
45
|
"vue-eslint-parser": "^9.4.2",
|
46
|
-
"vue-router": "^4.
|
46
|
+
"vue-router": "^4.3.2"
|
47
47
|
},
|
48
48
|
"dependencies": {
|
49
49
|
"@heroicons/vue": "v1",
|
@@ -1,7 +1,7 @@
|
|
1
1
|
<template>
|
2
2
|
<div
|
3
3
|
class="dx-action-table overflow-hidden"
|
4
|
-
:class="{'dx-no-data': !hasData}"
|
4
|
+
:class="{'dx-no-data': !hasData, 'dx-is-loading': loadingList || loadingSummary, 'dx-is-loading-list': loadingList}"
|
5
5
|
>
|
6
6
|
<ActionVnode />
|
7
7
|
<QTable
|
@@ -9,7 +9,7 @@
|
|
9
9
|
:selected="selectedRows"
|
10
10
|
:pagination="pagination"
|
11
11
|
:columns="tableColumns"
|
12
|
-
:loading="loadingList"
|
12
|
+
:loading="loadingList || loadingSummary"
|
13
13
|
:rows="pagedItems?.data || []"
|
14
14
|
:binary-state-sort="false"
|
15
15
|
selection="multiple"
|
@@ -1,5 +1,10 @@
|
|
1
1
|
<template>
|
2
2
|
<div>
|
3
|
+
<FieldLabel
|
4
|
+
v-if="label"
|
5
|
+
:label="label"
|
6
|
+
class="mb-1 block"
|
7
|
+
/>
|
3
8
|
<QSelect
|
4
9
|
ref="selectField"
|
5
10
|
v-bind="$props"
|
@@ -15,6 +20,7 @@
|
|
15
20
|
option-label="label"
|
16
21
|
option-value="value"
|
17
22
|
placeholder=""
|
23
|
+
label=""
|
18
24
|
:input-class="{'is-hidden': !isShowing, [inputClass]: true}"
|
19
25
|
class="max-w-full dx-select-field"
|
20
26
|
@filter="onFilter"
|
@@ -68,6 +74,7 @@
|
|
68
74
|
import { ChevronDownIcon as DropDownIcon } from "@heroicons/vue/outline";
|
69
75
|
import { QSelect, QSelectProps } from "quasar";
|
70
76
|
import { computed, isRef, nextTick, ref } from "vue";
|
77
|
+
import FieldLabel from "./FieldLabel";
|
71
78
|
|
72
79
|
export interface Props extends QSelectProps {
|
73
80
|
modelValue?: any;
|
@@ -1,12 +1,14 @@
|
|
1
1
|
<template>
|
2
2
|
<div>
|
3
|
-
<
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
3
|
+
<FieldLabel
|
4
|
+
:field="field"
|
5
|
+
:label="label"
|
6
|
+
:show-name="showName"
|
7
|
+
:class="labelClass"
|
8
|
+
:value="readonly ? modelValue : ''"
|
9
|
+
class="mb-1 block"
|
10
|
+
/>
|
11
|
+
<template v-if="!readonly">
|
10
12
|
<QInput
|
11
13
|
:placeholder="field?.placeholder"
|
12
14
|
outlined
|
@@ -24,16 +26,7 @@
|
|
24
26
|
:debounce="debounce"
|
25
27
|
@keydown.enter="$emit('submit')"
|
26
28
|
@update:model-value="$emit('update:model-value', $event)"
|
27
|
-
|
28
|
-
<template #label>
|
29
|
-
<FieldLabel
|
30
|
-
:field="field"
|
31
|
-
:label="label"
|
32
|
-
:show-name="showName"
|
33
|
-
:class="labelClass"
|
34
|
-
/>
|
35
|
-
</template>
|
36
|
-
</QInput>
|
29
|
+
/>
|
37
30
|
<MaxLengthCounter
|
38
31
|
:length="modelValue?.length || 0"
|
39
32
|
:max-length="field?.maxLength"
|
@@ -46,7 +39,6 @@
|
|
46
39
|
import { TextFieldProps } from "../../../../types";
|
47
40
|
import MaxLengthCounter from "../Utilities/MaxLengthCounter";
|
48
41
|
import FieldLabel from "./FieldLabel";
|
49
|
-
import LabelValueBlock from "./LabelValueBlock";
|
50
42
|
|
51
43
|
defineEmits(["update:model-value", "submit"]);
|
52
44
|
withDefaults(defineProps<TextFieldProps>(), {
|
@@ -8,6 +8,7 @@
|
|
8
8
|
:actions="actions?.filter(a => a.batch)"
|
9
9
|
:action-target="controller.selectedRows.value"
|
10
10
|
:exporter="controller.exportList"
|
11
|
+
:loading="controller.isLoadingList.value || controller.isLoadingSummary.value"
|
11
12
|
@refresh="controller.refreshAll"
|
12
13
|
>
|
13
14
|
<template #default>
|
@@ -9,24 +9,28 @@
|
|
9
9
|
:class="{'has-selection': selectedCount}"
|
10
10
|
>
|
11
11
|
<div class="flex flex-nowrap items-center">
|
12
|
-
<div
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
class="w-6 mr-3"
|
18
|
-
@click="$emit('clear')"
|
12
|
+
<div class="relative">
|
13
|
+
<QSpinner
|
14
|
+
v-if="loading"
|
15
|
+
class="absolute top-0 left-0"
|
16
|
+
size="18"
|
19
17
|
/>
|
20
|
-
|
18
|
+
<div
|
19
|
+
:class="{'opacity-0': loading}"
|
20
|
+
class="flex items-center nowrap"
|
21
|
+
>
|
22
|
+
<ClearIcon
|
23
|
+
v-if="selectedCount"
|
24
|
+
class="w-6 mr-3 cursor-pointer"
|
25
|
+
@click="$emit('clear')"
|
26
|
+
/>
|
27
|
+
|
28
|
+
{{ fNumber(selectedCount || itemCount) }}
|
29
|
+
</div>
|
21
30
|
</div>
|
22
|
-
<div
|
23
|
-
{{
|
31
|
+
<div class="ml-2">
|
32
|
+
{{ selectedCount ? selectedLabel : label }}
|
24
33
|
</div>
|
25
|
-
<QSpinner
|
26
|
-
v-if="loading"
|
27
|
-
class="ml-3"
|
28
|
-
size="18"
|
29
|
-
/>
|
30
34
|
</div>
|
31
35
|
</QTd>
|
32
36
|
<QTd
|
@@ -48,49 +52,49 @@ import { fNumber } from "../../helpers";
|
|
48
52
|
|
49
53
|
defineEmits(["clear"]);
|
50
54
|
const props = defineProps({
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
55
|
+
loading: Boolean,
|
56
|
+
label: {
|
57
|
+
type: String,
|
58
|
+
default: "Rows"
|
59
|
+
},
|
60
|
+
selectedLabel: {
|
61
|
+
type: String,
|
62
|
+
default: "Selected"
|
63
|
+
},
|
64
|
+
selectedCount: {
|
65
|
+
type: Number,
|
66
|
+
default: 0
|
67
|
+
},
|
68
|
+
itemCount: {
|
69
|
+
type: Number,
|
70
|
+
default: 0
|
71
|
+
},
|
72
|
+
summary: {
|
73
|
+
type: Object,
|
74
|
+
default: null
|
75
|
+
},
|
76
|
+
columns: {
|
77
|
+
type: Array,
|
78
|
+
required: true
|
79
|
+
},
|
80
|
+
stickyColspan: {
|
81
|
+
type: Number,
|
82
|
+
default: 2
|
83
|
+
}
|
80
84
|
});
|
81
85
|
|
82
86
|
const summaryColumns = computed(() => {
|
83
|
-
|
84
|
-
|
87
|
+
// The sticky columns are where we display the selection count and should not be included in the summary columns
|
88
|
+
return props.columns.slice(props.stickyColspan - 1);
|
85
89
|
});
|
86
90
|
|
87
91
|
function formatValue(column) {
|
88
|
-
|
89
|
-
|
92
|
+
const value = props.summary[column.name];
|
93
|
+
if (value === undefined) return "";
|
90
94
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
+
if (column.format) {
|
96
|
+
return column.format(value);
|
97
|
+
}
|
98
|
+
return value;
|
95
99
|
}
|
96
100
|
</script>
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import { computed, Ref, ref, shallowRef, watch } from "vue";
|
2
|
-
import { RouteLocationNormalizedLoaded, RouteParams, Router
|
2
|
+
import { RouteLocationNormalizedLoaded, RouteParams, Router } from "vue-router";
|
3
3
|
import { getItem, setItem, storeObject, waitForRef } from "../../helpers";
|
4
4
|
import {
|
5
5
|
ActionController,
|
@@ -7,6 +7,7 @@ import {
|
|
7
7
|
AnyObject,
|
8
8
|
FilterGroup,
|
9
9
|
ListControlsFilter,
|
10
|
+
ListControlsInitializeOptions,
|
10
11
|
ListControlsOptions,
|
11
12
|
ListControlsPagination,
|
12
13
|
PagedItems
|
@@ -15,8 +16,8 @@ import { getFilterFromUrl } from "./listHelpers";
|
|
15
16
|
|
16
17
|
export function useListControls(name: string, options: ListControlsOptions): ActionController {
|
17
18
|
let isInitialized = false;
|
18
|
-
let vueRoute: RouteLocationNormalizedLoaded | null
|
19
|
-
let vueRouter: Router | null
|
19
|
+
let vueRoute: RouteLocationNormalizedLoaded | null | undefined;
|
20
|
+
let vueRouter: Router | null | undefined;
|
20
21
|
const PAGE_SETTINGS_KEY = `dx-${name}-pager`;
|
21
22
|
const pagedItems = shallowRef<PagedItems | null>(null);
|
22
23
|
const activeFilter = ref<ListControlsFilter>({});
|
@@ -74,8 +75,12 @@ export function useListControls(name: string, options: ListControlsOptions): Act
|
|
74
75
|
async function loadList() {
|
75
76
|
if (!isInitialized) return;
|
76
77
|
isLoadingList.value = true;
|
77
|
-
|
78
|
-
|
78
|
+
try {
|
79
|
+
setPagedItems(await options.routes.list(pager.value));
|
80
|
+
isLoadingList.value = false;
|
81
|
+
} catch (e) {
|
82
|
+
// Fail silently
|
83
|
+
}
|
79
84
|
}
|
80
85
|
|
81
86
|
async function loadSummary() {
|
@@ -86,8 +91,12 @@ export function useListControls(name: string, options: ListControlsOptions): Act
|
|
86
91
|
if (selectedRows.value.length) {
|
87
92
|
summaryFilter.id = selectedRows.value.map((row) => row.id);
|
88
93
|
}
|
89
|
-
|
90
|
-
|
94
|
+
try {
|
95
|
+
summary.value = await options.routes.summary(summaryFilter);
|
96
|
+
isLoadingSummary.value = false;
|
97
|
+
} catch (e) {
|
98
|
+
// Fail silently
|
99
|
+
}
|
91
100
|
}
|
92
101
|
|
93
102
|
async function loadListAndSummary() {
|
@@ -109,8 +118,12 @@ export function useListControls(name: string, options: ListControlsOptions): Act
|
|
109
118
|
async function loadFieldOptions() {
|
110
119
|
if (!options.routes.fieldOptions || !isInitialized) return;
|
111
120
|
isLoadingFilters.value = true;
|
112
|
-
|
113
|
-
|
121
|
+
try {
|
122
|
+
fieldOptions.value = await options.routes.fieldOptions(activeFilter.value) || {};
|
123
|
+
isLoadingFilters.value = false;
|
124
|
+
} catch (e) {
|
125
|
+
// Fail silently
|
126
|
+
}
|
114
127
|
}
|
115
128
|
|
116
129
|
/**
|
@@ -196,18 +209,22 @@ export function useListControls(name: string, options: ListControlsOptions): Act
|
|
196
209
|
async function loadMore(index: number, perPage: number | undefined = undefined) {
|
197
210
|
if (!options.routes.more) return false;
|
198
211
|
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
if (newItems && newItems.length > 0) {
|
206
|
-
setPagedItems({
|
207
|
-
data: [...(pagedItems.value?.data || []), ...newItems],
|
208
|
-
meta: { total: pagedItems.value?.meta?.total || 0 }
|
212
|
+
try {
|
213
|
+
const newItems = await options.routes.more({
|
214
|
+
page: index + 1,
|
215
|
+
perPage,
|
216
|
+
filter: { ...activeFilter.value, ...globalFilter.value }
|
209
217
|
});
|
210
|
-
|
218
|
+
|
219
|
+
if (newItems && newItems.length > 0) {
|
220
|
+
setPagedItems({
|
221
|
+
data: [...(pagedItems.value?.data || []), ...newItems],
|
222
|
+
meta: { total: pagedItems.value?.meta?.total || 0 }
|
223
|
+
});
|
224
|
+
return true;
|
225
|
+
}
|
226
|
+
} catch (e) {
|
227
|
+
// Fail silently
|
211
228
|
}
|
212
229
|
|
213
230
|
return false;
|
@@ -282,14 +299,18 @@ export function useListControls(name: string, options: ListControlsOptions): Act
|
|
282
299
|
async function getActiveItemDetails() {
|
283
300
|
if (!activeItem.value || !options.routes.details) return;
|
284
301
|
|
285
|
-
|
302
|
+
try {
|
303
|
+
const result = await options.routes.details(activeItem.value);
|
286
304
|
|
287
|
-
|
288
|
-
|
289
|
-
|
305
|
+
if (!result || !result.__type || !result.id) {
|
306
|
+
return console.error("Invalid response from details route: All responses must include a __type and id field. result =", result);
|
307
|
+
}
|
290
308
|
|
291
|
-
|
292
|
-
|
309
|
+
// Reassign the active item to the store object to ensure reactivity
|
310
|
+
activeItem.value = storeObject(result);
|
311
|
+
} catch (e) {
|
312
|
+
// Fail silently
|
313
|
+
}
|
293
314
|
}
|
294
315
|
|
295
316
|
// Whenever the active item changes, fill the additional item details
|
@@ -313,12 +334,8 @@ export function useListControls(name: string, options: ListControlsOptions): Act
|
|
313
334
|
activePanel.value = panel;
|
314
335
|
|
315
336
|
// Push vue router change /:id/:panel
|
316
|
-
if (
|
317
|
-
|
318
|
-
name: Array.isArray(vueRoute.name) ? vueRoute.name[0] : vueRoute.name,
|
319
|
-
params: { id: item.id, panel },
|
320
|
-
replace: true
|
321
|
-
});
|
337
|
+
if (item?.id) {
|
338
|
+
updateRouteParams({ id: item.id, panel });
|
322
339
|
}
|
323
340
|
}
|
324
341
|
|
@@ -329,7 +346,7 @@ export function useListControls(name: string, options: ListControlsOptions): Act
|
|
329
346
|
activeItem.value = item ? storeObject(item) : item;
|
330
347
|
|
331
348
|
if (!item?.id) {
|
332
|
-
|
349
|
+
updateRouteParams({});
|
333
350
|
}
|
334
351
|
}
|
335
352
|
|
@@ -380,23 +397,20 @@ export function useListControls(name: string, options: ListControlsOptions): Act
|
|
380
397
|
}
|
381
398
|
|
382
399
|
async function exportList(filter?: ListControlsFilter) {
|
383
|
-
|
400
|
+
options.routes.export && await options.routes.export(filter);
|
384
401
|
}
|
385
402
|
|
386
403
|
// Initialize the list actions and load settings, lists, summaries, filter fields, etc.
|
387
|
-
function initialize() {
|
404
|
+
function initialize(initOptions?: ListControlsInitializeOptions) {
|
405
|
+
vueRouter = initOptions?.vueRouter;
|
406
|
+
vueRoute = initOptions?.vueRouter?.currentRoute.value;
|
388
407
|
isInitialized = true;
|
389
408
|
loadSettings();
|
390
409
|
|
391
|
-
// Setup Vue Router handling
|
392
|
-
vueRoute = useRoute();
|
393
|
-
vueRouter = useRouter();
|
394
|
-
|
395
|
-
console.log("init listControl", name, options, "vueRoute", vueRoute);
|
396
410
|
/**
|
397
411
|
* Watch the id params in the route and set the active item to the item with the given id.
|
398
412
|
*/
|
399
|
-
if (options.routes.details && vueRoute) {
|
413
|
+
if (options.routes.details && vueRoute && vueRouter) {
|
400
414
|
const { params, meta } = vueRoute;
|
401
415
|
|
402
416
|
const controlRouteName = vueRoute.name;
|
@@ -410,6 +424,18 @@ export function useListControls(name: string, options: ListControlsOptions): Act
|
|
410
424
|
}
|
411
425
|
}
|
412
426
|
|
427
|
+
/**
|
428
|
+
* Updates the URL bar and route to the given params.
|
429
|
+
*/
|
430
|
+
function updateRouteParams(params: AnyObject) {
|
431
|
+
if (vueRouter && vueRoute) {
|
432
|
+
vueRouter.push({
|
433
|
+
name: (Array.isArray(vueRoute.name) ? vueRoute.name[0] : vueRoute.name) || "home",
|
434
|
+
params
|
435
|
+
});
|
436
|
+
}
|
437
|
+
}
|
438
|
+
|
413
439
|
function setPanelFromRoute(params: RouteParams, meta: AnyObject) {
|
414
440
|
const id = Array.isArray(params?.id) ? params.id[0] : params?.id;
|
415
441
|
if (id && meta.type) {
|
@@ -0,0 +1,20 @@
|
|
1
|
+
let appRefreshed = false;
|
2
|
+
|
3
|
+
export function refreshApplication(callback: () => void) {
|
4
|
+
// Only allow refreshing the application once
|
5
|
+
if (appRefreshed) return;
|
6
|
+
appRefreshed = true;
|
7
|
+
|
8
|
+
// Create a hidden iframe
|
9
|
+
const iframe = document.createElement("iframe");
|
10
|
+
iframe.style.display = "none";
|
11
|
+
iframe.src = window.location.href;
|
12
|
+
document.body.appendChild(iframe);
|
13
|
+
|
14
|
+
// Listen for the iframe to finish loading
|
15
|
+
iframe.onload = () => {
|
16
|
+
// Remove the iframe
|
17
|
+
document.body.removeChild(iframe);
|
18
|
+
callback();
|
19
|
+
};
|
20
|
+
}
|
package/src/helpers/index.ts
CHANGED
package/src/helpers/request.ts
CHANGED
@@ -1,46 +1,75 @@
|
|
1
1
|
import { Ref } from "vue";
|
2
2
|
import { danxOptions } from "../config";
|
3
|
-
import {
|
3
|
+
import { HttpResponse, RequestApi } from "../types";
|
4
4
|
|
5
5
|
/**
|
6
6
|
* A simple request helper that wraps the fetch API
|
7
7
|
* to make GET and POST requests easier w/ JSON payloads
|
8
8
|
*/
|
9
|
-
export const request = {
|
10
|
-
|
9
|
+
export const request: RequestApi = {
|
10
|
+
abortControllers: {},
|
11
|
+
|
12
|
+
url(url) {
|
11
13
|
if (url.startsWith("http")) {
|
12
14
|
return url;
|
13
15
|
}
|
14
16
|
return (danxOptions.value.request?.baseUrl || "").replace(/\/$/, "") + "/" + url;
|
15
17
|
},
|
16
18
|
|
17
|
-
async call(url
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
19
|
+
async call(url, options) {
|
20
|
+
options = options || {};
|
21
|
+
const abortKey = options?.abortOn !== undefined ? options.abortOn : url;
|
22
|
+
const timestamp = new Date().getTime();
|
23
|
+
|
24
|
+
if (abortKey) {
|
25
|
+
const abort = new AbortController();
|
26
|
+
const previousAbort = request.abortControllers[abortKey];
|
27
|
+
// If there is already an abort controller set for this key, abort it
|
28
|
+
if (previousAbort) {
|
29
|
+
previousAbort.abort.abort("Request was aborted due to a newer request being made");
|
28
30
|
}
|
29
31
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
32
|
+
// Set the new abort controller for this key
|
33
|
+
request.abortControllers[abortKey] = { abort, timestamp };
|
34
|
+
options.signal = abort.signal;
|
35
|
+
}
|
36
|
+
|
37
|
+
const response = await fetch(request.url(url), options);
|
38
|
+
|
39
|
+
// Verify the app version of the client and server are matching
|
40
|
+
checkAppVersion(response);
|
41
|
+
|
42
|
+
// handle the case where the request was aborted too late, and we need to abort the response via timestamp check
|
43
|
+
if (abortKey) {
|
44
|
+
// If the request was aborted too late, but there was still another request that was made after the current,
|
45
|
+
// then abort the current request with an abort flag
|
46
|
+
if (timestamp < request.abortControllers[abortKey].timestamp) {
|
47
|
+
throw new Error("Request was aborted due to a newer request being made: " + timestamp + " < " + request.abortControllers[abortKey].timestamp);
|
34
48
|
}
|
35
49
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
50
|
+
// Otherwise, the current is the most recent request, so we can delete the abort controller
|
51
|
+
delete request.abortControllers[abortKey];
|
52
|
+
}
|
53
|
+
|
54
|
+
const result = await response.json();
|
55
|
+
|
56
|
+
if (response.status === 401) {
|
57
|
+
const onUnauthorized = danxOptions.value.request?.onUnauthorized;
|
58
|
+
return onUnauthorized ? onUnauthorized(response) : {
|
59
|
+
error: true,
|
60
|
+
message: "Unauthorized"
|
40
61
|
};
|
41
62
|
}
|
63
|
+
|
64
|
+
if (response.status > 400) {
|
65
|
+
if (result.exception && !result.error) {
|
66
|
+
result.error = true;
|
67
|
+
}
|
68
|
+
}
|
69
|
+
|
70
|
+
return result;
|
42
71
|
},
|
43
|
-
async get(url
|
72
|
+
async get(url, options) {
|
44
73
|
return await request.call(url, {
|
45
74
|
method: "get",
|
46
75
|
headers: {
|
@@ -52,10 +81,10 @@ export const request = {
|
|
52
81
|
});
|
53
82
|
},
|
54
83
|
|
55
|
-
async post(url
|
56
|
-
return request.call(url, {
|
84
|
+
async post(url, data, options) {
|
85
|
+
return await request.call(url, {
|
57
86
|
method: "post",
|
58
|
-
body: JSON.stringify(data),
|
87
|
+
body: data && JSON.stringify(data),
|
59
88
|
headers: {
|
60
89
|
Accept: "application/json",
|
61
90
|
"Content-Type": "application/json",
|
@@ -66,6 +95,23 @@ export const request = {
|
|
66
95
|
}
|
67
96
|
};
|
68
97
|
|
98
|
+
/**
|
99
|
+
* Checks the app version of the client and server to see if they match.
|
100
|
+
* If they do not match, the onAppVersionMismatch callback is called
|
101
|
+
*/
|
102
|
+
function checkAppVersion(response: HttpResponse) {
|
103
|
+
const requestOptions = danxOptions.value.request;
|
104
|
+
if (!requestOptions || !requestOptions.headers || !requestOptions.onAppVersionMismatch) {
|
105
|
+
return;
|
106
|
+
}
|
107
|
+
|
108
|
+
const clientAppVersion = requestOptions.headers["X-App-Version"] || "";
|
109
|
+
const serverAppVersion = response.headers.get("X-App-Version");
|
110
|
+
if (clientAppVersion && clientAppVersion !== serverAppVersion) {
|
111
|
+
requestOptions.onAppVersionMismatch(serverAppVersion);
|
112
|
+
}
|
113
|
+
}
|
114
|
+
|
69
115
|
/**
|
70
116
|
* Fetches a resource list applying the filter. If there is a selected resource,
|
71
117
|
* stores that resource from the already populated list. If that resource does not exist
|
package/src/helpers/routes.ts
CHANGED
@@ -1,28 +1,28 @@
|
|
1
|
-
import {
|
1
|
+
import { ListControlsRoutes } from "../types";
|
2
2
|
import { downloadFile } from "./downloadPdf";
|
3
3
|
import { request } from "./request";
|
4
4
|
|
5
|
-
export function useActionRoutes(baseUrl: string) {
|
5
|
+
export function useActionRoutes(baseUrl: string): ListControlsRoutes {
|
6
6
|
return {
|
7
|
-
list(pager
|
7
|
+
list(pager?) {
|
8
8
|
return request.post(`${baseUrl}/list`, pager);
|
9
9
|
},
|
10
|
-
summary(filter
|
10
|
+
summary(filter) {
|
11
11
|
return request.post(`${baseUrl}/summary`, { filter });
|
12
12
|
},
|
13
|
-
details(target
|
13
|
+
details(target) {
|
14
14
|
return request.get(`${baseUrl}/${target.id}/details`);
|
15
15
|
},
|
16
16
|
fieldOptions() {
|
17
17
|
return request.get(`${baseUrl}/field-options`);
|
18
18
|
},
|
19
|
-
applyAction(action
|
19
|
+
applyAction(action, target, data) {
|
20
20
|
return request.post(`${baseUrl}/${target ? target.id : "new"}/apply-action`, { action, data });
|
21
21
|
},
|
22
|
-
batchAction(action
|
22
|
+
batchAction(action, targets, data) {
|
23
23
|
return request.post(`${baseUrl}/batch-action`, { action, filter: { id: targets.map(r => r.id) }, data });
|
24
24
|
},
|
25
|
-
export(filter
|
25
|
+
export(filter, name) {
|
26
26
|
return downloadFile(`${baseUrl}/export`, name || "export.csv", { filter });
|
27
27
|
}
|
28
28
|
};
|