refactor: move manager to code to documents manager
Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
@@ -1,13 +1,18 @@
|
|||||||
/**
|
/**
|
||||||
* Central export point for all Files module composables
|
* Central export point for all Files module composables
|
||||||
|
*
|
||||||
|
* useDocumentManager/useDocumentSelection live in documents_manager (they only
|
||||||
|
* ever depended on its SDK, never on this module's own code) so other modules
|
||||||
|
* can reuse them too; re-exported here so existing imports from
|
||||||
|
* '@/composables' keep working unchanged.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export { useFileManager } from './useFileManager'
|
export { useDocumentManager } from '@DocumentsManager/composables/useDocumentManager'
|
||||||
export { useFileSelection } from './useFileSelection'
|
export { useDocumentSelection } from '@DocumentsManager/composables/useDocumentSelection'
|
||||||
export { useFileUpload } from './useFileUpload'
|
export { useFileUpload } from './useFileUpload'
|
||||||
export { useFileEditor } from './useFileEditor'
|
export { useFileEditor } from './useFileEditor'
|
||||||
|
|
||||||
export type { UseFileManagerOptions } from './useFileManager'
|
export type { UseDocumentManagerOptions } from '@DocumentsManager/composables/useDocumentManager'
|
||||||
export type { UseFileSelectionOptions } from './useFileSelection'
|
export type { UseDocumentSelectionOptions } from '@DocumentsManager/composables/useDocumentSelection'
|
||||||
export type { UseFileUploadOptions, FileUploadProgress } from './useFileUpload'
|
export type { UseFileUploadOptions, FileUploadProgress } from './useFileUpload'
|
||||||
export type { UseFileEditorReturn } from './useFileEditor'
|
export type { UseFileEditorReturn } from './useFileEditor'
|
||||||
|
|||||||
@@ -1,295 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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<string> | ComputedRef<string>
|
|
||||||
serviceId: string | Ref<string> | ComputedRef<string>
|
|
||||||
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<string> = 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<CollectionObject[]> = computed(() =>
|
|
||||||
nodesStore.getChildCollections(currentProviderId(), currentServiceId(), currentLocation.value)
|
|
||||||
)
|
|
||||||
|
|
||||||
const currentEntities: ComputedRef<EntityObject[]> = 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<CollectionObject> => {
|
|
||||||
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<EntityObject> => {
|
|
||||||
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<boolean> => {
|
|
||||||
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<string | null> => {
|
|
||||||
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<number> => {
|
|
||||||
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([currentProviderId()])
|
|
||||||
await servicesStore.list()
|
|
||||||
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
|
|
||||||
@@ -1,135 +0,0 @@
|
|||||||
/**
|
|
||||||
* File selection composable
|
|
||||||
* Provides reactive selection management for file manager
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { ref, computed } from 'vue'
|
|
||||||
import type { Ref, ComputedRef } from 'vue'
|
|
||||||
import { CollectionObject } from '@DocumentsManager/models/collection'
|
|
||||||
import { EntityObject } from '@DocumentsManager/models/entity'
|
|
||||||
|
|
||||||
type NodeRecord = CollectionObject | EntityObject
|
|
||||||
|
|
||||||
export interface UseFileSelectionOptions {
|
|
||||||
multiple?: boolean
|
|
||||||
allowFolders?: boolean
|
|
||||||
allowFiles?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useFileSelection(options: UseFileSelectionOptions = {}) {
|
|
||||||
const {
|
|
||||||
multiple = true,
|
|
||||||
allowFolders = true,
|
|
||||||
allowFiles = true,
|
|
||||||
} = options
|
|
||||||
|
|
||||||
const selectedIds: Ref<Set<string>> = ref(new Set())
|
|
||||||
const selectedNodes: Ref<Map<string, NodeRecord>> = ref(new Map())
|
|
||||||
|
|
||||||
const count: ComputedRef<number> = computed(() => selectedIds.value.size)
|
|
||||||
const hasSelection: ComputedRef<boolean> = computed(() => selectedIds.value.size > 0)
|
|
||||||
|
|
||||||
const selectedIdArray: ComputedRef<string[]> = computed(() => Array.from(selectedIds.value))
|
|
||||||
const selectedNodeArray: ComputedRef<NodeRecord[]> = computed(() => Array.from(selectedNodes.value.values()))
|
|
||||||
|
|
||||||
const selectedCollections: ComputedRef<CollectionObject[]> = computed(() =>
|
|
||||||
selectedNodeArray.value.filter((node): node is CollectionObject => node instanceof CollectionObject),
|
|
||||||
)
|
|
||||||
|
|
||||||
const selectedEntities: ComputedRef<EntityObject[]> = computed(() =>
|
|
||||||
selectedNodeArray.value.filter((node): node is EntityObject => node instanceof EntityObject),
|
|
||||||
)
|
|
||||||
|
|
||||||
const isSelected = (nodeId: string): boolean => selectedIds.value.has(nodeId)
|
|
||||||
|
|
||||||
const isTypeAllowed = (node: NodeRecord): boolean => {
|
|
||||||
if (node instanceof CollectionObject && !allowFolders) return false
|
|
||||||
if (node instanceof EntityObject && !allowFiles) return false
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
const select = (node: NodeRecord) => {
|
|
||||||
if (!isTypeAllowed(node)) return
|
|
||||||
|
|
||||||
if (!multiple) {
|
|
||||||
selectedIds.value.clear()
|
|
||||||
selectedNodes.value.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
const nodeId = String(node.identifier)
|
|
||||||
selectedIds.value.add(nodeId)
|
|
||||||
selectedNodes.value.set(nodeId, node)
|
|
||||||
}
|
|
||||||
|
|
||||||
const deselect = (nodeId: string) => {
|
|
||||||
selectedIds.value.delete(nodeId)
|
|
||||||
selectedNodes.value.delete(nodeId)
|
|
||||||
}
|
|
||||||
|
|
||||||
const toggle = (node: NodeRecord) => {
|
|
||||||
const nodeId = String(node.identifier)
|
|
||||||
if (isSelected(nodeId)) {
|
|
||||||
deselect(nodeId)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
select(node)
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectMultiple = (nodes: NodeRecord[]) => {
|
|
||||||
if (!multiple) {
|
|
||||||
const lastNode = nodes[nodes.length - 1]
|
|
||||||
if (lastNode && isTypeAllowed(lastNode)) {
|
|
||||||
selectedIds.value.clear()
|
|
||||||
selectedNodes.value.clear()
|
|
||||||
const nodeId = String(lastNode.identifier)
|
|
||||||
selectedIds.value.add(nodeId)
|
|
||||||
selectedNodes.value.set(nodeId, lastNode)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const node of nodes) {
|
|
||||||
if (isTypeAllowed(node)) {
|
|
||||||
const nodeId = String(node.identifier)
|
|
||||||
selectedIds.value.add(nodeId)
|
|
||||||
selectedNodes.value.set(nodeId, node)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectAll = (nodes: NodeRecord[]) => {
|
|
||||||
if (!multiple) return
|
|
||||||
selectMultiple(nodes)
|
|
||||||
}
|
|
||||||
|
|
||||||
const clear = () => {
|
|
||||||
selectedIds.value.clear()
|
|
||||||
selectedNodes.value.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
const setSelection = (nodes: NodeRecord[]) => {
|
|
||||||
clear()
|
|
||||||
selectMultiple(nodes)
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
selectedIds,
|
|
||||||
selectedNodes,
|
|
||||||
count,
|
|
||||||
hasSelection,
|
|
||||||
selectedIdArray,
|
|
||||||
selectedNodeArray,
|
|
||||||
selectedCollections,
|
|
||||||
selectedEntities,
|
|
||||||
isSelected,
|
|
||||||
select,
|
|
||||||
deselect,
|
|
||||||
toggle,
|
|
||||||
selectMultiple,
|
|
||||||
selectAll,
|
|
||||||
clear,
|
|
||||||
setSelection,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default useFileSelection
|
|
||||||
+30
-30
@@ -2,7 +2,7 @@
|
|||||||
import { ref, computed, onMounted, watch, nextTick, defineAsyncComponent } from 'vue'
|
import { ref, computed, onMounted, watch, nextTick, defineAsyncComponent } from 'vue'
|
||||||
import { useDisplay } from 'vuetify'
|
import { useDisplay } from 'vuetify'
|
||||||
import { useModuleStore, useIntegrationStore } from '@KTXC'
|
import { useModuleStore, useIntegrationStore } from '@KTXC'
|
||||||
import { useFileManager, useFileSelection, useFileUpload, useFileEditor } from '@/composables'
|
import { useDocumentManager, useDocumentSelection, useFileUpload, useFileEditor } from '@/composables'
|
||||||
import type { ViewMode, SortField, SortOrder, BreadcrumbItem } from '@/types'
|
import type { ViewMode, SortField, SortOrder, BreadcrumbItem } from '@/types'
|
||||||
import { CollectionObject } from '@DocumentsManager/models/collection'
|
import { CollectionObject } from '@DocumentsManager/models/collection'
|
||||||
import { EntityObject } from '@DocumentsManager/models/entity'
|
import { EntityObject } from '@DocumentsManager/models/entity'
|
||||||
@@ -42,13 +42,13 @@ const activeProviderId = ref('default')
|
|||||||
const activeServiceId = ref('personal')
|
const activeServiceId = ref('personal')
|
||||||
|
|
||||||
// File manager composable
|
// File manager composable
|
||||||
const fileManager = useFileManager({
|
const documentManager = useDocumentManager({
|
||||||
providerId: activeProviderId,
|
providerId: activeProviderId,
|
||||||
serviceId: activeServiceId,
|
serviceId: activeServiceId,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Selection composable
|
// Selection composable
|
||||||
const selection = useFileSelection({ multiple: true })
|
const selection = useDocumentSelection({ multiple: true })
|
||||||
|
|
||||||
// Upload composable
|
// Upload composable
|
||||||
const upload = useFileUpload({
|
const upload = useFileUpload({
|
||||||
@@ -57,11 +57,11 @@ const upload = useFileUpload({
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Keep upload collection in sync with current location
|
// Keep upload collection in sync with current location
|
||||||
watch(() => fileManager.currentLocation.value, (newLocation) => {
|
watch(() => documentManager.currentLocation.value, (newLocation) => {
|
||||||
upload.setCollection(newLocation)
|
upload.setCollection(newLocation)
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(() => fileManager.error.value, (message) => {
|
watch(() => documentManager.error.value, (message) => {
|
||||||
if (message) {
|
if (message) {
|
||||||
notify(message, 'error')
|
notify(message, 'error')
|
||||||
}
|
}
|
||||||
@@ -129,11 +129,11 @@ function entityToViewerItem(entity: EntityObject): ViewerItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function resolveViewerSource(item: ViewerItem): string {
|
function resolveViewerSource(item: ViewerItem): string {
|
||||||
return fileManager.getEntityUrl(item.id, (item.meta?.collection as string | null) ?? null)
|
return documentManager.getEntityUrl(item.id, (item.meta?.collection as string | null) ?? null)
|
||||||
}
|
}
|
||||||
|
|
||||||
function downloadViewerItem(item: ViewerItem): void {
|
function downloadViewerItem(item: ViewerItem): void {
|
||||||
fileManager.downloadEntity(item.id, (item.meta?.collection as string | null) ?? null)
|
documentManager.downloadEntity(item.id, (item.meta?.collection as string | null) ?? null)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Editor state
|
// Editor state
|
||||||
@@ -241,10 +241,10 @@ function renameCurrentName(node: CollectionObject | EntityObject | null): string
|
|||||||
// Computed
|
// Computed
|
||||||
const breadcrumbs = computed<BreadcrumbItem[]>(() => {
|
const breadcrumbs = computed<BreadcrumbItem[]>(() => {
|
||||||
const items: BreadcrumbItem[] = [
|
const items: BreadcrumbItem[] = [
|
||||||
{ id: fileManager.ROOT_ID, label: 'Home', isRoot: true }
|
{ id: documentManager.ROOT_ID, label: 'Home', isRoot: true }
|
||||||
]
|
]
|
||||||
|
|
||||||
for (const node of fileManager.breadcrumbs.value) {
|
for (const node of documentManager.breadcrumbs.value) {
|
||||||
items.push({
|
items.push({
|
||||||
id: nodeId(node),
|
id: nodeId(node),
|
||||||
label: nodeLabel(node),
|
label: nodeLabel(node),
|
||||||
@@ -256,8 +256,8 @@ const breadcrumbs = computed<BreadcrumbItem[]>(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const sortedItems = computed(() => {
|
const sortedItems = computed(() => {
|
||||||
const collections = [...fileManager.currentCollections.value]
|
const collections = [...documentManager.currentCollections.value]
|
||||||
const entities = [...fileManager.currentEntities.value]
|
const entities = [...documentManager.currentEntities.value]
|
||||||
|
|
||||||
// Sort collections
|
// Sort collections
|
||||||
collections.sort((a, b) => {
|
collections.sort((a, b) => {
|
||||||
@@ -375,8 +375,8 @@ watch([activeProviderId, activeServiceId], async ([providerId, serviceId], [prev
|
|||||||
}
|
}
|
||||||
|
|
||||||
selection.clear()
|
selection.clear()
|
||||||
upload.setCollection(fileManager.rootId.value)
|
upload.setCollection(documentManager.rootId.value)
|
||||||
await fileManager.navigateToRoot()
|
await documentManager.navigateToRoot()
|
||||||
}, { flush: 'post' })
|
}, { flush: 'post' })
|
||||||
|
|
||||||
async function handleServiceSelect(service: ServiceObject) {
|
async function handleServiceSelect(service: ServiceObject) {
|
||||||
@@ -398,12 +398,12 @@ async function handleServiceSelect(service: ServiceObject) {
|
|||||||
// Navigation methods
|
// Navigation methods
|
||||||
async function handleBreadcrumbNavigate(item: BreadcrumbItem) {
|
async function handleBreadcrumbNavigate(item: BreadcrumbItem) {
|
||||||
selection.clear()
|
selection.clear()
|
||||||
await fileManager.navigateTo(item.isRoot ? null : item.id)
|
await documentManager.navigateTo(item.isRoot ? null : item.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleFolderOpen(folder: CollectionObject) {
|
async function handleFolderOpen(folder: CollectionObject) {
|
||||||
selection.clear()
|
selection.clear()
|
||||||
await fileManager.navigateTo(nodeId(folder))
|
await documentManager.navigateTo(nodeId(folder))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Item interaction methods
|
// Item interaction methods
|
||||||
@@ -440,7 +440,7 @@ function handleShowDetails(item: CollectionObject | EntityObject) {
|
|||||||
// Folder operations
|
// Folder operations
|
||||||
async function handleCreateFolder(name: string) {
|
async function handleCreateFolder(name: string) {
|
||||||
try {
|
try {
|
||||||
await fileManager.createFolder(name)
|
await documentManager.createFolder(name)
|
||||||
showNewFolderDialog.value = false
|
showNewFolderDialog.value = false
|
||||||
notify(`Folder "${name}" created`, 'success')
|
notify(`Folder "${name}" created`, 'success')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -460,7 +460,7 @@ async function handleRename(newName: string) {
|
|||||||
const currentName = renameCurrentName(nodeToRename.value)
|
const currentName = renameCurrentName(nodeToRename.value)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await fileManager.renameNode(nodeId(nodeToRename.value), newName)
|
await documentManager.renameNode(nodeId(nodeToRename.value), newName)
|
||||||
showRenameDialog.value = false
|
showRenameDialog.value = false
|
||||||
nodeToRename.value = null
|
nodeToRename.value = null
|
||||||
notify(`Renamed "${currentName}" to "${newName}"`, 'success')
|
notify(`Renamed "${currentName}" to "${newName}"`, 'success')
|
||||||
@@ -481,7 +481,7 @@ async function handleDelete() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
for (const node of nodesToDelete.value) {
|
for (const node of nodesToDelete.value) {
|
||||||
await fileManager.deleteNode(nodeId(node))
|
await documentManager.deleteNode(nodeId(node))
|
||||||
}
|
}
|
||||||
selection.clear()
|
selection.clear()
|
||||||
showDeleteDialog.value = false
|
showDeleteDialog.value = false
|
||||||
@@ -500,11 +500,11 @@ async function handleDelete() {
|
|||||||
function handleDownloadItem(item: CollectionObject | EntityObject) {
|
function handleDownloadItem(item: CollectionObject | EntityObject) {
|
||||||
if (item instanceof EntityObject) {
|
if (item instanceof EntityObject) {
|
||||||
// Download single file
|
// Download single file
|
||||||
fileManager.downloadEntity(nodeId(item), String(item.collection ?? fileManager.ROOT_ID))
|
documentManager.downloadEntity(nodeId(item), String(item.collection ?? documentManager.ROOT_ID))
|
||||||
notify(`Download started for "${nodeLabel(item)}"`, 'info')
|
notify(`Download started for "${nodeLabel(item)}"`, 'info')
|
||||||
} else if (item instanceof CollectionObject) {
|
} else if (item instanceof CollectionObject) {
|
||||||
// Download folder as ZIP
|
// Download folder as ZIP
|
||||||
fileManager.downloadCollection(nodeId(item))
|
documentManager.downloadCollection(nodeId(item))
|
||||||
notify(`Archive download started for "${nodeLabel(item)}"`, 'info')
|
notify(`Archive download started for "${nodeLabel(item)}"`, 'info')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -656,7 +656,7 @@ async function readEntriesRecursively(
|
|||||||
async function handleUploadAll() {
|
async function handleUploadAll() {
|
||||||
try {
|
try {
|
||||||
await upload.uploadAll()
|
await upload.uploadAll()
|
||||||
await fileManager.refresh()
|
await documentManager.refresh()
|
||||||
notify('Uploads completed', 'success')
|
notify('Uploads completed', 'success')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to upload files:', error)
|
console.error('Failed to upload files:', error)
|
||||||
@@ -673,7 +673,7 @@ function handleUploadDialogClose() {
|
|||||||
// Refresh
|
// Refresh
|
||||||
async function handleRefresh() {
|
async function handleRefresh() {
|
||||||
try {
|
try {
|
||||||
await fileManager.refresh()
|
await documentManager.refresh()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to refresh files:', error)
|
console.error('Failed to refresh files:', error)
|
||||||
notify(getErrorMessage(error, 'Failed to refresh files'), 'error')
|
notify(getErrorMessage(error, 'Failed to refresh files'), 'error')
|
||||||
@@ -683,10 +683,10 @@ async function handleRefresh() {
|
|||||||
// Initialize
|
// Initialize
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
try {
|
try {
|
||||||
await fileManager.initialize()
|
await documentManager.initialize()
|
||||||
await servicesStore.list()
|
await servicesStore.list()
|
||||||
syncActiveService(availableServices.value)
|
syncActiveService(availableServices.value)
|
||||||
await fileManager.refresh()
|
await documentManager.refresh()
|
||||||
console.log('[Files] - Initialized with:', {
|
console.log('[Files] - Initialized with:', {
|
||||||
provider: activeProviderId.value,
|
provider: activeProviderId.value,
|
||||||
service: activeServiceId.value,
|
service: activeServiceId.value,
|
||||||
@@ -723,7 +723,7 @@ onMounted(async () => {
|
|||||||
<FilesToolbar
|
<FilesToolbar
|
||||||
v-model:search-query="searchQuery"
|
v-model:search-query="searchQuery"
|
||||||
v-model:view-mode="viewMode"
|
v-model:view-mode="viewMode"
|
||||||
:is-loading="fileManager.isLoading.value"
|
:is-loading="documentManager.isLoading.value"
|
||||||
@toggle-sidebar="sidebarVisible = !sidebarVisible"
|
@toggle-sidebar="sidebarVisible = !sidebarVisible"
|
||||||
@refresh="handleRefresh"
|
@refresh="handleRefresh"
|
||||||
@open-file-picker="openFilePicker"
|
@open-file-picker="openFilePicker"
|
||||||
@@ -747,7 +747,7 @@ onMounted(async () => {
|
|||||||
:active-provider-id="activeProviderId"
|
:active-provider-id="activeProviderId"
|
||||||
:active-service-id="activeServiceId"
|
:active-service-id="activeServiceId"
|
||||||
:services="availableServices"
|
:services="availableServices"
|
||||||
@navigate-home="fileManager.navigateToRoot()"
|
@navigate-home="documentManager.navigateToRoot()"
|
||||||
@select-service="handleServiceSelect"
|
@select-service="handleServiceSelect"
|
||||||
@new-folder="showNewFolderDialog = true"
|
@new-folder="showNewFolderDialog = true"
|
||||||
@open-file-picker="openFilePicker"
|
@open-file-picker="openFilePicker"
|
||||||
@@ -766,7 +766,7 @@ onMounted(async () => {
|
|||||||
|
|
||||||
<!-- Loading -->
|
<!-- Loading -->
|
||||||
<v-progress-linear
|
<v-progress-linear
|
||||||
v-if="fileManager.isLoading.value"
|
v-if="documentManager.isLoading.value"
|
||||||
indeterminate
|
indeterminate
|
||||||
color="primary"
|
color="primary"
|
||||||
class="mx-4"
|
class="mx-4"
|
||||||
@@ -774,7 +774,7 @@ onMounted(async () => {
|
|||||||
|
|
||||||
<div class="files-view-panel">
|
<div class="files-view-panel">
|
||||||
<!-- Empty state -->
|
<!-- Empty state -->
|
||||||
<FilesEmptyState v-if="!hasItems && !fileManager.isLoading.value" />
|
<FilesEmptyState v-if="!hasItems && !documentManager.isLoading.value" />
|
||||||
|
|
||||||
<!-- Grid View -->
|
<!-- Grid View -->
|
||||||
<FilesGridView
|
<FilesGridView
|
||||||
@@ -911,8 +911,8 @@ onMounted(async () => {
|
|||||||
<FileEditorDialog
|
<FileEditorDialog
|
||||||
v-model="showEditor"
|
v-model="showEditor"
|
||||||
:entity="editorEntity"
|
:entity="editorEntity"
|
||||||
:read-file="fileManager.readFile"
|
:read-file="documentManager.readFile"
|
||||||
:write-file="fileManager.writeFile"
|
:write-file="documentManager.writeFile"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<v-snackbar
|
<v-snackbar
|
||||||
|
|||||||
@@ -1,71 +1,7 @@
|
|||||||
import { EntityObject } from '@DocumentsManager/models/entity'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the appropriate icon for a file based on its MIME type
|
* These helpers only ever depended on documents_manager's EntityObject model,
|
||||||
|
* never on this module's own code, so they live in documents_manager (where
|
||||||
|
* other modules can reuse them too); re-exported here so existing imports
|
||||||
|
* from '@/utils/fileHelpers' keep working unchanged.
|
||||||
*/
|
*/
|
||||||
export function getFileIcon(entity: EntityObject): string {
|
export { getFileIcon, formatSize, formatDate, getUploadStatusIcon, getUploadStatusColor } from '@DocumentsManager/utils/fileHelpers'
|
||||||
const mime = entity.properties.mime || ''
|
|
||||||
if (!mime) return 'mdi-file'
|
|
||||||
if (mime.startsWith('image/')) return 'mdi-file-image'
|
|
||||||
if (mime.startsWith('video/')) return 'mdi-file-video'
|
|
||||||
if (mime.startsWith('audio/')) return 'mdi-file-music'
|
|
||||||
if (mime.startsWith('text/')) return 'mdi-file-document'
|
|
||||||
if (mime === 'application/pdf') return 'mdi-file-pdf-box'
|
|
||||||
if (mime.includes('zip') || mime.includes('tar') || mime.includes('compressed')) return 'mdi-folder-zip'
|
|
||||||
if (mime.includes('word') || mime.includes('document')) return 'mdi-file-word'
|
|
||||||
if (mime.includes('excel') || mime.includes('spreadsheet')) return 'mdi-file-excel'
|
|
||||||
if (mime.includes('powerpoint') || mime.includes('presentation')) return 'mdi-file-powerpoint'
|
|
||||||
return 'mdi-file'
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Format file size in human readable format
|
|
||||||
*/
|
|
||||||
export function formatSize(bytes: number): string {
|
|
||||||
const safeBytes = Number.isFinite(bytes) ? bytes : 0
|
|
||||||
if (safeBytes === 0) return '0 B'
|
|
||||||
const k = 1024
|
|
||||||
const sizes = ['B', 'KB', 'MB', 'GB', 'TB']
|
|
||||||
const i = Math.floor(Math.log(safeBytes) / Math.log(k))
|
|
||||||
return parseFloat((safeBytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Format date string to localized format
|
|
||||||
*/
|
|
||||||
export function formatDate(value: Date | string | null | undefined): string {
|
|
||||||
if (!value) return '—'
|
|
||||||
const date = value instanceof Date ? value : new Date(value)
|
|
||||||
if (Number.isNaN(date.getTime())) return '—'
|
|
||||||
return date.toLocaleDateString('en-US', {
|
|
||||||
year: 'numeric',
|
|
||||||
month: 'short',
|
|
||||||
day: 'numeric',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get upload status icon
|
|
||||||
*/
|
|
||||||
export function getUploadStatusIcon(status: string): string {
|
|
||||||
switch (status) {
|
|
||||||
case 'pending': return 'mdi-clock-outline'
|
|
||||||
case 'uploading': return 'mdi-loading mdi-spin'
|
|
||||||
case 'completed': return 'mdi-check-circle'
|
|
||||||
case 'error': return 'mdi-alert-circle'
|
|
||||||
default: return 'mdi-file'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get upload status color
|
|
||||||
*/
|
|
||||||
export function getUploadStatusColor(status: string): string {
|
|
||||||
switch (status) {
|
|
||||||
case 'pending': return 'grey'
|
|
||||||
case 'uploading': return 'primary'
|
|
||||||
case 'completed': return 'success'
|
|
||||||
case 'error': return 'error'
|
|
||||||
default: return 'grey'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user