Partial improvements to typescript types
This commit is contained in:
1
.idea/inspectionProfiles/Project_Default.xml
generated
1
.idea/inspectionProfiles/Project_Default.xml
generated
@@ -1,6 +1,7 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="CheckEmptyScriptTag" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
</profile>
|
||||
</component>
|
||||
7
package-lock.json
generated
7
package-lock.json
generated
@@ -30,6 +30,7 @@
|
||||
"@sveltejs/kit": "^1.5.0",
|
||||
"@types/axios": "^0.14.0",
|
||||
"@types/cookie": "^0.5.1",
|
||||
"@types/node": "^20.3.3",
|
||||
"@typescript-eslint/eslint-plugin": "^5.45.0",
|
||||
"@typescript-eslint/parser": "^5.45.0",
|
||||
"autoprefixer": "^10.4.14",
|
||||
@@ -2938,9 +2939,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.2.6.tgz",
|
||||
"integrity": "sha512-GQBWUtGoefMEOx/vu+emHEHU5aw6JdDoEtZhoBrHFPZbA/YNRFfN996XbBASEWdvmLSLyv9FKYppYGyZjCaq/g==",
|
||||
"version": "20.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.3.tgz",
|
||||
"integrity": "sha512-wheIYdr4NYML61AjC8MKj/2jrR/kDQri/CIpVoZwldwhnIrD/j9jIU5bJ8yBKuB2VhpFV7Ab6G2XkBjv9r9Zzw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/pug": {
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
"@sveltejs/kit": "^1.5.0",
|
||||
"@types/axios": "^0.14.0",
|
||||
"@types/cookie": "^0.5.1",
|
||||
"@types/node": "^20.3.3",
|
||||
"@typescript-eslint/eslint-plugin": "^5.45.0",
|
||||
"@typescript-eslint/parser": "^5.45.0",
|
||||
"autoprefixer": "^10.4.14",
|
||||
|
||||
30
src/routes/+layout.server.ts
Normal file
30
src/routes/+layout.server.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import type { LayoutServerLoad } from './$types';
|
||||
import {
|
||||
RADARR_API_KEY,
|
||||
RADARR_BASE_URL,
|
||||
SONARR_API_KEY,
|
||||
SONARR_BASE_URL
|
||||
} from '$env/static/private';
|
||||
import { PUBLIC_JELLYFIN_API_KEY, PUBLIC_JELLYFIN_URL } from '$env/static/public';
|
||||
|
||||
export const load = (async () => {
|
||||
const isApplicationSetUp =
|
||||
!!RADARR_API_KEY &&
|
||||
!!RADARR_BASE_URL &&
|
||||
!!SONARR_API_KEY &&
|
||||
!!SONARR_BASE_URL &&
|
||||
!!PUBLIC_JELLYFIN_API_KEY &&
|
||||
!!PUBLIC_JELLYFIN_URL;
|
||||
|
||||
return {
|
||||
isApplicationSetUp,
|
||||
missingEnvironmentVariables: {
|
||||
RADARR_API_KEY: !RADARR_API_KEY,
|
||||
RADARR_BASE_URL: !RADARR_BASE_URL,
|
||||
SONARR_API_KEY: !SONARR_API_KEY,
|
||||
SONARR_BASE_URL: !SONARR_BASE_URL,
|
||||
PUBLIC_JELLYFIN_API_KEY: !PUBLIC_JELLYFIN_API_KEY,
|
||||
PUBLIC_JELLYFIN_URL: !PUBLIC_JELLYFIN_URL
|
||||
}
|
||||
};
|
||||
}) satisfies LayoutServerLoad;
|
||||
@@ -3,29 +3,26 @@
|
||||
import Navbar from './components/Navbar/Navbar.svelte';
|
||||
import VideoPlayer from './components/VideoPlayer/VideoPlayer.svelte';
|
||||
import { setContext } from 'svelte';
|
||||
import { writable } from 'svelte/store';
|
||||
import type { LayoutData } from './$types';
|
||||
import { initialPlayerState } from './components/VideoPlayer/VideoPlayer';
|
||||
|
||||
let playerState = writable({ visible: false, jellyfinId: '' });
|
||||
setContext('player', initialPlayerState);
|
||||
|
||||
setContext('player', {
|
||||
playerState,
|
||||
close: () => {
|
||||
playerState.set({ visible: false, jellyfinId: '' });
|
||||
},
|
||||
streamJellyfinId: (id: string) => {
|
||||
playerState.set({ visible: true, jellyfinId: id });
|
||||
}
|
||||
});
|
||||
export let data: LayoutData;
|
||||
</script>
|
||||
|
||||
<div class="app">
|
||||
<Navbar />
|
||||
<main>
|
||||
<slot />
|
||||
</main>
|
||||
<VideoPlayer />
|
||||
{#if data.isApplicationSetUp}
|
||||
<div class="app">
|
||||
<Navbar />
|
||||
<main>
|
||||
<slot />
|
||||
</main>
|
||||
<VideoPlayer />
|
||||
|
||||
<!-- <footer>-->
|
||||
<!-- <p>visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to learn SvelteKit</p>-->
|
||||
<!-- </footer>-->
|
||||
</div>
|
||||
<!-- <footer>-->
|
||||
<!-- <p>visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to learn SvelteKit</p>-->
|
||||
<!-- </footer>-->
|
||||
</div>
|
||||
{:else}
|
||||
<div>Application not set up</div>
|
||||
{/if}
|
||||
|
||||
@@ -1,19 +1,15 @@
|
||||
<script lang="ts">
|
||||
import type { Genre, TmdbMovie, TmdbMovieFull } from '$lib/tmdb-api';
|
||||
import { formatGenres, formatMinutesToTime } from '$lib/utils';
|
||||
import { formatMinutesToTime } from '$lib/utils';
|
||||
import classNames from 'classnames';
|
||||
import { TMDB_IMAGES } from '$lib/constants';
|
||||
import { onMount } from 'svelte';
|
||||
import { fetchTmdbMovie, fetchTmdbMovieImages, TmdbApi } from '$lib/tmdb-api';
|
||||
import CardPlaceholder from './CardPlaceholder.svelte';
|
||||
import { Clock, Star, StarFilled } from 'radix-icons-svelte';
|
||||
import { Clock, Star } from 'radix-icons-svelte';
|
||||
|
||||
export let tmdbId;
|
||||
export let title;
|
||||
export let tmdbId: string;
|
||||
export let title: string;
|
||||
export let genres: string[];
|
||||
export let runtimeMinutes;
|
||||
export let completionTime;
|
||||
export let backdropUrl;
|
||||
export let runtimeMinutes: number;
|
||||
export let completionTime = '';
|
||||
export let backdropUrl: string;
|
||||
export let rating: number;
|
||||
|
||||
export let available = true;
|
||||
@@ -33,6 +29,7 @@
|
||||
})}
|
||||
>
|
||||
<div style={'width: ' + progress + '%'} class="h-[2px] bg-zinc-200 bottom-0 absolute z-[1]" />
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
on:click={() => window.open('/movie/' + tmdbId, '_self')}
|
||||
class="h-full w-full opacity-0 hover:opacity-100 transition-opacity flex flex-col justify-between cursor-pointer p-2 px-3 relative z-[1] peer"
|
||||
|
||||
@@ -14,14 +14,15 @@
|
||||
import { getContext, onDestroy } from 'svelte';
|
||||
import { PUBLIC_JELLYFIN_URL } from '$env/static/public';
|
||||
import getDeviceProfile from '$lib/jellyfin/playback-profiles';
|
||||
import type { PlayerState, PlayerStateValue } from './VideoPlayer';
|
||||
|
||||
const { playerState, close } = getContext('player');
|
||||
const { playerState, close }: PlayerState = getContext('player');
|
||||
|
||||
let video: HTMLVideoElement;
|
||||
|
||||
let stopCallback;
|
||||
let stopCallback: () => void;
|
||||
|
||||
let progressInterval;
|
||||
let progressInterval: ReturnType<typeof setInterval>;
|
||||
onDestroy(() => clearInterval(progressInterval));
|
||||
|
||||
const fetchPlaybackInfo = (itemId: string) =>
|
||||
@@ -35,13 +36,14 @@
|
||||
|
||||
hls.loadSource(PUBLIC_JELLYFIN_URL + uri);
|
||||
hls.attachMedia(video);
|
||||
video.play().then(() => {
|
||||
console.log(item);
|
||||
if (item?.UserData?.PlaybackPositionTicks) {
|
||||
console.log('Setting time');
|
||||
video.currentTime = item?.UserData?.PlaybackPositionTicks / 10_000_000;
|
||||
}
|
||||
});
|
||||
video
|
||||
.play()
|
||||
.then(() => video.requestFullscreen())
|
||||
.then(() => {
|
||||
if (item?.UserData?.PlaybackPositionTicks) {
|
||||
video.currentTime = item?.UserData?.PlaybackPositionTicks / 10_000_000;
|
||||
}
|
||||
});
|
||||
await reportJellyfinPlaybackStarted(itemId, sessionId, mediaSourceId);
|
||||
progressInterval = setInterval(() => {
|
||||
reportJellyfinPlaybackProgress(
|
||||
@@ -66,7 +68,7 @@
|
||||
}
|
||||
|
||||
let uiVisible = false;
|
||||
let timeout;
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
function handleMouseMove() {
|
||||
uiVisible = true;
|
||||
clearTimeout(timeout);
|
||||
@@ -75,7 +77,7 @@
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
let state;
|
||||
let state: PlayerStateValue;
|
||||
playerState.subscribe((s) => (state = s));
|
||||
|
||||
$: {
|
||||
|
||||
17
src/routes/components/VideoPlayer/VideoPlayer.ts
Normal file
17
src/routes/components/VideoPlayer/VideoPlayer.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
const initialValue = { visible: false, jellyfinId: '' };
|
||||
export const playerState = writable(initialValue);
|
||||
|
||||
export const initialPlayerState = {
|
||||
playerState,
|
||||
close: () => {
|
||||
playerState.set({ visible: false, jellyfinId: '' });
|
||||
},
|
||||
streamJellyfinId: (id: string) => {
|
||||
playerState.set({ visible: true, jellyfinId: id });
|
||||
}
|
||||
};
|
||||
|
||||
export type PlayerState = typeof initialPlayerState;
|
||||
export type PlayerStateValue = typeof initialValue;
|
||||
@@ -3,6 +3,11 @@ import { RadarrApi } from '$lib/radarr/radarr';
|
||||
import type { CardProps } from '../components/Card/card';
|
||||
import { fetchCardProps } from '../components/Card/card';
|
||||
|
||||
interface DownloadingCardProps extends CardProps {
|
||||
progress: number;
|
||||
completionTime: string;
|
||||
}
|
||||
|
||||
export const load = (() => {
|
||||
const [downloading, available, unavailable] = getLibraryItems();
|
||||
|
||||
@@ -22,7 +27,7 @@ export const load = (() => {
|
||||
|
||||
async function getLibraryInfo(): Promise<any> {}
|
||||
|
||||
function getLibraryItems() {
|
||||
function getLibraryItems(): [Promise<DownloadingCardProps[]>, Promise<CardProps[]>, Promise<CardProps[]>] {
|
||||
const radarrMovies = RadarrApi.get('/api/v3/movie', {
|
||||
params: {}
|
||||
}).then((r) => r.data);
|
||||
@@ -71,17 +76,22 @@ function getLibraryItems() {
|
||||
);
|
||||
});
|
||||
|
||||
const downloading: Promise<CardProps[]> = downloadingRadarrMovies.then(async (movies) => {
|
||||
return Promise.all(
|
||||
movies
|
||||
?.filter((m) => m?.movie?.tmdbId)
|
||||
?.map(async (m) => ({
|
||||
...(await fetchCardProps(m.movie as any)),
|
||||
progress: m.sizeleft && m.size ? ((m.size - m.sizeleft) / m.size) * 100 : 0,
|
||||
completionTime: m.estimatedCompletionTime
|
||||
})) || []
|
||||
);
|
||||
});
|
||||
const downloading: Promise<DownloadingCardProps[]> = downloadingRadarrMovies.then(
|
||||
async (movies) => {
|
||||
return Promise.all(
|
||||
movies
|
||||
?.filter((m) => m?.movie?.tmdbId)
|
||||
?.map(
|
||||
async (m) =>
|
||||
({
|
||||
...(await fetchCardProps(m.movie as any)),
|
||||
progress: m.sizeleft && m.size ? ((m.size - m.sizeleft) / m.size) * 100 : 0,
|
||||
completionTime: m.estimatedCompletionTime
|
||||
} as DownloadingCardProps)
|
||||
) || []
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
return [downloading, available, unavailable];
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<script lang="ts">
|
||||
import type { PageData } from './$types';
|
||||
import Card from '../components/Card/Card.svelte';
|
||||
import { TMDB_IMAGES } from '$lib/constants.js';
|
||||
import CardPlaceholder from '../components/Card/CardPlaceholder.svelte';
|
||||
import { formatSize } from '$lib/utils.js';
|
||||
export let data: PageData;
|
||||
@@ -11,8 +10,8 @@
|
||||
const headerStyle = 'uppercase tracking-widest font-bold text-center mt-2';
|
||||
</script>
|
||||
|
||||
<div class="bg-black pt-24 pb-8 px-8">
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 items-center justify-center gap-4">
|
||||
<div class="pt-24 pb-8 px-8">
|
||||
<div class="bg-black grid grid-cols-1 lg:grid-cols-2 items-center justify-center gap-4">
|
||||
<div class="bg-highlight-dim relative w-full m-auto p-3 px-4 rounded-xl overflow-hidden">
|
||||
<div class="absolute left-0 inset-y-0 w-[70%] bg-[#ffffff22]" />
|
||||
<div class="relative z-[1] flex justify-between items-center">
|
||||
@@ -62,54 +61,46 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="py-8 backdrop-blur-2xl bg-darken px-8 flex flex-col gap-4">
|
||||
<!-- Contains all the titles available locally, the ones already watched previously (greyed out at the-->
|
||||
<!-- bottom), and the ones that are in some sort of watchlist and not available via any source.-->
|
||||
<div class="py-8 backdrop-blur-2xl bg-stone-900 px-8 flex flex-col gap-4">
|
||||
<!-- Contains all the titles available locally, the ones already watched previously (greyed out at the-->
|
||||
<!-- bottom), and the ones that are in some sort of watchlist and not available via any source.-->
|
||||
|
||||
{#await Promise.all( [data.streamed.available, data.streamed.unavailable, data.streamed.downloading] )}
|
||||
{#await Promise.all( [data.streamed.available, data.streamed.unavailable, data.streamed.downloading] )}
|
||||
<div class={posterGridStyle}>
|
||||
{#each [...Array(20).keys()] as index (index)}
|
||||
<CardPlaceholder {index} />
|
||||
{/each}
|
||||
</div>
|
||||
{:then [available, unavailable, downloading]}
|
||||
{#if downloading.length > 0}
|
||||
<h1 class={headerStyle}>Downloading</h1>
|
||||
<div class={posterGridStyle}>
|
||||
{#each [...Array(20).keys()] as index (index)}
|
||||
<CardPlaceholder {index} />
|
||||
{#each downloading as movie (movie)}
|
||||
<Card {...movie} progress={movie.progress} progressType="downloading" available={false} />
|
||||
{/each}
|
||||
</div>
|
||||
{:then [available, unavailable, downloading]}
|
||||
{#if downloading.length > 0}
|
||||
<h1 class={headerStyle}>Downloading</h1>
|
||||
<div class={posterGridStyle}>
|
||||
{#each downloading as movie (movie.tmdbId)}
|
||||
<Card
|
||||
{...movie}
|
||||
progress={movie.progress}
|
||||
progressType="downloading"
|
||||
available={false}
|
||||
type="download"
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#if available.length > 0}
|
||||
<h1 class={headerStyle}>Available</h1>
|
||||
<div class={posterGridStyle}>
|
||||
{#each available as movie (movie.tmdbId)}
|
||||
<Card {...movie} randomProgress={false} />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{#if available.length > 0}
|
||||
<h1 class={headerStyle}>Available</h1>
|
||||
<div class={posterGridStyle}>
|
||||
{#each available as movie (movie.tmdbId)}
|
||||
<Card {...movie} randomProgress={false} />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if unavailable.length > 0}
|
||||
<h1 class={headerStyle}>Unavailable</h1>
|
||||
<div class={posterGridStyle}>
|
||||
{#each unavailable as movie (movie.tmdbId)}
|
||||
<Card {...movie} available={false} />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{#if unavailable.length > 0}
|
||||
<h1 class={headerStyle}>Unavailable</h1>
|
||||
<div class={posterGridStyle}>
|
||||
{#each unavailable as movie (movie.tmdbId)}
|
||||
<Card {...movie} available={false} />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if watched.length > 0}
|
||||
<h1 class={headerStyle}>Watched</h1>
|
||||
{/if}
|
||||
{/await}
|
||||
</div>
|
||||
{#if watched.length > 0}
|
||||
<h1 class={headerStyle}>Watched</h1>
|
||||
{/if}
|
||||
{/await}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user