Merge pull request #796 from Materialious/update/svelte-5

Update/svelte 5
This commit is contained in:
Ward
2025-02-05 18:51:27 +13:00
committed by GitHub
41 changed files with 2793 additions and 2265 deletions
+22
View File
@@ -124,6 +124,17 @@
<option name="screenX" value="2220" />
<option name="screenY" value="1080" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
<option name="codename" value="dm2q" />
<option name="id" value="dm2q" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="S23 Plus" />
<option name="screenDensity" value="450" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
@@ -267,6 +278,17 @@
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="motorola" />
<option name="codename" value="maui" />
<option name="id" value="maui" />
<option name="manufacturer" value="Motorola" />
<option name="name" value="moto g play - 2023" />
<option name="screenDensity" value="280" />
<option name="screenX" value="720" />
<option name="screenY" value="1600" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="31" />
<option name="brand" value="google" />
+2 -2
View File
@@ -7,8 +7,8 @@ android {
applicationId "us.materialio.app"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 86
versionName "1.7.4"
versionCode 87
versionName "1.7.5"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
+6 -6
View File
@@ -1,12 +1,12 @@
{
"name": "Materialious",
"version": "1.7.4",
"version": "1.7.5",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "Materialious",
"version": "1.7.4",
"version": "1.7.5",
"license": "MIT",
"dependencies": {
"@capacitor-community/electron": "^5.0.0",
@@ -19,7 +19,7 @@
"jsonfile": "^6.1.0"
},
"devDependencies": {
"electron": "^34.0.1",
"electron": "^34.0.2",
"electron-builder": "^25.1.8",
"electron-rebuild": "^3.2.9",
"typescript": "^5.7.3"
@@ -2358,9 +2358,9 @@
}
},
"node_modules/electron": {
"version": "34.0.1",
"resolved": "https://registry.npmjs.org/electron/-/electron-34.0.1.tgz",
"integrity": "sha512-aArw5tAM80i3CKwEREnyZSM1SkARf5Jd1yBMTIdOL4pB1M+p/oDeyWSFI9Dl+vujyfJKiK4SS5+j19wna1onMw==",
"version": "34.0.2",
"resolved": "https://registry.npmjs.org/electron/-/electron-34.0.2.tgz",
"integrity": "sha512-u3F+DSUlg9NaGS+9qnYmSRN8VjAnc3LJDDk1ye1uISJnh4gjG76y3681qLowsPMx4obvCP2eBINnmbLo0yT5WA==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "Materialious",
"version": "1.7.4",
"version": "1.7.5",
"description": "Modern material design for Invidious.",
"author": {
"name": "Ward Pearce",
@@ -32,7 +32,7 @@
"jsonfile": "^6.1.0"
},
"devDependencies": {
"electron": "^34.0.1",
"electron": "^34.0.2",
"electron-builder": "^25.1.8",
"electron-rebuild": "^3.2.9",
"typescript": "^5.7.3"
@@ -41,4 +41,4 @@
"capacitor",
"electron"
]
}
}
+2402 -1985
View File
File diff suppressed because it is too large Load Diff
+10 -10
View File
@@ -1,6 +1,6 @@
{
"name": "materialious",
"version": "1.7.4",
"version": "1.7.5",
"private": true,
"scripts": {
"dev": "vite dev",
@@ -15,8 +15,8 @@
"@capacitor/assets": "^3.0.5",
"@sveltejs/adapter-auto": "^3.2.5",
"@sveltejs/adapter-static": "^3.0.5",
"@sveltejs/kit": "^2.9.1",
"@sveltejs/vite-plugin-svelte": "^3.1.1",
"@sveltejs/kit": "^2.17.1",
"@sveltejs/vite-plugin-svelte": "^5.0.3",
"@types/eslint": "^8.56.7",
"@types/event-source-polyfill": "^1.0.5",
"@types/he": "^1.2.3",
@@ -28,16 +28,16 @@
"buffer": "^6.0.3",
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-svelte": "^2.44.1",
"eslint-plugin-svelte": "^2.45.1",
"i": "^0.3.7",
"jsdom": "^25.0.1",
"prettier": "^3.3.3",
"prettier-plugin-svelte": "^3.2.7",
"svelte": "^4.2.19",
"svelte-check": "^3.8.6",
"svelte": "^5.19.7",
"svelte-check": "^4.0.0",
"tslib": "^2.7.0",
"typescript": "^5.6.3",
"vite": "^5.4.9"
"vite": "^6.0.11"
},
"type": "module",
"dependencies": {
@@ -68,11 +68,11 @@
"mousetrap": "^1.6.5",
"peerjs": "^1.5.4",
"sponsorblock-api": "^0.2.4",
"svelte-i18n": "^4.0.0",
"svelte-i18n": "^4.0.1",
"svelte-infinite-loading": "^1.4.0",
"svelte-persisted-store": "^0.12.0",
"terser": "^5.34.1",
"vidstack": "^1.12.9",
"vidstack": "^1.12.12",
"youtubei.js": "^12.2.0"
}
}
}
@@ -8,9 +8,13 @@
import { truncate } from '../misc';
import { interfaceLowBandwidthMode } from '../store';
export let channel: Channel;
interface Props {
channel: Channel;
}
let channelPfp: HTMLImageElement | undefined;
let { channel }: Props = $props();
let channelPfp: HTMLImageElement | undefined = $state();
onMount(async () => {
if (get(interfaceLowBandwidthMode)) return;
+11 -6
View File
@@ -1,4 +1,5 @@
<script lang="ts">
import Comment_1 from './Comment.svelte';
import { getComments } from '$lib/api';
import { type Comment, type Comments } from '$lib/api/model';
import { getBestThumbnail, proxyGoogleImage } from '$lib/images';
@@ -6,10 +7,14 @@
import { numberWithCommas } from '$lib/time';
import { _ } from 'svelte-i18n';
export let comment: Comment;
export let videoId: string;
interface Props {
comment: Comment;
videoId: string;
}
let replies: Comments | undefined = undefined;
let { comment, videoId }: Props = $props();
let replies: Comments | undefined = $state(undefined);
async function loadReplies(continuation: string) {
try {
@@ -79,20 +84,20 @@
{#if replies}
{#each replies.comments as reply}
<svelte:self comment={reply} {videoId} />
<Comment_1 comment={reply} {videoId} />
{/each}
{/if}
{#if comment.replies && !replies}
<button
on:click={async () => loadReplies(comment.replies.continuation)}
onclick={async () => loadReplies(comment.replies.continuation)}
class="transparent replies"
>
<i class="primary-text">expand_more</i>
<span class="primary-text">{comment.replies.replyCount} {$_('replies')}</span>
</button>
{:else if replies}
<button on:click={() => (replies = undefined)} class="transparent replies">
<button onclick={() => (replies = undefined)} class="transparent replies">
<i class="primary-text">expand_less</i>
<span class="primary-text">{$_('hideReplies')}</span>
</button>
@@ -1,7 +1,12 @@
<script lang="ts">
import { onMount } from 'svelte';
interface Props {
children?: import('svelte').Snippet;
}
let largeCol = '2';
let { children }: Props = $props();
let largeCol = $state('2');
function checkWidth() {
if (innerWidth <= 1750) {
@@ -19,5 +24,5 @@
</script>
<div class="s12 m6 l{largeCol}">
<slot />
{@render children?.()}
</div>
@@ -4,7 +4,11 @@
import type { HashTag } from '../api/model';
import { truncate } from '../misc';
export let hashtag: HashTag;
interface Props {
hashtag: HashTag;
}
let { hashtag }: Props = $props();
let channelPfp: HTMLImageElement | undefined;
</script>
+6 -2
View File
@@ -4,9 +4,13 @@
import { darkModeStore, themeColorStore } from '../store';
import { getDynamicTheme } from '../theme';
export let classes = '';
interface Props {
classes?: string;
}
let fillColor = '';
let { classes = '' }: Props = $props();
let fillColor = $state('');
async function setFill() {
fillColor = (await getDynamicTheme())['--primary'];
@@ -4,7 +4,7 @@
import { proxyVideoUrl } from '../misc';
import { miniPlayerSrcStore, playerProxyVideosStore } from '../store';
let currentTime: number = 0;
let currentTime: number = $state(0);
function setTime() {
const player = (document.getElementById('video') as HTMLVideoElement) || null;
@@ -30,7 +30,7 @@
{#if $miniPlayerSrcStore && $miniPlayerSrcStore.video.formatStreams.length > 0}
<nav class="bottom no-padding">
<article class="surface-bright">
<button on:click={() => miniPlayerSrcStore.set(null)} class="s circle transparent no-margin">
<button onclick={() => miniPlayerSrcStore.set(null)} class="s circle transparent no-margin">
<i>close</i>
</button>
<div class="flex-container">
@@ -39,7 +39,7 @@
{$miniPlayerSrcStore.video.title}
</p>
<button
on:click={() => miniPlayerSrcStore.set(null)}
onclick={() => miniPlayerSrcStore.set(null)}
class="m l circle transparent no-margin"
>
<i>close</i>
@@ -49,14 +49,14 @@
></progress>
</div>
<video
on:click={() => {
onclick={() => {
goto(`/watch/${$miniPlayerSrcStore.video.videoId}?time=${currentTime}`);
miniPlayerSrcStore.set(null);
}}
id="video"
style="cursor: pointer;"
bind:currentTime
on:loadedmetadata={setTime}
onloadedmetadata={setTime}
controls={false}
autoplay
src={get(playerProxyVideosStore)
+20 -9
View File
@@ -41,16 +41,27 @@
} from '../store';
import { getDynamicTheme, setStatusBarColor } from '../theme';
export let data: { video: VideoPlay; content: PhasedDescription; playlistId: string | null };
export let audioMode = false;
export let player: MediaPlayerElement;
export let isSyncing: boolean = false;
export let isEmbed: boolean = false;
export let segments: Segment[] = [];
interface Props {
data: { video: VideoPlay; content: PhasedDescription; playlistId: string | null };
audioMode?: boolean;
player: MediaPlayerElement;
isSyncing?: boolean;
isEmbed?: boolean;
segments?: Segment[];
}
let src: PlayerSrc = [];
let snackBarAlert = '';
let playerIsLive = false;
let {
data,
audioMode = false,
player = $bindable(),
isSyncing = false,
isEmbed = false,
segments = $bindable([])
}: Props = $props();
let src: PlayerSrc = $state([]);
let snackBarAlert = $state('');
let playerIsLive = $state(false);
let playerPosSet = false;
let userVideoSpeed = 1;
@@ -8,12 +8,16 @@
import { truncate } from '../misc';
import { interfaceLowBandwidthMode } from '../store';
export let playlist: Playlist;
export let disabled: boolean = false;
interface Props {
playlist: Playlist;
disabled?: boolean;
}
let loading = true;
let { playlist, disabled = false }: Props = $props();
let img: HTMLImageElement;
let loading = $state(true);
let img: HTMLImageElement | undefined = $state();
const playlistLink = `/playlist/${playlist.playlistId}`;
@@ -49,7 +53,7 @@
{#if playlist.videoCount > 0 && !$interfaceLowBandwidthMode}
{#if loading}
<progress class="circle"></progress>
{:else if img.src !== ''}
{:else if img && img.src !== ''}
<img
class="responsive"
style="max-width: 100%;height: 100%;"
+14 -12
View File
@@ -1,4 +1,6 @@
<script lang="ts">
import { preventDefault } from 'svelte/legacy';
import { goto } from '$app/navigation';
import Mousetrap from 'mousetrap';
import { createEventDispatcher, onMount } from 'svelte';
@@ -12,14 +14,14 @@
const dispatch = createEventDispatcher();
let searchSuggestions = false;
let searchSuggestions = $state(false);
interfaceSearchSuggestionsStore.subscribe((value) => (searchSuggestions = value));
let search: string;
let suggestionsForSearch: string[] = [];
let selectedSuggestionIndex: number = -1;
let search: string = $state('');
let suggestionsForSearch: string[] = $state([]);
let selectedSuggestionIndex: number = $state(-1);
let showSearchBox = false;
let showSearchBox = $state(false);
let debounceTimer: NodeJS.Timeout;
function debouncedSearch(event: any) {
@@ -97,29 +99,29 @@
});
</script>
<form on:submit|preventDefault={handleSubmit}>
<form onsubmit={preventDefault(handleSubmit)}>
<div class="field prefix round fill no-margin search">
<i class="front">search</i>
<input
id="search-box"
placeholder={$_('searchPlaceholder')}
bind:value={search}
on:click={async () => {
onclick={async () => {
showSearchBox = true;
}}
/>
{#if showSearchBox}
<menu class="min suggestions-container">
<div class="field large prefix suffix no-margin fixed">
<i class="front" on:click={() => dispatch('searchCancelled')}>arrow_back</i>
<i class="front" onclick={() => dispatch('searchCancelled')}>arrow_back</i>
<input
placeholder={$_('searchPlaceholder')}
type="text"
autofocus
required
bind:value={search}
on:keydown={handleKeyDown}
on:keyup={(event) => {
onkeydown={handleKeyDown}
onkeyup={(event) => {
if (event.key === 'Enter') {
handleSubmit();
} else {
@@ -127,12 +129,12 @@
}
}}
/>
<i class="front" on:click={resetSearch}>close</i>
<i class="front" onclick={resetSearch}>close</i>
</div>
{#if searchSuggestions}
{#each suggestionsForSearch as suggestion, index}
<a
on:click={() => {
onclick={() => {
search = suggestion;
handleSubmit();
}}
@@ -1,15 +1,17 @@
<script lang="ts">
import { preventDefault } from 'svelte/legacy';
import { _ } from 'svelte-i18n';
import { get } from 'svelte/store';
import { ensureNoTrailingSlash } from '../../misc';
import { synciousInstanceStore, synciousStore } from '../../store';
let synciousInstance = get(synciousInstanceStore);
let synciousInstance = $state(get(synciousInstanceStore));
</script>
<form
on:submit|preventDefault={() =>
synciousInstanceStore.set(ensureNoTrailingSlash(synciousInstance))}
onsubmit={preventDefault(() =>
synciousInstanceStore.set(ensureNoTrailingSlash(synciousInstance)))}
>
<nav>
<div class="field label border max">
@@ -29,7 +31,7 @@
<label class="switch">
<input
bind:checked={$synciousStore}
on:click={() => synciousStore.set(!$synciousStore)}
onclick={() => synciousStore.set(!$synciousStore)}
type="checkbox"
/>
<span></span>
@@ -1,4 +1,6 @@
<script lang="ts">
import { preventDefault } from 'svelte/legacy';
import { _ } from 'svelte-i18n';
import { get } from 'svelte/store';
import { ensureNoTrailingSlash } from '../../misc';
@@ -9,11 +11,11 @@
deArrowTitlesOnly
} from '../../store';
let deArrowUrl = get(deArrowInstanceStore);
let deArrowThumbnailUrl = get(deArrowThumbnailInstanceStore);
let deArrowUrl = $state(get(deArrowInstanceStore));
let deArrowThumbnailUrl = $state(get(deArrowThumbnailInstanceStore));
</script>
<form on:submit|preventDefault={() => deArrowInstanceStore.set(ensureNoTrailingSlash(deArrowUrl))}>
<form onsubmit={preventDefault(() => deArrowInstanceStore.set(ensureNoTrailingSlash(deArrowUrl)))}>
<nav>
<div class="field label border max">
<input bind:value={deArrowUrl} name="dearrow-instance" type="text" />
@@ -25,7 +27,7 @@
</nav>
</form>
<form on:submit|preventDefault={() => deArrowThumbnailInstanceStore.set(deArrowThumbnailUrl)}>
<form onsubmit={preventDefault(() => deArrowThumbnailInstanceStore.set(deArrowThumbnailUrl))}>
<nav>
<div class="field label border max">
<input bind:value={deArrowThumbnailUrl} name="dearrow-thumbnail-instance" type="text" />
@@ -44,7 +46,7 @@
<label class="switch">
<input
bind:checked={$deArrowTitlesOnly}
on:click={() => deArrowTitlesOnly.set(!$deArrowTitlesOnly)}
onclick={() => deArrowTitlesOnly.set(!$deArrowTitlesOnly)}
type="checkbox"
/>
<span></span>
@@ -58,7 +60,7 @@
<label class="switch">
<input
bind:checked={$deArrowEnabledStore}
on:click={() => deArrowEnabledStore.set(!$deArrowEnabledStore)}
onclick={() => deArrowEnabledStore.set(!$deArrowEnabledStore)}
type="checkbox"
/>
<span></span>
@@ -1,4 +1,6 @@
<script lang="ts">
import { preventDefault } from 'svelte/legacy';
import { goto } from '$app/navigation';
import { bookmarkletSaveToUrl } from '$lib/externalSettings';
import { letterCase, titleCases } from '$lib/letterCasing';
@@ -30,10 +32,10 @@
themeColorStore
} from '../../store';
let invidiousInstance = get(instanceStore);
let region = get(interfaceRegionStore);
let forceCase = get(interfaceForceCase);
let defaultPage = get(interfaceDefaultPage);
let invidiousInstance = $state(get(instanceStore));
let region = $state(get(interfaceRegionStore));
let forceCase = $state(get(interfaceForceCase));
let defaultPage = $state(get(interfaceDefaultPage));
async function setColor(color: any) {
const target = color.target;
@@ -58,12 +60,12 @@
{#if Capacitor.isNativePlatform()}
<form
on:submit|preventDefault={() => {
onsubmit={preventDefault(() => {
instanceStore.set(ensureNoTrailingSlash(invidiousInstance));
authStore.set(null);
goto('/', { replaceState: true });
ui('#dialog-settings');
}}
})}
>
<nav>
<div class="field label border max">
@@ -79,7 +81,7 @@
<div class="margin"></div>
<button on:click={toggleDarkMode} class="no-margin">
<button onclick={toggleDarkMode} class="no-margin">
{#if !$darkModeStore}
<i>dark_mode</i>
<span>{$_('layout.theme.darkMode')}</span>
@@ -91,7 +93,7 @@
<button>
<i>palette</i>
<span>{$_('layout.theme.color')}</span>
<input on:change={setColor} type="color" />
<input onchange={setColor} type="color" />
</button>
<div class="margin"></div>
@@ -106,7 +108,7 @@
<input
type="checkbox"
bind:checked={$interfaceAmoledTheme}
on:click={() => interfaceAmoledTheme.set(!$interfaceAmoledTheme)}
onclick={() => interfaceAmoledTheme.set(!$interfaceAmoledTheme)}
/>
<span></span>
</label>
@@ -123,7 +125,7 @@
<input
type="checkbox"
bind:checked={$interfaceDisplayThumbnailAvatars}
on:click={() => interfaceDisplayThumbnailAvatars.set(!$interfaceDisplayThumbnailAvatars)}
onclick={() => interfaceDisplayThumbnailAvatars.set(!$interfaceDisplayThumbnailAvatars)}
/>
<span></span>
</label>
@@ -139,7 +141,7 @@
<input
type="checkbox"
bind:checked={$interfaceSearchHistoryEnabled}
on:click={() => {
onclick={() => {
interfaceSearchHistoryEnabled.set(!$interfaceSearchHistoryEnabled);
searchHistoryStore.set([]);
}}
@@ -158,7 +160,7 @@
<input
type="checkbox"
bind:checked={$interfaceLowBandwidthMode}
on:click={() => interfaceLowBandwidthMode.set(!$interfaceLowBandwidthMode)}
onclick={() => interfaceLowBandwidthMode.set(!$interfaceLowBandwidthMode)}
/>
<span></span>
</label>
@@ -174,7 +176,7 @@
<input
type="checkbox"
bind:checked={$interfaceSearchSuggestionsStore}
on:click={() => interfaceSearchSuggestionsStore.set(!$interfaceSearchSuggestionsStore)}
onclick={() => interfaceSearchSuggestionsStore.set(!$interfaceSearchSuggestionsStore)}
/>
<span></span>
</label>
@@ -190,7 +192,7 @@
<input
type="checkbox"
bind:checked={$interfacePreviewVideoOnHoverStore}
on:click={() => interfacePreviewVideoOnHoverStore.set(!$interfacePreviewVideoOnHoverStore)}
onclick={() => interfacePreviewVideoOnHoverStore.set(!$interfacePreviewVideoOnHoverStore)}
/>
<span></span>
</label>
@@ -206,7 +208,7 @@
<input
type="checkbox"
bind:checked={$interfaceAutoExpandDesc}
on:click={() => interfaceAutoExpandDesc.set(!$interfaceAutoExpandDesc)}
onclick={() => interfaceAutoExpandDesc.set(!$interfaceAutoExpandDesc)}
/>
<span></span>
</label>
@@ -222,7 +224,7 @@
<input
type="checkbox"
bind:checked={$interfaceAutoExpandComments}
on:click={() => interfaceAutoExpandComments.set(!$interfaceAutoExpandComments)}
onclick={() => interfaceAutoExpandComments.set(!$interfaceAutoExpandComments)}
/>
<span></span>
</label>
@@ -230,7 +232,7 @@
</div>
<div class="field label suffix border">
<select name="region" bind:value={region} on:change={() => interfaceRegionStore.set(region)}>
<select name="region" bind:value={region} onchange={() => interfaceRegionStore.set(region)}>
{#each iso31661 as region}
<option selected={$interfaceRegionStore === region.alpha2} value={region.alpha2}
>{region.alpha2} - {region.name}</option
@@ -242,7 +244,7 @@
</div>
<div class="field label suffix border">
<select name="case" bind:value={forceCase} on:change={() => interfaceForceCase.set(forceCase)}>
<select name="case" bind:value={forceCase} onchange={() => interfaceForceCase.set(forceCase)}>
<option selected={$interfaceForceCase === null} value={null}>Default</option>
{#each titleCases as caseType}
<option selected={$interfaceForceCase === caseType} value={caseType}
@@ -258,7 +260,7 @@
<select
name="defaultPage"
bind:value={defaultPage}
on:change={() => interfaceDefaultPage.set(defaultPage)}
onchange={() => interfaceDefaultPage.set(defaultPage)}
>
{#each getPages() as page}
{#if !page.requiresAuth || get(authStore)}
@@ -276,7 +278,7 @@
<h6>{$_('layout.bookmarklet')}</h6>
<button
class="no-margin"
on:click={async () => await Clipboard.write({ string: bookmarkletSaveToUrl() })}
onclick={async () => await Clipboard.write({ string: bookmarkletSaveToUrl() })}
>{$_('copyUrl')}</button
>
</div>
@@ -21,7 +21,7 @@
silenceSkipperStore
} from '../../store';
let defaultLanguage = get(playerDefaultLanguage);
let defaultLanguage = $state(get(playerDefaultLanguage));
const languageNames = ISO6391.getAllCodes().map((code) =>
ISO6391.getName(code).toLocaleLowerCase()
@@ -33,7 +33,7 @@
<select
name="case"
bind:value={defaultLanguage}
on:change={() => playerDefaultLanguage.set(defaultLanguage)}
onchange={() => playerDefaultLanguage.set(defaultLanguage)}
>
{#each languageNames as language}
<option selected={$playerDefaultLanguage === language} value={language}
@@ -54,7 +54,7 @@
<input
type="checkbox"
bind:checked={$playerMiniPlayerStore}
on:click={() => playerMiniPlayerStore.set(!$playerMiniPlayerStore)}
onclick={() => playerMiniPlayerStore.set(!$playerMiniPlayerStore)}
/>
<span></span>
</label>
@@ -70,7 +70,7 @@
<input
type="checkbox"
bind:checked={$playerAutoPlayStore}
on:click={() => playerAutoPlayStore.set(!$playerAutoPlayStore)}
onclick={() => playerAutoPlayStore.set(!$playerAutoPlayStore)}
/>
<span></span>
</label>
@@ -86,7 +86,7 @@
<input
type="checkbox"
bind:checked={$playerAlwaysLoopStore}
on:click={() => playerAlwaysLoopStore.set(!$playerAlwaysLoopStore)}
onclick={() => playerAlwaysLoopStore.set(!$playerAlwaysLoopStore)}
/>
<span></span>
</label>
@@ -103,7 +103,7 @@
<input
type="checkbox"
bind:checked={$playerAndroidBgPlayer}
on:click={() => playerAndroidBgPlayer.set(!$playerAndroidBgPlayer)}
onclick={() => playerAndroidBgPlayer.set(!$playerAndroidBgPlayer)}
/>
<span></span>
</label>
@@ -119,7 +119,7 @@
<input
type="checkbox"
bind:checked={$playerAndroidLockOrientation}
on:click={() => playerAndroidLockOrientation.set(!$playerAndroidLockOrientation)}
onclick={() => playerAndroidLockOrientation.set(!$playerAndroidLockOrientation)}
/>
<span></span>
</label>
@@ -137,7 +137,7 @@
<input
type="checkbox"
bind:checked={$playerProxyVideosStore}
on:click={() => playerProxyVideosStore.set(!$playerProxyVideosStore)}
onclick={() => playerProxyVideosStore.set(!$playerProxyVideosStore)}
/>
<span></span>
</label>
@@ -154,7 +154,7 @@
<input
type="checkbox"
bind:checked={$playerSavePlaybackPositionStore}
on:click={() => playerSavePlaybackPositionStore.set(!$playerSavePlaybackPositionStore)}
onclick={() => playerSavePlaybackPositionStore.set(!$playerSavePlaybackPositionStore)}
/>
<span></span>
</label>
@@ -170,7 +170,7 @@
<input
type="checkbox"
bind:checked={$playerListenByDefaultStore}
on:click={() => playerListenByDefaultStore.set(!$playerListenByDefaultStore)}
onclick={() => playerListenByDefaultStore.set(!$playerListenByDefaultStore)}
/>
<span></span>
</label>
@@ -186,7 +186,7 @@
<input
type="checkbox"
bind:checked={$playerTheatreModeByDefaultStore}
on:click={() => playerTheatreModeByDefaultStore.set(!$playerTheatreModeByDefaultStore)}
onclick={() => playerTheatreModeByDefaultStore.set(!$playerTheatreModeByDefaultStore)}
/>
<span></span>
</label>
@@ -202,7 +202,7 @@
<input
type="checkbox"
bind:checked={$playerAutoplayNextByDefaultStore}
on:click={() => playerAutoplayNextByDefaultStore.set(!$playerAutoplayNextByDefaultStore)}
onclick={() => playerAutoplayNextByDefaultStore.set(!$playerAutoplayNextByDefaultStore)}
/>
<span></span>
</label>
@@ -218,7 +218,7 @@
<input
type="checkbox"
bind:checked={$silenceSkipperStore}
on:click={() => silenceSkipperStore.set(!$silenceSkipperStore)}
onclick={() => silenceSkipperStore.set(!$silenceSkipperStore)}
/>
<span></span>
</label>
@@ -235,7 +235,7 @@
<input
type="checkbox"
bind:checked={$playerYouTubeJsAlways}
on:click={() => playerYouTubeJsAlways.set(!$playerYouTubeJsAlways)}
onclick={() => playerYouTubeJsAlways.set(!$playerYouTubeJsAlways)}
/>
<span></span>
</label>
@@ -251,7 +251,7 @@
<input
type="checkbox"
bind:checked={$playerYouTubeJsFallback}
on:click={() => playerYouTubeJsFallback.set(!$playerYouTubeJsFallback)}
onclick={() => playerYouTubeJsFallback.set(!$playerYouTubeJsFallback)}
/>
<span></span>
</label>
@@ -1,15 +1,17 @@
<script lang="ts">
import { preventDefault } from 'svelte/legacy';
import { _ } from 'svelte-i18n';
import { get } from 'svelte/store';
import { ensureNoTrailingSlash } from '../../misc';
import { returnYTDislikesInstanceStore, returnYtDislikesStore } from '../../store';
let returnYTInstance = get(returnYTDislikesInstanceStore);
let returnYTInstance = $state(get(returnYTDislikesInstanceStore));
</script>
<form
on:submit|preventDefault={() =>
returnYTDislikesInstanceStore.set(ensureNoTrailingSlash(returnYTInstance))}
onsubmit={preventDefault(() =>
returnYTDislikesInstanceStore.set(ensureNoTrailingSlash(returnYTInstance)))}
>
<nav>
<div class="field label border max">
@@ -29,7 +31,7 @@
<label class="switch">
<input
bind:checked={$returnYtDislikesStore}
on:click={() => returnYtDislikesStore.set(!$returnYtDislikesStore)}
onclick={() => returnYtDislikesStore.set(!$returnYtDislikesStore)}
type="checkbox"
/>
<span></span>
@@ -68,9 +68,9 @@
| 'ryd'
| 'api extended'
| 'sponsorblock'
| 'dearrow' = 'interface';
| 'dearrow' = $state('interface');
let dialogType = '';
let dialogType = $state('');
function checkWidth() {
if (innerWidth <= 1320) {
@@ -104,31 +104,31 @@
<div>
<nav class="tabbed small">
<a class:active={activeTab === 'interface'} on:click={() => (activeTab = 'interface')}>
<a class:active={activeTab === 'interface'} onclick={() => (activeTab = 'interface')}>
<i>grid_view</i>
<span>{$_('layout.interface')}</span>
</a>
<a class:active={activeTab === 'player'} on:click={() => (activeTab = 'player')}>
<a class:active={activeTab === 'player'} onclick={() => (activeTab = 'player')}>
<i>smart_display</i>
<span>{$_('layout.player.title')}</span>
</a>
<a class:active={activeTab === 'data'} on:click={() => (activeTab = 'data')}>
<a class:active={activeTab === 'data'} onclick={() => (activeTab = 'data')}>
<i>save</i>
<span>{$_('layout.dataPreferences.dataPreferences')}</span>
</a>
<a class:active={activeTab === 'ryd'} on:click={() => (activeTab = 'ryd')}>
<a class:active={activeTab === 'ryd'} onclick={() => (activeTab = 'ryd')}>
<i>thumb_down</i>
<span>RYD</span>
</a>
<a class:active={activeTab === 'api extended'} on:click={() => (activeTab = 'api extended')}>
<a class:active={activeTab === 'api extended'} onclick={() => (activeTab = 'api extended')}>
<i>sync</i>
<span>API Extended</span>
</a>
<a class:active={activeTab === 'sponsorblock'} on:click={() => (activeTab = 'sponsorblock')}>
<a class:active={activeTab === 'sponsorblock'} onclick={() => (activeTab = 'sponsorblock')}>
<i>block</i>
<span>Sponsorblock</span>
</a>
<a class:active={activeTab === 'dearrow'} on:click={() => (activeTab = 'dearrow')}>
<a class:active={activeTab === 'dearrow'} onclick={() => (activeTab = 'dearrow')}>
<i>keyboard_double_arrow_down</i>
<span>{$_('layout.deArrow.title')}</span>
</a>
@@ -1,4 +1,6 @@
<script lang="ts">
import { preventDefault } from 'svelte/legacy';
import { _ } from 'svelte-i18n';
import { get } from 'svelte/store';
import { ensureNoTrailingSlash } from '../../misc';
@@ -10,10 +12,10 @@
sponsorBlockUrlStore
} from '../../store';
let sponsorCategoriesList: string[] = [];
let sponsorCategoriesList: string[] = $state([]);
sponsorBlockCategoriesStore.subscribe((value) => (sponsorCategoriesList = value));
let sponsorBlockInstance = get(sponsorBlockUrlStore);
let sponsorBlockInstance = $state(get(sponsorBlockUrlStore));
const sponsorCategories = [
{ name: $_('layout.sponsors.sponsor'), category: 'sponsor' },
@@ -36,8 +38,8 @@
</script>
<form
on:submit|preventDefault={() =>
sponsorBlockUrlStore.set(ensureNoTrailingSlash(sponsorBlockInstance))}
onsubmit={preventDefault(() =>
sponsorBlockUrlStore.set(ensureNoTrailingSlash(sponsorBlockInstance)))}
>
<nav>
<div class="field label border max">
@@ -57,7 +59,7 @@
<label class="switch">
<input
bind:checked={$sponsorBlockStore}
on:click={() => sponsorBlockStore.set(!$sponsorBlockStore)}
onclick={() => sponsorBlockStore.set(!$sponsorBlockStore)}
type="checkbox"
/>
<span></span>
@@ -71,7 +73,7 @@
<label class="switch">
<input
bind:checked={$sponsorBlockDisplayToastStore}
on:click={() => sponsorBlockDisplayToastStore.set(!$sponsorBlockDisplayToastStore)}
onclick={() => sponsorBlockDisplayToastStore.set(!$sponsorBlockDisplayToastStore)}
type="checkbox"
/>
<span></span>
@@ -85,7 +87,7 @@
<label class="switch">
<input
bind:checked={$sponsorBlockTimelineStore}
on:click={() => sponsorBlockTimelineStore.set(!$sponsorBlockTimelineStore)}
onclick={() => sponsorBlockTimelineStore.set(!$sponsorBlockTimelineStore)}
type="checkbox"
/>
<span></span>
@@ -106,7 +108,7 @@
<input
type="checkbox"
checked={sponsorCategoriesList.includes(sponsor.category)}
on:click={() => toggleSponsor(sponsor.category)}
onclick={() => toggleSponsor(sponsor.category)}
/>
<span></span>
</label>
@@ -4,14 +4,18 @@
import { _ } from 'svelte-i18n';
import type { Notification, PlaylistPageVideo, Video, VideoBase } from '../api/model';
export let video: VideoBase | Video | Notification | PlaylistPageVideo;
interface Props {
video: VideoBase | Video | Notification | PlaylistPageVideo;
}
let { video }: Props = $props();
</script>
{#if !Capacitor.isNativePlatform()}
<a
class="row"
href="#copy"
on:click={async () =>
onclick={async () =>
await Clipboard.write({ string: `${location.origin}/watch/${video.videoId}` })}
>
<div class="min">{$_('player.share.materialiousLink')}</div></a
@@ -20,14 +24,14 @@
<a
href="#copy"
class="row"
on:click={async () =>
onclick={async () =>
await Clipboard.write({ string: `https://redirect.invidious.io/watch?v=${video.videoId}` })}
>
<div class="min">{$_('player.share.invidiousRedirect')}</div></a
><a
class="row"
href="#copy"
on:click={async () =>
onclick={async () =>
await Clipboard.write({ string: `https://www.youtube.com/watch?v=${video.videoId}` })}
>
<div class="min">{$_('player.share.youtubeLink')}</div></a
@@ -130,7 +130,7 @@
<label class="active" for="sync-share">Share URL</label>
</div>
<button
on:click={async () => {
onclick={async () => {
await Clipboard.write({ string: `${location.origin}?sync=${$syncPartyPeerStore?.id}` });
}}
class="square round"
@@ -142,7 +142,7 @@
<div class="space"></div>
<button
class="no-margin"
on:click={startWatchSync}
onclick={startWatchSync}
data-ui={`${$syncPartyPeerStore ? '#sync-party' : ''}`}
>{#if $syncPartyPeerStore}
{$_('layout.endSyncParty')}
@@ -26,22 +26,26 @@
const dispatch = createEventDispatcher();
export let video: VideoBase | Video | Notification | PlaylistPageVideo;
export let playlistId: string = '';
export let sideways: boolean = false;
interface Props {
video: VideoBase | Video | Notification | PlaylistPageVideo;
playlistId?: string;
sideways?: boolean;
}
let thumbnailHidden: boolean = false;
let showVideoPreview: boolean = false;
let videoPreview: VideoPlay | null = null;
let videoPreviewMuted: boolean = true;
let videoPreviewVolume: number = 0.4;
let imgHeight: number;
let { video = $bindable(), playlistId = '', sideways = $bindable(false) }: Props = $props();
let authorImg: HTMLImageElement | undefined;
let thumbnailHidden: boolean = $state(false);
let showVideoPreview: boolean = $state(false);
let videoPreview: VideoPlay | null = $state(null);
let videoPreviewMuted: boolean = $state(true);
let videoPreviewVolume: number = $state(0.4);
let imgHeight: number = $state(0);
let authorImg: HTMLImageElement | undefined = $state();
let proxyVideos = get(playerProxyVideosStore);
let placeholderHeight: number = 0;
let placeholderHeight: number = $state(0);
let watchUrl = new URL(`${location.origin}/watch/${video.videoId}`);
@@ -55,20 +59,20 @@
}
});
let loading = true;
let loaded = false;
let loading = $state(true);
let loaded = $state(false);
let img: HTMLImageElement;
let img: HTMLImageElement | undefined = $state();
let progress: string | null;
let progress: string | undefined = $state();
if (get(playerSavePlaybackPositionStore)) {
try {
progress = localStorage.getItem(`v_${video.videoId}`);
progress = localStorage.getItem(`v_${video.videoId}`) ?? undefined;
} catch {
progress = null;
progress = undefined;
}
} else {
progress = null;
progress = undefined;
}
let startedSideways = sideways === true;
@@ -86,11 +90,11 @@
try {
const channel = await getChannel(video.authorId, { priority: 'low' });
const img = new Image();
img.src = proxyGoogleImage(getBestThumbnail(channel.authorThumbnails, 75, 75));
const loadedPfp = new Image();
loadedPfp.src = proxyGoogleImage(getBestThumbnail(channel.authorThumbnails, 75, 75));
img.onload = () => {
authorImg = img;
loadedPfp.onload = () => {
authorImg = loadedPfp;
};
} catch {}
}
@@ -274,9 +278,9 @@
{#if !thumbnailHidden}
<div class:sideways-root={sideways}>
<div
on:mouseover={previewVideo}
on:mouseleave={() => (showVideoPreview = false)}
on:focus={() => {}}
onmouseover={previewVideo}
onmouseleave={() => (showVideoPreview = false)}
onfocus={() => {}}
id="thumbnail-container"
role="region"
>
@@ -284,7 +288,7 @@
class="wave thumbnail"
href={watchUrl.toString()}
data-sveltekit-preload-data="off"
on:click={syncChangeVideo}
onclick={syncChangeVideo}
>
{#if !$interfaceLowBandwidthMode}
{#if loading}
@@ -293,7 +297,7 @@
style="width: 100%;height: {placeholderHeight}px;"
></div>
{:else if loaded}
{#if showVideoPreview && videoPreview}
{#if showVideoPreview && videoPreview && img}
<div style="max-width: 100%; max-height: {imgHeight}px;">
<video
id="video-preview"
@@ -311,7 +315,7 @@
>
</video>
</div>
{:else}
{:else if img}
<img class="responsive" src={img.src} alt="Thumbnail for video" />
{/if}
{:else}
@@ -352,7 +356,7 @@
<button
class="no-padding"
style="position: absolute; bottom: 10px; left: 10px; width: 30px; height: 30px;"
on:click={() => {
onclick={() => {
videoPreviewMuted = !videoPreviewMuted;
}}
>
@@ -10,17 +10,21 @@
import { decodeHtmlCharCodes } from '../misc';
import { instanceStore } from '../store';
export let video: VideoPlay;
export let player: MediaPlayerElement;
interface Props {
video: VideoPlay;
player: MediaPlayerElement;
}
let url: string | null = null;
let autoScroll: boolean = true;
let { video, player = $bindable() }: Props = $props();
let transcript: ParsedCaptionsResult | null = null;
let transcriptCues: VTTCue[] = [];
let isLoading = false;
let currentTime = 0;
let search: string = '';
let url: string | null = $state(null);
let autoScroll: boolean = $state(true);
let transcript: ParsedCaptionsResult | null = $state(null);
let transcriptCues: VTTCue[] = $state([]);
let isLoading = $state(false);
let currentTime = $state(0);
let search: string = $state('');
player.addEventListener('time-update', (event: MediaTimeUpdateEvent) => {
currentTime = event.detail.currentTime;
@@ -72,7 +76,7 @@
<article class="no-elevate" style="position: sticky; top: 0; z-index: 3;">
<h6>{$_('transcript')}</h6>
<div class="field label suffix border">
<select bind:value={url} on:change={loadTranscript} name="captions">
<select bind:value={url} onchange={loadTranscript} name="captions">
<option selected={true} value={null}>{$_('selectLang')}</option>
{#each video.captions as caption}
<option value={caption.url}>{caption.label}</option>
@@ -85,7 +89,7 @@
<div class="max field round suffix prefix small no-margin surface-variant">
<i class="front">search</i><input
bind:value={search}
on:input={searchTranscript}
oninput={searchTranscript}
type="text"
placeholder={$_('searchPlaceholder')}
/>
@@ -111,7 +115,7 @@
<div
class="transcript-line"
id={`transcript-line-${cue.startTime}`}
on:click={() => (player.currentTime = cue.startTime)}
onclick={() => (player.currentTime = cue.startTime)}
class:secondary-container={currentTime >= cue.startTime && currentTime <= cue.endTime}
>
<p class="chip no-margin">{videoLength(cue.startTime)}</p>
@@ -7,11 +7,15 @@
import { authStore } from '../store';
import ContentColumn from './ContentColumn.svelte';
export let videos: VideoBase[] | Video[] | Notification[] | PlaylistPageVideo[] = [];
export let playlistId: string = '';
export let playlistAuthor: string = '';
interface Props {
videos?: VideoBase[] | Video[] | Notification[] | PlaylistPageVideo[];
playlistId?: string;
playlistAuthor?: string;
}
let hiddenVideos: string[] = [];
let { videos = [], playlistId = '', playlistAuthor = '' }: Props = $props();
let hiddenVideos: string[] = $state([]);
let auth = get(authStore);
async function removePlaylistItem(indexId: string, videoId: string) {
@@ -36,7 +40,7 @@
{#if auth && decodeURIComponent(auth.username) === playlistAuthor && 'indexId' in video}
<div class="right-align" style="margin: 1em .5em;">
<button
on:click={async () => removePlaylistItem(video.indexId, video.videoId)}
onclick={async () => removePlaylistItem(video.indexId, video.videoId)}
class="tertiary circle small"
>
<i>delete</i>
+20 -15
View File
@@ -36,18 +36,23 @@
import { _ } from 'svelte-i18n';
import { get } from 'svelte/store';
import { pwaInfo } from 'virtual:pwa-info';
interface Props {
children?: import('svelte').Snippet;
}
let mobileSearchShow = false;
let { children }: Props = $props();
let currentPage: string | null = '';
let mobileSearchShow = $state(false);
let currentPage: string | null = $state('');
activePageStore.subscribe((page) => (currentPage = page));
let isLoggedIn = false;
let isLoggedIn = $state(false);
authStore.subscribe((value) => {
isLoggedIn = value !== null;
});
let notifications: Notification[] = [];
let notifications: Notification[] = $state([]);
interfaceAmoledTheme.subscribe(async () => {
setAmoledTheme();
@@ -178,7 +183,7 @@
}
});
$: webManifestLink = pwaInfo ? pwaInfo.webManifest.linkTag : '';
let webManifestLink = $derived(pwaInfo ? pwaInfo.webManifest.linkTag : '');
</script>
<svelte:head>
@@ -200,7 +205,7 @@
<nav class="top">
{#if !mobileSearchShow}
<button
on:click={() => (mobileSearchShow = !mobileSearchShow)}
onclick={() => (mobileSearchShow = !mobileSearchShow)}
class="transparent s circle large"
>
<i>search</i>
@@ -209,16 +214,16 @@
{#if Capacitor.getPlatform() === 'electron'}
<nav class="no-space">
<button on:click={() => window.history.back()} class="border left-round">
<button onclick={() => window.history.back()} class="border left-round">
<i>arrow_back</i>
</button>
<button on:click={() => window.history.forward()} class="border right-round">
<button onclick={() => window.history.forward()} class="border right-round">
<i>arrow_forward</i>
</button>
</nav>
{/if}
<nav on:click={() => goto('/')} style="cursor: pointer;" class="m l">
<nav onclick={() => goto('/')} style="cursor: pointer;" class="m l">
<Logo />
<h6 class="l">Materialious</h6>
</nav>
@@ -243,23 +248,23 @@
</button>
{/if}
{#if isLoggedIn}
<button class="circle large transparent" on:click={() => ui('#dialog-notifications')}
<button class="circle large transparent" onclick={() => ui('#dialog-notifications')}
><i>notifications</i>
<div class="tooltip bottom">{$_('layout.notifications')}</div>
</button>
{/if}
<button class="circle large transparent" on:click={() => ui('#dialog-settings')}>
<button class="circle large transparent" onclick={() => ui('#dialog-settings')}>
<i>settings</i>
<div class="tooltip bottom">{$_('layout.settings')}</div>
</button>
{#if !isLoggedIn}
<button on:click={login} class="circle large transparent">
<button onclick={login} class="circle large transparent">
<i>login</i>
<div class="tooltip bottom">{$_('layout.login')}</div>
</button>
{:else}
<button on:click={logout} class="circle large transparent">
<button onclick={logout} class="circle large transparent">
<i>logout</i>
<div class="tooltip bottom">{$_('layout.logout')}</div>
</button>
@@ -315,7 +320,7 @@
</p>
</div>
<nav class="right-align no-space">
<button class="transparent link" on:click={() => showWarningStore.set(false)}
<button class="transparent link" onclick={() => showWarningStore.set(false)}
>Don't show again</button
>
</nav>
@@ -324,7 +329,7 @@
{#if $navigating}
<PageLoading />
{:else}
<slot />
{@render children?.()}
{/if}
<SyncParty />
+1 -1
View File
@@ -2,7 +2,7 @@
import VideoList from '$lib/components/VideoList.svelte';
import { activePageStore } from '$lib/store';
export let data;
let { data } = $props();
activePageStore.set('home');
</script>
@@ -15,15 +15,16 @@
import InfiniteLoading, { type InfiniteEvent } from 'svelte-infinite-loading';
import { get } from 'svelte/store';
export let data;
let { data } = $props();
activePageStore.set(null);
let isSubscribed = false;
let isSubscribed = $state(false);
let tab: 'videos' | 'playlists' | 'streams' | 'shorts' = 'videos';
let tab: 'videos' | 'playlists' | 'streams' | 'shorts' = $state('videos');
let displayContent: ChannelContentPlaylists | ChannelContentVideos | undefined = undefined;
let displayContent: ChannelContentPlaylists | ChannelContentVideos | undefined =
$state(undefined);
async function loadMore(event: InfiniteEvent) {
if (typeof displayContent === 'undefined') return;
@@ -61,7 +62,7 @@
displayContent = await getChannelContent(data.channel.authorId, { type: tab });
}
let channelPfp: string;
let channelPfp: string | undefined = $state();
onMount(async () => {
displayContent = await getChannelContent(data.channel.authorId, { type: 'videos' });
@@ -118,7 +119,7 @@
<div class="grid no-padding">
<div class="s12 m12 l5">
<button
on:click={toggleSubscribed}
onclick={toggleSubscribed}
class:inverse-surface={!isSubscribed}
class:border={isSubscribed}
>
@@ -138,14 +139,14 @@
{#if !Capacitor.isNativePlatform()}
<a
href="#share"
on:click={async () => {
onclick={async () => {
await Clipboard.write({ string: location.href });
}}>{$_('player.share.materialiousLink')}</a
>
{/if}
<a
href="#share"
on:click={async () => {
onclick={async () => {
await Clipboard.write({
string: `https://www.youtube.com/channel/${data.channel.authorId}`
});
@@ -159,19 +160,19 @@
<div class="tabs left-align scroll">
{#if data.channel.tabs.includes('videos')}
<a class:active={tab === 'videos'} on:click={() => changeTab('videos')} href={`#video`}>
<a class:active={tab === 'videos'} onclick={() => changeTab('videos')} href={`#video`}>
<i>movie</i>
<span>{$_('videoTabs.videos')}</span>
</a>
{/if}
{#if data.channel.tabs.includes('shorts')}
<a class:active={tab === 'shorts'} on:click={() => changeTab('shorts')} href={`#short`}>
<a class:active={tab === 'shorts'} onclick={() => changeTab('shorts')} href={`#short`}>
<i>smartphone</i>
<span>{$_('videoTabs.shorts')}</span>
</a>
{/if}
{#if data.channel.tabs.includes('streams')}
<a class:active={tab === 'streams'} on:click={() => changeTab('streams')} href={`#stream`}>
<a class:active={tab === 'streams'} onclick={() => changeTab('streams')} href={`#stream`}>
<i>stream</i>
<span>{$_('videoTabs.streams')}</span>
</a>
@@ -179,7 +180,7 @@
{#if data.channel.tabs.includes('playlists')}
<a
class:active={tab === 'playlists'}
on:click={() => changeTab('playlists')}
onclick={() => changeTab('playlists')}
href={`#playlist`}
>
<i>playlist_add_check</i>
@@ -1,13 +1,19 @@
<script lang="ts">
import { getHashtag } from '$lib/api';
import type { Video } from '$lib/api/model.js';
import VideoList from '$lib/components/VideoList.svelte';
import { activePageStore } from '$lib/store';
import { onMount } from 'svelte';
import InfiniteLoading, { type InfiniteEvent } from 'svelte-infinite-loading';
export let data;
let { data } = $props();
let currentPage = 1;
$: videos = [...data.hashTagVideos.results];
let videos: Video[] = $state([]);
onMount(() => {
videos = [...data.hashTagVideos.results];
});
activePageStore.set('hashtag');
@@ -12,8 +12,8 @@
activePageStore.set('history');
let history: VideoPlay[] = [];
let loaded = false;
let history: VideoPlay[] = $state([]);
let loaded = $state(false);
let currentPage = 1;
@@ -61,7 +61,7 @@
<div class="space"></div>
<nav class="right-align">
<button
on:click={async () => {
onclick={async () => {
await deleteHistory();
if (get(synciousStore)) {
@@ -10,11 +10,11 @@
import { onMount } from 'svelte';
import { _ } from 'svelte-i18n';
export let data;
let { data } = $props();
activePageStore.set(null);
let videos: PlaylistPageVideo[] | undefined;
let videos: PlaylistPageVideo[] | undefined = $state();
if (data.playlist.videos.length > 0) {
videos = data.playlist.videos.sort((a: PlaylistPageVideo, b: PlaylistPageVideo) => {
return a.index < b.index ? -1 : 1;
@@ -59,7 +59,7 @@
<a
href={`/watch/${unsafeRandomItem(videos).videoId}?playlist=${data.playlist.playlistId}`}
on:click={() =>
onclick={() =>
playlistSettingsStore.set({ [data.playlist.playlistId]: { shuffle: true, loop: false } })}
class="button circle extra no-margin border"
>
@@ -91,14 +91,14 @@
{#if !Capacitor.isNativePlatform()}
<a
href="#share"
on:click={async () => {
onclick={async () => {
await Clipboard.write({ string: location.href });
}}>{$_('player.share.materialiousLink')}</a
>
{/if}
<a
href="#share"
on:click={async () => {
onclick={async () => {
await Clipboard.write({
string: `https://www.youtube.com/playlist?list=${data.playlist.playlistId}`
});
@@ -1,4 +1,6 @@
<script lang="ts">
import { preventDefault } from 'svelte/legacy';
import { deletePersonalPlaylist, getPersonalPlaylists, postPersonalPlaylist } from '$lib/api';
import ContentColumn from '$lib/components/ContentColumn.svelte';
import PlaylistThumbnail from '$lib/components/PlaylistThumbnail.svelte';
@@ -6,12 +8,12 @@
import { ui } from 'beercss';
import { _ } from 'svelte-i18n';
export let data;
let { data = $bindable() } = $props();
activePageStore.set('playlists');
let playlistPrivacy: 'public' | 'private' | 'unlisted' = 'public';
let playlistTitle: string;
let playlistTitle: string = $state('');
function onPrivacyChange(event: any) {
playlistPrivacy = event.currentTarget.value;
@@ -43,7 +45,7 @@
<nav class="right-align padding">
<button
on:click={async () => await removePlaylistItem(playlist.playlistId)}
onclick={async () => await removePlaylistItem(playlist.playlistId)}
class="tertiary square round"
>
<i>delete</i>
@@ -54,7 +56,7 @@
{/each}
<ContentColumn>
<article style="height: 100%;display: flex;align-items: center;justify-content: center;">
<button on:click={() => ui('#create-playlist')} class="round extra">
<button onclick={() => ui('#create-playlist')} class="round extra">
<i>add_circle</i>
<span>{$_('playlist.createPlaylist')}</span>
</button>
@@ -64,7 +66,7 @@
</div>
<dialog id="create-playlist">
<form on:submit|preventDefault={createPlaylist}>
<form onsubmit={preventDefault(createPlaylist)}>
<div class="field label border">
<input bind:value={playlistTitle} required name="title" type="text" />
<label for="title">{$_('title')}</label>
@@ -72,21 +74,21 @@
<div class="field middle-align">
<nav>
<label class="radio">
<input checked on:change={onPrivacyChange} value="public" type="radio" name="privacy" />
<input checked onchange={onPrivacyChange} value="public" type="radio" name="privacy" />
<span>{$_('playlist.public')}</span>
</label>
<label class="radio">
<input on:change={onPrivacyChange} type="radio" value="unlisted" name="privacy" />
<input onchange={onPrivacyChange} type="radio" value="unlisted" name="privacy" />
<span>{$_('playlist.unlisted')}</span>
</label>
<label class="radio">
<input on:change={onPrivacyChange} type="radio" value="private" name="privacy" />
<input onchange={onPrivacyChange} type="radio" value="private" name="privacy" />
<span>{$_('playlist.private')}</span>
</label>
</nav>
</div>
<nav class="right-align">
<button type="button" on:click={() => ui('#create-playlist')}>{$_('cancel')}</button>
<button type="button" onclick={() => ui('#create-playlist')}>{$_('cancel')}</button>
<button type="submit">{$_('create')}</button>
</nav>
</form>
@@ -10,7 +10,7 @@
import { _ } from 'svelte-i18n';
import InfiniteLoading, { type InfiniteEvent } from 'svelte-infinite-loading';
export let data;
let { data = $bindable() } = $props();
let currentPage = 1;
@@ -44,7 +44,7 @@
<a
class:active={data.searchType === 'all'}
href="#all"
on:click={async () => changeType('all')}
onclick={async () => changeType('all')}
>
<i>home</i>
<span>{$_('videoTabs.all')}</span>
@@ -52,7 +52,7 @@
<a
class:active={data.searchType === 'video'}
href="#videos"
on:click={async () => changeType('video')}
onclick={async () => changeType('video')}
>
<i>movie</i>
<span>{$_('videoTabs.videos')}</span>
@@ -60,7 +60,7 @@
<a
class:active={data.searchType === 'playlist'}
href="#playlists"
on:click={async () => changeType('playlist')}
onclick={async () => changeType('playlist')}
>
<i>playlist_add_check</i>
<span>{$_('videoTabs.playlists')}</span>
@@ -68,7 +68,7 @@
<a
class:active={data.searchType === 'channel'}
href="#channels"
on:click={async () => changeType('channel')}
onclick={async () => changeType('channel')}
>
<i>person</i>
<span>{$_('videoTabs.channels')}</span>
@@ -1,14 +1,20 @@
<script lang="ts">
import { getFeed } from '$lib/api/index';
import type { Notification, Video } from '$lib/api/model.js';
import VideoList from '$lib/components/VideoList.svelte';
import { activePageStore } from '$lib/store';
import { onMount } from 'svelte';
import { _ } from 'svelte-i18n';
import InfiniteLoading, { type InfiniteEvent } from 'svelte-infinite-loading';
export let data;
let { data } = $props();
let currentPage = 1;
$: videos = [...data.feed.notifications, ...data.feed.videos];
let videos: (Notification | Video)[] = $state([]);
onMount(() => {
videos = [...data.feed.notifications, ...data.feed.videos];
});
activePageStore.set('subscriptions');
@@ -32,6 +38,6 @@
</a>
</nav>
<VideoList bind:videos />
<VideoList {videos} />
<InfiniteLoading on:infinite={loadMore} />
@@ -6,11 +6,11 @@
activePageStore.set(null);
export let data;
let { data } = $props();
let subscriptions = structuredClone(data.subscriptions);
let subscriptions = $state(structuredClone(data.subscriptions));
let search: string;
let search: string = $state('');
const fuse = new Fuse(subscriptions, {
keys: ['author']
@@ -39,7 +39,7 @@
<div class="max field round suffix prefix small no-margin surface-variant">
<i class="front">search</i><input
bind:value={search}
on:input={searchSubs}
oninput={searchSubs}
type="text"
placeholder={$_('searchPlaceholder')}
/>
@@ -54,7 +54,7 @@
</h6></a
>
<div class="max"></div>
<button on:click={async () => unsubscribe(sub.authorId)} class="border">Unsubscribe</button>
<button onclick={async () => unsubscribe(sub.authorId)} class="border">Unsubscribe</button>
</nav>
</article>
{/each}
@@ -2,7 +2,7 @@
import VideoList from '$lib/components/VideoList.svelte';
import { activePageStore } from '$lib/store';
export let data;
let { data } = $props();
activePageStore.set('trending');
</script>
@@ -1,4 +1,6 @@
<script lang="ts">
import { run } from 'svelte/legacy';
import { goto } from '$app/navigation';
import {
addPlaylistVideo,
@@ -44,49 +46,51 @@
import type { MediaTimeUpdateEvent } from 'vidstack';
import type { MediaPlayerElement } from 'vidstack/elements';
export let data;
let { data = $bindable() } = $props();
let comments: Comments | null = null;
let comments: Comments | null = $state(null);
data.streamed.comments?.then((streamedComments) => {
comments = streamedComments;
});
let subscribed: boolean = false;
let subscribed: boolean = $state(false);
data.streamed.subscribed.then((streamedIsSubbed) => {
subscribed = streamedIsSubbed;
});
let personalPlaylists: PlaylistPage[] | null = null;
let personalPlaylists: PlaylistPage[] | null = $state(null);
data.streamed.personalPlaylists?.then((streamPlaylists) => (personalPlaylists = streamPlaylists));
activePageStore.set(null);
let playlistVideos: PlaylistPageVideo[] = [];
let playlist: PlaylistPage | null = null;
let playlistVideos: PlaylistPageVideo[] = $state([]);
let playlist: PlaylistPage | null = $state(null);
let loopPlaylist: boolean = false;
let shufflePlaylist: boolean = false;
let loopPlaylist: boolean = $state(false);
let shufflePlaylist: boolean = $state(false);
let theatreMode = get(playerTheatreModeByDefaultStore);
let theatreMode = $state(get(playerTheatreModeByDefaultStore));
let audioMode = get(playerListenByDefaultStore);
let audioMode = $state(get(playerListenByDefaultStore));
let player: MediaPlayerElement;
let segments: Segment[] = [];
let segments: Segment[] = $state([]);
let pauseTimerSeconds: number = -1;
let pauseTimerSeconds: number = $state(-1);
let showTranscript = false;
let showTranscript = $state(false);
let playerCurrentTime: number = 0;
let playerCurrentTime: number = $state(0);
// @ts-ignore
$: if (typeof player !== 'undefined') {
playerCurrentTime = player.currentTime;
player.addEventListener(
'time-update',
(event: MediaTimeUpdateEvent) => (playerCurrentTime = event.detail.currentTime)
);
}
run(() => {
if (typeof player !== 'undefined') {
playerCurrentTime = player.currentTime;
player.addEventListener(
'time-update',
(event: MediaTimeUpdateEvent) => (playerCurrentTime = event.detail.currentTime)
);
}
});
playlistSettingsStore.subscribe((playlistSetting) => {
if (!data.playlistId) return;
@@ -411,7 +415,7 @@
theatreMode = !theatreMode;
}
let pauseTimeout: NodeJS.Timeout;
let pauseTimeout: NodeJS.Timeout | undefined = $state();
function setPauseTimer() {
if (pauseTimeout) {
clearTimeout(pauseTimeout);
@@ -487,7 +491,7 @@
</a>
{#if $authStore}
<button
on:click={toggleSubscribed}
onclick={toggleSubscribed}
class:inverse-surface={!subscribed}
class:border={subscribed}
>
@@ -529,17 +533,17 @@
{/await}
<div>
<button on:click={() => (audioMode = !audioMode)} class:border={!audioMode}>
<button onclick={() => (audioMode = !audioMode)} class:border={!audioMode}>
<i>headphones</i>
<div class="tooltip">{$_('player.audioOnly')}</div>
</button>
<button on:click={toggleTheatreMode} class="m l" class:border={!theatreMode}>
<button onclick={toggleTheatreMode} class="m l" class:border={!theatreMode}>
<i>width_wide</i>
<div class="tooltip">{$_('player.theatreMode')}</div>
</button>
{#if data.video.lengthSeconds > 60 && !data.video.hlsUrl}
<button
on:click={() => {
onclick={() => {
if (pauseTimeout) {
clearTimeout(pauseTimeout);
}
@@ -553,7 +557,7 @@
</button>
{/if}
<button
on:click={() => ((showTranscript = !showTranscript), (theatreMode = false))}
onclick={() => ((showTranscript = !showTranscript), (theatreMode = false))}
class:border={!showTranscript}
>
<i>description</i>
@@ -578,7 +582,7 @@
{#each personalPlaylists as personalPlaylist}
<a
href="#add"
on:click={async () => await toggleVideoToPlaylist(personalPlaylist.playlistId)}
onclick={async () => await toggleVideoToPlaylist(personalPlaylist.playlistId)}
>
<nav>
<span class="max">{personalPlaylist.title}</span>
@@ -629,7 +633,7 @@
{#if data.content.timestamps.length > 0}
<h6 style="margin-bottom: .3em;">Chapters</h6>
{#each data.content.timestamps as timestamp}
<button on:click={() => (player.currentTime = timestamp.time)} class="timestamps"
<button onclick={() => (player.currentTime = timestamp.time)} class="timestamps"
>{timestamp.timePretty}
{#if !timestamp.title.startsWith('-')}
-
@@ -668,7 +672,7 @@
<Comment {comment} videoId={data.video.videoId}></Comment>
{/each}
{#if comments.continuation}
<button on:click={loadMoreComments} class="margin">{$_('loadMore')}</button>
<button onclick={loadMoreComments} class="margin">{$_('loadMore')}</button>
{/if}
</div>
</details>
@@ -696,7 +700,7 @@
<p><a href={`/channel/${playlist.authorId}`}>{playlist.author}</a></p>
<nav>
<button
on:click={() => {
onclick={() => {
if (!playlist) return;
loopPlaylist = !loopPlaylist;
@@ -713,7 +717,7 @@
</div>
</button>
<button
on:click={() => {
onclick={() => {
if (!playlist) return;
shufflePlaylist = !shufflePlaylist;
@@ -788,12 +792,12 @@
<nav class="right-align no-space">
<button
class="transparent link"
on:click={() => {
onclick={() => {
ui('#pause-timer');
pauseTimerSeconds = 0;
}}>Cancel</button
>
<button disabled={pauseTimerSeconds < 1} class="transparent link" on:click={setPauseTimer}
<button disabled={pauseTimerSeconds < 1} class="transparent link" onclick={setPauseTimer}
>Confirm</button
>
</nav>
@@ -2,7 +2,7 @@
import Player from '$lib/components/Player.svelte';
import type { MediaPlayerElement } from 'vidstack/elements';
export let data;
let { data } = $props();
let player: MediaPlayerElement;
</script>
+1 -1
View File
@@ -5,7 +5,7 @@ import json
import os
import re
LATEST_VERSION = "1.7.4"
LATEST_VERSION = "1.7.5"
WORKING_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "materialious")
ROOT_PACKAGE = os.path.join(WORKING_DIR, "package.json")