/** * File Manager composable for convenient file/folder operations * Provides reactive access to file manager state and actions */ import { computed, ref, unref } from 'vue' import type { Ref, ComputedRef } from 'vue' import { useProvidersStore } from '@DocumentsManager/stores/providersStore' import { useServicesStore } from '@DocumentsManager/stores/servicesStore' import { useNodesStore, ROOT_ID } from '@DocumentsManager/stores/nodesStore' import type { ListFilter, ListSort, ListRange } from '@DocumentsManager/types/common' import type { DocumentInterface } from '@DocumentsManager/types/document' import { CollectionObject } from '@DocumentsManager/models/collection' import { EntityObject } from '@DocumentsManager/models/entity' // Base URL for file manager transfer endpoints const TRANSFER_BASE_URL = '/m/documents_manager' export interface UseFileManagerOptions { providerId: string | Ref | ComputedRef serviceId: string | Ref | ComputedRef autoFetch?: boolean } export function useFileManager(options: UseFileManagerOptions) { const providersStore = useProvidersStore() const servicesStore = useServicesStore() const nodesStore = useNodesStore() const { providerId, serviceId, autoFetch = false } = options const currentProviderId = () => unref(providerId) const currentServiceId = () => unref(serviceId) // Current location (folder being viewed) const currentLocation: Ref = ref(ROOT_ID) // Loading/error state const isLoading = computed(() => nodesStore.transceiving) const error = computed(() => nodesStore.error) // Provider and service const provider = computed(() => providersStore.provider(currentProviderId())) const service = computed(() => servicesStore.service(currentProviderId(), currentServiceId())) const rootId = computed(() => ROOT_ID) // Current children const currentChildren = computed(() => nodesStore.getChildren(currentProviderId(), currentServiceId(), currentLocation.value) ) const currentCollections: ComputedRef = computed(() => nodesStore.getChildCollections(currentProviderId(), currentServiceId(), currentLocation.value) ) const currentEntities: ComputedRef = computed(() => nodesStore.getChildEntities(currentProviderId(), currentServiceId(), currentLocation.value) ) // Breadcrumb path const breadcrumbs = computed(() => { if (currentLocation.value === ROOT_ID) { return [] } return nodesStore.getPath(currentProviderId(), currentServiceId(), 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(currentProviderId(), currentServiceId(), currentLocation.value) if (currentNode) { await navigateTo(currentNode.collection ? String(currentNode.collection) : ROOT_ID) } } // Navigate to root const navigateToRoot = async () => { await navigateTo(ROOT_ID) } // Refresh current location const refresh = async ( filter?: ListFilter, sort?: ListSort, range?: ListRange ) => { await nodesStore.fetchNodes( currentProviderId(), currentServiceId(), currentLocation.value === ROOT_ID ? ROOT_ID : currentLocation.value, filter, sort, range ) } // Create a new folder const createFolder = async (label: string): Promise => { return await nodesStore.createCollection( currentProviderId(), currentServiceId(), currentLocation.value === ROOT_ID ? ROOT_ID : currentLocation.value, { label, owner: '' } ) } // Create a new file const createFile = async ( label: string, mime: string = 'application/octet-stream' ): Promise => { const properties: DocumentInterface = { '@type': 'documents.properties', urid: null, size: 0, label, mime, format: null, encoding: null, } return await nodesStore.createEntity( currentProviderId(), currentServiceId(), currentLocation.value === ROOT_ID ? ROOT_ID : currentLocation.value, properties ) } // Rename a node const renameNode = async (nodeId: string, newLabel: string) => { const node = nodesStore.getNode(currentProviderId(), currentServiceId(), nodeId) if (!node) { throw new Error('Node not found') } if (node instanceof CollectionObject) { return await nodesStore.updateCollection(currentProviderId(), currentServiceId(), nodeId, { label: newLabel, owner: node.properties.owner, }) } else { const properties: DocumentInterface = { '@type': 'documents.properties', urid: node.properties.urid, size: node.properties.size, label: newLabel, mime: node.properties.mime, format: node.properties.format, encoding: node.properties.encoding, } return await nodesStore.updateEntity(currentProviderId(), currentServiceId(), node.collection, nodeId, properties) } } // Delete a node const deleteNode = async (nodeId: string): Promise => { const node = nodesStore.getNode(currentProviderId(), currentServiceId(), nodeId) if (!node) { throw new Error('Node not found') } if (node instanceof CollectionObject) { return await nodesStore.deleteCollection(currentProviderId(), currentServiceId(), nodeId) } else { return await nodesStore.deleteEntity(currentProviderId(), currentServiceId(), node.collection, nodeId) } } // Read file content const readFile = async (entityId: string): Promise => { const node = nodesStore.getNode(currentProviderId(), currentServiceId(), entityId) if (!node || !(node instanceof EntityObject)) { throw new Error('Entity not found') } return await nodesStore.readEntity(currentProviderId(), currentServiceId(), node.collection || ROOT_ID, entityId) } // Write file content const writeFile = async (entityId: string, content: string): Promise => { const node = nodesStore.getNode(currentProviderId(), currentServiceId(), entityId) if (!node || !(node instanceof EntityObject)) { throw new Error('Entity not found') } return await nodesStore.writeEntity(currentProviderId(), currentServiceId(), node.collection, entityId, content) } // Get a URL suitable for inline viewing (img src / video src) const getEntityUrl = (entityId: string, collectionId?: string | null): string => { const collection = collectionId ?? currentLocation.value return `${TRANSFER_BASE_URL}/download/entity/${encodeURIComponent(currentProviderId())}/${encodeURIComponent(currentServiceId())}/${encodeURIComponent(collection)}/${encodeURIComponent(entityId)}` } // 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(currentProviderId())}/${encodeURIComponent(currentServiceId())}/${encodeURIComponent(collection)}/${encodeURIComponent(entityId)}` // Trigger download by opening URL (browser handles it) window.open(url, '_blank') } const downloadCollection = (collectionId: string): void => { // Use path parameters: /download/collection/{provider}/{service}/{identifier} const url = `${TRANSFER_BASE_URL}/download/collection/${encodeURIComponent(currentProviderId())}/${encodeURIComponent(currentServiceId())}/${encodeURIComponent(collectionId)}` window.open(url, '_blank') } const downloadArchive = (ids: string[], name: string = 'download', collectionId?: string | null): void => { const collection = collectionId ?? currentLocation.value const params = new URLSearchParams({ provider: currentProviderId(), service: currentServiceId(), }) 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 () => { await providersStore.list() await servicesStore.list({ [currentProviderId()]: true }) 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, readFile, writeFile, getEntityUrl, downloadEntity, downloadCollection, downloadArchive, // Initialize initialize, // Constants ROOT_ID, } } export default useFileManager