added title and description upload support

This commit is contained in:
2023-05-08 17:27:01 +10:00
parent 5d663d21b3
commit ff040eec58

View File

@@ -1,7 +1,12 @@
<template> <template>
<SMFormCard full class="dialog-media"> <SMFormCard full class="dialog-media">
<SMLoading v-if="progressText" overlay :text="progressText" /> <SMLoading v-if="progressText" overlay :text="progressText" />
<template #header>
<h3>Insert Media</h3> <h3>Insert Media</h3>
</template>
<template #body>
<SMTabGroup v-model="selectedTab">
<SMTab id="tab-browser" label="Media Browser">
<SMToolbar> <SMToolbar>
<SMGroupButtons <SMGroupButtons
:buttons="[ :buttons="[
@@ -21,6 +26,7 @@
label="Search" label="Search"
class="toolbar-search" class="toolbar-search"
size="small" size="small"
no-help
@keyup.enter="handleSearch" @keyup.enter="handleSearch"
@blur="handleSearch"> @blur="handleSearch">
<template #append> <template #append>
@@ -56,7 +62,9 @@
)}')`, )}')`,
}" }"
class="media-image"></div> class="media-image"></div>
<span class="media-title">{{ item.title }}</span> <span class="media-title">{{
item.title
}}</span>
</li> </li>
</ul> </ul>
</div> </div>
@@ -67,8 +75,50 @@
:total="totalItems" :total="totalItems"
:per-page="perPage" :per-page="perPage"
size="small" size="small"
class="mt-1" /> class="my-0" />
</SMRow> </SMRow>
</SMTab>
<SMTab id="tab-upload" label="Upload">
<SMForm v-model="uploadForm">
<SMFormError v-model="uploadForm" />
<SMRow>
<SMColumn width="250px">
<div
class="upload-preview mb-4"
:style="{
backgroundImage: `url(${uploadPreview})`,
}"></div>
<SMButton
v-if="props.allowUpload"
type="primary"
label="Select File"
@click="handleClickSelectFile" />
</SMColumn>
<SMColumn>
<SMInput
label="Title"
control="title"
:disabled="uploadPreview.length == 0" />
<SMInput
type="textarea"
label="Description"
control="description"
:disabled="uploadPreview.length == 0" />
</SMColumn>
</SMRow>
</SMForm>
<input
v-if="props.allowUpload"
id="file"
ref="refUploadInput"
type="file"
style="display: none"
:accept="computedAccepts"
@change="handleChangeSelectFile" />
</SMTab>
</SMTabGroup>
</template>
<template #footer>
<SMButtonRow> <SMButtonRow>
<template #left> <template #left>
<SMButton <SMButton
@@ -77,31 +127,27 @@
@click="handleClickCancel" /> @click="handleClickCancel" />
</template> </template>
<template #right> <template #right>
<SMButton
v-if="props.allowUpload"
type="button"
label="Upload"
@click="handleClickUpload" />
<SMButton <SMButton
type="primary" type="primary"
label="Insert" label="Insert"
:disabled="selected.length == 0" :disabled="computedInsertDisabled"
@click="handleClickInsert" /> @click="handleClickInsert" />
</template> </template>
</SMButtonRow> </SMButtonRow>
<input </template>
v-if="props.allowUpload"
id="file"
ref="refUploadInput"
type="file"
style="display: none"
:accept="computedAccepts"
@change="handleChangeUpload" />
</SMFormCard> </SMFormCard>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, onMounted, onUnmounted, ref, Ref, watch } from "vue"; import {
computed,
onMounted,
onUnmounted,
reactive,
ref,
Ref,
watch,
} from "vue";
import { closeDialog } from "../SMDialog"; import { closeDialog } from "../SMDialog";
import { api } from "../../helpers/api"; import { api } from "../../helpers/api";
import { Media, MediaCollection, MediaResponse } from "../../helpers/api.types"; import { Media, MediaCollection, MediaResponse } from "../../helpers/api.types";
@@ -117,6 +163,12 @@ import SMGroupButtons from "../SMGroupButtons.vue";
import SMPagination from "../SMPagination.vue"; import SMPagination from "../SMPagination.vue";
import SMButtonRow from "../SMButtonRow.vue"; import SMButtonRow from "../SMButtonRow.vue";
import SMLoading from "../SMLoading.vue"; import SMLoading from "../SMLoading.vue";
import SMTabGroup from "../SMTabGroup.vue";
import SMTab from "../SMTab.vue";
import { Form, FormControl } from "../../helpers/form";
import { And, Min, Required } from "../../helpers/validate";
import SMForm from "../SMForm.vue";
import SMFormError from "../SMFormError.vue";
const props = defineProps({ const props = defineProps({
mime: { mime: {
@@ -142,9 +194,21 @@ const props = defineProps({
const refUploadInput = ref<HTMLInputElement | null>(null); const refUploadInput = ref<HTMLInputElement | null>(null);
/** /**
* The form user message to display * The selected tab
*/ */
const formMessage = ref(""); const selectedTab = ref("");
/**
* Upload form
*/
let uploadForm = reactive(
Form({
title: FormControl("", And([Required(), Min(4)])),
description: FormControl(""),
})
);
const uploadPreview = ref("");
/** /**
* Is the media loading/busy * Is the media loading/busy
@@ -230,7 +294,8 @@ const handleClickCancel = () => {
/** /**
* Handle user clicking the insert button. * Handle user clicking the insert button.
*/ */
const handleClickInsert = () => { const handleClickInsert = async () => {
if (selectedTab.value == "tab-browser") {
if (selected.value !== "") { if (selected.value !== "") {
const mediaItem = getMediaItem(selected.value); const mediaItem = getMediaItem(selected.value);
if (mediaItem != null) { if (mediaItem != null) {
@@ -238,6 +303,112 @@ const handleClickInsert = () => {
return; return;
} }
} }
} else if (selectedTab.value == "tab-upload") {
if (
refUploadInput.value != null &&
refUploadInput.value.files != null
) {
const firstFile: File | undefined = refUploadInput.value.files[0];
if (firstFile != null) {
let submitFormData = new FormData();
submitFormData.append("file", firstFile);
submitFormData.append("title", uploadForm.controls.title.value);
submitFormData.append(
"description",
uploadForm.controls.description.value
);
try {
let result = await api.post({
url: "/media",
body: submitFormData,
headers: {
"Content-Type": "multipart/form-data",
},
progress: (progressData) =>
(progressText.value = `Uploading File: ${Math.floor(
(progressData.loaded / progressData.total) * 100
)}%`),
});
if (result.data) {
const data = result.data as MediaResponse;
if (
data.medium.status != "" &&
data.medium.status.startsWith("Failed") == false
) {
progressText.value = `${data.medium.status}...`;
let mediaProcessed = false;
let timeout = 0;
while (mediaProcessed == false) {
timeout++;
if (timeout >= 240) {
mediaProcessed = true;
uploadForm._message =
"Timed out processing the file. Please try again later.";
}
await new Promise((resolve) =>
setTimeout(resolve, 500)
);
try {
let updateResult = await api.get({
url: "/media/{id}",
params: {
id: data.medium.id,
},
});
if (updateResult.data) {
const updateData =
updateResult.data as MediaResponse;
if (updateData.medium.status == "") {
data.medium = updateData.medium;
mediaProcessed = true;
} else if (
updateData.medium.status.startsWith(
"Failed"
) == true
) {
throw "error";
} else {
progressText.value = `${updateData.medium.status}...`;
}
} else {
throw "error";
}
} catch {
mediaProcessed = true;
uploadForm._message =
"An server error occurred processing the file.";
}
}
if (data.medium.status.length == 0) {
closeDialog(data.medium);
}
}
} else {
uploadForm._message =
"An unexpected response was received from the server";
}
} catch (error) {
if (error.status === 413) {
uploadForm._message =
"The selected file is larger than the maximum size limit";
} else {
uploadForm._message =
error.response?.data?.message ||
"An unexpected error occurred";
}
} finally {
progressText.value = "";
}
} else {
uploadForm._message = "No file was selected to upload";
}
} else {
uploadForm._message = "No file was selected to upload";
}
}
closeDialog(false); closeDialog(false);
}; };
@@ -281,7 +452,7 @@ const handleClickLayout = (name: string) => {
/** /**
* When the user clicks the upload button * When the user clicks the upload button
*/ */
const handleClickUpload = async () => { const handleClickSelectFile = async () => {
if (refUploadInput.value != null) { if (refUploadInput.value != null) {
refUploadInput.value.click(); refUploadInput.value.click();
} }
@@ -290,100 +461,23 @@ const handleClickUpload = async () => {
/** /**
* Upload the file to the server. * Upload the file to the server.
*/ */
const handleChangeUpload = async () => { const handleChangeSelectFile = async () => {
formMessage.value = ""; uploadForm._message = "";
if (refUploadInput.value != null && refUploadInput.value.files != null) { if (refUploadInput.value != null && refUploadInput.value.files != null) {
const firstFile: File | undefined = refUploadInput.value.files[0]; const firstFile: File | undefined = refUploadInput.value.files[0];
if (firstFile != null) { if (firstFile != null) {
let submitFormData = new FormData(); if (uploadForm.controls.title.value.length == 0) {
submitFormData.append("file", firstFile); uploadForm.controls.title.value = firstFile.name;
try {
let result = await api.post({
url: "/media",
body: submitFormData,
headers: {
"Content-Type": "multipart/form-data",
},
progress: (progressData) =>
(progressText.value = `Uploading File: ${Math.floor(
(progressData.loaded / progressData.total) * 100
)}%`),
});
if (result.data) {
const data = result.data as MediaResponse;
if (
data.medium.status != "" &&
data.medium.status.startsWith("Failed") == false
) {
progressText.value = `${data.medium.status}...`;
let mediaProcessed = false;
while (mediaProcessed == false) {
await new Promise((resolve) =>
setTimeout(resolve, 500)
);
try {
let updateResult = await api.get({
url: "/media/{id}",
params: {
id: data.medium.id,
},
});
if (updateResult.data) {
const updateData =
updateResult.data as MediaResponse;
if (
updateData.medium.status == "" &&
data.medium.status.startsWith(
"Failed"
) == false
) {
mediaProcessed = true;
} else {
progressText.value = `${updateData.medium.status}...`;
}
} else {
throw "error";
}
} catch {
mediaProcessed = true;
formMessage.value =
"An server error occurred processing the file";
}
} }
progressText.value; const reader = new FileReader();
reader.onload = (event) => {
const imgSrc = event.target.result;
uploadPreview.value = imgSrc as string;
};
reader.readAsDataURL(firstFile);
} }
closeDialog(data.medium);
} else {
formMessage.value =
"An unexpected response was received from the server";
}
} catch (error) {
if (error.status === 413) {
formMessage.value =
"The selected file is larger than the maximum size limit";
} else {
formMessage.value =
error.response?.data?.message ||
"An unexpected error occurred";
}
} finally {
progressText.value = "";
}
} else {
formMessage.value = "No file was selected to upload";
}
} else {
formMessage.value = "No file was selected to upload";
} }
}; };
@@ -432,7 +526,7 @@ const handleLoad = async () => {
} }
}) })
.catch((error) => { .catch((error) => {
formMessage.value = uploadForm._message =
error?.data?.message || "An unexpected error occurred"; error?.data?.message || "An unexpected error occurred";
}) })
.finally(() => { .finally(() => {
@@ -473,6 +567,19 @@ watch(page, () => {
handleLoad(); handleLoad();
}); });
/**
* Determine if the Insert button should be disabled
*/
const computedInsertDisabled = computed(() => {
if (selectedTab.value == "tab-browser") {
return selected.value.length == 0;
} else if (selectedTab.value == "tab-upload") {
return uploadPreview.value.length == 0;
}
return false;
});
handleLoad(); handleLoad();
</script> </script>
@@ -592,5 +699,14 @@ handleLoad();
} }
} }
} }
.upload-preview {
width: 250px;
height: 140px;
border: 1px solid var(--base-color-dark);
border-radius: 8px;
background-position: center;
background-size: cover;
}
} }
</style> </style>