Frontend created and rewritten a few times, with some backend fixes #1
|
@ -13,6 +13,7 @@
|
|||
"format": "prettier --write src/"
|
||||
},
|
||||
"dependencies": {
|
||||
"@imengyu/vue3-context-menu": "^1.3.3",
|
||||
"@vueuse/core": "^10.4.1",
|
||||
"esbuild": "^0.19.5",
|
||||
"lodash": "^4.17.21",
|
||||
|
|
|
@ -57,9 +57,9 @@
|
|||
<td class="menu"></td>
|
||||
</tr>
|
||||
<template
|
||||
v-for="doc of sorted(props.documents as Document[])"
|
||||
v-for="(doc, index) in sortedDocuments"
|
||||
: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>
|
||||
</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
|
||||
}
|
||||
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({
|
||||
newFolder() {
|
||||
const now = Date.now() / 1000
|
||||
|
@ -230,7 +236,7 @@ defineExpose({
|
|||
},
|
||||
cursorMove(d: number, select = false) {
|
||||
// Move cursor up or down (keyboard navigation)
|
||||
const documents = sorted(props.documents as Document[])
|
||||
const documents = sortedDocuments.value
|
||||
if (documents.length === 0) {
|
||||
cursor.value = null
|
||||
return
|
||||
|
@ -358,8 +364,6 @@ const allSelected = computed({
|
|||
})
|
||||
|
||||
const loc = computed(() => props.path.join('/'))
|
||||
let prevloc = ''
|
||||
onBeforeUpdate(() => { prevloc = loc.value })
|
||||
|
||||
const contextMenu = (ev: Event, doc: Document) => {
|
||||
cursor.value = doc
|
||||
|
|
|
@ -20,14 +20,26 @@
|
|||
/>
|
||||
</template>
|
||||
<SvgButton ref="searchButton" name="find" @click.prevent="toggleSearchInput" />
|
||||
<SvgButton name="cog" @click="console.log('settings menu')" />
|
||||
<SvgButton name="cog" @click="settingsMenu" />
|
||||
</div>
|
||||
</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>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useDocumentStore } from '@/stores/documents'
|
||||
import { ref, nextTick } from 'vue'
|
||||
import ContextMenu from '@imengyu/vue3-context-menu'
|
||||
|
||||
const documentStore = useDocumentStore()
|
||||
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({
|
||||
toggleSearchInput,
|
||||
closeSearch,
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
<template>
|
||||
<button v-if="store.isUserLogged" @click="logout" class="action-button">
|
||||
Logout {{ store.user.username }}
|
||||
</button>
|
||||
<ModalDialog v-if="store.user.isOpenLoginModal" title="Login">
|
||||
<ModalDialog v-if="store.user.isOpenLoginModal" title="Authentication required">
|
||||
<form @submit.prevent="login">
|
||||
<div class="login-container">
|
||||
<label for="username">Username:</label>
|
||||
|
@ -26,7 +23,10 @@
|
|||
<h3 v-if="loginForm.error.length > 0" class="error-text">
|
||||
{{ loginForm.error }}
|
||||
</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>
|
||||
</ModalDialog>
|
||||
</template>
|
||||
|
@ -81,10 +81,19 @@ const login = async () => {
|
|||
align-items: center;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
.dialog-buttons {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.button-login {
|
||||
cursor: pointer;
|
||||
border: 0;
|
||||
border-radius: .5rem;
|
||||
padding: .5rem;
|
||||
margin-left: auto;
|
||||
background-color: var(--secondary-color);
|
||||
color: var(--secondary-background);
|
||||
background-color: var(--accent-color);
|
||||
color: var(--primary-color);
|
||||
}
|
||||
.ant-btn-primary:not(:disabled):hover {
|
||||
background-color: var(--blue-color);
|
||||
|
|
|
@ -46,6 +46,8 @@ body:has(dialog[open])::before {
|
|||
|
||||
/* Hide the dialog by default */
|
||||
dialog[open] {
|
||||
background: #ddd;
|
||||
color: black;
|
||||
display: block;
|
||||
border: none;
|
||||
border-radius: 0.5rem;
|
||||
|
@ -58,8 +60,8 @@ dialog[open] {
|
|||
}
|
||||
|
||||
dialog[open] > h1 {
|
||||
background: #00f;
|
||||
color: #fff;
|
||||
background: var(--accent-color);
|
||||
color: black;
|
||||
font-size: 1rem;
|
||||
margin: -1rem -1rem 0 -1rem;
|
||||
padding: 0.5rem 1rem 0.5rem 1rem;
|
||||
|
|
|
@ -8,6 +8,9 @@ import router from './router'
|
|||
|
||||
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)
|
||||
app.config.errorHandler = err => {
|
||||
/* handle error */
|
||||
|
@ -17,6 +20,6 @@ app.config.errorHandler = err => {
|
|||
const pinia = createPinia()
|
||||
pinia.use(piniaPluginPersistedState)
|
||||
app.use(pinia)
|
||||
|
||||
app.use(router)
|
||||
app.use(ContextMenu)
|
||||
app.mount('#app')
|
||||
|
|
|
@ -4,7 +4,6 @@ export type Document = {
|
|||
loc: string
|
||||
name: string
|
||||
key: FUID
|
||||
type: 'folder' | 'file'
|
||||
size: number
|
||||
sizedisp: string
|
||||
mtime: number
|
||||
|
|
|
@ -3,7 +3,6 @@ import type {
|
|||
DirEntry,
|
||||
FileEntry,
|
||||
FUID,
|
||||
DirList,
|
||||
SelectedItems
|
||||
} from '@/repositories/Document'
|
||||
import { formatSize, formatUnixDate, haystackFormat } from '@/utils'
|
||||
|
@ -67,8 +66,10 @@ export const useDocumentStore = defineStore({
|
|||
// Recurse but replace recursive structure with boolean
|
||||
loc = doc.loc ? `${doc.loc}/${doc.name}` : doc.name
|
||||
queue.push(...Object.entries(doc.dir).map(mapper))
|
||||
// @ts-ignore
|
||||
doc.dir = true
|
||||
}
|
||||
// @ts-ignore
|
||||
else doc.dir = false
|
||||
}
|
||||
// Pre sort directory entries folders first then files, names in natural ordering
|
||||
|
@ -77,7 +78,7 @@ export const useDocumentStore = defineStore({
|
|||
b.dir - a.dir ||
|
||||
collator.compare(a.name, b.name)
|
||||
)
|
||||
this.document = docs
|
||||
this.document = docs as Document[]
|
||||
},
|
||||
updateUploadingDocuments(key: number, progress: number) {
|
||||
for (const d of this.uploadingDocuments) {
|
||||
|
@ -107,6 +108,12 @@ export const useDocumentStore = defineStore({
|
|||
this.user.privileged = privileged
|
||||
this.user.isLoggedIn = true
|
||||
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: {
|
||||
|
|
Loading…
Reference in New Issue
Block a user