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>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
<script setup lang="ts">
|
||||
import moment from 'moment'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import type { FileItem, FileBrowserResponse } from '~/types/filebrowser'
|
||||
|
||||
const route = useRoute()
|
||||
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_order = useStorage('sort_order', 'asc')
|
||||
|
||||
const isLoading = ref(false)
|
||||
const initialLoad = ref(true)
|
||||
const items = ref([])
|
||||
const path = ref(`/${route.params.slug?.length > 0 ? route.params.slug?.join('/') : ''}`)
|
||||
const table_container = ref(false)
|
||||
const search = ref('')
|
||||
const show_filter = ref(false)
|
||||
const isLoading = ref<boolean>(false)
|
||||
const initialLoad = ref<boolean>(true)
|
||||
const items = ref<FileItem[]>([])
|
||||
const path = ref<string>((() => {
|
||||
const slug = route.params.slug
|
||||
if (Array.isArray(slug) && slug.length > 0) {
|
||||
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) {
|
||||
return sortedItems(items.value)
|
||||
}
|
||||
|
||||
const searchLower = search.value.toLowerCase()
|
||||
return sortedItems(items.value.filter(item => {
|
||||
return sortedItems(items.value.filter((item: FileItem) => {
|
||||
return item.name.toLowerCase().includes(searchLower)
|
||||
}))
|
||||
})
|
||||
|
||||
const sortedItems = items => {
|
||||
const sortedItems = (items: FileItem[]): FileItem[] => {
|
||||
|
||||
if (!items || items.length < 1) {
|
||||
return []
|
||||
|
|
@ -247,7 +254,7 @@ const sortedItems = items => {
|
|||
return items.sort((a, b) => {
|
||||
let aDate = new Date(a.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
|
||||
}
|
||||
|
||||
const model_item = ref()
|
||||
const closeModel = () => model_item.value = null
|
||||
const model_item = ref<any>()
|
||||
const closeModel = (): void => { model_item.value = null }
|
||||
|
||||
watch(() => config.app.basic_mode, async () => {
|
||||
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) {
|
||||
model_item.value = {
|
||||
"type": 'video',
|
||||
|
|
@ -325,10 +332,10 @@ const handleClick = item => {
|
|||
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 {
|
||||
isLoading.value = true
|
||||
|
||||
|
|
@ -346,11 +353,7 @@ const reloadContent = async (dir = '/', fromMounted = false) => {
|
|||
|
||||
items.value = []
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
if (data.length < 1) {
|
||||
return
|
||||
}
|
||||
const data = await response.json() as FileBrowserResponse
|
||||
|
||||
if (!data?.contents || data.contents.length > 0) {
|
||||
items.value = data.contents
|
||||
|
|
@ -368,7 +371,7 @@ const reloadContent = async (dir = '/', fromMounted = false) => {
|
|||
}
|
||||
|
||||
useHead({ title: decodeURIComponent(title) })
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
if (fromMounted) {
|
||||
return
|
||||
}
|
||||
|
|
@ -379,17 +382,17 @@ const reloadContent = async (dir = '/', fromMounted = false) => {
|
|||
}
|
||||
}
|
||||
|
||||
const event_handler = e => {
|
||||
|
||||
if (!e.state) {
|
||||
const event_handler = (e: Event): void => {
|
||||
const evt = e as PopStateEvent
|
||||
if (!evt.state) {
|
||||
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
|
||||
}
|
||||
|
||||
reloadContent(e.state.path, true)
|
||||
reloadContent(evt.state.path, true)
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
|
|
@ -397,12 +400,12 @@ onMounted(async () => {
|
|||
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 = '/'
|
||||
|
||||
path = path.replace(/^\/+/, '').replace(/\/+$/, '')
|
||||
|
|
@ -428,16 +431,15 @@ const makeBreadCrumb = path => {
|
|||
return links
|
||||
}
|
||||
|
||||
|
||||
watch(model_item, v => {
|
||||
if (!bg_enable.value) {
|
||||
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) {
|
||||
return 'fa-link'
|
||||
}
|
||||
|
|
@ -460,7 +462,7 @@ const setIcon = item => {
|
|||
return 'fa-file'
|
||||
}
|
||||
|
||||
const changeSort = by => {
|
||||
const changeSort = (by: string): void => {
|
||||
if (!['name', 'size', 'date', 'type'].includes(by)) {
|
||||
return
|
||||
}
|
||||
|
|
@ -472,17 +474,17 @@ const changeSort = by => {
|
|||
sort_order.value = 'asc' === sort_order.value ? 'desc' : 'asc'
|
||||
}
|
||||
|
||||
const toggleFilter = () => {
|
||||
const toggleFilter = (): void => {
|
||||
show_filter.value = !show_filter.value
|
||||
if (!show_filter.value) {
|
||||
search.value = ''
|
||||
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) {
|
||||
return
|
||||
}
|
||||
|
|
@ -497,13 +499,13 @@ const createDirectory = async (dir) => {
|
|||
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)
|
||||
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) {
|
||||
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) {
|
||||
return
|
||||
}
|
||||
|
|
@ -591,10 +598,9 @@ const actionRequest = async (item, action, data, cb) => {
|
|||
}
|
||||
|
||||
return response
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
console.error(error)
|
||||
toast.error(`Failed to perform action: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -172,7 +172,7 @@ watch(() => config.app.file_logging, async v => {
|
|||
const filteredItems = computed(() => {
|
||||
const raw = query.value.trim().toLowerCase()
|
||||
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()
|
||||
|
||||
if (!searchTerm) {
|
||||
|
|
@ -191,7 +191,7 @@ const filteredItems = computed(() => {
|
|||
})
|
||||
|
||||
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
|
||||
|
|
@ -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 () => {
|
||||
await fetchLogs()
|
||||
socket.on('log_lines', log_handler)
|
||||
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) {
|
||||
document.querySelector('body')?.setAttribute("style", `opacity: 1.0`)
|
||||
}
|
||||
|
|
@ -298,7 +297,7 @@ onMounted(async () => {
|
|||
|
||||
onBeforeUnmount(() => {
|
||||
socket.emit('unsubscribe', 'log_lines')
|
||||
socket.off('log_lines')
|
||||
socket.off('log_lines', log_handler)
|
||||
if (bg_enable.value) {
|
||||
document.querySelector('body')?.setAttribute("style", `opacity: ${bg_opacity.value}`)
|
||||
}
|
||||
|
|
@ -306,7 +305,7 @@ onBeforeUnmount(() => {
|
|||
|
||||
onBeforeUnmount(() => {
|
||||
socket.emit('unsubscribe', 'log_lines')
|
||||
socket.off('log_lines')
|
||||
socket.off('log_lines', log_handler)
|
||||
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)))
|
||||
}
|
||||
|
||||
const off = (event: string | string[], callback: (...args: any[]) => void): any => {
|
||||
const off = (event: string | string[], callback?: (...args: any[]) => void): any => {
|
||||
if (!Array.isArray(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