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