This commit is contained in:
2023-09-02 18:58:13 +10:00
parent eacf33b642
commit 29acd76051
4 changed files with 417 additions and 343 deletions

View File

@@ -19,9 +19,6 @@
@dragover.prevent="handleDragOver"> @dragover.prevent="handleDragOver">
<div <div
class="flex flex-col m-4 border-1 bg-white rounded-xl text-gray-5 px-4 md:px-12 py-4 md:py-8 w-full overflow-hidden"> class="flex flex-col m-4 border-1 bg-white rounded-xl text-gray-5 px-4 md:px-12 py-4 md:py-8 w-full overflow-hidden">
<SMLoading v-if="progressText" overlay>{{
progressText
}}</SMLoading>
<h2 class="mb-4">Select or Upload Media</h2> <h2 class="mb-4">Select or Upload Media</h2>
<SMTabGroup v-model="selectedTab" class="flex flex-col flex-1"> <SMTabGroup v-model="selectedTab" class="flex flex-col flex-1">
<SMTab <SMTab
@@ -152,10 +149,12 @@
{{ item.title }} {{ item.title }}
</div> </div>
<SMLoading <SMLoading
v-if="item.status" v-if="getMediaStatus(item).busy"
small small
class="bg-white bg-op-90 w-full h-full" class="bg-white bg-op-90 w-full h-full"
>{{ item.status }}</SMLoading >{{
getMediaStatusText(item)
}}</SMLoading
> >
</div> </div>
</li> </li>
@@ -185,40 +184,19 @@
<div <div
class="absolute top-0 right-0 bottom-0 w-60 p-4 border-l border-gray-3 bg-gray-1 rounded-r-2 overflow-auto hidden md:block"> class="absolute top-0 right-0 bottom-0 w-60 p-4 border-l border-gray-3 bg-gray-1 rounded-r-2 overflow-auto hidden md:block">
<div <div
v-if="uploadFileList" v-if="getUploadingMediaItems().length > 0"
class="flex flex-col text-xs border-b border-gray-3 pb-4 mb-4"> class="flex flex-col text-xs border-b border-gray-3 pb-4 mb-4">
<h3 class="text-xs mb-2">Uploading</h3> <h3 class="text-xs mb-2">
{{ computedUploadMediaTitle }}
</h3>
<div <div
class="w-full bg-gray-3 h-3 mb-2 rounded-2"> class="w-full bg-gray-3 h-3 mb-2 rounded-2">
<div <div
class="bg-sky-600 h-3 rounded-2" class="bg-sky-600 h-3 rounded-2"
:style="{ :style="{
width: `${ width: `${computedUploadProgress}%`,
(100 / uploadFileList.length) *
(currentUploadFileNum - 1) +
(100 /
uploadFileList.length /
100) *
currentUploadFileProgress
}%`,
}"></div> }"></div>
</div> </div>
<p class="m-0">
{{ currentUploadFileNum }} /
{{
uploadFileList && uploadFileList.length
}}
-
{{
uploadFileList &&
uploadFileList.length >=
currentUploadFileNum
? uploadFileList[
currentUploadFileNum - 1
].name
: ""
}}
</p>
</div> </div>
<div v-if="lastSelected != null"> <div v-if="lastSelected != null">
<div <div
@@ -253,9 +231,15 @@
}} }}
</p> </p>
<p <p
v-if="lastSelected.status != 'OK'" v-if="
getMediaStatusText(
lastSelected,
) != ''
"
class="m-0 italic"> class="m-0 italic">
{{ lastSelected.status }} {{
getMediaStatusText(lastSelected)
}}
</p> </p>
<p <p
v-if=" v-if="
@@ -371,7 +355,7 @@
'flex-items-center', 'flex-items-center',
'flex-col', 'flex-col',
]" ]"
@click="handleShowFileItem(item.id)"> @click="handleChangeLastSelected(item.id)">
<div <div
:class="[ :class="[
'flex', 'flex',
@@ -398,7 +382,7 @@
class="bg-white bg-op-90 w-full h-full" /> class="bg-white bg-op-90 w-full h-full" />
<div <div
class="absolute rounded-5 bg-white -top-1.5 -right-1.5 hidden item-delete" class="absolute rounded-5 bg-white -top-1.5 -right-1.5 hidden item-delete"
@click="handleRemoveItem(item.id)"> @click="handleRemoveItemFromSelection(item.id)">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6 block" class="h-6 w-6 block"
@@ -474,6 +458,7 @@ import {
ApiInfo, ApiInfo,
Media, Media,
MediaCollection, MediaCollection,
MediaJobResponse,
MediaResponse, MediaResponse,
} from "../../helpers/api.types"; } from "../../helpers/api.types";
import { useApplicationStore } from "../../store/ApplicationStore"; import { useApplicationStore } from "../../store/ApplicationStore";
@@ -481,7 +466,10 @@ import {
mediaGetThumbnail, mediaGetThumbnail,
mimeMatches, mimeMatches,
mediaIsBusy, mediaIsBusy,
mediaStatus, getMediaStatus,
createMediaItem,
createMediaJobItem,
getMediaStatusText,
} from "../../helpers/media"; } from "../../helpers/media";
import SMInput from "../SMInput.vue"; import SMInput from "../SMInput.vue";
import SMLoading from "../SMLoading.vue"; import SMLoading from "../SMLoading.vue";
@@ -489,7 +477,11 @@ import SMTabGroup from "../SMTabGroup.vue";
import SMTab from "../SMTab.vue"; import SMTab from "../SMTab.vue";
import { Form, FormControl, FormObject } from "../../helpers/form"; import { Form, FormControl, FormObject } from "../../helpers/form";
import { And, Required, Url } from "../../helpers/validate"; import { And, Required, Url } from "../../helpers/validate";
import { convertFileNameToTitle, userHasPermission } from "../../helpers/utils"; import {
convertFileNameToTitle,
generateRandomId,
userHasPermission,
} from "../../helpers/utils";
import { bytesReadable } from "../../helpers/types"; import { bytesReadable } from "../../helpers/types";
import { SMDate } from "../../helpers/datetime"; import { SMDate } from "../../helpers/datetime";
import { isUUID } from "../../helpers/uuid"; import { isUUID } from "../../helpers/uuid";
@@ -498,11 +490,6 @@ import { useUserStore } from "../../store/UserStore";
import SMDialogConfirm from "../../components/dialogs/SMDialogConfirm.vue"; import SMDialogConfirm from "../../components/dialogs/SMDialogConfirm.vue";
import { openDialog } from "../../components/SMDialog"; import { openDialog } from "../../components/SMDialog";
/* Extended Media Interface */
interface ExtendedMedia extends Media {
status: string;
}
const props = defineProps({ const props = defineProps({
mime: { mime: {
type: String, type: String,
@@ -540,13 +527,8 @@ const props = defineProps({
* Reference to the File Upload Input element. * Reference to the File Upload Input element.
*/ */
const refUploadInput = ref<HTMLInputElement | null>(null); const refUploadInput = ref<HTMLInputElement | null>(null);
const refMediaList = ref<HTMLUListElement | null>(null); const refMediaList = ref<HTMLUListElement | null>(null);
const userStore = useUserStore(); const userStore = useUserStore();
const forceRefresh = [];
const allowUploads = ref(props.allowUpload && userStore.id); const allowUploads = ref(props.allowUpload && userStore.id);
const formLoading = ref(false); const formLoading = ref(false);
const form: FormObject = reactive( const form: FormObject = reactive(
@@ -585,13 +567,13 @@ const totalItems = ref(0);
/** /**
* List of current media items. * List of current media items.
*/ */
const mediaItems: Ref<ExtendedMedia[]> = ref([]); const mediaItems: Ref<Media[]> = ref([]);
/** /**
* Selected media item id. * Selected media item id.
*/ */
const selected: Ref<ExtendedMedia[]> = ref([]); const selected: Ref<Media[]> = ref([]);
let lastSelected: Ref<ExtendedMedia | null> = ref(null); let lastSelected: Ref<Media | null> = ref(null);
/** /**
* How many media items are we showing per page. * How many media items are we showing per page.
@@ -601,11 +583,6 @@ const perPage = ref(24);
const showFileDrop = ref(false); const showFileDrop = ref(false);
const applicationStore = useApplicationStore(); const applicationStore = useApplicationStore();
const progressText = ref("");
const currentUploadFileNum = ref(0);
const currentUploadFileProgress = ref(0);
const uploadFileList: Ref<File[]> = ref(null);
/** /**
* Returns the file types accepted. * Returns the file types accepted.
@@ -625,10 +602,10 @@ const computedAccepts = computed(() => {
/** /**
* Get the media item by id. * Get the media item by id.
* @param {string} item_id The media item id. * @param {string} item_id The media item id.
* @returns {ExtendedMedia | null} The media object or null. * @returns {Media | null} The media object or null.
*/ */
const getMediaItem = (item_id: string): ExtendedMedia | null => { const getMediaItemById = (item_id: string): Media | null => {
let found: ExtendedMedia | null = null; let found: Media | null = null;
mediaItems.value.every((item) => { mediaItems.value.every((item) => {
if (item.id == item_id) { if (item.id == item_id) {
@@ -642,6 +619,17 @@ const getMediaItem = (item_id: string): ExtendedMedia | null => {
return found; return found;
}; };
const setMediaItemById = (item_id: string, updatedMedia: Media): Media => {
const index = mediaItems.value.findIndex((item) => item.id === item_id);
if (index !== -1) {
// Replace the existing media item with the updated one
mediaItems.value.splice(index, 1, updatedMedia);
}
return updatedMedia;
};
/** /**
* Handle user clicking the cancel/close button. * Handle user clicking the cancel/close button.
*/ */
@@ -668,7 +656,7 @@ const handleClickSelect = async () => {
} else if (selectedTab.value == "tab-url") { } else if (selectedTab.value == "tab-url") {
formLoading.value = true; formLoading.value = true;
if (await form.validate()) { if (await form.validate()) {
const response = await fetch(form.controls.url.value, { const response = await fetch(form.controls.url.value as string, {
method: "HEAD", method: "HEAD",
}); });
@@ -693,23 +681,16 @@ const handleClickSelect = async () => {
"Invalid file type", "Invalid file type",
); );
} else { } else {
closeDialog({ closeDialog(
id: "", createMediaItem({
user_id: "", title: form.controls.title.value as string,
title: form.controls.title.value, mime_type: mime,
name: "", size: -1,
mime_type: mime, url: form.controls.url.value as string,
permission: "", description: form.controls.description
size: -1, .value as string,
status: "OK", }),
storage: "", );
url: form.controls.url.value,
description: form.controls.description.value,
dimensions: "",
variants: {},
created_at: "",
updated_at: "",
});
} }
} }
} }
@@ -723,8 +704,9 @@ const handleClickSelect = async () => {
* @param {string} item_id The media id. * @param {string} item_id The media id.
*/ */
const handleClickItem = (item_id: string): void => { const handleClickItem = (item_id: string): void => {
// only allow selecting of items that have a UUID (ie not items being uploaded)
if (isUUID(item_id)) { if (isUUID(item_id)) {
const mediaItem = getMediaItem(item_id); const mediaItem = getMediaItemById(item_id);
if (props.multiple) { if (props.multiple) {
if (selected.value.findIndex((item) => item.id === item_id) > -1) { if (selected.value.findIndex((item) => item.id === item_id) > -1) {
@@ -744,11 +726,9 @@ const handleClickItem = (item_id: string): void => {
lastSelected.value = mediaItem; lastSelected.value = mediaItem;
} }
} else { } else {
selected.value[0] = getMediaItem(item_id); selected.value[0] = getMediaItemById(item_id);
lastSelected.value = mediaItem; lastSelected.value = mediaItem;
} }
} else {
// selected.value = null;
} }
}; };
@@ -759,25 +739,32 @@ const handleClickItem = (item_id: string): void => {
const handleDblClickItem = (item_id: string): void => { const handleDblClickItem = (item_id: string): void => {
if (!props.multiple) { if (!props.multiple) {
if (isUUID(item_id)) { if (isUUID(item_id)) {
const mediaItem = getMediaItem(item_id); const mediaItem = getMediaItemById(item_id);
if (mediaItem != null) { if (mediaItem != null) {
closeDialog(mediaItem); closeDialog(mediaItem);
return; } else {
closeDialog(false);
} }
closeDialog(false);
} }
} }
}; };
const handleShowFileItem = (item_id: string): void => { /**
* Change last selected item.
* @param {string} item_id The item id to make the last selected.
*/
const handleChangeLastSelected = (item_id: string): void => {
const index = selected.value.findIndex((item) => item.id === item_id); const index = selected.value.findIndex((item) => item.id === item_id);
if (index > -1) { if (index > -1) {
lastSelected.value = selected.value[index]; lastSelected.value = selected.value[index];
} }
}; };
const handleRemoveItem = (item_id: string): void => { /**
* Remove an item from the selection list.
* @param {string} item_id The item id to remove.
*/
const handleRemoveItemFromSelection = (item_id: string): void => {
selected.value = selected.value.filter((item) => item.id != item_id); selected.value = selected.value.filter((item) => item.id != item_id);
if (lastSelected.value && lastSelected.value.id === item_id) { if (lastSelected.value && lastSelected.value.id === item_id) {
if (selected.value.length > 0) { if (selected.value.length > 0) {
@@ -808,197 +795,174 @@ const handleChangeSelectFile = async () => {
refUploadInput.value.value = ""; refUploadInput.value.value = "";
}; };
/**
* Process the file list, uploading to the server.
* @param {FileList} files The list of files to upload to the server.
*/
const handleFilesUpload = (files: FileList) => { const handleFilesUpload = (files: FileList) => {
const fileList = []; const fileList = [];
if (props.multiple == false && files.length > 1) { fileList.push(...Array.from(files));
fileList.push(Array.from(files)[0]);
} else {
fileList.push(...Array.from(files));
}
Array.from(fileList).forEach((file, index) => { Array.from(fileList).forEach((file: File) => {
mediaItems.value.unshift({ if (mimeMatches(props.mime, file.type) == true) {
id: (currentUploadFileNum.value + index + 1).toString(), const uploadId = generateRandomId("upload_", 8, (s) => {
user_id: "", return getMediaItemById(s) != null;
title: "", });
name: file.name,
mime_type: "",
permission: "",
size: 0,
status: "",
storage: "",
url: "",
thumbnail: "",
description: "",
dimensions: "",
variants: {},
created_at: "",
updated_at: "",
jobs: [],
});
});
if (uploadFileList.value != null) { mediaItems.value.unshift(
uploadFileList.value.push(...Array.from(fileList)); createMediaItem({
} else { id: uploadId,
uploadFileList.value = Array.from(fileList); }),
} );
startFilesUpload(); window.setTimeout(() => {
}; uploadFileById(uploadId, file);
}, 50);
const startFilesUpload = async () => {
if (uploadFileList.value != null) {
if (currentUploadFileNum.value < 1) {
currentUploadFileNum.value = 1;
while (currentUploadFileNum.value <= uploadFileList.value.length) {
const file =
uploadFileList.value[currentUploadFileNum.value - 1];
let submitFormData = new FormData();
submitFormData.append("file", file);
submitFormData.append(
"title",
convertFileNameToTitle(file.name),
);
submitFormData.append("description", "");
try {
let result = await api.chunk({
url: "/media",
body: submitFormData,
headers: {
"Content-Type": "multipart/form-data",
},
progress: (progressEvent) => {
const currentUploadFileNumStr =
currentUploadFileNum.value.toString();
currentUploadFileProgress.value = Math.floor(
(progressEvent.loaded / progressEvent.total) *
100,
);
mediaItems.value.every((item, index) => {
if (item.id == currentUploadFileNumStr) {
mediaItems.value[
index
].status = `${currentUploadFileProgress.value}% Uploaded`;
return false;
}
return true;
});
},
});
if (result.data) {
const data = result.data as MediaResponse;
const extendedMedium: ExtendedMedia = {
...data.medium,
status: "",
};
const currentUploadFileNumStr =
currentUploadFileNum.value.toString();
mediaItems.value.every((item, index) => {
if (item.id == currentUploadFileNumStr) {
mediaItems.value[index] = extendedMedium;
if (!selected.value) {
selected.value.push(extendedMedium);
} else if (props.multiple) {
selected.value.push(extendedMedium);
}
return false;
}
return true;
});
totalItems.value++;
}
} catch (error) {
const currentUploadFileNumStr =
currentUploadFileNum.value.toString();
mediaItems.value.every((item, index) => {
if (item.id == currentUploadFileNumStr) {
mediaItems.value[index].status = "Error";
return false;
}
return true;
});
let errorString = "A server error occurred";
if (error.status == 413) {
errorString = `The file is larger than ${max_upload_size.value}`;
}
useToastStore().addToast({
title: "Upload failed",
type: "danger",
content: errorString,
});
} finally {
currentUploadFileNum.value++;
updateFiles();
}
}
uploadFileList.value = null;
currentUploadFileNum.value = 0;
}
}
};
const updateFilesNonce = ref(null);
const updateFiles = async () => {
if (updateFilesNonce.value == null) {
let remaining = false;
for (const [index, item] of mediaItems.value.entries()) {
if (isUUID(item.id)) {
let updateResult = await api.get({
url: "/media/{id}",
params: {
id: item.id,
},
});
if (updateResult.data) {
const updateData = updateResult.data as MediaResponse;
const statusData = mediaStatus(updateData.medium);
console.log(item.id, statusData);
if (updateData.medium.thumbnail != item.thumbnail) {
mediaItems.value[index] = {
...updateData.medium,
status: "",
};
}
if (statusData.busy == false) {
mediaItems.value[index].status = "";
} else {
remaining = true;
mediaItems.value[index].status =
statusData.status +
" " +
statusData.status_text +
" " +
statusData.progress;
}
}
}
}
if (remaining) {
updateFilesNonce.value = setTimeout(() => {
updateFilesNonce.value = null;
updateFiles();
}, 500);
} else { } else {
updateFilesNonce.value = null; useToastStore().addToast({
title: "Incorrect File",
type: "danger",
content: `Cannot upload the file ${file.name} as the file type is not supported.`,
});
}
});
};
const getUploadingMediaItems = (): Media[] => {
return mediaItems.value.filter((item) => item.id.startsWith("upload_"));
};
const computedUploadMediaTitle = computed(() => {
const items = getUploadingMediaItems();
return `Uploading ${items.length} File${items.length == 1 ? "" : "s"}`;
});
const computedUploadProgress = computed(() => {
const items = getUploadingMediaItems();
if (items.length === 0) {
return 100;
}
const totalProgress = items.reduce((accumulator, item) => {
if (item.jobs.length > 0) {
accumulator += item.jobs[0].progress || 0;
}
return accumulator;
}, 0);
return Math.floor(totalProgress / items.length);
});
/**
* Upload a File to the server.
* @param {string} uploadId The ID of the new media item.
* @param {File} file The file object.
* @returns {void}
*/
const uploadFileById = (uploadId: string, file: File): void => {
let submitFormData = new FormData();
submitFormData.append("file", file);
submitFormData.append("title", convertFileNameToTitle(file.name));
submitFormData.append("description", "");
api.chunk({
url: "/media",
body: submitFormData,
headers: {
"Content-Type": "multipart/form-data",
},
progress: (progressEvent) => {
const mediaItem = getMediaItemById(uploadId);
if (mediaItem != null) {
mediaItem.jobs[0] = createMediaJobItem({
status: "uploading",
progress: Math.floor(
(progressEvent.loaded / progressEvent.total) * 100,
),
});
}
},
})
.then((result) => {
if (result.data) {
const mediaItem = getMediaItemById(uploadId);
if (mediaItem != null) {
mediaItem.jobs[0] = (
result.data as MediaJobResponse
).media_job;
}
updateMediaItem(uploadId);
}
})
.catch((error) => {
// let errorString = "A server error occurred";
// if (error.status == 413) {
// errorString = `The file is larger than ${max_upload_size.value}`;
// }
console.log(error);
});
};
/**
* Update media item.
* @param {string} id The media item id.
* @returns {void}
*/
const updateMediaItem = (id: string): void => {
let media = getMediaItemById(id);
if (media != null && media.jobs.length > 0) {
if (id.startsWith("upload_")) {
api.get({
url: "/media/job/{id}",
params: {
id: media.jobs[0].id,
},
})
.then((result) => {
const data = result.data as MediaJobResponse;
if (data.media_job.media_id != null) {
media.id = data.media_job.media_id;
}
setTimeout(() => {
updateMediaItem(media.id);
}, 50);
})
.catch((error) => {
/* error */
console.log(error);
});
} else {
api.get({
url: "/media/{id}",
params: {
id: id,
},
})
.then((result) => {
const data = result.data as MediaResponse;
media = setMediaItemById(id, data.medium);
const activeJobs = media.jobs.filter(
(job) =>
!["complete", "invalid", "failed"].includes(
job.status,
),
);
selectedMediaUpdateId(media);
if (activeJobs.length > 0) {
setTimeout(() => {
updateMediaItem(id);
}, 50);
}
})
.catch((error) => {
/* error */
console.log(error);
});
} }
} }
}; };
@@ -1070,7 +1034,7 @@ const handleLoad = async () => {
totalItems.value = data.total; totalItems.value = data.total;
mediaItems.value = data.media.map((item) => ({ mediaItems.value = data.media.map((item) => ({
...item, ...item,
status: mediaStatus(item).status_text, status: getMediaStatus(item).status_text,
})); }));
} }
}) })
@@ -1141,7 +1105,8 @@ const computedSelectDisabled = computed(() => {
); );
} else if (selectedTab.value == "tab-url") { } else if (selectedTab.value == "tab-url") {
return ( return (
!form.controls.url.isValid() || form.controls.url.value.length == 0 !form.controls.url.isValid() ||
(form.controls.url.value as string).length == 0
); );
} }
@@ -1211,7 +1176,7 @@ interface MediaUpdate {
id: string; id: string;
title: string; title: string;
description: string; description: string;
timer: any; timer: string | number;
} }
const pendingUpdates = ref<MediaUpdate[]>([]); const pendingUpdates = ref<MediaUpdate[]>([]);
@@ -1226,7 +1191,12 @@ const handleUpdate = () => {
} }
}; };
const handleRotateLeft = async (item: Media) => { /**
* Rotate a Media Item to the left.
* @param {Media} item The media item to rotate from the server.
* @returns {void}
*/
const handleRotateLeft = (item: Media): void => {
api.put({ api.put({
url: "/media/{id}", url: "/media/{id}",
params: { params: {
@@ -1236,26 +1206,21 @@ const handleRotateLeft = async (item: Media) => {
transform: "rotate-90", transform: "rotate-90",
}, },
}) })
.then((result) => { .then(() => {
if (result.data) { updateMediaItem(item.id);
const data = result.data as MediaResponse;
// const index = mediaItems.value.findIndex(
// (mediaItem) => mediaItem.id === item.id,
// );
// if (index !== -1) {
// mediaItems.value[index] = data.medium;
// }
updateFiles();
}
}) })
.catch(() => { .catch((error) => {
/* empty */ /* error */
console.log(error);
}); });
}; };
const handleRotateRight = async (item: Media) => { /**
* Rotate a Media Item to the right.
* @param {Media} item The media item to rotate from the server.
* @returns {void}
*/
const handleRotateRight = (item: Media): void => {
api.put({ api.put({
url: "/media/{id}", url: "/media/{id}",
params: { params: {
@@ -1265,26 +1230,21 @@ const handleRotateRight = async (item: Media) => {
transform: "rotate-270", transform: "rotate-270",
}, },
}) })
.then((result) => { .then(() => {
if (result.data) { updateMediaItem(item.id);
const data = result.data as MediaResponse;
// const index = mediaItems.value.findIndex(
// (mediaItem) => mediaItem.id === item.id,
// );
// if (index !== -1) {
// mediaItems.value[index] = data.medium;
// }
updateFiles();
}
}) })
.catch(() => { .catch((error) => {
/* empty */ /* error */
console.log(error);
}); });
}; };
const handleDelete = async (item: Media) => { /**
* Delete a Media item from the server.
* @param {Media} item The media item to delete from the server.
* @returns {Promise<void>}
*/
const handleDelete = async (item: Media): Promise<void> => {
let result = await openDialog(SMDialogConfirm, { let result = await openDialog(SMDialogConfirm, {
title: "Delete File?", title: "Delete File?",
text: `Are you sure you want to delete the file <strong>${item.title}</strong>?`, text: `Are you sure you want to delete the file <strong>${item.title}</strong>?`,
@@ -1323,11 +1283,23 @@ const handleDelete = async (item: Media) => {
totalItems.value--; totalItems.value--;
}) })
.catch((error) => { .catch((error) => {
/* error */
console.log(error); console.log(error);
}); });
} }
}; };
const selectedMediaUpdateId = (media: Media): void => {
if (lastSelected.value.id == media.id) {
lastSelected.value = media;
}
const index = selected.value.findIndex((item) => item.id === media.id);
if (index !== -1) {
selected.value[index] = media;
}
};
const addUpdate = (id: string, title: string, description: string): void => { const addUpdate = (id: string, title: string, description: string): void => {
let found = false; let found = false;
@@ -1338,7 +1310,7 @@ const addUpdate = (id: string, title: string, description: string): void => {
pendingUpdates.value[index].description = description; pendingUpdates.value[index].description = description;
if (pendingUpdates.value[index].timer != null) { if (pendingUpdates.value[index].timer != null) {
clearTimeout(pendingUpdates.value[index].timer); clearTimeout(pendingUpdates.value[index].timer);
pendingUpdates.value[index].timer = setTimeout(() => { pendingUpdates.value[index].timer = window.setTimeout(() => {
const data = pendingUpdates.value[index]; const data = pendingUpdates.value[index];
pendingUpdates.value.splice(index, 1); pendingUpdates.value.splice(index, 1);
@@ -1359,7 +1331,7 @@ const addUpdate = (id: string, title: string, description: string): void => {
timer: null, timer: null,
}); });
pendingUpdates.value[index - 1].timer = setTimeout(() => { pendingUpdates.value[index - 1].timer = window.setTimeout(() => {
const data = pendingUpdates.value[index - 1]; const data = pendingUpdates.value[index - 1];
pendingUpdates.value.splice(index - 1, 1); pendingUpdates.value.splice(index - 1, 1);
@@ -1411,17 +1383,6 @@ const loadInitial = () => {
} }
}; };
const itemRequiresRefresh = (id) => {
const index = forceRefresh.indexOf(id);
if (index !== -1) {
forceRefresh.splice(index, 1); // Remove the item at the found index
return true;
}
return false;
};
// Get max upload size // Get max upload size
api.get({ api.get({
url: "", url: "",

View File

@@ -6,7 +6,7 @@ import {
ValidationResult, ValidationResult,
} from "./validate"; } from "./validate";
type FormObjectValidateFunction = (item: string | null) => Promise<boolean>; type FormObjectValidateFunction = (item?: string | null) => Promise<boolean>;
type FormObjectLoadingFunction = (state?: boolean) => boolean; type FormObjectLoadingFunction = (state?: boolean) => boolean;
type FormObjectMessageFunction = ( type FormObjectMessageFunction = (
message?: string, message?: string,
@@ -149,15 +149,6 @@ interface FormControlValidation {
result: ValidationResult; result: ValidationResult;
} }
const defaultFormControlValidation: FormControlValidation = {
validator: {
validate: async (): Promise<ValidationResult> => {
return defaultValidationResult;
},
},
result: defaultValidationResult,
};
const getDefaultFormControlValidation = (): FormControlValidation => { const getDefaultFormControlValidation = (): FormControlValidation => {
return { return {
validator: { validator: {

View File

@@ -1,4 +1,5 @@
import { Media } from "./api.types"; import { Media, MediaJob } from "./api.types";
import { toTitleCase } from "./string";
export const mediaGetVariantUrl = ( export const mediaGetVariantUrl = (
media: Media, media: Media,
@@ -145,7 +146,7 @@ interface MediaStatus {
* @param {Media} media The media item to check. * @param {Media} media The media item to check.
* @returns {MediaStatus} The media status. * @returns {MediaStatus} The media status.
*/ */
export const mediaStatus = (media: Media): MediaStatus => { export const getMediaStatus = (media: Media): MediaStatus => {
const status = { const status = {
busy: false, busy: false,
status: "", status: "",
@@ -171,3 +172,98 @@ export const mediaStatus = (media: Media): MediaStatus => {
return status; return status;
}; };
/**
* Get the current Media status Text
* @param {Media} media The media item to check.
* @returns {string} Human readable string.
*/
export const getMediaStatusText = (media: Media): string => {
let status = "";
if (media.jobs.length > 0) {
if (
media.jobs[0].status != "invalid" &&
media.jobs[0].status != "failed" &&
media.jobs[0].status != "complete"
) {
if (media.jobs[0].status_text != "") {
status = toTitleCase(media.jobs[0].status_text);
if (
media.jobs[0].status == "processing" &&
media.jobs[0].progress > -1
) {
status += ` ${media.jobs[0].progress}%`;
}
} else {
status = toTitleCase(media.jobs[0].status);
}
}
}
return status;
};
export interface MediaParams {
id?: string;
user_id?: string;
title?: string;
name?: string;
mime_type?: string;
permission?: string;
size?: number;
storage?: string;
url?: string;
thumbnail?: string;
description?: string;
dimensions?: string;
variants?: { [key: string]: string };
created_at?: string;
updated_at?: string;
jobs?: Array<MediaJob>;
}
export interface MediaJobParams {
id?: string;
media_id?: string;
user_id?: string;
status?: string;
status_text?: string;
progress?: number;
}
export const createMediaItem = (params?: MediaParams): Media => {
const media = {
id: params.id || "",
user_id: params.user_id || "",
title: params.title || "",
name: params.name || "",
mime_type: params.mime_type || "",
permission: params.permission || "",
size: params.size !== undefined ? params.size : 0,
storage: params.storage || "",
url: params.url || "",
thumbnail: params.thumbnail || "",
description: params.description || "",
dimensions: params.dimensions || "",
variants: params.variants || {},
created_at: params.created_at || "",
updated_at: params.updated_at || "",
jobs: params.jobs || [],
};
return media;
};
export const createMediaJobItem = (params?: MediaJobParams): MediaJob => {
const job = {
id: params.id || "",
media_id: params.media_id || "",
user_id: params.user_id || "",
status: params.status || "",
status_text: params.status_text || "",
progress: params.progress || 0,
};
return job;
};

View File

@@ -83,23 +83,49 @@ export const clamp = (n: number, min: number, max: number): number => {
return n; return n;
}; };
type RandomIDVerifyCallback = (id: string) => boolean;
/**
* Generate a random ID.
* @param {string} prefix Any prefix to add to the ID.
* @param {number} length The length of the ID string (default = 6).
* @param {RandomIDVerifyCallback|null} callback Callback that if returns true generates a ID string.
* @returns {string} A random string.
*/
export const generateRandomId = (
prefix: string = "",
length: number = 6,
callback: RandomIDVerifyCallback | null = null,
): string => {
let randomId = "";
const letters =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
do {
randomId = prefix;
for (let i = 0; i < length; i++) {
randomId += letters.charAt(
Math.floor(Math.random() * letters.length),
);
}
} while (callback != null ? callback(randomId) : false);
return randomId;
};
/** /**
* Generate a random element ID. * Generate a random element ID.
* @param {string} prefix Any prefix to add to the ID. * @param {string} prefix Any prefix to add to the ID.
* @param {number} length The length of the ID string (default = 6).
* @returns {string} A random string non-existent in the document. * @returns {string} A random string non-existent in the document.
*/ */
export const generateRandomElementId = (prefix: string = ""): string => { export const generateRandomElementId = (
let randomId = ""; prefix: string = "",
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; length: number = 6,
): string => {
do { return generateRandomId(prefix, length, (s) => {
randomId = return document.getElementById(s) != null;
prefix + });
letters.charAt(Math.floor(Math.random() * letters.length)) +
Math.random().toString(36).substring(2, 9);
} while (document.getElementById(randomId));
return randomId;
}; };
/** /**