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
16 changed files with 303 additions and 178 deletions
Showing only changes of commit b3fd9637eb - Show all commits

View File

@ -16,6 +16,7 @@ declare module 'vue' {
AImage: typeof import('ant-design-vue/es')['Image']
AInput: typeof import('ant-design-vue/es')['Input']
AInputSearch: typeof import('ant-design-vue/es')['InputSearch']
AModal: typeof import('ant-design-vue/es')['Modal']
APageHeader: typeof import('ant-design-vue/es')['PageHeader']
APopover: typeof import('ant-design-vue/es')['Popover']
AppNavigation: typeof import('./src/components/AppNavigation.vue')['default']
@ -27,6 +28,7 @@ declare module 'vue' {
FileExplorer: typeof import('./src/components/FileExplorer.vue')['default']
FileViewer: typeof import('./src/components/FileViewer.vue')['default']
HeaderMain: typeof import('./src/components/HeaderMain.vue')['default']
LoginModal: typeof import('./src/components/LoginModal.vue')['default']
NotificationLoading: typeof import('./src/components/NotificationLoading.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']

View File

@ -5,7 +5,8 @@
--table-background: #535353;
--primary-color: #ffffff;
--secondary-color: #ccc;
--blue-color: #66ffeb
--blue-color: #66ffeb;
--red-color: #ff4d4f;
}
@media (prefers-color-scheme: dark) {
:root {
@ -15,7 +16,8 @@
--table-background: #535353;
--primary-color: #ffffff;
--secondary-color: #ccc;
--blue-color: #66ffeb
--blue-color: #66ffeb;
--red-color: #ff4d4f;
}
}
body {
@ -93,5 +95,11 @@ body {
.ant-empty-description{
color: var(--primary-color);
}
.ant-modal .ant-modal-content{
background-color: var(--secondary-background);
}
.ant-modal .ant-modal-close-x{
color: var(--font-color);
}
}

View File

@ -1,5 +1,6 @@
<template>
<main>
<context-holder />
<!-- <h2 v-if="!documentStore.loading && documentStore.error"> {{ documentStore.error }} </h2> -->
<div class="carousel-container" v-if="!documentStore.loading && documentStore.mainDocument[0] && documentStore.mainDocument[0].type === 'file'">
<FileCarousel></FileCarousel>
@ -32,13 +33,27 @@
<template #content>
<div class="more-action">
<div class="action-container">
<a-button type="text" class="action-button" :icon="h(ImportOutlined)"/>Open
<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" :icon="h(EditOutlined)"/> Rename
<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" :icon="h(LinkOutlined)"/> Share
<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
@ -69,14 +84,14 @@
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 { message } from 'ant-design-vue';
import type { Document, FolderDocument } from '@/repositories/Document';
import FileCarousel from './FileCarousel.vue';
const [messageApi, contextHolder] = message.useMessage();
type Key = string | number;
const documentStore = useDocumentStore()
watchEffect(()=>{
console.log(documentStore.mainDocument)
})
const editableData: UnwrapRef<Record<string, Document>> = reactive({});
const state = reactive<{
selectedRowKeys: Key[];
@ -143,7 +158,10 @@
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");
}
</script>
<style>

View File

@ -30,11 +30,6 @@ import { url_document_get } from '@/repositories/Document';
const dataURL = ref('')
watchEffect(()=>{
console.log('😎😎😎😎')
console.log(url_document_get)
console.log(Router.currentRoute)
console.log(Router.currentRoute.value.path)
console.log('----------')
dataURL.value = new URL(
url_document_get + Router.currentRoute.value.path,
location.origin

View File

@ -1,8 +1,9 @@
<script setup lang="ts">
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, defineProps } from 'vue';
import { h, ref } from 'vue';
const documentStore = useDocumentStore()
const searchQuery = ref<string>('')
@ -94,8 +95,8 @@ function download(){
</a-tooltip>
</template>
</div>
<div class="actions-list">
<LoginModal></LoginModal>
<template v-if="showSearchInput">
<a-input-search
v-model="searchQuery"

View File

@ -0,0 +1,90 @@
<template>
<a-tooltip title="Login">
<template v-if="DocumentStore.isUserLogged">
<a-button @click="logout" type="text" class="action-button" :icon="h(UserDeleteOutlined)" />
</template>
<template v-else>
<a-button @click="showModal" type="text" class="action-button" :icon="h(UserOutlined)" />
</template>
</a-tooltip>
<a-modal v-model:open="DocumentStore.user.isOpenLoginModal" :confirm-loading="confirmLoading" okText="Login" @ok="login">
<div class="login-container">
<a-form :model="loginForm">
<a-form-item label="Username" prop="username" :rules="[{ required: true, message: 'Please input your username!' }]">
<a-input v-model:value="loginForm.username" />
</a-form-item>
<a-form-item label="Password" prop="password" :rules="[{ required: true, message: 'Please input your password!' }]">
<a-input type="password" v-model:value="loginForm.password" />
</a-form-item>
<h3 v-if="loginForm.error.length > 0" class="error-text">{{loginForm.error}}</h3>
</a-form>
</div>
</a-modal>
</template>
<script lang="ts" setup>
import { ref, h } from 'vue';
import { UserOutlined, UserDeleteOutlined } from '@ant-design/icons-vue';
import { useDocumentStore } from '@/stores/documents';
import { loginUser, logoutUser } from '@/repositories/User';
import type { ISimpleError } from '@/repositories/Client';
const DocumentStore = useDocumentStore();
const confirmLoading = ref<boolean>(false);
const showModal = () => {
DocumentStore.user.isOpenLoginModal = true;
};
const logout = async () => {
try {
await logoutUser();
} catch (error) {} finally {
location.reload();
}
}
const loginForm = ref({
username: '',
password: '',
error: '',
});
const login = async () => {
try {
loginForm.value.error = '';
confirmLoading.value = true;
const user = await loginUser(loginForm.value.username, loginForm.value.password);
if(user){
location.reload();
}
} catch (error) {
const httpError = error as ISimpleError
if(httpError.name){
loginForm.value.error = httpError.message
}
}finally{
confirmLoading.value = false;
}
};
</script>
<style scoped>
.login-container {
display: flex;
justify-content: center;
align-items: center;
min-height: 30vh;
}
.button-login {
background-color: var(--secondary-color);
color: var(--secondary-background);
}
.ant-btn-primary:not(:disabled):hover{
background-color: var(--blue-color);
}
.error-text {
color :var(--red-color)
}
</style>

View File

@ -1,4 +1,4 @@
import axios from 'axios'
import axios, {AxiosError} from 'axios'
/* Base domain for all request */
export const baseURL = import.meta.env.VITE_URL_DOCUMENT
@ -17,9 +17,10 @@ Client.interceptors.response.use(
// Do something with response data
return response
},
(error) => {
const msg = error.response?.data.message ?? 'Unexpected error'
const code = error.code ? Number(error.code) : 500
(error: AxiosError<any>) => {
const msg = error.response && error.response.data && error.response.data.error ?
error.response.data.error.message : 'Unexpected error'
const code = error.code ? Number(error.response?.status) : 500
const standardizedError = new SimpleError(code, msg)
return Promise.reject(standardizedError)

View File

@ -23,6 +23,14 @@ export type FileDocument = BaseDocument & {
data: string;
};
export type errorEvent = {
error: {
code : number;
message: string;
redirect: string;
}
};
export type Document = FolderDocument | FileDocument;
@ -44,19 +52,36 @@ export class DocumentHandler {
case !!msg.update:
this.handleUpdateMessage(msg);
break;
case !!msg.error:
this.handleError(msg);
break;
default:
}
}
private handleRootMessage({ root }: { root: FileStructure }) {
if (this.store && this.store.root) this.store.root = root;
if (this.store && this.store.root) {
this.store.user.isLoggedIn = true;
this.store.root = root;
}
}
private handleUpdateMessage(updateData: { update: FileStructure[] }) {
const root = updateData.update[0]
if(root) this.store.root = root
if(root) {
this.store.user.isLoggedIn = true;
this.store.root = root
}
}
private handleError(msg: errorEvent){
if(msg.error.code === 401){
this.store.user.isOpenLoginModal = true;
this.store.user.isLoggedIn = false;
return
}
}
}
export class DocumentUploadHandler {
constructor( private store: DocumentStore = useDocumentStore() ) {
this.handleWebSocketMessage = this.handleWebSocketMessage.bind(this);

View File

@ -0,0 +1,23 @@
import Client from '@/repositories/Client'
export const url_login = '/login'
export const url_logout = '/logout '
export async function loginUser(username : string, password: string){
try {
const user = await Client.post(url_login, {
username, password
})
return user;
} catch (error) {
throw error
}
}
export async function logoutUser(){
try {
const data = await Client.post(url_logout)
return data;
} catch (error) {
throw error
}
}

View File

@ -1,6 +1,5 @@
import { createRouter, createWebHashHistory } from 'vue-router'
import ExplorerView from '../views/ExplorerView.vue'
import LoginView from '../views/LoginView.vue'
const router = createRouter({
history: createWebHashHistory(import.meta.env.BASE_URL),

View File

@ -10,6 +10,10 @@ type DirectoryData = {
[filename: string]: FileData;
};
export type FileStructure = {id: string, mtime: number, size: number, dir: DirectoryData};
type User = {
isOpenLoginModal: boolean,
isLoggedIn : boolean,
}
export type DocumentStore = {
root: FileStructure,
@ -20,6 +24,7 @@ export type DocumentStore = {
wsWatch: WebSocket | undefined,
wsUpload: WebSocket | undefined,
selectedDocuments: Document[],
user: User,
error: string,
}
@ -35,6 +40,7 @@ export const useDocumentStore = defineStore({
wsUpload: undefined,
selectedDocuments: [] as Document[],
error: '' as string,
user: { isLoggedIn: false, isOpenLoginModal: false } as User
}),
actions: {
@ -140,7 +146,7 @@ export const useDocumentStore = defineStore({
for (const d of this.document) {
if ("mtime" in d) d.modified = formatUnixDate(d.mtime)
}
}
},
},
getters: {
mainDocument(): Document[] {
@ -151,6 +157,9 @@ export const useDocumentStore = defineStore({
},
rootMain(): DirectoryData | undefined {
if(this.root) return this.root.dir
},
isUserLogged(): boolean{
return this.user.isLoggedIn
}
},
});

View File

@ -1,46 +0,0 @@
<template>
<div class="login-container">
<a-form :model="loginForm">
<a-form-item label="Username" prop="username" :rules="[{ required: true, message: 'Please input your username!' }]">
<a-input v-model:value="loginForm.username" />
</a-form-item>
<a-form-item label="Password" prop="password" :rules="[{ required: true, message: 'Please input your password!' }]">
<a-input type="password" v-model:value="loginForm.password" />
</a-form-item>
<a-form-item>
<a-button type="primary" @click="login" class="button-login">Login</a-button>
</a-form-item>
</a-form>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
const loginForm = ref({
username: '',
password: '',
});
const login = () => {
};
</script>
<style scoped>
.login-container {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
.button-login {
background-color: var(--secondary-color);
color: var(--secondary-background);
}
.ant-btn-primary:not(:disabled):hover{
background-color: var(--blue-color);
}
</style>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -5,8 +5,8 @@
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Vite Vasanko</title>
<script type="module" crossorigin src="/assets/index-10851222.js"></script>
<link rel="stylesheet" href="/assets/index-ee545ab1.css">
<script type="module" crossorigin src="/assets/index-25722397.js"></script>
<link rel="stylesheet" href="/assets/index-213aec14.css">
</head>
<body>
<div id="app"></div>