diff options
Diffstat (limited to 'src/routes/hololive/+page.svelte')
| -rw-r--r-- | src/routes/hololive/+page.svelte | 127 |
1 files changed, 127 insertions, 0 deletions
diff --git a/src/routes/hololive/+page.svelte b/src/routes/hololive/+page.svelte new file mode 100644 index 00000000..bd49bbf8 --- /dev/null +++ b/src/routes/hololive/+page.svelte @@ -0,0 +1,127 @@ +<script lang="ts"> + import { onMount } from 'svelte'; + import Message from '$lib/Loading/Message.svelte'; + import Skeleton from '$lib/Loading/Skeleton.svelte'; + + interface ParseResult { + lives: { + time: Date; + link: string; + videoId: string; + streamer: string; + livePreviewImage: string; + guests: string[]; + streaming: boolean; + }[]; + dict: Record<string, string>; + } + + let schedulePromise: Promise<Response>; + + onMount(() => (schedulePromise = fetch('/api/hololive'))); + + const typeSchedule = (schedule: any) => schedule as ParseResult; +</script> + +{#await schedulePromise} + <Message message="Loading schedule ..." /> + + <Skeleton grid={true} count={100} width="49%" height="16.25em" /> +{:then scheduleResponse} + {#if scheduleResponse} + {#await scheduleResponse.json()} + <Message message="Parsing schedule ..." /> + + <Skeleton grid={true} count={100} width="49%" height="16.25em" /> + {:then untypedSchedule} + {@const schedule = typeSchedule(untypedSchedule)} + + {#if schedule.lives.length === 0} + <Message message="No upcoming streams." /> + {/if} + + <div class="container"> + {#each schedule.lives + .filter((live) => { + const time = new Date(live.time); + + return time.getTime() > Date.now() - 12 * 60 * 60 * 1000 || time.getTime() > Date.now() || live.streaming; + }) + .sort((a, b) => { + if (a.streaming && !b.streaming) return -1; + + if (!a.streaming && b.streaming) return 1; + + return new Date(a.time).getTime() - new Date(b.time).getTime(); + }) as live} + <div class="stream card"> + <p> + [{#if live.streaming} + <b class="live">LIVE</b>{:else} + <span class="upcoming">Upcoming</span>{/if}] + <b>{live.streamer}</b> <span class="opaque">|</span> + {new Date(live.time).toLocaleString()} + {#if live.guests.length > 0} + <br /> + <small> + With {live.guests.join(', ').replace(/, ([^,]+)$/, ', & $1')} + </small> + {/if} + </p> + + <a href={live.link} class="preview"> + <img src={live.livePreviewImage} alt="Stream Preview" /> + </a> + </div> + {/each} + </div> + {:catch} + <Message message="Could not parse schedule. Please try again later." loader="ripple" /> + {/await} + {:else} + <Message message="Loading schedule ..." /> + + <Skeleton grid={true} count={100} width="49%" height="16.25em" /> + {/if} +{:catch} + <Message message="Could not load schedule. Please try again later." loader="ripple" /> +{/await} + +<style lang="scss"> + .preview { + // margin: 0.15rem; + + img { + border-radius: 8px; + height: 20vh; + transition: transform 0.3s ease; + } + + &:hover { + img { + position: relative; + z-index: 2; + transition: transform 0.3s ease; + transform: scale(1.05); + } + } + + display: flex; + justify-content: center; + align-items: center; + } + + .live { + color: var(--base0F); + } + + .upcoming { + color: var(--base0C); + } + + .container { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(22.5em, 1fr)); + gap: 0.5em; + } +</style> |