diff --git a/resources/js/components/dialogs/SMDialogMedia.vue b/resources/js/components/dialogs/SMDialogMedia.vue index 17f1c20..e9f787d 100644 --- a/resources/js/components/dialogs/SMDialogMedia.vue +++ b/resources/js/components/dialogs/SMDialogMedia.vue @@ -1,61 +1,71 @@ + :accept="computedAccepts" + @change="handleChangeUpload" /> @@ -97,7 +110,9 @@ import SMMessage from "../SMMessage.vue"; import SMModal from "../SMModal.vue"; import { api } from "../../helpers/api"; import { bytesReadable } from "../../helpers/types"; -import { Media } from "../../helpers/api.types"; +import { getFilePreview } from "../../helpers/utils"; +import { Media, MediaCollection, MediaResponse } from "../../helpers/api.types"; +import SMLoadingIcon from "../SMLoadingIcon.vue"; const props = defineProps({ mime: { @@ -105,141 +120,337 @@ const props = defineProps({ default: "image/", required: false, }, + accepts: { + type: String, + default: "image/*", + required: false, + }, + allowUpload: { + type: Boolean, + default: true, + required: false, + }, }); -const uploader = ref(null); -const formLoading = ref(false); -const formLoadingMessage = ref(""); +/** + * Reference to the File Upload Input element. + */ +const refUploadInput = ref(null); + +/** + * Is the dialog loading/busy + */ +const dialogLoading = ref(false); + +/** + * The dialog loading message to display + */ +const dialogLoadingMessage = ref(""); + +/** + * The form user message to display + */ const formMessage = ref(""); +/** + * Is the media loading/busy + */ +const mediaLoading = ref(true); + +/** + * Classes to apply to the media browser + */ +const mediaBrowserClasses = ref(["media-browser-grid"]); + +/** + * Current page. + */ const page = ref(1); + +/** + * Total media items expressed by API. + */ const totalItems = ref(0); + +/** + * List of current media items. + */ const mediaItems: Ref = ref([]); + +/** + * Selected media item id. + */ const selected = ref(""); + +/** + * How many media items are we showing per page. + */ const perPage = ref(12); -const handleCancel = () => { +/** + * Returns the pagination info + */ +const computedPaginationInfo = computed(() => { + if (totalItems.value == 0) { + return "0 - 0 of 0"; + } + + const start = (page.value - 1) * perPage.value + 1; + const end = start + perPage.value - 1; + + return `${start} - ${end} of ${totalItems.value}`; +}); + +/** + * Returns the file types accepted. + */ +const computedAccepts = computed(() => { + if (props.accepts.length > 0) { + return props.accepts; + } + + if (props.mime.endsWith("/")) { + return `${props.mime}*`; + } + + return props.mime; +}); + +/** + * Return the total number of pages. + */ +const computedTotalPages = computed(() => { + return Math.ceil(totalItems.value / perPage.value); +}); + +/** + * Return if the previous button should be disabled. + */ +const computedDisablePrevButton = computed(() => { + return page.value <= 1; +}); + +/** + * Return if the next button should be disabled. + */ +const computedDisableNextButton = computed(() => { + return page.value >= computedTotalPages.value; +}); + +/** + * Get the media item by id. + * + * @param {string} item_id The media item id. + * @returns {Media | null} The media object or null. + */ +const getMediaItem = (item_id: string): Media | null => { + let found: Media | null = null; + + mediaItems.value.every((item) => { + console.log(item.id, item_id); + if (item.id == item_id) { + found = item; + return false; + } + + return true; + }); + + return found; +}; + +/** + * Handle user clicking the cancel/close button. + */ +const handleClickCancel = () => { closeDialog(false); }; -const handleConfirm = () => { +/** + * Handle user clicking the insert button. + */ +const handleClickInsert = () => { if (selected.value !== "") { - closeDialog(selected.value); - } else { - closeDialog(false); + const mediaItem = getMediaItem(selected.value); + console.log(mediaItem, selected.value); + if (mediaItem != null) { + closeDialog(mediaItem); + return; + } } + + closeDialog(false); }; -const handleSelection = (item_id: string): void => { +/** + * Handle user clicking a media item (selecting). + * + * @param {string} item_id The media id. + */ +const handleClickItem = (item_id: string): void => { selected.value = item_id; }; -const handlePickSelection = (item_id: string): void => { - closeDialog(item_id); -}; - -const handleLoad = async () => { - formMessage.value = ""; - selected.value = ""; - - try { - let params = { - page: 0, - limit: 0, - // fields: "", - }; - params.page = page.value; - params.limit = perPage.value; - // params.fields = "url"; - - let res = await api.get({ - url: "/media", - params: params, - }); - - totalItems.value = res.data.total; - mediaItems.value = res.data.media; - } catch (error) { - if (error.status == 404) { - // formMessage.type = "primary"; - // formMessage.icon = "folder-open-outline"; - // formMessage.message = "No media items found"; - } else { - formMessage.value = - error?.data?.message || "An unexpected error occurred"; - } - } -}; - -const handleAskUpload = () => { - uploader.value.click(); -}; - -const handleUpload = async () => { - formLoading.value = true; - formMessage.value = ""; - - try { - let submitFormData = new FormData(); - if (uploader.value.files[0] instanceof File) { - submitFormData.append("file", uploader.value.files[0]); - - let res = await api.post({ - url: "/media", - params: { - mime: props.mime, - }, - body: submitFormData, - headers: { - "Content-Type": "multipart/form-data", - }, - // onUploadProgress: (progressEvent) => - // (formLoadingMessage.value = `Uploading Files ${Math.floor( - // (progressEvent.loaded / progressEvent.total) * 100 - // )}%`), - }); - - if (res.data.medium) { - closeDialog(res.data.medium); - } else { - formMessage.value = - "An unexpected response was received from the server"; - } - } else { - formMessage.value = "No file was selected to upload"; - } - } catch (err) { - console.log(err); - formMessage.value = - err.response?.data?.message || "An unexpected error occurred"; +/** + * Handle user double clicking a media item. + * + * @param item_id The media id. + */ +const handleDblClickItem = (item_id: string): void => { + const mediaItem = getMediaItem(item_id); + if (mediaItem != null) { + closeDialog(mediaItem); + return; } - formLoading.value = false; + closeDialog(false); }; -const handlePrev = ($event) => { +/** + * Handle Grid layout request click + */ +const handleClickGridLayout = () => { + mediaBrowserClasses.value = ["media-browser-grid"]; +}; + +/** + * Handle List layout request click + */ +const handleClickListLayout = () => { + mediaBrowserClasses.value = ["media-browser-list"]; +}; + +/** + * Handle click on previous button + * + * @param {MouseEvent} $event The mouse event. + */ +const handleClickPrev = ($event: MouseEvent): void => { if ( - $event.target.classList.contains("disabled") == false && + $event.target && + ($event.target as HTMLElement).classList.contains("disabled") == + false && page.value > 1 ) { page.value--; } }; -const handleNext = ($event) => { +/** + * Handle click on next button + * + * @param {MouseEvent} $event The mouse event. + */ +const handleClickNext = ($event: MouseEvent): void => { if ( - $event.target.classList.contains("disabled") == false && - page.value < totalPages.value + $event.target && + ($event.target as HTMLElement).classList.contains("disabled") == + false && + page.value < computedTotalPages.value ) { page.value++; } }; +/** + * When the user clicks the upload button + */ +const handleClickUpload = () => { + if (refUploadInput.value != null) { + refUploadInput.value.click(); + } +}; + +/** + * Upload the file to the server. + */ +const handleChangeUpload = async () => { + dialogLoading.value = true; + formMessage.value = ""; + + if (refUploadInput.value != null && refUploadInput.value.files != null) { + const firstFile: File | undefined = refUploadInput.value.files[0]; + if (firstFile != null) { + let submitFormData = new FormData(); + submitFormData.append("file", firstFile); + + api.post({ + url: "/media", + body: submitFormData, + headers: { + "Content-Type": "multipart/form-data", + }, + // progress: (progressData) => + // (dialogLoadingMessage.value = `Uploading Files ${Math.floor( + // (progressData.loaded / progressData.total) * 100 + // )}%`), + }) + .then((result) => { + if (result.data) { + const data = result.data as MediaResponse; + closeDialog(data.medium); + } else { + formMessage.value = + "An unexpected response was received from the server"; + } + }) + .catch((error) => { + formMessage.value = + error.response?.data?.message || + "An unexpected error occurred"; + }); + } else { + formMessage.value = "No file was selected to upload"; + } + } else { + formMessage.value = "No file was selected to upload"; + } + + dialogLoading.value = false; +}; + +/** + * Load the data of the dialog + */ +const handleLoad = async () => { + mediaLoading.value = true; + + api.get({ + url: "/media", + params: { + page: page.value, + limit: perPage.value, + }, + }) + .then((result) => { + if (result.data) { + const data = result.data as MediaCollection; + + totalItems.value = data.total; + mediaItems.value = data.media; + } + }) + .catch((error) => { + formMessage.value = + error?.data?.message || "An unexpected error occurred"; + }) + .finally(() => { + mediaLoading.value = false; + }); +}; + +/** + * Handle the user pressing keyboard keys. + * + * @param {KeyboardEvent} event The keyboard event. + */ const eventKeyUp = (event: KeyboardEvent) => { if (event.key === "Escape") { - handleCancel(); + handleClickCancel(); } else if (event.key === "Enter") { - handleConfirm(); + if (selected.value.length > 0) { + handleClickInsert(); + } } }; @@ -251,19 +462,7 @@ onUnmounted(() => { document.removeEventListener("keyup", eventKeyUp); }); -const totalPages = computed(() => { - return Math.ceil(totalItems.value / perPage.value); -}); - -const prevDisabled = computed(() => { - return page.value <= 1; -}); - -const nextDisabled = computed(() => { - return page.value >= totalPages.value; -}); - -watch(page, (value) => { +watch(page, () => { handleLoad(); }); @@ -273,27 +472,114 @@ handleLoad();