Merge pull request #104 from WardPearce/update/playlist-minor-syling

Update/playlist minor syling
This commit is contained in:
Ward
2024-04-11 22:30:43 +12:00
committed by GitHub
11 changed files with 181 additions and 63 deletions
+2 -1
View File
@@ -7,5 +7,6 @@
"materialious/src/lib/i18n",
"materialious/src/lib/i18n/locales"
],
"i18n-ally.extract.autoDetect": true
"i18n-ally.extract.autoDetect": true,
"i18n-ally.keystyle": "nested"
}
@@ -69,7 +69,9 @@
><div class="bold">{truncate(playlist.title)}</div></a
>
<div>
<a href={`/channel/${playlist.authorId}`}>{playlist.author}</a>
<a class:link-disabled={playlist.authorId === null} href={`/channel/${playlist.authorId}`}
>{playlist.author}</a
>
</div>
</div>
</nav>
+8 -4
View File
@@ -16,11 +16,15 @@
export let video: VideoBase | Video | Notification | PlaylistPageVideo;
export let playlistId: string = '';
let watchUrl = `/watch/${video.videoId}` + (playlistId ? `?playlist=${playlistId}` : '');
let watchUrl = new URL(`${import.meta.env.VITE_DEFAULT_FRONTEND_URL}/watch/${video.videoId}`);
if (playlistId !== '') {
watchUrl.searchParams.set('playlist', playlistId);
}
syncPartyPeer.subscribe((peer) => {
if (peer) {
watchUrl += (playlistId ? '&' : '?') + `sync=${peer.id}`;
watchUrl.searchParams.set('sync', peer.id);
}
});
@@ -140,7 +144,7 @@
<a
class="wave"
style="width: 100%; overflow: hidden;min-height:100px;"
href={watchUrl}
href={watchUrl.toString()}
data-sveltekit-preload-data="off"
on:click={syncChangeVideo}
>
@@ -179,7 +183,7 @@
<div class="small-padding">
<nav class="no-margin">
<div class="max">
<a href={watchUrl}><div class="bold">{truncate(video.title)}</div></a>
<a href={watchUrl.toString()}><div class="bold">{truncate(video.title)}</div></a>
<div>
<a href={`/channel/${video.authorId}`}>{video.author}</a
>{#if !('publishedText' in video) && 'viewCountText' in video}
+5 -2
View File
@@ -26,7 +26,10 @@
"createPlaylist": "Create playlist",
"public": "Public",
"unlisted": "Unlisted",
"private": "Private"
"private": "Private",
"playVideos": "Play videos",
"shuffleVideos": "Play videos shuffled",
"loopPlaylist": "Loop playlist"
},
"thumbnail": {
"live": "LIVE",
@@ -107,4 +110,4 @@
},
"bookmarklet": "Bookmarklet"
}
}
}
+21 -2
View File
@@ -1,4 +1,7 @@
import { pushState } from '$app/navigation';
import { page } from '$app/stores';
import humanNumber from 'human-number';
import { get } from 'svelte/store';
export function truncate(value: string, maxLength: number = 50): string {
return value.length > maxLength ? `${value.substring(0, maxLength)}...` : value;
@@ -37,11 +40,11 @@ export function videoLength(lengthSeconds: number): string {
export interface PhasedDescription {
description: string;
timestamps: { title: string; time: number; timePretty: string }[];
timestamps: { title: string; time: number; timePretty: string; }[];
}
export function phaseDescription(content: string): PhasedDescription {
const timestamps: { title: string; time: number; timePretty: string }[] = [];
const timestamps: { title: string; time: number; timePretty: string; }[] = [];
const lines = content.split('\n');
const urlRegex = /<a href="([^"]+)"/;
@@ -105,3 +108,19 @@ export function proxyVideoUrl(source: string): string {
return rawSrc.toString();
}
export function unsafeRandomItem(array: any[]): any {
return array[Math.floor(Math.random() * array.length)];
}
export function setWindowQueryFlag(key: string, value: string) {
const currentPage = get(page);
currentPage.url.searchParams.set(key, value);
pushState(currentPage.url, currentPage.state);
}
export function removeWindowQueryFlag(key: string) {
const currentPage = get(page);
currentPage.url.searchParams.delete(key);
pushState(currentPage.url, currentPage.state);
}
+6 -10
View File
@@ -7,6 +7,7 @@
import Search from '$lib/Search.svelte';
import Thumbnail from '$lib/Thumbnail.svelte';
import { bookmarkletLoadFromUrl, bookmarkletSaveToUrl } from '$lib/bookmarklet';
import { removeWindowQueryFlag, setWindowQueryFlag } from '$lib/misc';
import type { PlayerEvents } from '$lib/player';
import 'beercss';
import 'material-dynamic-colors';
@@ -109,7 +110,7 @@
function changeVideoEvent(conn: DataConnection) {
conn.on('data', (data) => {
const events = data as PlayerEvents;
const currentUrl = new URL(location.href);
const currentUrl = get(page).url;
events.events.forEach((event) => {
if (event.type === 'change-video' && event.videoId) {
@@ -121,20 +122,15 @@
}
async function startWatchSync() {
const currentUrl = new URL(location.href);
if ($syncPartyPeer) {
$syncPartyPeer.destroy();
syncPartyPeer.set(null);
currentUrl.searchParams.delete('sync');
window.history.pushState({ path: currentUrl.toString() }, '', currentUrl.toString());
removeWindowQueryFlag('sync');
return;
}
const peerId = crypto.randomUUID();
currentUrl.searchParams.set('sync', peerId);
window.history.pushState({ path: currentUrl.toString() }, '', currentUrl.toString());
setWindowQueryFlag('sync', peerId);
$syncPartyPeer = new Peer(peerId);
if ($syncPartyPeer) {
@@ -248,7 +244,7 @@
loadNotifications().catch(() => auth.set(null));
}
const currentUrl = new URL(location.href);
const currentUrl = get(page).url;
const syncId = currentUrl.searchParams.get('sync');
if (syncId) {
$syncPartyPeer = new Peer(crypto.randomUUID());
@@ -486,7 +482,7 @@
</nav>
</div>
<div class="field no-margin">
<div class="field no-margin m l">
<nav class="no-padding">
<div class="max">
<div>{$_('layout.player.theatreModeByDefault')}</div>
@@ -2,10 +2,10 @@
import { getPlaylist } from '$lib/Api/index';
import type { PlaylistPageVideo } from '$lib/Api/model.js';
import VideoList from '$lib/VideoList.svelte';
import { cleanNumber } from '$lib/misc.js';
import { cleanNumber, unsafeRandomItem } from '$lib/misc.js';
import { onMount } from 'svelte';
import { _ } from 'svelte-i18n';
import { activePage } from '../../../store';
import { activePage, playlistSettings } from '../../../store';
export let data;
@@ -36,6 +36,31 @@
<div class="space"></div>
<article>
{#if videos}
<nav>
<a
href={`/watch/${videos[0].videoId}?playlist=${data.playlist.playlistId}`}
class="button circle extra no-margin"
>
<i>play_arrow</i>
<div class="tooltip bottom">
{$_('playlist.playVideos')}
</div>
</a>
<a
href={`/watch/${unsafeRandomItem(videos).videoId}?playlist=${data.playlist.playlistId}`}
on:click={() =>
playlistSettings.set({ [data.playlist.playlistId]: { shuffle: true, loop: false } })}
class="button circle extra no-margin border"
>
<i>shuffle</i>
<div class="tooltip bottom">
{$_('playlist.shuffleVideos')}
</div>
</a>
</nav>
{/if}
<h3>{data.playlist.title}</h3>
<p>
{cleanNumber(data.playlist.viewCount)}
@@ -43,7 +68,10 @@
{$_('videos')}
</p>
<div class="divider" style="margin-bottom: 1em;"></div>
<p style="white-space: pre-line;word-wrap: break-word;">{data.playlist.description}</p>
<article class="medium scroll no-padding no-elevate no-round">
<p style="white-space: pre-line;word-wrap: break-word;">{data.playlist.description}</p>
</article>
</article>
{#if videos}
@@ -12,15 +12,13 @@
let currentPage = 1;
$: search = data.search;
activePage.set(null);
async function changeType(type: 'playlist' | 'all' | 'video' | 'channel') {
data.searchType = type;
currentPage = 1;
search = [];
search = await getSearch(data.slug, { type: type });
data.search = [];
data.search = await getSearch(data.slug, { type: type });
}
async function loadMore(event: InfiniteEvent) {
@@ -33,7 +31,7 @@
if (newSearch.length === 0) {
event.detail.complete();
} else {
search = [...search, ...newSearch];
data.search = [...data.search, ...newSearch];
event.detail.loaded();
}
}
@@ -76,11 +74,11 @@
</div>
</div>
{#if search.length > 0}
{#if data.search.length > 0}
<div class="page right active">
<div class="space"></div>
<div class="grid">
{#each search as item}
{#each data.search as item}
<div class="s12 m6 l2">
{#key item}
<article class="no-padding" style="height: 100%;">
@@ -12,7 +12,7 @@
import Comment from '$lib/Comment.svelte';
import Player from '$lib/Player.svelte';
import Thumbnail from '$lib/Thumbnail.svelte';
import { cleanNumber, numberWithCommas } from '$lib/misc.js';
import { cleanNumber, numberWithCommas, unsafeRandomItem } from '$lib/misc';
import type { PlayerEvents } from '$lib/player';
import type { DataConnection } from 'peerjs';
import { onDestroy, onMount } from 'svelte';
@@ -25,6 +25,7 @@
playerAutoplayNextByDefault,
playerListenByDefault,
playerTheatreModeByDefault,
playlistSettings,
syncPartyConnections,
syncPartyPeer
} from '../../../store';
@@ -38,6 +39,9 @@
let playlistVideos: PlaylistPageVideo[] = [];
let playlist: PlaylistPage | null = null;
let loopPlaylist: boolean = false;
let shufflePlaylist: boolean = false;
let theatreMode = get(playerTheatreModeByDefault);
let audioMode = get(playerListenByDefault);
@@ -45,6 +49,14 @@
let seekTo: (time: number) => void;
let player: MediaPlayerElement;
playlistSettings.subscribe((playlistSetting) => {
if (!data.playlistId) return;
if (data.playlistId in playlistSetting) {
loopPlaylist = playlistSetting[data.playlistId].loop;
shufflePlaylist = playlistSetting[data.playlistId].shuffle;
}
});
function playerSyncEvents(conn: DataConnection) {
if (player) {
conn.send({
@@ -195,12 +207,49 @@
if (player) {
player.addEventListener('end', () => {
if ($playerAutoplayNextByDefault && !playlist) {
goto(`/watch/${data.video.recommendedVideos[0].videoId}`);
if (!playlistVideos) {
if ($playerAutoplayNextByDefault) {
goto(`/watch/${data.video.recommendedVideos[0].videoId}`);
}
return;
}
if (data.playlistId) {
goToCurrentPlaylistItem();
goToCurrentPlaylistItem();
const playlistVideoIds = playlistVideos.map((value) => {
return value.videoId;
});
let goToVideo: PlaylistPageVideo | undefined;
if (shufflePlaylist) {
goToVideo = unsafeRandomItem(playlistVideos);
} else {
const currentVideoIndex = playlistVideoIds.indexOf(data.video.videoId);
const newIndex = currentVideoIndex + 1;
if (currentVideoIndex !== -1 && newIndex < playlistVideoIds.length) {
goToVideo = playlistVideos[newIndex];
} else if (loopPlaylist) {
// Loop playlist on end
goToVideo = playlistVideos[0];
}
}
if (typeof goToVideo !== 'undefined') {
if ($syncPartyConnections) {
$syncPartyConnections.forEach((conn) => {
if (typeof goToVideo === 'undefined') return;
conn.send({
events: [
{ type: 'change-video', videoId: goToVideo.videoId },
{ type: 'playlist', playlistId: data.playlistId }
]
} as PlayerEvents);
});
}
goto(`/watch/${goToVideo.videoId}?playlist=${data.playlistId}`);
}
});
}
@@ -210,32 +259,6 @@
await loadPlaylist(data.playlistId);
goToCurrentPlaylistItem();
if (playlistVideos && player) {
player.addEventListener('end', () => {
if (!playlistVideos) return;
const playlistVideoIds = playlistVideos.map((value) => {
return value.videoId;
});
const currentVideoIndex = playlistVideoIds.indexOf(data.video.videoId);
const newIndex = currentVideoIndex + 1;
if (currentVideoIndex !== -1 && newIndex <= playlistVideoIds.length) {
if ($syncPartyConnections) {
$syncPartyConnections.forEach((conn) => {
conn.send({
events: [
{ type: 'change-video', videoId: playlistVideos[newIndex].videoId },
{ type: 'playlist', playlistId: data.playlistId }
]
} as PlayerEvents);
});
}
goto(`/watch/${playlistVideos[newIndex].videoId}?playlist=${data.playlistId}`);
}
});
}
});
onDestroy(() => {
@@ -533,6 +556,40 @@
{$_('videos')}
</p>
<p><a href={`/channel/${playlist.authorId}`}>{playlist.author}</a></p>
<nav>
<button
on:click={() => (
(loopPlaylist = !loopPlaylist),
playlistSettings.set({
[playlist.playlistId]: { loop: loopPlaylist, shuffle: shufflePlaylist }
})
)}
class="circle"
class:fill={!loopPlaylist}
>
<i>loop</i>
<div class="tooltip bottom">
{$_('playlist.loopPlaylist')}
</div>
</button>
<button
on:click={() => (
(shufflePlaylist = !shufflePlaylist),
playlistSettings.set({
[playlist.playlistId]: { loop: loopPlaylist, shuffle: shufflePlaylist }
})
)}
class="circle"
class:fill={!shufflePlaylist}
>
<i>shuffle</i>
<div class="tooltip bottom">
{$_('playlist.shuffleVideos')}
</div>
</button>
</nav>
<div class="space"></div>
<div class="divider"></div>
</article>
@@ -545,7 +602,9 @@
id={playlistVideo.videoId}
class:border={playlistVideo.videoId === data.video.videoId}
>
<Thumbnail video={playlistVideo} playlistId={data.playlistId} />
{#key playlistVideo.videoId}
<Thumbnail video={playlistVideo} playlistId={data.playlistId || undefined} />
{/key}
</article>
{/each}
</article>
+3 -1
View File
@@ -25,7 +25,7 @@ export const returnYtDislikes = persisted('returnYtDislikes', true);
export const interfaceSearchSuggestions = persisted('searchSuggestions', true);
export const auth: Writable<null | { username: string; token: string }> = persisted(
export const auth: Writable<null | { username: string; token: string; }> = persisted(
'authToken',
null
);
@@ -49,3 +49,5 @@ export const deArrowThumbnailInstance = persisted(
export const syncPartyPeer: Writable<Peer | null> = writable(null);
export const syncPartyConnections: Writable<DataConnection[] | null> = writable();
export const playlistSettings: Writable<Record<string, { shuffle: boolean, loop: boolean; }>> = writable({});
+6
View File
@@ -12,6 +12,12 @@
max-width: 200px !important;
}
.vds-kb-action {
position: absolute !important;
top: 50% !important;
left: 50% !important;
}
.vds-chapter-radio {
align-items: start !important;
}