Skip to content

Commit

Permalink
Add settings page
Browse files Browse the repository at this point in the history
  • Loading branch information
Yureien committed Oct 29, 2023
1 parent 5f73b4b commit 35193a6
Show file tree
Hide file tree
Showing 10 changed files with 279 additions and 38 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "User" ADD COLUMN "settings" JSONB NOT NULL DEFAULT '{}';
1 change: 1 addition & 0 deletions src/lib/server/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ model User {
password String
name String
verified Boolean @default(false)
settings Json @default("{}")
pastes Paste[]
AuthToken AuthToken[]
}
Expand Down
8 changes: 8 additions & 0 deletions src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,11 @@ export interface PastePatchResponse {
};
error?: string;
}

export interface UserSettings {
defaults?: {
encrypted?: boolean;
burnAfterRead?: boolean;
expiresAfterSeconds?: number;
};
}
18 changes: 18 additions & 0 deletions src/lib/utils/time.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export function secondsToDHM(seconds: number) {
const days = Math.floor(seconds / (3600 * 24));
const hours = Math.floor((seconds % (3600 * 24)) / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
return { days, hours, minutes };
}

export function DHMToSeconds({
days,
hours,
minutes
}: {
days?: number;
hours?: number;
minutes?: number;
}) {
return (days ?? 0) * 3600 * 24 + (hours ?? 0) * 3600 + (minutes ?? 0) * 60;
}
2 changes: 1 addition & 1 deletion src/routes/(auth)/logout/+page.server.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { redirect, type Actions, type RequestHandler } from '@sveltejs/kit';
import { redirect, type Actions } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';

export const load: PageServerLoad = async ({ cookies }) => {
Expand Down
15 changes: 14 additions & 1 deletion src/routes/+page.server.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
import { getUserIdFromCookie } from '$lib/server/auth';
import type { UserSettings } from '$lib/types';
import prisma from '@db';

export async function load({ cookies }) {
const userId = await getUserIdFromCookie(cookies);
return { loggedIn: !!userId };

let settings: UserSettings | undefined;

if (userId) {
const user = await prisma.user.findUnique({
where: { id: userId },
select: { settings: true }
});
settings = user?.settings as UserSettings;
}

return { loggedIn: !!userId, settings };
}
61 changes: 25 additions & 36 deletions src/routes/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { languageKeysByName } from '$lib/data';
import type { Paste, PasteConfig, PasteCreateResponse } from '$lib/types';
import type { Paste, PasteConfig, PasteCreateResponse, UserSettings } from '$lib/types';
import { onMount } from 'svelte';
import Select from 'svelte-select';
import { encrypt, encryptWithPassword } from '$lib/crypto';
import Hamburger from '$lib/components/Hamburger.svelte';
import { PUBLIC_REGISRATION_ENABLED } from '$env/static/public';
import type { PageData } from './$types';
import { DHMToSeconds, secondsToDHM } from '$lib/utils/time';
export let data: PageData;
Expand All @@ -25,42 +26,18 @@
} = {};
$: {
if (expiresAfter.days) {
expiresAfter.days = Math.max(0, Math.round(expiresAfter.days));
let expiresAfterSeconds = DHMToSeconds(expiresAfter);
// Don't allow pastes to be saved for more than a year
expiresAfterSeconds = Math.min(expiresAfterSeconds, 365 * 24 * 60 * 60);
// Don't allow pastes to be saved for less than 5 minutes
if (expiresAfterSeconds > 0) {
expiresAfterSeconds = Math.max(expiresAfterSeconds, 5 * 60);
expiresAfter = secondsToDHM(expiresAfterSeconds);
} else {
expiresAfter = {};
}
if (expiresAfter.hours) {
expiresAfter.hours = Math.max(0, Math.round(expiresAfter.hours));
if (expiresAfter.hours > 23) {
expiresAfter.days ??= 0;
expiresAfter.days += Math.floor(expiresAfter.hours / 24);
expiresAfter.hours = expiresAfter.hours % 24;
}
}
if (expiresAfter.minutes) {
expiresAfter.minutes = Math.max(0, Math.round(expiresAfter.minutes));
if (expiresAfter.minutes > 59) {
expiresAfter.days ??= 0;
expiresAfter.hours ??= 0;
expiresAfter.days += Math.floor(expiresAfter.minutes / 1440);
expiresAfter.hours += Math.floor((expiresAfter.minutes % 1440) / 60);
expiresAfter.minutes = expiresAfter.minutes % 60;
}
if (
!expiresAfter.days &&
!expiresAfter.hours &&
expiresAfter.minutes > 0 &&
expiresAfter.minutes < 5
) {
expiresAfter.minutes = 5;
}
}
config.expiresAfter =
((expiresAfter.days ?? 0) * 1440 +
(expiresAfter.hours ?? 0) * 60 +
(expiresAfter.minutes ?? 0)) *
60;
config.expiresAfter = expiresAfterSeconds;
}
let inputRef: HTMLTextAreaElement;
Expand All @@ -71,6 +48,17 @@
let config: PasteConfig = { ...initialConfig };
let sidebarOpen = false;
const updateInitialConfig = (defaults: UserSettings['defaults']) => {
if (!defaults) return;
if (defaults?.encrypted !== undefined) config.encrypted = defaults.encrypted;
if (defaults?.burnAfterRead !== undefined) config.burnAfterRead = defaults.burnAfterRead;
if (defaults?.expiresAfterSeconds) {
expiresAfter = secondsToDHM(defaults.expiresAfterSeconds);
config.expiresAfter = defaults.expiresAfterSeconds;
}
};
$: updateInitialConfig(data?.settings?.defaults);
let _sessionStorage: Storage | undefined;
$: if (_sessionStorage) {
Expand All @@ -84,7 +72,7 @@
if (contentBackup) {
const data: { content: string; config: PasteConfig } = JSON.parse(contentBackup);
content = data.content;
config = data.config;
config = { ...config, language: data.config.language ?? config.language };
}
inputRef.focus();
Expand Down Expand Up @@ -229,6 +217,7 @@
{#if PUBLIC_REGISRATION_ENABLED == 'true'}
<div class="flex flex-row gap-4 mb-4 justify-center">
{#if data.loggedIn}
<a href="/dashboard/settings" class="underline underline-offset-4 py-1">Dashboard</a>
<form action="/logout" method="post">
<button class="underline underline-offset-4 py-1">Logout</button>
</form>
Expand Down
55 changes: 55 additions & 0 deletions src/routes/dashboard/+layout.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { onMount } from 'svelte';
let cmdKey = 'Ctrl';
onMount(() => {
const isMac =
(navigator as any).userAgentData?.platform?.toLowerCase() === 'macos' ||
navigator.platform?.toLowerCase().startsWith('mac');
cmdKey = isMac ? '' : 'Ctrl';
document.addEventListener('keydown', (e) => {
if (e.key === 'n' && (e.ctrlKey || e.metaKey)) {
e.preventDefault();
goto('/');
}
if (e.key === 'i' && (e.ctrlKey || e.metaKey)) {
e.preventDefault();
goto('/info');
}
});
});
</script>

<div class="p-2 min-h-screen w-screen flex flex-col text-primary">
<div class="pb-4">
<div class="flex flex-row items-center gap-4">
<h1 class="mr-auto text-2xl"><a href="/">YABin</a></h1>

<!-- <a class="underline underline-offset-4 px-2 py-1" href="/dashboard/pastes">Pastes</a> -->
<a class="underline underline-offset-4 px-2 py-1" href="/dashboard/settings">Settings</a>

<button
class="underline underline-offset-4 px-2 py-1"
title="{cmdKey}+I"
on:click={() => goto('/info')}
>
Info
</button>

<button
class="bg-amber-500 text-black text-lg px-4 py-1"
title="{cmdKey}+N"
on:click={() => goto('/')}
>
New
</button>
</div>
</div>

<div class="px-24 py-4">
<slot />
</div>
</div>
40 changes: 40 additions & 0 deletions src/routes/dashboard/settings/+page.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { getUserIdFromCookie } from '$lib/server/auth';
import { redirect, type Actions } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
import prisma from '@db';
import type { UserSettings } from '$lib/types';

export const load: PageServerLoad = async ({ cookies }) => {
const userId = await getUserIdFromCookie(cookies);
if (!userId) throw redirect(303, '/login');

const user = await prisma.user.findUnique({
where: { id: userId },
select: { settings: true }
});

return { settings: user?.settings as UserSettings };
};

export const actions: Actions = {
defaults: async ({ cookies, request }) => {
const userId = await getUserIdFromCookie(cookies);
if (!userId) throw redirect(303, '/login');

const formData = await request.formData();
const expiresAfterSeconds = parseInt(formData.get('expires-after-seconds')?.toString() ?? '0');
const data = {
encrypted: formData.get('encrypted') === 'on',
burnAfterRead: formData.get('burn-after-read') === 'on',
expiresAfterSeconds: Math.max(0, Math.min(365 * 24 * 3600, expiresAfterSeconds))
};

const user = await prisma.user.update({
where: { id: userId },
data: { settings: { defaults: data } },
select: { settings: true }
});

return { defaultsForm: { success: true, settings: user.settings as UserSettings } };
}
};
115 changes: 115 additions & 0 deletions src/routes/dashboard/settings/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
<script lang="ts">
import { applyAction, enhance } from '$app/forms';
import { goto } from '$app/navigation';
import type { UserSettings } from '$lib/types';
import { DHMToSeconds, secondsToDHM } from '$lib/utils/time';
import type { ActionData, PageData } from './$types';
export let data: PageData;
export let form: ActionData;
let expiresAfter: {
days?: number;
hours?: number;
minutes?: number;
} = {};
let expiresAfterSeconds: number = 0;
$: settings = form?.defaultsForm?.settings || data?.settings;
const updateInitialConfig = (defaults: UserSettings['defaults']) => {
if (!defaults) return;
if (defaults?.expiresAfterSeconds) {
expiresAfterSeconds = defaults.expiresAfterSeconds;
expiresAfter = secondsToDHM(expiresAfterSeconds);
}
};
$: updateInitialConfig(settings?.defaults);
$: {
expiresAfterSeconds = DHMToSeconds(expiresAfter);
// Don't allow pastes to be saved for more than a year
expiresAfterSeconds = Math.min(expiresAfterSeconds, 365 * 24 * 60 * 60);
// Don't allow pastes to be saved for less than 5 minutes
if (expiresAfterSeconds > 0) {
expiresAfterSeconds = Math.max(expiresAfterSeconds, 5 * 60);
expiresAfter = secondsToDHM(expiresAfterSeconds);
} else {
expiresAfter = {};
}
}
</script>

<h1 class="text-5xl">Settings</h1>

<div class="px-4">
<h4 class="text-2xl mt-6 mb-4">Defaults</h4>

<form
method="post"
action="?/defaults"
class="mt-2 flex flex-col gap-4"
use:enhance={() => {
return async ({ result }) => {
if (result.type === 'redirect') await goto(result.location);
else await applyAction(result);
};
}}
>
<div>
<label for="encrypted" class="py-2">Encrypted</label>
<input
id="encrypted"
class="bg-dark px-2 py-1"
type="checkbox"
name="encrypted"
checked={settings?.defaults?.encrypted}
/>
</div>

<div>
<label for="burn-after-read" class="py-2">Burn after read</label>
<input
id="burn-after-read"
class="bg-dark px-2 py-1"
type="checkbox"
name="burn-after-read"
checked={settings?.defaults?.burnAfterRead}
/>
</div>

<div>
<span>Expires in:</span>
<div class="grid grid-cols-3 gap-2 justify-center items-center">
<input
type="number"
class="bg-dark py-1 text-center"
placeholder="DD"
bind:value={expiresAfter.days}
/>
<input
type="number"
class="bg-dark py-1 text-center"
placeholder="HH"
bind:value={expiresAfter.hours}
/>
<input
type="number"
class="bg-dark py-1 text-center"
placeholder="MM"
bind:value={expiresAfter.minutes}
/>
<input type="hidden" name="expires-after-seconds" bind:value={expiresAfterSeconds} />
</div>
</div>

<div class="mt-2">
<button class="bg-amber-500 text-black text-lg px-4 py-1">Save</button>
{#if form?.defaultsForm.success}
<span class="text-green-500">Saved</span>
<!-- {:else if form?.defaultsForm.error}
<span class="text-red-500">Error</span> -->
{/if}
</div>
</form>
</div>

0 comments on commit 35193a6

Please sign in to comment.