Skip to content

Commit 9ec41af

Browse files
committed
fix: enhance validation for upload plugins and improve modal layout in Vision components
1 parent 5766ea9 commit 9ec41af

File tree

4 files changed

+72
-100
lines changed

4 files changed

+72
-100
lines changed

custom/ImageGenerationCarousel.vue

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11

22
<template>
33
<!-- Main modal -->
4-
<div tabindex="-1" class="overflow-y-auto overflow-x-hidden fixed top-0 right-0 left-0 bottom-0 z-10 flex justify-center items-center w-full md:inset-0 h-full max-h-full bg-black/50 dark:bg-gray-900 dark:bg-opacity-50">
5-
<div class="relative p-4 w-10/12 max-w-full max-h-full ">
4+
<div tabindex="-1" class="fixed inset-0 z-10 flex justify-center items-center dark:bg-gray-900/50 overflow-y-auto">
5+
<div class="relative p-4 w-full max-w-[1600px] max-h-[90vh] ">
66
<!-- Modal content -->
77
<div class="relative bg-white rounded-lg shadow-xl dark:bg-gray-700">
88
<!-- Modal header -->
@@ -95,20 +95,20 @@
9595
</div>
9696

9797

98-
<div id="gallery" class="relative w-full" data-carousel="static">
98+
<div id="gallery" class="relative w-full min-w-0" data-carousel="static">
9999
<!-- Carousel wrapper -->
100100
<div class="relative h-56 overflow-hidden rounded-lg md:h-[calc(100vh-400px)]">
101101
<!-- Item 1 -->
102102
<div
103103
v-for="(img, index) in images"
104104
:key="index"
105+
class="flex items-center justify-center w-full h-full"
105106
:class="[
106-
index === 0 ? 'block' : 'hidden',
107-
'duration-700 ease-in-out'
107+
index === 0 ? 'block' : 'hidden'
108108
]"
109109
data-carousel-item
110110
>
111-
<img :src="img" class="absolute block max-w-full max-h-full -translate-x-1/2 -translate-y-1/2 top-1/2 left-1/2 object-cover"
111+
<img :src="img" class="max-w-full max-h-full object-contain"
112112
:alt="`Generated image ${index + 1}`"
113113
/>
114114
</div>

custom/VisionAction.vue

Lines changed: 30 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -5,59 +5,36 @@
55
</div>
66
<p class="text-justify max-h-[18px] truncate max-w-[60vw] md:max-w-none">{{ props.meta.actionName }}</p>
77
</div>
8-
<Dialog ref="confirmDialog">
9-
<div
10-
class="fixed inset-0 z-20 flex items-center justify-center bg-black/40"
11-
>
12-
<div
13-
class="bulk-vision-dialog flex items-center justify-center relative w-[100vw] h-[100vh] max-h-[100vh] md:w-auto md:max-w-[95vw] md:min-w-[640px] md:h-auto md:max-h-[90vh] bg-white dark:bg-gray-900 rounded-none md:rounded-md shadow-2xl overflow-hidden"
14-
>
15-
<div class="bulk-vision-table flex flex-col items-center justify-evenly md:max-h-[90vh] gap-3 md:gap-4 w-full h-full p-4 md:p-6 overflow-y-auto overflow-x-auto">
16-
<button type="button"
17-
@click="closeDialog"
18-
class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:hover:bg-gray-600 dark:hover:text-white" >
19-
<svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
20-
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
21-
</svg>
22-
</button>
23-
24-
<div v-if="records && props.checkboxes.length" class="w-full overflow-x-auto">
25-
<VisionTable
26-
:checkbox="props.checkboxes"
27-
:records="records"
28-
:meta="props.meta"
29-
:images="images"
30-
:tableHeaders="tableHeaders"
31-
:tableColumns="tableColumns"
32-
:customFieldNames="customFieldNames"
33-
:tableColumnsIndexes="tableColumnsIndexes"
34-
:selected="selected"
35-
:isAiResponseReceivedAnalize="isAiResponseReceivedAnalize"
36-
:isAiResponseReceivedImage="isAiResponseReceivedImage"
37-
:primaryKey="primaryKey"
38-
:openGenerationCarousel="openGenerationCarousel"
39-
@error="handleTableError"
40-
/>
41-
</div>
42-
<div class="flex w-full flex-col md:flex-row items-stretch md:items-end justify-end gap-3 md:gap-4">
43-
<div class="h-full text-red-600 font-semibold flex items-center justify-center md:mb-2">
44-
<p v-if="isError === true">{{ errorMessage }}</p>
45-
</div>
46-
<button type="button" class="w-full md:w-auto py-2.5 px-5 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded-lg border border-gray-200 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"
47-
@click="closeDialog"
48-
>
49-
{{'Cancel'}}
50-
</button>
51-
<Button
52-
class="w-full md:w-64"
53-
@click="saveData"
54-
:disabled="isLoading || checkedCount < 1 || isCriticalError"
55-
:loader="isLoading"
56-
>
57-
{{ checkedCount > 1 ? 'Save fields' : 'Save field' }}
58-
</Button>
59-
</div>
60-
</div>
8+
<Dialog
9+
ref="confirmDialog"
10+
header="Bulk AI Flow"
11+
class="!max-w-full w-full lg:w-[1600px] !lg:max-w-[1600px]"
12+
:buttons="[
13+
{ label: checkedCount > 1 ? 'Save fields' : 'Save field', options: { disabled: isLoading || checkedCount < 1 || isCriticalError, loader: isLoading, class: 'w-fit sm:w-40' }, onclick: (dialog) => { saveData(); dialog.hide(); } },
14+
{ label: 'Cancel', onclick: (dialog) => dialog.hide() },
15+
]"
16+
>
17+
<div class="bulk-vision-table flex flex-col items-center max-w-[1560px] md:max-h-[90vh] gap-3 md:gap-4 w-full h-full overflow-y-auto">
18+
<div v-if="records && props.checkboxes.length" class="w-full overflow-x-auto">
19+
<VisionTable
20+
:checkbox="props.checkboxes"
21+
:records="records"
22+
:meta="props.meta"
23+
:images="images"
24+
:tableHeaders="tableHeaders"
25+
:tableColumns="tableColumns"
26+
:customFieldNames="customFieldNames"
27+
:tableColumnsIndexes="tableColumnsIndexes"
28+
:selected="selected"
29+
:isAiResponseReceivedAnalize="isAiResponseReceivedAnalize"
30+
:isAiResponseReceivedImage="isAiResponseReceivedImage"
31+
:primaryKey="primaryKey"
32+
:openGenerationCarousel="openGenerationCarousel"
33+
@error="handleTableError"
34+
/>
35+
</div>
36+
<div class="text-red-600 flex items-center w-full">
37+
<p v-if="isError === true">{{ errorMessage }}</p>
6138
</div>
6239
</div>
6340
</Dialog>
@@ -157,21 +134,6 @@ watch(selected, (val) => {
157134
checkedCount.value = val.filter(item => item.isChecked === true).length;
158135
}, { deep: true });
159136
160-
const closeDialog = () => {
161-
confirmDialog.value.close();
162-
isAiResponseReceivedAnalize.value = [];
163-
isAiResponseReceivedImage.value = [];
164-
165-
records.value = [];
166-
images.value = [];
167-
selected.value = [];
168-
tableColumns.value = [];
169-
tableColumnsIndexes.value = [];
170-
isError.value = false;
171-
isCriticalError.value = false;
172-
isImageGenerationError.value = false;
173-
errorMessage.value = '';
174-
}
175137
176138
function formatLabel(str) {
177139
return str

custom/VisionTable.vue

Lines changed: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
<template>
2-
<div>
32
<Table
43
:columns="tableHeaders"
54
:data="tableColumns"
6-
:pageSize="8"
5+
:pageSize="6"
76
>
87
<!-- HEADER TEMPLATE -->
98
<template #header:checkboxes="{ item }">
@@ -28,18 +27,22 @@
2827
@click="zoomImage(image)"
2928
/>
3029
</div>
30+
</div>
31+
<transition name="fade">
3132
<div
3233
v-if="zoomedImage"
3334
class="fixed inset-0 z-50 flex items-center justify-center bg-black/60"
3435
@click.self="closeZoom"
3536
>
36-
<img
37-
:src="zoomedImage"
38-
ref="zoomedImg"
39-
class="max-w-full max-h-full rounded-lg object-contain cursor-grab z-75"
40-
/>
37+
<transition name="zoom">
38+
<img
39+
v-if="zoomedImage"
40+
:src="zoomedImage"
41+
class="max-w-full max-h-full rounded-lg object-contain cursor-grab z-75"
42+
/>
43+
</transition>
4144
</div>
42-
</div>
45+
</transition>
4346
</div>
4447
</template>
4548
<!-- CUSTOM FIELD TEMPLATES -->
@@ -112,12 +115,10 @@
112115
</div>
113116
</template>
114117
</Table>
115-
</div>
116118
</template>
117119

118120
<script lang="ts" setup>
119-
import { ref, nextTick, watch } from 'vue'
120-
import mediumZoom from 'medium-zoom'
121+
import { ref } from 'vue'
121122
import { Select, Input, Textarea, Table, Checkbox, Skeleton, Toggle } from '@/afcl'
122123
import GenerationCarousel from './ImageGenerationCarousel.vue'
123124
@@ -139,7 +140,6 @@ const emit = defineEmits(['error']);
139140
140141
141142
const zoomedImage = ref(null)
142-
const zoomedImg = ref(null)
143143
144144
145145
function zoomImage(img) {
@@ -150,17 +150,6 @@ function closeZoom() {
150150
zoomedImage.value = null
151151
}
152152
153-
watch(zoomedImage, async (val) => {
154-
await nextTick()
155-
if (val && zoomedImg.value) {
156-
mediumZoom(zoomedImg.value, {
157-
margin: 24,
158-
background: 'rgba(0, 0, 0, 0.9)',
159-
scrollOffset: 150
160-
}).show()
161-
}
162-
})
163-
164153
function isInColumnEnum(key: string): boolean {
165154
const colEnum = props.meta.columnEnums?.find(c => c.name === key);
166155
if (!colEnum) {
@@ -193,4 +182,21 @@ function handleError({ isError, errorMessage }) {
193182
});
194183
}
195184
196-
</script>
185+
</script>
186+
187+
<style scoped>
188+
.fade-enter-active, .fade-leave-active {
189+
transition: opacity 0.2s ease;
190+
}
191+
.fade-enter-from, .fade-leave-to {
192+
opacity: 0;
193+
}
194+
195+
.zoom-enter-active, .zoom-leave-active {
196+
transition: transform 0.2s ease, opacity 0.2s ease;
197+
}
198+
.zoom-enter-from, .zoom-leave-to {
199+
transform: scale(0.95);
200+
opacity: 0;
201+
}
202+
</style>

index.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,9 +112,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
112112
p.resourceConfig!.resourceId === this.resourceConfig.resourceId &&
113113
p.pluginOptions.pathColumnName === key
114114
);
115-
if (plugin && plugin.pluginOptions.storageAdapter.objectCanBeAccesedPublicly()) {
116115
outputImagesPluginInstanceIds[key] = plugin.pluginInstanceId;
117-
}
118116
}
119117
}
120118

@@ -203,6 +201,12 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
203201
if (!plugin) {
204202
throw new Error(`Plugin for attachment field '${key}' not found in resource '${this.resourceConfig.resourceId}', please check if Upload Plugin is installed on the field ${key}`);
205203
}
204+
if (!plugin.pluginOptions || !plugin.pluginOptions.storageAdapter) {
205+
throw new Error(`Upload Plugin for attachment field '${key}' in resource '${this.resourceConfig.resourceId}' is missing a storageAdapter configuration.`);
206+
}
207+
if (typeof plugin.pluginOptions.storageAdapter.objectCanBeAccesedPublicly !== 'function') {
208+
throw new Error(`Upload Plugin for attachment field '${key}' in resource '${this.resourceConfig.resourceId}' uses a storage adapter without 'objectCanBeAccesedPublicly' method.`);
209+
}
206210
if (!plugin.pluginOptions.storageAdapter.objectCanBeAccesedPublicly()) {
207211
throw new Error(`Upload Plugin for attachment field '${key}' in resource '${this.resourceConfig.resourceId}'
208212
uses adapter which is not configured to store objects in public way, so it will produce only signed private URLs which can not be used in HTML text of blog posts.

0 commit comments

Comments
 (0)