/** * File Manager composable for convenient file/folder operations * Provides reactive access to file manager state and actions */ import { computed, ref } from 'vue' import type { Ref, ComputedRef } from 'vue' import { useProvidersStore } from '@FilesManager/stores/providersStore' import { useServicesStore } from '@FilesManager/stores/servicesStore' import { useNodesStore, ROOT_ID } from '@FilesManager/stores/nodesStore' import type { FilterCondition, SortCondition, RangeCondition } from '@FilesManager/types/common' import { FileCollectionObject } from '@FilesManager/models/collection' import { FileEntityObject } from '@FilesManager/models/entity' // Base URL for file manager transfer endpoints const TRANSFER_BASE_URL = '/m/file_manager' export interface UseFileManagerOptions { providerId: string serviceId: string autoFetch?: boolean } export function useFileManager(options: UseFileManagerOptions) { const providersStore = useProvidersStore() const servicesStore = useServicesStore() const nodesStore = useNodesStore() const { providerId, serviceId, autoFetch = false } = options // Current location (folder being viewed) const currentLocation: Ref = ref(ROOT_ID) // Loading/error state const isLoading = computed(() => nodesStore.loading) const error = computed(() => nodesStore.error) // Provider and service const provider = computed(() => providersStore.getProvider(providerId)) const service = computed(() => servicesStore.getService(providerId, serviceId)) const rootId = computed(() => servicesStore.getRootId(providerId, serviceId) || ROOT_ID) // Current children const currentChildren = computed(() => nodesStore.getChildren(providerId, serviceId, currentLocation.value) ) const currentCollections: ComputedRef = computed(() => nodesStore.getChildCollections(providerId, serviceId, currentLocation.value) ) const currentEntities: ComputedRef = computed(() => nodesStore.getChildEntities(providerId, serviceId, currentLocation.value) ) // Breadcrumb path const breadcrumbs = computed(() => { if (currentLocation.value === ROOT_ID) { return [] } return nodesStore.getPath(providerId, serviceId, currentLocation.value) }) // Is at root? const isAtRoot = computed(() => currentLocation.value === ROOT_ID) // Navigate to a folder const navigateTo = async (collectionId: string | null) => { currentLocation.value = collectionId || ROOT_ID await refresh() } // Navigate up one level const navigateUp = async () => { if (currentLocation.value === ROOT_ID) { return } const currentNode = nodesStore.getNode(providerId, serviceId, currentLocation.value) if (currentNode) { await navigateTo(currentNode.in || ROOT_ID) } } // Navigate to root const navigateToRoot = async () => { await navigateTo(ROOT_ID) } // Refresh current location const refresh = async ( filter?: FilterCondition[] | null, sort?: SortCondition[] | null, range?: RangeCondition | null ) => { await nodesStore.fetchNodes( providerId, serviceId, currentLocation.value === ROOT_ID ? null : currentLocation.value, false, filter, sort, range ) } // Create a new folder const createFolder = async (label: string): Promise => { return await nodesStore.createCollection( providerId, serviceId, currentLocation.value === ROOT_ID ? ROOT_ID : currentLocation.value, { label } ) } // Create a new file const createFile = async ( label: string, mime: string = 'application/octet-stream' ): Promise => { return await nodesStore.createEntity( providerId, serviceId, currentLocation.value === ROOT_ID ? ROOT_ID : currentLocation.value, { label, mime } ) } // Rename a node const renameNode = async (nodeId: string, newLabel: string) => { const node = nodesStore.getNode(providerId, serviceId, nodeId) if (!node) { throw new Error('Node not found') } if (node['@type'] === 'files.collection') { return await nodesStore.modifyCollection(providerId, serviceId, nodeId, { label: newLabel }) } else { return await nodesStore.modifyEntity(providerId, serviceId, node.in, nodeId, { label: newLabel }) } } // Delete a node const deleteNode = async (nodeId: string): Promise => { const node = nodesStore.getNode(providerId, serviceId, nodeId) if (!node) { throw new Error('Node not found') } if (node['@type'] === 'files.collection') { return await nodesStore.destroyCollection(providerId, serviceId, nodeId) } else { return await nodesStore.destroyEntity(providerId, serviceId, node.in, nodeId) } } // Copy a node const copyNode = async (nodeId: string, destinationId?: string | null) => { const node = nodesStore.getNode(providerId, serviceId, nodeId) if (!node) { throw new Error('Node not found') } const destination = destinationId ?? currentLocation.value if (node['@type'] === 'files.collection') { return await nodesStore.copyCollection(providerId, serviceId, nodeId, destination) } else { return await nodesStore.copyEntity(providerId, serviceId, node.in, nodeId, destination) } } // Move a node const moveNode = async (nodeId: string, destinationId?: string | null) => { const node = nodesStore.getNode(providerId, serviceId, nodeId) if (!node) { throw new Error('Node not found') } const destination = destinationId ?? currentLocation.value if (node['@type'] === 'files.collection') { return await nodesStore.moveCollection(providerId, serviceId, nodeId, destination) } else { return await nodesStore.moveEntity(providerId, serviceId, node.in, nodeId, destination) } } // Read file content const readFile = async (entityId: string): Promise => { const node = nodesStore.getNode(providerId, serviceId, entityId) if (!node || node['@type'] !== 'files.entity') { throw new Error('Entity not found') } return await nodesStore.readEntity(providerId, serviceId, node.in || ROOT_ID, entityId) } // Write file content const writeFile = async (entityId: string, content: string): Promise => { const node = nodesStore.getNode(providerId, serviceId, entityId) if (!node || node['@type'] !== 'files.entity') { throw new Error('Entity not found') } return await nodesStore.writeEntity(providerId, serviceId, node.in, entityId, content) } // Download a single file const downloadEntity = (entityId: string, collectionId?: string | null): void => { const collection = collectionId ?? currentLocation.value // Use path parameters: /download/entity/{provider}/{service}/{collection}/{identifier} const url = `${TRANSFER_BASE_URL}/download/entity/${encodeURIComponent(providerId)}/${encodeURIComponent(serviceId)}/${encodeURIComponent(collection)}/${encodeURIComponent(entityId)}` // Trigger download by opening URL (browser handles it) window.open(url, '_blank') } // Download a collection (folder) as ZIP const downloadCollection = (collectionId: string): void => { // Use path parameters: /download/collection/{provider}/{service}/{identifier} const url = `${TRANSFER_BASE_URL}/download/collection/${encodeURIComponent(providerId)}/${encodeURIComponent(serviceId)}/${encodeURIComponent(collectionId)}` window.open(url, '_blank') } // Download multiple items as ZIP archive const downloadArchive = (ids: string[], name: string = 'download', collectionId?: string | null): void => { const collection = collectionId ?? currentLocation.value const params = new URLSearchParams({ provider: providerId, service: serviceId, }) ids.forEach(id => params.append('ids[]', id)) if (name) { params.append('name', name) } if (collection && collection !== ROOT_ID) { params.append('collection', collection) } const url = `${TRANSFER_BASE_URL}/download/archive?${params.toString()}` window.open(url, '_blank') } // Initialize - fetch providers, services, and initial nodes if autoFetch const initialize = async () => { if (!providersStore.initialized) { await providersStore.fetchProviders() } if (!servicesStore.initialized) { await servicesStore.fetchServices() } if (autoFetch) { await refresh() } } return { // State currentLocation, isLoading, error, // Provider/Service provider, service, rootId, // Current view currentChildren, currentCollections, currentEntities, breadcrumbs, isAtRoot, // Navigation navigateTo, navigateUp, navigateToRoot, refresh, // Operations createFolder, createFile, renameNode, deleteNode, copyNode, moveNode, readFile, writeFile, downloadEntity, downloadCollection, downloadArchive, // Initialize initialize, // Constants ROOT_ID, } } export default useFileManager