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:
Leo Vasanko
2023-11-03 16:15:37 +00:00
parent 6cba674b30
commit f52d58d645
75 changed files with 554 additions and 226 deletions

View File

@@ -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>

View 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>

View 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>

View File

@@ -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);

View File

@@ -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>