quasar-ui-danx 0.4.29 → 0.4.31

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.29",
3
+ "version": "0.4.31",
4
4
  "author": "Dan <dan@flytedesk.com>",
5
5
  "description": "DanX Vue / Quasar component library",
6
6
  "license": "MIT",
@@ -2,17 +2,19 @@
2
2
  <div class="inline-block relative">
3
3
  <div
4
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"
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 min-w-10 min-h-10"
6
6
  :style="{minWidth, minHeight}"
7
7
  :class="contentClass"
8
8
  @input="onInput"
9
+ @focusin="hasFocus = true"
10
+ @focusout="hasFocus = false"
9
11
  >
10
12
  {{ text }}
11
13
  </div>
12
14
  <div
13
- v-if="!text && placeholder"
15
+ v-if="!text && placeholder && !hasFocus"
14
16
  ref="placeholderDiv"
15
- class="text-gray-600 absolute-top-left whitespace-nowrap z-1"
17
+ class="text-gray-600 absolute-top-left whitespace-nowrap z-1 pointer-events-none"
16
18
  >
17
19
  {{ placeholder }}
18
20
  </div>
@@ -21,15 +23,17 @@
21
23
 
22
24
  <script setup lang="ts">
23
25
  import { useDebounceFn } from "@vueuse/core";
24
- import { computed, onMounted, ref } from "vue";
26
+ import { computed, onMounted, ref, watch } from "vue";
25
27
 
26
28
  const emit = defineEmits(["update:model-value", "change"]);
27
29
  const props = withDefaults(defineProps<{
28
- modelValue: string;
30
+ modelValue?: string;
29
31
  color?: string;
32
+ textColor?: string;
30
33
  debounceDelay?: number;
31
34
  placeholder?: string;
32
35
  }>(), {
36
+ modelValue: "",
33
37
  // NOTE: You must safe-list required colors in tailwind.config.js
34
38
  // 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
35
39
  color: "blue-200",
@@ -42,6 +46,7 @@ const text = ref(props.modelValue);
42
46
  const placeholderDiv = ref(null);
43
47
  const minWidth = ref(0);
44
48
  const minHeight = ref(0);
49
+ const hasFocus = ref(false);
45
50
 
46
51
  onMounted(() => {
47
52
  // Set the min-width to the width of the placeholder
@@ -51,6 +56,11 @@ onMounted(() => {
51
56
  }
52
57
  });
53
58
 
59
+ watch(() => props.modelValue, (value) => {
60
+ if (!hasFocus.value)
61
+ text.value = value;
62
+ });
63
+
54
64
  const debouncedChange = useDebounceFn(() => {
55
65
  emit("update:model-value", text.value);
56
66
  emit("change", text.value);
@@ -64,6 +74,7 @@ function onInput(e) {
64
74
  const contentClass = computed(() => [
65
75
  `hover:bg-${props.color} focus:bg-${props.color}`,
66
76
  `hover:text-${props.textColor} focus:text-${props.textColor}`,
67
- `hover:outline-${props.color} focus:outline-${props.color}`
77
+ `hover:outline-${props.color} focus:outline-${props.color}`,
78
+ text.value ? "" : "!bg-none"
68
79
  ]);
69
80
  </script>
@@ -24,7 +24,9 @@
24
24
  <FilePreview
25
25
  v-for="file in uploadedFiles"
26
26
  :key="'file-upload-' + file.id"
27
- class="w-32 h-32 m-2 cursor-pointer bg-gray-200"
27
+ class="m-2 cursor-pointer bg-gray-200"
28
+ :class="filePreviewClass"
29
+ :style="styleSize"
28
30
  :file="file"
29
31
  :related-files="file.transcodes || uploadedFiles"
30
32
  downloadable
@@ -33,14 +35,19 @@
33
35
  />
34
36
  <div
35
37
  v-if="!disable && !readonly"
36
- class="dx-add-remove-files w-32 h-32 m-2 rounded-2xl flex flex-col flex-nowrap items-center overflow-hidden cursor-pointer"
38
+ class="dx-add-remove-files m-2 flex flex-col flex-nowrap items-center overflow-hidden cursor-pointer"
39
+ :class="filePreviewClass"
40
+ :style="styleSize"
37
41
  >
38
42
  <div
39
43
  class="dx-add-file flex-grow p-1 pt-3 flex justify-center items-center bg-green-200 text-green-700 w-full hover:bg-green-100"
40
44
  @click="$refs.file.click()"
41
45
  >
42
46
  <div>
43
- <AddFileIcon class="w-10 m-auto" />
47
+ <AddFileIcon
48
+ class="m-auto"
49
+ :class="addIconClass"
50
+ />
44
51
  <div class="mt-1 text-center">
45
52
  Add
46
53
  </div>
@@ -66,7 +73,7 @@
66
73
  </template>
67
74
 
68
75
  <script setup lang="ts">
69
- import { onMounted } from "vue";
76
+ import { computed, onMounted } from "vue";
70
77
  import { useMultiFileUpload } from "../../../../helpers";
71
78
  import { ImageIcon as AddFileIcon, TrashIcon as RemoveFileIcon } from "../../../../svg";
72
79
  import { FormField, UploadedFile } from "../../../../types";
@@ -74,14 +81,28 @@ import { FilePreview } from "../../../Utility";
74
81
  import FieldLabel from "./FieldLabel";
75
82
 
76
83
  const emit = defineEmits(["update:model-value"]);
77
- const props = defineProps<{
84
+ const props = withDefaults(defineProps<{
78
85
  modelValue?: UploadedFile[];
79
86
  field?: FormField;
80
87
  label?: string;
81
88
  showName?: boolean;
82
89
  disable?: boolean;
83
90
  readonly?: boolean;
84
- }>();
91
+ width?: number | string;
92
+ height?: number | string;
93
+ addIconClass?: string;
94
+ filePreviewClass?: string;
95
+ filePreviewBtnSize?: string;
96
+ }>(), {
97
+ modelValue: null,
98
+ field: null,
99
+ label: "",
100
+ width: 128,
101
+ height: 128,
102
+ addIconClass: "w-10",
103
+ filePreviewClass: "rounded-2xl",
104
+ filePreviewBtnSize: "sm"
105
+ });
85
106
 
86
107
  const { onComplete, onDrop, onFilesSelected, uploadedFiles, clearUploadedFiles, onRemove } = useMultiFileUpload();
87
108
  onMounted(() => {
@@ -90,4 +111,11 @@ onMounted(() => {
90
111
  }
91
112
  });
92
113
  onComplete(() => emit("update:model-value", uploadedFiles.value));
114
+
115
+ const styleSize = computed(() => {
116
+ return {
117
+ width: typeof props.width === "number" ? `${props.width}px` : props.width,
118
+ height: typeof props.height === "number" ? `${props.height}px` : props.height
119
+ };
120
+ });
93
121
  </script>
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <div
3
- class="relative flex justify-center bg-gray-100 overflow-hidden"
3
+ class="group relative flex justify-center bg-gray-100 overflow-hidden"
4
4
  :class="{'rounded-2xl': !square}"
5
5
  >
6
6
  <template v-if="computedImage">
@@ -91,10 +91,10 @@
91
91
  </slot>
92
92
  </template>
93
93
 
94
- <div class="absolute top-1 right-1 flex items-center justify-between space-x-1">
94
+ <div class="absolute top-1 right-1 flex items-center flex-nowrap justify-between space-x-1 transition-all opacity-0 group-hover:opacity-100">
95
95
  <QBtn
96
96
  v-if="downloadable && computedImage?.url"
97
- size="sm"
97
+ :size="btnSize"
98
98
  class="dx-file-preview-download py-1 px-2 opacity-70 hover:opacity-100"
99
99
  :class="downloadButtonClass"
100
100
  @click.stop="download(computedImage.url)"
@@ -104,7 +104,7 @@
104
104
 
105
105
  <QBtn
106
106
  v-if="removable"
107
- size="sm"
107
+ :size="btnSize"
108
108
  class="dx-file-preview-remove bg-red-900 text-white opacity-50 hover:opacity-100 py-1 px-2"
109
109
  @click.stop="onRemove"
110
110
  >
@@ -158,6 +158,7 @@ export interface FilePreviewProps {
158
158
  removable?: boolean;
159
159
  disabled?: boolean;
160
160
  square?: boolean;
161
+ btnSize?: "xs" | "sm" | "md" | "lg";
161
162
  }
162
163
 
163
164
  const emit = defineEmits(["remove"]);
@@ -172,7 +173,8 @@ const props = withDefaults(defineProps<FilePreviewProps>(), {
172
173
  downloadable: false,
173
174
  removable: false,
174
175
  disabled: false,
175
- square: false
176
+ square: false,
177
+ btnSize: "sm"
176
178
  });
177
179
 
178
180
 
@@ -166,6 +166,10 @@ export function useActions(actions: ActionOptions[], globalOptions: ActionGlobal
166
166
  result.item = storeObject(result.item);
167
167
  }
168
168
 
169
+ if (result?.result?.__type) {
170
+ result.result = storeObject(result.result);
171
+ }
172
+
169
173
  return result;
170
174
  }
171
175
 
@@ -79,6 +79,12 @@ export function storeObject<T extends TypedObject>(newObject: T, recentlyStoredO
79
79
  return reactiveObject;
80
80
  }
81
81
 
82
+ /**
83
+ * Auto refresh an object based on a condition and a callback. Returns the timeout ID for the auto-refresh.
84
+ * NOTE: Use the timeout ID to clear the auto-refresh when the object is no longer needed (eg: when the component is unmounted)
85
+ */
86
+ const registeredAutoRefreshes: AnyObject = {};
87
+
82
88
  export async function autoRefreshObject<T extends TypedObject>(object: T, condition: (object: T) => boolean, callback: (object: T) => Promise<T>, interval = 3000) {
83
89
  if (!object?.id || !object?.__type) {
84
90
  throw new Error("Invalid stored object. Cannot auto-refresh");
@@ -88,11 +94,21 @@ export async function autoRefreshObject<T extends TypedObject>(object: T, condit
88
94
  const refreshedObject = await callback(object);
89
95
 
90
96
  if (!refreshedObject.id) {
91
- return FlashMessages.error(`Failed to refresh ${object.__type} (${object.id}) status: ` + object.name);
97
+ FlashMessages.error(`Failed to refresh ${object.__type} (${object.id}) status: ` + object.name);
98
+ return null;
92
99
  }
93
100
 
94
101
  storeObject(refreshedObject);
95
102
  }
96
103
 
97
- setTimeout(() => autoRefreshObject(object, condition, callback), interval);
104
+
105
+ const timeoutId = setTimeout(() => autoRefreshObject(object, condition, callback, interval), interval);
106
+
107
+ registeredAutoRefreshes[object.__type + ":" + object.id] = timeoutId;
108
+ }
109
+
110
+ export async function stopAutoRefreshObject<T extends TypedObject>(object: T) {
111
+ const timeoutId = registeredAutoRefreshes[object.__type + ":" + object.id];
112
+
113
+ timeoutId && clearTimeout(timeoutId);
98
114
  }
@@ -12,6 +12,13 @@ export function sleep(delay: number) {
12
12
  return new Promise((resolve) => setTimeout(resolve, delay));
13
13
  }
14
14
 
15
+ /**
16
+ * Deep clone an object
17
+ */
18
+ export function cloneDeep(obj: any) {
19
+ return JSON.parse(JSON.stringify(obj));
20
+ }
21
+
15
22
  /**
16
23
  * Poll a callback function until the result is true
17
24
  */
@@ -20,7 +20,7 @@ export interface FilterGroup {
20
20
  }
21
21
 
22
22
  export interface ListControlsRoutes<T = ActionTargetItem> {
23
- list(pager?: ListControlsPagination): Promise<T[]>;
23
+ list(pager?: ListControlsPagination): Promise<PagedItems>;
24
24
 
25
25
  summary?(filter?: ListControlsFilter): Promise<AnyObject>;
26
26