Compare commits

...

3 Commits

Author SHA1 Message Date
129250e072 Implement tooltips and other UI tuning. 2023-11-06 15:33:43 +00:00
c2be2ecd31 Fix copying of files. 2023-11-06 15:32:58 +00:00
dd1d85f412 dateformat code cleanup 2023-11-06 11:38:11 +00:00
9 changed files with 90 additions and 48 deletions

View File

@ -87,8 +87,7 @@
tbody .selection input:checked { tbody .selection input:checked {
opacity: 1 !important; opacity: 1 !important;
transform: scale(0.5); transform: scale(0.5);
top: 0.1rem !important; left: 0;
left: -0.3rem !important;
} }
} }
@ -103,7 +102,7 @@ main {
body { body {
background-color: var(--primary-background); background-color: var(--primary-background);
font-size: 1rem; font-size: 1rem;
font-family: 'Roboto', sans-serif; font-family: 'Roboto';
color: var(--primary-color); color: var(--primary-color);
margin: 0; margin: 0;
} }
@ -157,8 +156,48 @@ table {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
nav {
/* Position so that tooltips can appear on top of other positioned elements */
position: relative;
z-index: 10;
}
main { main {
height: calc(100svh - 9rem); /* fill almost the rest of the screen after header */ height: calc(100svh - 9rem); /* fill almost the rest of the screen after header */
padding-bottom: 3rem; /* convenience space on the bottom */ padding-bottom: 3rem; /* convenience space on the bottom */
overflow-y: scroll; overflow-y: scroll;
} }
[data-tooltip]:hover:after {
z-index: 1000;
content: attr(data-tooltip);
position: absolute;
font-size: 1rem;
text-align: center;
padding: .5rem 1rem;
border-radius: 3rem 0 3rem 0;
box-shadow: 0 0 2rem var(--accent-color);
transform: translate(calc(1rem + -50%), 150%);
background-color: var(--accent-color);
color: var(--primary-color);
white-space: pre;
animation: appearbriefly calc(10 * var(--transition-time)) linear forwards;
}
.modified [data-tooltip]:hover:after {
transform: translate(calc(1rem + 1ex + -100%), calc(-1.5rem + 100%));
}
@keyframes appearbriefly {
from {
opacity: 0;
}
30% {
opacity: 0;
}
40% {
opacity: 1;
}
90% {
opacity: 1;
}
to {
opacity: 0;
}
}

View File

@ -34,20 +34,20 @@ const props = defineProps<{
.breadcrumb > a { .breadcrumb > a {
margin: 0 -0.7rem 0 -0.7rem; margin: 0 -0.7rem 0 -0.7rem;
padding: 0; padding: 0;
max-width: 8em; max-width: 8rem;
font-size: 1.3em; font-size: 1.3rem;
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
overflow: hidden; overflow: hidden;
height: 1em; height: 1.5rem;
color: var(--breadcrumb-color); color: var(--breadcrumb-color);
padding: 0.3em 1.5em; padding: 0.3rem 1.5rem;
clip-path: polygon(0 0, 1em 50%, 0 100%, 100% 100%, 100% 0, 0 0); clip-path: polygon(0 0, 1rem 50%, 0 100%, 100% 100%, 100% 0, 0 0);
transition: all var(--breadcrumb-transtime); transition: all var(--breadcrumb-transtime);
} }
.breadcrumb a:first-child { .breadcrumb a:first-child {
margin-left: 0; margin-left: 0;
padding-left: 0; padding-left: .2rem;
clip-path: none; clip-path: none;
} }
.breadcrumb a:last-child { .breadcrumb a:last-child {

View File

@ -110,7 +110,7 @@
</td> </td>
<td class="modified right"> <td class="modified right">
<time <time
:datetime="new Date(1000 * doc.mtime).toISOString().replace('.000', '')" :data-tooltip="new Date(1000 * doc.mtime).toISOString().replace('T', '\n').replace('.000Z', ' UTC')"
>{{ doc.modified }}</time >{{ doc.modified }}</time
> >
</td> </td>
@ -136,7 +136,7 @@ import type { Document, FolderDocument } from '@/repositories/Document'
import FileRenameInput from './FileRenameInput.vue' import FileRenameInput from './FileRenameInput.vue'
import createWebSocket from '@/repositories/WS' import createWebSocket from '@/repositories/WS'
import { formatSize, formatUnixDate } from '@/utils' import { formatSize, formatUnixDate } from '@/utils'
import { isNavigationFailure, useRouter } from 'vue-router' import { useRouter } from 'vue-router'
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
@ -352,12 +352,13 @@ thead tr {
} }
tbody tr { tbody tr {
position: relative; position: relative;
z-index: auto;
} }
table thead input[type='checkbox'] { table thead input[type='checkbox'] {
position: inherit; position: inherit;
width: 1rem; width: 1rem;
height: 1rem; height: 1rem;
margin: 0.5rem; padding: 0.5rem;
} }
table tbody input[type='checkbox'] { table tbody input[type='checkbox'] {
width: 2rem; width: 2rem;
@ -413,6 +414,7 @@ table td {
thead tr { thead tr {
background: linear-gradient(to bottom, #eee, #fff 30%, #ddd); background: linear-gradient(to bottom, #eee, #fff 30%, #ddd);
color: #000; color: #000;
box-shadow: 0 0 .2rem black;
} }
tbody tr.cursor { tbody tr.cursor {
background: var(--accent-color); background: var(--accent-color);
@ -458,12 +460,13 @@ tbody .selection input {
opacity: 0.7; opacity: 0.7;
} }
.file .selection::before { .file .selection::before {
content: '📄 '; content: '📄';
font-size: 1.5em; font-size: 1.5rem;
} }
.folder .selection::before { .folder .selection::before {
content: '📁 '; height: 2rem;
font-size: 1.5em; content: '📁';
font-size: 1.5rem;
} }
.empty-container { .empty-container {
padding-top: 3rem; padding-top: 3rem;

View File

@ -32,6 +32,7 @@ defineExpose({
<UploadButton /> <UploadButton />
<SvgButton <SvgButton
name="create-folder" name="create-folder"
data-tooltip="New folder"
@click="() => documentStore.fileExplorer.newFolder()" @click="() => documentStore.fileExplorer.newFolder()"
/> />
<slot></slot> <slot></slot>
@ -53,10 +54,11 @@ defineExpose({
<style scoped> <style scoped>
.buttons { .buttons {
padding: 0 0.5em; padding: 0;
display: flex; display: flex;
align-items: center; align-items: center;
height: 3.5rem; height: 3.5rem;
z-index: 10;
} }
.buttons > * { .buttons > * {
flex-shrink: 1; flex-shrink: 1;

View File

@ -2,11 +2,11 @@
<template v-if="documentStore.selected.size"> <template v-if="documentStore.selected.size">
<div class="smallgap"></div> <div class="smallgap"></div>
<p class="select-text">{{ documentStore.selected.size }} selected </p> <p class="select-text">{{ documentStore.selected.size }} selected </p>
<SvgButton name="download" @click="download" /> <SvgButton name="download" data-tooltip="Download" @click="download" />
<SvgButton name="copy" @click="op('cp', dst)" /> <SvgButton name="copy" data-tooltip="Copy here" @click="op('cp', dst)" />
<SvgButton name="paste" @click="op('mv', dst)" /> <SvgButton name="paste" data-tooltip="Move here" @click="op('mv', dst)" />
<SvgButton name="trash" @click="op('rm')" /> <SvgButton name="trash" data-tooltip="Delete " @click="op('rm')" />
<button @click="documentStore.selected.clear()"></button> <button class="action-button unselect" data-tooltip="Unselect all" @click="documentStore.selected.clear()"></button>
</template> </template>
</template> </template>

View File

@ -15,8 +15,8 @@ const props = defineProps<{
const icon = defineAsyncComponent(() => import(`@/assets/svg/${props.name}.svg`)) const icon = defineAsyncComponent(() => import(`@/assets/svg/${props.name}.svg`))
</script> </script>
<style scoped> <style>
button { .action-button {
background: none; background: none;
border: none; border: none;
color: #ccc; color: #ccc;
@ -26,8 +26,8 @@ button {
width: 3rem; width: 3rem;
height: 3rem; height: 3rem;
} }
button:hover, .action-button:hover,
button:focus { .action-button:focus {
color: #fff; color: #fff;
transform: scale(1.1); transform: scale(1.1);
} }
@ -35,8 +35,8 @@ svg {
fill: #ccc; fill: #ccc;
transform: fill 0.2s ease; transform: fill 0.2s ease;
} }
button:hover svg, .action-button:hover svg,
button:focus svg { .action-button:focus svg {
fill: #fff; fill: #fff;
} }
</style> </style>

View File

@ -91,6 +91,6 @@ async function uploadFileChangeHandler(event: Event) {
webkitdirectory webkitdirectory
/> />
</template> </template>
<SvgButton name="add-file" @click="fileUploadButton.click()" /> <SvgButton name="add-file" data-tooltip="Upload files" @click="fileUploadButton.click()" />
<SvgButton name="add-folder" @click="folderUploadButton.click()" /> <SvgButton name="add-folder" data-tooltip="Upload folder" @click="folderUploadButton.click()" />
</template> </template>

View File

@ -22,26 +22,21 @@ export function formatUnixDate(t: number) {
const date = new Date(t * 1000) const date = new Date(t * 1000)
const now = new Date() const now = new Date()
const diff = date.getTime() - now.getTime() const diff = date.getTime() - now.getTime()
const adiff = Math.abs(diff)
const formatter = new Intl.RelativeTimeFormat('en', { numeric: 'auto' }) const formatter = new Intl.RelativeTimeFormat('en', { numeric: 'auto' })
if (Math.abs(diff) <= 5000) { if (adiff <= 5000) return 'now'
return 'now' if (adiff <= 60000) {
}
if (Math.abs(diff) <= 60000) {
return formatter.format(Math.round(diff / 1000), 'second').replace(' ago', '').replaceAll(' ', '\u202F') return formatter.format(Math.round(diff / 1000), 'second').replace(' ago', '').replaceAll(' ', '\u202F')
} }
if (adiff <= 3600000) {
if (Math.abs(diff) <= 3600000) {
return formatter.format(Math.round(diff / 60000), 'minute').replace('utes', '').replace('ute', '').replaceAll(' ', '\u202F') return formatter.format(Math.round(diff / 60000), 'minute').replace('utes', '').replace('ute', '').replaceAll(' ', '\u202F')
} }
if (adiff <= 86400000) {
if (Math.abs(diff) <= 86400000) {
return formatter.format(Math.round(diff / 3600000), 'hour').replaceAll(' ', '\u202F') return formatter.format(Math.round(diff / 3600000), 'hour').replaceAll(' ', '\u202F')
} }
if (adiff <= 604800000) {
if (Math.abs(diff) <= 604800000) {
return formatter.format(Math.round(diff / 86400000), 'day').replaceAll(' ', '\u202F') return formatter.format(Math.round(diff / 86400000), 'day').replaceAll(' ', '\u202F')
} }
let d = date.toLocaleDateString('en-ie', { let d = date.toLocaleDateString('en-ie', {
weekday: 'short', weekday: 'short',
year: 'numeric', year: 'numeric',

View File

@ -75,13 +75,16 @@ class Cp(ControlBase):
if not dst.is_dir(): if not dst.is_dir():
raise BadRequest("The destination must be a directory") raise BadRequest("The destination must be a directory")
for p in sel: for p in sel:
# Note: copies as dst rather than in dst unless name is appended. if p.is_dir():
shutil.copytree( # Note: copies as dst rather than in dst unless name is appended.
p, shutil.copytree(
dst / p.name, p,
dirs_exist_ok=True, dst / p.name,
ignore_dangling_symlinks=True, dirs_exist_ok=True,
) ignore_dangling_symlinks=True,
)
else:
shutil.copy2(p, dst)
ControlTypes = MkDir | Rename | Rm | Mv | Cp ControlTypes = MkDir | Rename | Rm | Mv | Cp