import { computed, reactive, ref } from 'vue' import { defineStore } from 'pinia' import { io } from 'socket.io-client' import api from '@/services/HttpService' import { useLoader } from './loader' function constructUrl(path: string | string[], append = true) { const arr = Array.isArray(path) ? path : path.split('/').filter(Boolean) const url = import.meta.env.VITE_API_ENDPOINT + arr.reduce((a, v, i) => { switch (String(i)) { case '0': return `cabinet/${v}` case '1': return `${a}/drawer/${v}` case '2': return `${a}/folder/${v}` case '3': return `${a}/subfolder/${v}` default: return a } }, '') return append ? url + ['cabinet', '/drawer', '/folder', '/subfolder'][arr.length] : url } const useStorage = defineStore('storageStore', () => { const loader = useLoader() const init = ref(false) const folder = ref< Record< string, // path that contains folders { pathname: string name: string }[] > >({}) const file = ref< Record< string, // path that contains files { pathname: string path: string fileName: string fileSize: string fileType: string title: string description: string category: string[] keyword: string[] updatedAt: string updatedBy: string createdAt: string createdBy: string }[] > >({}) const tree = computed(() => { type Structure = { pathname: string name: string folder: Structure file: (typeof file.value)[string] }[] let structure: Structure = [] // parse list of folder and list of file into tree Object.entries(folder.value).forEach(([key, value]) => { const arr = key.split('/').filter(Boolean) // init outer tree if (arr.length === 0) { if (!init.value) init.value = true structure = value.map((v) => ({ pathname: v.pathname, name: v.name, folder: [], file: [], })) } else { let current: Structure[number] | undefined // traverse into tree arr.forEach((v, i) => { current = i === 0 ? structure.find((x) => x.name === v) : current?.folder.find((x) => x.name === v) }) // set data in tree (object is references to the same object) if (current) { current.folder = value.map((v) => ({ pathname: v.pathname, name: v.name, folder: [], file: [], })) current.file = file.value[key] ?? [] } } }) return structure }) const currentInfo = reactive<{ path: string dept: number }>({ path: '', dept: 1, }) if (!init.value) goto() async function getStorage(path: string = '') { const arr = path.split('/').filter(Boolean) if (arr.length >= 4) return // this system does not have more than 4 level const res = await api.get<(typeof folder.value)[string]>(constructUrl(arr)) if (res.status === 200 && res.data && Array.isArray(res.data)) folder.value[path] = res.data.sort((a, b) => a.pathname.localeCompare(b.pathname), ) } async function getStorageFile(path: string = '') { const arr = path.split('/').filter(Boolean) if (arr.length < 3) return // file in this system only lives in level 3 and 4 const res = await api.get<(typeof file.value)[string]>( constructUrl(arr, false) + '/file', ) if (res.status === 200 && res.data && Array.isArray(res.data)) file.value[path] = res.data.sort((a, b) => a.pathname.localeCompare(b.pathname), ) } async function goto(path: string = '', force = false) { loader.show() const arr = path.split('/').filter(Boolean) // get all parent to the root structure for (let i = 0; i < arr.length; i++) { const current = arr.slice(0, i - arr.length).join('/') + '/' if (!folder.value[current] || force) await getStorage(current) } // only get this path once, after that will get from socket.io-client instead if (!folder.value[path] || force) await getStorage(path) if (!file.value[path] || force) await getStorageFile(path) currentInfo.path = path currentInfo.dept = path.split('/').filter(Boolean).length loader.hide() } async function gotoParent() { const arr = currentInfo.path.split('/').filter(Boolean) await goto([...arr.slice(0, -1), ''].join('/')) } // socket.io zone const socket = io('http://localhost:25565') socket.on('connect', () => console.info('Socket.io connected.')) socket.on('disconnect', () => console.info('Socket.io disconnected.')) socket.on('CreateFolder', (data: { pathname: string }) => { const arr = data.pathname.split('/').filter(Boolean) const path = [...arr.slice(0, -1), ''].join('/') if (folder.value[path]) { folder.value[path].push({ pathname: data.pathname, name: arr[arr.length - 1], }) folder.value[path].sort((a, b) => a.pathname.localeCompare(b.pathname)) } }) // NOTE: // API planned to make new endpoint that can move and rename in one go. // Need to change if api handle move and rename file instead of just edit. socket.on('EditFolder', (data: { from: string; to: string }) => { const src = data.from.split('/').filter(Boolean) const dst = data.to.split('/').filter(Boolean) const path = [...src.slice(0, -1), ''].join('/') if (folder.value[path]) { const val = folder.value[path].find((v) => v.pathname === data.from) if (val) { val.pathname = data.to val.name = dst[dst.length - 1] } } for (let key in folder.value) { if (key.startsWith(data.from)) { folder.value[key.replace(data.from, data.to)] = folder.value[key].map( (v) => { v.pathname = v.pathname.replace(data.from, data.to) return v }, ) delete folder.value[key] } } }) socket.on('DeleteFolder', (data: { pathname: string }) => { for (let key in folder.value) { if (key.startsWith(data.pathname)) { delete folder.value[key] } } const arr = data.pathname.split('/').filter(Boolean) const path = [...arr.slice(0, -1), ''].join('/') if (folder.value[path]) { folder.value[path] = folder.value[path].filter( (v) => v.pathname !== data.pathname, ) } }) async function createFolder(name: string, path: string = currentInfo.path) { loader.show() await api.post(constructUrl(path, true), { name }) loader.hide() } async function editFolder(name: string, path: string) { loader.show() await api.put(constructUrl(path), { name }) loader.hide() } async function deleteFolder(path: string) { loader.show() await api.delete(constructUrl(path)) loader.hide() } return { // information currentInfo, folder, file, tree, // fetch getStorage, getStorageFile, // traverse goto, gotoParent, // operation createFolder, editFolder, deleteFolder, } }) export default useStorage