quasar-ui-danx 0.4.36 → 0.4.38

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,20 +1,25 @@
1
- import { DragAndDrop } from "./dragAndDrop";
1
+ import { DragAndDrop, DraggableData } from "./dragAndDrop";
2
+
3
+ export type DragPositionChangeCallback =
4
+ ((position: number, initialPosition: number, data: DraggableData) => void)
5
+ | null;
6
+ export type DropZoneChangeCallback =
7
+ ((e: DragEvent, dropZone: HTMLElement, position: number, initialPosition: number, data: DraggableData) => void)
8
+ | null;
2
9
 
3
10
  /**
4
11
  * ListDragAndDrop supports dragging elements in a list to new positions in the same list.
5
12
  * A placeholder is rendered to show the position the list item will be dropped.
6
- *
7
- * @class
8
13
  */
9
14
  export class ListDragAndDrop extends DragAndDrop {
10
15
  listPosition = 0;
11
16
  cursorPosition = 0;
12
17
  initialPosition = 0;
13
18
  initialDropZone: HTMLElement | null = null;
14
- onPositionChangeCb = null;
15
- onDragPositionChangeCb = null;
16
- onDropZoneChangeCb = null;
17
- placeholder = null;
19
+ onPositionChangeCb: DragPositionChangeCallback = null;
20
+ onDragPositionChangeCb: DragPositionChangeCallback = null;
21
+ onDropZoneChangeCb: DropZoneChangeCallback = null;
22
+ placeholder: HTMLElement | null = null;
18
23
 
19
24
  constructor(options = {}) {
20
25
  super({
@@ -27,7 +32,7 @@ export class ListDragAndDrop extends DragAndDrop {
27
32
  /**
28
33
  * Callback that fires after dragging has ended and the list position has changed from the original
29
34
  */
30
- onPositionChange(cb): ListDragAndDrop {
35
+ onPositionChange(cb: DragPositionChangeCallback): ListDragAndDrop {
31
36
  this.onPositionChangeCb = cb;
32
37
  return this;
33
38
  }
@@ -35,31 +40,29 @@ export class ListDragAndDrop extends DragAndDrop {
35
40
  /**
36
41
  * Callback that fires when the drop zone has changed
37
42
  */
38
- onDropZoneChange(cb): ListDragAndDrop {
43
+ onDropZoneChange(cb: DropZoneChangeCallback): ListDragAndDrop {
39
44
  this.onDropZoneChangeCb = cb;
40
45
  return this;
41
46
  }
42
47
 
43
48
  /**
44
49
  * Callback that fires while dragging the element when the cursor's position has changed in the list
45
- * @param cb
46
- * @returns {ListDragAndDrop}
47
50
  */
48
- onDragPositionChange(cb) {
51
+ onDragPositionChange(cb: DragPositionChangeCallback) {
49
52
  this.onDragPositionChangeCb = cb;
50
53
  return this;
51
54
  }
52
55
 
53
56
  /**
54
57
  * Start listening for drag events and prepare an element for drag/drop
55
- * @param e
56
- * @param data
57
58
  */
58
- dragStart(e, data) {
59
+ dragStart(e: DragEvent, data: DraggableData) {
60
+ if (this.options.disabled) return;
61
+
59
62
  super.dragStart(e, data);
60
63
 
61
64
  if (this.currentDropZone) {
62
- this.listPosition = this.getListPosition(e.target);
65
+ this.listPosition = this.getListPosition(e.target as HTMLElement);
63
66
  this.initialPosition = this.listPosition;
64
67
  this.initialDropZone = this.currentDropZone;
65
68
  this.updateScrollPosition();
@@ -69,7 +72,9 @@ export class ListDragAndDrop extends DragAndDrop {
69
72
  /**
70
73
  * When dragging has ended, check for list position changes and fire the onPositionChange callback if it has
71
74
  */
72
- dragEnd(e) {
75
+ dragEnd(e: DragEvent) {
76
+ if (this.options.disabled) return;
77
+
73
78
  const draggableData = this.draggableData;
74
79
  this.placeholder?.remove();
75
80
  const newDropZone = this.currentDropZone;
@@ -87,16 +92,15 @@ export class ListDragAndDrop extends DragAndDrop {
87
92
 
88
93
  /**
89
94
  * The dragging element is moving
90
- * @param e
91
95
  */
92
- dragOver(e) {
96
+ dragOver(e: DragEvent) {
97
+ if (this.options.disabled) return;
93
98
  super.dragOver(e);
94
99
  this.updateListPosition(e);
95
100
  }
96
101
 
97
102
  /**
98
103
  * Notify if the targeted position of the cursor is different from the current position
99
- * @param e
100
104
  */
101
105
  updateListPosition(e: MouseEvent) {
102
106
  const point = {
@@ -129,17 +133,15 @@ export class ListDragAndDrop extends DragAndDrop {
129
133
  // The position has changed, trigger the callback
130
134
  if (this.listPosition !== prevPosition) {
131
135
  this.onDragPositionChangeCb &&
132
- this.onDragPositionChangeCb(this.listPosition, this.draggableData);
136
+ this.onDragPositionChangeCb(this.listPosition, this.initialPosition, this.draggableData);
133
137
  }
134
138
  }
135
139
  }
136
140
 
137
141
  /**
138
142
  * Find the numeric position of the element in the children of the list
139
- * @returns {Number|null}
140
- * @param item
141
143
  */
142
- getListPosition(item) {
144
+ getListPosition(item: HTMLElement): number {
143
145
  let index = 0;
144
146
  for (const child of this.getChildren()) {
145
147
  if (child === item) {
@@ -148,14 +150,14 @@ export class ListDragAndDrop extends DragAndDrop {
148
150
  index++;
149
151
  }
150
152
 
151
- return null;
153
+ return 0;
152
154
  }
153
155
 
154
156
  /**
155
157
  * Get all the children of the current drop zone, excluding the placeholder
156
- * @returns {*}
157
158
  */
158
159
  getChildren() {
160
+ // @ts-expect-error HTMLCollection is iterable
159
161
  return [...(this.currentDropZone?.children || [])].filter(
160
162
  (c) => c.className.match(/dx-drag-placeholder/) === null
161
163
  );
@@ -177,10 +179,8 @@ export class ListDragAndDrop extends DragAndDrop {
177
179
 
178
180
  /**
179
181
  * Find the element at the current cursor position in the given drop zone
180
- * @param point
181
- * @returns {null}
182
182
  */
183
- getListPositionOfPoint(point) {
183
+ getListPositionOfPoint(point: { x: number, y: number }) {
184
184
  let index = 0;
185
185
  const children = this.getChildren();
186
186
 
@@ -205,45 +205,47 @@ export class ListDragAndDrop extends DragAndDrop {
205
205
  * Updates the scroll position while dragging an element so a user can navigate a longer list while dragging
206
206
  */
207
207
  updateScrollPosition() {
208
- if (this.currentDropZone) {
209
- const rect = this.currentDropZone.getBoundingClientRect();
210
- const threshold = 100;
211
- let velocity = 0;
212
- const velocityFn = (x) => x * 5;
213
- const cursorPos = this.isVertical() ? this.cursorY : this.cursorX;
214
- const rectStart = this.isVertical() ? rect.top : rect.left;
215
- const rectEnd = this.isVertical() ? rect.bottom : rect.right;
216
- const beforeDiff = rectStart + threshold - cursorPos;
217
- const afterDiff = cursorPos - (rectEnd - threshold);
218
-
219
- if (beforeDiff > 0) {
220
- velocity = -velocityFn(beforeDiff);
221
- } else if (afterDiff > 0) {
222
- velocity = velocityFn(afterDiff);
223
- }
208
+ if (!this.currentDropZone) return;
224
209
 
225
- if (velocity) {
226
- if (this.isVertical()) {
227
- this.currentDropZone.scrollTo({
228
- top: this.currentDropZone.scrollTop + velocity,
229
- behavior: "smooth"
230
- });
231
- } else {
232
- this.currentDropZone.scrollTo({
233
- left: this.currentDropZone.scrollLeft + velocity,
234
- behavior: "smooth"
235
- });
236
- }
237
- }
210
+ const rect = this.currentDropZone.getBoundingClientRect();
211
+ const threshold = 100;
212
+ let velocity = 0;
213
+ const velocityFn = (x: number) => x * 5;
214
+ const cursorPos = this.isVertical() ? this.cursorY : this.cursorX;
215
+ const rectStart = this.isVertical() ? rect.top : rect.left;
216
+ const rectEnd = this.isVertical() ? rect.bottom : rect.right;
217
+ const beforeDiff = rectStart + threshold - cursorPos;
218
+ const afterDiff = cursorPos - (rectEnd - threshold);
238
219
 
239
- setTimeout(() => this.updateScrollPosition(), 500);
220
+ if (beforeDiff > 0) {
221
+ velocity = -velocityFn(beforeDiff);
222
+ } else if (afterDiff > 0) {
223
+ velocity = velocityFn(afterDiff);
240
224
  }
225
+
226
+ if (velocity) {
227
+ if (this.isVertical()) {
228
+ this.currentDropZone.scrollTo({
229
+ top: this.currentDropZone.scrollTop + velocity,
230
+ behavior: "smooth"
231
+ });
232
+ } else {
233
+ this.currentDropZone.scrollTo({
234
+ left: this.currentDropZone.scrollLeft + velocity,
235
+ behavior: "smooth"
236
+ });
237
+ }
238
+ }
239
+
240
+ setTimeout(() => this.updateScrollPosition(), 500);
241
241
  }
242
242
 
243
243
  /**
244
244
  * Render a placeholder element at the given position (in between the elements)
245
245
  */
246
246
  renderPlaceholder() {
247
+ if (!this.currentDropZone) return;
248
+
247
249
  // If we're not allowed to change drop zones and we're not in the same drop zone, don't render the placeholder
248
250
  if (!this.options.allowDropZoneChange && !this.isSameDropZone()) {
249
251
  return;
@@ -258,7 +260,7 @@ export class ListDragAndDrop extends DragAndDrop {
258
260
  if (this.isVertical()) {
259
261
  this.placeholder.classList.add("dx-direction-vertical");
260
262
  this.placeholder.classList.remove("dx-direction-horizontal");
261
- this.placeholder.style.height = undefined;
263
+ this.placeholder.style.height = "";
262
264
  } else {
263
265
  this.placeholder.classList.add("dx-direction-horizontal");
264
266
  this.placeholder.classList.remove("dx-direction-vertical");
@@ -0,0 +1,163 @@
1
+ <template>
2
+ <QBtn
3
+ :loading="isSaving"
4
+ class="shadow-none"
5
+ :class="colorClass"
6
+ @click="onAction"
7
+ >
8
+ <div class="flex items-center flex-nowrap">
9
+ <component
10
+ :is="icon || typeOptions.icon"
11
+ class="transition-all"
12
+ :class="iconClass + ' ' + typeOptions.iconClass"
13
+ />
14
+ <slot>
15
+ <div
16
+ v-if="label"
17
+ class="ml-2"
18
+ >
19
+ {{ label }}
20
+ </div>
21
+ </slot>
22
+ </div>
23
+ <QTooltip
24
+ v-if="tooltip"
25
+ class="whitespace-nowrap"
26
+ >
27
+ {{ tooltip }}
28
+ </QTooltip>
29
+ </QBtn>
30
+ </template>
31
+ <script setup lang="ts">
32
+ import {
33
+ FaSolidArrowsRotate as RefreshIcon,
34
+ FaSolidPause as PauseIcon,
35
+ FaSolidPencil as EditIcon,
36
+ FaSolidPlay as PlayIcon,
37
+ FaSolidPlus as CreateIcon,
38
+ FaSolidStop as StopIcon,
39
+ FaSolidTrash as TrashIcon
40
+ } from "danx-icon";
41
+ import { computed } from "vue";
42
+ import { ActionTarget, ResourceAction } from "../../../types";
43
+
44
+ export interface ActionButtonProps {
45
+ type?: "trash" | "trash-red" | "create" | "edit" | "play" | "stop" | "pause" | "refresh";
46
+ color?: "red" | "blue" | "sky" | "green" | "green-invert" | "lime" | "white" | "gray";
47
+ icon?: object | string;
48
+ iconClass?: string;
49
+ label?: string;
50
+ tooltip?: string;
51
+ saving?: boolean;
52
+ action?: ResourceAction;
53
+ target?: ActionTarget;
54
+ input?: object;
55
+ }
56
+
57
+ const emit = defineEmits(["success", "error", "always"]);
58
+ const props = withDefaults(defineProps<ActionButtonProps>(), {
59
+ type: null,
60
+ color: null,
61
+ icon: null,
62
+ iconClass: "",
63
+ label: "",
64
+ tooltip: "",
65
+ action: null,
66
+ target: null,
67
+ input: null
68
+ });
69
+
70
+ const colorClass = computed(() => {
71
+ switch (props.color) {
72
+ case "red":
73
+ return "text-red-900 bg-red-300 hover:bg-red-400";
74
+ case "lime":
75
+ return "text-lime-900 bg-lime-300 hover:bg-lime-400";
76
+ case "green":
77
+ return "text-green-900 bg-green-300 hover:bg-green-400";
78
+ case "green-invert":
79
+ return "text-lime-800 bg-green-200 hover:bg-lime-800 hover:text-green-200";
80
+ case "blue":
81
+ return "text-blue-900 bg-blue-300 hover:bg-blue-400";
82
+ case "sky":
83
+ return "text-sky-900 bg-sky-300 hover:bg-sky-400";
84
+ case "white":
85
+ return "text-white bg-gray-800 hover:bg-gray-200";
86
+ case "gray":
87
+ return "text-slate-200 bg-slate-800 hover:bg-slate-900";
88
+ default:
89
+ return "text-slate-200 hover:bg-slate-800";
90
+ }
91
+ });
92
+ const typeOptions = computed(() => {
93
+ switch (props.type) {
94
+ case "trash":
95
+ return {
96
+ icon: TrashIcon,
97
+ iconClass: "w-3"
98
+ };
99
+ case "create":
100
+ return {
101
+ icon: CreateIcon,
102
+ iconClass: "w-3"
103
+ };
104
+ case "edit":
105
+ return {
106
+ icon: EditIcon,
107
+ iconClass: "w-3"
108
+ };
109
+ case "play":
110
+ return {
111
+ icon: PlayIcon,
112
+ iconClass: "w-3"
113
+ };
114
+ case "stop":
115
+ return {
116
+ icon: StopIcon,
117
+ iconClass: "w-3"
118
+ };
119
+ case "pause":
120
+ return {
121
+ icon: PauseIcon,
122
+ iconClass: "w-3"
123
+ };
124
+ case "refresh":
125
+ return {
126
+ icon: RefreshIcon,
127
+ iconClass: "w-4"
128
+ };
129
+ default:
130
+ return {
131
+ icon: EditIcon,
132
+ iconClass: "w-3"
133
+ };
134
+ }
135
+ });
136
+
137
+ const isSaving = computed(() => {
138
+ if (props.saving) return true;
139
+ if (props.target) {
140
+ if (Array.isArray(props.target)) {
141
+ return props.target.some((t) => t.isSaving);
142
+ }
143
+ return props.target.isSaving;
144
+ }
145
+ if (props.action) {
146
+ return props.action.isApplying;
147
+ }
148
+ return false;
149
+ });
150
+
151
+ function onAction() {
152
+ if (props.action) {
153
+ props.action.trigger(props.target, props.input).then(async (response) => {
154
+ emit("success", typeof response.json === "function" ? await response.json() : response);
155
+ }).catch((e) => {
156
+ console.error(`Action emitted an error: ${props.action.name}`, e, props.target);
157
+ emit("error", e);
158
+ }).finally(() => {
159
+ emit("always");
160
+ });
161
+ }
162
+ }
163
+ </script>
@@ -10,6 +10,11 @@
10
10
  :is="isShowing ? (hideIcon || DefaultHideIcon) : (showIcon || DefaultShowIcon)"
11
11
  :class="iconClass"
12
12
  />
13
+ </slot>
14
+ <slot
15
+ name="label"
16
+ :is-showing="isShowing"
17
+ >
13
18
  <div
14
19
  v-if="label"
15
20
  :class="labelClass"
@@ -4,6 +4,7 @@
4
4
  v-bind="$props"
5
5
  @close="onClose"
6
6
  >
7
+ <slot />
7
8
  <template
8
9
  v-if="$slots.title"
9
10
  #title
@@ -308,6 +308,10 @@ async function onConfirmAction(action: ActionOptions, target: ActionTarget, inpu
308
308
 
309
309
  export function withDefaultActions(label: string, listController?: ListController): ActionOptions[] {
310
310
  return [
311
+ {
312
+ name: "quick-create",
313
+ alias: "create"
314
+ },
311
315
  {
312
316
  name: "create",
313
317
  label: "Create " + label,
@@ -180,16 +180,27 @@ export function fSecondsToTime(second: number) {
180
180
  return (hours ? hours + ":" : "") + time.toFormat("mm:ss");
181
181
  }
182
182
 
183
- export function fElapsedTime(start: string, end?: string) {
183
+ /**
184
+ * Formats a number of seconds into a duration string in 00h 00m 00s format
185
+ */
186
+ export function fSecondsToDuration(seconds: number) {
187
+ const hours = Math.floor(seconds / 3600);
188
+ const minutes = Math.floor((seconds % 3600) / 60);
189
+ const secs = Math.floor(seconds % 60);
190
+ return `${hours ? hours + "h " : ""}${minutes ? minutes + "m " : ""}${secs}s`;
191
+ }
192
+
193
+ /**
194
+ * Formats a duration between two date strings in 00h 00m 00s format
195
+ */
196
+ export function fDuration(start: string, end?: string) {
184
197
  const endDateTime = end ? parseDateTime(end) : DateTime.now();
185
198
  const diff = endDateTime?.diff(parseDateTime(start) || DateTime.now(), ["hours", "minutes", "seconds"]);
186
199
  if (!diff?.isValid) {
187
200
  return "-";
188
201
  }
189
- const hours = Math.floor(diff.hours);
190
- const minutes = Math.floor(diff.minutes);
191
- const seconds = Math.floor(diff.seconds);
192
- return `${hours ? hours + "h " : ""}${minutes ? minutes + "m " : ""}${seconds}s`;
202
+ const totalSeconds = diff.as("seconds");
203
+ return fSecondsToDuration(totalSeconds);
193
204
  }
194
205
 
195
206
  /**
@@ -5,27 +5,31 @@ import { request } from "./request";
5
5
 
6
6
  export function useActionRoutes(baseUrl: string, extend?: object): ListControlsRoutes {
7
7
  return {
8
- list(pager?) {
9
- return request.post(`${baseUrl}/list`, pager);
8
+ list(pager?, options?) {
9
+ return request.post(`${baseUrl}/list`, pager, options);
10
10
  },
11
- summary(filter) {
12
- return request.post(`${baseUrl}/summary`, { filter });
11
+ summary(filter, options?) {
12
+ return request.post(`${baseUrl}/summary`, { filter }, options);
13
13
  },
14
- details(target, fields) {
15
- return request.get(`${baseUrl}/${target.id}/details`, { params: { fields } });
14
+ details(target, fields, options?) {
15
+ options = options || {};
16
+ fields && (options.params = { fields });
17
+ return request.get(`${baseUrl}/${target.id}/details`, options);
16
18
  },
17
- async detailsAndStore(target, fields) {
18
- const item = await request.get(`${baseUrl}/${target.id}/details`, { params: { fields } });
19
+ async detailsAndStore(target, fields, options?) {
20
+ options = options || {};
21
+ fields && (options.params = { fields });
22
+ const item = await request.get(`${baseUrl}/${target.id}/details`, options);
19
23
  return storeObject(item);
20
24
  },
21
- fieldOptions() {
22
- return request.get(`${baseUrl}/field-options`);
25
+ fieldOptions(options?) {
26
+ return request.get(`${baseUrl}/field-options`, options);
23
27
  },
24
- applyAction(action, target, data) {
25
- return request.post(`${baseUrl}/${target ? target.id : "new"}/apply-action`, { action, data });
28
+ applyAction(action, target, data, options?) {
29
+ return request.post(`${baseUrl}/${target ? target.id : "new"}/apply-action`, { action, data }, options);
26
30
  },
27
- batchAction(action, targets, data) {
28
- return request.post(`${baseUrl}/batch-action`, { action, filter: { id: targets.map(r => r.id) }, data });
31
+ batchAction(action, targets, data, options?) {
32
+ return request.post(`${baseUrl}/batch-action`, { action, filter: { id: targets.map(r => r.id) }, data }, options);
29
33
  },
30
34
  export(filter, name) {
31
35
  return downloadFile(`${baseUrl}/export`, name || "export.csv", { filter });
@@ -1,3 +1,4 @@
1
+ import { RequestCallOptions } from "src/types/requests";
1
2
  import { ActionOptions, ActionTargetItem, ResourceAction } from "./actions";
2
3
  import { AnyObject, ComputedRef, LabelValueItem, Ref } from "./shared";
3
4
 
@@ -20,21 +21,21 @@ export interface FilterGroup {
20
21
  }
21
22
 
22
23
  export interface ListControlsRoutes<T = ActionTargetItem> {
23
- list(pager?: ListControlsPagination): Promise<PagedItems>;
24
+ list(pager?: ListControlsPagination, options?: RequestCallOptions): Promise<PagedItems>;
24
25
 
25
- summary?(filter?: ListControlsFilter): Promise<AnyObject>;
26
+ summary?(filter?: ListControlsFilter, options?: RequestCallOptions): Promise<AnyObject>;
26
27
 
27
- details?(target: T, fields: ControlsFieldsList): Promise<T>;
28
+ details?(target: T, fields?: ControlsFieldsList, options?: RequestCallOptions): Promise<T>;
28
29
 
29
- detailsAndStore?(target: T, fields: ControlsFieldsList): Promise<T>;
30
+ detailsAndStore?(target: T, fields?: ControlsFieldsList, options?: RequestCallOptions): Promise<T>;
30
31
 
31
- more?(pager: ListControlsPagination): Promise<T[]>;
32
+ more?(pager: ListControlsPagination, options?: RequestCallOptions): Promise<T[]>;
32
33
 
33
- fieldOptions?(filter?: AnyObject): Promise<AnyObject>;
34
+ fieldOptions?(filter?: AnyObject, options?: RequestCallOptions): Promise<AnyObject>;
34
35
 
35
- applyAction?(action: string | ResourceAction | ActionOptions, target: T | null, data?: object): Promise<AnyObject>;
36
+ applyAction?(action: string | ResourceAction | ActionOptions, target: T | null, data?: object, options?: RequestCallOptions): Promise<AnyObject>;
36
37
 
37
- batchAction?(action: string | ResourceAction | ActionOptions, targets: T[], data: object): Promise<AnyObject>;
38
+ batchAction?(action: string | ResourceAction | ActionOptions, targets: T[], data: object, options?: RequestCallOptions): Promise<AnyObject>;
38
39
 
39
40
  export?(filter?: ListControlsFilter, name?: string): Promise<void>;
40
41
  }
@@ -19,6 +19,7 @@ export interface TextFieldProps {
19
19
  disabled?: boolean;
20
20
  readonly?: boolean;
21
21
  debounce?: string | number;
22
+ loading?: boolean;
22
23
  }
23
24
 
24
25
  export interface NumberFieldProps extends TextFieldProps {