Prototyping plain table for files list

This commit is contained in:
Leo Vasanko 2023-11-02 15:34:37 +00:00
parent 05a16e3037
commit 68a701538b
4 changed files with 112 additions and 134 deletions

View File

@ -18,11 +18,9 @@ declare module 'vue' {
AInputSearch: typeof import('ant-design-vue/es')['InputSearch'] AInputSearch: typeof import('ant-design-vue/es')['InputSearch']
AModal: typeof import('ant-design-vue/es')['Modal'] AModal: typeof import('ant-design-vue/es')['Modal']
APageHeader: typeof import('ant-design-vue/es')['PageHeader'] APageHeader: typeof import('ant-design-vue/es')['PageHeader']
APopover: typeof import('ant-design-vue/es')['Popover']
AppNavigation: typeof import('./src/components/AppNavigation.vue')['default'] AppNavigation: typeof import('./src/components/AppNavigation.vue')['default']
AProgress: typeof import('ant-design-vue/es')['Progress'] AProgress: typeof import('ant-design-vue/es')['Progress']
ARow: typeof import('ant-design-vue/es')['Row'] ARow: typeof import('ant-design-vue/es')['Row']
ATable: typeof import('ant-design-vue/es')['Table']
ATooltip: typeof import('ant-design-vue/es')['Tooltip'] ATooltip: typeof import('ant-design-vue/es')['Tooltip']
FileCarousel: typeof import('./src/components/FileCarousel.vue')['default'] FileCarousel: typeof import('./src/components/FileCarousel.vue')['default']
FileExplorer: typeof import('./src/components/FileExplorer.vue')['default'] FileExplorer: typeof import('./src/components/FileExplorer.vue')['default']

View File

@ -6,72 +6,26 @@
<FileCarousel></FileCarousel> <FileCarousel></FileCarousel>
</div> </div>
<a-table <table v-else-if="!documentStore.loading && documentStore.mainDocument">
v-else-if="!documentStore.loading && documentStore.mainDocument" <thead>
:pagination=false <tr>
:row-selection="{ selectedRowKeys: state.selectedRowKeys, onChange: onSelectChange }" <th class="selection"><input type="checkbox" v-model="allSelected" :indeterminate="selectionIndeterminate"></th>
:columns="columns" <th class="sortcolumn" :class="{sortactive: sort === 'name'}" @click="toggleSort('name')">Name</th>
:data-source="documentStore.mainDocument" <th class="sortcolumn modified right" :class="{sortactive: sort === 'modified'}" @click="toggleSort('modified')">Modified</th>
> <th class="sortcolumn size right" :class="{sortactive: sort === 'size'}" @click="toggleSort('size')">Size</th>
<template #headerCell="{column}"></template> </tr>
<template #bodyCell="{ column, record }"> </thead>
<template v-if="column.key === 'name'"> <tbody>
<div class="editable-cell" :class="record.type === 'folder' ? 'folder' : 'file'"> <tr v-for="doc of sorted(documentStore.mainDocument as FolderDocument[])" :key="doc.key" :class="doc.type === 'folder' ? 'folder' : 'file'">
<div v-if="editableData[record.key]" class="action-container editable-cell-input-wrapper"> <td class="selection"><input type="checkbox" v-model="doc.selected"></td>
<a-input class="name" v-model:value="editableData[record.key].name" @pressEnter="save(record.key)" /> <td class="name">
<CheckOutlined class="edit-action editable-cell-icon-check" @click="save(record.key)" /> <a :href="url_for(doc)">{{doc.name}}</a>
</div> </td>
<div v-else class="action-container editable-cell-text-wrapper"> <td class="right">{{doc.modified}}</td>
<a v-if="record.type === 'folder'" class="name" :href="`#${linkBasePath}/${record.name}`">{{record.name}}</a> <td class="right">{{doc.sizedisp}}</td>
<a v-else class="name" :href="`${filesBasePath}/${record.name}`">{{record.name}}</a> </tr>
<edit-outlined class="edit-action editable-cell-icon" @click="edit(record.key)" /> </tbody>
</div> </table>
</div>
</template>
<template v-if="column.key === 'action'">
<a-popover trigger="click">
<template #content>
<div class="more-action">
<div class="action-container">
<a :href="`${record.type === 'folder'? linkBasePath+'#' : filesBasePath}/${record.name}`">
<a-button type="text" class="action-button" :icon="h(ImportOutlined)"/>
</a>
Open
</div>
<div v-if="record.type === 'folder-file'" class="action-container">
<a :href="`${filesBasePath}/${record.name}`" download>
<a-button type="text" class="action-button" :icon="h(DownloadOutlined)"/>
</a>
Download
</div>
<div class="action-container">
<a-button type="text" class="action-button" @click="edit(record.key)" :icon="h(EditOutlined)"/> Rename
</div>
<div class="action-container">
<a-button
type="text"
class="action-button"
@click="share(`${record.type === 'folder'? linkBasePath+'/#' : filesBasePath}/${record.name}`)"
:icon="h(LinkOutlined)"
/> Share
</div>
<div class="action-container">
<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
</div>
<div class="action-container">
<a-button type="text" class="action-button" :icon="h(DeleteOutlined)"/> Delete
</div>
</div>
</template>
<a-button type="text" class="action-button" :icon="h(EllipsisOutlined)" />
</a-popover>
</template>
</template>
</a-table>
</main> </main>
</template> </template>
@ -79,10 +33,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, h, computed, reactive, watchEffect } from 'vue' import { ref, h, computed, reactive, watchEffect } from 'vue'
import type { UnwrapRef } from 'vue' import type { UnwrapRef } from 'vue'
import { cloneDeep } from 'lodash';
import { useDocumentStore } from '@/stores/documents' import { useDocumentStore } from '@/stores/documents'
import { EditOutlined, ImportOutlined, CheckOutlined,CopyOutlined, ScissorOutlined, LinkOutlined, DownloadOutlined, DeleteOutlined, EllipsisOutlined } from '@ant-design/icons-vue'
import type { TableColumnsType } from 'ant-design-vue';
import Router from '@/router/index'; import Router from '@/router/index';
import { message } from 'ant-design-vue'; import { message } from 'ant-design-vue';
import type { Document, FolderDocument } from '@/repositories/Document'; import type { Document, FolderDocument } from '@/repositories/Document';
@ -104,70 +55,95 @@
return path === '/' ? '' : path return path === '/' ? '' : path
}) })
const filesBasePath = computed(() => `/files${linkBasePath.value}`) const filesBasePath = computed(() => `/files${linkBasePath.value}`)
const url_for = (doc: FolderDocument) => doc.type === "folder" ? `#${linkBasePath.value}/${doc.name}` : `${filesBasePath}/${doc.name}`
const columns = ref<TableColumnsType>([ const toggleSort = (name: string) => { sort.value = sort.value === name ? "" : name }
{ const sort = ref<string>("")
title: 'Name', const sortCompare = {
dataIndex: 'name', "name": (a: Document, b: Document) => a.name.localeCompare(b.name),
width: '70%', "modified": (a: FolderDocument, b: FolderDocument) => b.mtime - a.mtime,
key: 'name', "size": (a: FolderDocument, b: FolderDocument) => b.size - a.size
sortDirections: ['ascend', 'descend'],
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) => a.mtime - b.mtime,
key: 'modified',
},
{
// TODO BETTER SORT FOR MULTPLE SIZE OR CUSTOM PIPE TO kB to MB / GB
title: 'Size',
dataIndex: 'sizedisp',
className: 'column-size',
responsive: ['lg'],
sortDirections: ['ascend', 'descend'],
sorter: (a: FolderDocument, b: FolderDocument) => a.size - b.size,
key: 'size',
},
{
width: '5%',
key: 'action',
},
])
const onSelectChange = (selectedRowKeys: Key[]) => {
const newSelectedRowKeys: Document[] = []
selectedRowKeys.forEach( key => {
if(documentStore.mainDocument){
const found = documentStore.mainDocument.find( e=> e.key === key )
if(found) newSelectedRowKeys.push(found)
}
})
documentStore.setSelectedDocuments(newSelectedRowKeys)
state.selectedRowKeys = selectedRowKeys;
};
const edit = (key: number) => {
editableData[key] = cloneDeep(documentStore.mainDocument.filter(item => key === item.key)[0]);
};
const save = (key: number) => {
Object.assign(documentStore.mainDocument.filter(item => key === item.key)[0], editableData[key]);
delete editableData[key];
};
const share = async (url : string) => {
await navigator.clipboard.writeText(location.origin + url)
messageApi.success("Link successfully copied to the clipboard");
} }
const sorted = (documents: FolderDocument[]) => {
const cmp = sortCompare[sort.value as keyof typeof sortCompare]
const sorted = [...documents]
if (cmp) sorted.sort(cmp)
return sorted
}
const selectionIndeterminate = computed({
get: () => {
return documentStore.mainDocument && documentStore.mainDocument.length > 0 && documentStore.mainDocument.some((doc: Document) => doc.selected) && !allSelected.value
},
set: (value: boolean) => {}
})
const allSelected = computed({
get: () => {
return documentStore.mainDocument && documentStore.mainDocument.length > 0 && documentStore.mainDocument.every((doc: Document) => doc.selected)
},
set: (value: boolean) => {
if (documentStore.mainDocument) {
documentStore.mainDocument.forEach((doc: Document) => doc.selected = value)
}
}
})
</script> </script>
<style> <style>
.column-date, .column-size { table {
width: 100%;
table-layout: fixed;
}
table input[type=checkbox] {
width: 1em;
height: 1em;
}
table .modified { width: 10em; }
table .size { width: 6em; }
table th, table td {
padding: .5em;
font-weight: normal;
text-align: left;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
thead tr {
border: 1px solid #ddd;
background: #ddd;
}
tbody tr {
background: #444;
color: #ddd;
}
tbody tr:hover {
background: #00f8;
}
.right {
text-align: right; text-align: right;
} }
.selection {
width: 2em;
}
.sortcolumn:hover {
cursor: pointer;
}
.sortcolumn:hover::after {
color: #f80;
}
.sortcolumn {
padding-right: 1.7em;
}
.sortcolumn::after {
content: "▸";
color: #888;
margin: 0 1em 0 .5em;
position: absolute;
transition: transform 0.5s ease;
}
.sortactive::after {
transform: rotate(90deg);
color: #000;
}
main { main {
padding: 5px; padding: 5px;
height: 100%; height: 100%;
@ -190,6 +166,9 @@
.carousel-container{ .carousel-container{
height: inherit; height: inherit;
} }
.name a {
text-decoration: none;
}
.file .name::before { .file .name::before {
content: '📄 '; content: '📄 ';
font-size: 1.5em; font-size: 1.5em;

View File

@ -5,8 +5,9 @@ import Client from '@/repositories/Client'
type BaseDocument = { type BaseDocument = {
name: string; name: string
key?: number | string; key?: number | string
selected?: boolean
}; };
export type FolderDocument = BaseDocument & { export type FolderDocument = BaseDocument & {

View File

@ -5,8 +5,8 @@
<link rel="icon" href="/favicon.ico"> <link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Vite Vasanko</title> <title>Vite Vasanko</title>
<script type="module" crossorigin src="/assets/index-1ae30b84.js"></script> <script type="module" crossorigin src="/assets/index-06f39339.js"></script>
<link rel="stylesheet" href="/assets/index-f91a1fb3.css"> <link rel="stylesheet" href="/assets/index-38d160e9.css">
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>