Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/better status page #170

Merged
merged 4 commits into from
Jan 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions frontend/src/routes/api/items/[id]/+server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { json } from '@sveltejs/kit';

export const GET = async ({ fetch, params }) => {
const id: number = Number(params.id);
console.log(`Fetching extended data of item ${id} from backend`);

async function getExtendedData() {
try {
const res = await fetch(`http://127.0.0.1:8080/items/extended/${id}`);
if (res.ok) {
return await res.json();
}
return {
status: res.status,
statusText: res.statusText
};
} catch (e) {
console.error(e);
return {
status: 503,
statusText: 'Unable to fetch extended data. API is down.'
};
}
}

const data = await getExtendedData();

return new Response(JSON.stringify(data), {
headers: {
'Content-Type': 'application/json'
}
});
};
172 changes: 125 additions & 47 deletions frontend/src/routes/status/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import { Button } from '$lib/components/ui/button';
import { Badge } from '$lib/components/ui/badge';
import * as Tooltip from '$lib/components/ui/tooltip';
import * as Dialog from '$lib/components/ui/dialog';
import { Loader2, ArrowUpRight, RotateCw, MoveUpRight } from 'lucide-svelte';
import { toast } from 'svelte-sonner';
import type { StatusInfo } from '$lib/types';
Expand Down Expand Up @@ -47,6 +48,18 @@
description: 'Items which are in your plex library but are missing some files'
}
};

let extendedDataLoading = false;
let extendedItem: any;

async function getExtendedData(id: number) {
extendedDataLoading = true;
const res = await fetch(`/api/items/${id}`);
const data = await res.json();
console.log(data);
extendedItem = data.item;
extendedDataLoading = false;
}
</script>

<svelte:head>
Expand Down Expand Up @@ -110,56 +123,121 @@

{@const icebergItems = convertIcebergItemsToObject(items.items)}
<div class="flex flex-col gap-12 mt-4 w-full">
{#each Object.keys(icebergItems) as key (key)}
<Carousel.Root opts={{ dragFree: true }} class="w-full max-w-full flex flex-col gap-4">
<div class="flex items-center justify-between">
<div class="flex flex-col">
<a href="/status/type/{key}" class="flex gap-1 items-center hover:underline">
<h3 class="text-xl md:text-2xl font-semibold">
{statusInfo[key].text ?? formatWords(key)}
</h3>
<MoveUpRight class="size-4 md:size-6" />
</a>
<p class="text-muted-foreground text-sm">{statusInfo[key].description}</p>
{#if Object.keys(icebergItems).length === 0}
<div class="flex flex-col w-full h-full font-primary">
<h3 class="text-xl font-semibold">No items found :(</h3>
<p class="text-sm text-muted-foreground">
You can request items from the content services configured.
</p>
</div>
{:else}
{#each Object.keys(icebergItems) as key (key)}
<Carousel.Root opts={{ dragFree: true }} class="w-full max-w-full flex flex-col gap-4">
<div class="flex items-center justify-between">
<div class="flex flex-col">
<a href="/status/type/{key}" class="flex gap-1 items-center hover:underline">
<h3 class="text-xl md:text-2xl font-semibold">
{statusInfo[key].text ?? formatWords(key)}
</h3>
<MoveUpRight class="size-4 md:size-6" />
</a>
<p class="text-muted-foreground text-sm">{statusInfo[key].description}</p>
</div>
<div class="flex items-center justify-center gap-2 mt-6">
<Carousel.Previous class="rounded-md static h-8 w-8" />
<Carousel.Next class="rounded-md static h-8 w-8" />
</div>
</div>
<div class="flex items-center justify-center gap-2 mt-6">
<Carousel.Previous class="rounded-md static h-8 w-8" />
<Carousel.Next class="rounded-md static h-8 w-8" />
</div>
</div>
<Carousel.Content class="flex flex-row h-full w-full">
{#each icebergItems[key] as item}
<Carousel.Item class="flex-none mr-2 min-w-0 max-w-max w-full h-full group/item">
<div class="flex flex-col w-full h-full max-w-[144px] md:max-w-[176px]">
<div class="relative h-full w-full">
<img
alt={item.imdb_id}
class="bg-cover bg-center h-[216px] w-[144px] md:w-[176px] md:h-[264px] rounded-md border-muted group-hover/item:scale-105 duration-300 transition-all ease-in-out"
src={`https://images.metahub.space/poster/small/${item.imdb_id}/img`}
/>
<div class="absolute top-2 left-2">
<Badge class="rounded-md bg-opacity-40 backdrop-blur-lg drop-shadow-lg">
{item.type === 'movie' ? 'Movie' : 'TV Show'}
</Badge>
<Carousel.Content class="flex flex-row h-full w-full">
{#each icebergItems[key] as item}
<Carousel.Item class="flex-none mr-2 min-w-0 max-w-max w-full h-full group/item">
<div class="flex flex-col w-full h-full max-w-[144px] md:max-w-[176px]">
<div class="relative h-full w-full">
<img
alt={item.imdb_id}
class="bg-cover bg-center h-[216px] w-[144px] md:w-[176px] md:h-[264px] rounded-md border-muted group-hover/item:scale-105 duration-300 transition-all ease-in-out"
src={`https://images.metahub.space/poster/small/${item.imdb_id}/img`}
/>
<div class="absolute top-2 left-2">
<Badge class="rounded-md bg-opacity-40 backdrop-blur-lg drop-shadow-lg">
{item.type === 'movie' ? 'Movie' : 'TV Show'}
</Badge>
</div>
</div>

<Dialog.Root
onOpenChange={async (open) => {
console.log(open);
if (open) {
await getExtendedData(item.item_id);
}
}}
>
<Dialog.Trigger>
<p
class="text-start text-sm mt-2 text-ellipsis line-clamp-1 group-hover/item:underline focus:underline"
>
{item.title}
</p>
</Dialog.Trigger>
<Dialog.Content>
<Dialog.Header>
<Dialog.Title>{item.title}</Dialog.Title>
<Dialog.Description class="flex flex-col gap-2">
<p class="text-muted-foreground text-sm">
Aired {formatDate(item.aired_at, 'short')}
</p>
<div
class="flex flex-wrap gap-2 w-full items-center justify-center md:justify-start"
>
{#each item.genres as genre}
<Badge variant="secondary">
{formatWords(genre)}
</Badge>
{/each}
</div>
</Dialog.Description>
</Dialog.Header>
{#if extendedDataLoading}
<div class="flex items-center gap-1 w-full justify-center">
<Loader2 class="animate-spin w-4 h-4" />
<p class="text-muted-foreground">Loading item data...</p>
</div>
{:else}
<div class="flex flex-col items-start">
<p>
The item was requested <span class="font-semibold"
>{formatDate(item.requested_at, 'long', true)}</span
>
by <span class="font-semibold">{item.requested_by}</span>.
</p>
{#if item.scraped_at}
<p>
Last scraped <span class="font-semibold"
>{formatDate(item.scraped_at, 'long', true)}</span
>
for a total of
<span class="font-semibold">{item.scraped_times}</span>
times.
</p>
{/if}
</div>
{/if}
</Dialog.Content>
</Dialog.Root>
<p class="text-muted-foreground text-xs mt-1">
{formatDate(item.aired_at, 'year')}
</p>
<p class="text-muted-foreground text-xs mt-1">
Requested {formatDate(item.requested_at, 'long', true)}
</p>
</div>
<p
class="text-start text-sm mt-2 text-ellipsis line-clamp-1 group-hover/item:underline focus:underline"
>
{item.title}
</p>
<p class="text-muted-foreground text-xs mt-1">
{formatDate(item.aired_at, 'year')}
</p>
<p class="text-muted-foreground text-xs mt-1">
Requested {formatDate(item.requested_at, 'long', true)}
</p>
</div>
</Carousel.Item>
{/each}
</Carousel.Content>
</Carousel.Root>
{/each}
</Carousel.Item>
{/each}
</Carousel.Content>
</Carousel.Root>
{/each}
{/if}
</div>
{:catch error}
<div class="flex flex-col items-center justify-center w-full h-full font-primary">
Expand Down
Loading