Added video hiding & video sharing
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
<script lang="ts">
|
||||
import { Capacitor } from '@capacitor/core';
|
||||
import { _ } from 'svelte-i18n';
|
||||
import type { Notification, PlaylistPageVideo, Video, VideoBase } from './Api/model';
|
||||
|
||||
export let video: VideoBase | Video | Notification | PlaylistPageVideo;
|
||||
</script>
|
||||
|
||||
{#if !Capacitor.isNativePlatform()}
|
||||
<a
|
||||
class="row"
|
||||
href="#copy"
|
||||
on:click={async () =>
|
||||
await navigator.clipboard.writeText(`${location.origin}watch/${video.videoId}`)}
|
||||
>
|
||||
<div class="min">{$_('player.share.materialiousLink')}</div></a
|
||||
>
|
||||
{/if}
|
||||
<a
|
||||
href="#copy"
|
||||
class="row"
|
||||
on:click={async () =>
|
||||
await navigator.clipboard.writeText(`https://redirect.invidious.io/watch?v=${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=${video.videoId}`)}
|
||||
>
|
||||
<div class="min">{$_('player.share.youtubeLink')}</div></a
|
||||
>
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { createEventDispatcher, onMount } from 'svelte';
|
||||
import { _ } from 'svelte-i18n';
|
||||
import { get } from 'svelte/store';
|
||||
import {
|
||||
@@ -14,12 +14,16 @@
|
||||
} from '../store';
|
||||
import { getDeArrow, getThumbnail, getVideo, getVideoProgress } from './Api';
|
||||
import type { Notification, PlaylistPageVideo, Video, VideoBase, VideoPlay } from './Api/model';
|
||||
import ShareVideo from './ShareVideo.svelte';
|
||||
import { cleanNumber, getBestThumbnail, proxyVideoUrl, videoLength } from './misc';
|
||||
import type { PlayerEvents } from './player';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let video: VideoBase | Video | Notification | PlaylistPageVideo;
|
||||
export let playlistId: string = '';
|
||||
|
||||
let thumbnailHidden: boolean = false;
|
||||
let showVideoPreview: boolean = false;
|
||||
let videoPreview: VideoPlay | null = null;
|
||||
let videoPreviewMuted: boolean = true;
|
||||
@@ -56,6 +60,15 @@
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
try {
|
||||
thumbnailHidden = localStorage.getItem(`v_h_${video.videoId}`) === '1';
|
||||
} catch {}
|
||||
|
||||
if (thumbnailHidden) {
|
||||
dispatch('videoHidden');
|
||||
return;
|
||||
}
|
||||
|
||||
let imageSrc = getBestThumbnail(video.videoThumbnails);
|
||||
|
||||
if (get(deArrowEnabledStore)) {
|
||||
@@ -180,119 +193,141 @@
|
||||
showVideoPreview = true;
|
||||
}
|
||||
}
|
||||
|
||||
function hideVideo() {
|
||||
try {
|
||||
localStorage.setItem(`v_h_${video.videoId}`, '1');
|
||||
dispatch('videoHidden');
|
||||
thumbnailHidden = true;
|
||||
} catch {}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
style="position: relative;"
|
||||
on:mouseover={previewVideo}
|
||||
on:mouseleave={() => (showVideoPreview = false)}
|
||||
on:focus={() => {}}
|
||||
role="region"
|
||||
>
|
||||
<a
|
||||
class="wave thumbnail"
|
||||
href={watchUrl.toString()}
|
||||
data-sveltekit-preload-data="off"
|
||||
on:click={syncChangeVideo}
|
||||
{#if !thumbnailHidden}
|
||||
<div
|
||||
style="position: relative;"
|
||||
on:mouseover={previewVideo}
|
||||
on:mouseleave={() => (showVideoPreview = false)}
|
||||
on:focus={() => {}}
|
||||
role="region"
|
||||
>
|
||||
{#if loading}
|
||||
<progress class="circle"></progress>
|
||||
{:else if loaded}
|
||||
{#if showVideoPreview && videoPreview}
|
||||
<div style="max-width: 100%; max-height: 200px;">
|
||||
<video
|
||||
style="max-width: 100%; max-height: 200px;"
|
||||
autoplay
|
||||
poster={img.src}
|
||||
width="100%"
|
||||
height="100%"
|
||||
muted={videoPreviewMuted}
|
||||
controls={false}
|
||||
volume={videoPreviewVolume}
|
||||
src={proxyVideos
|
||||
? proxyVideoUrl(videoPreview.formatStreams[0].url)
|
||||
: videoPreview.formatStreams[0].url}
|
||||
>
|
||||
</video>
|
||||
</div>
|
||||
{:else}
|
||||
<img
|
||||
class="responsive"
|
||||
style="max-width: 100%;min-height: 160px;"
|
||||
src={img.src}
|
||||
alt="Thumbnail for video"
|
||||
/>
|
||||
{/if}
|
||||
{:else}
|
||||
<p>{$_('thumbnail.failedToLoadImage')}</p>
|
||||
{/if}
|
||||
{#if progress}
|
||||
<progress
|
||||
class="absolute right bottom"
|
||||
style="z-index: 1;"
|
||||
value={progress}
|
||||
max={video.lengthSeconds}
|
||||
></progress>
|
||||
{/if}
|
||||
{#if !('liveVideo' in video) || !video.liveVideo}
|
||||
{#if video.lengthSeconds !== 0}
|
||||
<div
|
||||
class="absolute right bottom small-margin black white-text small-text thumbnail-corner"
|
||||
>
|
||||
{videoLength(video.lengthSeconds)}
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="absolute right bottom small-margin red white-text small-text thumbnail-corner">
|
||||
{$_('thumbnail.live')}
|
||||
</div>
|
||||
{/if}
|
||||
</a>
|
||||
{#if showVideoPreview && videoPreview}
|
||||
<button
|
||||
class="no-padding"
|
||||
style="position: absolute; bottom: 10px; left: 10px; width: 30px; height: 30px;"
|
||||
on:click={() => {
|
||||
videoPreviewMuted = !videoPreviewMuted;
|
||||
}}
|
||||
<a
|
||||
class="wave thumbnail"
|
||||
href={watchUrl.toString()}
|
||||
data-sveltekit-preload-data="off"
|
||||
on:click={syncChangeVideo}
|
||||
>
|
||||
<i>
|
||||
{#if videoPreviewMuted}
|
||||
volume_off
|
||||
{#if loading}
|
||||
<progress class="circle"></progress>
|
||||
{:else if loaded}
|
||||
{#if showVideoPreview && videoPreview}
|
||||
<div style="max-width: 100%; max-height: 200px;">
|
||||
<video
|
||||
style="max-width: 100%; max-height: 200px;"
|
||||
autoplay
|
||||
poster={img.src}
|
||||
width="100%"
|
||||
height="100%"
|
||||
muted={videoPreviewMuted}
|
||||
controls={false}
|
||||
volume={videoPreviewVolume}
|
||||
src={proxyVideos
|
||||
? proxyVideoUrl(videoPreview.formatStreams[0].url)
|
||||
: videoPreview.formatStreams[0].url}
|
||||
>
|
||||
</video>
|
||||
</div>
|
||||
{:else}
|
||||
volume_up
|
||||
<img
|
||||
class="responsive"
|
||||
style="max-width: 100%;min-height: 160px;"
|
||||
src={img.src}
|
||||
alt="Thumbnail for video"
|
||||
/>
|
||||
{/if}
|
||||
</i>
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="small-padding">
|
||||
<nav class="no-margin">
|
||||
<div class="max">
|
||||
<a
|
||||
href={watchUrl.toString()}
|
||||
data-sveltekit-preload-data="off"
|
||||
style="display: flex; justify-content:flex-start; position: absolute; width: 100%;"
|
||||
><div class="bold" style="white-space: nowrap; overflow: hidden;text-overflow: ellipsis;">
|
||||
{video.title}
|
||||
</div>
|
||||
|
||||
<div class="tooltip bottom small">{video.title}</div>
|
||||
</a>
|
||||
<div style="margin-top: 1.4em;">
|
||||
<a href={`/channel/${video.authorId}`}>{video.author}</a
|
||||
>{#if !('publishedText' in video) && 'viewCountText' in video}
|
||||
• {video.viewCountText}{/if}
|
||||
</div>
|
||||
{#if 'publishedText' in video}
|
||||
<div>
|
||||
{cleanNumber(video.viewCount)} • {video.publishedText}
|
||||
{:else}
|
||||
<p>{$_('thumbnail.failedToLoadImage')}</p>
|
||||
{/if}
|
||||
{#if progress}
|
||||
<progress
|
||||
class="absolute right bottom"
|
||||
style="z-index: 1;"
|
||||
value={progress}
|
||||
max={video.lengthSeconds}
|
||||
></progress>
|
||||
{/if}
|
||||
{#if !('liveVideo' in video) || !video.liveVideo}
|
||||
{#if video.lengthSeconds !== 0}
|
||||
<div
|
||||
class="absolute right bottom small-margin black white-text small-text thumbnail-corner"
|
||||
>
|
||||
{videoLength(video.lengthSeconds)}
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="absolute right bottom small-margin red white-text small-text thumbnail-corner">
|
||||
{$_('thumbnail.live')}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</a>
|
||||
{#if showVideoPreview && videoPreview}
|
||||
<button
|
||||
class="no-padding"
|
||||
style="position: absolute; bottom: 10px; left: 10px; width: 30px; height: 30px;"
|
||||
on:click={() => {
|
||||
videoPreviewMuted = !videoPreviewMuted;
|
||||
}}
|
||||
>
|
||||
<i>
|
||||
{#if videoPreviewMuted}
|
||||
volume_off
|
||||
{:else}
|
||||
volume_up
|
||||
{/if}
|
||||
</i>
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="small-padding">
|
||||
<nav class="no-margin">
|
||||
<div class="max">
|
||||
<a
|
||||
href={watchUrl.toString()}
|
||||
data-sveltekit-preload-data="off"
|
||||
style="display: flex; justify-content:flex-start; position: absolute; width: 100%;"
|
||||
><div class="bold" style="white-space: nowrap; overflow: hidden;text-overflow: ellipsis;">
|
||||
{video.title}
|
||||
</div>
|
||||
|
||||
<div class="tooltip bottom small">{video.title}</div>
|
||||
</a>
|
||||
<div style="margin-top: 1.4em;">
|
||||
<a href={`/channel/${video.authorId}`}>{video.author}</a
|
||||
>{#if !('publishedText' in video) && 'viewCountText' in video}
|
||||
• {video.viewCountText}{/if}
|
||||
</div>
|
||||
<nav class="no-margin">
|
||||
{#if 'publishedText' in video}
|
||||
<div class="max">
|
||||
{cleanNumber(video.viewCount)} • {video.publishedText}
|
||||
</div>
|
||||
<button class="transparent circle">
|
||||
<i>arrow_drop_down</i>
|
||||
<menu class="no-wrap">
|
||||
<a href="#hide" on:click={hideVideo}>
|
||||
{$_('hideVideo')}
|
||||
</a>
|
||||
<div class="divider"></div>
|
||||
<ShareVideo {video} />
|
||||
</menu>
|
||||
</button>
|
||||
{/if}
|
||||
</nav>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<canvas id="canvas" style="display: none;"></canvas>
|
||||
<div id="video-container" style="display: none;"></div>
|
||||
|
||||
@@ -1,21 +1,29 @@
|
||||
<script lang="ts">
|
||||
import type { PlaylistPageVideo, Video, VideoBase } from './Api/model';
|
||||
import type { Notification, PlaylistPageVideo, Video, VideoBase } from './Api/model';
|
||||
import Thumbnail from './Thumbnail.svelte';
|
||||
|
||||
export let videos: VideoBase[] | Video[] | Notification[] | PlaylistPageVideo[] = [];
|
||||
export let oneItemPerRow: boolean = false;
|
||||
export let playlistId: string = '';
|
||||
|
||||
let hiddenVideos: string[] = [];
|
||||
</script>
|
||||
|
||||
<div class="page right active">
|
||||
<div class="space"></div>
|
||||
<div class="grid">
|
||||
{#each videos as video}
|
||||
<div class={`s12 m${oneItemPerRow ? '12' : '6'} l${oneItemPerRow ? '12' : '2'}`}>
|
||||
<article class="no-padding" style="height: 100%;">
|
||||
<Thumbnail {video} {playlistId} />
|
||||
</article>
|
||||
</div>
|
||||
{#if !hiddenVideos.includes(video.videoId)}
|
||||
<div class={`s12 m${oneItemPerRow ? '12' : '6'} l${oneItemPerRow ? '12' : '2'}`}>
|
||||
<article class="no-padding" style="height: 100%;">
|
||||
<Thumbnail
|
||||
on:videoHidden={() => (hiddenVideos = [...hiddenVideos, video.videoId])}
|
||||
{video}
|
||||
{playlistId}
|
||||
/>
|
||||
</article>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"deleteAllHistory": "Delete all history",
|
||||
"skipping": "Skipping",
|
||||
"replies": "replies",
|
||||
"hideVideo": "Hide video",
|
||||
"syncParty": {
|
||||
"userJoined": "User joined your watch party.",
|
||||
"userLeft": "User left your watch party."
|
||||
@@ -116,4 +117,4 @@
|
||||
}
|
||||
},
|
||||
"subscribe": "Subscribe"
|
||||
}
|
||||
}
|
||||
@@ -12,10 +12,10 @@
|
||||
import type { Comments, PlaylistPage, PlaylistPageVideo } from '$lib/Api/model.js';
|
||||
import Comment from '$lib/Comment.svelte';
|
||||
import Player from '$lib/Player.svelte';
|
||||
import ShareVideo from '$lib/ShareVideo.svelte';
|
||||
import Thumbnail from '$lib/Thumbnail.svelte';
|
||||
import { cleanNumber, getBestThumbnail, numberWithCommas, unsafeRandomItem } from '$lib/misc';
|
||||
import type { PlayerEvents } from '$lib/player';
|
||||
import { Capacitor } from '@capacitor/core';
|
||||
import type { DataConnection } from 'peerjs';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import { _ } from 'svelte-i18n';
|
||||
@@ -468,38 +468,8 @@
|
||||
{$_('player.share.title')}
|
||||
</div>
|
||||
<menu class="no-wrap">
|
||||
{#if !Capacitor.isNativePlatform()}
|
||||
<a
|
||||
class="row"
|
||||
href="#copy"
|
||||
on:click={async () =>
|
||||
await navigator.clipboard.writeText(
|
||||
`${location.origin}watch/${data.video.videoId}`
|
||||
)}
|
||||
>
|
||||
<div class="min">{$_('player.share.materialiousLink')}</div></a
|
||||
>
|
||||
{/if}
|
||||
<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
|
||||
<ShareVideo video={data.video} />
|
||||
</menu></button
|
||||
>
|
||||
{#if data.downloadOptions.length > 0}
|
||||
<button class="border"
|
||||
|
||||
Reference in New Issue
Block a user