Frontend created and rewritten a few times, with some backend fixes #1
2
cista-front/components.d.ts
vendored
2
cista-front/components.d.ts
vendored
|
@ -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']
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
90
cista-front/src/components/LoginModal.vue
Normal file
90
cista-front/src/components/LoginModal.vue
Normal 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>
|
||||
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
23
cista-front/src/repositories/User.ts
Normal file
23
cista-front/src/repositories/User.ts
Normal 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
|
||||
}
|
||||
}
|
|
@ -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),
|
||||
|
|
|
@ -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
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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>
|
||||
|
1
cista/wwwroot/assets/index-213aec14.css
Normal file
1
cista/wwwroot/assets/index-213aec14.css
Normal file
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
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue
Block a user