Channel sorting options

This commit is contained in:
WardPearce
2025-11-07 13:52:23 +13:00
parent 44126a3c78
commit 562f7fec24
4 changed files with 244 additions and 207 deletions
+7 -1
View File
@@ -145,11 +145,15 @@ export async function getChannel(
return await resp.json();
}
export type channelSortBy = 'oldest' | 'newest' | 'popular';
export type channelContentTypes = 'videos' | 'playlists' | 'streams' | 'shorts';
export async function getChannelContent(
channelId: string,
parameters: {
type?: 'videos' | 'playlists' | 'streams' | 'shorts';
type?: channelContentTypes;
continuation?: string;
sortBy?: channelSortBy;
},
fetchOptions?: RequestInit
): Promise<ChannelContentVideos | ChannelContentPlaylists> {
@@ -160,6 +164,8 @@ export async function getChannelContent(
if (typeof parameters.continuation !== 'undefined')
url.searchParams.set('continuation', parameters.continuation);
if (typeof parameters.sortBy !== 'undefined') url.searchParams.set('sort_by', parameters.sortBy);
const resp = await fetchErrorHandle(await fetch(url.toString(), fetchOptions));
return await resp.json();
}
@@ -25,9 +25,6 @@
if (get(interfaceLowBandwidthMode)) return;
let imgSrc = '';
console.log(playlist);
if (playlist.videos.length > 0) {
imgSrc = getBestThumbnail(playlist.videos[0].videoThumbnails) || '';
} else if (playlist.playlistThumbnail.length > 0) {
+200 -197
View File
@@ -1,199 +1,202 @@
{
"enabled": "Enabled",
"disabled": "Disabled",
"popularPageDisabled": "Popular page has been disabled by Admins",
"premium": "Premium YouTube content can't be watched on Materialious.",
"copyUrl": "Copy URL",
"loadMore": "Load more",
"views": "views",
"login": "Login",
"recommendedVideos": "Recommended Videos",
"playlistVideos": "Playlist Videos",
"invidiousLogin": "Please log in with your Invidious account",
"invalidInstance": "Please verify the URL. If it's correct, the instance may be down or may not support third-party clients.",
"invidiousBlockWarning": "Invidious is currently being blocked by Google. If videos aren't loading for this instance, please use this instance on Materialious on {android} or {desktop} to get around this with local video fallback.",
"videos": "videos",
"cancel": "Cancel",
"create": "Create",
"filters": {
"filters": "Filters",
"type": "Type",
"uploadDate": "Upload date",
"duration": "Duration",
"sortBy": "Sort By",
"features": "Features"
},
"title": "Title",
"searchPlaceholder": "Search",
"delete": "Delete",
"donate": "Donate",
"deleteAllHistory": "Delete all history",
"skipping": "Skipping",
"comments": "comments",
"reply": "reply",
"replies": "replies",
"region": "Region",
"contributors": "Contributors",
"noResult": "No results",
"version": "Version",
"language": "Language",
"channels": "channels",
"transcript": "Transcript",
"noCaptionData": "Captions don't have any data.",
"selectLang": "Select language",
"letterCase": "Letter case for titles",
"hideReplies": "Hide replies",
"hideVideo": "Hide video",
"defaultPage": "Default page",
"syncParty": {
"userJoined": "User joined your watch party.",
"userLeft": "User left your watch party.",
"userOnPrivatePage": "User currently viewing a private page, syncing will resume shorty."
},
"videoTabs": {
"all": "All",
"videos": "Videos",
"playlists": "Playlists",
"channels": "Channels",
"shorts": "Shorts",
"streams": "Streams"
},
"subscriptions": {
"manageSubscriptions": "Manage subscriptions"
},
"playlist": {
"createPlaylist": "Create playlist",
"public": "Public",
"unlisted": "Unlisted",
"private": "Private",
"playVideos": "Play videos",
"shuffleVideos": "Play videos shuffled",
"loopPlaylist": "Loop playlist",
"next": "Next",
"previous": "Previous"
},
"thumbnail": {
"live": "LIVE",
"failedToLoadImage": "Failed to load image"
},
"pages": {
"home": "Home",
"trending": "Trending",
"subscriptions": "Subscriptions",
"history": "History",
"playlists": "Playlists"
},
"subscribers": "subscribers",
"unsubscribe": "Unsubscribe",
"loginRequired": "Login required",
"player": {
"audioOnly": "Audio only",
"theatreMode": "Theatre mode",
"pauseTimer": "Pause timer",
"pauseVideoIn": "Pause video in ",
"preferredQuality": "Preferred quality",
"premiere": "Video will be available",
"chapters": "Chapters",
"share": {
"title": "Share",
"materialiousLink": "Copy Materialious link",
"invidiousRedirect": "Copy Invidious redirect link",
"youtubeLink": "Copy Youtube link",
"includeTimestamp": "Timestamped"
},
"download": "Download",
"downloadSteps": {
"merging": "Merging video & audio together",
"video": "Downloading video",
"audio": "Downloading audio",
"ffmpeg": "Downloading ffmpeg library & components",
"classWorker": "Downloading ffmpeg class worker",
"core": "Downloading ffmpeg core",
"wasm": "Downloading ffmpeg wasm",
"worker": "Downloading ffmpeg worker"
},
"noPlaylists": "No playlists",
"unableToLoadComments": "Unable to load comments",
"addToPlaylist": "Add to playlist",
"youtubeJsFallBack": "Local video fallback being used",
"defaultLanguage": "Default Language",
"youtubeJsLoading": "Video failed to load, attempting to reload with local video fallback.",
"retryText": "Video failed to load, how would you like to continue?",
"enableYoutubejsTemp": "Use local video fallback once",
"enableYoutubejsPerm": "Always allow local video fallback"
},
"layout": {
"interface": "Interface",
"star": "Star us on Github!",
"syncParty": "Sync party",
"about": "About",
"syncPartyWarning": "Please note your IP will be visible to users you invite.",
"startSyncParty": "Start sync party",
"endSyncParty": "End sync party",
"notifications": "Notifications",
"noNewNotifications": "No new notifications here",
"settings": "Settings",
"login": "Login",
"logout": "Logout",
"customize": "Customize",
"lowBandwidthMode": "Low bandwidth mode",
"displayThumbnailAvatars": "Thumbnail avatars (Slow)",
"searchHistory": "Search history",
"companionUrl": "Companion URL",
"theme": {
"theme": "Theme",
"darkMode": "Dark mode",
"lightMode": "Light mode",
"AmoledTheme": "Amoled theme",
"color": "Color"
},
"searchSuggestions": "Search suggestions",
"previewVideoOnHover": "Preview video on hover",
"expandDescription": "Expand description by default",
"expandChapters": "Expand chapters by default",
"expandComments": "Expand comments by default",
"allowInsecureRequests": "Allow insecure requests",
"disableAutoUpdate": "Disable automatic updates",
"player": {
"title": "Player",
"autoPlay": "Autoplay video",
"alwaysLoopVideo": "Always loop video",
"savePlaybackPosition": "Save playback position",
"listenByDefault": "Listen by default",
"ccByDefault": "Subtitles on by default",
"theatreModeByDefault": "Theatre mode by default",
"autoPlayNextByDefault": "Autoplay next by default",
"miniPlayer": "Mini player",
"proxyVideos": "Proxy videos",
"backgroundPlay": "Play audio in background",
"dash": "Dash",
"silenceSkipper": "Silence skipper (Experimental)",
"localVideoFallback": "Local video fallback",
"youtubeJsFallback": "Local video processing fallback",
"youtubeJsAlways": "Always use",
"lockOrientation": "Orientation locked to aspect ratio",
"defaultPlaybackSpeed": "Default playback speed",
"playerStatistics": "Show player statistics by default"
},
"bookmarklet": "Bookmarklet",
"instanceUrl": "Instance URL",
"sponsors": {
"sponsor": "Sponsor",
"disableToast": "Disable toast",
"disableTimeline": "Disable segments on timeline",
"Catagories": "Categories",
"unpaidSelfPromotion": "Unpaid/Self Promotion",
"interactionReminder": "Interaction Reminder (Subscribe)",
"intermissionIntroAnimation": "Intermission/Intro Animation",
"credits": "Endcards/Credits",
"preViewRecapHook": "Preview/Recap/Hook",
"tangentJokes": "Filler Tangent/Jokes"
},
"deArrow": {
"title": "DeArrow",
"thumbnailInstanceUrl": "Thumbnail instance URL",
"titleOnly": "Title only"
}
},
"subscribe": "Subscribe"
"enabled": "Enabled",
"disabled": "Disabled",
"popularPageDisabled": "Popular page has been disabled by Admins",
"premium": "Premium YouTube content can't be watched on Materialious.",
"copyUrl": "Copy URL",
"loadMore": "Load more",
"views": "views",
"login": "Login",
"recommendedVideos": "Recommended Videos",
"playlistVideos": "Playlist Videos",
"invidiousLogin": "Please log in with your Invidious account",
"invalidInstance": "Please verify the URL. If it's correct, the instance may be down or may not support third-party clients.",
"invidiousBlockWarning": "Invidious is currently being blocked by Google. If videos aren't loading for this instance, please use this instance on Materialious on {android} or {desktop} to get around this with local video fallback.",
"videos": "videos",
"cancel": "Cancel",
"create": "Create",
"filters": {
"filters": "Filters",
"type": "Type",
"uploadDate": "Upload date",
"duration": "Duration",
"sortBy": "Sort By",
"features": "Features"
},
"title": "Title",
"searchPlaceholder": "Search",
"delete": "Delete",
"donate": "Donate",
"deleteAllHistory": "Delete all history",
"skipping": "Skipping",
"comments": "comments",
"reply": "reply",
"replies": "replies",
"region": "Region",
"contributors": "Contributors",
"noResult": "No results",
"version": "Version",
"language": "Language",
"channels": "channels",
"transcript": "Transcript",
"noCaptionData": "Captions don't have any data.",
"selectLang": "Select language",
"letterCase": "Letter case for titles",
"hideReplies": "Hide replies",
"hideVideo": "Hide video",
"defaultPage": "Default page",
"newest": "Newest",
"oldest": "Oldest",
"popular": "Popular",
"syncParty": {
"userJoined": "User joined your watch party.",
"userLeft": "User left your watch party.",
"userOnPrivatePage": "User currently viewing a private page, syncing will resume shorty."
},
"videoTabs": {
"all": "All",
"videos": "Videos",
"playlists": "Playlists",
"channels": "Channels",
"shorts": "Shorts",
"streams": "Streams"
},
"subscriptions": {
"manageSubscriptions": "Manage subscriptions"
},
"playlist": {
"createPlaylist": "Create playlist",
"public": "Public",
"unlisted": "Unlisted",
"private": "Private",
"playVideos": "Play videos",
"shuffleVideos": "Play videos shuffled",
"loopPlaylist": "Loop playlist",
"next": "Next",
"previous": "Previous"
},
"thumbnail": {
"live": "LIVE",
"failedToLoadImage": "Failed to load image"
},
"pages": {
"home": "Home",
"trending": "Trending",
"subscriptions": "Subscriptions",
"history": "History",
"playlists": "Playlists"
},
"subscribers": "subscribers",
"unsubscribe": "Unsubscribe",
"loginRequired": "Login required",
"player": {
"audioOnly": "Audio only",
"theatreMode": "Theatre mode",
"pauseTimer": "Pause timer",
"pauseVideoIn": "Pause video in ",
"preferredQuality": "Preferred quality",
"premiere": "Video will be available",
"chapters": "Chapters",
"share": {
"title": "Share",
"materialiousLink": "Copy Materialious link",
"invidiousRedirect": "Copy Invidious redirect link",
"youtubeLink": "Copy Youtube link",
"includeTimestamp": "Timestamped"
},
"download": "Download",
"downloadSteps": {
"merging": "Merging video & audio together",
"video": "Downloading video",
"audio": "Downloading audio",
"ffmpeg": "Downloading ffmpeg library & components",
"classWorker": "Downloading ffmpeg class worker",
"core": "Downloading ffmpeg core",
"wasm": "Downloading ffmpeg wasm",
"worker": "Downloading ffmpeg worker"
},
"noPlaylists": "No playlists",
"unableToLoadComments": "Unable to load comments",
"addToPlaylist": "Add to playlist",
"youtubeJsFallBack": "Local video fallback being used",
"defaultLanguage": "Default Language",
"youtubeJsLoading": "Video failed to load, attempting to reload with local video fallback.",
"retryText": "Video failed to load, how would you like to continue?",
"enableYoutubejsTemp": "Use local video fallback once",
"enableYoutubejsPerm": "Always allow local video fallback"
},
"layout": {
"interface": "Interface",
"star": "Star us on Github!",
"syncParty": "Sync party",
"about": "About",
"syncPartyWarning": "Please note your IP will be visible to users you invite.",
"startSyncParty": "Start sync party",
"endSyncParty": "End sync party",
"notifications": "Notifications",
"noNewNotifications": "No new notifications here",
"settings": "Settings",
"login": "Login",
"logout": "Logout",
"customize": "Customize",
"lowBandwidthMode": "Low bandwidth mode",
"displayThumbnailAvatars": "Thumbnail avatars (Slow)",
"searchHistory": "Search history",
"companionUrl": "Companion URL",
"theme": {
"theme": "Theme",
"darkMode": "Dark mode",
"lightMode": "Light mode",
"AmoledTheme": "Amoled theme",
"color": "Color"
},
"searchSuggestions": "Search suggestions",
"previewVideoOnHover": "Preview video on hover",
"expandDescription": "Expand description by default",
"expandChapters": "Expand chapters by default",
"expandComments": "Expand comments by default",
"allowInsecureRequests": "Allow insecure requests",
"disableAutoUpdate": "Disable automatic updates",
"player": {
"title": "Player",
"autoPlay": "Autoplay video",
"alwaysLoopVideo": "Always loop video",
"savePlaybackPosition": "Save playback position",
"listenByDefault": "Listen by default",
"ccByDefault": "Subtitles on by default",
"theatreModeByDefault": "Theatre mode by default",
"autoPlayNextByDefault": "Autoplay next by default",
"miniPlayer": "Mini player",
"proxyVideos": "Proxy videos",
"backgroundPlay": "Play audio in background",
"dash": "Dash",
"silenceSkipper": "Silence skipper (Experimental)",
"localVideoFallback": "Local video fallback",
"youtubeJsFallback": "Local video processing fallback",
"youtubeJsAlways": "Always use",
"lockOrientation": "Orientation locked to aspect ratio",
"defaultPlaybackSpeed": "Default playback speed",
"playerStatistics": "Show player statistics by default"
},
"bookmarklet": "Bookmarklet",
"instanceUrl": "Instance URL",
"sponsors": {
"sponsor": "Sponsor",
"disableToast": "Disable toast",
"disableTimeline": "Disable segments on timeline",
"Catagories": "Categories",
"unpaidSelfPromotion": "Unpaid/Self Promotion",
"interactionReminder": "Interaction Reminder (Subscribe)",
"intermissionIntroAnimation": "Intermission/Intro Animation",
"credits": "Endcards/Credits",
"preViewRecapHook": "Preview/Recap/Hook",
"tangentJokes": "Filler Tangent/Jokes"
},
"deArrow": {
"title": "DeArrow",
"thumbnailInstanceUrl": "Thumbnail instance URL",
"titleOnly": "Title only"
}
},
"subscribe": "Subscribe"
}
@@ -1,9 +1,14 @@
<script lang="ts">
import { amSubscribed, deleteUnsubscribe, getChannelContent, postSubscribe } from '$lib/api';
import {
amSubscribed,
deleteUnsubscribe,
getChannelContent,
postSubscribe,
type channelContentTypes,
type channelSortBy
} from '$lib/api';
import type { ChannelContentPlaylists, ChannelContentVideos } from '$lib/api/model';
import ContentColumn from '$lib/components/ContentColumn.svelte';
import PageLoading from '$lib/components/PageLoading.svelte';
import PlaylistThumbnail from '$lib/components/PlaylistThumbnail.svelte';
import { getBestThumbnail, proxyGoogleImage } from '$lib/images';
import { cleanNumber } from '$lib/numbers';
import { authStore, interfaceLowBandwidthMode, isAndroidTvStore } from '$lib/store';
@@ -19,7 +24,10 @@
let isSubscribed = $state(false);
let tab: 'videos' | 'playlists' | 'streams' | 'shorts' = $state('videos');
let tab: channelContentTypes = $state('videos');
let sortBy: channelSortBy = $state('newest');
const sortByOptions: channelSortBy[] = ['newest', 'oldest', 'popular'];
let displayContent: ChannelContentPlaylists | ChannelContentVideos | undefined =
$state(undefined);
@@ -34,7 +42,8 @@
const newContent = await getChannelContent(data.channel.authorId, {
type: tab,
continuation: displayContent.continuation
continuation: displayContent.continuation,
sortBy: sortBy
});
if ('videos' in newContent && 'videos' in displayContent) {
if (displayContent.continuation === newContent.continuation) {
@@ -62,7 +71,10 @@
let channelPfp: string | undefined = $state();
onMount(async () => {
displayContent = await getChannelContent(data.channel.authorId, { type: 'videos' });
displayContent = await getChannelContent(data.channel.authorId, {
type: 'videos',
sortBy: sortBy
});
if (!get(interfaceLowBandwidthMode)) {
const channelPfpResp = await fetch(
@@ -201,6 +213,25 @@
</div>
</div>
<nav class="group">
{#each sortByOptions as sortingOption}
<button
class="no-round"
onclick={async () => {
sortBy = sortingOption;
displayContent = await getChannelContent(data.channel.authorId, {
type: tab,
sortBy: sortBy
});
}}
class:active={sortBy === sortingOption}>{$_(sortingOption)}</button
>
{/each}
</nav>
<div class="space"></div>
{#if displayContent}
{#if 'videos' in displayContent}
<ItemsList items={displayContent.videos} />