aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorFuwn <[email protected]>2024-02-17 16:32:59 -0800
committerFuwn <[email protected]>2024-02-17 16:32:59 -0800
commit7a306e433e343818e259eaf757e5c63af3d1c93b (patch)
tree8541753b1e96bac5c29307f231d84f990db2dab4 /src
parentfix(hololive): make space for pin icon (diff)
downloaddue.moe-7a306e433e343818e259eaf757e5c63af3d1c93b.tar.xz
due.moe-7a306e433e343818e259eaf757e5c63af3d1c93b.zip
refactor(hololive): move lives to component
Diffstat (limited to 'src')
-rw-r--r--src/lib/Hololive/Lives.svelte167
-rw-r--r--src/lib/Hololive/hololive.ts12
-rw-r--r--src/routes/hololive/+page.svelte174
3 files changed, 183 insertions, 170 deletions
diff --git a/src/lib/Hololive/Lives.svelte b/src/lib/Hololive/Lives.svelte
new file mode 100644
index 00000000..9c5b6b04
--- /dev/null
+++ b/src/lib/Hololive/Lives.svelte
@@ -0,0 +1,167 @@
+<script lang="ts">
+ import Message from '$lib/Loading/Message.svelte';
+ import root from '$lib/Utility/root';
+ import identity from '$stores/identity';
+ import locale from '$stores/locale';
+ import Icon from '@iconify/svelte';
+ import type { ParseResult } from './hololive';
+
+ export let schedule: ParseResult;
+ export let closestUpcomingPinnedStreams: Map<string, any>;
+ export let pinnedStreams: string[];
+ export let getPinnedStreams: () => void;
+
+ const pinStream = (streamer: string) =>
+ fetch(root(`/api/configuration/pin?stream=${encodeURIComponent(streamer)}`), {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json'
+ }
+ }).then(getPinnedStreams);
+</script>
+
+{#if schedule.lives.length === 0}
+ <Message message="No upcoming streams." loader="ripple" />
+{/if}
+
+<div class="container">
+ {#each schedule.lives
+ .filter((live) => {
+ try {
+ $locale().hololive.dateFormatter(new Date(live.time));
+
+ return true;
+ } catch {
+ return false;
+ }
+ })
+ .sort((a, b) => {
+ const now = Date.now();
+ const aTime = new Date(a.time).getTime();
+ const bTime = new Date(b.time).getTime();
+ const aIsLive = a.streaming;
+ const bIsLive = b.streaming;
+ const aIsClosestPinned = closestUpcomingPinnedStreams.get(a.streamer) === a;
+ const bIsClosestPinned = closestUpcomingPinnedStreams.get(b.streamer) === b;
+
+ if (aIsLive && pinnedStreams.includes(a.streamer)) return -1;
+ if (bIsLive && pinnedStreams.includes(b.streamer)) return 1;
+
+ if (aIsLive && !bIsLive) return -1;
+ if (bIsLive && !aIsLive) return 1;
+
+ if (aIsClosestPinned && !bIsClosestPinned) return -1;
+ if (bIsClosestPinned && !aIsClosestPinned) return 1;
+
+ if (aTime > now && !(aTime < now && !aIsLive)) return -1;
+ if (bTime > now && !(bTime < now && !bIsLive)) return 1;
+
+ return bTime - aTime;
+ }) as live}
+ <div class="stream card">
+ {#if $identity.id !== -2}
+ <div class="pin-icon">
+ <a
+ href={'#'}
+ on:click={(e) => {
+ e.preventDefault();
+ pinStream(live.streamer);
+ }}
+ >
+ <Icon
+ icon={`pajamas:thumbtack${pinnedStreams.includes(live.streamer) ? '-solid' : ''}`}
+ width="1em"
+ />
+ </a>
+ </div>
+ {/if}
+
+ <p class="stream-heading">
+ [{#if live.streaming}
+ <b class="live">{$locale().hololive.live}</b
+ >{:else if new Date(live.time).getTime() < Date.now()}
+ <span class="ended">{$locale().hololive.ended}</span>{:else}
+ <span class="upcoming">{$locale().hololive.upcoming}</span>{/if}]
+ <b>{live.streamer}</b> <span class="opaque">|</span>
+ {$locale().hololive.dateFormatter(new Date(live.time))}
+ {#if live.guests.length > 0}
+ <br />
+ <small>
+ {$locale().hololive.with}{live.guests
+ .join($locale().hololive.comma)
+ .replace(
+ new RegExp(
+ `${$locale().hololive.comma}([^${$locale().hololive.commaNoSpace}]+)$`,
+ 'g'
+ ),
+ `${$locale().hololive.comma}${$locale().hololive.ampersand}$1`
+ )}
+ </small>
+ {/if}
+ </p>
+
+ <a href={live.link} class="preview" target="_blank">
+ <img src={live.livePreviewImage} alt="Stream Thumbnail" />
+ </a>
+ </div>
+ {/each}
+</div>
+
+<style lang="scss">
+ .preview {
+ // margin: 0.15rem;
+
+ img {
+ border-radius: 8px;
+ height: 20vh;
+ object-fit: cover;
+ 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);
+ }
+
+ .ended {
+ color: var(--base0D);
+ }
+
+ .container {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(22.5em, 1fr));
+ gap: 0.5em;
+ }
+
+ .pin-icon {
+ position: absolute;
+ right: 0;
+ top: 0;
+ padding: 0.75rem;
+ }
+
+ .stream {
+ position: relative;
+ }
+
+ .stream-heading {
+ padding-right: 2em;
+ }
+</style>
diff --git a/src/lib/Hololive/hololive.ts b/src/lib/Hololive/hololive.ts
new file mode 100644
index 00000000..4ba7e46e
--- /dev/null
+++ b/src/lib/Hololive/hololive.ts
@@ -0,0 +1,12 @@
+export interface ParseResult {
+ lives: {
+ time: Date;
+ link: string;
+ videoId: string;
+ streamer: string;
+ livePreviewImage: string;
+ guests: string[];
+ streaming: boolean;
+ }[];
+ dict: Record<string, string>;
+}
diff --git a/src/routes/hololive/+page.svelte b/src/routes/hololive/+page.svelte
index 96fbea67..099a09fc 100644
--- a/src/routes/hololive/+page.svelte
+++ b/src/routes/hololive/+page.svelte
@@ -8,20 +8,8 @@
import locale from '$stores/locale';
import root from '$lib/Utility/root';
import identity from '$stores/identity';
- import Icon from '@iconify/svelte';
-
- interface ParseResult {
- lives: {
- time: Date;
- link: string;
- videoId: string;
- streamer: string;
- livePreviewImage: string;
- guests: string[];
- streaming: boolean;
- }[];
- dict: Record<string, string>;
- }
+ import Lives from '$lib/Hololive/Lives.svelte';
+ import type { ParseResult } from '$lib/Hololive/hololive';
let schedulePromise: Promise<Response>;
let pinnedStreams: string[] = [];
@@ -58,14 +46,6 @@
const typeSchedule = (schedule: any) => schedule as ParseResult;
- const pinStream = (streamer: string) =>
- fetch(root(`/api/configuration/pin?stream=${encodeURIComponent(streamer)}`), {
- method: 'PUT',
- headers: {
- 'Content-Type': 'application/json'
- }
- }).then(getPinnedStreams);
-
const getClosestUpcomingPinnedStreams = (schedule: ParseResult) => {
const now = Date.now();
let closestUpcomingPinnedStreams = new Map();
@@ -101,94 +81,7 @@
{@const schedule = typeSchedule(parseScheduleHtml(untypedSchedule))}
{@const closestUpcomingPinnedStreams = getClosestUpcomingPinnedStreams(schedule)}
- {#if schedule.lives.length === 0}
- <Message message="No upcoming streams." loader="ripple" />
- {/if}
-
- <div class="container">
- {#each schedule.lives
- .filter((live) => {
- try {
- $locale().hololive.dateFormatter(new Date(live.time));
-
- return true;
- } catch {
- return false;
- }
- })
- .sort((a, b) => {
- const now = Date.now();
- const aTime = new Date(a.time).getTime();
- const bTime = new Date(b.time).getTime();
- const aIsLive = a.streaming;
- const bIsLive = b.streaming;
- const aIsClosestPinned = closestUpcomingPinnedStreams.get(a.streamer) === a;
- const bIsClosestPinned = closestUpcomingPinnedStreams.get(b.streamer) === b;
-
- if (aIsLive && pinnedStreams.includes(a.streamer)) return -1;
- if (bIsLive && pinnedStreams.includes(b.streamer)) return 1;
-
- if (aIsLive && !bIsLive) return -1;
- if (bIsLive && !aIsLive) return 1;
-
- if (aIsClosestPinned && !bIsClosestPinned) return -1;
- if (bIsClosestPinned && !aIsClosestPinned) return 1;
-
- if (aTime > now && !(aTime < now && !aIsLive)) return -1;
- if (bTime > now && !(bTime < now && !bIsLive)) return 1;
-
- return bTime - aTime;
- }) as live}
- <div class="stream card">
- {#if $identity.id !== -2}
- <div class="pin-icon">
- <a
- href={'#'}
- on:click={(e) => {
- e.preventDefault();
- pinStream(live.streamer);
- }}
- >
- <Icon
- icon={`pajamas:thumbtack${
- pinnedStreams.includes(live.streamer) ? '-solid' : ''
- }`}
- width="1em"
- />
- </a>
- </div>
- {/if}
-
- <p class="stream-heading">
- [{#if live.streaming}
- <b class="live">{$locale().hololive.live}</b
- >{:else if new Date(live.time).getTime() < Date.now()}
- <span class="ended">{$locale().hololive.ended}</span>{:else}
- <span class="upcoming">{$locale().hololive.upcoming}</span>{/if}]
- <b>{live.streamer}</b> <span class="opaque">|</span>
- {$locale().hololive.dateFormatter(new Date(live.time))}
- {#if live.guests.length > 0}
- <br />
- <small>
- {$locale().hololive.with}{live.guests
- .join($locale().hololive.comma)
- .replace(
- new RegExp(
- `${$locale().hololive.comma}([^${$locale().hololive.commaNoSpace}]+)$`,
- 'g'
- ),
- `${$locale().hololive.comma}${$locale().hololive.ampersand}$1`
- )}
- </small>
- {/if}
- </p>
-
- <a href={live.link} class="preview" target="_blank">
- <img src={live.livePreviewImage} alt="Stream Thumbnail" />
- </a>
- </div>
- {/each}
- </div>
+ <Lives {schedule} {closestUpcomingPinnedStreams} {pinnedStreams} {getPinnedStreams} />
{:catch}
<Message loader="ripple" slot>
{$locale().hololive.parseError}
@@ -201,67 +94,8 @@
<Skeleton grid={true} count={100} width="49%" height="16.25em" />
{/if}
{:catch}
- <Message message="" loader="ripple" slot>
+ <Message loader="ripple" slot>
{$locale().hololive.loadError} Please
<a href={'#'} on:click={() => location.reload()}>try again</a> later.
</Message>
{/await}
-
-<style lang="scss">
- .preview {
- // margin: 0.15rem;
-
- img {
- border-radius: 8px;
- height: 20vh;
- object-fit: cover;
- 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);
- }
-
- .ended {
- color: var(--base0D);
- }
-
- .container {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(22.5em, 1fr));
- gap: 0.5em;
- }
-
- .pin-icon {
- position: absolute;
- right: 0;
- top: 0;
- padding: 0.75rem;
- }
-
- .stream {
- position: relative;
- }
-
- .stream-heading {
- padding-right: 2em;
- }
-</style>