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.
- package/commands/callTsProxy.js +14 -4
- package/commands/createApp/templates/api.ts.hbs +10 -0
- package/commands/createApp/templates/custom/tsconfig.json.hbs +2 -3
- package/commands/createApp/templates/index.ts.hbs +12 -1
- package/commands/createApp/templates/package.json.hbs +1 -1
- package/commands/createApp/templates/prisma.config.ts.hbs +8 -0
- package/commands/createApp/templates/schema.prisma.hbs +0 -1
- package/commands/createApp/utils.js +10 -0
- package/commands/createCustomComponent/configLoader.js +17 -4
- package/commands/createCustomComponent/main.js +13 -7
- package/commands/createCustomComponent/templates/customCrud/beforeActionButtons.vue.hbs +38 -0
- package/commands/createCustomComponent/templates/customCrud/saveButton.vue.hbs +28 -0
- package/commands/createPlugin/templates/custom/tsconfig.json.hbs +2 -5
- package/commands/createPlugin/templates/package.json.hbs +1 -1
- package/commands/generateModels.js +30 -22
- package/dist/auth.d.ts +9 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +21 -2
- package/dist/auth.js.map +1 -1
- package/dist/dataConnectors/baseConnector.d.ts +1 -1
- package/dist/dataConnectors/baseConnector.d.ts.map +1 -1
- package/dist/dataConnectors/baseConnector.js +70 -18
- package/dist/dataConnectors/baseConnector.js.map +1 -1
- package/dist/dataConnectors/clickhouse.d.ts.map +1 -1
- package/dist/dataConnectors/clickhouse.js +15 -0
- package/dist/dataConnectors/clickhouse.js.map +1 -1
- package/dist/dataConnectors/mongo.d.ts.map +1 -1
- package/dist/dataConnectors/mongo.js +50 -15
- package/dist/dataConnectors/mongo.js.map +1 -1
- package/dist/dataConnectors/mysql.d.ts.map +1 -1
- package/dist/dataConnectors/mysql.js +11 -0
- package/dist/dataConnectors/mysql.js.map +1 -1
- package/dist/dataConnectors/postgres.d.ts.map +1 -1
- package/dist/dataConnectors/postgres.js +43 -14
- package/dist/dataConnectors/postgres.js.map +1 -1
- package/dist/dataConnectors/sqlite.d.ts.map +1 -1
- package/dist/dataConnectors/sqlite.js +11 -0
- package/dist/dataConnectors/sqlite.js.map +1 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +44 -21
- package/dist/index.js.map +1 -1
- package/dist/modules/codeInjector.d.ts +2 -0
- package/dist/modules/codeInjector.d.ts.map +1 -1
- package/dist/modules/codeInjector.js +62 -6
- package/dist/modules/codeInjector.js.map +1 -1
- package/dist/modules/configValidator.d.ts +6 -0
- package/dist/modules/configValidator.d.ts.map +1 -1
- package/dist/modules/configValidator.js +209 -25
- package/dist/modules/configValidator.js.map +1 -1
- package/dist/modules/restApi.d.ts +1 -1
- package/dist/modules/restApi.d.ts.map +1 -1
- package/dist/modules/restApi.js +199 -31
- package/dist/modules/restApi.js.map +1 -1
- package/dist/modules/styles.d.ts +499 -13
- package/dist/modules/styles.d.ts.map +1 -1
- package/dist/modules/styles.js +555 -31
- package/dist/modules/styles.js.map +1 -1
- package/dist/modules/utils.d.ts +7 -15
- package/dist/modules/utils.d.ts.map +1 -1
- package/dist/modules/utils.js +45 -68
- package/dist/modules/utils.js.map +1 -1
- package/dist/servers/express.d.ts +5 -0
- package/dist/servers/express.d.ts.map +1 -1
- package/dist/servers/express.js +40 -1
- package/dist/servers/express.js.map +1 -1
- package/dist/spa/index.html +1 -1
- package/dist/spa/package-lock.json +1208 -708
- package/dist/spa/package.json +34 -34
- package/dist/spa/src/App.vue +132 -174
- package/dist/spa/src/adminforth.ts +41 -17
- package/dist/spa/src/afcl/AreaChart.vue +0 -1
- package/dist/spa/src/afcl/BarChart.vue +2 -2
- package/dist/spa/src/afcl/Button.vue +3 -3
- package/dist/spa/src/afcl/ButtonGroup.vue +91 -0
- package/dist/spa/src/afcl/Card.vue +25 -0
- package/dist/spa/src/afcl/Checkbox.vue +21 -13
- package/dist/spa/src/afcl/CountryFlag.vue +4 -1
- package/dist/spa/src/{components/CustomDatePicker.vue → afcl/DatePicker.vue} +95 -9
- package/dist/spa/src/afcl/Dialog.vue +47 -27
- package/dist/spa/src/afcl/Dropzone.vue +145 -48
- package/dist/spa/src/afcl/Input.vue +14 -6
- package/dist/spa/src/afcl/JsonViewer.vue +25 -0
- package/dist/spa/src/afcl/LinkButton.vue +3 -3
- package/dist/spa/src/afcl/PieChart.vue +5 -5
- package/dist/spa/src/afcl/ProgressBar.vue +7 -7
- package/dist/spa/src/afcl/Select.vue +82 -34
- package/dist/spa/src/afcl/Skeleton.vue +6 -6
- package/dist/spa/src/afcl/Table.vue +313 -75
- package/dist/spa/src/afcl/Textarea.vue +31 -0
- package/dist/spa/src/afcl/Toggle.vue +32 -0
- package/dist/spa/src/afcl/Tooltip.vue +28 -18
- package/dist/spa/src/afcl/VerticalTabs.vue +21 -7
- package/dist/spa/src/afcl/index.ts +6 -3
- package/dist/spa/src/components/AcceptModal.vue +48 -14
- package/dist/spa/src/components/Breadcrumbs.vue +5 -5
- package/dist/spa/src/components/CallActionWrapper.vue +15 -0
- package/dist/spa/src/components/ColumnValueInput.vue +38 -18
- package/dist/spa/src/components/ColumnValueInputWrapper.vue +4 -3
- package/dist/spa/src/components/CustomDateRangePicker.vue +9 -8
- package/dist/spa/src/components/CustomRangePicker.vue +37 -21
- package/dist/spa/src/components/ErrorMessage.vue +21 -0
- package/dist/spa/src/components/Filters.vue +195 -132
- package/dist/spa/src/components/GroupsTable.vue +9 -8
- package/dist/spa/src/components/MenuLink.vue +95 -23
- package/dist/spa/src/components/ResourceForm.vue +99 -51
- package/dist/spa/src/components/ResourceListTable.vue +121 -95
- package/dist/spa/src/components/ResourceListTableVirtual.vue +119 -88
- package/dist/spa/src/components/ShowTable.vue +21 -15
- package/dist/spa/src/components/Sidebar.vue +472 -0
- package/dist/spa/src/components/SingleSkeletLoader.vue +6 -6
- package/dist/spa/src/components/SkeleteLoader.vue +3 -3
- package/dist/spa/src/components/ThreeDotsMenu.vue +84 -15
- package/dist/spa/src/components/Toast.vue +40 -29
- package/dist/spa/src/components/UserMenuSettingsButton.vue +69 -0
- package/dist/spa/src/components/ValueRenderer.vue +44 -17
- package/dist/spa/src/controls/BoolToggle.vue +34 -0
- package/dist/spa/src/i18n.ts +5 -3
- package/dist/spa/src/main.ts +1 -1
- package/dist/spa/src/renderers/CompactField.vue +1 -1
- package/dist/spa/src/renderers/CompactUUID.vue +1 -1
- package/dist/spa/src/router/index.ts +8 -0
- package/dist/spa/src/shims-vue.d.ts +5 -0
- package/dist/spa/src/spa_types/core.ts +13 -1
- package/dist/spa/src/stores/core.ts +15 -1
- package/dist/spa/src/stores/filters.ts +33 -2
- package/dist/spa/src/stores/modal.ts +6 -1
- package/dist/spa/src/stores/toast.ts +22 -3
- package/dist/spa/src/types/Back.ts +168 -23
- package/dist/spa/src/types/Common.ts +109 -32
- package/dist/spa/src/types/FrontendAPI.ts +32 -23
- package/dist/spa/src/types/adapters/CaptchaAdapter.ts +34 -0
- package/dist/spa/src/types/adapters/EmailAdapter.ts +2 -4
- package/dist/spa/src/types/adapters/ImageVisionAdapter.ts +30 -0
- package/dist/spa/src/types/adapters/KeyValueAdapter.ts +16 -0
- package/dist/spa/src/types/adapters/StorageAdapter.ts +4 -2
- package/dist/spa/src/types/adapters/index.ts +3 -0
- package/dist/spa/src/utils.ts +291 -11
- package/dist/spa/src/views/CreateView.vue +88 -22
- package/dist/spa/src/views/EditView.vue +55 -22
- package/dist/spa/src/views/ListView.vue +144 -87
- package/dist/spa/src/views/LoginView.vue +26 -35
- package/dist/spa/src/views/ResourceParent.vue +2 -2
- package/dist/spa/src/views/SettingsView.vue +121 -0
- package/dist/spa/src/views/ShowView.vue +83 -53
- package/dist/spa/src/websocket.ts +6 -1
- package/dist/spa/tsconfig.app.json +1 -1
- package/dist/spa/vite.config.ts +45 -2
- package/dist/types/Back.d.ts +151 -14
- package/dist/types/Back.d.ts.map +1 -1
- package/dist/types/Back.js +15 -0
- package/dist/types/Back.js.map +1 -1
- package/dist/types/Common.d.ts +123 -29
- package/dist/types/Common.d.ts.map +1 -1
- package/dist/types/Common.js.map +1 -1
- package/dist/types/FrontendAPI.d.ts +32 -18
- package/dist/types/FrontendAPI.d.ts.map +1 -1
- package/dist/types/FrontendAPI.js.map +1 -1
- package/dist/types/adapters/CaptchaAdapter.d.ts +30 -0
- package/dist/types/adapters/CaptchaAdapter.d.ts.map +1 -0
- package/dist/types/adapters/CaptchaAdapter.js +5 -0
- package/dist/types/adapters/CaptchaAdapter.js.map +1 -0
- package/dist/types/adapters/EmailAdapter.d.ts +2 -3
- package/dist/types/adapters/EmailAdapter.d.ts.map +1 -1
- package/dist/types/adapters/ImageVisionAdapter.d.ts +25 -0
- package/dist/types/adapters/ImageVisionAdapter.d.ts.map +1 -0
- package/dist/types/adapters/ImageVisionAdapter.js +2 -0
- package/dist/types/adapters/ImageVisionAdapter.js.map +1 -0
- package/dist/types/adapters/KeyValueAdapter.d.ts +10 -0
- package/dist/types/adapters/KeyValueAdapter.d.ts.map +1 -0
- package/dist/types/adapters/KeyValueAdapter.js +2 -0
- package/dist/types/adapters/KeyValueAdapter.js.map +1 -0
- package/dist/types/adapters/StorageAdapter.d.ts +2 -0
- package/dist/types/adapters/StorageAdapter.d.ts.map +1 -1
- package/dist/types/adapters/index.d.ts +3 -0
- package/dist/types/adapters/index.d.ts.map +1 -1
- package/package.json +4 -2
|
@@ -1,57 +1,103 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<!-- tag form used to reset the input (method .reset() in
|
|
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
|
|
9
|
-
hover:bg-
|
|
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-
|
|
12
|
-
'border-
|
|
13
|
-
'bg-
|
|
14
|
-
'bg-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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 =
|
|
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.
|
|
204
|
+
storedFiles.value = validFiles.slice(0, 1);
|
|
205
|
+
} else {
|
|
206
|
+
storedFiles.value = [...storedFiles.value, ...validFiles];
|
|
119
207
|
}
|
|
120
|
-
|
|
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',
|
|
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-
|
|
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
|
|
19
|
-
blue-500 focus:border-blue-500 block w-20 p-2.5 dark:bg-
|
|
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-
|
|
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-
|
|
7
|
-
focus:ring-4 focus:outline-none focus:ring-
|
|
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
|
|
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-
|
|
3
|
-
<span class="absolute -top-6 left-0 text-sm text-
|
|
4
|
-
<span class="absolute -top-6 right-0 text-sm text-
|
|
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-
|
|
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-
|
|
10
|
-
<span v-if="showProgress" class="absolute top-4 right-1/2 translate-x-1/2 text-sm text-
|
|
11
|
-
<span v-if="showValues" class="absolute top-4 right-0 text-sm text-
|
|
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
|
|