basic file sharing
This commit is contained in:
203
frontend/components/actions/ShareDialog.vue
Normal file
203
frontend/components/actions/ShareDialog.vue
Normal file
@@ -0,0 +1,203 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
Dialog,
|
||||
DialogTrigger,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
} from '@/components/ui/dialog';
|
||||
import { useShareDialog } from '@/stores';
|
||||
import { toTypedSchema } from '@vee-validate/yup';
|
||||
import { useForm } from 'vee-validate';
|
||||
import { toast } from 'vue-sonner';
|
||||
import { createShare, listShares } from '~/lib/api/shares';
|
||||
import { shareSchema } from '~/lib/schemas/share';
|
||||
import type { Share } from '~/shared/types/shares';
|
||||
|
||||
const warrenStore = useWarrenStore();
|
||||
const dialog = useShareDialog();
|
||||
|
||||
const newScreen = ref(false);
|
||||
|
||||
const existingShares = ref<Share[]>([]);
|
||||
useAsyncData('current-file-shares', async () => {
|
||||
if (warrenStore.current == null || dialog.target == null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const result = await listShares(
|
||||
warrenStore.current.warrenId,
|
||||
joinPaths(warrenStore.current.path, dialog.target.name)
|
||||
);
|
||||
|
||||
if (!result.success) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (result.shares.length < 1) {
|
||||
newScreen.value = true;
|
||||
} else {
|
||||
newScreen.value = false;
|
||||
}
|
||||
|
||||
existingShares.value = result.shares;
|
||||
});
|
||||
dialog.$subscribe(async (_, value) => {
|
||||
if (value.target !== null) {
|
||||
await refreshNuxtData('current-file-shares');
|
||||
}
|
||||
});
|
||||
|
||||
function onOpenChange() {
|
||||
dialog.reset();
|
||||
}
|
||||
|
||||
function onCancel() {
|
||||
if (newScreen.value && existingShares.value.length > 0) {
|
||||
newScreen.value = false;
|
||||
} else {
|
||||
dialog.reset();
|
||||
}
|
||||
}
|
||||
|
||||
const form = useForm({
|
||||
validationSchema: toTypedSchema(shareSchema),
|
||||
});
|
||||
|
||||
const onSubmit = form.handleSubmit(async (values) => {
|
||||
if (dialog.target == null || warrenStore.current == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await createShare(
|
||||
warrenStore.current.warrenId,
|
||||
joinPaths(warrenStore.current.path, dialog.target.name),
|
||||
values.password ?? null,
|
||||
values.lifetime ?? null
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
toast.success('Share', {
|
||||
description: `Successfully created a share for ${dialog.target.name}`,
|
||||
});
|
||||
await refreshNuxtData('current-file-shares');
|
||||
newScreen.value = false;
|
||||
} else {
|
||||
toast.error('Share', {
|
||||
description: `Failed to create share`,
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Dialog :open="dialog.target != null" @update:open="onOpenChange">
|
||||
<DialogTrigger as-child>
|
||||
<slot />
|
||||
</DialogTrigger>
|
||||
<DialogContent
|
||||
v-if="dialog.target != null"
|
||||
class="w-full !max-w-[calc(100vw-8px)] sm:!max-w-[min(98vw,1000px)]"
|
||||
>
|
||||
<DialogHeader class="overflow-hidden">
|
||||
<DialogTitle class="truncate"
|
||||
>Share {{ dialog.target.name }}</DialogTitle
|
||||
>
|
||||
<DialogDescription
|
||||
>Create a shareable link to this
|
||||
{{ dialog.target.fileType }}</DialogDescription
|
||||
>
|
||||
</DialogHeader>
|
||||
|
||||
<SharesTable v-if="!newScreen" :shares="existingShares" />
|
||||
<form
|
||||
v-else
|
||||
id="share-form"
|
||||
class="grid gap-4"
|
||||
@submit.prevent="onSubmit"
|
||||
>
|
||||
<FormField v-slot="{ componentField }" name="password">
|
||||
<FormItem>
|
||||
<FormLabel>Password</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
v-bind="componentField"
|
||||
id="password"
|
||||
type="password"
|
||||
autocomplete="off"
|
||||
data-1p-ignore
|
||||
data-protonpass-ignore
|
||||
data-bwignore
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
<FormDescription>
|
||||
<span
|
||||
v-if="
|
||||
form.values.password == null ||
|
||||
form.values.password.length < 1
|
||||
"
|
||||
>
|
||||
The share will not require a password
|
||||
</span>
|
||||
<span v-else>
|
||||
The share will require the specified password
|
||||
</span>
|
||||
</FormDescription>
|
||||
</FormItem>
|
||||
</FormField>
|
||||
<FormField v-slot="{ componentField }" name="lifetime">
|
||||
<FormItem>
|
||||
<FormLabel>Lifetime (seconds)</FormLabel>
|
||||
<FormControl>
|
||||
<div
|
||||
class="flex w-full flex-row justify-between gap-1"
|
||||
>
|
||||
<Input
|
||||
v-bind="componentField"
|
||||
id="lifetime"
|
||||
type="number"
|
||||
class="w-full max-w-full min-w-0"
|
||||
autocomplete="off"
|
||||
data-1p-ignore
|
||||
data-protonpass-ignore
|
||||
data-bwignore
|
||||
/>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
<FormDescription>
|
||||
<span
|
||||
v-if="
|
||||
form.values.lifetime != null &&
|
||||
form.values.lifetime > 0
|
||||
"
|
||||
class="w-full"
|
||||
>
|
||||
The share will expire in
|
||||
{{
|
||||
$dayjs
|
||||
.duration({
|
||||
seconds: form.values.lifetime,
|
||||
})
|
||||
.humanize()
|
||||
}}
|
||||
</span>
|
||||
<span v-else> The share will be permanent </span>
|
||||
</FormDescription>
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</form>
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="ghost" @click="onCancel">Cancel</Button>
|
||||
<Button v-if="newScreen" type="submit" form="share-form"
|
||||
>Share</Button
|
||||
>
|
||||
<Button v-else @click="() => (newScreen = true)">New</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
Reference in New Issue
Block a user