mirror of
https://github.com/tubearchivist/tubearchivist-frontend.git
synced 2024-12-22 09:50:15 +00:00
Merge pull request #2 from n8detar/feat/react-frontend
Added download page.
This commit is contained in:
commit
d6b34a7b30
8
tubearchivist/www/src/components/Nav.tsx
Normal file → Executable file
8
tubearchivist/www/src/components/Nav.tsx
Normal file → Executable file
@ -53,9 +53,11 @@ export const Nav = () => {
|
||||
<div className="nav-item">playlists</div>
|
||||
</a>
|
||||
</NextLink>
|
||||
<a href="/downloads">
|
||||
<div className="nav-item">downloads</div>
|
||||
</a>
|
||||
<NextLink href="/download">
|
||||
<a>
|
||||
<div className="nav-item">downloads</div>
|
||||
</a>
|
||||
</NextLink>
|
||||
</div>
|
||||
<div className="nav-icons">
|
||||
<a href="/search">
|
||||
|
41
tubearchivist/www/src/lib/getDownloads.ts
Normal file
41
tubearchivist/www/src/lib/getDownloads.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import { Download } from "../types/download";
|
||||
import { DownloadResponse } from "../types/download";
|
||||
import { TA_BASE_URL } from "./constants";
|
||||
|
||||
export const getDownloads = async (token: string): Promise<Download> => {
|
||||
const response = await fetch(`${TA_BASE_URL}/api/download/`, {
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Token ${token}`,
|
||||
mode: "no-cors",
|
||||
},
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error("Error getting download queue information");
|
||||
}
|
||||
return response.json();
|
||||
};
|
||||
|
||||
export const sendDownloads = async (token: string, input: string): Promise<DownloadResponse> => {
|
||||
var data = {
|
||||
"data": [{
|
||||
"youtube_id": input,
|
||||
"status": "pending"
|
||||
}]
|
||||
};
|
||||
const response = await fetch(`${TA_BASE_URL}/api/download/`, {
|
||||
body: JSON.stringify(data),
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Token ${token}`,
|
||||
mode: "no-cors",
|
||||
},
|
||||
method: "POST"
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error("Error adding content to the download queue.");
|
||||
}
|
||||
return response.json();
|
||||
};
|
302
tubearchivist/www/src/pages/download.tsx
Executable file
302
tubearchivist/www/src/pages/download.tsx
Executable file
@ -0,0 +1,302 @@
|
||||
import type { GetServerSideProps, NextPage } from "next";
|
||||
import { getSession, useSession } from "next-auth/react";
|
||||
import { useState } from "react";
|
||||
import { dehydrate, QueryClient, useQuery } from "react-query";
|
||||
import { CustomHead } from "../components/CustomHead";
|
||||
import { Layout } from "../components/Layout";
|
||||
import NextImage from "next/image";
|
||||
import { TA_BASE_URL } from "../lib/constants";
|
||||
import { getDownloads } from "../lib/getDownloads";
|
||||
import { sendDownloads } from "../lib/getDownloads";
|
||||
import RescanIcon from "../images/icon-rescan.svg";
|
||||
import DownloadIcon from "../images/icon-download.svg";
|
||||
import AddIcon from "../images/icon-add.svg";
|
||||
import GridViewIcon from "../images/icon-gridview.svg";
|
||||
import ListViewIcon from "../images/icon-listview.svg";
|
||||
|
||||
|
||||
type ViewStyle = "grid" | "list";
|
||||
type IgnoredStatus = boolean;
|
||||
type FormHidden = boolean;
|
||||
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = async (context) => {
|
||||
const queryClient = new QueryClient();
|
||||
const session = await getSession(context);
|
||||
|
||||
if (!session) {
|
||||
return {
|
||||
redirect: {
|
||||
destination: "/auth/login",
|
||||
permanent: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
await queryClient.prefetchQuery(["downloads", session.ta_token.token], () =>
|
||||
getDownloads(session.ta_token.token)
|
||||
);
|
||||
|
||||
return {
|
||||
props: {
|
||||
dehydratedState: dehydrate(queryClient),
|
||||
session,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const Download: NextPage = () => {
|
||||
const { data: session } = useSession();
|
||||
const {
|
||||
data: downloads,
|
||||
error,
|
||||
isLoading,
|
||||
} = useQuery(
|
||||
["downloads", session.ta_token.token],
|
||||
() => getDownloads(session.ta_token.token),
|
||||
{
|
||||
enabled: !!session?.ta_token?.token,
|
||||
}
|
||||
);
|
||||
|
||||
var count = 0;
|
||||
|
||||
const [viewStyle, setViewStyle] = useState<ViewStyle>(downloads?.config?.default_view?.downloads);
|
||||
const [ignoredStatus, setIgnoredStatus] = useState<IgnoredStatus>(false);
|
||||
const [formHidden, setFormHidden] = useState<FormHidden>(true);
|
||||
|
||||
const handleSetViewstyle = (selectedViewStyle: ViewStyle) => {
|
||||
setViewStyle(selectedViewStyle);
|
||||
};
|
||||
|
||||
const handleSetIgnoredStatus = (selectedIgnoredStatus: IgnoredStatus) => {
|
||||
setIgnoredStatus(selectedIgnoredStatus);
|
||||
};
|
||||
|
||||
const handleSetFormHidden = (selectedFormHidden: FormHidden) => {
|
||||
setFormHidden(selectedFormHidden);
|
||||
};
|
||||
|
||||
const addToDownloadQueue = event => {
|
||||
event.preventDefault();
|
||||
sendDownloads(session.ta_token.token, event.target.vid_url.value);
|
||||
handleSetFormHidden(true);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<CustomHead title="Downloads" />
|
||||
|
||||
<Layout>
|
||||
<div className="boxed-content">
|
||||
<div className="title-bar">
|
||||
<h1>Downloads</h1>
|
||||
</div>
|
||||
<div id="notifications"></div>
|
||||
<div id="downloadControl"></div>
|
||||
<div className="info-box info-box-3">
|
||||
<div className="icon-text">
|
||||
<NextImage
|
||||
width={80}
|
||||
height={80}
|
||||
src={RescanIcon}
|
||||
alt="rescan-icon"
|
||||
title="Rescan subscriptions"
|
||||
onClick={() => console.log("rescanPending()")}
|
||||
/>
|
||||
{/* <img id="rescan-icon" onclick="rescanPending()" src="{% static 'img/icon-rescan.svg' %}" alt="rescan-icon"></img> */}
|
||||
<p>Rescan subscriptions</p>
|
||||
</div>
|
||||
<div className="icon-text">
|
||||
<NextImage
|
||||
width={80}
|
||||
height={80}
|
||||
src={DownloadIcon}
|
||||
alt="download-icon"
|
||||
title="Start download"
|
||||
onClick={() => console.log("dlPending()")}
|
||||
/>
|
||||
{/* <img id="download-icon" onclick="dlPending()" src="{% static 'img/icon-download.svg' %}" alt="download-icon"></img> */}
|
||||
<p>Start download</p>
|
||||
</div>
|
||||
<div className="icon-text">
|
||||
<NextImage
|
||||
width={80}
|
||||
height={80}
|
||||
src={AddIcon}
|
||||
alt="add-icon"
|
||||
title="Add to download queue"
|
||||
onClick={() => formHidden ? handleSetFormHidden(false) : handleSetFormHidden(true)}
|
||||
/>
|
||||
<p>Add to download queue</p>
|
||||
{!formHidden &&
|
||||
<div className="show-form">
|
||||
<form id="hidden-form" onSubmit={addToDownloadQueue}>
|
||||
<textarea name="vid_url" cols={40} rows={4} placeholder="Enter Video Urls or IDs here..." required id="id_vid_url" spellCheck="false" />
|
||||
<button type="submit">Add to download queue</button>
|
||||
</form>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div className="view-controls">
|
||||
<div className="toggle">
|
||||
<span>Show only ignored videos:</span>
|
||||
{ignoredStatus &&
|
||||
<div className="toggleBox">
|
||||
<input
|
||||
id="show_ignored_only"
|
||||
onChange={() => handleSetIgnoredStatus(false)}
|
||||
type="checkbox"
|
||||
checked
|
||||
/>
|
||||
<label htmlFor="" className="onbtn">
|
||||
On
|
||||
</label>
|
||||
</div>
|
||||
}
|
||||
{!ignoredStatus &&
|
||||
<div className="toggleBox">
|
||||
<input
|
||||
id="show_ignored_only"
|
||||
onChange={() => handleSetIgnoredStatus(true)}
|
||||
type="checkbox"
|
||||
/>
|
||||
<label htmlFor="" className="ofbtn">
|
||||
Off
|
||||
</label>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div className="view-icons">
|
||||
<NextImage
|
||||
width={30}
|
||||
height={34}
|
||||
src={GridViewIcon}
|
||||
alt="grid view"
|
||||
title="Switch to grid view"
|
||||
onClick={() => handleSetViewstyle("grid")}
|
||||
/>
|
||||
{/* <img src="{% static 'img/icon-gridview.svg' %}" onclick="changeView(this)" data-origin="downloads" data-value="grid" alt="grid view"> */}
|
||||
<NextImage
|
||||
width={30}
|
||||
height={34}
|
||||
src={ListViewIcon}
|
||||
alt="list view"
|
||||
title="Switch to list view"
|
||||
onClick={() => handleSetViewstyle("list")}
|
||||
/>
|
||||
{/* <img src="{% static 'img/icon-listview.svg' %}" onclick="changeView(this)" data-origin="downloads" data-value="list" alt="list view"> */}
|
||||
</div>
|
||||
</div>
|
||||
{ignoredStatus &&
|
||||
<div className="title-split">
|
||||
<h2>Ignored from download</h2>
|
||||
<button onClick={() => console.log("deleteQueue(this)")} data-id="ignore" title="Delete all previously ignored videos from the queue">Delete all ignored</button>
|
||||
</div>
|
||||
}
|
||||
{!ignoredStatus &&
|
||||
<div className="title-split">
|
||||
<h2>Download queue</h2>
|
||||
<button onClick={() => console.log("deleteQueue(this)")} data-id="pending" title="Delete all pending videos from the queue">Delete all queued</button>
|
||||
</div>
|
||||
}
|
||||
{downloads?.data?.forEach((video) => {
|
||||
if ((video?.status == "ignore" && ignoredStatus) || (video?.status == "pending" && !ignoredStatus)) {
|
||||
count++;
|
||||
}
|
||||
})}
|
||||
<h3>Total videos: {count} {!count && <p>No videos queued for download. Press rescan subscriptions to check if there are any new videos.</p>}</h3>
|
||||
<div className={`dl-list ${viewStyle}`}>
|
||||
{downloads.data &&
|
||||
downloads?.data?.map((video) => {
|
||||
count++;
|
||||
if ((video?.status == "ignore" && ignoredStatus) || (video?.status == "pending" && !ignoredStatus)) {
|
||||
return (
|
||||
<div key={video?.youtube_id} className={`dl-item ${viewStyle}`}>
|
||||
<div className={`dl-thumb ${viewStyle}`}>
|
||||
<img src={`${TA_BASE_URL}${video?.vid_thumb_url}`} alt="video_thumb"></img>
|
||||
{ignoredStatus && <span>ignored</span>}
|
||||
{/* {% if show_ignored_only %} */}
|
||||
{/* <span>ignored</span> */}
|
||||
{!ignoredStatus && <span>queued</span>}
|
||||
{/* {% else %} */}
|
||||
{/* <span>queued</span> */}
|
||||
{/* {% endif %} */}
|
||||
</div>
|
||||
<div className={`dl-desc ${viewStyle}`}>
|
||||
<h3>{video?.title}</h3>
|
||||
{video?.channel_indexed && <a href={`/channel/${video?.channel_id}`}>{video?.channel_name}</a>}
|
||||
{/* {% if video.source.channel_indexed %} */}
|
||||
{/* <a href="{% url 'channel_id' video.source.channel_id %}">{{ video.source.channel_name }}</a> */}
|
||||
{!video?.channel_indexed && <span>{video?.channel_name}</span>}
|
||||
{/* {% else %} */}
|
||||
{/* <span>{{ video.source.channel_name }}</span> */}
|
||||
{/* {% endif %} */}
|
||||
<p>Published: {video?.published} | Duration: {video?.duration} | {video?.youtube_id}</p>
|
||||
{/* <p>Published: {{ video.source.published }} | Duration: {{ video.source.duration }} | {{ video.source.youtube_id }}</p> */}
|
||||
{ignoredStatus &&
|
||||
<div>
|
||||
<button data-id={`${video?.youtube_id}`} onClick={() => console.log("forgetIgnore(this)")}>Forget</button>
|
||||
<button data-id={`${video?.youtube_id}`} onClick={() => console.log("addSingle(this)")}>Add to queue</button>
|
||||
</div>
|
||||
}
|
||||
{/* {% if show_ignored_only %} */}
|
||||
{/* <button data-id="{{ video.source.youtube_id }}" onclick="forgetIgnore(this)">Forget</button> */}
|
||||
{/* <button data-id="{{ video.source.youtube_id }}" onclick="addSingle(this)">Add to queue</button> */}
|
||||
{!ignoredStatus &&
|
||||
<div>
|
||||
<button data-id={`${video?.youtube_id}`} onClick={() => console.log("toIgnore(this)")}>Ignore</button>
|
||||
<button id={`${video?.youtube_id}`} data-id={`${video?.youtube_id}`} onClick={() => console.log("downloadNow(this)")}>Download now</button>
|
||||
</div>
|
||||
}
|
||||
{/* {% else %} */}
|
||||
{/* <button data-id="{{ video.source.youtube_id }}" onclick="toIgnore(this)">Ignore</button> */}
|
||||
{/* <button id="{{ video.source.youtube_id }}" data-id="{{ video.source.youtube_id }}" onclick="downloadNow(this)">Download now</button> */}
|
||||
{/* {% endif %} */}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
})
|
||||
}
|
||||
{/* {% if results %} */}
|
||||
{/* {% for video in results %} */}
|
||||
{/* <div className="dl-item {{ view_style }}" id="dl-{{ video.source.youtube_id }}"> */}
|
||||
{/* <div className="dl-thumb {{ view_style }}"> */}
|
||||
{/* <img src="/cache/{{ video.source.vid_thumb_url }}" alt="video_thumb"> */}
|
||||
{/* {% if show_ignored_only %} */}
|
||||
{/* <span>ignored</span> */}
|
||||
{/* {% else %} */}
|
||||
{/* <span>queued</span> */}
|
||||
{/* {% endif %} */}
|
||||
{/* </div> */}
|
||||
{/* <div className="dl-desc {{ view_style }}"> */}
|
||||
{/* <h3>{{ video.source.title }}</h3> */}
|
||||
{/* {% if video.source.channel_indexed %} */}
|
||||
{/* <a href="{% url 'channel_id' video.source.channel_id %}">{{ video.source.channel_name }}</a> */}
|
||||
{/* {% else %} */}
|
||||
{/* <span>{{ video.source.channel_name }}</span> */}
|
||||
{/* {% endif %} */}
|
||||
{/* <p>Published: {{ video.source.published }} | Duration: {{ video.source.duration }} | {{ video.source.youtube_id }}</p> */}
|
||||
{/* {% if show_ignored_only %} */}
|
||||
{/* <button data-id="{{ video.source.youtube_id }}" onclick="forgetIgnore(this)">Forget</button> */}
|
||||
{/* <button data-id="{{ video.source.youtube_id }}" onclick="addSingle(this)">Add to queue</button> */}
|
||||
{/* {% else %} */}
|
||||
{/* <button data-id="{{ video.source.youtube_id }}" onclick="toIgnore(this)">Ignore</button> */}
|
||||
{/* <button id="{{ video.source.youtube_id }}" data-id="{{ video.source.youtube_id }}" onclick="downloadNow(this)">Download now</button> */}
|
||||
{/* {% endif %} */}
|
||||
{/* </div> */}
|
||||
{/* </div> */}
|
||||
{/* {% endfor %} */}
|
||||
{/* {% endif %} */}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{/* <script type="text/javascript" src="{% static 'progress.js' %}"></script> */}
|
||||
</Layout>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Download;
|
4
tubearchivist/www/src/styles/globals.css
Normal file → Executable file
4
tubearchivist/www/src/styles/globals.css
Normal file → Executable file
@ -333,9 +333,9 @@ button:hover {
|
||||
filter: var(--img-filter);
|
||||
}
|
||||
|
||||
#hidden-form {
|
||||
/* #hidden-form {
|
||||
display: none;
|
||||
}
|
||||
} */
|
||||
|
||||
#text-reveal {
|
||||
height: 0px;
|
||||
|
166
tubearchivist/www/src/types/download.ts
Executable file
166
tubearchivist/www/src/types/download.ts
Executable file
@ -0,0 +1,166 @@
|
||||
export interface Download {
|
||||
data: Datum[];
|
||||
config: Config;
|
||||
paginate: boolean;
|
||||
}
|
||||
|
||||
export interface DownloadResponse {
|
||||
data: Datum[];
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface Config {
|
||||
archive: Archive;
|
||||
default_view: DefaultView;
|
||||
subscriptions: Subscriptions;
|
||||
downloads: Downloads;
|
||||
application: Application;
|
||||
scheduler: Scheduler;
|
||||
}
|
||||
|
||||
export interface Application {
|
||||
app_root: string;
|
||||
cache_dir: string;
|
||||
videos: string;
|
||||
file_template: string;
|
||||
colors: string;
|
||||
enable_cast: boolean;
|
||||
REDIS_HOST: string;
|
||||
es_url: string;
|
||||
es_auth: string[];
|
||||
HOST_UID: number;
|
||||
HOST_GID: number;
|
||||
}
|
||||
|
||||
export interface Archive {
|
||||
sort_by: string;
|
||||
sort_order: string;
|
||||
page_size: number;
|
||||
}
|
||||
|
||||
export interface DefaultView {
|
||||
home: string;
|
||||
channel: string;
|
||||
downloads: "grid" | "list";
|
||||
playlist: string;
|
||||
}
|
||||
|
||||
export interface Downloads {
|
||||
limit_count: boolean;
|
||||
limit_speed: boolean;
|
||||
sleep_interval: number;
|
||||
autodelete_days: boolean;
|
||||
format: boolean;
|
||||
add_metadata: boolean;
|
||||
add_thumbnail: boolean;
|
||||
subtitle: boolean;
|
||||
subtitle_source: boolean;
|
||||
subtitle_index: boolean;
|
||||
throttledratelimit: boolean;
|
||||
integrate_ryd: boolean;
|
||||
integrate_sponsorblock: boolean;
|
||||
}
|
||||
|
||||
export interface Scheduler {
|
||||
update_subscribed: boolean;
|
||||
download_pending: boolean;
|
||||
check_reindex: CheckReindex;
|
||||
check_reindex_days: number;
|
||||
thumbnail_check: CheckReindex;
|
||||
run_backup: CheckReindex;
|
||||
run_backup_rotate: number;
|
||||
}
|
||||
|
||||
export interface CheckReindex {
|
||||
minute: string;
|
||||
hour: string;
|
||||
day_of_week: string;
|
||||
}
|
||||
|
||||
export interface Subscriptions {
|
||||
auto_search: boolean;
|
||||
auto_download: boolean;
|
||||
channel_size: number;
|
||||
}
|
||||
|
||||
export interface Datum {
|
||||
channel_id: string;
|
||||
channel_indexed: boolean;
|
||||
channel_name: string;
|
||||
duration: string;
|
||||
published: string;
|
||||
status: string;
|
||||
timestamp: number;
|
||||
title: string;
|
||||
vid_thumb_url: string;
|
||||
youtube_id: string;
|
||||
|
||||
// active: boolean;
|
||||
// category: Category[];
|
||||
// channel: Channel;
|
||||
// date_downloaded: number;
|
||||
// description: string;
|
||||
// media_url: string;
|
||||
// player: Player;
|
||||
// playlist: Playlist[];
|
||||
// stats: Stats;
|
||||
// tags: string[];
|
||||
// vid_last_refresh: LastRefresh;
|
||||
// vid_thumb_base64: string;
|
||||
}
|
||||
|
||||
export enum Category {
|
||||
ScienceTechnology = "Science & Technology",
|
||||
}
|
||||
|
||||
export interface Channel {
|
||||
channel_active: boolean;
|
||||
channel_banner_url: ChannelBannerURL;
|
||||
channel_description: string;
|
||||
channel_id: ChannelID;
|
||||
channel_last_refresh: LastRefresh;
|
||||
channel_name: ChannelName;
|
||||
channel_subs: number;
|
||||
channel_subscribed: boolean;
|
||||
channel_thumb_url: ChannelThumbURL;
|
||||
channel_tvart_url: boolean;
|
||||
channel_views: number;
|
||||
channel_indexed: boolean;
|
||||
}
|
||||
|
||||
export enum ChannelBannerURL {
|
||||
CacheChannelsUCFhXFikryT4AFcLkLw2LBLABannerJpg = "/cache/channels/UCFhXFikryT4aFcLkLw2LBLA_banner.jpg",
|
||||
}
|
||||
|
||||
export enum ChannelID {
|
||||
UCFhXFikryT4AFcLkLw2LBLA = "UCFhXFikryT4aFcLkLw2LBLA",
|
||||
}
|
||||
|
||||
export enum LastRefresh {
|
||||
The05APR2022 = "05 Apr, 2022",
|
||||
}
|
||||
|
||||
export enum ChannelName {
|
||||
NileRed = "NileRed",
|
||||
}
|
||||
|
||||
export enum ChannelThumbURL {
|
||||
CacheChannelsUCFhXFikryT4AFcLkLw2LBLAThumbJpg = "/cache/channels/UCFhXFikryT4aFcLkLw2LBLA_thumb.jpg",
|
||||
}
|
||||
|
||||
export interface Player {
|
||||
watched: boolean;
|
||||
duration: number;
|
||||
duration_str: string;
|
||||
}
|
||||
|
||||
export enum Playlist {
|
||||
PLbaramj7Nly5K5AsvQoI9PJQhy47PfDAF = "PLbaramj7Nly5K5AsvQoI9PJQhy47pfDAf",
|
||||
}
|
||||
|
||||
export interface Stats {
|
||||
view_count: number;
|
||||
like_count: number;
|
||||
dislike_count: number;
|
||||
average_rating: null;
|
||||
}
|
Loading…
Reference in New Issue
Block a user