Merge pull request #108 from WardPearce/fix/history-playlist
Fixed playlist history
@@ -186,9 +186,9 @@ export async function deleteUnsubscribe(authorId: string) {
|
||||
);
|
||||
}
|
||||
|
||||
export async function getHistory(page: number = 1): Promise<string[]> {
|
||||
export async function getHistory(page: number = 1, maxResults: number = 20): Promise<string[]> {
|
||||
const resp = await fetchErrorHandle(
|
||||
await fetch(buildPath(`auth/history?page=${page}`), buildAuthHeaders())
|
||||
await fetch(buildPath(`auth/history?page=${page}&max_results=${maxResults}`), buildAuthHeaders())
|
||||
);
|
||||
return await resp.json();
|
||||
}
|
||||
@@ -276,6 +276,15 @@ export async function addPlaylistVideo(playlistId: string, videoId: string) {
|
||||
);
|
||||
}
|
||||
|
||||
export async function removePlaylistVideo(playlistId: string, indexId: string) {
|
||||
await fetchErrorHandle(
|
||||
await fetch(buildPath(`auth/playlists/${playlistId}/videos/${indexId}`), {
|
||||
method: 'DELETE',
|
||||
...buildAuthHeaders()
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export async function getDeArrow(videoId: string): Promise<DeArrow> {
|
||||
const resp = await fetchErrorHandle(
|
||||
await fetch(`${get(deArrowInstance)}/api/branding?videoID=${videoId}`)
|
||||
|
||||
@@ -180,6 +180,7 @@ export interface Playlist {
|
||||
export interface PlaylistPageVideo extends PlaylistVideo {
|
||||
author: string;
|
||||
index: number;
|
||||
indexId: string;
|
||||
authorId: string;
|
||||
viewCount: number;
|
||||
}
|
||||
|
||||
@@ -23,28 +23,57 @@
|
||||
suggestionsForSearch = (await getSearchSuggestions(event.target.value)).suggestions;
|
||||
}, 250);
|
||||
};
|
||||
|
||||
function handleSubmit() {
|
||||
goto(`/search/${encodeURIComponent(search)}`);
|
||||
}
|
||||
</script>
|
||||
|
||||
<form on:submit|preventDefault={() => goto(`/search/${encodeURIComponent(search)}`)}>
|
||||
<div class="max field round suffix prefix small no-margin white black-text">
|
||||
<i class="front">search</i><input
|
||||
data-ui="search-suggestions"
|
||||
type="text"
|
||||
placeholder={$_('searchPlaceholder')}
|
||||
bind:value={search}
|
||||
on:keyup={(target) => debouncedSearch(target)}
|
||||
/>
|
||||
{#if searchSuggestions}
|
||||
<menu
|
||||
class="no-wrap"
|
||||
style="width: 100%;"
|
||||
id="search-suggestions"
|
||||
data-ui="#search-suggestions"
|
||||
>
|
||||
<form on:submit|preventDefault={handleSubmit}>
|
||||
<div class="field prefix round fill no-margin search">
|
||||
<i class="front">search</i>
|
||||
<input bind:value={search} on:click={() => document.getElementById('search')?.focus()} />
|
||||
<menu class="min">
|
||||
<div class="field large prefix suffix no-margin fixed">
|
||||
<i class="front">arrow_back</i>
|
||||
<input
|
||||
placeholder={$_('searchPlaceholder')}
|
||||
type="text"
|
||||
id="search"
|
||||
required
|
||||
bind:value={search}
|
||||
on:keyup={(event) => {
|
||||
if (event.key === 'Enter') {
|
||||
handleSubmit();
|
||||
} else {
|
||||
debouncedSearch(event);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<i class="front" on:click={() => (search = '')}>close</i>
|
||||
</div>
|
||||
{#if searchSuggestions}
|
||||
{#each suggestionsForSearch as suggestion}
|
||||
<a href={`/search/${encodeURIComponent(suggestion)}`}>{suggestion}</a>
|
||||
<a
|
||||
on:click={() => (search = suggestion)}
|
||||
class="row"
|
||||
href={`/search/${encodeURIComponent(suggestion)}`}
|
||||
>
|
||||
<div>{suggestion}</div>
|
||||
</a>
|
||||
{/each}
|
||||
</menu>
|
||||
{/if}
|
||||
{/if}
|
||||
</menu>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<style>
|
||||
.search {
|
||||
width: 500px;
|
||||
}
|
||||
@media screen and (max-width: 1140px) {
|
||||
.search {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -18,13 +18,19 @@
|
||||
|
||||
async function loadPageHistory() {
|
||||
try {
|
||||
const videoIds = await getHistory(currentPage);
|
||||
const videoIds = await getHistory(currentPage, 20);
|
||||
let promises = [];
|
||||
for (const videoId of videoIds) {
|
||||
promises.push(getVideo(videoId));
|
||||
promises.push(
|
||||
getVideo(videoId).catch(() => {
|
||||
return null;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
const loadedHistory = await Promise.all(promises);
|
||||
const loadedHistory = (await Promise.all(promises)).filter((item) => {
|
||||
return item !== null;
|
||||
}) as VideoPlay[];
|
||||
history = [...history, ...loadedHistory];
|
||||
} catch (errorMessage: any) {
|
||||
error(500, errorMessage);
|
||||
@@ -34,6 +40,8 @@
|
||||
async function loadMore(event: InfiniteEvent) {
|
||||
const pastHistoryLen = Number(history.length);
|
||||
|
||||
currentPage++;
|
||||
|
||||
await loadPageHistory();
|
||||
|
||||
if (pastHistoryLen === history.length) {
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
</p>
|
||||
<div class="divider" style="margin-bottom: 1em;"></div>
|
||||
|
||||
<article class="medium scroll no-padding no-elevate no-round">
|
||||
<article style="max-height: 200px;" class="scroll no-padding no-elevate no-round">
|
||||
<p style="white-space: pre-line;word-wrap: break-word;">{data.playlist.description}</p>
|
||||
</article>
|
||||
</article>
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
getComments,
|
||||
getPersonalPlaylists,
|
||||
getPlaylist,
|
||||
postSubscribe
|
||||
postSubscribe,
|
||||
removePlaylistVideo
|
||||
} from '$lib/Api/index.js';
|
||||
import type { PlaylistPage, PlaylistPageVideo } from '$lib/Api/model.js';
|
||||
import Comment from '$lib/Comment.svelte';
|
||||
@@ -294,8 +295,28 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function addVideoToPlaylist(playlistId: string) {
|
||||
await addPlaylistVideo(playlistId, data.video.videoId);
|
||||
async function toggleVideoToPlaylist(playlistId: string) {
|
||||
if (!data.personalPlaylists) return;
|
||||
|
||||
const selectedPlaylist = data.personalPlaylists.filter((item) => {
|
||||
return item.playlistId === playlistId;
|
||||
});
|
||||
|
||||
if (selectedPlaylist.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const videosToDelete = selectedPlaylist[0].videos.filter((item) => {
|
||||
return item.videoId === data.video.videoId;
|
||||
});
|
||||
|
||||
if (videosToDelete.length > 0) {
|
||||
videosToDelete.forEach(async (toDelete) => {
|
||||
await removePlaylistVideo(playlistId, toDelete.indexId);
|
||||
});
|
||||
} else {
|
||||
await addPlaylistVideo(playlistId, data.video.videoId);
|
||||
}
|
||||
|
||||
data.personalPlaylists = await getPersonalPlaylists();
|
||||
}
|
||||
@@ -462,12 +483,22 @@
|
||||
<button class="border">
|
||||
<i>add</i>
|
||||
<div class="tooltip">{$_('player.addToPlaylist')}</div>
|
||||
<menu>
|
||||
<menu class="no-wrap">
|
||||
{#each data.personalPlaylists as personalPlaylist}
|
||||
<a
|
||||
href="#add"
|
||||
on:click={async () => await addVideoToPlaylist(personalPlaylist.playlistId)}
|
||||
>{personalPlaylist.title}
|
||||
on:click={async () => await toggleVideoToPlaylist(personalPlaylist.playlistId)}
|
||||
>
|
||||
<nav>
|
||||
<span class="max">{personalPlaylist.title}</span>
|
||||
{#if personalPlaylist.videos.filter((item) => {
|
||||
return item.videoId === data.video.videoId;
|
||||
}).length > 0}
|
||||
<i>close</i>
|
||||
{:else}
|
||||
<i>add</i>
|
||||
{/if}
|
||||
</nav>
|
||||
</a>
|
||||
{/each}
|
||||
</menu>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 1.7 MiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 684 KiB |
|
Before Width: | Height: | Size: 1.6 MiB After Width: | Height: | Size: 7.1 MiB |
|
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 1.6 MiB After Width: | Height: | Size: 706 KiB |
|
Before Width: | Height: | Size: 1.8 MiB After Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 1.3 MiB |