quasar-ui-danx 0.4.36 → 0.4.38

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.
@@ -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 {