mirror of
https://github.com/Drop-OSS/drop.git
synced 2026-06-22 04:11:32 +10:00
* feat: support for file upload handler to track multiple files * feat: update image upload endpoint to allow multiple files * fix: lint
This commit is contained in:
@@ -51,15 +51,22 @@
|
|||||||
class="transition mt-2 block text-sm font-semibold text-zinc-400 group-hover:text-zinc-500"
|
class="transition mt-2 block text-sm font-semibold text-zinc-400 group-hover:text-zinc-500"
|
||||||
>Upload file</span
|
>Upload file</span
|
||||||
>
|
>
|
||||||
<p v-if="currentFile" class="mt-1 text-xs text-zinc-400">
|
<div v-if="currentFileList">
|
||||||
{{ currentFile.name }}
|
<p
|
||||||
</p>
|
v-for="currentFile in currentFileList"
|
||||||
|
:key="currentFile"
|
||||||
|
class="mt-1 text-[10px] text-zinc-500 whitespace-nowrap"
|
||||||
|
>
|
||||||
|
{{ currentFile }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
id="file-upload"
|
id="file-upload"
|
||||||
:accept="props.accept"
|
:accept="props.accept"
|
||||||
class="hidden"
|
class="hidden"
|
||||||
type="file"
|
type="file"
|
||||||
|
:multiple="props.multiple"
|
||||||
@change="(e) => (file = (e.target as any)?.files)"
|
@change="(e) => (file = (e.target as any)?.files)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -67,7 +74,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
|
<div class="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
|
||||||
<LoadingButton
|
<LoadingButton
|
||||||
:disabled="currentFile == undefined"
|
:disabled="currentFiles == undefined"
|
||||||
type="button"
|
type="button"
|
||||||
:loading="uploadLoading"
|
:loading="uploadLoading"
|
||||||
:class="['inline-flex w-full shadow-sm sm:ml-3 sm:w-auto']"
|
:class="['inline-flex w-full shadow-sm sm:ml-3 sm:w-auto']"
|
||||||
@@ -123,10 +130,19 @@ const open = defineModel<boolean>({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const file = ref<FileList | undefined>();
|
const file = ref<FileList | undefined>();
|
||||||
const currentFile = computed(() => file.value?.item(0));
|
const currentFiles = computed(() => file.value);
|
||||||
|
const currentFileList = computed(() => {
|
||||||
|
if (!currentFiles.value) return undefined;
|
||||||
|
const list = [];
|
||||||
|
for (const file of currentFiles.value) {
|
||||||
|
list.push(file.name);
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
});
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
endpoint: string;
|
endpoint: string;
|
||||||
accept: string;
|
accept: string;
|
||||||
|
multiple?: boolean;
|
||||||
options?: { [key: string]: string };
|
options?: { [key: string]: string };
|
||||||
}>();
|
}>();
|
||||||
const emit = defineEmits(["upload"]);
|
const emit = defineEmits(["upload"]);
|
||||||
@@ -134,10 +150,12 @@ const emit = defineEmits(["upload"]);
|
|||||||
const uploadLoading = ref(false);
|
const uploadLoading = ref(false);
|
||||||
const uploadError = ref<string | undefined>();
|
const uploadError = ref<string | undefined>();
|
||||||
async function uploadFile() {
|
async function uploadFile() {
|
||||||
if (!currentFile.value) return;
|
if (!currentFiles.value) return;
|
||||||
|
|
||||||
const form = new FormData();
|
const form = new FormData();
|
||||||
form.append("file", currentFile.value);
|
for (const file of currentFiles.value) {
|
||||||
|
form.append(file.name, file);
|
||||||
|
}
|
||||||
|
|
||||||
if (props.options) {
|
if (props.options) {
|
||||||
for (const [key, value] of Object.entries(props.options)) {
|
for (const [key, value] of Object.entries(props.options)) {
|
||||||
|
|||||||
@@ -293,6 +293,7 @@
|
|||||||
:options="{ id: game.id }"
|
:options="{ id: game.id }"
|
||||||
accept="image/*"
|
accept="image/*"
|
||||||
endpoint="/api/v1/admin/game/image"
|
endpoint="/api/v1/admin/game/image"
|
||||||
|
:multiple="true"
|
||||||
@upload="(result: Game) => uploadAfterImageUpload(result)"
|
@upload="(result: Game) => uploadAfterImageUpload(result)"
|
||||||
/>
|
/>
|
||||||
<ModalTemplate v-model="showAddCarouselModal">
|
<ModalTemplate v-model="showAddCarouselModal">
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ export default defineEventHandler(async (h3) => {
|
|||||||
statusMessage: "Failed to upload file",
|
statusMessage: "Failed to upload file",
|
||||||
});
|
});
|
||||||
|
|
||||||
const [id, options, pull, dump] = uploadResult;
|
const [ids, options, pull, dump] = uploadResult;
|
||||||
if (!id) {
|
if (ids.length == 0) {
|
||||||
dump();
|
dump();
|
||||||
throw createError({
|
throw createError({
|
||||||
statusCode: 400,
|
statusCode: 400,
|
||||||
@@ -48,7 +48,7 @@ export default defineEventHandler(async (h3) => {
|
|||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
mImageLibraryObjectIds: {
|
mImageLibraryObjectIds: {
|
||||||
push: id,
|
push: ids,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -14,14 +14,16 @@ export default defineEventHandler(async (h3) => {
|
|||||||
statusMessage: "This endpoint requires multipart form data.",
|
statusMessage: "This endpoint requires multipart form data.",
|
||||||
});
|
});
|
||||||
|
|
||||||
const uploadResult = await handleFileUpload(h3, {}, ["internal:read"]);
|
const uploadResult = await handleFileUpload(h3, {}, ["internal:read"], 1);
|
||||||
if (!uploadResult)
|
if (!uploadResult)
|
||||||
throw createError({
|
throw createError({
|
||||||
statusCode: 400,
|
statusCode: 400,
|
||||||
statusMessage: "Failed to upload file",
|
statusMessage: "Failed to upload file",
|
||||||
});
|
});
|
||||||
|
|
||||||
const [id, options, pull, dump] = uploadResult;
|
const [ids, options, pull, dump] = uploadResult;
|
||||||
|
|
||||||
|
const id = ids.at(0);
|
||||||
|
|
||||||
// handleFileUpload reads the rest of the options for us.
|
// handleFileUpload reads the rest of the options for us.
|
||||||
const name = options.name;
|
const name = options.name;
|
||||||
|
|||||||
@@ -14,19 +14,20 @@ export default defineEventHandler(async (h3) => {
|
|||||||
statusMessage: "This endpoint requires multipart form data.",
|
statusMessage: "This endpoint requires multipart form data.",
|
||||||
});
|
});
|
||||||
|
|
||||||
const uploadResult = await handleFileUpload(h3, {}, ["internal:read"]);
|
const uploadResult = await handleFileUpload(h3, {}, ["internal:read"], 1);
|
||||||
if (!uploadResult)
|
if (!uploadResult)
|
||||||
throw createError({
|
throw createError({
|
||||||
statusCode: 400,
|
statusCode: 400,
|
||||||
statusMessage: "Failed to upload file",
|
statusMessage: "Failed to upload file",
|
||||||
});
|
});
|
||||||
|
|
||||||
const [imageId, options, pull, _dump] = uploadResult;
|
const [imageIds, options, pull, _dump] = uploadResult;
|
||||||
|
|
||||||
const title = options.title;
|
const title = options.title;
|
||||||
const description = options.description;
|
const description = options.description;
|
||||||
const content = options.content;
|
const content = options.content;
|
||||||
const tags = options.tags ? (JSON.parse(options.tags) as string[]) : [];
|
const tags = options.tags ? (JSON.parse(options.tags) as string[]) : [];
|
||||||
|
const imageId = imageIds.at(0);
|
||||||
|
|
||||||
if (!title || !description || !content)
|
if (!title || !description || !content)
|
||||||
throw createError({
|
throw createError({
|
||||||
|
|||||||
@@ -6,23 +6,21 @@ export async function handleFileUpload(
|
|||||||
h3: H3Event<EventHandlerRequest>,
|
h3: H3Event<EventHandlerRequest>,
|
||||||
metadata: { [key: string]: string },
|
metadata: { [key: string]: string },
|
||||||
permissions: Array<string>,
|
permissions: Array<string>,
|
||||||
): Promise<
|
max = -1,
|
||||||
[string | undefined, { [key: string]: string }, Pull, Dump] | undefined
|
): Promise<[string[], { [key: string]: string }, Pull, Dump] | undefined> {
|
||||||
> {
|
|
||||||
const formData = await readMultipartFormData(h3);
|
const formData = await readMultipartFormData(h3);
|
||||||
if (!formData) return undefined;
|
if (!formData) return undefined;
|
||||||
const transactionalHandler = new ObjectTransactionalHandler();
|
const transactionalHandler = new ObjectTransactionalHandler();
|
||||||
const [add, pull, dump] = transactionalHandler.new(metadata, permissions);
|
const [add, pull, dump] = transactionalHandler.new(metadata, permissions);
|
||||||
const options: { [key: string]: string } = {};
|
const options: { [key: string]: string } = {};
|
||||||
let id;
|
const ids = [];
|
||||||
|
|
||||||
for (const entry of formData) {
|
for (const entry of formData) {
|
||||||
if (entry.filename) {
|
if (entry.filename) {
|
||||||
// Only pick one file
|
if (max > 0 && ids.length >= max) continue;
|
||||||
if (id) continue;
|
|
||||||
|
|
||||||
// Add file to transaction handler so we can void it later if we error out
|
// Add file to transaction handler so we can void it later if we error out
|
||||||
id = add(entry.data);
|
ids.push(add(entry.data));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!entry.name) continue;
|
if (!entry.name) continue;
|
||||||
@@ -30,5 +28,5 @@ export async function handleFileUpload(
|
|||||||
options[entry.name] = entry.data.toString("utf-8");
|
options[entry.name] = entry.data.toString("utf-8");
|
||||||
}
|
}
|
||||||
|
|
||||||
return [id, options, pull, dump];
|
return [ids, options, pull, dump];
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user