diff options
| author | Fuwn <[email protected]> | 2024-02-17 16:32:59 -0800 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2024-02-17 16:32:59 -0800 |
| commit | 7a306e433e343818e259eaf757e5c63af3d1c93b (patch) | |
| tree | 8541753b1e96bac5c29307f231d84f990db2dab4 /src | |
| parent | fix(hololive): make space for pin icon (diff) | |
| download | due.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.svelte | 167 | ||||
| -rw-r--r-- | src/lib/Hololive/hololive.ts | 12 | ||||
| -rw-r--r-- | src/routes/hololive/+page.svelte | 174 |
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> |