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
8 changed files with 67 additions and 19 deletions
Showing only changes of commit 54d6ea6332 - Show all commits

View File

@ -13,6 +13,7 @@
"format": "prettier --write src/" "format": "prettier --write src/"
}, },
"dependencies": { "dependencies": {
"@imengyu/vue3-context-menu": "^1.3.3",
"@vueuse/core": "^10.4.1", "@vueuse/core": "^10.4.1",
"esbuild": "^0.19.5", "esbuild": "^0.19.5",
"lodash": "^4.17.21", "lodash": "^4.17.21",

View File

@ -57,9 +57,9 @@
<td class="menu"></td> <td class="menu"></td>
</tr> </tr>
<template <template
v-for="doc of sorted(props.documents as Document[])" v-for="(doc, index) in sortedDocuments"
:key="doc.key"> :key="doc.key">
<tr class="folder-change" v-if="doc.loc !== prevloc && ((prevloc = doc.loc) || true)"> <tr class="folder-change" v-if="showFolderBreadcrumb(index)">
<th colspan="5"><BreadCrumb :path="doc.loc ? doc.loc.split('/') : []" /></th> <th colspan="5"><BreadCrumb :path="doc.loc ? doc.loc.split('/') : []" /></th>
</tr> </tr>
@ -188,6 +188,12 @@ const rename = (doc: Document, newName: string) => {
} }
doc.name = newName // We should get an update from watch but this is quicker doc.name = newName // We should get an update from watch but this is quicker
} }
const sortedDocuments = computed(() => sorted(props.documents as Document[]))
const showFolderBreadcrumb = (i: number) => {
const docs = sortedDocuments.value
const docloc = docs[i].loc
return i === 0 ? docloc !== loc.value : docloc !== docs[i - 1].loc
}
defineExpose({ defineExpose({
newFolder() { newFolder() {
const now = Date.now() / 1000 const now = Date.now() / 1000
@ -230,7 +236,7 @@ defineExpose({
}, },
cursorMove(d: number, select = false) { cursorMove(d: number, select = false) {
// Move cursor up or down (keyboard navigation) // Move cursor up or down (keyboard navigation)
const documents = sorted(props.documents as Document[]) const documents = sortedDocuments.value
if (documents.length === 0) { if (documents.length === 0) {
cursor.value = null cursor.value = null
return return
@ -358,8 +364,6 @@ const allSelected = computed({
}) })
const loc = computed(() => props.path.join('/')) const loc = computed(() => props.path.join('/'))
let prevloc = ''
onBeforeUpdate(() => { prevloc = loc.value })
const contextMenu = (ev: Event, doc: Document) => { const contextMenu = (ev: Event, doc: Document) => {
cursor.value = doc cursor.value = doc

View File

@ -20,14 +20,26 @@
/> />
</template> </template>
<SvgButton ref="searchButton" name="find" @click.prevent="toggleSearchInput" /> <SvgButton ref="searchButton" name="find" @click.prevent="toggleSearchInput" />
<SvgButton name="cog" @click="console.log('settings menu')" /> <SvgButton name="cog" @click="settingsMenu" />
</div> </div>
</nav> </nav>
<context-menu v-model:show="showMenu">
<context-menu-item label="Simple item" @click="onMenuClick(1)" />
<context-menu-sperator /><!--use this to add sperator-->
<context-menu-group label="Menu with child">
<context-menu-item label="Item1" @click="onMenuClick(2)" />
<context-menu-item label="Item2" @click="onMenuClick(3)" />
<context-menu-group label="Child with v-for 50">
<context-menu-item v-for="index of 50" :key="index" :label="'Item3-'+index" @click="onLoopMenuClick(index)" />
</context-menu-group>
</context-menu-group>
</context-menu>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useDocumentStore } from '@/stores/documents' import { useDocumentStore } from '@/stores/documents'
import { ref, nextTick } from 'vue' import { ref, nextTick } from 'vue'
import ContextMenu from '@imengyu/vue3-context-menu'
const documentStore = useDocumentStore() const documentStore = useDocumentStore()
const showSearchInput = ref<boolean>(false) const showSearchInput = ref<boolean>(false)
@ -50,6 +62,17 @@ const toggleSearchInput = () => {
}) })
} }
const settingsMenu = (e: Event) => {
// show the context menu
ContextMenu.showContextMenu({
// @ts-ignore
x: e.target.getBoundingClientRect().right, y: e.target.getBoundingClientRect().bottom,
items: [
{ label: "Logout", onClick: () => { documentStore.logout() } },
]
})
}
defineExpose({ defineExpose({
toggleSearchInput, toggleSearchInput,
closeSearch, closeSearch,

View File

@ -1,8 +1,5 @@
<template> <template>
<button v-if="store.isUserLogged" @click="logout" class="action-button"> <ModalDialog v-if="store.user.isOpenLoginModal" title="Authentication required">
Logout {{ store.user.username }}
</button>
<ModalDialog v-if="store.user.isOpenLoginModal" title="Login">
<form @submit.prevent="login"> <form @submit.prevent="login">
<div class="login-container"> <div class="login-container">
<label for="username">Username:</label> <label for="username">Username:</label>
@ -26,7 +23,10 @@
<h3 v-if="loginForm.error.length > 0" class="error-text"> <h3 v-if="loginForm.error.length > 0" class="error-text">
{{ loginForm.error }} {{ loginForm.error }}
</h3> </h3>
<input id="submit" type="submit" class="button-login" /> <div class="dialog-buttons">
<div class="spacer"></div>
<input id="submit" type="submit" value="Login" class="button-login" />
</div>
</form> </form>
</ModalDialog> </ModalDialog>
</template> </template>
@ -81,10 +81,19 @@ const login = async () => {
align-items: center; align-items: center;
margin: 1rem 0; margin: 1rem 0;
} }
.dialog-buttons {
display: flex;
justify-content: space-between;
align-items: center;
}
.button-login { .button-login {
cursor: pointer;
border: 0;
border-radius: .5rem;
padding: .5rem;
margin-left: auto; margin-left: auto;
background-color: var(--secondary-color); background-color: var(--accent-color);
color: var(--secondary-background); color: var(--primary-color);
} }
.ant-btn-primary:not(:disabled):hover { .ant-btn-primary:not(:disabled):hover {
background-color: var(--blue-color); background-color: var(--blue-color);

View File

@ -46,6 +46,8 @@ body:has(dialog[open])::before {
/* Hide the dialog by default */ /* Hide the dialog by default */
dialog[open] { dialog[open] {
background: #ddd;
color: black;
display: block; display: block;
border: none; border: none;
border-radius: 0.5rem; border-radius: 0.5rem;
@ -58,8 +60,8 @@ dialog[open] {
} }
dialog[open] > h1 { dialog[open] > h1 {
background: #00f; background: var(--accent-color);
color: #fff; color: black;
font-size: 1rem; font-size: 1rem;
margin: -1rem -1rem 0 -1rem; margin: -1rem -1rem 0 -1rem;
padding: 0.5rem 1rem 0.5rem 1rem; padding: 0.5rem 1rem 0.5rem 1rem;

View File

@ -8,6 +8,9 @@ import router from './router'
import piniaPluginPersistedState from 'pinia-plugin-persistedstate' import piniaPluginPersistedState from 'pinia-plugin-persistedstate'
import ContextMenu from '@imengyu/vue3-context-menu'
import '@imengyu/vue3-context-menu/lib/vue3-context-menu.css'
const app = createApp(App) const app = createApp(App)
app.config.errorHandler = err => { app.config.errorHandler = err => {
/* handle error */ /* handle error */
@ -17,6 +20,6 @@ app.config.errorHandler = err => {
const pinia = createPinia() const pinia = createPinia()
pinia.use(piniaPluginPersistedState) pinia.use(piniaPluginPersistedState)
app.use(pinia) app.use(pinia)
app.use(router) app.use(router)
app.use(ContextMenu)
app.mount('#app') app.mount('#app')

View File

@ -4,7 +4,6 @@ export type Document = {
loc: string loc: string
name: string name: string
key: FUID key: FUID
type: 'folder' | 'file'
size: number size: number
sizedisp: string sizedisp: string
mtime: number mtime: number

View File

@ -3,7 +3,6 @@ import type {
DirEntry, DirEntry,
FileEntry, FileEntry,
FUID, FUID,
DirList,
SelectedItems SelectedItems
} from '@/repositories/Document' } from '@/repositories/Document'
import { formatSize, formatUnixDate, haystackFormat } from '@/utils' import { formatSize, formatUnixDate, haystackFormat } from '@/utils'
@ -67,8 +66,10 @@ export const useDocumentStore = defineStore({
// Recurse but replace recursive structure with boolean // Recurse but replace recursive structure with boolean
loc = doc.loc ? `${doc.loc}/${doc.name}` : doc.name loc = doc.loc ? `${doc.loc}/${doc.name}` : doc.name
queue.push(...Object.entries(doc.dir).map(mapper)) queue.push(...Object.entries(doc.dir).map(mapper))
// @ts-ignore
doc.dir = true doc.dir = true
} }
// @ts-ignore
else doc.dir = false else doc.dir = false
} }
// Pre sort directory entries folders first then files, names in natural ordering // Pre sort directory entries folders first then files, names in natural ordering
@ -77,7 +78,7 @@ export const useDocumentStore = defineStore({
b.dir - a.dir || b.dir - a.dir ||
collator.compare(a.name, b.name) collator.compare(a.name, b.name)
) )
this.document = docs this.document = docs as Document[]
}, },
updateUploadingDocuments(key: number, progress: number) { updateUploadingDocuments(key: number, progress: number) {
for (const d of this.uploadingDocuments) { for (const d of this.uploadingDocuments) {
@ -107,6 +108,12 @@ export const useDocumentStore = defineStore({
this.user.privileged = privileged this.user.privileged = privileged
this.user.isLoggedIn = true this.user.isLoggedIn = true
this.user.isOpenLoginModal = false this.user.isOpenLoginModal = false
},
async logout() {
const res = await fetch('/logout', { method: 'POST' })
if (!res.ok) throw Error(`Logout failed: ${res.statusText}`)
this.$reset()
history.go() // Reload page
} }
}, },
getters: { getters: {