frontend first steps
This commit is contained in:
parent
65a67ae19b
commit
01a6481d62
1
.env.example
Normal file
1
.env.example
Normal file
@ -0,0 +1 @@
|
||||
VUE_APP_ROOT_WEBDAV="http://127.0.0.1:8080"
|
@ -9,11 +9,15 @@
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"bootstrap": "^5.2.0",
|
||||
"bootstrap-darkmode": "^5.0.1",
|
||||
"bootstrap-icons": "^1.9.1",
|
||||
"core-js": "^3.6.5",
|
||||
"pinia": "^2.0.13",
|
||||
"register-service-worker": "^1.7.1",
|
||||
"typescript-is": "^0.19.0",
|
||||
"vue": "^3.0.0",
|
||||
"vue-router": "^4.0.0-0",
|
||||
"pinia": "^2.0.13",
|
||||
"webdav": "^4.9.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
48
src/App.vue
48
src/App.vue
@ -1,30 +1,24 @@
|
||||
<template>
|
||||
<div id="nav">
|
||||
<router-link to="/">Home</router-link> |
|
||||
<router-link to="/about">About</router-link>
|
||||
</div>
|
||||
<router-view />
|
||||
<header
|
||||
class="d-flex justify-content-between p-3 bg-darkmode-dark bg-light shadow"
|
||||
>
|
||||
<div>
|
||||
<b class="mx-2">vuedav</b>
|
||||
<router-link class="mx-2" to="/files">Files</router-link>
|
||||
</div>
|
||||
<div class="d-flex">
|
||||
<DarkModeToggle class="mx-2" v-slot="{ state }">
|
||||
<i v-if="state" class="bi-moon"></i>
|
||||
<i v-else class="bi-sun"></i>
|
||||
</DarkModeToggle>
|
||||
</div>
|
||||
</header>
|
||||
<main class="container my-5">
|
||||
<router-view />
|
||||
</main>
|
||||
<footer></footer>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
#app {
|
||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-align: center;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
#nav {
|
||||
padding: 30px;
|
||||
|
||||
a {
|
||||
font-weight: bold;
|
||||
color: #2c3e50;
|
||||
|
||||
&.router-link-exact-active {
|
||||
color: #42b983;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script setup lang="ts">
|
||||
import DarkModeToggle from '@/components/DarkmodeToggle.vue';
|
||||
</script>
|
||||
|
23
src/components/DarkmodeToggle.vue
Normal file
23
src/components/DarkmodeToggle.vue
Normal file
@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<div class="form-check form-switch">
|
||||
<label class="custom-control-label" for="darkSwitch">
|
||||
<slot :state="sliderState" />
|
||||
</label>
|
||||
<input v-model="sliderState" type="checkbox" class="form-check-input" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue';
|
||||
import { themeConfig } from '@/main';
|
||||
|
||||
const sliderState = ref(themeConfig.getTheme() === 'dark');
|
||||
|
||||
watch(sliderState, (state) => {
|
||||
themeConfig.setTheme(state ? 'dark' : 'light');
|
||||
});
|
||||
|
||||
themeConfig.themeChangeHandlers.push((newTheme) => {
|
||||
sliderState.value = newTheme === 'dark';
|
||||
});
|
||||
</script>
|
19
src/components/SideMenu.vue
Normal file
19
src/components/SideMenu.vue
Normal file
@ -0,0 +1,19 @@
|
||||
<template>
|
||||
<div class="card sideMenu sticky-top">
|
||||
<div class="card-header">Menu</div>
|
||||
<div class="card-body">
|
||||
<router-link class="py-2" to="/files">
|
||||
<i class="bi-archive"></i> My Files
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.sideMenu {
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
</style>
|
100
src/components/files/FileList.vue
Normal file
100
src/components/files/FileList.vue
Normal file
@ -0,0 +1,100 @@
|
||||
<template>
|
||||
<div class="px-0">
|
||||
<div class="files card">
|
||||
<div class="card-header sticky-top bg-light bg-darkmode-dark">
|
||||
<div class="row">
|
||||
<i
|
||||
v-if="path && path !== '/'"
|
||||
@click="navigateUp"
|
||||
class="col-1 px-0 btn btn-default bi-arrow-90deg-left"
|
||||
></i>
|
||||
<PathSegments
|
||||
class="col my-2"
|
||||
:path="path"
|
||||
@newPath="(newPath) => emit('newPath', newPath)"
|
||||
/>
|
||||
<div class="col-auto px-0">
|
||||
<FileUpload :path="path" @finished="fetchLocation" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div v-if="loading">loading...</div>
|
||||
<div v-else>
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Name</th>
|
||||
<th class="d-none d-md-table-cell">Modified</th>
|
||||
<th class="d-none d-sm-table-cell">Size</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<component
|
||||
v-for="file in files"
|
||||
:key="file"
|
||||
:is="file.type === 'file' ? FileListElement : FolderListElement"
|
||||
:file="file"
|
||||
:client="client"
|
||||
/>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{{ error }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import FileListElement from './FileListElement.vue';
|
||||
import FolderListElement from './FolderListElement.vue';
|
||||
import FileUpload from '@/components/helpers/FileUpload.vue';
|
||||
import PathSegments from '@/components/files/PathSegments.vue';
|
||||
import { useWebdavStore } from '@/store/webdav';
|
||||
import { ref, defineProps, watch, defineEmits } from 'vue';
|
||||
import type { File } from '@/models';
|
||||
|
||||
const props = defineProps({
|
||||
path: String,
|
||||
});
|
||||
const emit = defineEmits(['newPath']);
|
||||
|
||||
const files = ref([] as Array<File>);
|
||||
const loading = ref(true as boolean);
|
||||
const error = ref('' as string);
|
||||
const store = useWebdavStore();
|
||||
|
||||
const client = store.currentSession?.client;
|
||||
|
||||
const fetchLocation = async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
files.value = (await client?.getDirectoryContents(
|
||||
props.path ?? '/'
|
||||
)) as Array<File>;
|
||||
loading.value = false;
|
||||
error.value = '';
|
||||
} catch (e) {
|
||||
files.value = [];
|
||||
loading.value = false;
|
||||
error.value = e as string;
|
||||
}
|
||||
};
|
||||
|
||||
const navigateUp = () => {
|
||||
const newPath = (props.path ?? '/').split('/');
|
||||
newPath.pop();
|
||||
emit('newPath', newPath.join('/') || '/');
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.path,
|
||||
() => {
|
||||
fetchLocation();
|
||||
}
|
||||
);
|
||||
|
||||
fetchLocation();
|
||||
</script>
|
32
src/components/files/FileListElement.vue
Normal file
32
src/components/files/FileListElement.vue
Normal file
@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<tr>
|
||||
<td><i class="icon col-1" :class="`bi-${getIconFromFile(file)}`"></i></td>
|
||||
<td>{{ file.basename }}</td>
|
||||
<td class="d-none d-md-table-cell">
|
||||
{{ new Date(file.lastmod).toLocaleDateString() }}
|
||||
</td>
|
||||
<td class="d-none d-sm-table-cell"><ByteCalc :bytes="file.size" /></td>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import ByteCalc from '@/components/helpers/ByteCalc.vue';
|
||||
import { defineProps } from 'vue';
|
||||
import { fileExtensions, defaultIcon } from '@/lib/fileTypeToIconMappings';
|
||||
import type { File } from '@/models';
|
||||
|
||||
const props = defineProps({
|
||||
file: Object,
|
||||
client: Object,
|
||||
});
|
||||
|
||||
const getIconFromFile = (file: File): string => {
|
||||
const segments = file.basename.split('.');
|
||||
if (segments.length < 2) return defaultIcon;
|
||||
return fileExtensions.get(segments.pop() as string) ?? defaultIcon;
|
||||
};
|
||||
|
||||
const downloadFile = async (file: File) => {
|
||||
await props.client?.getFileContents(file.filename);
|
||||
};
|
||||
</script>
|
27
src/components/files/FolderListElement.vue
Normal file
27
src/components/files/FolderListElement.vue
Normal file
@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<tr @click="$router.push(`#${file.filename}`)" class="file">
|
||||
<td><i class="icon bi-folder"></i></td>
|
||||
<td>{{ file.basename }}</td>
|
||||
<td class="d-none d-md-table-cell">{{ new Date(file.lastmod).toLocaleDateString() }}</td>
|
||||
<td class="d-none d-sm-table-cell"></td>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineProps } from 'vue';
|
||||
|
||||
defineProps({
|
||||
file: Object,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.file {
|
||||
color: inherit !important;
|
||||
text-decoration: inherit !important;
|
||||
cursor: pointer;
|
||||
&:hover{
|
||||
color: inherit !important;
|
||||
}
|
||||
}
|
||||
</style>
|
46
src/components/files/PathSegments.vue
Normal file
46
src/components/files/PathSegments.vue
Normal file
@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<div>
|
||||
<span v-for="(segment, index) of getSegments().slice(0, -1)" :key="segment">
|
||||
<span
|
||||
class="segment p-1"
|
||||
@click="
|
||||
emit(
|
||||
'newPath',
|
||||
`/${getSegments()
|
||||
.slice(1, index + 1)
|
||||
.join('/')}`
|
||||
)
|
||||
"
|
||||
>{{ segment }}</span
|
||||
><i class="bi-caret-right mx-1"></i>
|
||||
</span>
|
||||
<span class="p-1">
|
||||
<b>{{ getSegments()?.pop() }}</b>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineProps, defineEmits } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
path: String,
|
||||
});
|
||||
const emit = defineEmits(['newPath']);
|
||||
|
||||
const getSegments = (): Array<string> => {
|
||||
const segments = `My Files${props.path ?? ''}`.split('/');
|
||||
if (segments[segments.length - 1] === '') segments?.pop();
|
||||
return segments;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.segment {
|
||||
border-radius: .2rem;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: #aaa6;
|
||||
}
|
||||
}
|
||||
</style>
|
23
src/components/helpers/ByteCalc.vue
Normal file
23
src/components/helpers/ByteCalc.vue
Normal file
@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<span>{{ getByteString(bytes) }}</span>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineProps } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
bytes: Number,
|
||||
});
|
||||
|
||||
const getByteString = (bytes: number): string => {
|
||||
const unit = ['B', 'kB', 'MB', 'GB', 'TB', 'PB']
|
||||
.map((symbol, index) => ({
|
||||
symbol,
|
||||
index,
|
||||
breakpoint: 10 ** ((index + 1) * 3),
|
||||
}))
|
||||
.find((unit) => bytes < unit.breakpoint);
|
||||
if (!unit) return 'wtf';
|
||||
return `${(bytes / 10 ** (unit.index * 3)).toFixed(2)} ${unit.symbol}`;
|
||||
};
|
||||
</script>
|
61
src/components/helpers/FileUpload.vue
Normal file
61
src/components/helpers/FileUpload.vue
Normal file
@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<button class="btn btn-secondary" @click.prevent="$refs.fileInput.click()">
|
||||
<slot><i class="bi-plus"></i></slot>
|
||||
</button>
|
||||
<input
|
||||
type="file"
|
||||
ref="fileInput"
|
||||
class="d-none"
|
||||
@change="setFile($refs.fileInput.files[0])"
|
||||
/>
|
||||
<div v-if="info">{{ info }}</div>
|
||||
<div v-if="progressRef">
|
||||
<ByteCalc :bytes="progressRef.loaded" /><span> of </span
|
||||
><ByteCalc :bytes="progressRef.total" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import ByteCalc from '@/components/helpers/ByteCalc.vue';
|
||||
import { defineEmits, defineProps, ref } from 'vue';
|
||||
import { readFileBuffer } from '@/lib/readFileBlob';
|
||||
import { useWebdavStore } from '@/store/webdav';
|
||||
import type { ProgressEvent } from 'webdav';
|
||||
|
||||
const props = defineProps({ path: String });
|
||||
const emit = defineEmits(['started', 'finished', 'failed', 'progress']);
|
||||
const store = useWebdavStore();
|
||||
|
||||
const progressRef = ref(null as null | ProgressEvent);
|
||||
const info = ref('');
|
||||
|
||||
const setFile = async (file: File) => {
|
||||
emit('started');
|
||||
const buffer = await readFileBuffer(file);
|
||||
try {
|
||||
await store.currentSession?.client.putFileContents(
|
||||
`${props.path}/${file.name}`,
|
||||
buffer,
|
||||
{
|
||||
onUploadProgress: (progress) => {
|
||||
progressRef.value = progress;
|
||||
emit('progress', progress);
|
||||
console.log(`Uploaded ${progress.loaded} bytes of ${progress.total}`);
|
||||
},
|
||||
}
|
||||
);
|
||||
info.value = 'upload completed';
|
||||
emit('finished');
|
||||
} catch (e) {
|
||||
info.value = 'upload failed';
|
||||
emit('failed', e);
|
||||
console.error(e);
|
||||
}
|
||||
setTimeout(() => {
|
||||
progressRef.value = null;
|
||||
info.value = '';
|
||||
}, 2000);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
41
src/lib/fileTypeToIconMappings.ts
Normal file
41
src/lib/fileTypeToIconMappings.ts
Normal file
@ -0,0 +1,41 @@
|
||||
export const defaultIcon = 'file-earmark';
|
||||
|
||||
export const fileExtensions = new Map([
|
||||
// Images
|
||||
['png', 'file-earmark-image'],
|
||||
['jpg', 'file-earmark-image'],
|
||||
['tiff', 'file-earmark-image'],
|
||||
// Music
|
||||
['mp3', 'file-earmark-music'],
|
||||
['m4a', 'file-earmark-music'],
|
||||
['aac', 'file-earmark-music'],
|
||||
['aiff', 'file-earmark-music'],
|
||||
['wav', 'file-earmark-music'],
|
||||
['wma', 'file-earmark-music'],
|
||||
// Code
|
||||
['html', 'file-earmark-code'],
|
||||
['htm', 'file-earmark-code'],
|
||||
['xml', 'file-earmark-code'],
|
||||
['js', 'file-earmark-code'],
|
||||
['mjs', 'file-earmark-code'],
|
||||
['py', 'file-earmark-code'],
|
||||
['sh', 'file-earmark-code'],
|
||||
['ts', 'file-earmark-code'],
|
||||
['go', 'file-earmark-code'],
|
||||
['rs', 'file-earmark-code'],
|
||||
['java', 'file-earmark-code'],
|
||||
// Binaries
|
||||
['jar', 'file-earmark-binary'],
|
||||
['exe', 'file-earmark-binary'],
|
||||
['iso', 'file-earmark-binary'],
|
||||
// etc...
|
||||
['pdf', 'file-earmark-pdf'],
|
||||
['txt', 'filetype-txt'],
|
||||
['zip', 'file-earmark-zip'],
|
||||
['gz', 'file-earmark-zip'],
|
||||
['xz', 'file-earmark-zip'],
|
||||
]);
|
||||
|
||||
export const mimeTypes = new Map([
|
||||
['application/pdf', 'file-earmark-pdf'],
|
||||
]);
|
41
src/lib/readFileBlob.ts
Normal file
41
src/lib/readFileBlob.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import { is } from 'typescript-is';
|
||||
|
||||
export const readFileAs = <T>(
|
||||
file: File,
|
||||
getReaderMethod = (reader: FileReader) => reader.readAsDataURL
|
||||
): Promise<T> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onerror = reject;
|
||||
reader.onload = async (event) => {
|
||||
const buffer = event.target?.result;
|
||||
if (!buffer) reject('failed to read file');
|
||||
if (!is<T>(buffer)) reject('wrong type');
|
||||
else resolve(buffer as T);
|
||||
};
|
||||
getReaderMethod(reader as FileReader)(file);
|
||||
});
|
||||
};
|
||||
|
||||
export const readFileBuffer = async (file: File): Promise<ArrayBuffer> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onerror = reject;
|
||||
reader.onload = async (event) => {
|
||||
const buffer = event.target?.result;
|
||||
if (buffer === null || buffer === undefined || typeof buffer === 'string')
|
||||
reject('failed to read file');
|
||||
else resolve(buffer);
|
||||
};
|
||||
reader.readAsArrayBuffer(file);
|
||||
});
|
||||
/*return readFileAs<ArrayBuffer>(
|
||||
file,
|
||||
(reader: FileReader) => reader.readAsArrayBuffer
|
||||
);*/
|
||||
};
|
||||
|
||||
export const readFileBlob = async (file: File): Promise<Blob> => {
|
||||
const data = await readFileAs<string>(file);
|
||||
return await (await fetch(data)).blob();
|
||||
};
|
@ -1,29 +1,29 @@
|
||||
import { Context } from '@/middleware/Context';
|
||||
|
||||
type RunMiddleware = (context: Context) => void;
|
||||
type Middleware = (context: Context) => void;
|
||||
function nextFactory(
|
||||
context: Context,
|
||||
middlewares: Array<RunMiddleware>,
|
||||
middlewares: Array<Middleware>,
|
||||
index: number
|
||||
) {
|
||||
const subsequentMiddleware = middlewares[index];
|
||||
if (!subsequentMiddleware) return context.next;
|
||||
|
||||
return (...args: any[]) => {
|
||||
return (...args: Array<any>) => {
|
||||
// @ts-ignore
|
||||
context.next(...args);
|
||||
context.next(...args as Array<any>);
|
||||
subsequentMiddleware({
|
||||
...context,
|
||||
next: nextFactory(context, middlewares, index++),
|
||||
});
|
||||
};
|
||||
}
|
||||
export function runMiddleware(context: Context) {
|
||||
export function runMiddleware(context: Context): void {
|
||||
const { to } = context;
|
||||
const middlewares = [
|
||||
...((Array.isArray(to.meta.middleware)
|
||||
? to.meta.middleware
|
||||
: [to.meta.middleware]) as Array<RunMiddleware>),
|
||||
: [to.meta.middleware]) as Array<Middleware>),
|
||||
];
|
||||
|
||||
middlewares[0]({ ...context, next: nextFactory(context, middlewares, 1) });
|
||||
|
16
src/main.scss
Normal file
16
src/main.scss
Normal file
@ -0,0 +1,16 @@
|
||||
// Your variable overrides
|
||||
//$body-bg: #222;
|
||||
//$body-color: #222;
|
||||
$dark-body-bg: #222;
|
||||
|
||||
// Bootstrap and its default variables
|
||||
@import "~bootstrap/scss/bootstrap";
|
||||
@import "~bootstrap-darkmode/css/darktheme";
|
||||
|
||||
html, body {
|
||||
}
|
||||
|
||||
#app{
|
||||
font-family: monospace;
|
||||
//color: #fff;
|
||||
}
|
18
src/main.ts
18
src/main.ts
@ -1,7 +1,13 @@
|
||||
import { createApp } from "vue";
|
||||
import App from "./App.vue";
|
||||
import "./registerServiceWorker";
|
||||
import router from "./router";
|
||||
import store from "./store";
|
||||
import { createApp } from 'vue';
|
||||
import App from './App.vue';
|
||||
import './registerServiceWorker';
|
||||
import router from './router';
|
||||
import store from './store';
|
||||
import 'bootstrap-icons/font/bootstrap-icons.scss';
|
||||
import './main.scss';
|
||||
import { ThemeConfig, writeDarkSwitch } from 'bootstrap-darkmode';
|
||||
|
||||
createApp(App).use(store).use(router).mount("#app");
|
||||
export const themeConfig = new ThemeConfig();
|
||||
themeConfig.initTheme();
|
||||
|
||||
createApp(App).use(store).use(router).mount('#app');
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { useWebdavStorage } from '@/store/webdav';
|
||||
import { useWebdavStore } from '@/store/webdav';
|
||||
import { Context } from '@/middleware/Context';
|
||||
|
||||
export default function auth({ next, router }: Context) {
|
||||
console.log('auth');
|
||||
if (!useWebdavStorage().currentSession?.isActive) return router.push({ name: 'Login' });
|
||||
export const auth = ({ next }: Context) => {
|
||||
if (!useWebdavStore().currentSession?.isActive)
|
||||
return next({ name: 'Login' });
|
||||
return next();
|
||||
}
|
||||
};
|
||||
|
9
src/models.ts
Normal file
9
src/models.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export type File = {
|
||||
basename: string;
|
||||
etag: string;
|
||||
filename: string;
|
||||
lastmod: string;
|
||||
mime: string;
|
||||
size: number;
|
||||
type: string;
|
||||
};
|
@ -1,7 +1,6 @@
|
||||
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
|
||||
import Home from '../views/Home.vue';
|
||||
import auth from '@/middleware/auth';
|
||||
import log from '@/middleware/log';
|
||||
import { auth } from '@/middleware/auth';
|
||||
import { runMiddleware } from '@/lib/runMiddleware';
|
||||
|
||||
const routes: Array<RouteRecordRaw> = [
|
||||
@ -22,9 +21,6 @@ const routes: Array<RouteRecordRaw> = [
|
||||
path: '/login',
|
||||
name: 'Login',
|
||||
component: () => import('../views/Login.vue'),
|
||||
meta: {
|
||||
middleware: [log],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -1,13 +0,0 @@
|
||||
import {defineStore} from "pinia";
|
||||
|
||||
export const useAuthStorage = defineStore('auth', {
|
||||
state: () => ({
|
||||
user: '',
|
||||
password: ''
|
||||
}),
|
||||
actions: {
|
||||
login(){
|
||||
|
||||
}
|
||||
}
|
||||
});
|
@ -11,31 +11,29 @@ export type Session = {
|
||||
isActive: boolean;
|
||||
};
|
||||
|
||||
export const useWebdavStorage = defineStore('auth', {
|
||||
export const useWebdavStore = defineStore('auth', {
|
||||
state: () => ({
|
||||
sessions: [] as Array<Session>,
|
||||
currentSession: null as null | Session,
|
||||
}),
|
||||
actions: {
|
||||
login({ user, pass }: Auth): Promise<Session> {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const client = createClient(
|
||||
process.env.VUE_APP_ROOT_WEBDAV as string,
|
||||
{
|
||||
authType: AuthType.Digest,
|
||||
username: user as string,
|
||||
password: pass as string,
|
||||
}
|
||||
) as WebDAVClient;
|
||||
const session = { client, isActive: true } as Session;
|
||||
this.sessions.push(session);
|
||||
this.currentSession = session;
|
||||
resolve(session);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
async login({ user, pass }: Auth): Promise<Session> {
|
||||
try {
|
||||
const client = createClient(
|
||||
`${process.env.VUE_APP_ROOT_WEBDAV ?? ''}/api/dav/files/` as string,
|
||||
{
|
||||
authType: AuthType.Password,
|
||||
username: user as string,
|
||||
password: pass as string,
|
||||
}
|
||||
) as WebDAVClient;
|
||||
const session = { client, isActive: true } as Session;
|
||||
this.sessions.push(session);
|
||||
this.currentSession = session;
|
||||
return session;
|
||||
} catch (e) {
|
||||
throw 'login failed';
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -1,11 +1,20 @@
|
||||
<template>
|
||||
<h1>Files</h1>
|
||||
<div class="row mx-1 justify-content-center">
|
||||
<div class="d-none d-lg-block col-3">
|
||||
<SideMenu />
|
||||
</div>
|
||||
<FileList
|
||||
class="col"
|
||||
:path="$route.hash.slice(1)"
|
||||
@newPath="(path) => $router.push(`#${path}`)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Files',
|
||||
};
|
||||
</script>
|
||||
<script setup lang="ts">
|
||||
import FileList from '@/components/files/FileList.vue';
|
||||
import SideMenu from '@/components/SideMenu.vue';
|
||||
import { useWebdavStore } from '@/store/webdav';
|
||||
|
||||
<style scoped></style>
|
||||
const store = useWebdavStore();
|
||||
</script>
|
||||
|
@ -2,22 +2,32 @@
|
||||
<h1>Login</h1>
|
||||
user: <input type="text" v-model="user" /><br />
|
||||
pass: <input type="password" v-model="pass" /><br />
|
||||
<button @click="login">Login</button>
|
||||
<button class="btn btn-primary" @click="login">Login</button><br />
|
||||
<span class="error">{{ error }}</span>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { useWebdavStorage } from '@/store/webdav';
|
||||
import { useWebdavStore } from '@/store/webdav';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const user = ref('');
|
||||
const pass = ref('');
|
||||
const error = ref('');
|
||||
|
||||
const router = useRouter();
|
||||
const store = useWebdavStore();
|
||||
|
||||
const login = async () => {
|
||||
await useWebdavStorage()
|
||||
.login({ user, pass })
|
||||
.then(async session => {
|
||||
console.log(await session.client.getDirectoryContents('/'));
|
||||
});
|
||||
try {
|
||||
await store.login({ user: user.value, pass: pass.value });
|
||||
error.value = '';
|
||||
await router.push({ name: 'Files' });
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
error.value = e as string;
|
||||
}
|
||||
await store.currentSession?.client.getDirectoryContents(`/`);
|
||||
};
|
||||
</script>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user