diff --git a/.idea/.gitignore b/.idea/.gitignore index 13566b8..a9d7db9 100644 --- a/.idea/.gitignore +++ b/.idea/.gitignore @@ -6,3 +6,5 @@ # Datasource local storage ignored files /dataSources/ /dataSources.local.xml +# GitHub Copilot persisted chat sessions +/copilot/chatSessions diff --git a/src/App.svelte b/src/App.svelte index fe6574a..6aafe93 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -8,13 +8,13 @@ import { Bookmark, CardStack, Gear, Laptop, MagnifyingGlass } from 'radix-icons-svelte'; import classNames from 'classnames'; import type { Readable } from 'svelte/store'; - import SeriesPage from './lib/pages/SeriesPage.svelte'; + import BrowseSeriesPage from './lib/pages/BrowseSeriesPage.svelte'; import MoviesPage from './lib/pages/MoviesPage.svelte'; import LibraryPage from './lib/pages/LibraryPage.svelte'; import ManagePage from './lib/pages/ManagePage.svelte'; import SearchPage from './lib/pages/SearchPage.svelte'; + import SeriesPage from './lib/pages/SeriesPage.svelte'; - Selectable.focusedObject.subscribe((s) => console.log('FocusedObject', s)); let mainContent: Selectable; onMount(() => { @@ -68,8 +68,8 @@ - - + + @@ -83,6 +83,10 @@ + + +
404
+
diff --git a/src/Container.svelte b/src/Container.svelte index fe380f7..407d192 100644 --- a/src/Container.svelte +++ b/src/Container.svelte @@ -1,7 +1,6 @@ + + + + {#if $contextMenu === id} + + {/if} + + + + + +
{ + if (anchored) { + e.stopPropagation(); + handleOpen(e); + } + }} +> + +
+ +{#if $contextMenu === id} + {#key fixedPosition} +
windowWidth / 2 ? menu?.clientWidth : 0) + }px; top: ${ + fixedPosition.y - (fixedPosition.y > windowHeight / 2 ? menu?.clientHeight : 0) + }px;` + : menu?.getBoundingClientRect()?.left > windowWidth / 2 + ? `right: 0;${fixedPosition.y > windowHeight / 2 ? 'bottom: 100%;' : ''}` + : `left: 0;${fixedPosition.y > windowHeight / 2 ? 'bottom: 100%;' : ''}`} + bind:this={menu} + in:fly|global={{ y: 5, duration: 100, delay: anchored ? 0 : 100 }} + out:fly|global={{ y: 5, duration: 100 }} + > + + {#if heading} +

+ {heading} +

+ {/if} +
+ + +
close()}> + +
+
+ {/key} +{/if} diff --git a/src/lib/components/ContextMenu/ContextMenu.ts b/src/lib/components/ContextMenu/ContextMenu.ts new file mode 100644 index 0000000..b02f190 --- /dev/null +++ b/src/lib/components/ContextMenu/ContextMenu.ts @@ -0,0 +1,17 @@ +import { writable } from 'svelte/store'; + +function createContextMenu() { + const visibleItem = writable(null); + + return { + subscribe: visibleItem.subscribe, + show: (item: symbol) => { + visibleItem.set(item); + }, + hide: () => { + visibleItem.set(null); + } + }; +} + +export const contextMenu = createContextMenu(); diff --git a/src/lib/components/ContextMenu/ContextMenuButton.svelte b/src/lib/components/ContextMenu/ContextMenuButton.svelte new file mode 100644 index 0000000..8c66510 --- /dev/null +++ b/src/lib/components/ContextMenu/ContextMenuButton.svelte @@ -0,0 +1,12 @@ + + +
+ + + + +
diff --git a/src/lib/components/ContextMenu/ContextMenuDivider.svelte b/src/lib/components/ContextMenu/ContextMenuDivider.svelte new file mode 100644 index 0000000..1018027 --- /dev/null +++ b/src/lib/components/ContextMenu/ContextMenuDivider.svelte @@ -0,0 +1 @@ +
diff --git a/src/lib/components/ContextMenu/ContextMenuItem.svelte b/src/lib/components/ContextMenu/ContextMenuItem.svelte new file mode 100644 index 0000000..7a6dc47 --- /dev/null +++ b/src/lib/components/ContextMenu/ContextMenuItem.svelte @@ -0,0 +1,17 @@ + + + diff --git a/src/lib/components/ContextMenu/LibraryItemContextItems.svelte b/src/lib/components/ContextMenu/LibraryItemContextItems.svelte new file mode 100644 index 0000000..d42df96 --- /dev/null +++ b/src/lib/components/ContextMenu/LibraryItemContextItems.svelte @@ -0,0 +1,71 @@ + + + + Mark as watched + + + Mark as unwatched + + + + Open in Jellyfin + +{#if type === 'movie'} + window.open($settings.radarr.baseUrl + '/movie/' + radarrMovie?.tmdbId)} + > + Open in Radarr + +{:else} + window.open($settings.sonarr.baseUrl + '/series/' + sonarrSeries?.titleSlug)} + > + Open in Sonarr + +{/if} + window.open(`https://www.themoviedb.org/${type}/${tmdbId}`)}> + Open in TMDB + diff --git a/src/lib/components/ContextMenu/SelectableContextMenuItem.svelte b/src/lib/components/ContextMenu/SelectableContextMenuItem.svelte new file mode 100644 index 0000000..ccbba36 --- /dev/null +++ b/src/lib/components/ContextMenu/SelectableContextMenuItem.svelte @@ -0,0 +1,22 @@ + + + +
+ +
+ +
+
+
diff --git a/src/lib/components/Poster.svelte b/src/lib/components/Poster.svelte index 5072305..30c0c9c 100644 --- a/src/lib/components/Poster.svelte +++ b/src/lib/components/Poster.svelte @@ -6,6 +6,8 @@ import LazyImg from './LazyImg.svelte'; import { Star } from 'radix-icons-svelte'; import type { TitleType } from '../types'; + import Container from '../../Container.svelte'; + import { useNavigate } from 'svelte-navigator'; export let tmdbId: number | undefined = undefined; export let tvdbId: number | undefined = undefined; @@ -22,9 +24,11 @@ export let shadow = false; export let size: 'dynamic' | 'md' | 'lg' | 'sm' = 'md'; export let orientation: 'portrait' | 'landscape' = 'landscape'; + + const navigate = useNavigate(); -
{/if} - + diff --git a/src/lib/components/VideoPlayer/Slider.svelte b/src/lib/components/VideoPlayer/Slider.svelte new file mode 100644 index 0000000..9191763 --- /dev/null +++ b/src/lib/components/VideoPlayer/Slider.svelte @@ -0,0 +1,47 @@ + + +
+
+
+ +
+ + +
+
+ +
+
+ + +
diff --git a/src/lib/components/VideoPlayer/VideoPlayer.svelte b/src/lib/components/VideoPlayer/VideoPlayer.svelte new file mode 100644 index 0000000..7a7cd5d --- /dev/null +++ b/src/lib/components/VideoPlayer/VideoPlayer.svelte @@ -0,0 +1,500 @@ + + + + + + + + + + + + + + + + +
handleUserInteraction(false)} + on:touchend|preventDefault={() => handleUserInteraction(true)} + in:fade|global={{ duration: 500, delay: 1200, easing: linear }} +> + +
+ + + + + + + + + diff --git a/src/lib/components/VideoPlayer/VideoPlayer.ts b/src/lib/components/VideoPlayer/VideoPlayer.ts new file mode 100644 index 0000000..96e2cb6 --- /dev/null +++ b/src/lib/components/VideoPlayer/VideoPlayer.ts @@ -0,0 +1,25 @@ +import { writable } from 'svelte/store'; +import { modalStack } from '../../stores/modal.store'; +import VideoPlayer from './VideoPlayer.svelte'; +import { jellyfinItemsStore } from '../../stores/data.store'; + +const initialValue = { visible: false, jellyfinId: '' }; +export type PlayerStateValue = typeof initialValue; + +function createPlayerState() { + const store = writable(initialValue); + + return { + ...store, + streamJellyfinId: (id: string) => { + store.set({ visible: true, jellyfinId: id }); + modalStack.create(VideoPlayer, {}); // FIXME + }, + close: () => { + store.set({ visible: false, jellyfinId: '' }); + jellyfinItemsStore.refresh(); + } + }; +} + +export const playerState = createPlayerState(); diff --git a/src/lib/pages/BrowseSeriesPage.svelte b/src/lib/pages/BrowseSeriesPage.svelte new file mode 100644 index 0000000..6be2528 --- /dev/null +++ b/src/lib/pages/BrowseSeriesPage.svelte @@ -0,0 +1,107 @@ + + + + +
+ {$_('discover.streamingNow')} +
+ {#await fetchNowStreaming()} + + {:then props} + {#each props as prop (prop.tmdbId)} + + + + {/each} + {/await} +
+ + + + + + + + + + + + + + + + + + +
diff --git a/src/lib/pages/SeriesPage.svelte b/src/lib/pages/SeriesPage.svelte index bd9cf41..7ab4750 100644 --- a/src/lib/pages/SeriesPage.svelte +++ b/src/lib/pages/SeriesPage.svelte @@ -1,81 +1,15 @@ - -
- {$_('discover.streamingNow')} + +
+ Series page for id: {id}
- {#await fetchNowStreaming()} - - {:then props} - {#each props as prop (prop.tmdbId)} - - - - {/each} - {/await} - +
+ This is something + history.back()}>Go back diff --git a/src/lib/selectable.ts b/src/lib/selectable.ts index 7aa4c2f..086c9e4 100644 --- a/src/lib/selectable.ts +++ b/src/lib/selectable.ts @@ -66,12 +66,31 @@ export class Selectable { this.children[get(this.focusIndex)]?.focus(); } else if (this.htmlElement) { this.htmlElement.focus({ preventScroll: true }); - this.htmlElement.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'nearest' }); + // this.htmlElement.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'nearest' }); + this.scrollIntoView(50); Selectable.focusedObject.set(this); this.updateFocusIndex(); } } + scrollIntoView(offset = 0, direction: Direction = 'left') { + if (this.htmlElement) { + const boundingRect = this.htmlElement.getBoundingClientRect(); + const offsetParent = this.htmlElement.offsetParent as HTMLElement; + + if (offsetParent) { + const left = this.htmlElement.offsetLeft - offset; + + console.log(boundingRect); + console.log('Scrolling to left: ', left); + offsetParent.scrollTo({ + left, + behavior: 'smooth' + }); + } + } + } + updateFocusIndex(selectable?: Selectable) { if (selectable) { const index = this.children.indexOf(selectable); @@ -191,7 +210,7 @@ export class Selectable { } _unmountContainer() { - console.log('Unmounting selectable', this); + // console.log('Unmounting selectable', this); const isFocusedWithin = get(this.hasFocusWithin); if (this.htmlElement) { @@ -214,7 +233,7 @@ export class Selectable { const selectable = _selectable || new Selectable().setDirection(flowDirection); return (htmlElement: HTMLElement) => { - console.log('Registering', htmlElement, selectable); + // console.log('Registering', htmlElement, selectable); selectable.setHtmlElement(htmlElement); return { @@ -274,6 +293,12 @@ export class Selectable { private shouldFocusByDefault(): boolean { return this.focusByDefault || this.parent?.shouldFocusByDefault() || false; } + + click() { + if (this.htmlElement) { + this.htmlElement.click(); + } + } } export function handleKeyboardNavigation(event: KeyboardEvent) { @@ -300,5 +325,7 @@ export function handleKeyboardNavigation(event: KeyboardEvent) { if (Selectable.focusLeft()) event.preventDefault(); } else if (event.key === 'ArrowRight') { if (Selectable.focusRight()) event.preventDefault(); + } else if (event.key === 'Enter') { + currentlyFocusedObject.click(); } } diff --git a/src/lib/stores/settings.store.ts b/src/lib/stores/settings.store.ts index 54914ce..89b6b9e 100644 --- a/src/lib/stores/settings.store.ts +++ b/src/lib/stores/settings.store.ts @@ -55,9 +55,9 @@ export const defaultSettings: SettingsValues = { rootFolderPath: '' }, jellyfin: { - apiKey: null, - baseUrl: null, - userId: null + apiKey: 'ff526980723144a095f560fc2975657b', + baseUrl: 'http://192.168.0.129:8096', + userId: '75dcb061c9404115a7acdc893ea6bbbc' } }; diff --git a/tizen/.gitignore b/tizen/.gitignore index d0d36e9..e397205 100644 --- a/tizen/.gitignore +++ b/tizen/.gitignore @@ -1,2 +1,3 @@ .sign/* -*.wgt \ No newline at end of file +*.wgt +.buildResult/ diff --git a/tsconfig.json b/tsconfig.json index 4be2476..6507d24 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,9 +14,9 @@ "allowJs": true, "checkJs": true, "isolatedModules": true, - "paths": { - "$lib/*": ["src/lib/*"], - } +// "paths": { +// "$lib/*": ["src/lib/*"], +// } }, "include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"], "references": [{ "path": "./tsconfig.node.json" }]