diff --git a/resources/js/components/assets/GalleryBrowser.vue b/resources/js/components/assets/GalleryBrowser.vue index 1c0e9702..6095ac50 100644 --- a/resources/js/components/assets/GalleryBrowser.vue +++ b/resources/js/components/assets/GalleryBrowser.vue @@ -14,10 +14,11 @@ import { Skeleton } from '@/components/ui/skeleton'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; import debounce from '@/debounce'; -import { destroy as assetsDestroy, search as assetsSearch, store as assetsStore, storeFromUrl } from '@/routes/app/assets'; +import { destroy as assetsDestroy, search as assetsSearch, storeChunked as assetsStoreChunked, storeFromUrl } from '@/routes/app/assets'; import { search as giphySearch, trending as giphyTrending } from '@/routes/app/assets/giphy'; import { search as unsplashSearch, trending as unsplashTrending } from '@/routes/app/assets/unsplash'; import { store as storePost } from '@/routes/app/posts'; +import { uploadChunked } from '@/utils/chunkedUpload'; interface AssetMedia { id: string; @@ -152,7 +153,6 @@ const fileInput = ref(null); const uploadsSentinel = useTemplateRef('uploadsSentinel'); const isDragging = ref(false); const uploading = ref(false); -const httpUpload = useHttp<{ media: File | null }>({ media: null }); let uploadsObserver: IntersectionObserver | null = null; const fetchUploads = async (page: number, term: string) => { @@ -236,8 +236,11 @@ const uploadFiles = async (files: File[]) => { uploading.value = true; for (const file of files) { try { - httpUpload.media = file; - await httpUpload.post(assetsStore.url()); + await uploadChunked({ + file, + url: assetsStoreChunked.url(), + collection: 'assets', + }); } catch { // ignore individual failure } diff --git a/resources/js/composables/useMediaManager.ts b/resources/js/composables/useMediaManager.ts deleted file mode 100644 index bf2ed106..00000000 --- a/resources/js/composables/useMediaManager.ts +++ /dev/null @@ -1,193 +0,0 @@ -import { ref, type Ref } from 'vue'; - -import { getMediaRulesForContentType } from '@/composables/useMediaRules'; -import { store as storeAsset, storeChunked as storeAssetChunked } from '@/routes/app/assets'; -import { uploadChunked, shouldUseChunkedUpload } from '@/utils/chunkedUpload'; - -export interface MediaItem { - id: string; - path: string; - url: string; - type?: string; - mime_type?: string; - original_filename?: string; -} - -interface PostPlatform { - id: string; - platform: string; - content_type: string | null; - media: MediaItem[]; -} - -interface UseMediaManagerOptions { - synced: Ref; - selectedPlatformIds: Ref; - platformContentTypes: Ref>; - postPlatforms: Ref; -} - -export const useMediaManager = (options: UseMediaManagerOptions) => { - const { synced, selectedPlatformIds, platformContentTypes, postPlatforms } = options; - - const platformMedia = ref>( - Object.fromEntries(postPlatforms.value.map((pp) => [pp.id, pp.media || []])), - ); - const isUploading = ref>({}); - - const isSingleMediaContentType = (platformId: string): boolean => { - const platform = postPlatforms.value.find((pp) => pp.id === platformId); - if (!platform) return false; - - const contentType = platformContentTypes.value[platformId] || platform.content_type; - if (!contentType) return false; - - const rules = getMediaRulesForContentType(contentType); - return rules.maxFiles === 1; - }; - - const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content ?? ''; - - const uploadToAssets = async (file: File): Promise => { - try { - if (shouldUseChunkedUpload(file)) { - const data = await uploadChunked({ - file, - url: storeAssetChunked.url(), - model: 'workspace', - modelId: '', - collection: 'assets', - }); - return { - id: data.id, - path: data.path ?? '', - url: data.url, - type: data.type, - mime_type: data.mime_type, - original_filename: data.original_filename, - }; - } - - const formData = new FormData(); - formData.append('media', file); - - const response = await fetch(storeAsset.url(), { - method: 'POST', - headers: { - Accept: 'application/json', - 'X-CSRF-TOKEN': csrfToken, - 'X-Requested-With': 'XMLHttpRequest', - }, - body: formData, - }); - - if (!response.ok) return null; - - const data = await response.json(); - return { - id: data.id, - path: data.path ?? '', - url: data.url, - type: data.type, - mime_type: data.mime_type, - original_filename: data.original_filename, - }; - } catch { - return null; - } - }; - - const upload = async (files: File[], postPlatformId: string) => { - if (!files || files.length === 0) return; - - const platform = postPlatforms.value.find((pp) => pp.id === postPlatformId); - const contentType = platformContentTypes.value[postPlatformId] || platform?.content_type || ''; - const rules = getMediaRulesForContentType(contentType); - - const targetPlatformIds = synced.value ? selectedPlatformIds.value : [postPlatformId]; - - for (const id of targetPlatformIds) { - isUploading.value[id] = true; - } - - if (rules.maxFiles === 1) { - platformMedia.value[postPlatformId] = []; - } - - const currentMedia = platformMedia.value[postPlatformId] || []; - const remainingSlots = rules.maxFiles - currentMedia.length; - const filesToUpload = Array.from(files).slice(0, remainingSlots); - - if (filesToUpload.length === 0) { - for (const id of targetPlatformIds) { - isUploading.value[id] = false; - } - return; - } - - for (const file of filesToUpload) { - const mediaItem = await uploadToAssets(file); - if (!mediaItem) continue; - - for (const targetId of targetPlatformIds) { - if (isSingleMediaContentType(targetId)) { - platformMedia.value[targetId] = [mediaItem]; - } else { - platformMedia.value[targetId] = [...(platformMedia.value[targetId] || []), mediaItem]; - } - } - } - - for (const id of targetPlatformIds) { - isUploading.value[id] = false; - } - }; - - const remove = (postPlatformId: string, mediaId: string) => { - const targetPlatformIds = synced.value ? selectedPlatformIds.value : [postPlatformId]; - - for (const targetId of targetPlatformIds) { - platformMedia.value[targetId] = (platformMedia.value[targetId] || []).filter((m) => m.id !== mediaId); - } - }; - - const reorder = (postPlatformId: string, mediaIds: string[]) => { - const currentMedia = platformMedia.value[postPlatformId] || []; - const reorderedMedia = mediaIds - .map((id) => currentMedia.find((m) => m.id === id)) - .filter(Boolean) as MediaItem[]; - - const targetPlatformIds = synced.value ? selectedPlatformIds.value : [postPlatformId]; - - for (const targetId of targetPlatformIds) { - if (isSingleMediaContentType(targetId)) { - platformMedia.value[targetId] = reorderedMedia.length > 0 ? [reorderedMedia[0]] : []; - } else { - const targetMedia = platformMedia.value[targetId] || []; - const reorderedIds = reorderedMedia.map((m) => m.id); - platformMedia.value[targetId] = reorderedIds - .map((id) => targetMedia.find((m) => m.id === id) || reorderedMedia.find((m) => m.id === id)) - .filter(Boolean) as MediaItem[]; - } - } - }; - - const getMedia = (platformId: string): MediaItem[] => { - return platformMedia.value[platformId] || []; - }; - - const isUploadingFor = (platformId: string): boolean => { - return isUploading.value[platformId] || false; - }; - - return { - platformMedia, - isUploading, - upload, - remove, - reorder, - getMedia, - isUploadingFor, - isSingleMediaContentType, - }; -}; diff --git a/resources/js/utils/chunkedUpload.ts b/resources/js/utils/chunkedUpload.ts index 1ada0f15..0114cef2 100644 --- a/resources/js/utils/chunkedUpload.ts +++ b/resources/js/utils/chunkedUpload.ts @@ -85,9 +85,3 @@ export const uploadChunked = async (options: ChunkedUploadOptions): Promise { - return file.size > CHUNKED_UPLOAD_THRESHOLD; -};