Rewrote Frontend

This commit is contained in:
2022-09-03 23:32:20 +02:00
parent 0939525cf3
commit 16876e090d
98 changed files with 4995 additions and 1757 deletions

View File

@@ -1,52 +1,93 @@
<script setup lang="ts">
import type { Status } from "naive-ui/es/progress/src/interface";
import { defineProps, defineExpose, ref } from "vue";
import { isErrorResponse, FS } from "@/api";
import { NProgress } from "naive-ui";
import filesize from "filesize";
import type { Status } from 'naive-ui/es/progress/src/interface';
import type { UploadFile } from '@/api';
import { ref } from 'vue';
import { isErrorResponse, FS } from '@/api';
import { NProgress } from 'naive-ui';
import filesize from 'filesize';
const props = defineProps<{
file: File;
file: UploadFile;
}>();
const progress = ref(0);
const percentage = ref(0);
const err = ref("");
const status = ref<Status>("info");
const err = ref('');
const status = ref<Status>('info');
const shown = ref(true);
async function startUpload(parent: number, token: string) {
const resp = await FS.upload_file(token, parent, props.file, (e) => {
progress.value = e.loaded;
percentage.value = (e.loaded / e.total) * 100;
});
percentage.value = 100;
if (isErrorResponse(resp)) {
err.value = resp.message ?? "Error";
status.value = "error";
} else status.value = "success";
async function startUpload(token: string, done: () => void) {
const resp = await FS.upload_file(token, props.file, (e) => {
progress.value = e.loaded;
percentage.value = (e.loaded / e.total) * 100;
if (e.loaded == e.total) done();
});
percentage.value = 100;
if (isErrorResponse(resp)) {
err.value = resp.message ?? 'Error';
status.value = 'error';
} else {
status.value = 'success';
shown.value = false;
}
}
defineExpose({
startUpload,
startUpload
});
</script>
<template>
<div v-if="percentage < 100">
{{ file.name }} - {{ filesize(progress) }} / {{ filesize(file.size) }} -
{{ Math.floor(percentage * 1000) / 1000 }}%
</div>
<div v-else-if="err !== ''">{{ file.name }} - Error: {{ err }}</div>
<div v-else>{{ file.name }} - Completed</div>
<n-progress
type="line"
:percentage="percentage"
:height="20"
:status="status"
border-radius="10px 0"
fill-border-radius="10px 0"
:show-indicator="false"
/>
<Transition name="slide-up">
<div class="container" v-show="shown">
<div v-if="percentage < 100">
{{ file.fullName }} -
{{
filesize(progress, {
base: 2,
standard: 'jedec'
})
}}
/
{{
filesize(file.file.size, {
base: 2,
standard: 'jedec'
})
}}
- {{ Math.floor(percentage * 1000) / 1000 }}%
</div>
<div v-else-if="err !== ''">
{{ file.fullName }} - Error: {{ err }}
</div>
<div v-else>{{ file.fullName }} - Completed</div>
<n-progress
type="line"
:percentage="percentage"
:height="20"
:status="status"
border-radius="10px 0"
fill-border-radius="10px 0"
:show-indicator="false"
/>
</div>
</Transition>
</template>
<style scoped></style>
<style scoped lang="scss">
.container {
height: 60px;
padding: 8px;
}
.slide-up-leave-active {
transition: all 2s ease-out;
}
.slide-up-leave-to {
height: 0;
padding: 0 8px;
opacity: 0;
transform: translateY(-60px);
}
</style>

View File

@@ -0,0 +1,203 @@
<script setup lang="ts">
import type { TokenInjectType, Responses, UploadFile } from '@/api';
import { inject, ref } from 'vue';
import { useMessage, NModal, NText, NIcon } from 'naive-ui';
import { CloudUpload } from '@vicons/carbon';
import { FS, check_token, isErrorResponse } from '@/api';
import UploadFileDialog from '@/components/UploadDialog/UploadFileDialog.vue';
import { loadingMsgWrapper } from '@/utils';
const message = useMessage();
const props = defineProps<{
node: Responses.GetNode;
}>();
const jwt = inject<TokenInjectType>('jwt') as TokenInjectType;
const emit = defineEmits<{
(e: 'reloadNode'): void;
}>();
const uploadArea = ref<HTMLDivElement>();
const fileInput = ref<HTMLInputElement>();
const uploadDialog = ref();
const uploadDialogShow = ref(false);
const files = ref<UploadFile[]>([]);
function startDrag() {
uploadArea.value?.classList.add('uploadActive');
}
function stopDrag() {
uploadArea.value?.classList.remove('uploadActive');
}
function openBrowser() {
fileInput.value?.click();
}
function browserChanged(event: InputEvent) {
files.value = Array.from(
(event.target as HTMLInputElement).files ?? []
).map((file) => {
return {
parent: props.node.id,
fullName: file.name,
file
};
});
uploadFiles();
}
interface FileSystemDirectoryReader {
readEntries(
successCallback: (entries: FileSystemEntry[]) => void,
errorCallback?: (err: DOMException) => void
): void;
}
interface FileSystemEntry {
readonly fullPath: string;
readonly isDirectory: boolean;
readonly isFile: boolean;
readonly name: string;
file(
successCallback: (file: File) => void,
errorCallback?: (err: DOMException) => void
): void;
createReader(): FileSystemDirectoryReader;
}
const asyncReadEntries = async (
reader: FileSystemDirectoryReader
): Promise<FileSystemEntry[]> =>
new Promise((resolve, reject) => reader.readEntries(resolve, reject));
const getFile = async (entry: FileSystemEntry): Promise<File> =>
new Promise((resolve, reject) => entry.file(resolve, reject));
async function processDirOrFile(
entry: FileSystemEntry,
parent: number,
token: string
) {
if (entry.isDirectory) {
const resp = await FS.create_folder(token, parent, entry.name);
if (isErrorResponse(resp)) return;
if ('exists' in resp && resp.isFile) return;
const reader = entry.createReader();
let entries = [];
do {
try {
entries = await asyncReadEntries(reader);
entries.forEach((e) => processDirOrFile(e, resp.id, token));
} catch {
break;
}
} while (entries.length != 0);
} else
files.value.push({
parent: parent,
fullName: entry.fullPath.slice(1),
file: await getFile(entry)
});
}
const filesDropped = loadingMsgWrapper(message, async (event: DragEvent) => {
stopDrag();
if (!event.dataTransfer) return;
const token = await check_token(jwt);
if (!token) return;
files.value = [];
for (const file of event.dataTransfer.items) {
const entry = file.webkitGetAsEntry();
if (entry)
await processDirOrFile(
entry as unknown as FileSystemEntry,
props.node.id,
token
);
}
uploadFiles();
});
function uploadFiles() {
if (files.value.length == 0) return;
uploadDialogShow.value = true;
}
async function uploadFilesDialogOpen() {
await uploadDialog.value?.startUpload();
uploadDialogShow.value = false;
if (fileInput.value) fileInput.value.value = '';
emit('reloadNode');
}
</script>
<template>
<div
class="uploadArea"
ref="uploadArea"
@drop.prevent
@dragenter.prevent
@dragover.prevent
@dragleave.prevent
@dragend.prevent
@click="openBrowser"
@drop="filesDropped"
@dragenter="startDrag"
@dragover="startDrag"
@dragleave="stopDrag"
@dragend="stopDrag"
>
<input type="file" ref="fileInput" multiple @input="browserChanged" />
<div>
<n-icon size="2em">
<CloudUpload />
</n-icon>
</div>
<n-text>
Click&nbsp;or&nbsp;drag&nbsp;here&nbsp;to&nbsp;upload&nbsp;files
</n-text>
</div>
<n-modal
v-model:show="uploadDialogShow"
:close-on-esc="false"
:mask-closable="false"
:on-after-enter="uploadFilesDialogOpen"
>
<UploadFileDialog ref="uploadDialog" :files="files" />
</n-modal>
</template>
<style scoped lang="scss">
.uploadArea {
border: 1px dashed #ddd;
border-radius: 3px;
cursor: pointer;
background-color: rgb(250, 250, 252);
text-align: center;
transition: border-color 250ms ease-out, background-color 250ms ease-out;
padding: 20px;
input {
display: block;
width: 0;
height: 0;
opacity: 0;
}
}
.uploadArea:hover {
border-color: #888;
}
.uploadActive {
background-color: rgb(240, 252, 240);
}
</style>

View File

@@ -1,44 +1,44 @@
<script setup lang="ts">
import type { TokenInjectType } from "@/api";
import { defineProps, defineExpose, ref, inject } from "vue";
import { check_token } from "@/api";
import UploadEntry from "@/components/UploadDialog/UploadEntry.vue";
import { NCard } from "naive-ui";
import type { TokenInjectType, UploadFile } from '@/api';
import { ref, inject } from 'vue';
import { check_token } from '@/api';
import UploadEntry from '@/components/UploadDialog/UploadEntry.vue';
import { NCard } from 'naive-ui';
const jwt = inject<TokenInjectType>("jwt") as TokenInjectType;
const jwt = inject<TokenInjectType>('jwt') as TokenInjectType;
const entries = ref<typeof UploadEntry[]>([]);
const done = ref(false);
let canCloseResolve: (value: unknown) => void = () => null;
const canClose = new Promise((r) => (canCloseResolve = r));
async function startUpload(parent: number) {
const token = await check_token(jwt);
if (!token) return;
await Promise.all(
entries.value.map((entry) => entry.startUpload(parent, token))
);
done.value = true;
await canClose;
async function startUpload() {
const token = await check_token(jwt);
if (!token) return;
const ents: typeof UploadEntry[] = entries.value;
const allProms: Promise<void>[] = [];
for (const entry of ents) {
await new Promise<void>((resolve) =>
allProms.push(entry.startUpload(token, resolve))
);
}
await Promise.all(allProms);
}
defineExpose({
startUpload,
startUpload
});
defineProps<{
files: File[];
files: UploadFile[];
}>();
</script>
<template>
<n-card title="Upload Files">
<div>
<UploadEntry v-for="f in files" :key="f.name" ref="entries" :file="f" />
</div>
<div>
<button v-if="done" @click="canCloseResolve(null)">Close</button>
</div>
</n-card>
<n-card title="Uploading files" style="margin: 20px">
<UploadEntry
v-for="f in files"
:key="f.file.name"
ref="entries"
:file="f"
/>
</n-card>
</template>
<style scoped></style>