457 lines
13 KiB
TypeScript
457 lines
13 KiB
TypeScript
import { computed, reactive, ref } from 'vue'
|
|
import { defineStore } from 'pinia'
|
|
import { io } from 'socket.io-client'
|
|
import axios from 'axios'
|
|
import { useErrorStore } from '@/stores/error'
|
|
|
|
import api from '@/services/HttpService'
|
|
|
|
import { useLoader } from './loader'
|
|
|
|
type Path = string
|
|
|
|
export interface StorageFolder {
|
|
pathname: string
|
|
name: string
|
|
createdAt: string
|
|
createdBy: string
|
|
}
|
|
|
|
export interface StorageFile {
|
|
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
|
|
}
|
|
|
|
export interface Structure extends StorageFolder {
|
|
folder: Structure[]
|
|
file: StorageFile[]
|
|
}
|
|
|
|
type Tree = Structure[]
|
|
|
|
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<string>((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
|
|
}
|
|
|
|
function consistantPath(path: string | string[]) {
|
|
return Array.isArray(path)
|
|
? path.join('/') + '/'
|
|
: path.split('/').filter(Boolean).join('/') + '/'
|
|
}
|
|
|
|
const useStorage = defineStore('storageStore', () => {
|
|
const error = useErrorStore()
|
|
const loader = useLoader()
|
|
const init = ref<boolean>(false)
|
|
const folder = ref<Record<Path, StorageFolder[]>>({})
|
|
const file = ref<Record<Path, StorageFile[]>>({})
|
|
const tree = computed(() => {
|
|
let structure: Tree = []
|
|
|
|
// parse list of folder and list of file into tree
|
|
Object.entries(folder.value).forEach(([key, value]) => {
|
|
const arr = key.split('/').filter(Boolean)
|
|
|
|
// Once run then it is init
|
|
if (!init.value) init.value = true
|
|
|
|
if (arr.length === 0) {
|
|
structure = value.map((v) => ({
|
|
pathname: v.pathname,
|
|
name: v.name,
|
|
createdAt: v.createdAt,
|
|
createdBy: v.createdBy,
|
|
folder: [],
|
|
file: [],
|
|
}))
|
|
} else {
|
|
let current: Structure | 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,
|
|
createdAt: v.createdAt,
|
|
createdBy: v.createdBy,
|
|
folder: [],
|
|
file: [],
|
|
}))
|
|
current.file = file.value[key] ?? []
|
|
}
|
|
}
|
|
})
|
|
|
|
return structure
|
|
})
|
|
const currentInfo = reactive<{
|
|
path: string
|
|
dept: number
|
|
}>({
|
|
path: '',
|
|
dept: 0,
|
|
})
|
|
|
|
if (!init.value) goto(sessionStorage.getItem('path') || '')
|
|
|
|
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[consistantPath(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[consistantPath(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
|
|
// this will also triggher init structure as it get root structure
|
|
for (let i = 0; i < arr.length; i++) {
|
|
const current = consistantPath(arr.slice(0, i - arr.length))
|
|
if (!folder.value[current] || force) await getStorage(current)
|
|
}
|
|
|
|
path = consistantPath(path)
|
|
|
|
// 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
|
|
sessionStorage.setItem('path', path)
|
|
loader.hide()
|
|
}
|
|
|
|
async function gotoParent() {
|
|
const arr = currentInfo.path.split('/').filter(Boolean)
|
|
await goto(consistantPath(arr.slice(0, -1)))
|
|
}
|
|
|
|
// 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
|
|
name: string
|
|
createdAt: string
|
|
createdBy: string
|
|
}) => {
|
|
const arr = data.pathname.split('/').filter(Boolean)
|
|
const path = consistantPath(arr.slice(0, -1))
|
|
|
|
if (folder.value[path]) {
|
|
const idx = folder.value[path].findIndex(
|
|
(v) => v.pathname === data.pathname,
|
|
)
|
|
|
|
if (idx === -1) {
|
|
folder.value[path].push({
|
|
pathname: data.pathname,
|
|
name: data.name,
|
|
createdAt: data.createdAt,
|
|
createdBy: data.createdBy,
|
|
})
|
|
} else {
|
|
error.title = 'ตั้งชื่อ Folder ซ้ำ'
|
|
error.msg = ''
|
|
error.show()
|
|
}
|
|
|
|
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 = consistantPath(src.slice(0, -1))
|
|
|
|
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]
|
|
}
|
|
}
|
|
|
|
const regex = new RegExp(`^${data.from}`)
|
|
|
|
for (let key in folder.value) {
|
|
if (key.startsWith(data.from)) {
|
|
folder.value[key.replace(regex, data.to)] = folder.value[key].map(
|
|
(v) => {
|
|
v.pathname = v.pathname.replace(regex, data.to)
|
|
return v
|
|
},
|
|
)
|
|
delete folder.value[key]
|
|
}
|
|
}
|
|
for (let key in file.value) {
|
|
if (key.startsWith(data.from)) {
|
|
file.value[key.replace(regex, data.to)] = file.value[key].map((v) => {
|
|
v.pathname = v.pathname.replace(regex, data.to)
|
|
return v
|
|
})
|
|
delete file.value[key]
|
|
}
|
|
}
|
|
})
|
|
socket.on('DeleteFolder', (data: { pathname: string }) => {
|
|
for (let key in folder.value) {
|
|
if (key.startsWith(data.pathname)) {
|
|
delete folder.value[key]
|
|
}
|
|
}
|
|
for (let key in file.value) {
|
|
if (key.startsWith(data.pathname)) {
|
|
delete file.value[key]
|
|
}
|
|
}
|
|
|
|
const arr = data.pathname.split('/').filter(Boolean)
|
|
const path = consistantPath(arr.slice(0, -1))
|
|
|
|
if (folder.value[path]) {
|
|
folder.value[path] = folder.value[path].filter(
|
|
(v) => v.pathname !== data.pathname,
|
|
)
|
|
}
|
|
|
|
if (currentInfo.path.includes(arr[arr.length - 1])) {
|
|
error.title = 'ตำเเหน่งที่คุณอยู่ถูกลบ'
|
|
error.msg = ''
|
|
error.show()
|
|
}
|
|
})
|
|
socket.on('FileUpload', (data: StorageFile) => {
|
|
const arr = data.pathname.split('/').filter(Boolean)
|
|
const path = consistantPath(arr.slice(0, -1))
|
|
|
|
if (file.value[path]) {
|
|
const idx = file.value[path].findIndex(
|
|
(v) => v.pathname === data.pathname,
|
|
)
|
|
if (idx !== -1) file.value[path][idx] = data
|
|
else file.value[path].push(data)
|
|
|
|
file.value[path].sort((a, b) => a.pathname.localeCompare(b.pathname))
|
|
}
|
|
})
|
|
socket.on('FileDelete', (data: { pathname: string }) => {
|
|
const arr = data.pathname.split('/').filter(Boolean)
|
|
const path = consistantPath(arr.slice(0, -1))
|
|
|
|
if (file.value[path]) {
|
|
file.value[path] = file.value[path].filter(
|
|
(v) => v.pathname !== data.pathname,
|
|
)
|
|
}
|
|
})
|
|
socket.on(
|
|
'FileUpdateMove',
|
|
(data: { from: StorageFile; to: StorageFile }) => {
|
|
const arr = data.from.pathname.split('/').filter(Boolean)
|
|
const path = consistantPath(arr.slice(0, -1))
|
|
|
|
if (file.value[path]) {
|
|
const idx = file.value[path].findIndex(
|
|
(v) => v.pathname === data.from.pathname,
|
|
)
|
|
if (idx !== -1) file.value[path][idx] = data.to
|
|
}
|
|
},
|
|
)
|
|
socket.on('FileUpdate', (data: StorageFile) => {
|
|
const arr = data.pathname.split('/').filter(Boolean)
|
|
const path = consistantPath(arr.slice(0, -1))
|
|
|
|
if (file.value[path]) {
|
|
const idx = file.value[path].findIndex(
|
|
(v) => v.pathname === data.pathname,
|
|
)
|
|
if (idx !== -1) file.value[path][idx] = data
|
|
}
|
|
})
|
|
socket.on('FileUploadRequest', (data: StorageFile) => {
|
|
const arr = data.pathname.split('/').filter(Boolean)
|
|
const path = consistantPath(arr.slice(0, -1))
|
|
|
|
if (file.value[path]) {
|
|
const idx = file.value[path].findIndex(
|
|
(v) => v.pathname === data.pathname,
|
|
)
|
|
|
|
if (idx !== -1) file.value[path][idx] = data
|
|
else file.value[path].push(data)
|
|
|
|
file.value[path].sort((a, b) => a.pathname.localeCompare(b.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, false), { name })
|
|
loader.hide()
|
|
}
|
|
async function deleteFolder(path: string) {
|
|
loader.show()
|
|
await api.delete(constructUrl(path, false))
|
|
loader.hide()
|
|
}
|
|
|
|
type FileMetadata = {
|
|
title?: string
|
|
description?: string
|
|
keyword?: string[]
|
|
category?: string[]
|
|
}
|
|
async function createFile(
|
|
file: File,
|
|
data: FileMetadata,
|
|
path: string = currentInfo.path,
|
|
) {
|
|
if (path.split('/').filter(Boolean).length < 3) return // the system only allow file to live in level 3 and 4
|
|
|
|
loader.show()
|
|
const res = await api.post(constructUrl(path, false) + '/file', {
|
|
file: file.name,
|
|
...data,
|
|
})
|
|
if (res && res.status === 201 && res.data && res.data.upload) {
|
|
await axios
|
|
.put(res.data.upload, file, {
|
|
headers: { 'Content-Type': file.type },
|
|
onUploadProgress: (e) => console.log(e),
|
|
})
|
|
.catch((e) => console.error(e))
|
|
}
|
|
loader.hide()
|
|
}
|
|
async function updateFile(pathname: string, data: FileMetadata, file?: File) {
|
|
const arr = pathname.split('/')
|
|
|
|
if (arr.length < 4) return // the system only allow file to live in level 3 and 4
|
|
|
|
loader.show()
|
|
const res = await api.patch(
|
|
constructUrl(arr.slice(0, -1), false) + `/file/${arr[arr.length - 1]}`,
|
|
{ file: file?.name, ...data },
|
|
)
|
|
if (res && res.status === 201 && res.data && res.data.upload) {
|
|
await axios
|
|
.put(res.data.upload, file, {
|
|
headers: { 'Content-Type': file?.type },
|
|
onUploadProgress: (e) => console.log(e),
|
|
})
|
|
.catch((e) => console.error(e))
|
|
}
|
|
loader.hide()
|
|
}
|
|
async function deleteFile(pathname: string) {
|
|
const arr = pathname.split('/')
|
|
|
|
if (arr.length < 4) return // the system only allow file to live in level 3 and 4
|
|
|
|
loader.show()
|
|
await api.delete(
|
|
constructUrl(arr.slice(0, -1), false) + `/file/${arr[arr.length - 1]}`,
|
|
)
|
|
loader.hide()
|
|
}
|
|
|
|
return {
|
|
// information
|
|
currentInfo,
|
|
folder,
|
|
file,
|
|
tree,
|
|
// fetch
|
|
getStorage,
|
|
getStorageFile,
|
|
// traverse
|
|
goto,
|
|
gotoParent,
|
|
// operation
|
|
createFolder,
|
|
editFolder,
|
|
deleteFolder,
|
|
createFile,
|
|
updateFile,
|
|
deleteFile,
|
|
}
|
|
})
|
|
|
|
export default useStorage
|