basic file sharing
This commit is contained in:
@@ -33,7 +33,6 @@ if (
|
||||
route.query.state &&
|
||||
typeof route.query.state === 'string'
|
||||
) {
|
||||
console.log('SEND');
|
||||
loggingIn.value = true;
|
||||
const { success } = await oidcLoginUser(
|
||||
route.query.code,
|
||||
|
||||
237
frontend/pages/share.vue
Normal file
237
frontend/pages/share.vue
Normal file
@@ -0,0 +1,237 @@
|
||||
<script lang="ts" setup>
|
||||
import { fetchShareFile, getShare, listShareFiles } from '~/lib/api/shares';
|
||||
import type { DirectoryEntry } from '~/shared/types';
|
||||
import type { Share } from '~/shared/types/shares';
|
||||
|
||||
definePageMeta({
|
||||
layout: 'share',
|
||||
});
|
||||
|
||||
const warrenStore = useWarrenStore();
|
||||
const route = useRoute();
|
||||
|
||||
const share = await getShareFromQuery();
|
||||
const entries = ref<DirectoryEntry[] | null>(null);
|
||||
const parent = ref<DirectoryEntry | null>(null);
|
||||
const password = ref<string>('');
|
||||
const loading = ref<boolean>(false);
|
||||
|
||||
if (share != null) {
|
||||
warrenStore.setCurrentWarren(share.data.warrenId, '/');
|
||||
|
||||
if (!share.data.password) {
|
||||
await loadFiles();
|
||||
}
|
||||
}
|
||||
|
||||
async function getShareFromQuery(): Promise<{
|
||||
data: Share;
|
||||
file: DirectoryEntry;
|
||||
} | null> {
|
||||
const shareId = route.query.id;
|
||||
|
||||
if (shareId == null || typeof shareId !== 'string') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const result = await getShare(shareId);
|
||||
|
||||
if (!result.success) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return { data: result.share, file: result.file };
|
||||
}
|
||||
|
||||
async function submitPassword() {
|
||||
loadFiles();
|
||||
}
|
||||
|
||||
async function loadFiles() {
|
||||
if (loading.value || share == null || warrenStore.current == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (share.file.fileType !== 'directory') {
|
||||
return;
|
||||
}
|
||||
|
||||
loading.value = true;
|
||||
|
||||
const result = await listShareFiles(
|
||||
share.data.id,
|
||||
warrenStore.current.path,
|
||||
password.value.length > 0 ? password.value : null
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
let cookie = `X-Share-Password=${password.value}; Path=/; SameSite=Lax; Secure;`;
|
||||
|
||||
if (share.data.expiresAt != null) {
|
||||
const dayjs = useDayjs();
|
||||
|
||||
const diff = dayjs(share.data.expiresAt).diff(dayjs()) / 1000;
|
||||
cookie += `Max-Age=${diff};`;
|
||||
}
|
||||
|
||||
document.cookie = cookie;
|
||||
|
||||
entries.value = result.files;
|
||||
parent.value = result.parent;
|
||||
}
|
||||
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
async function onEntryClicked(entry: DirectoryEntry) {
|
||||
if (warrenStore.current == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const entryPath = joinPaths(warrenStore.current.path, entry.name);
|
||||
|
||||
if (entry.fileType === 'directory') {
|
||||
warrenStore.setCurrentWarrenPath(entryPath);
|
||||
await loadFiles();
|
||||
return;
|
||||
}
|
||||
|
||||
if (entry.mimeType == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (entry.mimeType.startsWith('image/')) {
|
||||
const result = await fetchShareFile(
|
||||
share!.data.id,
|
||||
entryPath,
|
||||
password.value.length > 0 ? password.value : null
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
const url = URL.createObjectURL(result.data);
|
||||
warrenStore.imageViewer.src = url;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function onBack() {
|
||||
if (warrenStore.backCurrentPath()) {
|
||||
await loadFiles();
|
||||
}
|
||||
}
|
||||
|
||||
function onDowloadClicked() {
|
||||
if (share == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
downloadFile(
|
||||
share.file.name,
|
||||
getApiUrl(`warrens/files/cat_share?shareId=${share.data.id}&path=/`)
|
||||
);
|
||||
}
|
||||
|
||||
function onEntryDownload(entry: DirectoryEntry) {
|
||||
if (share == null || warrenStore.current == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
downloadFile(
|
||||
entry.name,
|
||||
getApiUrl(
|
||||
`warrens/files/cat_share?shareId=${share.data.id}&path=${joinPaths(warrenStore.current.path, entry.name)}`
|
||||
)
|
||||
);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="share != null"
|
||||
class="flex h-full w-full items-center justify-center px-2"
|
||||
>
|
||||
<div
|
||||
:class="[
|
||||
'w-full rounded-lg border transition-all',
|
||||
entries == null ? 'max-w-lg' : 'max-w-screen-xl',
|
||||
]"
|
||||
>
|
||||
<div
|
||||
class="flex flex-row items-center justify-between gap-4 px-6 pt-6"
|
||||
>
|
||||
<div class="flex w-full flex-row">
|
||||
<div class="flex grow flex-col gap-1.5">
|
||||
<h3 class="leading-none font-semibold">Share</h3>
|
||||
<p class="text-muted-foreground text-sm">
|
||||
Created
|
||||
{{
|
||||
$dayjs(share.data.createdAt).format(
|
||||
'MMM D, YYYY HH:mm'
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-row items-center justify-end gap-4">
|
||||
<p>{{ share.file.name }}</p>
|
||||
<DirectoryEntryIcon
|
||||
:entry="{ ...share.file, name: '/' }"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-row items-end">
|
||||
<Button
|
||||
:class="
|
||||
share.file.fileType !== 'file' &&
|
||||
entries == null &&
|
||||
'hidden'
|
||||
"
|
||||
size="icon"
|
||||
variant="outline"
|
||||
@click="onDowloadClicked"
|
||||
><Icon name="lucide:download"
|
||||
/></Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex w-full flex-col p-6">
|
||||
<DirectoryList
|
||||
v-if="entries != null"
|
||||
:entries
|
||||
:parent
|
||||
:disable-entries="loading"
|
||||
@entry-click="onEntryClicked"
|
||||
@entry-download="onEntryDownload"
|
||||
@back="onBack"
|
||||
/>
|
||||
<div
|
||||
v-else-if="share.data.password"
|
||||
class="flex h-full flex-col justify-between gap-2"
|
||||
>
|
||||
<div class="flex flex-col gap-1">
|
||||
<Label for="password">Password</Label>
|
||||
<Input
|
||||
id="password"
|
||||
v-model="password"
|
||||
type="password"
|
||||
name="password"
|
||||
autocomplete="off"
|
||||
data-1p-ignore
|
||||
data-protonpass-ignore
|
||||
data-bwignore
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-row-reverse items-end">
|
||||
<Button
|
||||
:disabled="loading || password.length < 1"
|
||||
@click="submitPassword"
|
||||
>Enter</Button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="bg-accent/20 rounded-md p-4">
|
||||
<p class="text-destructive-foreground">Failed to get share</p>
|
||||
</div>
|
||||
</template>
|
||||
@@ -3,7 +3,8 @@ import { useDropZone } from '@vueuse/core';
|
||||
import { toast } from 'vue-sonner';
|
||||
import DirectoryListContextMenu from '~/components/DirectoryListContextMenu.vue';
|
||||
import RenameEntryDialog from '~/components/actions/RenameEntryDialog.vue';
|
||||
import { getWarrenDirectory } from '~/lib/api/warrens';
|
||||
import { fetchFile, getWarrenDirectory } from '~/lib/api/warrens';
|
||||
import type { DirectoryEntry } from '~/shared/types';
|
||||
|
||||
definePageMeta({
|
||||
middleware: ['authenticated'],
|
||||
@@ -31,7 +32,10 @@ const dirData = useAsyncData(
|
||||
'current-directory',
|
||||
async () => {
|
||||
if (warrenStore.current == null) {
|
||||
return [];
|
||||
return {
|
||||
files: [],
|
||||
parent: null,
|
||||
};
|
||||
}
|
||||
|
||||
loadingIndicator.start();
|
||||
@@ -74,6 +78,57 @@ function onDrop(files: File[] | null, e: DragEvent) {
|
||||
uploadStore.dialogOpen = true;
|
||||
}
|
||||
}
|
||||
|
||||
async function onEntryClicked(entry: DirectoryEntry) {
|
||||
if (warrenStore.loading || warrenStore.current == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (entry.fileType === 'directory') {
|
||||
warrenStore.addToCurrentWarrenPath(entry.name);
|
||||
return;
|
||||
}
|
||||
|
||||
if (entry.mimeType == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (entry.mimeType.startsWith('image/')) {
|
||||
const result = await fetchFile(
|
||||
warrenStore.current.warrenId,
|
||||
warrenStore.current.path,
|
||||
entry.name
|
||||
);
|
||||
if (result.success) {
|
||||
const url = URL.createObjectURL(result.data);
|
||||
warrenStore.imageViewer.src = url;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onEntryDownload(entry: DirectoryEntry) {
|
||||
if (warrenStore.current == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (entry.fileType !== 'file') {
|
||||
toast.warning('Download', {
|
||||
description: 'Directory downloads are not supported yet',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
downloadFile(
|
||||
entry.name,
|
||||
getApiUrl(
|
||||
`warrens/files/cat?warrenId=${warrenStore.current.warrenId}&path=${joinPaths(warrenStore.current.path, entry.name)}`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function onBack() {
|
||||
warrenStore.backCurrentPath();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -87,6 +142,9 @@ function onDrop(files: File[] | null, e: DragEvent) {
|
||||
"
|
||||
:entries="dirData.files"
|
||||
:parent="dirData.parent"
|
||||
@entry-click="onEntryClicked"
|
||||
@entry-download="onEntryDownload"
|
||||
@back="onBack"
|
||||
/>
|
||||
</DirectoryListContextMenu>
|
||||
<RenameEntryDialog />
|
||||
|
||||
Reference in New Issue
Block a user