quasar-ui-danx 0.0.36 → 0.0.38

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.0.36",
3
+ "version": "0.0.38",
4
4
  "author": "Dan <dan@flytedesk.com>",
5
5
  "description": "DanX Vue / Quasar component library",
6
6
  "license": "MIT",
@@ -1,21 +1,22 @@
1
1
  <template>
2
2
  <PopoverMenu
3
3
  class="px-2 flex action-button"
4
- :items="activeItems"
4
+ :items="activeActions"
5
5
  :disabled="targets.length === 0"
6
+ :tooltip="targets.length === 0 ? tooltip : null"
7
+ :loading="loading || isSaving"
8
+ :loading-component="loadingComponent"
6
9
  @action-item="onAction"
7
- >
8
- <q-tooltip v-if="targets.length === 0">{{ tooltip }}</q-tooltip>
9
- </PopoverMenu>
10
+ />
10
11
  </template>
11
12
  <script setup>
12
- import { computed } from 'vue';
13
+ import { computed, ref } from 'vue';
13
14
  import { performAction } from '../../helpers';
14
15
  import { PopoverMenu } from '../Utility';
15
16
 
16
17
  const emit = defineEmits(['action']);
17
18
  const props = defineProps({
18
- items: {
19
+ actions: {
19
20
  type: Array,
20
21
  required: true
21
22
  },
@@ -26,16 +27,24 @@ const props = defineProps({
26
27
  tooltip: {
27
28
  type: String,
28
29
  default: 'First select records to perform a batch action'
30
+ },
31
+ loading: Boolean,
32
+ loadingComponent: {
33
+ type: [Function, Object],
34
+ default: undefined
29
35
  }
30
36
  });
31
37
 
32
- const activeItems = computed(() => props.items.filter(item => {
33
- if (item.enabled === undefined) return true;
34
- return typeof item.enabled === 'function' ? !!item.enabled(props.targets?.[0] ?? null, props.targets) : !!item.enabled;
38
+ const activeActions = computed(() => props.actions.filter(action => {
39
+ if (action.enabled === undefined) return true;
40
+ return typeof action.enabled === 'function' ? !!action.enabled(props.targets?.[0] ?? null, props.targets) : !!action.enabled;
35
41
  }));
36
42
 
37
- function onAction(item) {
38
- emit('action', item);
39
- performAction(item, props.targets);
43
+ const isSaving = ref(false);
44
+ async function onAction(action) {
45
+ emit('action', action);
46
+ isSaving.value = true;
47
+ await performAction(action, props.targets);
48
+ isSaving.value = false;
40
49
  }
41
50
  </script>
@@ -41,7 +41,7 @@
41
41
  v-if="rowProps.col.resizeable"
42
42
  :drop-zone="`resize-column-` + rowProps.col.name"
43
43
  class="resize-handle"
44
- @resize="rowProps.col.onResize"
44
+ @resize="onResizeColumn(rowProps.col, $event)"
45
45
  >
46
46
  <RowResizeIcon class="w-4 text-neutral-base" />
47
47
  </HandleDraggable>
@@ -53,6 +53,7 @@
53
53
  :is="rowProps.col.onClick ? 'a' : 'div'"
54
54
  class="flex items-center flex-nowrap"
55
55
  :class="{'justify-end': rowProps.col.align === 'right', 'justify-center': rowProps.col.align === 'center', 'justify-start': rowProps.col.align === 'left'}"
56
+ :style="getColumnStyle(rowProps.col)"
56
57
  @click="() => rowProps.col.onClick && rowProps.col.onClick(rowProps.row)"
57
58
  >
58
59
  <RenderComponent
@@ -74,8 +75,9 @@
74
75
  </div>
75
76
  <div v-if="rowProps.col.actions" class="flex-grow flex justify-end pl-2">
76
77
  <ActionMenu
77
- :items="rowProps.col.actions"
78
+ :actions="rowProps.col.actions"
78
79
  :targets="[rowProps.row]"
80
+ :loading="isSavingItem?.id === rowProps.row.id"
79
81
  @action="(action) => $emit('action', {action: action, row: rowProps.row})"
80
82
  />
81
83
  </div>
@@ -87,19 +89,18 @@
87
89
 
88
90
  <script setup>
89
91
  import { ref } from 'vue';
92
+ import { getItem, setItem } from '../../helpers';
90
93
  import { DragHandleIcon as RowResizeIcon } from '../../svg';
91
94
  import { HandleDraggable } from '../DragAndDrop';
92
- import {
93
- ActionMenu,
94
- EmptyTableState,
95
- mapSortBy,
96
- registerStickyScrolling,
97
- RenderComponent,
98
- TableSummaryRow
99
- } from './index';
95
+ import { mapSortBy, RenderComponent } from '../index';
96
+ import { ActionMenu, EmptyTableState, registerStickyScrolling, TableSummaryRow } from './index';
100
97
 
101
98
  defineEmits(['action', 'filter', 'update:quasar-pagination', 'update:selected-rows']);
102
- defineProps({
99
+ const props = defineProps({
100
+ name: {
101
+ type: String,
102
+ required: true
103
+ },
103
104
  label: {
104
105
  type: String,
105
106
  required: true
@@ -112,6 +113,10 @@ defineProps({
112
113
  type: Object,
113
114
  required: true
114
115
  },
116
+ isSavingItem: {
117
+ type: Object,
118
+ default: null
119
+ },
115
120
  isLoadingList: Boolean,
116
121
  pagedItems: {
117
122
  type: Object,
@@ -133,6 +138,23 @@ defineProps({
133
138
  });
134
139
  const actionTable = ref(null);
135
140
  registerStickyScrolling(actionTable);
141
+
142
+ const COLUMN_SETTINGS_KEY = `column-settings-${props.name}`;
143
+ const columnSettings = ref(getItem(COLUMN_SETTINGS_KEY) || {});
144
+ function onResizeColumn(column, val) {
145
+ columnSettings.value[column.name] = Math.max(Math.min(val.distance + val.startDropZoneSize, column.maxWidth || 500), column.minWidth || 80);
146
+ setItem(COLUMN_SETTINGS_KEY, columnSettings.value);
147
+ }
148
+ function getColumnStyle(column) {
149
+ const width = columnSettings.value[column.name] || column.width;
150
+
151
+ if (width) {
152
+ return {
153
+ width: `${width}px`
154
+ };
155
+ }
156
+ return null;
157
+ }
136
158
  </script>
137
159
 
138
160
  <style lang="scss" scoped>
@@ -7,5 +7,4 @@ export * from "./tableColumns";
7
7
  export { default as ActionMenu } from "./ActionMenu.vue";
8
8
  export { default as ActionTable } from "./ActionTable.vue";
9
9
  export { default as EmptyTableState } from "./EmptyTableState.vue";
10
- export { default as RenderComponent } from "./RenderComponent.vue";
11
10
  export { default as TableSummaryRow } from "./TableSummaryRow.vue";
@@ -228,11 +228,11 @@ export function useListActions(name: string, {
228
228
  /**
229
229
  * Applies an action to an item.
230
230
  */
231
- const isApplyingActionToItem = ref(null);
231
+ const isSavingItem = ref(null);
232
232
  let actionResultCount = 0;
233
233
 
234
234
  async function applyAction(item, input, itemData = {}) {
235
- isApplyingActionToItem.value = item;
235
+ isSavingItem.value = item;
236
236
  const resultNumber = ++actionResultCount;
237
237
  setItemInPagedList({ ...item, ...input, ...itemData });
238
238
  const result = await applyActionRoute(item, input);
@@ -248,7 +248,7 @@ export function useListActions(name: string, {
248
248
  activeItem.value = { ...activeItem.value, ...result.item };
249
249
  }
250
250
  }
251
- isApplyingActionToItem.value = null;
251
+ isSavingItem.value = null;
252
252
  return result;
253
253
  }
254
254
 
@@ -355,7 +355,7 @@ export function useListActions(name: string, {
355
355
  isLoadingSummary,
356
356
  pager,
357
357
  quasarPagination,
358
- isApplyingActionToItem,
358
+ isSavingItem,
359
359
  activeItem,
360
360
  activePanel,
361
361
 
@@ -1,11 +1,10 @@
1
1
  import { computed, ref, watch } from "vue";
2
2
  import { getItem, setItem } from "../../helpers";
3
3
 
4
- export function useTableColumns(name, columns, options = { titleMinWidth: 120, titleMaxWidth: 200 }) {
4
+ export function useTableColumns(name, columns) {
5
5
  const COLUMN_ORDER_KEY = `${name}-column-order`;
6
6
  const VISIBLE_COLUMNS_KEY = `${name}-visible-columns`;
7
7
  const TITLE_COLUMNS_KEY = `${name}-title-columns`;
8
- const TITLE_WIDTH_KEY = `${name}-title-width`;
9
8
 
10
9
  // The list that defines the order the columns should appear in
11
10
  const columnOrder = ref(getItem(COLUMN_ORDER_KEY) || []);
@@ -16,17 +15,6 @@ export function useTableColumns(name, columns, options = { titleMinWidth: 120, t
16
15
  // Title columns will have their name appear on the first column of the table as part of the records' title
17
16
  const titleColumnNames = ref(getItem(TITLE_COLUMNS_KEY, []));
18
17
 
19
- // The width of the title column
20
- const titleWidth = ref(getItem(TITLE_WIDTH_KEY, options.titleMinWidth));
21
-
22
- /**
23
- * When the title column is resized, update the titleWidth
24
- * @param val
25
- */
26
- function onResizeTitleColumn(val) {
27
- titleWidth.value = Math.max(Math.min(val.distance + val.startDropZoneSize, options.titleMaxWidth), options.titleMinWidth);
28
- }
29
-
30
18
  // Columns that should be locked to the left side of the table
31
19
  const lockedColumns = computed(() => orderedColumns.value.slice(0, 1));
32
20
 
@@ -57,7 +45,6 @@ export function useTableColumns(name, columns, options = { titleMinWidth: 120, t
57
45
  // Save changes to the list of hidden columns in localStorage
58
46
  watch(() => hiddenColumnNames.value, () => setItem(VISIBLE_COLUMNS_KEY, hiddenColumnNames.value));
59
47
  watch(() => titleColumnNames.value, () => setItem(TITLE_COLUMNS_KEY, titleColumnNames.value));
60
- watch(() => titleWidth.value, () => setItem(TITLE_WIDTH_KEY, titleWidth.value));
61
48
 
62
49
  return {
63
50
  sortableColumns,
@@ -65,8 +52,6 @@ export function useTableColumns(name, columns, options = { titleMinWidth: 120, t
65
52
  visibleColumns,
66
53
  hiddenColumnNames,
67
54
  titleColumnNames,
68
- titleWidth,
69
- orderedTitleColumns,
70
- onResizeTitleColumn
55
+ orderedTitleColumns
71
56
  };
72
57
  }
@@ -3,14 +3,16 @@
3
3
  class="p-3 actionable"
4
4
  :class="{'opacity-50 cursor-not-allowed': disabled}"
5
5
  >
6
- <slot />
6
+ <q-tooltip v-if="$slots.tooltip || tooltip">
7
+ <slot name="tooltip">{{ tooltip }}</slot>
8
+ </q-tooltip>
7
9
  <Transition
8
10
  mode="out-in"
9
11
  :duration="150"
10
12
  >
11
- <q-spinner
13
+ <RenderComponent
12
14
  v-if="loading"
13
- class="w-4 h-4 text-black"
15
+ :component="loadingComponent"
14
16
  />
15
17
  <MenuIcon
16
18
  v-else
@@ -49,6 +51,8 @@
49
51
  </template>
50
52
  <script setup>
51
53
  import { DotsVerticalIcon as MenuIcon } from '@heroicons/vue/outline';
54
+ import { QSpinner } from 'quasar';
55
+ import { RenderComponent } from '../index';
52
56
 
53
57
  const emit = defineEmits(['action', 'action-item']);
54
58
  defineProps({
@@ -56,11 +60,22 @@ defineProps({
56
60
  type: Array,
57
61
  required: true,
58
62
  validator(items) {
59
- return items.every((item) => item.label && (item.url || item.action || item.name));
63
+ return items.every((item) => item.url || item.action || item.name);
60
64
  }
61
65
  },
66
+ tooltip: {
67
+ type: String,
68
+ default: null
69
+ },
62
70
  disabled: Boolean,
63
- loading: Boolean
71
+ loading: Boolean,
72
+ loadingComponent: {
73
+ type: [Function, Object],
74
+ default: () => ({
75
+ is: QSpinner,
76
+ props: { class: 'w-4 h-4 text-black' }
77
+ })
78
+ }
64
79
  });
65
80
 
66
81
  function onAction(item) {
@@ -42,11 +42,22 @@ async function onConfirmAction(input) {
42
42
  }
43
43
 
44
44
  isSaving.value = true;
45
- const result = await props.action.onAction(props.targets, input);
45
+ let result;
46
+ try {
47
+ result = await props.action.onAction(props.targets, input);
48
+ } catch (e) {
49
+ console.error(e);
50
+ result = { error: `An error occurred while performing the action ${props.action.label}. Please try again later.` };
51
+ }
52
+
46
53
  isSaving.value = false;
47
54
 
48
- if (result.success) {
49
- FlashMessages.success(`The update was successful`);
55
+ // If there is no return value or the result marks it as successful, we show a success message
56
+ if (result === undefined || result?.success) {
57
+
58
+ if (result?.success) {
59
+ FlashMessages.success(`The update was successful`);
60
+ }
50
61
 
51
62
  if (props.action.onSuccess) {
52
63
  await props.action.onSuccess(result, props.targets, input);
@@ -58,7 +69,7 @@ async function onConfirmAction(input) {
58
69
  if (result.errors) {
59
70
  errors.push(...result.errors);
60
71
  } else if (result.error) {
61
- errors.push(result.error.message);
72
+ errors.push(typeof result.error === 'string' ? result.error : result.error.message);
62
73
  } else {
63
74
  errors.push('An unknown error occurred. Please try again later.');
64
75
  }
@@ -1 +1,2 @@
1
1
  export { default as ActionPerformerTool } from "./ActionPerformerTool.vue";
2
+ export { default as RenderComponent } from "./RenderComponent.vue";
package/tsconfig.json CHANGED
@@ -25,9 +25,6 @@
25
25
  "paths": {
26
26
  "@/*": [
27
27
  "./dev/src/*"
28
- ],
29
- "ui": [
30
- "./src/index.esm.js"
31
28
  ]
32
29
  }
33
30
  },