quasar-ui-danx 0.4.28 → 0.4.30

Sign up to get free protection for your applications and to get access to all the features.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "quasar-ui-danx",
3
- "version": "0.4.28",
3
+ "version": "0.4.30",
4
4
  "author": "Dan <dan@flytedesk.com>",
5
5
  "description": "DanX Vue / Quasar component library",
6
6
  "license": "MIT",
@@ -1,30 +1,63 @@
1
1
  <template>
2
- <div
3
- contenteditable
4
- class="inline-block hover:bg-blue-200 focus:bg-blue-200 transition duration-300 outline-none"
5
- @input="onInput"
6
- >
7
- {{ text }}
2
+ <div class="inline-block relative">
3
+ <div
4
+ contenteditable
5
+ class="relative inline-block transition duration-300 outline-none outline-offset-0 border-none focus:outline-4 hover:outline-4 rounded-sm z-10"
6
+ :style="{minWidth, minHeight}"
7
+ :class="contentClass"
8
+ @input="onInput"
9
+ @focusin="hasFocus = true"
10
+ @focusout="hasFocus = false"
11
+ >
12
+ {{ text }}
13
+ </div>
14
+ <div
15
+ v-if="!text && placeholder"
16
+ ref="placeholderDiv"
17
+ class="text-gray-600 absolute-top-left whitespace-nowrap z-1"
18
+ >
19
+ {{ placeholder }}
20
+ </div>
8
21
  </div>
9
22
  </template>
10
23
 
11
- <script setup>
24
+ <script setup lang="ts">
12
25
  import { useDebounceFn } from "@vueuse/core";
13
- import { ref } from "vue";
26
+ import { computed, onMounted, ref, watch } from "vue";
14
27
 
15
28
  const emit = defineEmits(["update:model-value", "change"]);
16
- const props = defineProps({
17
- modelValue: {
18
- type: String,
19
- required: true
20
- },
21
- debounceDelay: {
22
- type: Number,
23
- default: 1000
24
- }
29
+ const props = withDefaults(defineProps<{
30
+ modelValue: string;
31
+ color?: string;
32
+ debounceDelay?: number;
33
+ placeholder?: string;
34
+ }>(), {
35
+ // NOTE: You must safe-list required colors in tailwind.config.js
36
+ // Add text-blue-900, hover:bg-blue-200, hover:outline-blue-200, focus:outline-blue-200 and focus:bg-blue-200 for the following config
37
+ color: "blue-200",
38
+ textColor: "blue-900",
39
+ debounceDelay: 1000,
40
+ placeholder: "Enter Text..."
25
41
  });
26
42
 
27
43
  const text = ref(props.modelValue);
44
+ const placeholderDiv = ref(null);
45
+ const minWidth = ref(0);
46
+ const minHeight = ref(0);
47
+ const hasFocus = ref(false);
48
+
49
+ onMounted(() => {
50
+ // Set the min-width to the width of the placeholder
51
+ if (placeholderDiv.value) {
52
+ minWidth.value = placeholderDiv.value.offsetWidth + "px";
53
+ minHeight.value = placeholderDiv.value.offsetHeight + "px";
54
+ }
55
+ });
56
+
57
+ watch(() => props.modelValue, (value) => {
58
+ if (!hasFocus.value)
59
+ text.value = value;
60
+ });
28
61
 
29
62
  const debouncedChange = useDebounceFn(() => {
30
63
  emit("update:model-value", text.value);
@@ -36,4 +69,9 @@ function onInput(e) {
36
69
  debouncedChange();
37
70
  }
38
71
 
72
+ const contentClass = computed(() => [
73
+ `hover:bg-${props.color} focus:bg-${props.color}`,
74
+ `hover:text-${props.textColor} focus:text-${props.textColor}`,
75
+ `hover:outline-${props.color} focus:outline-${props.color}`
76
+ ]);
39
77
  </script>
@@ -23,6 +23,7 @@
23
23
  :disable="disabled"
24
24
  :label-slot="!noLabel"
25
25
  :input-class="inputClass"
26
+ :rows="rows"
26
27
  :class="{'dx-input-prepend-label': prependLabel}"
27
28
  stack-label
28
29
  :type="type"
@@ -71,6 +72,7 @@ withDefaults(defineProps<TextFieldProps>(), {
71
72
  labelClass: "",
72
73
  inputClass: "",
73
74
  maxLength: null,
75
+ rows: 3,
74
76
  debounce: 0,
75
77
  placeholder: null
76
78
  });
@@ -93,25 +93,11 @@
93
93
  </div>
94
94
  </template>
95
95
  <slot />
96
- <div
97
- v-if="savedAt"
98
- :class="savingClass"
99
- class="dx-saving-indicator flex items-center flex-nowrap"
100
- >
101
- <slot
102
- v-if="saving"
103
- name="saving"
104
- >
105
- Saving...
106
- <QSpinnerPie class="ml-2" />
107
- </slot>
108
- <slot
109
- v-else
110
- name="saved"
111
- >
112
- Saved at {{ fDateTime(savedAt, { format: "M/d/yy h:mm:ssa" }) }}
113
- </slot>
114
- </div>
96
+ <SaveStateIndicator
97
+ :saving="saving"
98
+ :saved-at="savedAt"
99
+ :saving-class="savingClass"
100
+ />
115
101
  <ConfirmDialog
116
102
  v-if="variationToEdit !== false"
117
103
  title="Change variation name"
@@ -139,7 +125,7 @@
139
125
  <script setup lang="ts">
140
126
  import { ExclamationCircleIcon as MissingIcon, PencilIcon as EditIcon } from "@heroicons/vue/solid";
141
127
  import { computed, onBeforeUnmount, onMounted, ref } from "vue";
142
- import { fDateTime, FlashMessages, incrementName, replace } from "../../../helpers";
128
+ import { FlashMessages, incrementName, replace } from "../../../helpers";
143
129
  import { TrashIcon as RemoveIcon } from "../../../svg";
144
130
  import { AnyObject, FormFieldValue, RenderedFormProps } from "../../../types";
145
131
  import { ConfirmDialog, RenderVnode } from "../../Utility";
@@ -154,6 +140,7 @@ import {
154
140
  TextField,
155
141
  WysiwygField
156
142
  } from "./Fields";
143
+ import SaveStateIndicator from "./Utilities/SaveStateIndicator";
157
144
 
158
145
  const props = withDefaults(defineProps<RenderedFormProps>(), {
159
146
  values: null,
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <div
3
3
  v-if="maxLength"
4
- class="danx-input-chars mt-1"
4
+ class="danx-input-chars"
5
5
  :class="{'danx-input-chars--error': length > maxLength}"
6
6
  >
7
7
  {{ fNumber(length) }} / {{ fNumber(maxLength) }} characters
@@ -0,0 +1,37 @@
1
+ <template>
2
+ <div
3
+ :class="savingClass"
4
+ class="dx-saving-indicator flex items-center flex-nowrap"
5
+ >
6
+ <slot
7
+ v-if="saving"
8
+ name="saving"
9
+ >
10
+ Saving...
11
+ <QSpinnerPie class="ml-2" />
12
+ </slot>
13
+ <slot
14
+ v-else
15
+ name="saved"
16
+ >
17
+ <template v-if="savedAt">
18
+ Saved at {{ fDateTime(savedAt, { format: "M/d/yy h:mm:ssa" }) }}
19
+ </template>
20
+ <template v-else>
21
+ Not saved
22
+ </template>
23
+ </slot>
24
+ </div>
25
+ </template>
26
+ <script setup lang="ts">
27
+ import { fDateTime } from "../../../../helpers";
28
+
29
+ withDefaults(defineProps<{
30
+ saving?: boolean;
31
+ savedAt?: string;
32
+ savingClass?: string;
33
+ }>(), {
34
+ savingClass: "text-sm text-slate-500",
35
+ savedAt: ""
36
+ });
37
+ </script>
@@ -1 +1,2 @@
1
1
  export { default as MaxLengthCounter } from "./MaxLengthCounter.vue";
2
+ export { default as SaveStateIndicator } from "./SaveStateIndicator.vue";
@@ -25,7 +25,7 @@
25
25
  v-if="activeFilter"
26
26
  :name="controller.name"
27
27
  :show-filters="showFilters"
28
- :filters="controller.filters.value"
28
+ :filters="controller.filters?.value"
29
29
  :active-filter="activeFilter"
30
30
  class="dx-action-table-filters"
31
31
  @update:active-filter="controller.setActiveFilter"
@@ -73,7 +73,7 @@
73
73
  </template>
74
74
  <script setup lang="ts">
75
75
  import { computed } from "vue";
76
- import { ActionController, ListController } from "../../../types";
76
+ import { DanxController } from "../../../types";
77
77
  import { PanelsDrawer } from "../../PanelsDrawer";
78
78
  import { PreviousNextControls } from "../../Utility";
79
79
  import ActionTable from "../ActionTable.vue";
@@ -81,7 +81,7 @@ import { CollapsableFiltersSidebar } from "../Filters";
81
81
  import { ActionToolbar } from "../Toolbars";
82
82
 
83
83
  export interface ActionTableLayoutProps {
84
- controller: ListController & ActionController;
84
+ controller: DanxController;
85
85
  exporter?: () => Promise<void>;
86
86
  hideToolbar?: boolean;
87
87
  panelTitleField?: string;
@@ -1,4 +1,4 @@
1
- import { computed, Ref, ref, shallowRef, watch } from "vue";
1
+ import { computed, ref, shallowRef, watch } from "vue";
2
2
  import { RouteParams, Router } from "vue-router";
3
3
  import { danxOptions } from "../../config";
4
4
  import { getItem, latestCallOnly, setItem, storeObject, waitForRef } from "../../helpers";
@@ -10,11 +10,12 @@ import {
10
10
  ListControlsFilter,
11
11
  ListControlsOptions,
12
12
  ListControlsPagination,
13
- PagedItems
13
+ PagedItems,
14
+ Ref
14
15
  } from "../../types";
15
16
  import { getFilterFromUrl } from "./listHelpers";
16
17
 
17
- export function useListControls(name: string, options: ListControlsOptions): ListController {
18
+ export function useControls(name: string, options: ListControlsOptions): ListController {
18
19
  let isInitialized = false;
19
20
  const PAGE_SETTINGS_KEY = `dx-${name}-pager`;
20
21
  const pagedItems = shallowRef<PagedItems | null>(null);
@@ -72,12 +73,13 @@ export function useListControls(name: string, options: ListControlsOptions): Lis
72
73
 
73
74
  async function loadList() {
74
75
  if (!isInitialized) return;
75
- isLoadingList.value = true;
76
+ // isLoadingList.value = true;
76
77
  try {
77
78
  setPagedItems(await options.routes.list(pager.value));
78
- isLoadingList.value = false;
79
79
  } catch (e) {
80
80
  // Fail silently
81
+ } finally {
82
+ isLoadingList.value = false;
81
83
  }
82
84
  }
83
85
 
@@ -91,9 +93,10 @@ export function useListControls(name: string, options: ListControlsOptions): Lis
91
93
  }
92
94
  try {
93
95
  summary.value = await options.routes.summary(summaryFilter);
94
- isLoadingSummary.value = false;
95
96
  } catch (e) {
96
97
  // Fail silently
98
+ } finally {
99
+ isLoadingSummary.value = false;
97
100
  }
98
101
  }
99
102
 
@@ -116,9 +119,10 @@ export function useListControls(name: string, options: ListControlsOptions): Lis
116
119
  isLoadingFilters.value = true;
117
120
  try {
118
121
  fieldOptions.value = await options.routes.fieldOptions(activeFilter.value) || {};
119
- isLoadingFilters.value = false;
120
122
  } catch (e) {
121
123
  // Fail silently
124
+ } finally {
125
+ isLoadingFilters.value = false;
122
126
  }
123
127
  }
124
128
 
@@ -1,8 +1,8 @@
1
1
  export * from "./Columns";
2
+ export * from "./controls";
2
3
  export * from "./Filters";
3
4
  export * from "./Form";
4
5
  export * from "./Layouts";
5
- export * from "./listControls";
6
6
  export * from "./listHelpers";
7
7
  export * from "./tableColumns";
8
8
  export * from "./Toolbars";
@@ -1,13 +1,14 @@
1
1
  <template>
2
2
  <div
3
- class="cursor-move"
3
+ :class="{'cursor-move': !showHandle}"
4
4
  draggable="true"
5
- @dragstart="dragAndDrop.dragStart"
5
+ @dragstart.stop="dragAndDrop.dragStart"
6
6
  @dragend="dragAndDrop.dragEnd"
7
7
  >
8
- <div class="flex items-center">
8
+ <div :class="contentClass">
9
9
  <div
10
10
  v-if="showHandle"
11
+ class="cursor-move"
11
12
  :class="handleClass"
12
13
  >
13
14
  <SvgImg
@@ -33,17 +34,20 @@ const props = withDefaults(defineProps<{
33
34
  dropZone: string | (() => string);
34
35
  direction?: "vertical" | "horizontal";
35
36
  showHandle?: boolean;
37
+ changeDropZone?: boolean;
38
+ contentClass?: string | object;
36
39
  handleClass?: string | object;
37
40
  listItems?: any[];
38
41
  }>(), {
39
42
  direction: "vertical",
40
43
  handleClass: "",
44
+ contentClass: "flex flex-nowrap items-center",
41
45
  listItems: () => []
42
46
  });
43
47
 
44
48
  const dragAndDrop = new ListDragAndDrop()
45
49
  .setDropZone(props.dropZone)
46
- .setOptions({ showPlaceholder: true, direction: props.direction })
50
+ .setOptions({ showPlaceholder: true, allowDropZoneChange: props.changeDropZone, direction: props.direction })
47
51
  .onStart(() => dragging.value = true)
48
52
  .onEnd(() => dragging.value = false)
49
53
  .onDropZoneChange((target, dropZone, newPosition, oldPosition, data) => {
@@ -6,6 +6,7 @@ export class DragAndDrop {
6
6
  direction?: string,
7
7
  hideDragImage?: boolean,
8
8
  showPlaceholder?: boolean,
9
+ allowDropZoneChange?: boolean,
9
10
  } = { direction: "vertical", hideDragImage: false };
10
11
 
11
12
  // State
@@ -138,7 +139,7 @@ export class DragAndDrop {
138
139
  */
139
140
  dragEnd(e) {
140
141
  this.currentDropZone = null;
141
- this.abortController.abort();
142
+ this.abortController?.abort();
142
143
  this.draggableData = null;
143
144
  this.onEndCb && this.onEndCb(e);
144
145
  }
@@ -19,6 +19,7 @@ export class ListDragAndDrop extends DragAndDrop {
19
19
  constructor(options = {}) {
20
20
  super({
21
21
  showPlaceholder: true,
22
+ allowDropZoneChange: true,
22
23
  ...options
23
24
  });
24
25
  }
@@ -156,7 +157,7 @@ export class ListDragAndDrop extends DragAndDrop {
156
157
  */
157
158
  getChildren() {
158
159
  return [...(this.currentDropZone?.children || [])].filter(
159
- (c) => c.className.match(/drag-placeholder/) === null
160
+ (c) => c.className.match(/dx-drag-placeholder/) === null
160
161
  );
161
162
  }
162
163
 
@@ -167,6 +168,13 @@ export class ListDragAndDrop extends DragAndDrop {
167
168
  return target.closest(`[data-drop-zone]`);
168
169
  }
169
170
 
171
+ /**
172
+ * Check if the current drop zone is the same as the initial drop zone
173
+ */
174
+ isSameDropZone() {
175
+ return this.currentDropZone === this.initialDropZone;
176
+ }
177
+
170
178
  /**
171
179
  * Find the element at the current cursor position in the given drop zone
172
180
  * @param point
@@ -236,19 +244,24 @@ export class ListDragAndDrop extends DragAndDrop {
236
244
  * Render a placeholder element at the given position (in between the elements)
237
245
  */
238
246
  renderPlaceholder() {
247
+ // If we're not allowed to change drop zones and we're not in the same drop zone, don't render the placeholder
248
+ if (!this.options.allowDropZoneChange && !this.isSameDropZone()) {
249
+ return;
250
+ }
251
+
239
252
  if (!this.placeholder) {
240
253
  this.placeholder = document.createElement("div");
241
- this.placeholder.classList.add("drag-placeholder");
254
+ this.placeholder.classList.add("dx-drag-placeholder");
242
255
  }
243
256
 
244
257
  // Make sure the placeholder is oriented correctly
245
258
  if (this.isVertical()) {
246
- this.placeholder.classList.add("direction-vertical");
247
- this.placeholder.classList.remove("direction-horizontal");
259
+ this.placeholder.classList.add("dx-direction-vertical");
260
+ this.placeholder.classList.remove("dx-direction-horizontal");
248
261
  this.placeholder.style.height = undefined;
249
262
  } else {
250
- this.placeholder.classList.add("direction-horizontal");
251
- this.placeholder.classList.remove("direction-vertical");
263
+ this.placeholder.classList.add("dx-direction-horizontal");
264
+ this.placeholder.classList.remove("dx-direction-vertical");
252
265
  this.placeholder.style.height =
253
266
  this.currentDropZone.getBoundingClientRect().height + "px";
254
267
  }
@@ -243,11 +243,11 @@ export class FileUpload {
243
243
  /**
244
244
  * Mark the presigned upload as completed and return the file resource from the platform server
245
245
  */
246
- async completePresignedUpload(fileUpload: XHRFileUpload) {
246
+ async completePresignedUpload(xhrFile: XHRFileUpload) {
247
247
  // Show 95% as the last 5% will be to complete the presigned upload
248
- this.fireProgressCallback(fileUpload, .95);
248
+ this.fireProgressCallback(xhrFile, .95);
249
249
 
250
- if (!fileUpload.file.resource_id) {
250
+ if (!xhrFile.file.resource_id) {
251
251
  throw new Error("File resource ID is required to complete presigned upload");
252
252
  }
253
253
 
@@ -256,7 +256,7 @@ export class FileUpload {
256
256
  }
257
257
 
258
258
  // Let the platform know the presigned upload is complete
259
- return await this.options.completePresignedUpload(fileUpload.file.resource_id);
259
+ return await this.options.completePresignedUpload(xhrFile.file.resource_id, xhrFile);
260
260
  }
261
261
 
262
262
  /**
@@ -1,53 +1,66 @@
1
1
  import ExifReader from "exifreader";
2
+ import { UploadedFile } from "src/types";
2
3
  import { useCompatibility } from "./compatibility";
3
4
  import { FlashMessages } from "./FlashMessages";
4
5
 
5
- export async function resolveFileLocation(file, waitMessage = null) {
6
- if (file.location) {
7
- return file.location;
8
- }
6
+ export async function resolveFileLocation(file: UploadedFile, waitMessage: string | null = null) {
7
+ if (file.location) {
8
+ return file.location;
9
+ }
9
10
 
10
- try {
11
- const tags = await ExifReader.load(file.blobUrl || file.url, {
12
- expanded: true
13
- });
14
- if (tags.gps) {
15
- return {
16
- latitude: tags.gps.Latitude,
17
- longitude: tags.gps.Longitude
18
- };
19
- }
11
+ try {
12
+ const tags = await ExifReader.load(file.blobUrl || file.url || "", {
13
+ expanded: true
14
+ });
15
+ if (tags.gps) {
16
+ return {
17
+ latitude: tags.gps.Latitude,
18
+ longitude: tags.gps.Longitude
19
+ };
20
+ }
21
+ } catch (error) {
22
+ console.error("Failed to load EXIF data from file:", error);
23
+ }
20
24
 
21
- const { waitForLocation, location } = useCompatibility();
25
+ try {
26
+ const { waitForLocation, location } = useCompatibility();
22
27
 
23
- // Show a waiting for location message if we have not returned within 1 second
24
- if (waitMessage) {
25
- setTimeout(() => {
26
- if (!location.value && waitMessage) {
27
- FlashMessages.warning(waitMessage);
28
- }
29
- }, 1000);
30
- }
28
+ // Show a waiting for location message if we have not returned within 1 second
29
+ if (waitMessage) {
30
+ setTimeout(() => {
31
+ if (!location.value && waitMessage) {
32
+ FlashMessages.warning(waitMessage);
33
+ }
34
+ }, 1000);
35
+ }
31
36
 
32
- // Wait for the browser to return the location (https only as http will not return a location)
33
- if (window.location.protocol === "https:") {
34
- await waitForLocation();
35
- }
36
- // Ignore the wait message if we already returned
37
- waitMessage = false;
38
- if (!location.value) {
39
- return null;
40
- }
37
+ // Wait for the browser to return the location (https only as http will not return a location)
38
+ if (window.location.protocol === "https:") {
39
+ await waitForLocation();
40
+ } else if (window.location.href.match("localhost")) {
41
+ // XXX: Special case for local development so we can test without https
42
+ return {
43
+ latitude: 37.7749,
44
+ longitude: -122.4194,
45
+ accuracy: 1,
46
+ altitude: 0,
47
+ altitudeAccuracy: 0
48
+ };
49
+ }
41
50
 
42
- return {
43
- latitude: location.value.latitude,
44
- longitude: location.value.longitude,
45
- accuracy: location.value.accuracy,
46
- altitude: location.value.altitude,
47
- altitudeAccuracy: location.value.altitudeAccuracy
48
- };
49
- } catch (error) {
50
- console.error(error);
51
- return null;
52
- }
51
+ if (!location.value) {
52
+ return null;
53
+ }
54
+
55
+ return {
56
+ latitude: location.value.latitude,
57
+ longitude: location.value.longitude,
58
+ accuracy: location.value.accuracy,
59
+ altitude: location.value.altitude,
60
+ altitudeAccuracy: location.value.altitudeAccuracy
61
+ };
62
+ } catch (error) {
63
+ console.error(error);
64
+ return null;
65
+ }
53
66
  }
@@ -1,25 +1,27 @@
1
- import { ListController, ListControlsRoutes } from "src/types/controls";
1
+ import { FilterGroup, ListController, ListControlsRoutes } from "src/types/controls";
2
+ import { FormField } from "src/types/forms";
3
+ import { TableColumn } from "src/types/tables";
2
4
  import { VNode } from "vue";
3
- import { AnyObject, TypedObject } from "./shared";
4
-
5
- export interface ActionPanel {
6
- name: string | number;
7
- label: string;
8
- category?: string;
9
- class?: string | object;
10
- enabled?: boolean | ((target: ActionTargetItem) => boolean);
11
- tabVnode?: (target: ActionTargetItem | null | undefined, activePanel: string | number) => VNode | any;
12
- vnode: (target: ActionTargetItem | null | undefined) => VNode | any;
13
- }
5
+ import { AnyObject, ComputedRef, TypedObject } from "./shared";
14
6
 
15
7
  export interface ActionTargetItem extends TypedObject {
16
8
  isSaving?: boolean;
17
9
  updated_at?: string;
18
10
  }
19
11
 
20
- export type ActionTarget = ActionTargetItem[] | ActionTargetItem | null;
12
+ export type ActionTarget<T = ActionTargetItem> = T[] | T | null;
21
13
 
22
- export interface ActionOptions {
14
+ export interface ActionPanel<T = ActionTargetItem> {
15
+ name: string | number;
16
+ label: string;
17
+ category?: string;
18
+ class?: string | object;
19
+ enabled?: boolean | ((target: T) => boolean);
20
+ tabVnode?: (target: T | null | undefined, activePanel: string | number) => VNode | any;
21
+ vnode: (target: T | null | undefined) => VNode | any;
22
+ }
23
+
24
+ export interface ActionOptions<T = ActionTargetItem> {
23
25
  name: string;
24
26
  alias?: string;
25
27
  label?: string;
@@ -30,17 +32,17 @@ export interface ActionOptions {
30
32
  category?: string;
31
33
  class?: string;
32
34
  debounce?: number;
33
- optimistic?: boolean | ((action: ActionOptions, target: ActionTargetItem | null, input: any) => void);
34
- vnode?: ((target: ActionTarget) => VNode) | any;
35
- enabled?: (target: object) => boolean;
36
- batchEnabled?: (targets: object[]) => boolean;
37
- onAction?: (action: string | ResourceAction | ActionOptions, target: ActionTargetItem | null, input?: AnyObject | any) => Promise<AnyObject | any> | void;
38
- onBatchAction?: (action: string | ResourceAction | ActionOptions, targets: ActionTargetItem[], input: any) => Promise<AnyObject | any> | void;
39
- onStart?: (action: ActionOptions | null, targets: ActionTarget, input: any) => boolean;
40
- onSuccess?: (result: any, targets: ActionTarget, input: any) => any;
41
- onBatchSuccess?: (result: any, targets: ActionTargetItem[], input: any) => any;
42
- onError?: (result: any, targets: ActionTarget, input: any) => any;
43
- onFinish?: (result: any, targets: ActionTarget, input: any) => any;
35
+ optimistic?: boolean | ((action: ActionOptions<T>, target: T | null, input: any) => void);
36
+ vnode?: (target: ActionTarget<T>, data: any) => VNode | any;
37
+ enabled?: (target: ActionTarget<T>) => boolean;
38
+ batchEnabled?: (targets: T[]) => boolean;
39
+ onAction?: (action: string | ResourceAction<T> | ActionOptions<T>, target: T | null, input?: AnyObject | any) => Promise<AnyObject | any> | void;
40
+ onBatchAction?: (action: string | ResourceAction<T> | ActionOptions<T>, targets: T[], input: any) => Promise<AnyObject | any> | void;
41
+ onStart?: (action: ActionOptions<T> | null, targets: ActionTarget<T>, input: any) => boolean;
42
+ onSuccess?: (result: any, targets: ActionTarget<T>, input: any) => any;
43
+ onBatchSuccess?: (result: any, targets: T[], input: any) => any;
44
+ onError?: (result: any, targets: ActionTarget<T>, input: any) => any;
45
+ onFinish?: (result: any, targets: ActionTarget<T>, input: any) => any;
44
46
  }
45
47
 
46
48
  export interface ActionGlobalOptions extends Partial<ActionOptions> {
@@ -48,8 +50,24 @@ export interface ActionGlobalOptions extends Partial<ActionOptions> {
48
50
  controls?: ListController;
49
51
  }
50
52
 
51
- export interface ResourceAction extends ActionOptions {
53
+ export interface ResourceAction<T = ActionTargetItem> extends ActionOptions<T> {
52
54
  isApplying: boolean;
53
- trigger: (target?: ActionTarget, input?: any) => Promise<any>;
55
+ trigger: (target?: ActionTarget<T>, input?: any) => Promise<any>;
54
56
  __type: string;
55
57
  }
58
+
59
+ export interface ActionController<T = ActionTargetItem> {
60
+ // Actions
61
+ action?: (actionName: string, target?: T | null, input?: any) => Promise<any | void>;
62
+ getAction?: (actionName: string, actionOptions?: Partial<ActionOptions>) => ResourceAction;
63
+ getActions?: (names?: string[]) => ResourceAction[];
64
+ extendAction?: (actionName: string, extendedId: string | number, actionOptions: Partial<ActionOptions>) => ResourceAction;
65
+ modifyAction?: (actionName: string, actionOptions: Partial<ActionOptions>) => ResourceAction;
66
+ batchActions?: ResourceAction[];
67
+ menuActions?: ResourceAction[];
68
+ columns?: TableColumn[];
69
+ filters?: ComputedRef<FilterGroup[]>;
70
+ fields?: FormField[];
71
+ panels?: ActionPanel[];
72
+ routes?: ListControlsRoutes;
73
+ }