From 675e2b2d68320bacc3113e9a016b636494b23d8c Mon Sep 17 00:00:00 2001 From: Eclypsed Date: Tue, 30 Jan 2024 00:33:19 -0500 Subject: [PATCH] Trashed half my styling, this settings page is going to be a nightmare --- src/app.css | 6 - src/lib/components/navbar/navTab.svelte | 24 +-- src/lib/components/navbar/playlistTab.svelte | 27 ++-- src/routes/(app)/+layout.svelte | 100 ++++--------- src/routes/(app)/+page.svelte | 4 +- src/routes/(app)/user/+page.svelte | 2 +- src/routes/settings/+layout.svelte | 8 +- src/routes/settings/+page.svelte | 56 ++++++- .../settings/connections/+page.server.ts | 110 ++++++++++++++ src/routes/settings/connections/+page.svelte | 137 ++++++++++++++++++ .../connections/jellyfinAuthBox.svelte | 52 +++++++ src/routes/settings/navMenu.svelte | 20 --- 12 files changed, 415 insertions(+), 131 deletions(-) create mode 100644 src/routes/settings/connections/+page.server.ts create mode 100644 src/routes/settings/connections/+page.svelte create mode 100644 src/routes/settings/connections/jellyfinAuthBox.svelte delete mode 100644 src/routes/settings/navMenu.svelte diff --git a/src/app.css b/src/app.css index d6cf9cb..5673d8e 100644 --- a/src/app.css +++ b/src/app.css @@ -47,9 +47,3 @@ img { --jellyfin-blue: #00a4dc; --youtube-red: #ff0000; } - -@media screen and (max-width: 768px) { - :root { - font-size: 0.7rem; - } -} diff --git a/src/lib/components/navbar/navTab.svelte b/src/lib/components/navbar/navTab.svelte index 2781c63..6b167ed 100644 --- a/src/lib/components/navbar/navTab.svelte +++ b/src/lib/components/navbar/navTab.svelte @@ -10,31 +10,33 @@ export let disabled = false export let nav: NavTab - import { createEventDispatcher } from "svelte"; - import { goto } from "$app/navigation"; + import { createEventDispatcher } from 'svelte' + import { goto } from '$app/navigation' const dispatch = createEventDispatcher() + + let button: HTMLButtonElement - \ No newline at end of file + button:not(:disabled):hover > div { + height: 40%; + } + diff --git a/src/lib/components/navbar/playlistTab.svelte b/src/lib/components/navbar/playlistTab.svelte index 3e2724d..fff30d2 100644 --- a/src/lib/components/navbar/playlistTab.svelte +++ b/src/lib/components/navbar/playlistTab.svelte @@ -10,12 +10,24 @@ export let disabled = false export let playlist: PlaylistTab - import { createEventDispatcher } from "svelte"; - import { goto } from "$app/navigation"; + import { createEventDispatcher } from 'svelte' + import { goto } from '$app/navigation' const dispatch = createEventDispatcher() let button: HTMLButtonElement + + type ButtonCenter = { + x: number + y: number + } + + const calculateCenter = (button: HTMLButtonElement): ButtonCenter => { + const rect = button.getBoundingClientRect() + const x = (rect.left + rect.right) / 2 + const y = (rect.top + rect.bottom) / 2 + return { x, y } + } \ No newline at end of file + diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte index 0fb8c94..4eb337c 100644 --- a/src/routes/(app)/+layout.svelte +++ b/src/routes/(app)/+layout.svelte @@ -1,89 +1,45 @@ {#if $pageWidth >= 768}
-
-
+
+
{#each data.navTabs as nav} - + {/each} -
-
+
{#each data.playlistTabs as playlist} - + setTooltip(event.detail.x, event.detail.y, event.detail.content)} on:mouseleave={() => (playlistTooltip.style.display = 'none')} /> {/each}
+
-
- {#key data.url} -
- -
- {/key} +
+
@@ -91,15 +47,9 @@
{:else}
- {#key data.url.pathname} -
- -
- {/key} +
+ +
-{/if} \ No newline at end of file +{/if} diff --git a/src/routes/(app)/+page.svelte b/src/routes/(app)/+page.svelte index e83d2cc..6a6712a 100644 --- a/src/routes/(app)/+page.svelte +++ b/src/routes/(app)/+page.svelte @@ -1,7 +1,7 @@
- + --> diff --git a/src/routes/(app)/user/+page.svelte b/src/routes/(app)/user/+page.svelte index 85ac96f..c9ec20d 100644 --- a/src/routes/(app)/user/+page.svelte +++ b/src/routes/(app)/user/+page.svelte @@ -1,7 +1,7 @@ diff --git a/src/routes/settings/+layout.svelte b/src/routes/settings/+layout.svelte index 5b07c2d..5c0e69c 100644 --- a/src/routes/settings/+layout.svelte +++ b/src/routes/settings/+layout.svelte @@ -1,13 +1,13 @@ -
-

+

history.back()}> - + - Account + Account

diff --git a/src/routes/settings/+page.svelte b/src/routes/settings/+page.svelte index 6634b68..21c225a 100644 --- a/src/routes/settings/+page.svelte +++ b/src/routes/settings/+page.svelte @@ -1,5 +1,57 @@ - + diff --git a/src/routes/settings/connections/+page.server.ts b/src/routes/settings/connections/+page.server.ts new file mode 100644 index 0000000..8fdca07 --- /dev/null +++ b/src/routes/settings/connections/+page.server.ts @@ -0,0 +1,110 @@ +import { fail } from '@sveltejs/kit' +import { SECRET_INTERNAL_API_KEY } from '$env/static/private' +import { Connections } from '$lib/server/users' + +const createProfile = async (connectionData) => { + const { id, serviceType, serviceUserId, serviceUrl, accessToken, refreshToken, expiry } = connectionData + + switch (serviceType) { + case 'jellyfin': + const userUrl = new URL(`Users/${serviceUserId}`, serviceUrl).href + const systemUrl = new URL('System/Info', serviceUrl).href + + const reqHeaders = new Headers({ Authorization: `MediaBrowser Token="${accessToken}"` }) + + const userResponse = await fetch(userUrl, { headers: reqHeaders }) + const systemResponse = await fetch(systemUrl, { headers: reqHeaders }) + + const userData = await userResponse.json() + const systemData = await systemResponse.json() + + return { + connectionId: id, + serviceType, + userId: serviceUserId, + username: userData?.Name, + serviceUrl: serviceUrl, + serverName: systemData?.ServerName, + } + default: + return null + } +} + +/** @type {import('./$types').PageServerLoad} */ +export const load = async ({ fetch, locals }) => { + const response = await fetch(`/api/user/connections?userId=${locals.userId}`, { + headers: { + apikey: SECRET_INTERNAL_API_KEY, + }, + }) + + const allConnections = await response.json() + const connectionProfiles = [] + if (allConnections) { + for (const connection of allConnections) { + const connectionProfile = await createProfile(connection) + connectionProfiles.push(connectionProfile) + } + } + + return { connectionProfiles } +} + +/** @type {import('./$types').Actions}} */ +export const actions = { + authenticateJellyfin: async ({ request, fetch, locals }) => { + const formData = await request.formData() + const { serverUrl, username, password, deviceId } = Object.fromEntries(formData) + const serverUrlOrigin = new URL(serverUrl).origin + + const jellyfinAuthResponse = await fetch('/api/jellyfin/auth', { + method: 'POST', + headers: { + apikey: SECRET_INTERNAL_API_KEY, + }, + body: JSON.stringify({ serverUrl: serverUrlOrigin, username, password, deviceId }), + }) + + if (!jellyfinAuthResponse.ok) { + const jellyfinAuthError = await jellyfinAuthResponse.text() + return fail(jellyfinAuthResponse.status, { message: jellyfinAuthError }) + } + + const jellyfinAuthData = await jellyfinAuthResponse.json() + const accessToken = jellyfinAuthData.AccessToken + const jellyfinUserId = jellyfinAuthData.User.Id + const updateConnectionsResponse = await fetch(`/api/user/connections?userId=${locals.userId}`, { + method: 'POST', + headers: { + apikey: SECRET_INTERNAL_API_KEY, + }, + body: JSON.stringify({ serviceType: 'jellyfin', serviceUserId: jellyfinUserId, serviceUrl: serverUrlOrigin, accessToken }), + }) + + if (!updateConnectionsResponse.ok) return fail(500, { message: 'Internal Server Error' }) + + const newConnection = await updateConnectionsResponse.json() + const newConnectionData = UserConnections.getConnection(newConnection.id) + + const jellyfinProfile = await createProfile(newConnectionData) + + return { newConnection: jellyfinProfile } + }, + deleteConnection: async ({ request, fetch, locals }) => { + const formData = await request.formData() + const connectionId = formData.get('connectionId') + + const deleteConnectionResponse = await fetch(`/api/user/connections?userId=${locals.userId}`, { + method: 'DELETE', + headers: { + apikey: SECRET_INTERNAL_API_KEY, + }, + body: JSON.stringify({ connectionId }), + }) + + if (!deleteConnectionResponse.ok) return fail(500, { message: 'Internal Server Error' }) + + return { deletedConnectionId: connectionId } + }, +} diff --git a/src/routes/settings/connections/+page.svelte b/src/routes/settings/connections/+page.svelte new file mode 100644 index 0000000..b3a22e9 --- /dev/null +++ b/src/routes/settings/connections/+page.svelte @@ -0,0 +1,137 @@ + + +
+
+

Add Connection

+
+ {#each Object.entries(Services) as [serviceType, serviceData]} + + {/each} +
+
+
+ {#each connectionProfiles as connectionProfile} + {@const serviceData = Services[connectionProfile.serviceType]} +
+
+ {serviceData.displayName} icon +
+
{connectionProfile?.username ? connectionProfile.username : 'Placeholder Account Name'}
+
+ {serviceData.displayName} + {#if connectionProfile.serviceType === 'jellyfin' && connectionProfile?.serverName} + - {connectionProfile.serverName} + {/if} +
+
+
+ (modal = `delete-${connectionProfile.connectionId}`)}> + + +
+
+
+
+
+ console.log(event.detail.toggled)} /> + Enable Connection +
+
+
+ {/each} +
+ {#if modal} +
+ {#if typeof modal === 'string'} + {@const connectionId = modal.replace('delete-', '')} + {@const connection = connectionProfiles.find((profile) => profile.connectionId === connectionId)} + {@const serviceData = Services[connection.serviceType]} +
+

Delete {serviceData.displayName} connection?

+
+ + + +
+
+ {:else} + (modal = null)} /> + {/if} + + {/if} +
diff --git a/src/routes/settings/connections/jellyfinAuthBox.svelte b/src/routes/settings/connections/jellyfinAuthBox.svelte new file mode 100644 index 0000000..c311964 --- /dev/null +++ b/src/routes/settings/connections/jellyfinAuthBox.svelte @@ -0,0 +1,52 @@ + + +
+

Jellyfin Sign In

+
+ +
+ + +
+
+
+ + +
+
+ + diff --git a/src/routes/settings/navMenu.svelte b/src/routes/settings/navMenu.svelte deleted file mode 100644 index ebc602d..0000000 --- a/src/routes/settings/navMenu.svelte +++ /dev/null @@ -1,20 +0,0 @@ - - -