From 1a02d461510d139e0ac81c08102abb11f615c9f3 Mon Sep 17 00:00:00 2001 From: James Collins Date: Wed, 12 Jul 2023 22:30:44 +1000 Subject: [PATCH] add support for multiple selections --- .../js/components/dialogs/SMDialogMedia.vue | 371 +++++++++++++----- 1 file changed, 281 insertions(+), 90 deletions(-) diff --git a/resources/js/components/dialogs/SMDialogMedia.vue b/resources/js/components/dialogs/SMDialogMedia.vue index f4c0dc4..4037606 100644 --- a/resources/js/components/dialogs/SMDialogMedia.vue +++ b/resources/js/components/dialogs/SMDialogMedia.vue @@ -120,7 +120,10 @@ 'flex-items-center', 'flex-col', selected != null && - item.id == selected.id + selected.filter( + (selectedItem) => + selectedItem.id == item.id, + ).length > 0 ? 'selected-checked' : 'border-white', ]" @@ -223,30 +226,44 @@ }}

-
+
-

- {{ selected.title }} -

-

- {{ formatDate(selected.created_at) }} -

-

- {{ bytesReadable(selected.size, 0) }} -

-

- {{ selected.status }} -

+ class="flex text-xs border-b border-gray-3 pb-4"> + +
+

+ {{ lastSelected.title }} +

+

+ {{ + formatDate( + lastSelected.created_at, + ) + }} +

+

+ {{ + bytesReadable( + lastSelected.size, + 0, + ) + }} +

+

+ {{ lastSelected.status }} +

+
@@ -264,39 +281,123 @@
+ +
+

Insert image from URL

+ + + +
+
-
- - +
+
    +
  • +
    + +
    +
  • +
+
+ + + +
@@ -321,13 +422,13 @@ import { MediaResponse, } from "../../helpers/api.types"; import { useApplicationStore } from "../../store/ApplicationStore"; -import { mediaGetVariantUrl } from "../../helpers/media"; +import { mediaGetVariantUrl, mimeMatches } from "../../helpers/media"; import SMInput from "../SMInput.vue"; import SMLoading from "../SMLoading.vue"; import SMTabGroup from "../SMTabGroup.vue"; import SMTab from "../SMTab.vue"; -import { Form, FormControl } from "../../helpers/form"; -import { And, Min, Required } from "../../helpers/validate"; +import { Form, FormControl, FormObject } from "../../helpers/form"; +import { And, Required, Url } from "../../helpers/validate"; import { convertFileNameToTitle, userHasPermission } from "../../helpers/utils"; import { bytesReadable } from "../../helpers/types"; import { SMDate } from "../../helpers/datetime"; @@ -338,7 +439,7 @@ import { useUserStore } from "../../store/UserStore"; const props = defineProps({ mime: { type: String, - default: "image/", + default: "image/*", required: false, }, accepts: { @@ -351,6 +452,16 @@ const props = defineProps({ default: false, required: false, }, + allowUrl: { + type: Boolean, + default: false, + required: false, + }, + multiple: { + type: Boolean, + default: false, + required: false, + }, }); /** @@ -363,6 +474,14 @@ const refMediaList = ref(null); const userStore = useUserStore(); const allowUploads = ref(props.allowUpload && userStore.id); +const formLoading = ref(false); +const form: FormObject = reactive( + Form({ + url: FormControl("", And([Required(), Url()])), + title: FormControl(""), + description: FormControl(""), + }), +); /** * The selected tab @@ -374,16 +493,6 @@ const selectedTab = ref("tab-browser"); */ const max_upload_size = ref(" "); -/** - * Upload form - */ -let uploadForm = reactive( - Form({ - title: FormControl("", And([Required(), Min(4)])), - description: FormControl(""), - }), -); - /** * Is the media loading/busy */ @@ -407,7 +516,14 @@ const mediaItems: Ref = ref([]); /** * Selected media item id. */ -const selected: Ref = ref(null); +const selected: Ref = ref([]); +const lastSelected = computed(() => { + if (selected.value.length > 0) { + return selected.value[selected.value.length - 1]; + } + + return null; +}); /** * How many media items are we showing per page. @@ -469,12 +585,67 @@ const handleClickCancel = () => { * Handle user clicking the select button. */ const handleClickSelect = async () => { + forceUpdate(); + if (selectedTab.value == "tab-browser") { - if (selected.value != null) { - forceUpdate(); - closeDialog(selected.value); + if (selected.value.length > 0) { + if (props.multiple) { + closeDialog(selected.value); + } else { + closeDialog(selected.value[0]); + } return; } + } else if (selectedTab.value == "tab-url") { + formLoading.value = true; + if (await form.validate()) { + const response = await fetch(form.controls.url.value, { + method: "HEAD", + }); + + if (response.status == 404) { + form.controls.url.setValidationResult( + false, + "File not found on server", + ); + } else if (response.status != 200) { + form.controls.url.setValidationResult( + false, + "Error occurred retrieving file from server", + ); + } else { + const mime = response.headers + .get("Content-Type") + .split(";")[0] + .trim(); + if (!mimeMatches(props.mime, mime)) { + form.controls.url.setValidationResult( + false, + "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: "", + }); + } + } + } + + formLoading.value = false; } }; @@ -484,9 +655,21 @@ const handleClickSelect = async () => { */ const handleClickItem = (item_id: string): void => { if (isUUID(item_id)) { - selected.value = getMediaItem(item_id); + if (props.multiple) { + if ( + selected.value.filter((item) => item.id == item_id).length > 0 + ) { + selected.value = selected.value.filter( + (item) => item.id != item_id, + ); + } else { + selected.value.push(getMediaItem(item_id)); + } + } else { + selected.value[0] = getMediaItem(item_id); + } } else { - selected.value = null; + // selected.value = null; } }; @@ -495,14 +678,16 @@ const handleClickItem = (item_id: string): void => { * @param item_id The media id. */ const handleDblClickItem = (item_id: string): void => { - if (isUUID(item_id)) { - const mediaItem = getMediaItem(item_id); - if (mediaItem != null) { - closeDialog(mediaItem); - return; - } + if (!props.multiple) { + if (isUUID(item_id)) { + const mediaItem = getMediaItem(item_id); + if (mediaItem != null) { + closeDialog(mediaItem); + return; + } - closeDialog(false); + closeDialog(false); + } } }; @@ -519,8 +704,6 @@ const handleClickSelectFile = async () => { * Upload the file to the server. */ const handleChangeSelectFile = async () => { - uploadForm._message = ""; - if (refUploadInput.value != null && refUploadInput.value.files != null) { handleFilesUpload(refUploadInput.value.files); showFileBrowserTab(); @@ -712,8 +895,7 @@ const handleLoad = async () => { } }) .catch((error) => { - uploadForm._message = - error?.data?.message || "An unexpected error occurred"; + /* empty */ }) .finally(() => { mediaLoading.value = false; @@ -730,7 +912,7 @@ const eventKeyPress = (event: KeyboardEvent): boolean => { handleClickCancel(); return true; } else if (event.key === "Enter") { - if (selected.value != null) { + if (selected.value.length > 0) { handleClickSelect(); } @@ -757,7 +939,14 @@ watch(page, () => { */ const computedSelectDisabled = computed(() => { if (selectedTab.value == "tab-browser") { - return selected.value == null || selected.value.status !== "OK"; + return ( + selected.value.length == 0 || + selected.value.filter((item) => item.status !== "OK").length != 0 + ); + } else if (selectedTab.value == "tab-url") { + return ( + !form.controls.url.isValid() || form.controls.url.value.length == 0 + ); } return true; @@ -813,10 +1002,10 @@ const formatDate = (date) => { const allowEditSelected = computed(() => { return ( - selected.value != null && + lastSelected.value != null && userStore.id && (userHasPermission("admin/media") || - selected.value.user_id == userStore.id) + lastSelected.value.user_id == userStore.id) ); }); @@ -830,11 +1019,11 @@ interface MediaUpdate { const pendingUpdates = ref([]); const handleUpdate = () => { - if (selected.value != null) { + if (lastSelected.value != null) { addUpdate( - selected.value.id, - selected.value.title, - selected.value.description, + lastSelected.value.id, + lastSelected.value.title, + lastSelected.value.description, ); } }; @@ -895,6 +1084,7 @@ const postUpdate = (data: MediaUpdate): void => { }; const forceUpdate = () => { + formLoading.value = true; pendingUpdates.value.forEach((item, index) => { if (pendingUpdates.value[index].timer != null) { clearTimeout(pendingUpdates.value[index].timer); @@ -904,6 +1094,7 @@ const forceUpdate = () => { }); pendingUpdates.value = []; + formLoading.value = false; }; // Get max upload size