Big changes...
- Added Droppy SVG icons - Implemented Droppy-style Breadcrumb component - Implemented a Dialog component - Attempted transition effects on file explorer (not yet functional) - Changed FileExplorer to take list of documents and current path via props. - Various other cleanup etc.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { RouterLink } from 'vue-router'
|
||||
import Breadcrumb from '@/components/Breadcrumb.vue'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
@@ -17,21 +18,52 @@ function generateUrl(pathIndex: number) {
|
||||
<nav>
|
||||
<!--
|
||||
|
||||
<a-breadcrumb>
|
||||
<a-breadcrumb-item>
|
||||
<RouterLink to="/">
|
||||
🏠
|
||||
</RouterLink>
|
||||
</a-breadcrumb-item>
|
||||
<div class="view-nav">
|
||||
<div class="view-nav-left">
|
||||
<button class="af button tip tip-se" aria-label="Upload files from disk">{{{svg "add-file"}}}</button>
|
||||
<button class="ad button tip tip-s" aria-label="Upload folder from disk">{{{svg "add-folder"}}}</button>
|
||||
<button class="cd button tip tip-s" aria-label="Create folder">{{{svg "create-folder"}}}</button>
|
||||
</div>
|
||||
<div class="view-nav-right">
|
||||
<div class="search toggled-off tip tip-s" aria-label="Search">
|
||||
{{{svg "find"}}}
|
||||
<input class="search-input" type="search" placeholder="Term" />
|
||||
</div>
|
||||
<button class="reload button tip tip-s" aria-label="Reload View">{{{svg "reload"}}}</button>
|
||||
<button class="newview button tip tip-s" aria-label="Create new View">{{{svg "window"}}}</button>
|
||||
<button class="prefs button tip tip-s" aria-label="Preferences">{{{svg "cog"}}}</button>
|
||||
<button class="about button tip tip-s" aria-label="About">{{{svg "info"}}}</button>
|
||||
<button class="logout button tip tip-sw" aria-label="Sign out">{{{svg "signout"}}}</button>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="path"></ul>
|
||||
<div class="content-container">
|
||||
<div class="content"></div>
|
||||
</div>
|
||||
|
||||
<a-breadcrumb-item v-for="(location, index) in path" :key="index">
|
||||
<RouterLink :to ="generateUrl(index)">
|
||||
<span :class="(index === path.length - 1) && 'last' ">{{ decodeURIComponent(location) }}</span>
|
||||
</RouterLink>
|
||||
</a-breadcrumb-item>
|
||||
</a-breadcrumb>
|
||||
<div class="dropzone">
|
||||
<svg></svg>
|
||||
</div>
|
||||
<div class="info-box">
|
||||
<div class="icon">{{{svg "link"}}}</div>
|
||||
<span></span>
|
||||
<div class="link-out mousetrap"></div>
|
||||
<div class="link-options">
|
||||
<div class="copy-link tip tip-nw" aria-label="Copy to clipboard">
|
||||
{{{svg "copy"}}}<div>Copy</div>
|
||||
</div>
|
||||
<div class="dl-link checked tip tip-sw" aria-label="Trigger a download when opened">
|
||||
{{{svg "check"}}}<div>Is DL</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="paste-button">
|
||||
{{{svg "paste"}}}
|
||||
<span>Paste here</span>
|
||||
{{{svg "triangle"}}}
|
||||
</div>
|
||||
-->
|
||||
|
||||
<Breadcrumb :path="props.path"/>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
|
||||
76
cista-front/src/components/Breadcrumb.vue
Normal file
76
cista-front/src/components/Breadcrumb.vue
Normal file
@@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<div class="breadcrumb">
|
||||
<a href="#/"><component :is="home"/></a>
|
||||
<template v-for="(location, index) in props.path">
|
||||
<a :href="`/#/${props.path.slice(0, index + 1).join('/')}/`">{{ decodeURIComponent(location) }}</a>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import home from '@/assets/svg/home.svg'
|
||||
import { withDefaults, defineProps } from 'vue'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
path: Array<string>
|
||||
}>(),
|
||||
{},
|
||||
)
|
||||
</script>
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--breadcrumb-background-odd: #2d2d2d;
|
||||
--breadcrumb-background-even: #404040;
|
||||
--breadcrumb-color: #ddd;
|
||||
--breadcrumb-hover-color: #fff;
|
||||
--breadcrumb-hover-background-odd: #25a;
|
||||
--breadcrumb-hover-background-even: #812;
|
||||
--breadcrumb-transtime: 0.3s;
|
||||
}
|
||||
.breadcrumb {
|
||||
display: flex;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0 1em 0 0;
|
||||
}
|
||||
.breadcrumb > a {
|
||||
margin: 0 -0.7rem 0 -.7rem;
|
||||
padding: 0;
|
||||
max-width: 8em;
|
||||
font-size: 1.3em;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
height: 1em;
|
||||
color: var(--breadcrumb-color);
|
||||
padding: .3em 1.5em;
|
||||
clip-path: polygon(0 0, 1em 50%, 0 100%, 100% 100%, 100% 0, 0 0);
|
||||
transition: all var(--breadcrumb-transtime);
|
||||
}
|
||||
.breadcrumb a:first-child {
|
||||
margin-left: 0;
|
||||
padding-left: 0;
|
||||
clip-path: none;
|
||||
}
|
||||
.breadcrumb a:last-child {
|
||||
max-width: none;
|
||||
clip-path: polygon(0 0, calc(100% - 1em) 0, 100% 50%, calc(100% - 1em) 100%, 0 100%, 1em 50%, 0 0);
|
||||
}
|
||||
.breadcrumb a:only-child {
|
||||
clip-path: polygon(0 0, calc(100% - 1em) 0, 100% 50%, calc(100% - 1em) 100%, 0 100%, 0 0);
|
||||
}
|
||||
.breadcrumb svg {
|
||||
/* FIXME: Custom positioning to align it well; needs proper solution */
|
||||
transform: translate(.3rem, -.3rem) scale(80%);
|
||||
fill: var(--breadcrumb-color);
|
||||
transition: fill var(--breadcrumb-transtime);
|
||||
}
|
||||
.breadcrumb a:nth-child(odd) { background: var(--breadcrumb-background-odd); }
|
||||
.breadcrumb a:nth-child(even) { background: var(--breadcrumb-background-even) }
|
||||
.breadcrumb a:nth-child(odd):hover { background: var(--breadcrumb-hover-background-odd); }
|
||||
.breadcrumb a:nth-child(even):hover { background: var(--breadcrumb-hover-background-even); }
|
||||
.breadcrumb a:hover { color: var(--breadcrumb-hover-color); }
|
||||
.breadcrumb a:hover svg { fill: var(--breadcrumb-hover-color); }
|
||||
</style>
|
||||
69
cista-front/src/components/Dialog.vue
Normal file
69
cista-front/src/components/Dialog.vue
Normal file
@@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<dialog ref="dialog">
|
||||
<h1 v-if="title">{{ title }}</h1>
|
||||
<div>
|
||||
<slot>Dialog with no content</slot>
|
||||
</div>
|
||||
<button onclick="dialog.close()">OK</button>
|
||||
</dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
|
||||
const dialog = ref<HTMLDialogElement | null>(null)
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
title: string
|
||||
}>(),
|
||||
{
|
||||
title: '',
|
||||
},
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
dialog.value!.showModal()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* Style for the background */
|
||||
body:has(dialog[open])::before {
|
||||
content: '';
|
||||
display: block;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #0008;
|
||||
backdrop-filter: blur(.2em);
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
/* Hide the dialog by default */
|
||||
dialog[open] {
|
||||
display: block;
|
||||
border: none;
|
||||
border-radius: .5rem;
|
||||
box-shadow: .2rem .2rem 1rem #000;
|
||||
padding: 1rem;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1001;
|
||||
}
|
||||
|
||||
dialog[open] > h1 {
|
||||
background: #00f;
|
||||
color: #fff;
|
||||
font-size: 1rem;
|
||||
margin: -1rem -1rem 0 -1rem;
|
||||
padding: .5rem 1rem .5rem 1rem;
|
||||
}
|
||||
|
||||
dialog[open] > div {
|
||||
padding: 1em 0;
|
||||
}
|
||||
</style>
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<main>
|
||||
<table v-if="documentStore.mainDocument.length">
|
||||
<table v-if="props.documents.length">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="selection"><input type="checkbox" v-model="allSelected" :indeterminate="selectionIndeterminate"></th>
|
||||
@@ -10,7 +10,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="doc of sorted(documentStore.mainDocument as FolderDocument[])" :key="doc.key" :class="doc.type === 'folder' ? 'folder' : 'file'">
|
||||
<tr v-for="doc of sorted(props.documents as FolderDocument[])" :key="doc.key" :class="doc.type === 'folder' ? 'folder' : 'file'">
|
||||
<td class="selection">
|
||||
<input type="checkbox" :checked="doc.key in documentStore.selected" @change="documentStore.selected.add(doc.key)">
|
||||
</td>
|
||||
@@ -26,21 +26,30 @@
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div v-else>
|
||||
<p>No files</p>
|
||||
</div>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { useDocumentStore } from '@/stores/documents'
|
||||
import Router from '@/router/index';
|
||||
import type { Document, FolderDocument } from '@/repositories/Document';
|
||||
import FileRenameInput from './FileRenameInput.vue'
|
||||
import createWebSocket from '@/repositories/WS';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
path: string,
|
||||
documents: Document[],
|
||||
}>(),
|
||||
{},
|
||||
)
|
||||
|
||||
const documentStore = useDocumentStore()
|
||||
const linkBasePath = computed(()=>{
|
||||
const path = Router.currentRoute.value.path
|
||||
const path = props.path
|
||||
return path === '/' ? '' : path
|
||||
})
|
||||
const filesBasePath = computed(() => `/files${linkBasePath.value}`)
|
||||
@@ -88,16 +97,16 @@ const sorted = (documents: FolderDocument[]) => {
|
||||
}
|
||||
const selectionIndeterminate = computed({
|
||||
get: () => {
|
||||
return documentStore.mainDocument && documentStore.mainDocument.length > 0 && documentStore.mainDocument.some((doc: Document) => doc.key in documentStore.selected) && !allSelected.value
|
||||
return props.documents.length > 0 && props.documents.some((doc: Document) => doc.key in documentStore.selected) && !allSelected.value
|
||||
},
|
||||
set: (value: boolean) => {}
|
||||
})
|
||||
const allSelected = computed({
|
||||
get: () => {
|
||||
return documentStore.mainDocument && documentStore.mainDocument.length > 0 && documentStore.mainDocument.every((doc: Document) => doc.key in documentStore.selected)
|
||||
return props.documents.length > 0 && props.documents.every((doc: Document) => doc.key in documentStore.selected)
|
||||
},
|
||||
set: (value: boolean) => {
|
||||
for (const doc of documentStore.mainDocument) {
|
||||
for (const doc of props.documents) {
|
||||
if (value) {
|
||||
documentStore.selected.add(doc.key)
|
||||
} else {
|
||||
@@ -147,10 +156,7 @@ table th, table td {
|
||||
thead tr {
|
||||
border: 1px solid #ddd;
|
||||
background: #ddd;
|
||||
}
|
||||
tbody tr {
|
||||
background: #444;
|
||||
color: #ddd;
|
||||
color: #000;
|
||||
}
|
||||
tbody tr:hover {
|
||||
background: #00f8;
|
||||
@@ -175,7 +181,7 @@ tbody tr:hover {
|
||||
color: #888;
|
||||
margin: 0 1em 0 .5em;
|
||||
position: absolute;
|
||||
transition: transform 0.2s linear;
|
||||
transition: all 0.2s linear;
|
||||
}
|
||||
.sortactive::after {
|
||||
transform: rotate(90deg);
|
||||
|
||||
@@ -2,13 +2,11 @@
|
||||
import { useDocumentStore } from '@/stores/documents'
|
||||
import LoginModal from '@/components/LoginModal.vue'
|
||||
import UploadButton from '@/components/UploadButton.vue'
|
||||
//import { InfoCircleOutlined, SettingOutlined, PlusSquareOutlined, SearchOutlined, DeleteOutlined, DownloadOutlined, FileAddFilled, LinkOutlined, FolderAddFilled, FileFilled, FolderFilled } from '@ant-design/icons-vue'
|
||||
import { h, ref } from 'vue';
|
||||
import { ref } from 'vue';
|
||||
|
||||
const documentStore = useDocumentStore()
|
||||
const searchQuery = ref<string>('')
|
||||
const showSearchInput = ref<boolean>(false)
|
||||
const isLoading = ref<boolean>(false)
|
||||
|
||||
const toggleSearchInput = () => {
|
||||
showSearchInput.value = !showSearchInput.value;
|
||||
@@ -60,7 +58,6 @@ function download(){
|
||||
<div class="actions-container">
|
||||
<div class="actions-list">
|
||||
<UploadButton />
|
||||
|
||||
<!--
|
||||
|
||||
<a-tooltip title="Upload folder from disk">
|
||||
@@ -148,4 +145,16 @@ function download(){
|
||||
.margin-input{
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.path {
|
||||
box-shadow: 0 0 0.5em rgba(0,0,0,.15);
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
flex: 0 0 1.5rem;
|
||||
order: 1;
|
||||
font-size: .9rem;
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user