Prettier file listing, using browser instead of viewer for file display (for now), sorting improved, modified timestamps improved.

This commit is contained in:
Leo Vasanko
2023-10-27 07:53:30 +03:00
parent 63bbe84859
commit 0d186726b5
8 changed files with 198 additions and 185 deletions

View File

@@ -9,7 +9,7 @@
import { computed } from 'vue'
import HeaderMain from './components/HeaderMain.vue'
import AppNavigation from './components/AppNavigation.vue'
import Router from './router/index';
import Router from './router/index';
interface Path {
path: string;
@@ -26,15 +26,16 @@
pathList
}
})
// Update human-readable x seconds ago messages from mtimes
setInterval(documentStore.updateModified, 1000)
watchEffect(() => {
const documentHandler = new DocumentHandler()
const documentUploadHandler = new DocumentUploadHandler()
const wsWatch = createWebSocket(url_document_watch_ws, documentHandler.handleWebSocketMessage)
const wsUpload = createWebSocket(url_document_upload_ws, documentUploadHandler.handleWebSocketMessage)
documentStore.wsWatch = wsWatch;
documentStore.wsUpload = wsUpload;
documentStore.wsWatch = wsWatch;
documentStore.wsUpload = wsUpload;
})
export type { Path }
@@ -45,14 +46,14 @@
<HeaderMain WS="WS"></HeaderMain>
<AppNavigation :path="path.pathList"></AppNavigation>
</header>
<RouterView class="page-container" />
</template>
<style scoped>
.wrapper{
background-color: var(--primary-background);
padding: 0.2em 0.5em;
padding: 0.2em 0.5em;
display: flex;
flex-direction: column;
gap: 10px;

View File

@@ -2,30 +2,31 @@
<main>
<!-- <h2 v-if="!documentStore.loading && documentStore.error"> {{ documentStore.error }} </h2> -->
<div class="carousel-container" v-if="!documentStore.loading && documentStore.mainDocument[0] && documentStore.mainDocument[0].type === 'file'">
<FileCarousel></FileCarousel>
<FileCarousel></FileCarousel>
</div>
<a-table
<a-table
v-else-if="!documentStore.loading && documentStore.mainDocument"
:pagination=false
:pagination=false
:row-selection="{ selectedRowKeys: state.selectedRowKeys, onChange: onSelectChange }"
:columns="columns"
:columns="columns"
:data-source="documentStore.mainDocument"
>
<template #headerCell="{column}"></template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'name'">
<div class="editable-cell">
<div class="editable-cell" :class="record.type === 'folder' ? 'folder' : 'file'">
<div v-if="editableData[record.key]" class="action-container editable-cell-input-wrapper">
<a-input class="name" v-model:value="editableData[record.key].name" @pressEnter="save(record.key)" />
<CheckOutlined class="edit-action editable-cell-icon-check" @click="save(record.key)" />
</div>
<div v-else class="action-container editable-cell-text-wrapper">
<a class="name" :href="`#${linkBasePath}/${record.name}`">{{record.name}}</a>
<a v-if="record.type === 'folder'" class="name" :href="`#${linkBasePath}/${record.name}`">{{record.name}}</a>
<a v-else class="name" :href="`${filesBasePath}/${record.name}`">{{record.name}}</a>
<edit-outlined class="edit-action editable-cell-icon" @click="edit(record.key)" />
</div>
</div>
</template>
</template>
<template v-if="column.key === 'action'">
<a-popover trigger="click">
<template #content>
@@ -34,19 +35,19 @@
<a-button type="text" class="action-button" :icon="h(ImportOutlined)"/>Open
</div>
<div class="action-container">
<a-button type="text" class="action-button" :icon="h(EditOutlined)"/> Rename
<a-button type="text" class="action-button" :icon="h(EditOutlined)"/> Rename
</div>
<div class="action-container">
<a-button type="text" class="action-button" :icon="h(LinkOutlined)"/> Share
<a-button type="text" class="action-button" :icon="h(LinkOutlined)"/> Share
</div>
<div class="action-container">
<a-button type="text" class="action-button" :icon="h(CopyOutlined)"/> Copy
<a-button type="text" class="action-button" :icon="h(CopyOutlined)"/> Copy
</div>
<div class="action-container">
<a-button type="text" class="action-button" :icon="h(ScissorOutlined)"/> Cut
<a-button type="text" class="action-button" :icon="h(ScissorOutlined)"/> Cut
</div>
<div class="action-container">
<a-button type="text" class="action-button" :icon="h(DeleteOutlined)"/> Delete
<a-button type="text" class="action-button" :icon="h(DeleteOutlined)"/> Delete
</div>
</div>
</template>
@@ -54,12 +55,12 @@
</a-popover>
</template>
</template>
</a-table>
</main>
</template>
<script setup lang="ts">
import { ref, h, computed, reactive, watchEffect } from 'vue'
import type { UnwrapRef } from 'vue'
@@ -82,12 +83,13 @@
}>({
selectedRowKeys: [],
});
const linkBasePath = computed(()=>{
if(Router.currentRoute.value.path === '/') return ''
return Router.currentRoute.value.path
const path = Router.currentRoute.value.path
return path === '/' ? '' : path
})
const filesBasePath = computed(() => `/files${linkBasePath.value}`)
const columns = ref<TableColumnsType>([
{
title: 'Name',
@@ -95,34 +97,26 @@
width: '70%',
key: 'name',
sortDirections: ['ascend', 'descend'],
sorter: (a: Document, b: Document, sortOrder) => {
return b.name.localeCompare(a.name)
}
sorter: (a: Document, b: Document) => a.name.localeCompare(b.name),
},
{
title: 'Modified',
dataIndex: 'modified',
className: 'column-date',
responsive: ['lg'],
sortDirections: ['ascend', 'descend'],
defaultSortOrder: 'descend',
sorter: (a: FolderDocument, b: FolderDocument) => {
const dateA = new Date(a.modified),
dateB = new Date(b.modified);
if (dateA < dateB) return -1
if (dateA > dateB) return 1
return 0
},
sorter: (a: FolderDocument, b: FolderDocument) => a.mtime - b.mtime,
key: 'modified',
},
{
// TODO BETTER SORT FOR MULTPLE SIZE OR CUSTOM PIPE TO kB to MB / GB
title: 'Size',
dataIndex: 'size',
className: 'column-size',
responsive: ['lg'],
sortDirections: ['ascend', 'descend'],
sorter: (a: FolderDocument, b: FolderDocument) => {
return a.size - b.size
},
sorter: (a: FolderDocument, b: FolderDocument) => a.size - b.size,
key: 'size',
},
{
@@ -130,12 +124,12 @@
key: 'action',
},
])
const onSelectChange = (selectedRowKeys: Key[]) => {
const newSelectedRowKeys: Document[] = []
const newSelectedRowKeys: Document[] = []
selectedRowKeys.forEach( key => {
if(documentStore.mainDocument){
const found = documentStore.mainDocument.find( e=> e.key === key )
const found = documentStore.mainDocument.find( e=> e.key === key )
if(found) newSelectedRowKeys.push(found)
}
})
@@ -151,8 +145,11 @@
};
</script>
<style scoped>
<style>
.column-date, .column-size {
text-align: right;
}
main {
padding: 5px;
height: 100%;
@@ -175,11 +172,19 @@
.carousel-container{
height: inherit;
}
.file .name::before {
content: '📄 ';
font-size: 1.5em;
}
.folder .name::before {
content: '📁 ';
font-size: 1.5em;
}
.editable-cell-text-wrapper .editable-cell-icon {
visibility: hidden; /* Oculta el ícono de manera predeterminada */
}
.editable-cell-text-wrapper:hover .editable-cell-icon {
.editable-cell-text-wrapper:hover .editable-cell-icon {
visibility: visible; /* Muestra el ícono al hacer hover en el contenedor */
}
</style>
</style>

View File

@@ -6,13 +6,14 @@ import Client from '@/repositories/Client'
type BaseDocument = {
name: string;
key?: number;
key?: number | string;
};
export type FolderDocument = BaseDocument & {
type: 'folder' | 'folder-file';
size: number;
mtime: number;
modified: string;
type: 'folder';
};
export type FileDocument = BaseDocument & {
@@ -84,4 +85,4 @@ export async function fetchFile(path: string): Promise<FileDocument>{
type: 'file',
ext: getFileExtension(name)
}
}
}

View File

@@ -1,15 +1,15 @@
import type { Document } from '@/repositories/Document';
import type { Document, FolderDocument } from '@/repositories/Document';
import type { ISimpleError } from '@/repositories/Client';
import { fetchFile } from '@/repositories/Document'
import { formatUnixDate } from '@/utils';
import { defineStore } from 'pinia';
type FileData = { mtime: number, size: number, dir: DirectoryData};
type FileData = { id: string, mtime: number, size: number, dir: DirectoryData};
type DirectoryData = {
[filename: string]: FileData;
};
export type FileStructure = {mtime: number, size: number, dir: DirectoryData};
export type FileStructure = {id: string, mtime: number, size: number, dir: DirectoryData};
export type DocumentStore = {
root: FileStructure,
@@ -36,7 +36,7 @@ export const useDocumentStore = defineStore({
selectedDocuments: [] as Document[],
error: '' as string,
}),
actions: {
setActualDocument(location: string){
this.loading = true;
@@ -54,18 +54,20 @@ export const useDocumentStore = defineStore({
})
// Transform data
let count = 0
for (const key in data.dir) {
for (const [name, attr] of Object.entries(data.dir)) {
const {id, size, mtime, dir} = attr
const element: Document = {
name: key,
key: count,
size: data.dir[key].size,
modified: formatUnixDate(data.dir[key].mtime),
type: 'folder',
name,
key: id,
size,
mtime,
modified: formatUnixDate(mtime),
type: dir === undefined ? 'folder-file' : 'folder',
}
count++
dataMapped.push(element)
}
// 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) : a.type === "folder" ? -1 : 1)
this.document = dataMapped
this.loading = false;
},
@@ -79,15 +81,13 @@ export const useDocumentStore = defineStore({
this.selectedDocuments = document
},
deleteDocument(document: Document){
this.document = this.document.filter(e => document.key !== e.key)
this.selectedDocuments = this.selectedDocuments.filter(e => document.key !== e.key)
this.document = this.document.filter(e => document.key !== e.key)
this.selectedDocuments = this.selectedDocuments.filter(e => document.key !== e.key)
},
updateUploadingDocuments(key: number, progress: number){
this.uploadingDocuments.forEach((document) => {
if(document.key === key) {
document.progress = progress
}
})
for (const d of this.uploadingDocuments) {
if(d.key === key) d.progress = progress
}
},
pushUploadingDocuments(name: string){
this.uploadCount++;
@@ -100,7 +100,7 @@ export const useDocumentStore = defineStore({
return document
},
deleteUploadingDocument(key: number){
this.uploadingDocuments = this.uploadingDocuments.filter((e)=> e.key !== key)
this.uploadingDocuments = this.uploadingDocuments.filter((e)=> e.key !== key)
},
getNextDocumentInRoute(direction: number, path: string){
const locations = path.split('/').slice(1)
@@ -116,7 +116,7 @@ export const useDocumentStore = defineStore({
}
}
})
//Store in a temporary array
//Store in a temporary array
for (const key in data.dir) {
actualDirArr.push({
name: key,
@@ -125,7 +125,7 @@ export const useDocumentStore = defineStore({
}
const actualFileName = decodeURIComponent(this.mainDocument[0].name).split('/').pop()
let index = actualDirArr.findIndex(e => e.name === actualFileName)
if(index < 1 && direction === -1 ){
index = actualDirArr.length -1
}else if(index >= actualDirArr.length - 1 && direction === 1){
@@ -134,9 +134,13 @@ export const useDocumentStore = defineStore({
index = index + direction
}
return actualDirArr[index].name
},
updateModified() {
for (const d of this.document) {
if ("mtime" in d) d.modified = formatUnixDate(d.mtime)
}
}
},
getters: {
mainDocument(): Document[] {
return this.document;

View File

@@ -12,7 +12,9 @@ export function formatUnixDate(t: number) {
const diff = date.getTime() - now.getTime()
const formatter = new Intl.RelativeTimeFormat('en', { numeric:
'auto' })
if (Math.abs(diff) <= 5000) {
return 'now'
}
if (Math.abs(diff) <= 60000) {
return formatter.format(Math.round(diff / 1000), 'second')
}
@@ -29,7 +31,7 @@ export function formatUnixDate(t: number) {
return formatter.format(Math.round(diff / 86400000), 'day')
}
return date.toLocaleDateString()
return date.toLocaleDateString(undefined, { weekday: 'short', year: 'numeric', month: 'short', day: 'numeric' })
}
export function getFileExtension(filename: string) {
@@ -54,4 +56,4 @@ export function getFileType(extension: string): string {
} else {
return "unknown";
}
}
}