adminforth 2.4.0-next.33 → 2.4.0-next.331

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.
Files changed (177) hide show
  1. package/commands/callTsProxy.js +14 -4
  2. package/commands/createApp/templates/api.ts.hbs +10 -0
  3. package/commands/createApp/templates/custom/tsconfig.json.hbs +2 -3
  4. package/commands/createApp/templates/index.ts.hbs +12 -1
  5. package/commands/createApp/templates/package.json.hbs +1 -1
  6. package/commands/createApp/templates/prisma.config.ts.hbs +8 -0
  7. package/commands/createApp/templates/schema.prisma.hbs +0 -1
  8. package/commands/createApp/utils.js +10 -0
  9. package/commands/createCustomComponent/configLoader.js +17 -4
  10. package/commands/createCustomComponent/main.js +13 -7
  11. package/commands/createCustomComponent/templates/customCrud/beforeActionButtons.vue.hbs +38 -0
  12. package/commands/createCustomComponent/templates/customCrud/saveButton.vue.hbs +28 -0
  13. package/commands/createPlugin/templates/custom/tsconfig.json.hbs +2 -5
  14. package/commands/createPlugin/templates/package.json.hbs +1 -1
  15. package/commands/generateModels.js +30 -22
  16. package/dist/auth.d.ts +9 -1
  17. package/dist/auth.d.ts.map +1 -1
  18. package/dist/auth.js +21 -2
  19. package/dist/auth.js.map +1 -1
  20. package/dist/dataConnectors/baseConnector.d.ts +1 -1
  21. package/dist/dataConnectors/baseConnector.d.ts.map +1 -1
  22. package/dist/dataConnectors/baseConnector.js +70 -18
  23. package/dist/dataConnectors/baseConnector.js.map +1 -1
  24. package/dist/dataConnectors/clickhouse.d.ts.map +1 -1
  25. package/dist/dataConnectors/clickhouse.js +15 -0
  26. package/dist/dataConnectors/clickhouse.js.map +1 -1
  27. package/dist/dataConnectors/mongo.d.ts.map +1 -1
  28. package/dist/dataConnectors/mongo.js +50 -15
  29. package/dist/dataConnectors/mongo.js.map +1 -1
  30. package/dist/dataConnectors/mysql.d.ts.map +1 -1
  31. package/dist/dataConnectors/mysql.js +11 -0
  32. package/dist/dataConnectors/mysql.js.map +1 -1
  33. package/dist/dataConnectors/postgres.d.ts.map +1 -1
  34. package/dist/dataConnectors/postgres.js +43 -14
  35. package/dist/dataConnectors/postgres.js.map +1 -1
  36. package/dist/dataConnectors/sqlite.d.ts.map +1 -1
  37. package/dist/dataConnectors/sqlite.js +11 -0
  38. package/dist/dataConnectors/sqlite.js.map +1 -1
  39. package/dist/index.d.ts +11 -1
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +44 -21
  42. package/dist/index.js.map +1 -1
  43. package/dist/modules/codeInjector.d.ts +2 -0
  44. package/dist/modules/codeInjector.d.ts.map +1 -1
  45. package/dist/modules/codeInjector.js +62 -6
  46. package/dist/modules/codeInjector.js.map +1 -1
  47. package/dist/modules/configValidator.d.ts +6 -0
  48. package/dist/modules/configValidator.d.ts.map +1 -1
  49. package/dist/modules/configValidator.js +209 -25
  50. package/dist/modules/configValidator.js.map +1 -1
  51. package/dist/modules/restApi.d.ts +1 -1
  52. package/dist/modules/restApi.d.ts.map +1 -1
  53. package/dist/modules/restApi.js +199 -31
  54. package/dist/modules/restApi.js.map +1 -1
  55. package/dist/modules/styles.d.ts +499 -13
  56. package/dist/modules/styles.d.ts.map +1 -1
  57. package/dist/modules/styles.js +555 -31
  58. package/dist/modules/styles.js.map +1 -1
  59. package/dist/modules/utils.d.ts +7 -15
  60. package/dist/modules/utils.d.ts.map +1 -1
  61. package/dist/modules/utils.js +45 -68
  62. package/dist/modules/utils.js.map +1 -1
  63. package/dist/servers/express.d.ts +5 -0
  64. package/dist/servers/express.d.ts.map +1 -1
  65. package/dist/servers/express.js +40 -1
  66. package/dist/servers/express.js.map +1 -1
  67. package/dist/spa/index.html +1 -1
  68. package/dist/spa/package-lock.json +1208 -708
  69. package/dist/spa/package.json +34 -34
  70. package/dist/spa/src/App.vue +132 -174
  71. package/dist/spa/src/adminforth.ts +41 -17
  72. package/dist/spa/src/afcl/AreaChart.vue +0 -1
  73. package/dist/spa/src/afcl/BarChart.vue +2 -2
  74. package/dist/spa/src/afcl/Button.vue +3 -3
  75. package/dist/spa/src/afcl/ButtonGroup.vue +91 -0
  76. package/dist/spa/src/afcl/Card.vue +25 -0
  77. package/dist/spa/src/afcl/Checkbox.vue +21 -13
  78. package/dist/spa/src/afcl/CountryFlag.vue +4 -1
  79. package/dist/spa/src/{components/CustomDatePicker.vue → afcl/DatePicker.vue} +95 -9
  80. package/dist/spa/src/afcl/Dialog.vue +47 -27
  81. package/dist/spa/src/afcl/Dropzone.vue +145 -48
  82. package/dist/spa/src/afcl/Input.vue +14 -6
  83. package/dist/spa/src/afcl/JsonViewer.vue +25 -0
  84. package/dist/spa/src/afcl/LinkButton.vue +3 -3
  85. package/dist/spa/src/afcl/PieChart.vue +5 -5
  86. package/dist/spa/src/afcl/ProgressBar.vue +7 -7
  87. package/dist/spa/src/afcl/Select.vue +82 -34
  88. package/dist/spa/src/afcl/Skeleton.vue +6 -6
  89. package/dist/spa/src/afcl/Table.vue +313 -75
  90. package/dist/spa/src/afcl/Textarea.vue +31 -0
  91. package/dist/spa/src/afcl/Toggle.vue +32 -0
  92. package/dist/spa/src/afcl/Tooltip.vue +28 -18
  93. package/dist/spa/src/afcl/VerticalTabs.vue +21 -7
  94. package/dist/spa/src/afcl/index.ts +6 -3
  95. package/dist/spa/src/components/AcceptModal.vue +48 -14
  96. package/dist/spa/src/components/Breadcrumbs.vue +5 -5
  97. package/dist/spa/src/components/CallActionWrapper.vue +15 -0
  98. package/dist/spa/src/components/ColumnValueInput.vue +38 -18
  99. package/dist/spa/src/components/ColumnValueInputWrapper.vue +4 -3
  100. package/dist/spa/src/components/CustomDateRangePicker.vue +9 -8
  101. package/dist/spa/src/components/CustomRangePicker.vue +37 -21
  102. package/dist/spa/src/components/ErrorMessage.vue +21 -0
  103. package/dist/spa/src/components/Filters.vue +195 -132
  104. package/dist/spa/src/components/GroupsTable.vue +9 -8
  105. package/dist/spa/src/components/MenuLink.vue +95 -23
  106. package/dist/spa/src/components/ResourceForm.vue +99 -51
  107. package/dist/spa/src/components/ResourceListTable.vue +121 -95
  108. package/dist/spa/src/components/ResourceListTableVirtual.vue +119 -88
  109. package/dist/spa/src/components/ShowTable.vue +21 -15
  110. package/dist/spa/src/components/Sidebar.vue +472 -0
  111. package/dist/spa/src/components/SingleSkeletLoader.vue +6 -6
  112. package/dist/spa/src/components/SkeleteLoader.vue +3 -3
  113. package/dist/spa/src/components/ThreeDotsMenu.vue +84 -15
  114. package/dist/spa/src/components/Toast.vue +40 -29
  115. package/dist/spa/src/components/UserMenuSettingsButton.vue +69 -0
  116. package/dist/spa/src/components/ValueRenderer.vue +44 -17
  117. package/dist/spa/src/controls/BoolToggle.vue +34 -0
  118. package/dist/spa/src/i18n.ts +5 -3
  119. package/dist/spa/src/main.ts +1 -1
  120. package/dist/spa/src/renderers/CompactField.vue +1 -1
  121. package/dist/spa/src/renderers/CompactUUID.vue +1 -1
  122. package/dist/spa/src/router/index.ts +8 -0
  123. package/dist/spa/src/shims-vue.d.ts +5 -0
  124. package/dist/spa/src/spa_types/core.ts +13 -1
  125. package/dist/spa/src/stores/core.ts +15 -1
  126. package/dist/spa/src/stores/filters.ts +33 -2
  127. package/dist/spa/src/stores/modal.ts +6 -1
  128. package/dist/spa/src/stores/toast.ts +22 -3
  129. package/dist/spa/src/types/Back.ts +168 -23
  130. package/dist/spa/src/types/Common.ts +109 -32
  131. package/dist/spa/src/types/FrontendAPI.ts +32 -23
  132. package/dist/spa/src/types/adapters/CaptchaAdapter.ts +34 -0
  133. package/dist/spa/src/types/adapters/EmailAdapter.ts +2 -4
  134. package/dist/spa/src/types/adapters/ImageVisionAdapter.ts +30 -0
  135. package/dist/spa/src/types/adapters/KeyValueAdapter.ts +16 -0
  136. package/dist/spa/src/types/adapters/StorageAdapter.ts +4 -2
  137. package/dist/spa/src/types/adapters/index.ts +3 -0
  138. package/dist/spa/src/utils.ts +291 -11
  139. package/dist/spa/src/views/CreateView.vue +88 -22
  140. package/dist/spa/src/views/EditView.vue +55 -22
  141. package/dist/spa/src/views/ListView.vue +144 -87
  142. package/dist/spa/src/views/LoginView.vue +26 -35
  143. package/dist/spa/src/views/ResourceParent.vue +2 -2
  144. package/dist/spa/src/views/SettingsView.vue +121 -0
  145. package/dist/spa/src/views/ShowView.vue +83 -53
  146. package/dist/spa/src/websocket.ts +6 -1
  147. package/dist/spa/tsconfig.app.json +1 -1
  148. package/dist/spa/vite.config.ts +45 -2
  149. package/dist/types/Back.d.ts +151 -14
  150. package/dist/types/Back.d.ts.map +1 -1
  151. package/dist/types/Back.js +15 -0
  152. package/dist/types/Back.js.map +1 -1
  153. package/dist/types/Common.d.ts +123 -29
  154. package/dist/types/Common.d.ts.map +1 -1
  155. package/dist/types/Common.js.map +1 -1
  156. package/dist/types/FrontendAPI.d.ts +32 -18
  157. package/dist/types/FrontendAPI.d.ts.map +1 -1
  158. package/dist/types/FrontendAPI.js.map +1 -1
  159. package/dist/types/adapters/CaptchaAdapter.d.ts +30 -0
  160. package/dist/types/adapters/CaptchaAdapter.d.ts.map +1 -0
  161. package/dist/types/adapters/CaptchaAdapter.js +5 -0
  162. package/dist/types/adapters/CaptchaAdapter.js.map +1 -0
  163. package/dist/types/adapters/EmailAdapter.d.ts +2 -3
  164. package/dist/types/adapters/EmailAdapter.d.ts.map +1 -1
  165. package/dist/types/adapters/ImageVisionAdapter.d.ts +25 -0
  166. package/dist/types/adapters/ImageVisionAdapter.d.ts.map +1 -0
  167. package/dist/types/adapters/ImageVisionAdapter.js +2 -0
  168. package/dist/types/adapters/ImageVisionAdapter.js.map +1 -0
  169. package/dist/types/adapters/KeyValueAdapter.d.ts +10 -0
  170. package/dist/types/adapters/KeyValueAdapter.d.ts.map +1 -0
  171. package/dist/types/adapters/KeyValueAdapter.js +2 -0
  172. package/dist/types/adapters/KeyValueAdapter.js.map +1 -0
  173. package/dist/types/adapters/StorageAdapter.d.ts +2 -0
  174. package/dist/types/adapters/StorageAdapter.d.ts.map +1 -1
  175. package/dist/types/adapters/index.d.ts +3 -0
  176. package/dist/types/adapters/index.d.ts.map +1 -1
  177. package/package.json +4 -2
@@ -1,57 +1,103 @@
1
1
  <template>
2
- <!-- tag form used to reset the input (method .reset() in claer() function) -->
2
+ <!-- tag form used to reset the input (method .reset() in clear() function) -->
3
3
  <form class="flex items-center justify-center w-full"
4
4
  @dragover.prevent="dragging = true"
5
5
  @dragleave.prevent="dragging = false"
6
- @drop.prevent="dragging = false; doEmit($event.dataTransfer.files)"
6
+ @drop.prevent="dragging = false; doEmit(($event.dataTransfer as DataTransfer).files)"
7
7
  >
8
- <label :id="id" class="flex flex-col items-center justify-center w-full border-2 border-dashed rounded-lg cursor-pointer dark:hover:bg-gray-800
9
- hover:bg-gray-100 dark:hover:border-gray-500 dark:hover:bg-gray-600"
8
+ <label :id="id" class="flex flex-col items-center justify-center w-full border-2 border-dashed rounded-lg cursor-pointer
9
+ hover:bg-lightDropzoneBackgroundHover hover:border-lightDropzoneBorderHover dark:hover:border-darkDropzoneBorderHover dark:hover:bg-darkDropzoneBackgroundHover"
10
10
  :class="{
11
- 'border-blue-600 dark:border-blue-400': dragging,
12
- 'border-gray-300 dark:border-gray-600': !dragging,
13
- 'bg-blue-50 dark:bg-blue-800': dragging,
14
- 'bg-gray-50 dark:bg-gray-800': !dragging,
11
+ 'border-lightDropzoneBorderDragging dark:border-darkDropzoneBorderDragging': dragging,
12
+ 'border-lightDropzoneBorder dark:border-darkDropzoneBorder': !dragging,
13
+ 'bg-lightDropzoneBackgroundDragging dark:bg-darkDropzoneBackgroundDragging': dragging,
14
+ 'bg-lightDropzoneBackground dark:bg-darkDropzoneBackground': !dragging,
15
15
  'min-h-32 h-full': props.multiple,
16
16
  'h-32': !props.multiple,
17
17
  }"
18
18
  >
19
- <div class="flex flex-col items-center justify-center pt-5 pb-6">
20
-
21
-
22
- <svg v-if="!selectedFiles.length" class="w-8 h-8 mb-4 text-gray-500 dark:text-gray-400" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 16">
23
- <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 13h3a3 3 0 0 0 0-6h-.025A5.56 5.56 0 0 0 16 6.5 5.5 5.5 0 0 0 5.207 5.021C5.137 5.017 5.071 5 5 5a4 4 0 0 0 0 8h2.167M10 15V6m0 0L8 8m2-2 2 2"/>
24
- </svg>
25
- <div v-else class="flex items-center justify-center flex-wrap gap-1 w-full mt-1 mb-4">
26
- <template v-for="file in selectedFiles">
27
- <p class="text-sm text-gray-500 dark:text-gray-400 flex items-center gap-1">
28
- <IconFileSolid class="w-5 h-5" />
29
- {{ file.name }} ({{ humanifySize(file.size) }})
30
- </p>
31
- </template>
32
-
19
+ <input
20
+ :id="id"
21
+ type="file"
22
+ class="hidden"
23
+ :accept="normalizedExtensions.join(',')"
24
+ @change="$event.target && doEmit(($event.target as HTMLInputElement).files!)"
25
+ :multiple="props.multiple || false"
26
+ />
27
+
28
+ <div class="flex flex-col items-center justify-center pt-5 pb-6">
29
+ <svg
30
+ v-if="!selectedFiles.length"
31
+ class="w-8 h-8 mb-4 text-lightDropzoneIcon dark:text-darkDropzoneIcon"
32
+ xmlns="http://www.w3.org/2000/svg"
33
+ fill="none"
34
+ viewBox="0 0 20 16"
35
+ >
36
+ <path
37
+ stroke="currentColor"
38
+ stroke-linecap="round"
39
+ stroke-linejoin="round"
40
+ stroke-width="2"
41
+ d="M13 13h3a3 3 0 0 0 0-6h-.025A5.56 5.56 0 0 0 16 6.5
42
+ 5.5 5.5 0 0 0 5.207 5.021C5.137 5.017 5.071 5 5 5a4 4 0
43
+ 0 0 0 8h2.167M10 15V6m0 0L8 8m2-2 2 2"
44
+ />
45
+ </svg>
46
+
47
+ <div
48
+ v-else
49
+ class="flex items-center justify-center py-1 flex-wrap gap-2 w-full mt-1 mb-4 px-4"
50
+ >
51
+ <template v-for="(file, index) in selectedFiles" :key="index">
52
+ <div
53
+ class="text-sm text-lightDropzoneIcon dark:text-darkDropzoneIcon bg-lightDropzoneBackgroundHover dark:bg-darkDropzoneBackgroundHover rounded-md
54
+ flex items-center gap-1 px-2 py-1 group"
55
+ >
56
+ <IconFileSolid class="w-4 h-4 flex-shrink-0" />
57
+ <span
58
+ class="truncate max-w-[200px]"
59
+ :title="file.name"
60
+ >
61
+ {{ shortenFileName(file.name) }}
62
+ </span>
63
+ <span class="text-xs">({{ humanifySize(file.size) }})</span>
64
+ <button
65
+ type="button"
66
+ @click.prevent.stop="removeFile(index)"
67
+ class="text-lightDropzoneIcon dark:text-darkDropzoneIcon hover:text-red-600 dark:hover:text-red-400
68
+ opacity-70 hover:opacity-100 transition-all"
69
+ :title="$t('Remove file')"
70
+ >
71
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
72
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
73
+ </svg>
74
+ </button>
33
75
  </div>
34
-
35
- <p v-if="!selectedFiles.length" class="mb-2 text-sm text-gray-500 dark:text-gray-400"><span class="font-semibold">{{ $t('Click to upload') }}</span> {{ $t('or drag and drop') }}</p>
36
- <p class="text-xs text-gray-500 dark:text-gray-400">
37
- {{ props.extensions.join(', ').toUpperCase().replace(/\./g, '') }}
38
- <template v-if="props.maxSizeBytes">
39
- (Max size: {{ humanifySize(props.maxSizeBytes) }})
40
- </template>
41
- </p>
76
+ </template>
42
77
  </div>
43
- <input :id="id" type="file" class="hidden"
44
- :accept="props.extensions.join(', ')"
45
- @change="doEmit($event.target.files)"
46
- :multiple="props.multiple || false"
47
- />
78
+
79
+ <p
80
+ v-if="!selectedFiles.length"
81
+ class="mb-2 text-sm text-lightDropzoneText dark:text-darkDropzoneText"
82
+ >
83
+ <span class="font-semibold">{{ $t('Click to upload') }}</span>
84
+ {{ $t('or drag and drop') }}
85
+ </p>
86
+
87
+ <p class="text-xs text-lightDropzoneText dark:text-darkDropzoneText">
88
+ {{ normalizedExtensions.join(', ').toUpperCase().replace(/\./g, '') }}
89
+ <template v-if="props.maxSizeBytes">
90
+ (Max size: {{ humanifySize(props.maxSizeBytes) }})
91
+ </template>
92
+ </p>
93
+ </div>
48
94
  </label>
49
- </form>
95
+ </form>
50
96
  </template>
51
97
 
52
98
  <script setup lang="ts">
53
99
  import { humanifySize } from '@/utils';
54
- import { ref, type Ref } from 'vue';
100
+ import { ref, type Ref, computed } from 'vue';
55
101
  import { IconFileSolid } from '@iconify-prerendered/vue-flowbite';
56
102
  import { watch } from 'vue';
57
103
  import adminforth from '@/adminforth';
@@ -67,25 +113,53 @@ const emit = defineEmits(['update:modelValue']);
67
113
 
68
114
  const id = `afcl-dropzone-${Math.random().toString(36).substring(7)}`;
69
115
 
116
+ const normalizedExtensions = computed(() => {
117
+ return props.extensions.map(ext => {
118
+ const trimmed = ext.trim().toLowerCase();
119
+ return trimmed.startsWith('.') ? trimmed : `.${trimmed}`;
120
+ });
121
+ });
122
+
70
123
  const selectedFiles: Ref<{
71
124
  name: string,
72
125
  size: number,
73
126
  mime: string,
74
127
  }[]> = ref([]);
75
128
 
129
+ const storedFiles: Ref<File[]> = ref([]);
130
+
131
+ function shortenFileName(name: string, maxLength = 24) {
132
+ if (name.length <= maxLength) return name;
133
+
134
+ const lastDotIndex = name.lastIndexOf('.');
135
+ const hasExtension = lastDotIndex > 0 && lastDotIndex < name.length - 1;
136
+ const extension = hasExtension ? name.slice(lastDotIndex + 1) : '';
137
+ const baseName = hasExtension ? name.slice(0, lastDotIndex) : name;
138
+
139
+ const startName = baseName.slice(0, 12);
140
+ const endName = baseName.split('').reverse().slice(0, 4).reverse().join('');
141
+ return hasExtension ? `${startName}...${endName}.${extension}` : `${startName}...${endName}`;
142
+ }
143
+
76
144
  watch(() => props.modelValue, (files) => {
77
- selectedFiles.value = Array.from(files).map(file => ({
78
- name: file.name,
79
- size: file.size,
80
- mime: file.type,
81
- }));
82
- });
145
+ if (files && files.length > 0) {
146
+ selectedFiles.value = Array.from(files).map(file => ({
147
+ name: file.name,
148
+ size: file.size,
149
+ mime: file.type,
150
+ }));
151
+ storedFiles.value = Array.from(files);
152
+ } else {
153
+ selectedFiles.value = [];
154
+ storedFiles.value = [];
155
+ }
156
+ }, { immediate: true });
83
157
 
84
158
  function doEmit(filesIn: FileList) {
85
159
 
86
160
  const multiple = props.multiple || false;
87
161
  const files = Array.from(filesIn);
88
- const allowedExtensions = props.extensions.map(ext => ext.toLowerCase());
162
+ const allowedExtensions = normalizedExtensions.value;
89
163
  const maxSizeBytes = props.maxSizeBytes;
90
164
 
91
165
  if (!files.length) return;
@@ -96,6 +170,18 @@ function doEmit(filesIn: FileList) {
96
170
  const extension = file.name.split('.').pop()?.toLowerCase() || '';
97
171
  const size = file.size;
98
172
 
173
+ const isDuplicate = storedFiles.value.some(
174
+ existingFile => existingFile.name === file.name && existingFile.size === file.size
175
+ );
176
+
177
+ if (isDuplicate) {
178
+ adminforth.alert({
179
+ message: `The file "${file.name}" is already selected.`,
180
+ variant: 'warning',
181
+ });
182
+ return;
183
+ }
184
+
99
185
  if (!allowedExtensions.includes(`.${extension}`)) {
100
186
  adminforth.alert({
101
187
  message: `Sorry, the file type .${extension} is not allowed. Please upload a file with one of the following extensions: ${allowedExtensions.join(', ')}`,
@@ -110,26 +196,37 @@ function doEmit(filesIn: FileList) {
110
196
  });
111
197
  return;
112
198
  }
113
-
199
+
114
200
  validFiles.push(file);
115
201
  });
116
202
 
117
203
  if (!multiple) {
118
- validFiles.splice(1);
204
+ storedFiles.value = validFiles.slice(0, 1);
205
+ } else {
206
+ storedFiles.value = [...storedFiles.value, ...validFiles];
119
207
  }
120
- selectedFiles.value = validFiles.map(file => ({
208
+
209
+ selectedFiles.value = storedFiles.value.map(file => ({
121
210
  name: file.name,
122
211
  size: file.size,
123
212
  mime: file.type,
124
213
  }));
125
214
 
126
- emit('update:modelValue', validFiles);
215
+ emit('update:modelValue', storedFiles.value);
216
+
127
217
  }
128
218
 
129
219
  const dragging = ref(false);
130
220
 
221
+ function removeFile(index: number) {
222
+ storedFiles.value = storedFiles.value.filter((_, i) => i !== index);
223
+ selectedFiles.value = selectedFiles.value.filter((_, i) => i !== index);
224
+ emit('update:modelValue', storedFiles.value);
225
+ }
226
+
131
227
  function clear() {
132
228
  selectedFiles.value = [];
229
+ storedFiles.value = [];
133
230
  emit('update:modelValue', []);
134
231
  const form = document.getElementById(id)?.closest('form');
135
232
  form?.reset();
@@ -3,7 +3,7 @@
3
3
  <div class="afcl-input-wrapper flex z-0 relative" :class="{'opacity-50' : readonly}">
4
4
  <span
5
5
  v-if="$slots.prefix || prefix"
6
- class="inline-flex items-center px-3 text-sm text-gray-900 bg-gray-200 border border-s-0 border-gray-300 rounded-s-md dark:bg-gray-600 dark:text-gray-400 dark:border-gray-600">
6
+ class="inline-flex items-center px-3 text-sm text-lightInputText bg-lightInputBackground border border-s-0 border-lightInputBorder rounded-s-md dark:bg-darkInputBackground dark:text-darkInputText dark:border-darkInputBorder">
7
7
  <slot name="prefix">{{ prefix }}</slot>
8
8
  </span>
9
9
 
@@ -12,12 +12,12 @@
12
12
  ref="input"
13
13
  v-bind="$attrs"
14
14
  :type="type"
15
- @input="$emit('update:modelValue', $event.target?.value)"
15
+ @input="$emit('update:modelValue', type === 'number' ? Number(($event.target as HTMLInputElement)?.value) : ($event.target as HTMLInputElement)?.value)"
16
16
  :value="modelValue"
17
17
  aria-describedby="helper-text-explanation"
18
- class="afcl-input inline-flex bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-0 focus:ring-lightPrimary focus:border-lightPrimary dark:focus:ring-darkPrimary dark:focus:border-darkPrimary
19
- blue-500 focus:border-blue-500 block w-20 p-2.5 dark:bg-gray-700 dark:border-gray-600 placeholder-gray-500 dark:placeholder-gray-400 dark:text-white translate-y-0"
20
- :class="{'rounded-l-md': !$slots.prefix && !prefix, 'rounded-r-md': !$slots.suffix && !suffix, 'w-full': fullWidth}"
18
+ class="afcl-input inline-flex bg-lightInputBackground border border-lightInputBorder rounded-0 focus:ring-lightPrimary focus:border-lightPrimary dark:focus:ring-darkPrimary dark:focus:border-darkPrimary
19
+ blue-500 focus:border-blue-500 block w-20 p-2.5 dark:bg-darkInputBackground dark:border-darkInputBorder placeholder-lightInputPlaceholderText dark:placeholder-darkInputPlaceholderText dark:text-darkInputText translate-y-0"
20
+ :class="{'rounded-l-md': !$slots.prefix && !prefix, 'rounded-r-md': !$slots.suffix && !suffix, 'w-full': fullWidth, 'text-base': isIOSDevice, 'text-sm': !isIOSDevice }"
21
21
  :disabled="readonly"
22
22
  >
23
23
 
@@ -26,7 +26,7 @@
26
26
  </div>
27
27
  <span
28
28
  v-if="$slots.suffix || suffix"
29
- class="inline-flex items-center px-3 text-sm text-gray-900 bg-gray-200 border border-s-0 border-gray-300 rounded-e-md dark:bg-gray-600 dark:text-gray-400 dark:border-gray-600 ">
29
+ class="inline-flex items-center px-3 text-sm text-lightInputText bg-lightInputBackground border border-s-0 border-lightInputBorder rounded-e-md dark:bg-darkInputBackground dark:text-darkInputText dark:border-darkInputBorder ">
30
30
  <slot name="suffix">{{ suffix }}</slot>
31
31
  </span>
32
32
 
@@ -36,6 +36,7 @@
36
36
  <script setup lang="ts">
37
37
 
38
38
  import { ref } from 'vue';
39
+ const isIOSDevice = isIOS();
39
40
 
40
41
  const props = defineProps<{
41
42
  type: string,
@@ -52,5 +53,12 @@ defineExpose({
52
53
  focus: () => input.value?.focus(),
53
54
  });
54
55
 
56
+ function isIOS() {
57
+ return (
58
+ /iPad|iPhone|iPod/.test(navigator.userAgent) ||
59
+ (navigator.userAgent.includes('Mac') && 'ontouchend' in document)
60
+ )
61
+ }
62
+
55
63
  </script>
56
64
 
@@ -0,0 +1,25 @@
1
+ <template>
2
+ <JsonViewer
3
+ class="afcl-json-viewer min-w-[6rem]"
4
+ :value="value"
5
+ :expandDepth="expandDepth"
6
+ copyable
7
+ sort
8
+ :theme="currentTheme"
9
+ />
10
+ </template>
11
+
12
+ <script setup lang="ts">
13
+ import { computed } from 'vue'
14
+ import { JsonViewer } from 'vue3-json-viewer'
15
+ import { useCoreStore } from '@/stores/core'
16
+
17
+ defineProps<{
18
+ value: any
19
+ expandDepth?: number
20
+ }>()
21
+
22
+ const coreStore = useCoreStore()
23
+
24
+ const currentTheme = computed(() => (coreStore.theme === 'dark' ? 'dark' : 'light'))
25
+ </script>
@@ -3,8 +3,8 @@
3
3
  v-bind="$attrs"
4
4
  :to="props.to"
5
5
  type="submit"
6
- class="afcl-link-button flex items-center justify-center gap-1 text-lightPrimaryContrast bg-lightPrimary dark:bg-darkPrimary hover:brightness-110
7
- focus:ring-4 focus:outline-none focus:ring-lightPrimary focus:ring-opacity-50 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:focus:ring-darkPrimary dark:focus:ring-opacity-50"
6
+ class="afcl-link-button flex items-center justify-center gap-1 text-lightButtonsText bg-lightButtonsBackground border border-lightButtonsBorder dark:bg-darkButtonsBackground hover:bg-lightButtonsHover hover:border-lightButtonsBorderHover
7
+ focus:ring-4 focus:outline-none focus:ring-lightButtonFocusRing focus:ring-opacity-50 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:focus:ring-darkButtonFocusRing dark:text-darkButtonsText dark:border-darkButtonsBorder dark:hover:bg-darkButtonsHover dark:hover:border-darkButtonsBorderHover"
8
8
  :class="{
9
9
  'cursor-default': props.disabled,
10
10
  'opacity-50': props.disabled,
@@ -18,7 +18,7 @@
18
18
  <script setup lang="ts">
19
19
 
20
20
  const props = defineProps<{
21
- disabled: boolean,
21
+ disabled?: boolean,
22
22
  to: string,
23
23
  }>();
24
24
 
@@ -63,8 +63,8 @@ const optionsBase = {
63
63
  show: false,
64
64
  fontFamily: "Inter, sans-serif",
65
65
  label: "",
66
- formatter: function (w) {
67
- const sum = w.globals.seriesTotals.reduce((a, b) => {
66
+ formatter: function (w: any) {
67
+ const sum = w.globals.seriesTotals.reduce((a: any, b: any) => {
68
68
  return a + b
69
69
  }, 0)
70
70
  return sum
@@ -74,7 +74,7 @@ const optionsBase = {
74
74
  show: true,
75
75
  fontFamily: "Inter, sans-serif",
76
76
  offsetY: -20,
77
- formatter: function (value) {
77
+ formatter: function (value: any) {
78
78
  return value + "k"
79
79
  },
80
80
  },
@@ -100,14 +100,14 @@ const optionsBase = {
100
100
  },
101
101
  yaxis: {
102
102
  labels: {
103
- formatter: function (value) {
103
+ formatter: function (value: any) {
104
104
  return value;
105
105
  },
106
106
  },
107
107
  },
108
108
  xaxis: {
109
109
  labels: {
110
- formatter: function (value) {
110
+ formatter: function (value: any) {
111
111
  return value;
112
112
  },
113
113
  },
@@ -1,14 +1,14 @@
1
1
  <template>
2
- <div class="relative mt-4 lg:mt-10 w-full max-w-[700px] bg-gray-200 rounded-full h-2.5 dark:bg-gray-700">
3
- <span class="absolute -top-6 left-0 text-sm text-gray-500">{{ leftLabel }}</span>
4
- <span class="absolute -top-6 right-0 text-sm text-gray-500">{{ rightLabel }}</span>
2
+ <div class="relative mt-4 lg:mt-10 w-full max-w-[700px] bg-lightProgressBarUnfilledColor rounded-full h-2.5 dark:bg-darkProgressBarUnfilledColor">
3
+ <span class="absolute -top-6 left-0 text-sm text-lightProgressBarText dark:text-darkProgressBarText">{{ leftLabel }}</span>
4
+ <span class="absolute -top-6 right-0 text-sm text-lightProgressBarText dark:text-darkProgressBarText">{{ rightLabel }}</span>
5
5
  <div
6
- class="bg-lightPrimary dark:bg-darkPrimary h-2.5 rounded-full transition-all duration-300 ease-in-out"
6
+ class="bg-lightProgressBarFilledColor dark:bg-darkProgressBarFilledColor h-2.5 rounded-full transition-all duration-300 ease-in-out"
7
7
  :style="{ width: `${percentage}%` }"
8
8
  ></div>
9
- <span v-if="showValues" class="absolute top-4 left-0 text-sm text-gray-500">{{ formatValue(minValue) }}</span>
10
- <span v-if="showProgress" class="absolute top-4 right-1/2 translate-x-1/2 text-sm text-gray-500">{{ progressText }}</span>
11
- <span v-if="showValues" class="absolute top-4 right-0 text-sm text-gray-500">{{ formatValue(maxValue) }}</span>
9
+ <span v-if="showValues" class="absolute top-4 left-0 text-sm text-lightProgressBarText dark:text-darkProgressBarText">{{ formatValue(minValue) }}</span>
10
+ <span v-if="showProgress" class="absolute top-4 right-1/2 translate-x-1/2 text-sm text-lightProgressBarText dark:text-darkProgressBarText">{{ progressText }}</span>
11
+ <span v-if="showValues" class="absolute top-4 right-0 text-sm text-lightProgressBarText dark:text-darkProgressBarText">{{ formatValue(maxValue) }}</span>
12
12
  </div>
13
13
  </template>
14
14