Prototyping plain table for files list
This commit is contained in:
parent
05a16e3037
commit
68a701538b
2
cista-front/components.d.ts
vendored
2
cista-front/components.d.ts
vendored
|
@ -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']
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 & {
|
||||||
|
@ -60,7 +61,7 @@ export class DocumentHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleRootMessage({ root }: { root: FileStructure }) {
|
private handleRootMessage({ root }: { root: FileStructure }) {
|
||||||
if (this.store && this.store.root) {
|
if (this.store && this.store.root) {
|
||||||
this.store.user.isLoggedIn = true;
|
this.store.user.isLoggedIn = true;
|
||||||
this.store.root = root;
|
this.store.root = root;
|
||||||
}
|
}
|
||||||
|
@ -68,7 +69,7 @@ export class DocumentHandler {
|
||||||
|
|
||||||
private handleUpdateMessage(updateData: { update: FileStructure[] }) {
|
private handleUpdateMessage(updateData: { update: FileStructure[] }) {
|
||||||
const root = updateData.update[0]
|
const root = updateData.update[0]
|
||||||
if(root) {
|
if(root) {
|
||||||
this.store.user.isLoggedIn = true;
|
this.store.user.isLoggedIn = true;
|
||||||
this.store.root = root
|
this.store.root = root
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user