Load feeds quicker with stores

This commit is contained in:
WardPearce
2025-05-31 15:32:40 +12:00
parent 2ebdeed7c6
commit 48396bda84
21 changed files with 91 additions and 121 deletions
+2 -2
View File
@@ -7,8 +7,8 @@ android {
applicationId "us.materialio.app"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 111
versionName "1.8.7"
versionCode 112
versionName "1.8.8"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
@@ -56,7 +56,11 @@
<release version="1.8.7" date="2025-5-30">
<release version="1.8.8" date="2025-5-31">
<url>https://github.com/Materialious/Materialious/releases/tag/1.8.8</url>
</release>
<release version="1.8.7" date="2025-5-30">
<url>https://github.com/Materialious/Materialious/releases/tag/1.8.7</url>
</release>
<release version="1.8.6" date="2025-5-29">
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "Materialious",
"version": "1.8.7",
"version": "1.8.8",
"description": "Modern material design for Invidious.",
"author": {
"name": "Ward Pearce",
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "materialious",
"version": "1.8.7",
"version": "1.8.8",
"private": true,
"scripts": {
"dev": "vite dev",
@@ -71,4 +71,4 @@
"terser": "^5.34.1",
"youtubei.js": "^13.4.0"
}
}
}
@@ -1,26 +1,29 @@
<script lang="ts">
import Thumbnail from '$lib/components/Thumbnail.svelte';
import { _ } from 'svelte-i18n';
import { get } from 'svelte/store';
import { removePlaylistVideo } from '../api';
import type { Notification, PlaylistPageVideo, Video, VideoBase } from '../api/model';
import { authStore } from '../store';
import type { PlaylistPageVideo, Video, VideoBase } from '../api/model';
import { authStore, feedLastItemId } from '../store';
import ContentColumn from './ContentColumn.svelte';
import { onMount } from 'svelte';
interface Props {
videos?: VideoBase[] | Video[] | Notification[] | PlaylistPageVideo[];
videos?: (VideoBase | Video | PlaylistPageVideo)[];
playlistId?: string;
playlistAuthor?: string;
}
let { videos = [], playlistId = '', playlistAuthor = '' }: Props = $props();
let auth = get(authStore);
async function removePlaylistItem(indexId: string) {
if (!playlistId) return;
await removePlaylistVideo(playlistId, indexId);
}
onMount(() => {
if ($feedLastItemId)
document.getElementById($feedLastItemId)?.scrollIntoView({ behavior: 'instant' });
});
</script>
<div class="page right active">
@@ -28,9 +31,14 @@
<div class="grid">
{#each videos as video}
<ContentColumn>
<article class="no-padding" style="height: 100%;">
<article
class="no-padding"
style="height: 100%;"
id={video.videoId}
onclick={() => feedLastItemId.set(video.videoId)}
>
<Thumbnail {video} {playlistId} />
{#if auth && decodeURIComponent(auth.username) === playlistAuthor && 'indexId' in video}
{#if $authStore && decodeURIComponent($authStore.username) === playlistAuthor && 'indexId' in video}
<div class="right-align" style="margin: 1em .5em;">
<button
onclick={async () => removePlaylistItem(video.indexId)}
+22 -9
View File
@@ -4,6 +4,7 @@ import type { DataConnection } from 'peerjs';
import { persisted } from 'svelte-persisted-store';
import { writable, type Writable } from 'svelte/store';
import type { TitleCase } from './letterCasing';
import type { PlaylistPageVideo, Video, VideoBase } from './api/model';
function platformDependentDefault(givenValue: any, defaultValue: any): any {
if (typeof givenValue !== 'undefined' && typeof givenValue !== null) {
@@ -14,11 +15,14 @@ function platformDependentDefault(givenValue: any, defaultValue: any): any {
}
export const instanceStore: Writable<string> = persisted(
"invidiousInstance",
platformDependentDefault(import.meta.env.VITE_DEFAULT_INVIDIOUS_INSTANCE, 'https://invidious.materialio.us')
'invidiousInstance',
platformDependentDefault(
import.meta.env.VITE_DEFAULT_INVIDIOUS_INSTANCE,
'https://invidious.materialio.us'
)
);
export const authStore: Writable<null | { username: string; token: string; }> = persisted(
export const authStore: Writable<null | { username: string; token: string }> = persisted(
'authToken',
null
);
@@ -28,8 +32,6 @@ export const themeColorStore: Writable<null | string> = persisted('themeColor',
export const showWarningStore = persisted('showWarning', true);
export const activePageStore: Writable<string | null> = writable('home');
export const playerAutoPlayStore = persisted('autoPlay', true);
export const playerAlwaysLoopStore = persisted('alwaysLoop', false);
export const playerProxyVideosStore = persisted('proxyVideos', true);
@@ -44,13 +46,20 @@ export const playerDefaultLanguage = persisted('defaultLanguage', '');
export const returnYtDislikesStore = persisted('returnYtDislikes', false);
export const returnYTDislikesInstanceStore: Writable<string | null | undefined> = persisted(
'returnYTDislikesInstance',
platformDependentDefault(import.meta.env.VITE_DEFAULT_RETURNYTDISLIKES_INSTANCE, 'https://ryd-proxy.materialio.us')
platformDependentDefault(
import.meta.env.VITE_DEFAULT_RETURNYTDISLIKES_INSTANCE,
'https://ryd-proxy.materialio.us'
)
);
export const synciousStore = persisted('syncious', true);
export const synciousInstanceStore: Writable<string | null | undefined> = persisted(
'synciousInstance',
platformDependentDefault(import.meta.env.VITE_DEFAULT_SYNCIOUS_INSTANCE || import.meta.env.VITE_DEFAULT_API_EXTENDED_INSTANCE, 'https://extended-api.materialio.us')
platformDependentDefault(
import.meta.env.VITE_DEFAULT_SYNCIOUS_INSTANCE ||
import.meta.env.VITE_DEFAULT_API_EXTENDED_INSTANCE,
'https://extended-api.materialio.us'
)
);
export const interfaceRegionStore: Writable<string> = persisted('interfaceRegion', 'US');
@@ -96,10 +105,14 @@ export const deArrowThumbnailInstanceStore = persisted(
export const syncPartyPeerStore: Writable<Peer | null> = writable(null);
export const syncPartyConnectionsStore: Writable<DataConnection[] | null> = writable();
export const playlistSettingsStore: Writable<Record<string, { shuffle: boolean; loop: boolean; }>> =
export const playlistSettingsStore: Writable<Record<string, { shuffle: boolean; loop: boolean }>> =
writable({});
export const poTokenCacheStore: Writable<string | undefined> = writable();
export const searchHistoryStore: Writable<string[]> = persisted('searchHistory', []);
export const feedCacheStore: Writable<{
[key: string]: (VideoBase | Video | PlaylistPageVideo)[];
}> = writable({});
export const feedLastItemId: Writable<string | undefined> = writable(undefined);
@@ -1,8 +1,5 @@
<script lang="ts">
import { page } from '$app/stores';
import { activePageStore } from '$lib/store';
activePageStore.set(null);
</script>
<div class="space"></div>
+13 -47
View File
@@ -1,7 +1,7 @@
<script lang="ts">
import { afterNavigate, beforeNavigate, goto } from '$app/navigation';
import { navigating } from '$app/stores';
import { navigating, page } from '$app/stores';
import '$lib/android/http/androidRequests';
import colorTheme, { convertToHexColorCode } from '$lib/android/plugins/colorTheme';
import { getFeed } from '$lib/api/index';
@@ -16,7 +16,6 @@
import { bookmarkletLoadFromUrl, loadSettingsFromEnv } from '$lib/externalSettings';
import { getPages } from '$lib/navPages';
import {
activePageStore,
authStore,
darkModeStore,
instanceStore,
@@ -33,7 +32,7 @@
import 'beercss';
import ui from 'beercss';
import 'material-dynamic-colors';
import { onMount, tick } from 'svelte';
import { onMount } from 'svelte';
import { _ } from 'svelte-i18n';
import { get } from 'svelte/store';
import { pwaInfo } from 'virtual:pwa-info';
@@ -42,9 +41,6 @@
let mobileSearchShow = $state(false);
let currentPage: string | null = $state('');
activePageStore.subscribe((page) => (currentPage = page));
let isLoggedIn = $state(false);
authStore.subscribe((value) => {
isLoggedIn = value !== null;
@@ -120,41 +116,11 @@
notifications = feed.notifications;
}
let scrollPositions = new Map();
let scrollableRoot: HTMLElement | null = null;
beforeNavigate(({ from }) => {
if (from?.url && scrollableRoot) {
// Save the scroll position of the root element
scrollPositions.set(from.url.pathname, scrollableRoot.scrollTop);
}
});
// Restore the scroll position after navigating to a new page
afterNavigate(async ({ to, type }) => {
if (!scrollableRoot) return;
if (to?.url && scrollPositions.has(to.url.pathname)) {
// Check user went back
if (type === 'popstate') {
await tick();
scrollableRoot.scrollTo(0, scrollPositions.get(to.url.pathname) || 0);
} else {
scrollPositions.delete(to.url.pathname);
}
} else {
// Default behavior: scroll to top
scrollableRoot.scrollTo(0, 0);
}
});
onMount(async () => {
ui();
document.addEventListener('click', linkClickOverwrite);
scrollableRoot = document.querySelector('.root');
loadSettingsFromEnv();
// Should always be loaded after env settings
// So user preferences overwrite instance preferences.
@@ -205,11 +171,11 @@
<nav class="left m l small">
<header></header>
{#each getPages() as page}
{#if !page.requiresAuth || isLoggedIn}
<a href={page.href} class:active={currentPage === page.name.toLowerCase()}
><i>{page.icon}</i>
<div>{page.name}</div>
{#each getPages() as navPage}
{#if !navPage.requiresAuth || isLoggedIn}
<a href={navPage.href} class:active={$page.url.href.endsWith(navPage.href)}
><i>{navPage.icon}</i>
<div>{navPage.name}</div>
</a>
{/if}
{/each}
@@ -288,12 +254,12 @@
</nav>
<nav class="bottom s">
{#each getPages() as page}
{#if !page.requiresAuth || isLoggedIn}
<a class="round" href={page.href} class:active={currentPage === page.name.toLowerCase()}
><i>{page.icon}</i>
{#if currentPage === page.name.toLowerCase()}
<span style="font-size: .8em;">{page.name}</span>
{#each getPages() as navPage}
{#if !navPage.requiresAuth || isLoggedIn}
<a class="round" href={navPage.href} class:active={$page.url.href.endsWith(navPage.href)}
><i>{navPage.icon}</i>
{#if $page.url.href.endsWith(navPage.href)}
<span style="font-size: .8em;">{navPage.name}</span>
{/if}
</a>
{/if}
@@ -1,10 +1,7 @@
<script lang="ts">
import VideoList from '$lib/components/VideoList.svelte';
import { activePageStore } from '$lib/store';
let { data } = $props();
activePageStore.set('home');
</script>
{#if data.popularDisabled}
@@ -7,7 +7,7 @@
import VideoList from '$lib/components/VideoList.svelte';
import { getBestThumbnail, proxyGoogleImage } from '$lib/images';
import { cleanNumber } from '$lib/numbers';
import { activePageStore, authStore, interfaceLowBandwidthMode } from '$lib/store';
import { authStore, interfaceLowBandwidthMode } from '$lib/store';
import { Clipboard } from '@capacitor/clipboard';
import { Capacitor } from '@capacitor/core';
import { onMount } from 'svelte';
@@ -17,8 +17,6 @@
let { data } = $props();
activePageStore.set(null);
let isSubscribed = $state(false);
let tab: 'videos' | 'playlists' | 'streams' | 'shorts' = $state('videos');
@@ -2,7 +2,6 @@
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';
@@ -15,8 +14,6 @@
videos = [...data.hashTagVideos.results];
});
activePageStore.set('hashtag');
async function loadMore(event: InfiniteEvent) {
currentPage++;
const hashtagVideos = (await getHashtag(data.hashtag, currentPage)).results;
@@ -3,15 +3,13 @@
import type { VideoPlay } from '$lib/api/model';
import PageLoading from '$lib/components/PageLoading.svelte';
import VideoList from '$lib/components/VideoList.svelte';
import { activePageStore, synciousStore } from '$lib/store';
import { synciousStore } from '$lib/store';
import { error } from '@sveltejs/kit';
import { onMount } from 'svelte';
import { _ } from 'svelte-i18n';
import InfiniteLoading, { type InfiniteEvent } from 'svelte-infinite-loading';
import { get } from 'svelte/store';
activePageStore.set('history');
let history: VideoPlay[] = $state([]);
let loaded = $state(false);
@@ -4,7 +4,7 @@
import VideoList from '$lib/components/VideoList.svelte';
import { unsafeRandomItem } from '$lib/misc';
import { cleanNumber } from '$lib/numbers';
import { activePageStore, playlistSettingsStore } from '$lib/store';
import { playlistSettingsStore } from '$lib/store';
import { Clipboard } from '@capacitor/clipboard';
import { Capacitor } from '@capacitor/core';
import { onMount } from 'svelte';
@@ -12,8 +12,6 @@
let { data } = $props();
activePageStore.set(null);
let videos: PlaylistPageVideo[] | undefined = $state();
if (data.playlist.videos.length > 0) {
videos = data.playlist.videos.sort((a: PlaylistPageVideo, b: PlaylistPageVideo) => {
@@ -4,7 +4,6 @@
import { deletePersonalPlaylist, getPersonalPlaylists, postPersonalPlaylist } from '$lib/api';
import ContentColumn from '$lib/components/ContentColumn.svelte';
import PlaylistThumbnail from '$lib/components/PlaylistThumbnail.svelte';
import { activePageStore } from '$lib/store';
import { ui } from 'beercss';
import { _ } from 'svelte-i18n';
@@ -12,8 +11,6 @@
let playlists = $state(data);
activePageStore.set('playlists');
let playlistPrivacy: 'public' | 'private' | 'unlisted' = 'public';
let playlistTitle: string = $state('');
@@ -6,7 +6,6 @@
import PageLoading from '$lib/components/PageLoading.svelte';
import PlaylistThumbnail from '$lib/components/PlaylistThumbnail.svelte';
import Thumbnail from '$lib/components/Thumbnail.svelte';
import { activePageStore } from '$lib/store';
import { _ } from 'svelte-i18n';
import InfiniteLoading, { type InfiniteEvent } from 'svelte-infinite-loading';
@@ -16,8 +15,6 @@
let currentPage = 1;
activePageStore.set(null);
async function changeType(type: 'playlist' | 'all' | 'video' | 'channel') {
searchResults.searchType = type;
currentPage = 1;
@@ -1,22 +1,15 @@
<script lang="ts">
import { getFeed } from '$lib/api/index';
import type { Notification, Video } from '$lib/api/model.js';
import type { PlaylistPageVideo, Video, VideoBase } from '$lib/api/model.js';
import VideoList from '$lib/components/VideoList.svelte';
import { activePageStore } from '$lib/store';
import { onMount } from 'svelte';
import { feedCacheStore } from '$lib/store';
import { _ } from 'svelte-i18n';
import InfiniteLoading, { type InfiniteEvent } from 'svelte-infinite-loading';
let { data } = $props();
let currentPage = 1;
let videos: (Notification | Video)[] = $state([]);
onMount(() => {
videos = [...data.feed.notifications, ...data.feed.videos];
});
activePageStore.set('subscriptions');
let videos: (VideoBase | Video | PlaylistPageVideo)[] = $state(data.videos);
async function loadMore(event: InfiniteEvent) {
currentPage++;
@@ -25,6 +18,7 @@
event.detail.complete();
} else {
videos = [...videos, ...feed.videos, ...feed.notifications];
feedCacheStore.set({ subscription: videos });
event.detail.loaded();
}
}
@@ -1,15 +1,30 @@
import { getFeed } from '$lib/api/index';
import { feedCacheStore } from '$lib/store';
import { error } from '@sveltejs/kit';
import { get } from 'svelte/store';
export async function load({ params }) {
let feed;
export async function load() {
let videos = get(feedCacheStore).subscription;
try {
feed = await getFeed(100, 1);
} catch (errorMessage: any) {
error(500, errorMessage);
if (!videos) {
let feeds;
try {
feeds = await getFeed(100, 1);
} catch (errorMessage: any) {
error(500, errorMessage);
}
videos = [...feeds.notifications, ...feeds.videos];
feedCacheStore.set({ subscription: videos });
} else {
await getFeed(100, 1).then((feeds) => {
const newVideos = [...feeds.notifications, ...feeds.videos];
feedCacheStore.set({ subscription: { ...Array.from(new Set(newVideos)), ...videos } });
});
}
return {
feed: feed
videos: videos
};
}
@@ -1,11 +1,8 @@
<script lang="ts">
import { deleteUnsubscribe } from '$lib/api';
import { activePageStore } from '$lib/store';
import Fuse from 'fuse.js';
import { _ } from 'svelte-i18n';
activePageStore.set(null);
let { data } = $props();
let subscriptions = $state(structuredClone(data.subscriptions));
@@ -1,10 +1,7 @@
<script lang="ts">
import VideoList from '$lib/components/VideoList.svelte';
import { activePageStore } from '$lib/store';
let { data } = $props();
activePageStore.set('trending');
</script>
<VideoList videos={data.trending} />
@@ -21,7 +21,6 @@
import { cleanNumber, humanizeSeconds, numberWithCommas } from '$lib/numbers';
import type { PlayerEvents } from '$lib/player.js';
import {
activePageStore,
authStore,
interfaceAutoExpandComments,
interfaceAutoExpandDesc,
@@ -56,8 +55,6 @@
let personalPlaylists: PlaylistPage[] | null = $state(null);
data.streamed.personalPlaylists?.then((streamPlaylists) => (personalPlaylists = streamPlaylists));
activePageStore.set(null);
let playlistVideos: PlaylistPageVideo[] = $state([]);
let playlist: PlaylistPage | null = $state(null);
+1 -1
View File
@@ -3,7 +3,7 @@ import os
import re
from datetime import datetime
LATEST_VERSION = "1.8.7"
LATEST_VERSION = "1.8.8"
RELEASE_DATE = datetime.now().strftime("%Y-%-m-%d") # Format: YYYY-M-D
WORKING_DIR = os.path.join(