migrated file browser to ts
This commit is contained in:
parent
5942d12f2e
commit
c94a4e5c75
4 changed files with 85 additions and 60 deletions
|
|
@ -192,9 +192,10 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup lang="ts">
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import { useStorage } from '@vueuse/core'
|
import { useStorage } from '@vueuse/core'
|
||||||
|
import type { FileItem, FileBrowserResponse } from '~/types/filebrowser'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const toast = useNotification()
|
const toast = useNotification()
|
||||||
|
|
@ -206,26 +207,32 @@ const bg_opacity = useStorage('random_bg_opacity', 0.95)
|
||||||
const sort_by = useStorage('sort_by', 'name')
|
const sort_by = useStorage('sort_by', 'name')
|
||||||
const sort_order = useStorage('sort_order', 'asc')
|
const sort_order = useStorage('sort_order', 'asc')
|
||||||
|
|
||||||
const isLoading = ref(false)
|
const isLoading = ref<boolean>(false)
|
||||||
const initialLoad = ref(true)
|
const initialLoad = ref<boolean>(true)
|
||||||
const items = ref([])
|
const items = ref<FileItem[]>([])
|
||||||
const path = ref(`/${route.params.slug?.length > 0 ? route.params.slug?.join('/') : ''}`)
|
const path = ref<string>((() => {
|
||||||
const table_container = ref(false)
|
const slug = route.params.slug
|
||||||
const search = ref('')
|
if (Array.isArray(slug) && slug.length > 0) {
|
||||||
const show_filter = ref(false)
|
return '/' + slug.join('/')
|
||||||
|
}
|
||||||
|
return '/'
|
||||||
|
})())
|
||||||
|
const table_container = ref<boolean>(false)
|
||||||
|
const search = ref<string>('')
|
||||||
|
const show_filter = ref<boolean>(false)
|
||||||
|
|
||||||
const filteredItems = computed(() => {
|
const filteredItems = computed<FileItem[]>(() => {
|
||||||
if (!search.value) {
|
if (!search.value) {
|
||||||
return sortedItems(items.value)
|
return sortedItems(items.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
const searchLower = search.value.toLowerCase()
|
const searchLower = search.value.toLowerCase()
|
||||||
return sortedItems(items.value.filter(item => {
|
return sortedItems(items.value.filter((item: FileItem) => {
|
||||||
return item.name.toLowerCase().includes(searchLower)
|
return item.name.toLowerCase().includes(searchLower)
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
const sortedItems = items => {
|
const sortedItems = (items: FileItem[]): FileItem[] => {
|
||||||
|
|
||||||
if (!items || items.length < 1) {
|
if (!items || items.length < 1) {
|
||||||
return []
|
return []
|
||||||
|
|
@ -247,7 +254,7 @@ const sortedItems = items => {
|
||||||
return items.sort((a, b) => {
|
return items.sort((a, b) => {
|
||||||
let aDate = new Date(a.mtime)
|
let aDate = new Date(a.mtime)
|
||||||
let bDate = new Date(b.mtime)
|
let bDate = new Date(b.mtime)
|
||||||
return 'asc' === sort_order.value ? aDate - bDate : bDate - aDate
|
return 'asc' === sort_order.value ? aDate.getTime() - bDate.getTime() : bDate.getTime() - aDate.getTime()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -260,8 +267,8 @@ const sortedItems = items => {
|
||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
|
|
||||||
const model_item = ref()
|
const model_item = ref<any>()
|
||||||
const closeModel = () => model_item.value = null
|
const closeModel = (): void => { model_item.value = null }
|
||||||
|
|
||||||
watch(() => config.app.basic_mode, async () => {
|
watch(() => config.app.basic_mode, async () => {
|
||||||
if (!config.app.basic_mode) {
|
if (!config.app.basic_mode) {
|
||||||
|
|
@ -284,7 +291,7 @@ watch(() => socket.isConnected, async () => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleClick = item => {
|
const handleClick = (item: FileItem): void => {
|
||||||
if ('video' === item.content_type) {
|
if ('video' === item.content_type) {
|
||||||
model_item.value = {
|
model_item.value = {
|
||||||
"type": 'video',
|
"type": 'video',
|
||||||
|
|
@ -325,10 +332,10 @@ const handleClick = item => {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
window.location = makeDownload(config, { "filename": item.path, "folder": "", "extras": {} })
|
window.location.href = makeDownload(config, { "filename": item.path, "folder": "", "extras": {} })
|
||||||
}
|
}
|
||||||
|
|
||||||
const reloadContent = async (dir = '/', fromMounted = false) => {
|
const reloadContent = async (dir: string = '/', fromMounted: boolean = false): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
isLoading.value = true
|
isLoading.value = true
|
||||||
|
|
||||||
|
|
@ -346,11 +353,7 @@ const reloadContent = async (dir = '/', fromMounted = false) => {
|
||||||
|
|
||||||
items.value = []
|
items.value = []
|
||||||
|
|
||||||
const data = await response.json()
|
const data = await response.json() as FileBrowserResponse
|
||||||
|
|
||||||
if (data.length < 1) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!data?.contents || data.contents.length > 0) {
|
if (!data?.contents || data.contents.length > 0) {
|
||||||
items.value = data.contents
|
items.value = data.contents
|
||||||
|
|
@ -368,7 +371,7 @@ const reloadContent = async (dir = '/', fromMounted = false) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
useHead({ title: decodeURIComponent(title) })
|
useHead({ title: decodeURIComponent(title) })
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
if (fromMounted) {
|
if (fromMounted) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -379,17 +382,17 @@ const reloadContent = async (dir = '/', fromMounted = false) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const event_handler = e => {
|
const event_handler = (e: Event): void => {
|
||||||
|
const evt = e as PopStateEvent
|
||||||
if (!e.state) {
|
if (!evt.state) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.state.path === path.value || e.state.path === window.location.pathname) {
|
if (evt.state.path === path.value || evt.state.path === window.location.pathname) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
reloadContent(e.state.path, true)
|
reloadContent(evt.state.path, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
|
@ -397,12 +400,12 @@ onMounted(async () => {
|
||||||
await reloadContent(path.value, true)
|
await reloadContent(path.value, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener('popstate', event_handler)
|
document.addEventListener('popstate', event_handler as EventListener)
|
||||||
})
|
})
|
||||||
|
|
||||||
onBeforeUnmount(() => document.removeEventListener('popstate', event_handler))
|
onBeforeUnmount(() => document.removeEventListener('popstate', event_handler as EventListener))
|
||||||
|
|
||||||
const makeBreadCrumb = path => {
|
const makeBreadCrumb = (path: string): { name: string, link: string, path: string }[] => {
|
||||||
const baseLink = '/'
|
const baseLink = '/'
|
||||||
|
|
||||||
path = path.replace(/^\/+/, '').replace(/\/+$/, '')
|
path = path.replace(/^\/+/, '').replace(/\/+$/, '')
|
||||||
|
|
@ -428,16 +431,15 @@ const makeBreadCrumb = path => {
|
||||||
return links
|
return links
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
watch(model_item, v => {
|
watch(model_item, v => {
|
||||||
if (!bg_enable.value) {
|
if (!bg_enable.value) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
document.querySelector('body').setAttribute("style", `opacity: ${v ? 1 : bg_opacity.value}`)
|
document.querySelector('body')?.setAttribute("style", `opacity: ${v ? 1 : bg_opacity.value}`)
|
||||||
})
|
})
|
||||||
|
|
||||||
const setIcon = item => {
|
const setIcon = (item: FileItem): string => {
|
||||||
if ('link' === item.type) {
|
if ('link' === item.type) {
|
||||||
return 'fa-link'
|
return 'fa-link'
|
||||||
}
|
}
|
||||||
|
|
@ -460,7 +462,7 @@ const setIcon = item => {
|
||||||
return 'fa-file'
|
return 'fa-file'
|
||||||
}
|
}
|
||||||
|
|
||||||
const changeSort = by => {
|
const changeSort = (by: string): void => {
|
||||||
if (!['name', 'size', 'date', 'type'].includes(by)) {
|
if (!['name', 'size', 'date', 'type'].includes(by)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -472,17 +474,17 @@ const changeSort = by => {
|
||||||
sort_order.value = 'asc' === sort_order.value ? 'desc' : 'asc'
|
sort_order.value = 'asc' === sort_order.value ? 'desc' : 'asc'
|
||||||
}
|
}
|
||||||
|
|
||||||
const toggleFilter = () => {
|
const toggleFilter = (): void => {
|
||||||
show_filter.value = !show_filter.value
|
show_filter.value = !show_filter.value
|
||||||
if (!show_filter.value) {
|
if (!show_filter.value) {
|
||||||
search.value = ''
|
search.value = ''
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
awaitElement('#search', e => e.focus())
|
awaitElement('#search', (e: Element) => (e as HTMLElement).focus())
|
||||||
}
|
}
|
||||||
|
|
||||||
const createDirectory = async (dir) => {
|
const createDirectory = async (dir: string): Promise<void> => {
|
||||||
if (!config.app.browser_control_enabled) {
|
if (!config.app.browser_control_enabled) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -497,13 +499,13 @@ const createDirectory = async (dir) => {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
await actionRequest({ path: dir }, 'directory', { new_dir: new_dir }, (item, action, data) => {
|
await actionRequest({ path: dir } as FileItem, 'directory', { new_dir: new_dir }, (item, action, data) => {
|
||||||
reloadContent(path.value, true)
|
reloadContent(path.value, true)
|
||||||
toast.success(`Successfully created '${new_dir}'.`)
|
toast.success(`Successfully created '${new_dir}'.`)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleAction = async (action, item) => {
|
const handleAction = async (action: string, item: FileItem): Promise<void> => {
|
||||||
if (!config.app.browser_control_enabled) {
|
if (!config.app.browser_control_enabled) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -561,7 +563,12 @@ const handleAction = async (action, item) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const actionRequest = async (item, action, data, cb) => {
|
const actionRequest = async (
|
||||||
|
item: FileItem,
|
||||||
|
action: string,
|
||||||
|
data: Record<string, any>,
|
||||||
|
cb: (item: FileItem, action: string, data: any) => void
|
||||||
|
): Promise<any> => {
|
||||||
if (!config.app.browser_control_enabled) {
|
if (!config.app.browser_control_enabled) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -591,10 +598,9 @@ const actionRequest = async (item, action, data, cb) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return response
|
return response
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
toast.error(`Failed to perform action: ${error.message}`)
|
toast.error(`Failed to perform action: ${error.message}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -172,7 +172,7 @@ watch(() => config.app.file_logging, async v => {
|
||||||
const filteredItems = computed(() => {
|
const filteredItems = computed(() => {
|
||||||
const raw = query.value.trim().toLowerCase()
|
const raw = query.value.trim().toLowerCase()
|
||||||
const contextMatch = raw.match(/context:(\d+)/)
|
const contextMatch = raw.match(/context:(\d+)/)
|
||||||
const context = contextMatch ? parseInt(contextMatch[1], 10) : 0
|
const context = contextMatch ? parseInt(String(contextMatch[1]), 10) : 0
|
||||||
const searchTerm = raw.replace(/context:\d+/, '').trim()
|
const searchTerm = raw.replace(/context:\d+/, '').trim()
|
||||||
|
|
||||||
if (!searchTerm) {
|
if (!searchTerm) {
|
||||||
|
|
@ -191,7 +191,7 @@ const filteredItems = computed(() => {
|
||||||
})
|
})
|
||||||
|
|
||||||
Array.from(matchedIndexes).sort((a: any, b: any) => a - b).forEach((index: any) => {
|
Array.from(matchedIndexes).sort((a: any, b: any) => a - b).forEach((index: any) => {
|
||||||
result.push(logs.value[index])
|
result.push(logs.value[index] as log_line)
|
||||||
})
|
})
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
@ -276,21 +276,20 @@ const scrollToBottom = (fast = false) => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const log_handler = (data: log_line) => {
|
||||||
|
logs.value.push(data)
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
if (autoScroll.value && bottomMarker.value) {
|
||||||
|
bottomMarker.value.scrollIntoView({ behavior: 'smooth' })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await fetchLogs()
|
await fetchLogs()
|
||||||
|
socket.on('log_lines', log_handler)
|
||||||
socket.emit('subscribe', 'log_lines')
|
socket.emit('subscribe', 'log_lines')
|
||||||
|
|
||||||
socket.on('log_lines', (data: any) => {
|
|
||||||
logs.value.push(data)
|
|
||||||
|
|
||||||
nextTick(() => {
|
|
||||||
if (autoScroll.value && bottomMarker.value) {
|
|
||||||
bottomMarker.value.scrollIntoView({ behavior: 'smooth' })
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
if (bg_enable.value) {
|
if (bg_enable.value) {
|
||||||
document.querySelector('body')?.setAttribute("style", `opacity: 1.0`)
|
document.querySelector('body')?.setAttribute("style", `opacity: 1.0`)
|
||||||
}
|
}
|
||||||
|
|
@ -298,7 +297,7 @@ onMounted(async () => {
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
socket.emit('unsubscribe', 'log_lines')
|
socket.emit('unsubscribe', 'log_lines')
|
||||||
socket.off('log_lines')
|
socket.off('log_lines', log_handler)
|
||||||
if (bg_enable.value) {
|
if (bg_enable.value) {
|
||||||
document.querySelector('body')?.setAttribute("style", `opacity: ${bg_opacity.value}`)
|
document.querySelector('body')?.setAttribute("style", `opacity: ${bg_opacity.value}`)
|
||||||
}
|
}
|
||||||
|
|
@ -306,7 +305,7 @@ onBeforeUnmount(() => {
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
socket.emit('unsubscribe', 'log_lines')
|
socket.emit('unsubscribe', 'log_lines')
|
||||||
socket.off('log_lines')
|
socket.off('log_lines', log_handler)
|
||||||
if (scrollTimeout) clearTimeout(scrollTimeout)
|
if (scrollTimeout) clearTimeout(scrollTimeout)
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ export const useSocketStore = defineStore('socket', () => {
|
||||||
event.forEach(e => socket.value?.on(e, (...args) => true === withEvent ? callback(e, ...args) : callback(...args)))
|
event.forEach(e => socket.value?.on(e, (...args) => true === withEvent ? callback(e, ...args) : callback(...args)))
|
||||||
}
|
}
|
||||||
|
|
||||||
const off = (event: string | string[], callback: (...args: any[]) => void): any => {
|
const off = (event: string | string[], callback?: (...args: any[]) => void): any => {
|
||||||
if (!Array.isArray(event)) {
|
if (!Array.isArray(event)) {
|
||||||
event = [event]
|
event = [event]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
20
ui/app/types/filebrowser.d.ts
vendored
Normal file
20
ui/app/types/filebrowser.d.ts
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
type FileItem = {
|
||||||
|
type: 'file' | 'dir' | 'link'
|
||||||
|
content_type: 'image' | 'video' | 'text' | 'subtitle' | 'metadata' | 'dir' | string
|
||||||
|
name: string
|
||||||
|
path: string
|
||||||
|
size: number
|
||||||
|
mime: string
|
||||||
|
mtime: string
|
||||||
|
ctime: string
|
||||||
|
is_dir: boolean
|
||||||
|
is_file: boolean
|
||||||
|
is_symlink: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
type FileBrowserResponse = {
|
||||||
|
path: string
|
||||||
|
contents: FileItem[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type { FileItem, FileBrowserResponse }
|
||||||
Loading…
Reference in a new issue