Style tuning

This commit is contained in:
Leo Vasanko
2023-11-04 20:54:14 +00:00
parent 41fbd3d122
commit 047facaacb
114 changed files with 246 additions and 179 deletions

View File

@@ -21,12 +21,10 @@ interface Path {
}
const documentStore = useDocumentStore()
const path: ComputedRef<Path> = computed(() => {
const pathList = Router.currentRoute.value.path
.split('/')
.filter(value => value !== '')
const p = decodeURIComponent(Router.currentRoute.value.path)
const pathList = p.split('/').filter(value => value !== '')
return {
path: Router.currentRoute.value.path,
path: p,
pathList
}
})
@@ -54,10 +52,10 @@ const globalShortcutHandler = (event: KeyboardEvent) => {
if (event.repeat) return
//console.log("key pressed", event)
const c = documentStore.fileExplorer.isCursor
const keyup = event.type === "keyup"
const keyup = event.type === 'keyup'
// For up/down implement custom fast repeat
if (event.key === "ArrowUp") vert = (keyup ? 0 : event.altKey ? -10 : -1)
else if (event.key === "ArrowDown") vert = (keyup ? 0 : event.altKey ? 10 : 1)
if (event.key === 'ArrowUp') vert = keyup ? 0 : event.altKey ? -10 : -1
else if (event.key === 'ArrowDown') vert = keyup ? 0 : event.altKey ? 10 : 1
// Find: process on keydown so that we can bypass the built-in search hotkey
else if (!keyup && event.key === 'f' && (event.ctrlKey || event.metaKey)) {
headerMain.value!.toggleSearchInput()
@@ -67,28 +65,30 @@ const globalShortcutHandler = (event: KeyboardEvent) => {
documentStore.fileExplorer.toggleSelectAll()
}
// Rename
else if (c && keyup && !event.ctrlKey && (event.key === 'F2' || event.key === "r")) {
else if (c && keyup && !event.ctrlKey && (event.key === 'F2' || event.key === 'r')) {
documentStore.fileExplorer.cursorRename()
}
// Toggle selections on file explorer; ignore all spaces to prevent scrolling built-in hotkey
else if (c && event.code === 'Space') {
if (keyup && !event.altKey && !event.ctrlKey) documentStore.fileExplorer.cursorSelect()
}
else return
if (keyup && !event.altKey && !event.ctrlKey)
documentStore.fileExplorer.cursorSelect()
} else return
event.preventDefault()
if (vertInterval !== null) clearInterval(vertInterval)
vertInterval = null
if (vert) {
vertInterval = setInterval(() => { console.log("X"), documentStore.fileExplorer.cursorMove(vert) }, 30)
vertInterval = setInterval(() => {
console.log('X'), documentStore.fileExplorer.cursorMove(vert)
}, 30)
}
}
onMounted(() => {
window.addEventListener("keydown", globalShortcutHandler)
window.addEventListener("keyup", globalShortcutHandler)
window.addEventListener('keydown', globalShortcutHandler)
window.addEventListener('keyup', globalShortcutHandler)
})
onUnmounted(() => {
window.removeEventListener("keydown", globalShortcutHandler)
window.removeEventListener("keyup", globalShortcutHandler)
window.removeEventListener('keydown', globalShortcutHandler)
window.removeEventListener('keyup', globalShortcutHandler)
})
export type { Path }
</script>
@@ -96,11 +96,11 @@ export type { Path }
<template>
<LoginModal />
<header>
<HeaderMain ref="headerMain"/>
<HeaderMain ref="headerMain"><HeaderSelected :path="path.pathList" /></HeaderMain>
<BreadCrumb :path="path.pathList" />
</header>
<main>
<RouterView />
<RouterView :path="path.pathList" />
</main>
</template>

View File

@@ -19,10 +19,8 @@
}
}
@media screen and (orientation: portrait) {
html {
font-size: 1.5rem;
}
.size, .modified {
.size,
.modified {
display: none;
}
}
@@ -30,10 +28,10 @@
html {
font-size: 1.5rem;
}
header .buttons:has(input[type=search]) > div {
header .buttons:has(input[type='search']) > div {
display: none;
}
header .buttons > div:has(input[type=search]) {
header .buttons > div:has(input[type='search']) {
display: inherit;
}
}

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path d="M19.2 2.6H6.1V29h19.8V9.3l-6.7-6.7zM18.5 16v7.1h-5.3V16H8.7l7.1-7.1L23 16h-4.5z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 28 28"><path d="M19.2 2.6H6.1V29h19.8V9.3l-6.7-6.7zM18.5 16v7.1h-5.3V16H8.7l7.1-7.1L23 16h-4.5z"/></svg>

Before

Width:  |  Height:  |  Size: 158 B

After

Width:  |  Height:  |  Size: 158 B

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="32" viewBox="0 0 448 512"><path d="M223.97 175A81 81 0 0 0 143 256c0 44.7 36.27 81.03 80.97 81.03 44.72 0 80.72-36.34 80.72-81.03 0-44.73-36-81-80.8-81zM386.3 302.53l-14.58 35.16 29.47 57.8-36.1 36.1-59.3-28-35.2 14.4-17.87 54.6-2.28 7.24h-51L177.4 418.2l-35.17-14.5-57.9 29.4-36.1-36.1 27.97-59.2-14.47-35.12L0 282.6v-51l61.7-22.1 14.5-35.1-25.96-51.23-3.43-6.72 36.1-36.03 59.3 27.92 35.1-14.5 17.9-54.6 2.3-7.24h51l22.1 61.73 35.07 14.52 58.04-29.4 36.06 36.03-27.96 59.2 14.42 35.17 61.8 20.13v50.97l-61.67 22.18z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M223.97 175A81 81 0 0 0 143 256c0 44.7 36.27 81.03 80.97 81.03 44.72 0 80.72-36.34 80.72-81.03 0-44.73-36-81-80.8-81zM386.3 302.53l-14.58 35.16 29.47 57.8-36.1 36.1-59.3-28-35.2 14.4-17.87 54.6-2.28 7.24h-51L177.4 418.2l-35.17-14.5-57.9 29.4-36.1-36.1 27.97-59.2-14.47-35.12L0 282.6v-51l61.7-22.1 14.5-35.1-25.96-51.23-3.43-6.72 36.1-36.03 59.3 27.92 35.1-14.5 17.9-54.6 2.3-7.24h51l22.1 61.73 35.07 14.52 58.04-29.4 36.06 36.03-27.96 59.2 14.42 35.17 61.8 20.13v50.97l-61.67 22.18z"/></svg>

Before

Width:  |  Height:  |  Size: 586 B

After

Width:  |  Height:  |  Size: 563 B

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-3 0 34 34"><path d="M23 25.9c0-.3-.1-.6-.3-.8s-.5-.3-.8-.3c-.3 0-.6.1-.8.3-.2.2-.3.5-.3.8s.1.6.3.8c.2.2.5.3.8.3.3 0 .6-.1.8-.3.2-.3.3-.5.3-.8zm4.6 0c0-.3-.1-.6-.3-.8s-.5-.3-.8-.3c-.3 0-.6.1-.8.3-.2.2-.3.5-.3.8s.1.6.3.8c.2.2.5.3.8.3.3 0 .6-.1.8-.3.2-.3.3-.5.3-.8zm2.3-4v5.7c0 .5-.2.9-.5 1.2-.3.3-.7.5-1.2.5H1.9c-.5 0-.9-.2-1.2-.5s-.5-.7-.5-1.2v-5.7c0-.5.2-.9.5-1.2.3-.3.7-.5 1.2-.5h8.3l2.4 2.4c.7.7 1.5 1 2.4 1 .9 0 1.7-.3 2.4-1l2.4-2.4h8.3c.5 0 .9.2 1.2.5.4.3.6.7.6 1.2zm-5.8-10.2c.2.5.1.9-.3 1.3l-8 8c-.2.2-.5.3-.8.3-.3 0-.6-.1-.8-.3l-8-8c-.4-.3-.5-.8-.3-1.3S6.5 11 7 11h4.6V3c0-.3.1-.6.3-.8s.5-.3.8-.3h4.6c.3 0 .6.1.8.3s.3.5.3.8v8H23c.5 0 .8.2 1.1.7z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30"><path d="M23 25.9c0-.3-.1-.6-.3-.8s-.5-.3-.8-.3c-.3 0-.6.1-.8.3-.2.2-.3.5-.3.8s.1.6.3.8c.2.2.5.3.8.3.3 0 .6-.1.8-.3.2-.3.3-.5.3-.8zm4.6 0c0-.3-.1-.6-.3-.8s-.5-.3-.8-.3c-.3 0-.6.1-.8.3-.2.2-.3.5-.3.8s.1.6.3.8c.2.2.5.3.8.3.3 0 .6-.1.8-.3.2-.3.3-.5.3-.8zm2.3-4v5.7c0 .5-.2.9-.5 1.2-.3.3-.7.5-1.2.5H1.9c-.5 0-.9-.2-1.2-.5s-.5-.7-.5-1.2v-5.7c0-.5.2-.9.5-1.2.3-.3.7-.5 1.2-.5h8.3l2.4 2.4c.7.7 1.5 1 2.4 1 .9 0 1.7-.3 2.4-1l2.4-2.4h8.3c.5 0 .9.2 1.2.5.4.3.6.7.6 1.2zm-5.8-10.2c.2.5.1.9-.3 1.3l-8 8c-.2.2-.5.3-.8.3-.3 0-.6-.1-.8-.3l-8-8c-.4-.3-.5-.8-.3-1.3S6.5 11 7 11h4.6V3c0-.3.1-.6.3-.8s.5-.3.8-.3h4.6c.3 0 .6.1.8.3s.3.5.3.8v8H23c.5 0 .8.2 1.1.7z"/></svg>

Before

Width:  |  Height:  |  Size: 711 B

After

Width:  |  Height:  |  Size: 711 B

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="10 10 372 468"><path d="M128 344V168c0-4.5-3.5-8-8-8h-16c-4.5 0-8 3.5-8 8v176c0 4.5 3.5 8 8 8h16c4.5 0 8-3.5 8-8zm64 0V168c0-4.5-3.5-8-8-8h-16c-4.5 0-8 3.5-8 8v176c0 4.5 3.5 8 8 8h16c4.5 0 8-3.5 8-8zm64 0V168c0-4.5-3.5-8-8-8h-16c-4.5 0-8 3.5-8 8v176c0 4.5 3.5 8 8 8h16c4.5 0 8-3.5 8-8zM120 96h112l-12-29.25c-.75-1-3-2.5-4.25-2.75H136.5c-1.5.25-3.5 1.75-4.25 2.75zm232 8v16c0 4.5-3.5 8-8 8h-24v237c0 27.5-18 51-40 51H72c-22 0-40-22.5-40-50V128H8c-4.5 0-8-3.5-8-8v-16c0-4.5 3.5-8 8-8h77.25l17.5-41.75C107.75 42 122.75 32 136 32h80c13.25 0 28.25 10 33.25 22.25L266.75 96H344c4.5 0 8 3.5 8 8z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="10 40 372 490"><path d="M128 344V168c0-4.5-3.5-8-8-8h-16c-4.5 0-8 3.5-8 8v176c0 4.5 3.5 8 8 8h16c4.5 0 8-3.5 8-8zm64 0V168c0-4.5-3.5-8-8-8h-16c-4.5 0-8 3.5-8 8v176c0 4.5 3.5 8 8 8h16c4.5 0 8-3.5 8-8zm64 0V168c0-4.5-3.5-8-8-8h-16c-4.5 0-8 3.5-8 8v176c0 4.5 3.5 8 8 8h16c4.5 0 8-3.5 8-8zM120 96h112l-12-29.25c-.75-1-3-2.5-4.25-2.75H136.5c-1.5.25-3.5 1.75-4.25 2.75zm232 8v16c0 4.5-3.5 8-8 8h-24v237c0 27.5-18 51-40 51H72c-22 0-40-22.5-40-50V128H8c-4.5 0-8-3.5-8-8v-16c0-4.5 3.5-8 8-8h77.25l17.5-41.75C107.75 42 122.75 32 136 32h80c13.25 0 28.25 10 33.25 22.25L266.75 96H344c4.5 0 8 3.5 8 8z"/></svg>

Before

Width:  |  Height:  |  Size: 647 B

After

Width:  |  Height:  |  Size: 647 B

View File

@@ -2,25 +2,20 @@
<div class="breadcrumb">
<a href="#/"><component :is="home" /></a>
<template v-for="(location, index) in props.path" :key="index">
<a :href="`/#/${props.path.slice(0, index + 1).join('/')}/`">{{
decodeURIComponent(location)
}}</a>
<a :href="`/#/${props.path.slice(0, index + 1).join('/')}/`">{{ location }}</a>
</template>
</div>
</template>
<script setup lang="ts">
//import home from '@/assets/svg/home.svg'
import { withDefaults, defineProps, defineAsyncComponent } from 'vue'
import { defineProps, defineAsyncComponent } from 'vue'
const home = defineAsyncComponent(() => import(`@/assets/svg/home.svg`))
const props = withDefaults(
defineProps<{
path: Array<string>
}>(),
{}
)
const props = defineProps<{
path: Array<string>
}>()
</script>
<style>
@@ -82,7 +77,7 @@ const props = withDefaults(
}
.breadcrumb svg {
/* FIXME: Custom positioning to align it well; needs proper solution */
padding-left: .6rem;
padding-left: 0.6rem;
width: 1.3rem;
height: 1.3rem;
fill: var(--breadcrumb-color);
@@ -94,10 +89,12 @@ const props = withDefaults(
.breadcrumb a:nth-child(even) {
background: var(--breadcrumb-background-even);
}
.breadcrumb a:nth-child(odd):hover, .breadcrumb a:focus:nth-child(odd) {
.breadcrumb a:nth-child(odd):hover,
.breadcrumb a:focus:nth-child(odd) {
background: var(--breadcrumb-hover-background-odd);
}
.breadcrumb a:nth-child(even):hover, .breadcrumb a:focus:nth-child(even) {
.breadcrumb a:nth-child(even):hover,
.breadcrumb a:focus:nth-child(even) {
background: var(--breadcrumb-hover-background-even);
}
.breadcrumb a:hover {

View File

@@ -25,7 +25,7 @@
Modified
</th>
<th
class="sortcolumn modified right"
class="sortcolumn size right"
:class="{ sortactive: sort === 'size' }"
@click="toggleSort('size')"
>
@@ -69,7 +69,11 @@
type="checkbox"
tabindex="-1"
:checked="documentStore.selected.has(doc.key)"
@change="($event.target as HTMLInputElement).checked ? documentStore.selected.add(doc.key) : documentStore.selected.delete(doc.key)"
@change="
($event.target as HTMLInputElement).checked
? documentStore.selected.add(doc.key)
: documentStore.selected.delete(doc.key)
"
/>
</td>
<td class="name">
@@ -84,13 +88,24 @@
"
/></template>
<template v-else>
<a :href="url_for(doc)" tabindex="-1" @contextmenu.stop @click.stop @focus.stop="cursor = doc">{{ doc.name }}</a>
<a
:href="url_for(doc)"
tabindex="-1"
@contextmenu.stop
@click.stop
@focus.stop="cursor = doc"
>{{ doc.name }}</a
>
<button @click="() => (editing = doc)">🖊</button>
</template>
</td>
<td class="modified right">{{ doc.modified }}</td>
<td class="size right">{{ doc.sizedisp }}</td>
<td class="menu"><button tabindex="-1" @click.stop="cursor = doc; contextMenu($event, doc)"></button></td>
<td class="menu">
<button tabindex="-1" @click.stop="cursor = doc; contextMenu($event, doc)">
</button>
</td>
</tr>
</tbody>
</table>
@@ -107,18 +122,15 @@ import { formatSize, formatUnixDate } from '@/utils'
const props = withDefaults(
defineProps<{
path: string
path: Array<string>
documents: Document[]
}>(),
{}
)
const documentStore = useDocumentStore()
const linkBasePath = computed(() => {
const path = props.path
return path === '/' ? '' : path
})
const filesBasePath = computed(() => `/files${linkBasePath.value}`)
const linkBasePath = computed(() => props.path.join('/'))
const filesBasePath = computed(() => `/files/${linkBasePath.value}`)
const url_for = (doc: FolderDocument) =>
doc.type === 'folder'
? `#${linkBasePath.value}/${doc.name}/`
@@ -162,7 +174,7 @@ defineExpose({
}
},
toggleSelectAll() {
console.log("Select")
console.log('Select')
allSelected.value = !allSelected.value
},
isCursor() {
@@ -172,7 +184,7 @@ defineExpose({
editing.value = cursor.value
},
cursorSelect() {
console.log("select", documentStore.selected)
console.log('select', documentStore.selected)
const doc = cursor.value
if (!doc) return
if (documentStore.selected.has(doc.key)) {
@@ -191,14 +203,18 @@ defineExpose({
const mod = (a: number, b: number) => ((a % b) + b) % b
const index = cursor.value !== null ? documents.indexOf(cursor.value) : -1
cursor.value = documents[mod(index + d, documents.length + 1)] ?? null
const tr = document.getElementById(`file-${cursor.value.key}`) as HTMLTableRowElement | null
const tr = document.getElementById(
`file-${cursor.value.key}`
) as HTMLTableRowElement | null
// @ts-ignore
if (tr) tr.scrollIntoView({ block: 'center' })
}
})
watchEffect(() => {
if (cursor.value) {
const a = document.querySelector(`#file-${cursor.value.key} .name a`) as HTMLAnchorElement | null
const a = document.querySelector(
`#file-${cursor.value.key} .name a`
) as HTMLAnchorElement | null
if (a) a.focus()
}
})
@@ -229,7 +245,8 @@ const toggleSort = (name: string) => {
}
const sort = ref<string>('')
const sortCompare = {
name: (a: Document, b: Document) => a.name.localeCompare(b.name, undefined, {numeric: true, sensitivity: 'base'}),
name: (a: Document, b: Document) =>
a.name.localeCompare(b.name, undefined, { numeric: true, sensitivity: 'base' }),
modified: (a: FolderDocument, b: FolderDocument) => b.mtime - a.mtime,
size: (a: FolderDocument, b: FolderDocument) => b.size - a.size
}
@@ -258,7 +275,7 @@ const allSelected = computed({
)
},
set: (value: boolean) => {
console.log("Setting allSelected", value)
console.log('Setting allSelected', value)
for (const doc of props.documents) {
if (value) {
documentStore.selected.add(doc.key)
@@ -287,10 +304,10 @@ table .selection {
width: 1rem;
}
table .modified {
width: 10rem;
width: 9rem;
}
table .size {
width: 5rem;
width: 4rem;
}
table .menu {
width: 1rem;

View File

@@ -30,8 +30,11 @@ defineExpose({
<nav>
<div class="buttons">
<UploadButton />
<SvgButton name="create-folder" @click="() => documentStore.fileExplorer.newFolder()"/>
<HeaderSelected />
<SvgButton
name="create-folder"
@click="() => documentStore.fileExplorer.newFolder()"
/>
<slot></slot>
<div class="spacer"></div>
<template v-if="showSearchInput">
<input
@@ -60,12 +63,6 @@ defineExpose({
.spacer {
flex-grow: 1;
}
.smallgap {
margin-left: 2em;
}
.select-text {
color: var(--accent-color);
}
input[type='search'] {
background: var(--primary-background);
color: var(--primary-color);

View File

@@ -1,84 +1,114 @@
<template v-if="documentStore.selected.size">
<div class="smallgap"></div>
<div class="selected-actions">
<p class="select-text">{{ documentStore.selected.size }} selected </p>
<SvgButton name="download" @click="download"/>
<SvgButton name="copy" />
<SvgButton name="paste" />
<SvgButton name="trash" />
<button @click="documentStore.selected.clear()"></button>
</div>
<p class="select-text">{{ documentStore.selected.size }} selected </p>
<SvgButton name="download" @click="download" />
<SvgButton name="copy" @click="op('cp', dst)" />
<SvgButton name="paste" @click="op('mv', dst)" />
<SvgButton name="trash" @click="op('rm')" />
<button @click="documentStore.selected.clear()"></button>
</template>
<script setup lang="ts">
import createWebSocket from '@/repositories/WS'
import { useDocumentStore } from '@/stores/documents'
import { computed } from 'vue'
import type { SelectedItems } from '@/repositories/Document'
const documentStore = useDocumentStore()
const props = defineProps({
path: Array<string>
})
const dst = computed(() => props.path!.join('/'))
const op = (op: string, dst?: string) => {
const sel = documentStore.selectedFiles
const msg = {
op,
sel: sel.ids.filter(id => sel.selected.has(id)).map(id => sel.fullpath[id])
}
// @ts-ignore
if (dst !== undefined) msg.dst = dst
const control = createWebSocket('/api/control', ev => {
const res = JSON.parse(ev.data)
if ('error' in res) {
console.error('Control socket error', msg, res.error)
return
} else if (res.status === 'ack') {
console.log('Control ack OK', res)
control.close()
documentStore.selected.clear()
return
} else console.log('Unknown control respons', msg, res)
})
control.onopen = () => {
control.send(JSON.stringify(msg))
}
}
const linkdl = (href: string) => {
const a = document.createElement("a")
const a = document.createElement('a')
a.href = href
a.download = ''
a.click()
}
const filesystemdl = async (sel: SelectedItems, handle: FileSystemDirectoryHandle) => {
let hdir = ""
let hdir = ''
let h = handle
let filelist = []
for (const id of sel.ids) {
filelist.push(sel.relpath[id])
}
console.log("Downloading to filesystem", filelist)
console.log('Downloading to filesystem', filelist)
for (const id of sel.ids) {
const rel = sel.relpath[id]
const url = sel.url[id] // Only files, not folders
const url = sel.url[id] // Only files, not folders
// Create any missing directories
if (!rel.startsWith(hdir)) {
hdir = ""
hdir = ''
h = handle
}
const r = rel.slice(hdir.length)
for (const dir of r.split('/').slice(0, url ? -1 : undefined)) {
hdir += `${dir}/`
try {
h = await h.getDirectoryHandle(dir.normalize("NFC"), { create: true })
h = await h.getDirectoryHandle(dir.normalize('NFC'), { create: true })
} catch (error) {
console.error("Failed to create directory", hdir, error)
console.error('Failed to create directory', hdir, error)
return
}
console.log("Created", hdir)
console.log('Created', hdir)
}
if (!url) continue // Target was a folder and was created
const name = rel.split('/').pop().normalize('NFC')
if (!url) continue // Target was a folder and was created
const name = rel.split('/').pop()!.normalize('NFC')
// Download file
let fileHandle
try {
fileHandle = await h.getFileHandle(name, { create: true })
} catch (error) {
console.error("Failed to create file", hdir + name, error)
console.error('Failed to create file', hdir + name, error)
return
}
const writable = await fileHandle.createWritable()
console.log("Fetching", url)
console.log('Fetching', url)
const res = await fetch(url)
if (!res.ok) throw new Error(`Failed to download ${url}: ${res.status} ${res.statusText}`)
if (!res.ok)
throw new Error(`Failed to download ${url}: ${res.status} ${res.statusText}`)
if (res.body) await res.body.pipeTo(writable)
else {
// Zero-sized files don't have a body, so we need to create an empty file
await writable.truncate(0)
await writable.close()
}
console.log("Saved", hdir + name)
console.log('Saved', hdir + name)
}
}
const download = async () => {
const sel = documentStore.selectedFiles
console.log("Download", sel)
console.log('Download', sel)
if (sel.selected.size === 0) {
console.warn("Attempted download but no files found. Missing:", sel.missing)
console.warn('Attempted download but no files found. Missing:', sel.missing)
documentStore.selected.clear()
return
}
@@ -89,25 +119,35 @@ const download = async () => {
return linkdl(urls[0] as string)
}
// Use FileSystem API if multiple files and the browser supports it
if ("showDirectoryPicker" in window) {
if ('showDirectoryPicker' in window) {
try {
// @ts-ignore
const handle = await window.showDirectoryPicker({ startIn: 'downloads', mode: 'readwrite' })
filesystemdl(sel, handle, "").then(() => { documentStore.selected.clear() })
const handle = await window.showDirectoryPicker({
startIn: 'downloads',
mode: 'readwrite'
})
filesystemdl(sel, handle).then(() => {
documentStore.selected.clear()
})
return
} catch (e) {
console.error("Download to folder aborted", e)
console.error('Download to folder aborted', e)
}
}
// Otherwise, zip and download
linkdl(`/zip/${sel.selected.join('+')}/download.zip`)
linkdl(`/zip/${Array.from(sel.selected).join('+')}/download.zip`)
documentStore.selected.clear()
}
</script>
<style scoped>
.selected-actions {
display: flex;
align-items: center;
.smallgap {
margin-left: 2em;
}
.select-text {
color: var(--accent-color);
text-wrap: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
</style>

View File

@@ -26,7 +26,8 @@ button {
width: 3rem;
height: 3rem;
}
button:hover, button:focus {
button:hover,
button:focus {
color: #fff;
transform: scale(1.1);
}
@@ -34,7 +35,8 @@ svg {
fill: #ccc;
transform: fill 0.2s ease;
}
button:hover svg, button:focus svg {
button:hover svg,
button:focus svg {
fill: #fff;
}
</style>

View File

@@ -65,7 +65,6 @@ export interface SelectedItems {
ids: FUID[]
}
export const url_document_watch_ws = '/api/watch'
export const url_document_upload_ws = '/api/upload'
export const url_document_get = '/files'

View File

@@ -4,7 +4,7 @@ import type {
FileEntry,
FUID,
DirList,
SelectedItems,
SelectedItems
} from '@/repositories/Document'
import { formatSize, formatUnixDate } from '@/utils'
import { defineStore } from 'pinia'
@@ -74,7 +74,14 @@ export const useDocumentStore = defineStore({
}
// Pre sort directory entries folders first then files, names in natural ordering
dataMapped.sort((a, b) =>
a.type === b.type ? a.name.localeCompare(b.name, undefined, {numeric: true, sensitivity: 'base'}) : a.type === 'folder' ? -1 : 1
a.type === b.type
? a.name.localeCompare(b.name, undefined, {
numeric: true,
sensitivity: 'base'
})
: a.type === 'folder'
? -1
: 1
)
this.document = dataMapped
},
@@ -183,7 +190,7 @@ export const useDocumentStore = defineStore({
ret.fullpath[attr.id] = fullname
ret.relpath[attr.id] = r
ret.ids.push(attr.id)
if (!("dir" in attr)) ret.url[attr.id] = `/files/${fullname}`
if (!('dir' in attr)) ret.url[attr.id] = `/files/${fullname}`
}
traverseDir(attr, fullname, r)
}
@@ -205,7 +212,12 @@ export const useDocumentStore = defineStore({
if (!ret.selected.has(id)) ret.missing.add(id)
}
// Sorted array of FUIDs for easy traversal
ret.ids.sort((a, b) => ret.relpath[a].localeCompare(ret.relpath[b] , undefined, {numeric: true, sensitivity: 'base'}))
ret.ids.sort((a, b) =>
ret.relpath[a].localeCompare(ret.relpath[b], undefined, {
numeric: true,
sensitivity: 'base'
})
)
return ret
}
}

View File

@@ -2,7 +2,7 @@
<FileExplorer
ref="fileExplorer"
:key="Router.currentRoute.value.path"
:path="Router.currentRoute.value.path"
:path="props.path"
:documents="documentStore.mainDocument"
/>
</template>
@@ -15,7 +15,12 @@ import Router from '@/router/index'
const documentStore = useDocumentStore()
const fileExplorer = ref()
watchEffect(() => { documentStore.fileExplorer = fileExplorer.value })
const props = defineProps({
path: Array<string>
})
watchEffect(() => {
documentStore.fileExplorer = fileExplorer.value
})
watchEffect(async () => {
const path = new String(Router.currentRoute.value.path) as string
documentStore.setActualDocument(path.toString())