304 lines
9.1 KiB
TypeScript
304 lines
9.1 KiB
TypeScript
/**
|
|
* 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<string> = 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<FileCollectionObject[]> = computed(() =>
|
|
nodesStore.getChildCollections(providerId, serviceId, currentLocation.value)
|
|
)
|
|
|
|
const currentEntities: ComputedRef<FileEntityObject[]> = 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<FileCollectionObject> => {
|
|
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<FileEntityObject> => {
|
|
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<boolean> => {
|
|
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<string | null> => {
|
|
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<number> => {
|
|
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
|