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:
Sean Norwood 2022-04-06 18:29:04 +00:00
parent 99316eeee8
commit d86cf81719
4 changed files with 290 additions and 96 deletions

View File

@ -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",

View File

@ -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`&apos;`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>
// </>
// );
};

View 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;
}

View File

@ -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"