Frontend created and rewritten a few times, with some backend fixes #1

Merged
leo merged 110 commits from plaintable into main 2023-11-08 20:38:40 +00:00
4 changed files with 112 additions and 134 deletions
Showing only changes of commit 68a701538b - Show all commits

View File

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

View File

@ -6,72 +6,26 @@
<FileCarousel></FileCarousel>
</div>
<a-table
v-else-if="!documentStore.loading && documentStore.mainDocument"
:pagination=false
:row-selection="{ selectedRowKeys: state.selectedRowKeys, onChange: onSelectChange }"
:columns="columns"
:data-source="documentStore.mainDocument"
>
<template #headerCell="{column}"></template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'name'">
<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 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 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>
<table v-else-if="!documentStore.loading && documentStore.mainDocument">
<thead>
<tr>
<th class="selection"><input type="checkbox" v-model="allSelected" :indeterminate="selectionIndeterminate"></th>
<th class="sortcolumn" :class="{sortactive: sort === 'name'}" @click="toggleSort('name')">Name</th>
<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>
</tr>
</thead>
<tbody>
<tr v-for="doc of sorted(documentStore.mainDocument as FolderDocument[])" :key="doc.key" :class="doc.type === 'folder' ? 'folder' : 'file'">
<td class="selection"><input type="checkbox" v-model="doc.selected"></td>
<td class="name">
<a :href="url_for(doc)">{{doc.name}}</a>
</td>
<td class="right">{{doc.modified}}</td>
<td class="right">{{doc.sizedisp}}</td>
</tr>
</tbody>
</table>
</main>
</template>
@ -79,10 +33,7 @@
<script setup lang="ts">
import { ref, h, computed, reactive, watchEffect } from 'vue'
import type { UnwrapRef } from 'vue'
import { cloneDeep } from 'lodash';
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 { message } from 'ant-design-vue';
import type { Document, FolderDocument } from '@/repositories/Document';
@ -104,70 +55,95 @@
return path === '/' ? '' : path
})
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>([
{
title: 'Name',
dataIndex: 'name',
width: '70%',
key: 'name',
sortDirections: ['ascend', 'descend'],
sorter: (a: Document, b: Document) => a.name.localeCompare(b.name),
const toggleSort = (name: string) => { sort.value = sort.value === name ? "" : name }
const sort = ref<string>("")
const sortCompare = {
"name": (a: Document, b: Document) => a.name.localeCompare(b.name),
"modified": (a: FolderDocument, b: FolderDocument) => b.mtime - a.mtime,
"size": (a: FolderDocument, b: FolderDocument) => b.size - a.size
}
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
},
{
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',
set: (value: boolean) => {}
})
const allSelected = computed({
get: () => {
return documentStore.mainDocument && documentStore.mainDocument.length > 0 && documentStore.mainDocument.every((doc: Document) => doc.selected)
},
{
// 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)
set: (value: boolean) => {
if (documentStore.mainDocument) {
documentStore.mainDocument.forEach((doc: Document) => doc.selected = value)
}
}
})
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");
}
</script>
<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;
}
.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 {
padding: 5px;
height: 100%;
@ -190,6 +166,9 @@
.carousel-container{
height: inherit;
}
.name a {
text-decoration: none;
}
.file .name::before {
content: '📄 ';
font-size: 1.5em;

View File

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

View File

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