fixes
This commit is contained in:
@@ -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,
|
|
||||||
name: "",
|
|
||||||
mime_type: mime,
|
mime_type: mime,
|
||||||
permission: "",
|
|
||||||
size: -1,
|
size: -1,
|
||||||
status: "OK",
|
url: form.controls.url.value as string,
|
||||||
storage: "",
|
description: form.controls.description
|
||||||
url: form.controls.url.value,
|
.value as string,
|
||||||
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)[0]);
|
|
||||||
} else {
|
|
||||||
fileList.push(...Array.from(files));
|
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({
|
||||||
|
id: uploadId,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
window.setTimeout(() => {
|
||||||
|
uploadFileById(uploadId, file);
|
||||||
|
}, 50);
|
||||||
} else {
|
} else {
|
||||||
uploadFileList.value = Array.from(fileList);
|
useToastStore().addToast({
|
||||||
|
title: "Incorrect File",
|
||||||
|
type: "danger",
|
||||||
|
content: `Cannot upload the file ${file.name} as the file type is not supported.`,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
});
|
||||||
startFilesUpload();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const startFilesUpload = async () => {
|
const getUploadingMediaItems = (): Media[] => {
|
||||||
if (uploadFileList.value != null) {
|
return mediaItems.value.filter((item) => item.id.startsWith("upload_"));
|
||||||
if (currentUploadFileNum.value < 1) {
|
};
|
||||||
currentUploadFileNum.value = 1;
|
|
||||||
|
|
||||||
while (currentUploadFileNum.value <= uploadFileList.value.length) {
|
const computedUploadMediaTitle = computed(() => {
|
||||||
const file =
|
const items = getUploadingMediaItems();
|
||||||
uploadFileList.value[currentUploadFileNum.value - 1];
|
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();
|
let submitFormData = new FormData();
|
||||||
submitFormData.append("file", file);
|
submitFormData.append("file", file);
|
||||||
submitFormData.append(
|
submitFormData.append("title", convertFileNameToTitle(file.name));
|
||||||
"title",
|
|
||||||
convertFileNameToTitle(file.name),
|
|
||||||
);
|
|
||||||
submitFormData.append("description", "");
|
submitFormData.append("description", "");
|
||||||
try {
|
|
||||||
let result = await api.chunk({
|
api.chunk({
|
||||||
url: "/media",
|
url: "/media",
|
||||||
body: submitFormData,
|
body: submitFormData,
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "multipart/form-data",
|
"Content-Type": "multipart/form-data",
|
||||||
},
|
},
|
||||||
progress: (progressEvent) => {
|
progress: (progressEvent) => {
|
||||||
const currentUploadFileNumStr =
|
const mediaItem = getMediaItemById(uploadId);
|
||||||
currentUploadFileNum.value.toString();
|
if (mediaItem != null) {
|
||||||
currentUploadFileProgress.value = Math.floor(
|
mediaItem.jobs[0] = createMediaJobItem({
|
||||||
(progressEvent.loaded / progressEvent.total) *
|
status: "uploading",
|
||||||
100,
|
progress: 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;
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
|
.then((result) => {
|
||||||
if (result.data) {
|
if (result.data) {
|
||||||
const data = result.data as MediaResponse;
|
const mediaItem = getMediaItemById(uploadId);
|
||||||
const extendedMedium: ExtendedMedia = {
|
if (mediaItem != null) {
|
||||||
...data.medium,
|
mediaItem.jobs[0] = (
|
||||||
status: "",
|
result.data as MediaJobResponse
|
||||||
};
|
).media_job;
|
||||||
|
|
||||||
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;
|
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);
|
||||||
});
|
});
|
||||||
|
|
||||||
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);
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
const updateFiles = async () => {
|
setTimeout(() => {
|
||||||
if (updateFilesNonce.value == null) {
|
updateMediaItem(media.id);
|
||||||
let remaining = false;
|
}, 50);
|
||||||
|
})
|
||||||
for (const [index, item] of mediaItems.value.entries()) {
|
.catch((error) => {
|
||||||
if (isUUID(item.id)) {
|
/* error */
|
||||||
let updateResult = await api.get({
|
console.log(error);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
api.get({
|
||||||
url: "/media/{id}",
|
url: "/media/{id}",
|
||||||
params: {
|
params: {
|
||||||
id: item.id,
|
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);
|
||||||
});
|
});
|
||||||
|
|
||||||
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 {
|
|
||||||
updateFilesNonce.value = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -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: "",
|
||||||
|
|||||||
@@ -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: {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
};
|
||||||
|
|||||||
@@ -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;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user