mirror of
https://github.com/tubearchivist/tubearchivist-frontend.git
synced 2024-12-22 18:00:17 +00:00
chore: update video list
* Store current video in local state temporaily * Default to list view temporarily * Implements the video list and video player with basic funtionality
This commit is contained in:
parent
99316eeee8
commit
d86cf81719
@ -12,7 +12,8 @@
|
||||
"next": "12.1.1",
|
||||
"next-auth": "^4.3.1",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0"
|
||||
"react-dom": "^18.0.0",
|
||||
"react-player": "^2.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "17.0.23",
|
||||
|
@ -1,12 +1,28 @@
|
||||
import { Videos } from "../types/video";
|
||||
import { Datum, Videos } from "../types/video";
|
||||
import NextImage from "next/image";
|
||||
import ReactPlayer from "react-player/file";
|
||||
import { useState } from "react";
|
||||
import IconPlay from "../images/icon-play.svg";
|
||||
import IconClose from "../images/icon-close.svg";
|
||||
import { formatNumbers } from "../lib/utils";
|
||||
|
||||
export const VideoList = ({ videos }: { videos: Videos }) => {
|
||||
const [selectedVideoUrl, setSelectedVideoUrl] = useState<Datum>();
|
||||
|
||||
const handleSelectedVideo = (video: Datum) => {
|
||||
setSelectedVideoUrl(video);
|
||||
};
|
||||
|
||||
const handleRemoveVideoPlayer = () => {
|
||||
setSelectedVideoUrl(undefined);
|
||||
};
|
||||
|
||||
if (!videos) {
|
||||
return (
|
||||
<div className="boxed-content">
|
||||
<h2>No videos found...</h2>
|
||||
<p>
|
||||
If you've already added a channel or playlist, try going to the{" "}
|
||||
If you`'`ve already added a channel or playlist, try going to the{" "}
|
||||
<a href="{% url 'downloads">downloads page</a> to start the scan and
|
||||
download tasks.
|
||||
</p>
|
||||
@ -15,97 +31,222 @@ export const VideoList = ({ videos }: { videos: Videos }) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ color: "white" }} className="boxed-content">
|
||||
<pre>{JSON.stringify(videos.data, null, 4)}</pre>
|
||||
</div>
|
||||
<>
|
||||
{selectedVideoUrl && (
|
||||
<>
|
||||
<div className="player-wrapper">
|
||||
<div className="video-player">
|
||||
<ReactPlayer
|
||||
controls={true}
|
||||
width="100%"
|
||||
height="100%"
|
||||
light="false"
|
||||
playing // TODO: Not currently working
|
||||
playsinline
|
||||
url={`http://localhost:8000/media/${selectedVideoUrl.media_url}`}
|
||||
/>
|
||||
<div className="player-title boxed-content">
|
||||
<NextImage
|
||||
className="close-button"
|
||||
src={IconClose}
|
||||
width={30}
|
||||
height={30}
|
||||
alt="close-icon"
|
||||
onClick={handleRemoveVideoPlayer}
|
||||
title="Close player"
|
||||
/>
|
||||
{/* ${watchStatusIndicator}
|
||||
${castButton}
|
||||
*/}
|
||||
<div className="thumb-icon player-stats">
|
||||
<img src="/img/icon-eye.svg" alt="views icon" />
|
||||
<span>
|
||||
{formatNumbers(
|
||||
selectedVideoUrl.stats.view_count.toString()
|
||||
)}
|
||||
</span>
|
||||
<span>|</span>
|
||||
<img src="/img/icon-thumb.svg" alt="thumbs-up" />
|
||||
<span>
|
||||
{formatNumbers(
|
||||
selectedVideoUrl.stats.like_count.toString()
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="player-channel-playlist">
|
||||
<h3>
|
||||
<a href="/channel/${channelId}/">
|
||||
{selectedVideoUrl.channel.channel_name}
|
||||
</a>
|
||||
</h3>
|
||||
{/* ${playlist} */}
|
||||
</div>
|
||||
<a href="/video/${videoId}/">
|
||||
<h2 id="video-title">{selectedVideoUrl.title}</h2>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="boxed-content">
|
||||
<div className="title-bar">
|
||||
<h1>Recent Videos</h1>
|
||||
</div>
|
||||
<div className="view-controls">
|
||||
<div className="toggle">
|
||||
<span>Hide watched:</span>
|
||||
<div className="toggleBox">
|
||||
<input
|
||||
id="hide_watched"
|
||||
// onClick="toggleCheckbox(this)"
|
||||
type="checkbox"
|
||||
/>
|
||||
{/* {% if not hide_watched %} */}
|
||||
<label htmlFor="" className="ofbtn">
|
||||
Off
|
||||
</label>
|
||||
{/* {% else %} */}
|
||||
<label htmlFor="" className="onbtn">
|
||||
On
|
||||
</label>
|
||||
{/* {% endif %} */}
|
||||
</div>
|
||||
</div>
|
||||
<div className="sort">
|
||||
<div id="hidden-form">
|
||||
<span>Sort by:</span>
|
||||
<select
|
||||
name="sort"
|
||||
id="sort"
|
||||
onChange={() => console.log("onChange sort")}
|
||||
>
|
||||
<option value="published">date published</option>
|
||||
<option value="downloaded">date downloaded</option>
|
||||
<option value="views">views</option>
|
||||
<option value="likes">likes</option>
|
||||
</select>
|
||||
<select
|
||||
name="sord-order"
|
||||
id="sort-order"
|
||||
onChange={() => console.log("onChange sort-order")}
|
||||
>
|
||||
<option value="asc">asc</option>
|
||||
<option value="desc">desc</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="view-icons">
|
||||
<img
|
||||
src="/img/icon-sort.svg"
|
||||
alt="sort-icon"
|
||||
onClick={() => console.log("showForm")}
|
||||
id="animate-icon"
|
||||
/>
|
||||
<img
|
||||
src="/img/icon-gridview.svg"
|
||||
onClick={() => console.log("grid view")}
|
||||
data-origin="home"
|
||||
data-value="grid"
|
||||
alt="grid view"
|
||||
/>
|
||||
<img
|
||||
src="/img/icon-listview.svg"
|
||||
onClick={() => console.log("list view")}
|
||||
data-origin="home"
|
||||
data-value="list"
|
||||
alt="list view"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="video-list list">
|
||||
{videos &&
|
||||
videos?.data?.map((video) => (
|
||||
<div key={video.youtube_id} className="video-item list">
|
||||
<a
|
||||
style={{ cursor: "pointer" }}
|
||||
onClick={() => handleSelectedVideo(video)}
|
||||
>
|
||||
<div className="video-thumb-wrap list">
|
||||
<div className="video-thumb">
|
||||
<NextImage
|
||||
src={`http://localhost:8000/cache/${video.vid_thumb_url}`}
|
||||
alt="video-thumb"
|
||||
width={250}
|
||||
height={141}
|
||||
/>
|
||||
{/* {% if video.source.player.progress %} */}
|
||||
<div
|
||||
className="video-progress-bar"
|
||||
id={`progress-${video.youtube_id}`}
|
||||
// style={{ width: video.player.progress }} // TODO: /video/youtube_id/progress
|
||||
></div>
|
||||
{/* {% else %} */}
|
||||
<div
|
||||
className="video-progress-bar"
|
||||
id={`progress-${video.youtube_id}`}
|
||||
style={{ width: "0%" }}
|
||||
></div>
|
||||
{/* {% endif %} */}
|
||||
</div>
|
||||
<div className="video-play">
|
||||
<NextImage
|
||||
width={40}
|
||||
height={40}
|
||||
src={IconPlay}
|
||||
alt="play-icon"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<div className="video-desc list">
|
||||
<div
|
||||
className="video-desc-player"
|
||||
id={`video-info-${video.youtube_id}`}
|
||||
>
|
||||
{video.player.watched ? (
|
||||
<img
|
||||
src="/img/icon-seen.svg"
|
||||
alt="seen-icon"
|
||||
data-id={video.youtube_id}
|
||||
data-status="watched"
|
||||
// onClick="updateVideoWatchStatus(this)"
|
||||
className="watch-button"
|
||||
title="Mark as unwatched"
|
||||
/>
|
||||
) : (
|
||||
<img
|
||||
src="/img/icon-unseen.svg"
|
||||
alt="unseen-icon"
|
||||
data-id={video.youtube_id}
|
||||
data-status="unwatched"
|
||||
// onClick="updateVideoWatchStatus(this)"
|
||||
className="watch-button"
|
||||
title="Mark as watched"
|
||||
/>
|
||||
)}
|
||||
|
||||
<span>
|
||||
{video.published} | {video.player.duration_str}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<a href={`/channel/${video.channel.channel_id}`}>
|
||||
<h3>{video.channel.channel_name}</h3>
|
||||
</a>
|
||||
<a
|
||||
className="video-more"
|
||||
href={`/video/${video.youtube_id}`}
|
||||
>
|
||||
<h2>{video.title}</h2>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
// return (
|
||||
// <>
|
||||
// <div id="player" className="player-wrapper"></div>
|
||||
// <div className="boxed-content">
|
||||
// <div className="video-list list">
|
||||
// {/* {% if results %}
|
||||
// {% for video in results %} */}
|
||||
// {videos &&
|
||||
// videos?.data?.map((video) => (
|
||||
// <div key={video.youtube_id} className="video-item list">
|
||||
// <a
|
||||
// href="#player"
|
||||
// // data-id="{{ video.source.youtube_id }}"
|
||||
// data-id={video.youtube_id}
|
||||
// // onClick="createPlayer(this)"
|
||||
// >
|
||||
// <div className="video-thumb-wrap list">
|
||||
// <div className="video-thumb">
|
||||
// <img
|
||||
// src="/cache/{{ video.source.vid_thumb_url }}"
|
||||
// alt="video-thumb"
|
||||
// />
|
||||
// {/* {% if video.source.player.progress %} */}
|
||||
// <div
|
||||
// className="video-progress-bar"
|
||||
// // id="progress-{{ video.source.youtube_id }}"
|
||||
// id={`progress-${video.youtube_id}`}
|
||||
// // style="width: {{video.source.player.progress}}%;"
|
||||
// ></div>
|
||||
// {/* {% else %} */}
|
||||
// <div
|
||||
// className="video-progress-bar"
|
||||
// // id="progress-{{ video.source.youtube_id }}"
|
||||
// id={`progress-${video.youtube_id}`}
|
||||
// // style="width: 0%;"
|
||||
// ></div>
|
||||
// {/* {% endif %} */}
|
||||
// </div>
|
||||
// <div className="video-play">
|
||||
// <img src="/img/icon-play.svg" alt="play-icon" />
|
||||
// </div>
|
||||
// </div>
|
||||
// </a>
|
||||
// <div className="video-desc list">
|
||||
// <div
|
||||
// className="video-desc-player"
|
||||
// // id="video-info-{{ video.source.youtube_id }}"
|
||||
// id={video.youtube_id}
|
||||
// >
|
||||
// {/* {% if video.source.player.watched %} */}
|
||||
// <img
|
||||
// src="/img/icon-seen.svg"
|
||||
// alt="seen-icon"
|
||||
// data-id="{{ video.source.youtube_id }}"
|
||||
// data-status="watched"
|
||||
// // onClick="updateVideoWatchStatus(this)"
|
||||
// className="watch-button"
|
||||
// title="Mark as unwatched"
|
||||
// />
|
||||
// {/* {% else %} */}
|
||||
// <img
|
||||
// src="/img/icon-unseen.svg"
|
||||
// alt="unseen-icon"
|
||||
// data-id="{{ video.source.youtube_id }}"
|
||||
// data-status="unwatched"
|
||||
// // onClick="updateVideoWatchStatus(this)"
|
||||
// className="watch-button"
|
||||
// title="Mark as watched"
|
||||
// />
|
||||
// {/* {% endif %} */}
|
||||
// {/* <span>{{ video.source.published }} | {{ video.source.player.duration_str }}</span> */}
|
||||
// </div>
|
||||
// <div>
|
||||
// {/* <a href="{% url 'channel_id' video.source.channel.channel_id %}"><h3>{{ video.source.channel.channel_name }}</h3></a> */}
|
||||
// {/* <a className="video-more" href="{% url 'video' video.source.youtube_id %}"><h2>{{ video.source.title }}</h2></a> */}
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
// ))}
|
||||
|
||||
// {/* {% endfor %}
|
||||
// {% else %} */}
|
||||
|
||||
// {/* {% endif %} */}
|
||||
// </div>
|
||||
// </div>
|
||||
// </>
|
||||
// );
|
||||
};
|
||||
|
21
tubearchivist/www/src/lib/utils.ts
Normal file
21
tubearchivist/www/src/lib/utils.ts
Normal file
@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Appends a letter depending on the size of the number
|
||||
* @param number A number or string number
|
||||
* @returns A number string with an appropriate letter appended
|
||||
*/
|
||||
export function formatNumbers(number: string): string {
|
||||
var numberUnformatted = parseFloat(number);
|
||||
if (numberUnformatted > 999999999) {
|
||||
var numberFormatted =
|
||||
(numberUnformatted / 1000000000).toFixed(1).toString() + "B";
|
||||
} else if (numberUnformatted > 999999) {
|
||||
var numberFormatted =
|
||||
(numberUnformatted / 1000000).toFixed(1).toString() + "M";
|
||||
} else if (numberUnformatted > 999) {
|
||||
var numberFormatted =
|
||||
(numberUnformatted / 1000).toFixed(1).toString() + "K";
|
||||
} else {
|
||||
var numberFormatted = numberUnformatted.toString();
|
||||
}
|
||||
return numberFormatted;
|
||||
}
|
@ -441,6 +441,11 @@ deep-is@^0.1.3:
|
||||
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831"
|
||||
integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==
|
||||
|
||||
deepmerge@^4.0.0:
|
||||
version "4.2.2"
|
||||
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955"
|
||||
integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==
|
||||
|
||||
define-properties@^1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
|
||||
@ -1114,6 +1119,11 @@ levn@^0.4.1:
|
||||
prelude-ls "^1.2.1"
|
||||
type-check "~0.4.0"
|
||||
|
||||
load-script@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/load-script/-/load-script-1.0.0.tgz#0491939e0bee5643ee494a7e3da3d2bac70c6ca4"
|
||||
integrity sha1-BJGTngvuVkPuSUp+PaPSuscMbKQ=
|
||||
|
||||
locate-path@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e"
|
||||
@ -1141,6 +1151,11 @@ lru-cache@^6.0.0:
|
||||
dependencies:
|
||||
yallist "^4.0.0"
|
||||
|
||||
memoize-one@^5.1.1:
|
||||
version "5.2.1"
|
||||
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e"
|
||||
integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==
|
||||
|
||||
merge2@^1.3.0, merge2@^1.4.1:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
|
||||
@ -1431,7 +1446,7 @@ pretty-format@^3.8.0:
|
||||
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-3.8.0.tgz#bfbed56d5e9a776645f4b1ff7aa1a3ac4fa3c385"
|
||||
integrity sha1-v77VbV6ad2ZF9LH/eqGjrE+jw4U=
|
||||
|
||||
prop-types@^15.8.1:
|
||||
prop-types@^15.7.2, prop-types@^15.8.1:
|
||||
version "15.8.1"
|
||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
|
||||
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
|
||||
@ -1458,11 +1473,27 @@ react-dom@^18.0.0:
|
||||
loose-envify "^1.1.0"
|
||||
scheduler "^0.21.0"
|
||||
|
||||
react-fast-compare@^3.0.1:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb"
|
||||
integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==
|
||||
|
||||
react-is@^16.13.1:
|
||||
version "16.13.1"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||
|
||||
react-player@^2.10.0:
|
||||
version "2.10.0"
|
||||
resolved "https://registry.yarnpkg.com/react-player/-/react-player-2.10.0.tgz#bd3e0f10ae756cc644efb14177a515d455d97e20"
|
||||
integrity sha512-PccIqea9nxSHAdai6R+Yj9lp6tb2lyXWbaF6YVHi5uO4FiXYMKKr9rMXJrivwV5vXwQa65rYKBmwebsBmRTT3w==
|
||||
dependencies:
|
||||
deepmerge "^4.0.0"
|
||||
load-script "^1.0.0"
|
||||
memoize-one "^5.1.1"
|
||||
prop-types "^15.7.2"
|
||||
react-fast-compare "^3.0.1"
|
||||
|
||||
react@^18.0.0:
|
||||
version "18.0.0"
|
||||
resolved "https://registry.yarnpkg.com/react/-/react-18.0.0.tgz#b468736d1f4a5891f38585ba8e8fb29f91c3cb96"
|
||||
|
Loading…
Reference in New Issue
Block a user