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">
<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">
<SMLoading v-if="progressText" overlay>{{
progressText
}}</SMLoading>
<h2 class="mb-4">Select or Upload Media</h2>
<SMTabGroup v-model="selectedTab" class="flex flex-col flex-1">
<SMTab
@@ -152,10 +149,12 @@
{{ item.title }}
</div>
<SMLoading
v-if="item.status"
v-if="getMediaStatus(item).busy"
small
class="bg-white bg-op-90 w-full h-full"
>{{ item.status }}</SMLoading
>{{
getMediaStatusText(item)
}}</SMLoading
>
</div>
</li>
@@ -185,40 +184,19 @@
<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">
<div
v-if="uploadFileList"
v-if="getUploadingMediaItems().length > 0"
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
class="w-full bg-gray-3 h-3 mb-2 rounded-2">
<div
class="bg-sky-600 h-3 rounded-2"
:style="{
width: `${
(100 / uploadFileList.length) *
(currentUploadFileNum - 1) +
(100 /
uploadFileList.length /
100) *
currentUploadFileProgress
}%`,
width: `${computedUploadProgress}%`,
}"></div>
</div>
<p class="m-0">
{{ currentUploadFileNum }} /
{{
uploadFileList && uploadFileList.length
}}
-
{{
uploadFileList &&
uploadFileList.length >=
currentUploadFileNum
? uploadFileList[
currentUploadFileNum - 1
].name
: ""
}}
</p>
</div>
<div v-if="lastSelected != null">
<div
@@ -253,9 +231,15 @@
}}
</p>
<p
v-if="lastSelected.status != 'OK'"
v-if="
getMediaStatusText(
lastSelected,
) != ''
"
class="m-0 italic">
{{ lastSelected.status }}
{{
getMediaStatusText(lastSelected)
}}
</p>
<p
v-if="
@@ -371,7 +355,7 @@
'flex-items-center',
'flex-col',
]"
@click="handleShowFileItem(item.id)">
@click="handleChangeLastSelected(item.id)">
<div
:class="[
'flex',
@@ -398,7 +382,7 @@
class="bg-white bg-op-90 w-full h-full" />
<div
class="absolute rounded-5 bg-white -top-1.5 -right-1.5 hidden item-delete"
@click="handleRemoveItem(item.id)">
@click="handleRemoveItemFromSelection(item.id)">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6 block"
@@ -474,6 +458,7 @@ import {
ApiInfo,
Media,
MediaCollection,
MediaJobResponse,
MediaResponse,
} from "../../helpers/api.types";
import { useApplicationStore } from "../../store/ApplicationStore";
@@ -481,7 +466,10 @@ import {
mediaGetThumbnail,
mimeMatches,
mediaIsBusy,
mediaStatus,
getMediaStatus,
createMediaItem,
createMediaJobItem,
getMediaStatusText,
} from "../../helpers/media";
import SMInput from "../SMInput.vue";
import SMLoading from "../SMLoading.vue";
@@ -489,7 +477,11 @@ import SMTabGroup from "../SMTabGroup.vue";
import SMTab from "../SMTab.vue";
import { Form, FormControl, FormObject } from "../../helpers/form";
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 { SMDate } from "../../helpers/datetime";
import { isUUID } from "../../helpers/uuid";
@@ -498,11 +490,6 @@ import { useUserStore } from "../../store/UserStore";
import SMDialogConfirm from "../../components/dialogs/SMDialogConfirm.vue";
import { openDialog } from "../../components/SMDialog";
/* Extended Media Interface */
interface ExtendedMedia extends Media {
status: string;
}
const props = defineProps({
mime: {
type: String,
@@ -540,13 +527,8 @@ const props = defineProps({
* Reference to the File Upload Input element.
*/
const refUploadInput = ref<HTMLInputElement | null>(null);
const refMediaList = ref<HTMLUListElement | null>(null);
const userStore = useUserStore();
const forceRefresh = [];
const allowUploads = ref(props.allowUpload && userStore.id);
const formLoading = ref(false);
const form: FormObject = reactive(
@@ -585,13 +567,13 @@ const totalItems = ref(0);
/**
* List of current media items.
*/
const mediaItems: Ref<ExtendedMedia[]> = ref([]);
const mediaItems: Ref<Media[]> = ref([]);
/**
* Selected media item id.
*/
const selected: Ref<ExtendedMedia[]> = ref([]);
let lastSelected: Ref<ExtendedMedia | null> = ref(null);
const selected: Ref<Media[]> = ref([]);
let lastSelected: Ref<Media | null> = ref(null);
/**
* How many media items are we showing per page.
@@ -601,11 +583,6 @@ const perPage = ref(24);
const showFileDrop = ref(false);
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.
@@ -625,10 +602,10 @@ const computedAccepts = computed(() => {
/**
* Get the media item by 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 => {
let found: ExtendedMedia | null = null;
const getMediaItemById = (item_id: string): Media | null => {
let found: Media | null = null;
mediaItems.value.every((item) => {
if (item.id == item_id) {
@@ -642,6 +619,17 @@ const getMediaItem = (item_id: string): ExtendedMedia | null => {
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.
*/
@@ -668,7 +656,7 @@ const handleClickSelect = async () => {
} else if (selectedTab.value == "tab-url") {
formLoading.value = true;
if (await form.validate()) {
const response = await fetch(form.controls.url.value, {
const response = await fetch(form.controls.url.value as string, {
method: "HEAD",
});
@@ -693,23 +681,16 @@ const handleClickSelect = async () => {
"Invalid file type",
);
} else {
closeDialog({
id: "",
user_id: "",
title: form.controls.title.value,
name: "",
mime_type: mime,
permission: "",
size: -1,
status: "OK",
storage: "",
url: form.controls.url.value,
description: form.controls.description.value,
dimensions: "",
variants: {},
created_at: "",
updated_at: "",
});
closeDialog(
createMediaItem({
title: form.controls.title.value as string,
mime_type: mime,
size: -1,
url: form.controls.url.value as string,
description: form.controls.description
.value as string,
}),
);
}
}
}
@@ -723,8 +704,9 @@ const handleClickSelect = async () => {
* @param {string} item_id The media id.
*/
const handleClickItem = (item_id: string): void => {
// only allow selecting of items that have a UUID (ie not items being uploaded)
if (isUUID(item_id)) {
const mediaItem = getMediaItem(item_id);
const mediaItem = getMediaItemById(item_id);
if (props.multiple) {
if (selected.value.findIndex((item) => item.id === item_id) > -1) {
@@ -744,11 +726,9 @@ const handleClickItem = (item_id: string): void => {
lastSelected.value = mediaItem;
}
} else {
selected.value[0] = getMediaItem(item_id);
selected.value[0] = getMediaItemById(item_id);
lastSelected.value = mediaItem;
}
} else {
// selected.value = null;
}
};
@@ -759,25 +739,32 @@ const handleClickItem = (item_id: string): void => {
const handleDblClickItem = (item_id: string): void => {
if (!props.multiple) {
if (isUUID(item_id)) {
const mediaItem = getMediaItem(item_id);
const mediaItem = getMediaItemById(item_id);
if (mediaItem != null) {
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);
if (index > -1) {
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);
if (lastSelected.value && lastSelected.value.id === item_id) {
if (selected.value.length > 0) {
@@ -808,197 +795,174 @@ const handleChangeSelectFile = async () => {
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 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) => {
mediaItems.value.unshift({
id: (currentUploadFileNum.value + index + 1).toString(),
user_id: "",
title: "",
name: file.name,
mime_type: "",
permission: "",
size: 0,
status: "",
storage: "",
url: "",
thumbnail: "",
description: "",
dimensions: "",
variants: {},
created_at: "",
updated_at: "",
jobs: [],
});
});
Array.from(fileList).forEach((file: File) => {
if (mimeMatches(props.mime, file.type) == true) {
const uploadId = generateRandomId("upload_", 8, (s) => {
return getMediaItemById(s) != null;
});
if (uploadFileList.value != null) {
uploadFileList.value.push(...Array.from(fileList));
} else {
uploadFileList.value = Array.from(fileList);
}
mediaItems.value.unshift(
createMediaItem({
id: uploadId,
}),
);
startFilesUpload();
};
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);
window.setTimeout(() => {
uploadFileById(uploadId, file);
}, 50);
} 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;
mediaItems.value = data.media.map((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") {
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;
title: string;
description: string;
timer: any;
timer: string | number;
}
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({
url: "/media/{id}",
params: {
@@ -1236,26 +1206,21 @@ const handleRotateLeft = async (item: Media) => {
transform: "rotate-90",
},
})
.then((result) => {
if (result.data) {
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();
}
.then(() => {
updateMediaItem(item.id);
})
.catch(() => {
/* empty */
.catch((error) => {
/* 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({
url: "/media/{id}",
params: {
@@ -1265,26 +1230,21 @@ const handleRotateRight = async (item: Media) => {
transform: "rotate-270",
},
})
.then((result) => {
if (result.data) {
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();
}
.then(() => {
updateMediaItem(item.id);
})
.catch(() => {
/* empty */
.catch((error) => {
/* 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, {
title: "Delete File?",
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--;
})
.catch((error) => {
/* 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 => {
let found = false;
@@ -1338,7 +1310,7 @@ const addUpdate = (id: string, title: string, description: string): void => {
pendingUpdates.value[index].description = description;
if (pendingUpdates.value[index].timer != null) {
clearTimeout(pendingUpdates.value[index].timer);
pendingUpdates.value[index].timer = setTimeout(() => {
pendingUpdates.value[index].timer = window.setTimeout(() => {
const data = pendingUpdates.value[index];
pendingUpdates.value.splice(index, 1);
@@ -1359,7 +1331,7 @@ const addUpdate = (id: string, title: string, description: string): void => {
timer: null,
});
pendingUpdates.value[index - 1].timer = setTimeout(() => {
pendingUpdates.value[index - 1].timer = window.setTimeout(() => {
const data = pendingUpdates.value[index - 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
api.get({
url: "",

View File

@@ -6,7 +6,7 @@ import {
ValidationResult,
} from "./validate";
type FormObjectValidateFunction = (item: string | null) => Promise<boolean>;
type FormObjectValidateFunction = (item?: string | null) => Promise<boolean>;
type FormObjectLoadingFunction = (state?: boolean) => boolean;
type FormObjectMessageFunction = (
message?: string,
@@ -149,15 +149,6 @@ interface FormControlValidation {
result: ValidationResult;
}
const defaultFormControlValidation: FormControlValidation = {
validator: {
validate: async (): Promise<ValidationResult> => {
return defaultValidationResult;
},
},
result: defaultValidationResult,
};
const getDefaultFormControlValidation = (): FormControlValidation => {
return {
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 = (
media: Media,
@@ -145,7 +146,7 @@ interface MediaStatus {
* @param {Media} media The media item to check.
* @returns {MediaStatus} The media status.
*/
export const mediaStatus = (media: Media): MediaStatus => {
export const getMediaStatus = (media: Media): MediaStatus => {
const status = {
busy: false,
status: "",
@@ -171,3 +172,98 @@ export const mediaStatus = (media: Media): MediaStatus => {
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;
};
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.
* @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.
*/
export const generateRandomElementId = (prefix: string = ""): string => {
let randomId = "";
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
do {
randomId =
prefix +
letters.charAt(Math.floor(Math.random() * letters.length)) +
Math.random().toString(36).substring(2, 9);
} while (document.getElementById(randomId));
return randomId;
export const generateRandomElementId = (
prefix: string = "",
length: number = 6,
): string => {
return generateRandomId(prefix, length, (s) => {
return document.getElementById(s) != null;
});
};
/**