Merge pull request #92 from WardPearce/update/video-name
Display video title in tab
This commit is contained in:
@@ -10,13 +10,12 @@
|
||||
} from '$lib/Api/index.js';
|
||||
import type { PlaylistPage, PlaylistPageVideo } from '$lib/Api/model.js';
|
||||
import Comment from '$lib/Comment.svelte';
|
||||
import PageLoading from '$lib/PageLoading.svelte';
|
||||
import Player from '$lib/Player.svelte';
|
||||
import Thumbnail from '$lib/Thumbnail.svelte';
|
||||
import { cleanNumber, numberWithCommas } from '$lib/misc.js';
|
||||
import type { PlayerEvents } from '$lib/player';
|
||||
import type { DataConnection } from 'peerjs';
|
||||
import { onMount } from 'svelte';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import { _ } from 'svelte-i18n';
|
||||
import { get } from 'svelte/store';
|
||||
import type { MediaPlayerElement } from 'vidstack/elements';
|
||||
@@ -239,6 +238,11 @@
|
||||
}
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
// Reset title when page left.
|
||||
document.title = 'Materialious';
|
||||
});
|
||||
|
||||
async function loadPlaylist(playlistId: string) {
|
||||
for (let page = 1; page < Infinity; page++) {
|
||||
const newPlaylist = await getPlaylist(playlistId, page);
|
||||
@@ -302,257 +306,253 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if data}
|
||||
<div class="grid">
|
||||
<div class={`s12 m12 l${theatreMode ? '12' : '10'}`}>
|
||||
{#key data.video.videoId}
|
||||
<Player
|
||||
{data}
|
||||
{audioMode}
|
||||
isSyncing={$syncPartyPeer !== null}
|
||||
bind:seekTo
|
||||
bind:currentTime
|
||||
bind:player
|
||||
/>
|
||||
{/key}
|
||||
<svelte:head>
|
||||
<title>{data.video.title} | Materialious</title>
|
||||
</svelte:head>
|
||||
|
||||
<h5>{data.video.title}</h5>
|
||||
<div class="grid">
|
||||
<div class={`s12 m12 l${theatreMode ? '12' : '10'}`}>
|
||||
{#key data.video.videoId}
|
||||
<Player
|
||||
{data}
|
||||
{audioMode}
|
||||
isSyncing={$syncPartyPeer !== null}
|
||||
bind:seekTo
|
||||
bind:currentTime
|
||||
bind:player
|
||||
/>
|
||||
{/key}
|
||||
|
||||
<div class="grid no-padding">
|
||||
<div class="s12 m12 l5">
|
||||
<nav>
|
||||
<a href={`/channel/${data.video.authorId}`}>
|
||||
<nav>
|
||||
<img
|
||||
class="circle large"
|
||||
src={data.video.authorThumbnails[2].url}
|
||||
alt="Channel profile"
|
||||
/>
|
||||
<div>
|
||||
<p class="bold">{data.video.author}</p>
|
||||
<p>{data.video.subCountText}</p>
|
||||
</div>
|
||||
</nav>
|
||||
</a>
|
||||
{#if $auth}
|
||||
<button
|
||||
on:click={toggleSubscribed}
|
||||
class:inverse-surface={!data.subscribed}
|
||||
class:border={data.subscribed}
|
||||
>
|
||||
{#if !data.subscribed}
|
||||
{$_('subscribe')}
|
||||
{:else}
|
||||
{$_('unsubscribe')}
|
||||
{/if}
|
||||
</button>
|
||||
{:else}
|
||||
<button class="inverse-surface" disabled>
|
||||
{$_('subscribe')}
|
||||
<div class="tooltip">
|
||||
{$_('loginRequired')}
|
||||
</div>
|
||||
</button>
|
||||
{/if}
|
||||
</nav>
|
||||
</div>
|
||||
<div class="s12 m12 l7 video-actions">
|
||||
{#if data.returnYTDislikes}
|
||||
<nav class="no-space" style="margin-right: .5em;">
|
||||
<button style="cursor: default;" class="border left-round">
|
||||
<i class="small">thumb_up</i>
|
||||
<span>{cleanNumber(data.returnYTDislikes.likes)}</span>
|
||||
</button>
|
||||
<button style="cursor: default;" class="border right-round">
|
||||
<i class="small">thumb_down_alt</i>
|
||||
<span>{cleanNumber(data.returnYTDislikes.dislikes)}</span>
|
||||
</button>
|
||||
</nav>
|
||||
{/if}
|
||||
<h5>{data.video.title}</h5>
|
||||
|
||||
<div>
|
||||
<button on:click={() => (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}>
|
||||
<i>width_wide</i>
|
||||
<div class="tooltip">{$_('player.theatreMode')}</div>
|
||||
</button>
|
||||
<button class="border"
|
||||
><i>share</i>
|
||||
<div class="tooltip">
|
||||
{$_('player.share.title')}
|
||||
</div>
|
||||
<menu class="no-wrap">
|
||||
<a
|
||||
class="row"
|
||||
href="#copy"
|
||||
on:click={async () =>
|
||||
await navigator.clipboard.writeText(
|
||||
`${import.meta.env.VITE_DEFAULT_FRONTEND_URL}/watch/${data.video.videoId}`
|
||||
)}
|
||||
>
|
||||
<div class="min">{$_('player.share.materialiousLink')}</div></a
|
||||
><a
|
||||
href="#copy"
|
||||
class="row"
|
||||
on:click={async () =>
|
||||
await navigator.clipboard.writeText(
|
||||
`https://redirect.invidious.io/watch?v=${data.video.videoId}`
|
||||
)}
|
||||
>
|
||||
<div class="min">{$_('player.share.invidiousRedirect')}</div></a
|
||||
><a
|
||||
class="row"
|
||||
href="#copy"
|
||||
on:click={async () =>
|
||||
await navigator.clipboard.writeText(
|
||||
`https://www.youtube.com/watch?v=${data.video.videoId}`
|
||||
)}
|
||||
>
|
||||
<div class="min">{$_('player.share.youtubeLink')}</div></a
|
||||
></menu
|
||||
></button
|
||||
>
|
||||
{#if data.downloadOptions.length > 0}
|
||||
<button class="border"
|
||||
><i>download</i>
|
||||
<div class="tooltip">{$_('player.download')}</div>
|
||||
<menu class="no-wrap">
|
||||
{#each data.downloadOptions as download}
|
||||
<a class="row" href={download.url} target="_blank" rel="noopener noreferrer"
|
||||
>{download.title}</a
|
||||
>
|
||||
{/each}
|
||||
</menu></button
|
||||
>
|
||||
{/if}
|
||||
{#if data.personalPlaylists}
|
||||
<button class="border">
|
||||
<i>add</i>
|
||||
<div class="tooltip">{$_('player.addToPlaylist')}</div>
|
||||
<menu>
|
||||
{#each data.personalPlaylists as personalPlaylist}
|
||||
<a
|
||||
href="#add"
|
||||
on:click={async () => await addVideoToPlaylist(personalPlaylist.playlistId)}
|
||||
>{personalPlaylist.title}
|
||||
</a>
|
||||
{/each}
|
||||
</menu>
|
||||
</button>
|
||||
{:else}
|
||||
<button disabled class="border no-margin">
|
||||
<i>add</i>
|
||||
<div class="tooltip">
|
||||
{#if $auth}
|
||||
{$_('player.noPlaylists')}
|
||||
{:else}
|
||||
{$_('loginRequired')}
|
||||
{/if}
|
||||
</div>
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<article>
|
||||
<details>
|
||||
<summary class="bold none">
|
||||
<div class="grid no-padding">
|
||||
<div class="s12 m12 l5">
|
||||
<nav>
|
||||
<a href={`/channel/${data.video.authorId}`}>
|
||||
<nav>
|
||||
<div class="max">
|
||||
{numberWithCommas(data.video.viewCount)} views • {data.video.publishedText}
|
||||
<img
|
||||
class="circle large"
|
||||
src={data.video.authorThumbnails[2].url}
|
||||
alt="Channel profile"
|
||||
/>
|
||||
<div>
|
||||
<p class="bold">{data.video.author}</p>
|
||||
<p>{data.video.subCountText}</p>
|
||||
</div>
|
||||
<i>expand_more</i>
|
||||
</nav>
|
||||
</summary>
|
||||
<div class="space"></div>
|
||||
<div class="medium scroll">
|
||||
<div style="white-space: pre-line; overflow-wrap: break-word;">
|
||||
{@html data.content.description}
|
||||
</a>
|
||||
{#if $auth}
|
||||
<button
|
||||
on:click={toggleSubscribed}
|
||||
class:inverse-surface={!data.subscribed}
|
||||
class:border={data.subscribed}
|
||||
>
|
||||
{#if !data.subscribed}
|
||||
{$_('subscribe')}
|
||||
{:else}
|
||||
{$_('unsubscribe')}
|
||||
{/if}
|
||||
</button>
|
||||
{:else}
|
||||
<button class="inverse-surface" disabled>
|
||||
{$_('subscribe')}
|
||||
<div class="tooltip">
|
||||
{$_('loginRequired')}
|
||||
</div>
|
||||
</button>
|
||||
{/if}
|
||||
</nav>
|
||||
</div>
|
||||
<div class="s12 m12 l7 video-actions">
|
||||
{#if data.returnYTDislikes}
|
||||
<nav class="no-space" style="margin-right: .5em;">
|
||||
<button style="cursor: default;" class="border left-round">
|
||||
<i class="small">thumb_up</i>
|
||||
<span>{cleanNumber(data.returnYTDislikes.likes)}</span>
|
||||
</button>
|
||||
<button style="cursor: default;" class="border right-round">
|
||||
<i class="small">thumb_down_alt</i>
|
||||
<span>{cleanNumber(data.returnYTDislikes.dislikes)}</span>
|
||||
</button>
|
||||
</nav>
|
||||
{/if}
|
||||
|
||||
<div>
|
||||
<button on:click={() => (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}>
|
||||
<i>width_wide</i>
|
||||
<div class="tooltip">{$_('player.theatreMode')}</div>
|
||||
</button>
|
||||
<button class="border"
|
||||
><i>share</i>
|
||||
<div class="tooltip">
|
||||
{$_('player.share.title')}
|
||||
</div>
|
||||
{#if data.content}
|
||||
{#if data.content.timestamps.length > 0}
|
||||
<h6 style="margin-bottom: .3em;">Chapters</h6>
|
||||
{#each data.content.timestamps as timestamp}
|
||||
<button on:click={() => seekTo(timestamp.time)} class="timestamps"
|
||||
>{timestamp.timePretty}
|
||||
{#if !timestamp.title.startsWith('-')}
|
||||
-
|
||||
{/if}
|
||||
{timestamp.title}</button
|
||||
<menu class="no-wrap">
|
||||
<a
|
||||
class="row"
|
||||
href="#copy"
|
||||
on:click={async () =>
|
||||
await navigator.clipboard.writeText(
|
||||
`${import.meta.env.VITE_DEFAULT_FRONTEND_URL}/watch/${data.video.videoId}`
|
||||
)}
|
||||
>
|
||||
<div class="min">{$_('player.share.materialiousLink')}</div></a
|
||||
><a
|
||||
href="#copy"
|
||||
class="row"
|
||||
on:click={async () =>
|
||||
await navigator.clipboard.writeText(
|
||||
`https://redirect.invidious.io/watch?v=${data.video.videoId}`
|
||||
)}
|
||||
>
|
||||
<div class="min">{$_('player.share.invidiousRedirect')}</div></a
|
||||
><a
|
||||
class="row"
|
||||
href="#copy"
|
||||
on:click={async () =>
|
||||
await navigator.clipboard.writeText(
|
||||
`https://www.youtube.com/watch?v=${data.video.videoId}`
|
||||
)}
|
||||
>
|
||||
<div class="min">{$_('player.share.youtubeLink')}</div></a
|
||||
></menu
|
||||
></button
|
||||
>
|
||||
{#if data.downloadOptions.length > 0}
|
||||
<button class="border"
|
||||
><i>download</i>
|
||||
<div class="tooltip">{$_('player.download')}</div>
|
||||
<menu class="no-wrap">
|
||||
{#each data.downloadOptions as download}
|
||||
<a class="row" href={download.url} target="_blank" rel="noopener noreferrer"
|
||||
>{download.title}</a
|
||||
>
|
||||
{/each}
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
</details>
|
||||
</article>
|
||||
|
||||
<div class="space"></div>
|
||||
{#if comments !== null && comments.comments !== undefined && comments.comments.length > 0}
|
||||
<h6>{numberWithCommas(comments.commentCount)} comments</h6>
|
||||
{#each comments.comments as comment}
|
||||
<Comment {comment} videoId={data.video.videoId}></Comment>
|
||||
{/each}
|
||||
{#if comments.continuation}
|
||||
<button on:click={loadMoreComments} class="margin">{$_('loadMore')}</button>
|
||||
{/if}
|
||||
{:else}
|
||||
<h6>{$_('player.unableToLoadComments')}</h6>
|
||||
{/if}
|
||||
</div>
|
||||
{#if !theatreMode}
|
||||
<div class="s12 m12 l2">
|
||||
{#if !playlist}
|
||||
{#if data.video.recommendedVideos}
|
||||
{#each data.video.recommendedVideos as recommendedVideo}
|
||||
<article class="no-padding">
|
||||
{#key recommendedVideo.videoId}
|
||||
<Thumbnail video={recommendedVideo} />
|
||||
{/key}
|
||||
</article>
|
||||
{/each}
|
||||
</menu></button
|
||||
>
|
||||
{/if}
|
||||
{:else}
|
||||
<article
|
||||
style="height: 75vh; position: relative;"
|
||||
id="playlist"
|
||||
class="scroll no-padding"
|
||||
>
|
||||
<article class="no-elevate" style="position: sticky; top: 0; z-index: 3;">
|
||||
<h6>{playlist.title}</h6>
|
||||
<p>
|
||||
{cleanNumber(playlist.viewCount)}
|
||||
{$_('views')} • {playlist.videoCount}
|
||||
{$_('videos')}
|
||||
</p>
|
||||
<p><a href={`/channel/${playlist.authorId}`}>{playlist.author}</a></p>
|
||||
<div class="divider"></div>
|
||||
</article>
|
||||
|
||||
<div class="space"></div>
|
||||
|
||||
{#each playlistVideos as playlistVideo}
|
||||
<article
|
||||
class="no-padding primary-border"
|
||||
style="margin: .7em;"
|
||||
id={playlistVideo.videoId}
|
||||
class:border={playlistVideo.videoId === data.video.videoId}
|
||||
>
|
||||
<Thumbnail video={playlistVideo} playlistId={data.playlistId} />
|
||||
</article>
|
||||
{/each}
|
||||
</article>
|
||||
{/if}
|
||||
{#if data.personalPlaylists}
|
||||
<button class="border">
|
||||
<i>add</i>
|
||||
<div class="tooltip">{$_('player.addToPlaylist')}</div>
|
||||
<menu>
|
||||
{#each data.personalPlaylists as personalPlaylist}
|
||||
<a
|
||||
href="#add"
|
||||
on:click={async () => await addVideoToPlaylist(personalPlaylist.playlistId)}
|
||||
>{personalPlaylist.title}
|
||||
</a>
|
||||
{/each}
|
||||
</menu>
|
||||
</button>
|
||||
{:else}
|
||||
<button disabled class="border no-margin">
|
||||
<i>add</i>
|
||||
<div class="tooltip">
|
||||
{#if $auth}
|
||||
{$_('player.noPlaylists')}
|
||||
{:else}
|
||||
{$_('loginRequired')}
|
||||
{/if}
|
||||
</div>
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<article>
|
||||
<details>
|
||||
<summary class="bold none">
|
||||
<nav>
|
||||
<div class="max">
|
||||
{numberWithCommas(data.video.viewCount)} views • {data.video.publishedText}
|
||||
</div>
|
||||
<i>expand_more</i>
|
||||
</nav>
|
||||
</summary>
|
||||
<div class="space"></div>
|
||||
<div class="medium scroll">
|
||||
<div style="white-space: pre-line; overflow-wrap: break-word;">
|
||||
{@html data.content.description}
|
||||
</div>
|
||||
{#if data.content}
|
||||
{#if data.content.timestamps.length > 0}
|
||||
<h6 style="margin-bottom: .3em;">Chapters</h6>
|
||||
{#each data.content.timestamps as timestamp}
|
||||
<button on:click={() => seekTo(timestamp.time)} class="timestamps"
|
||||
>{timestamp.timePretty}
|
||||
{#if !timestamp.title.startsWith('-')}
|
||||
-
|
||||
{/if}
|
||||
{timestamp.title}</button
|
||||
>
|
||||
{/each}
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
</details>
|
||||
</article>
|
||||
|
||||
<div class="space"></div>
|
||||
{#if comments !== null && comments.comments !== undefined && comments.comments.length > 0}
|
||||
<h6>{numberWithCommas(comments.commentCount)} comments</h6>
|
||||
{#each comments.comments as comment}
|
||||
<Comment {comment} videoId={data.video.videoId}></Comment>
|
||||
{/each}
|
||||
{#if comments.continuation}
|
||||
<button on:click={loadMoreComments} class="margin">{$_('loadMore')}</button>
|
||||
{/if}
|
||||
{:else}
|
||||
<h6>{$_('player.unableToLoadComments')}</h6>
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<PageLoading />
|
||||
{/if}
|
||||
{#if !theatreMode}
|
||||
<div class="s12 m12 l2">
|
||||
{#if !playlist}
|
||||
{#if data.video.recommendedVideos}
|
||||
{#each data.video.recommendedVideos as recommendedVideo}
|
||||
<article class="no-padding">
|
||||
{#key recommendedVideo.videoId}
|
||||
<Thumbnail video={recommendedVideo} />
|
||||
{/key}
|
||||
</article>
|
||||
{/each}
|
||||
{/if}
|
||||
{:else}
|
||||
<article style="height: 75vh; position: relative;" id="playlist" class="scroll no-padding">
|
||||
<article class="no-elevate" style="position: sticky; top: 0; z-index: 3;">
|
||||
<h6>{playlist.title}</h6>
|
||||
<p>
|
||||
{cleanNumber(playlist.viewCount)}
|
||||
{$_('views')} • {playlist.videoCount}
|
||||
{$_('videos')}
|
||||
</p>
|
||||
<p><a href={`/channel/${playlist.authorId}`}>{playlist.author}</a></p>
|
||||
<div class="divider"></div>
|
||||
</article>
|
||||
|
||||
<div class="space"></div>
|
||||
|
||||
{#each playlistVideos as playlistVideo}
|
||||
<article
|
||||
class="no-padding primary-border"
|
||||
style="margin: .7em;"
|
||||
id={playlistVideo.videoId}
|
||||
class:border={playlistVideo.videoId === data.video.videoId}
|
||||
>
|
||||
<Thumbnail video={playlistVideo} playlistId={data.playlistId} />
|
||||
</article>
|
||||
{/each}
|
||||
</article>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
:root {
|
||||
|
||||
Reference in New Issue
Block a user