mirror of
https://github.com/tubearchivist/tubearchivist-frontend.git
synced 2024-11-21 19:30:16 +00:00
commit
9b4b75a11a
@ -134,7 +134,9 @@ chown 1000:0 /path/to/mount/point
|
||||
This will match the permissions with the **UID** and **GID** of elasticsearch within the container and should fix the issue.
|
||||
|
||||
### Disk usage
|
||||
The Elasticsearch index will turn to *read only* if the disk usage of the container goes above 95% until the usage drops below 90% again. Similar to that, TubeArchivist will become all sorts of messed up when running out of disk space. There are some error messages in the logs when that happens, but it's best to make sure to have enough disk space before starting to download.
|
||||
The Elasticsearch index will turn to *read only* if the disk usage of the container goes above 95% until the usage drops below 90% again, you will see error messages like `disk usage exceeded flood-stage watermark`, [link](https://github.com/tubearchivist/tubearchivist#disk-usage).
|
||||
|
||||
Similar to that, TubeArchivist will become all sorts of messed up when running out of disk space. There are some error messages in the logs when that happens, but it's best to make sure to have enough disk space before starting to download.
|
||||
|
||||
## Getting Started
|
||||
1. Go through the **settings** page and look at the available options. Particularly set *Download Format* to your desired video quality before downloading. **Tube Archivist** downloads the best available quality by default. To support iOS or MacOS and some other browsers a compatible format must be specified. For example:
|
||||
|
@ -20,6 +20,19 @@ headers = {"Authorization": "Token xxxxxxxxxx"}
|
||||
response = requests.get(url, headers=headers)
|
||||
```
|
||||
|
||||
## Pagination
|
||||
The list views return a paginate object with the following keys:
|
||||
- page_size: int current page size set in config
|
||||
- page_from: int first result idx
|
||||
- prev_pages: array of ints of previous pages, if available
|
||||
- current_page: int current page from query
|
||||
- max_hits: reached: bool if max of 10k results is reached
|
||||
- last_page: int of last page link
|
||||
- next_pages: array of ints of next pages
|
||||
- total_hits: int total results
|
||||
|
||||
Pass page number as a query parameter: `page=2`. Defaults to *0*, `page=1` is redundant and falls back to *0*. If a page query doesn't return any results, you'll get `HTTP 404 Not Found`.
|
||||
|
||||
## Login View
|
||||
Return token and user ID for username and password:
|
||||
POST /api/login
|
||||
|
@ -3,6 +3,7 @@
|
||||
from api.src.search_processor import SearchProcess
|
||||
from home.src.download.queue import PendingInteract
|
||||
from home.src.es.connect import ElasticWrap
|
||||
from home.src.index.generic import Pagination
|
||||
from home.src.index.video import SponsorBlock
|
||||
from home.src.ta.config import AppConfig
|
||||
from home.src.ta.helper import UrlListParser
|
||||
@ -25,12 +26,15 @@ class ApiBaseView(APIView):
|
||||
authentication_classes = [SessionAuthentication, TokenAuthentication]
|
||||
permission_classes = [IsAuthenticated]
|
||||
search_base = False
|
||||
data = False
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.response = {"data": False, "config": AppConfig().config}
|
||||
self.data = {"query": {"match_all": {}}}
|
||||
self.status_code = False
|
||||
self.context = False
|
||||
self.pagination_handler = False
|
||||
|
||||
def get_document(self, document_id):
|
||||
"""get single document from es"""
|
||||
@ -44,20 +48,33 @@ class ApiBaseView(APIView):
|
||||
self.response["data"] = False
|
||||
self.status_code = status_code
|
||||
|
||||
def get_paginate(self):
|
||||
"""add pagination detail to response"""
|
||||
self.response["paginate"] = False
|
||||
def initiate_pagination(self, request):
|
||||
"""set initial pagination values"""
|
||||
user_id = request.user.id
|
||||
page_get = int(request.GET.get("page", 0))
|
||||
self.pagination_handler = Pagination(page_get, user_id)
|
||||
self.data.update(
|
||||
{
|
||||
"size": self.pagination_handler.pagination["page_size"],
|
||||
"from": self.pagination_handler.pagination["page_from"],
|
||||
}
|
||||
)
|
||||
|
||||
def get_document_list(self, data):
|
||||
def get_document_list(self, request):
|
||||
"""get a list of results"""
|
||||
print(self.search_base)
|
||||
response, status_code = ElasticWrap(self.search_base).get(data=data)
|
||||
self.initiate_pagination(request)
|
||||
es_handler = ElasticWrap(self.search_base)
|
||||
response, status_code = es_handler.get(data=self.data)
|
||||
self.response["data"] = SearchProcess(response).process()
|
||||
if self.response["data"]:
|
||||
self.status_code = status_code
|
||||
else:
|
||||
self.status_code = 404
|
||||
|
||||
self.pagination_handler.validate(response["hits"]["total"]["value"])
|
||||
self.response["paginate"] = self.pagination_handler.pagination
|
||||
|
||||
|
||||
class VideoApiView(ApiBaseView):
|
||||
"""resolves to /api/video/<video_id>/
|
||||
@ -81,11 +98,9 @@ class VideoApiListView(ApiBaseView):
|
||||
search_base = "ta_video/_search/"
|
||||
|
||||
def get(self, request):
|
||||
# pylint: disable=unused-argument
|
||||
"""get request"""
|
||||
data = {"query": {"match_all": {}}}
|
||||
self.get_document_list(data)
|
||||
self.get_paginate()
|
||||
self.data.update({"sort": [{"published": {"order": "desc"}}]})
|
||||
self.get_document_list(request)
|
||||
|
||||
return Response(self.response)
|
||||
|
||||
@ -200,11 +215,11 @@ class ChannelApiListView(ApiBaseView):
|
||||
search_base = "ta_channel/_search/"
|
||||
|
||||
def get(self, request):
|
||||
# pylint: disable=unused-argument
|
||||
"""get request"""
|
||||
data = {"query": {"match_all": {}}}
|
||||
self.get_document_list(data)
|
||||
self.get_paginate()
|
||||
self.get_document_list(request)
|
||||
self.data.update(
|
||||
{"sort": [{"channel_name.keyword": {"order": "asc"}}]}
|
||||
)
|
||||
|
||||
return Response(self.response)
|
||||
|
||||
@ -234,13 +249,16 @@ class ChannelApiVideoView(ApiBaseView):
|
||||
search_base = "ta_video/_search/"
|
||||
|
||||
def get(self, request, channel_id):
|
||||
# pylint: disable=unused-argument
|
||||
"""handle get request"""
|
||||
data = {
|
||||
"query": {"term": {"channel.channel_id": {"value": channel_id}}}
|
||||
}
|
||||
self.get_document_list(data)
|
||||
self.get_paginate()
|
||||
self.data.update(
|
||||
{
|
||||
"query": {
|
||||
"term": {"channel.channel_id": {"value": channel_id}}
|
||||
},
|
||||
"sort": [{"published": {"order": "desc"}}],
|
||||
}
|
||||
)
|
||||
self.get_document_list(request)
|
||||
|
||||
return Response(self.response, status=self.status_code)
|
||||
|
||||
@ -253,11 +271,11 @@ class PlaylistApiListView(ApiBaseView):
|
||||
search_base = "ta_playlist/_search/"
|
||||
|
||||
def get(self, request):
|
||||
# pylint: disable=unused-argument
|
||||
"""handle get request"""
|
||||
data = {"query": {"match_all": {}}}
|
||||
self.get_document_list(data)
|
||||
self.get_paginate()
|
||||
self.data.update(
|
||||
{"sort": [{"playlist_name.keyword": {"order": "asc"}}]}
|
||||
)
|
||||
self.get_document_list(request)
|
||||
return Response(self.response)
|
||||
|
||||
|
||||
@ -283,13 +301,13 @@ class PlaylistApiVideoView(ApiBaseView):
|
||||
search_base = "ta_video/_search/"
|
||||
|
||||
def get(self, request, playlist_id):
|
||||
# pylint: disable=unused-argument
|
||||
"""handle get request"""
|
||||
data = {
|
||||
"query": {"term": {"playlist.keyword": {"value": playlist_id}}}
|
||||
self.data["query"] = {
|
||||
"term": {"playlist.keyword": {"value": playlist_id}}
|
||||
}
|
||||
self.get_document_list(data)
|
||||
self.get_paginate()
|
||||
self.data.update({"sort": [{"published": {"order": "desc"}}]})
|
||||
|
||||
self.get_document_list(request)
|
||||
return Response(self.response, status=self.status_code)
|
||||
|
||||
|
||||
@ -344,23 +362,18 @@ class DownloadApiListView(ApiBaseView):
|
||||
valid_filter = ["pending", "ignore"]
|
||||
|
||||
def get(self, request):
|
||||
# pylint: disable=unused-argument
|
||||
"""get request"""
|
||||
query_filter = request.GET.get("filter", False)
|
||||
data = {
|
||||
"query": {"match_all": {}},
|
||||
"sort": [{"timestamp": {"order": "asc"}}],
|
||||
}
|
||||
self.data.update({"sort": [{"timestamp": {"order": "asc"}}]})
|
||||
if query_filter:
|
||||
if query_filter not in self.valid_filter:
|
||||
message = f"invalid url query filder: {query_filter}"
|
||||
print(message)
|
||||
return Response({"message": message}, status=400)
|
||||
|
||||
data["query"] = {"term": {"status": {"value": query_filter}}}
|
||||
self.data["query"] = {"term": {"status": {"value": query_filter}}}
|
||||
|
||||
self.get_document_list(data)
|
||||
self.get_paginate()
|
||||
self.get_document_list(request)
|
||||
return Response(self.response)
|
||||
|
||||
@staticmethod
|
||||
|
@ -147,3 +147,4 @@ class Pagination:
|
||||
]
|
||||
|
||||
self.pagination["next_pages"] = next_pages
|
||||
self.pagination["total_hits"] = total_hits
|
||||
|
@ -195,6 +195,11 @@ class SubtitleParser:
|
||||
|
||||
if flatten:
|
||||
# fix overlapping retiming issue
|
||||
if "dDurationMs" not in flatten[-1]:
|
||||
# some events won't have a duration
|
||||
print(f"failed to parse event without duration: {event}")
|
||||
continue
|
||||
|
||||
last_end = flatten[-1]["tStartMs"] + flatten[-1]["dDurationMs"]
|
||||
if event["tStartMs"] < last_end:
|
||||
joined = flatten[-1]["segs"][0]["utf8"] + "\n" + text
|
||||
|
90
tubearchivist/www/src/lib/getDownloads.ts
Normal file → Executable file
90
tubearchivist/www/src/lib/getDownloads.ts
Normal file → Executable file
@ -1,9 +1,10 @@
|
||||
import { Download } from "../types/download";
|
||||
import { DownloadResponse } from "../types/download";
|
||||
import { TA_BASE_URL } from "./constants";
|
||||
import { getTAUrl } from "./constants";
|
||||
|
||||
export const getDownloads = async (token: string): Promise<Download> => {
|
||||
const response = await fetch(`${TA_BASE_URL}/api/download/`, {
|
||||
const TA_BASE_URL = getTAUrl();
|
||||
|
||||
export const getDownloads = async (token: string, filter: boolean, pageNumber: number): Promise<Download> => {
|
||||
const response = await fetch(`${TA_BASE_URL.server}/api/download/?filter=${filter ? 'ignore' : 'pending'}&page=${pageNumber}`, {
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
@ -11,20 +12,87 @@ export const getDownloads = async (token: string): Promise<Download> => {
|
||||
mode: "no-cors",
|
||||
},
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error("Error getting download queue information");
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
} else {
|
||||
// var error: Download = {
|
||||
// data: null,
|
||||
// config: null,
|
||||
// paginate: null,
|
||||
// message: response.statusText + " (" + response.status + ")",
|
||||
// }
|
||||
// error = response.statusText + " (" + response.status + ");
|
||||
throw new Error(response.statusText + " (" + response.status + ")");
|
||||
// return error;
|
||||
}
|
||||
return response.json();
|
||||
};
|
||||
|
||||
export const sendDownloads = async (token: string, input: string): Promise<DownloadResponse> => {
|
||||
export const sendDownloads = async (token: string, input: string): Promise<Download> => {
|
||||
var data = {
|
||||
"data": [{
|
||||
"youtube_id": input,
|
||||
"status": "pending"
|
||||
}]
|
||||
};
|
||||
const response = await fetch(`${TA_BASE_URL}/api/download/`, {
|
||||
const response = await fetch(`${TA_BASE_URL.server}/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) {
|
||||
return response.json();
|
||||
} else if (response.status == 400) {
|
||||
throw new Error("Failed to extract links. Please input IDs or URLs for videos, channels, or playlists.");
|
||||
} else {
|
||||
throw new Error("Failed to connect to the API.");
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
export const sendDeleteAllQueuedIgnored = async (token: string, filter: string): Promise<Download> => {
|
||||
const response = await fetch(`${TA_BASE_URL.server}/api/download/?filter=${filter}`, {
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Token ${token}`,
|
||||
mode: "no-cors",
|
||||
},
|
||||
method: "DELETE"
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error("Error removing all videos.");
|
||||
// return response.json();
|
||||
}
|
||||
return response.json();
|
||||
};
|
||||
|
||||
export const sendDeleteVideoQueuedIgnored = async (token: string, videoId: string): Promise<Download> => {
|
||||
const response = await fetch(`${TA_BASE_URL.server}/api/download/${videoId}/`, {
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Token ${token}`,
|
||||
mode: "no-cors",
|
||||
},
|
||||
method: "DELETE"
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error("Error removing video.");
|
||||
// return response.json();
|
||||
}
|
||||
return response.json();
|
||||
};
|
||||
|
||||
export const sendMoveVideoQueuedIgnored = async (token: string, videoId: string, status: string): Promise<Download> => {
|
||||
var data = {
|
||||
"status": status
|
||||
};
|
||||
const response = await fetch(`${TA_BASE_URL.server}/api/download/${videoId}/`, {
|
||||
body: JSON.stringify(data),
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
@ -35,7 +103,7 @@ export const sendDownloads = async (token: string, input: string): Promise<Downl
|
||||
method: "POST"
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error("Error adding content to the download queue.");
|
||||
throw new Error("Error moving video to" + status + ".");
|
||||
}
|
||||
return response.json();
|
||||
};
|
||||
};
|
@ -6,6 +6,7 @@ import { CustomHead } from "../components/CustomHead";
|
||||
import { Layout } from "../components/Layout";
|
||||
import { getTAUrl } from "../lib/constants";
|
||||
import { getChannels } from "../lib/getChannels";
|
||||
import { formatNumbers } from "../lib/utils";
|
||||
|
||||
const TA_BASE_URL = getTAUrl();
|
||||
|
||||
@ -170,7 +171,7 @@ const Channel: NextPage = () => {
|
||||
</a>
|
||||
</h3>
|
||||
{/* {% if channel.source.channel_subs >= 1000000 %} */}
|
||||
<p>Subscribers: {channel?.channel_subs} </p>
|
||||
<p>Subscribers: {formatNumbers(channel?.channel_subs)} </p>
|
||||
{/* {% else %} */}
|
||||
</div>
|
||||
</div>
|
||||
@ -184,20 +185,20 @@ const Channel: NextPage = () => {
|
||||
className="unsubscribe"
|
||||
type="button"
|
||||
id="{{ channel.source.channel_id }}"
|
||||
onClick={() => console.log("unsubscribe(this.id)")}
|
||||
title="Unsubscribe from {{ channel.source.channel_name }}"
|
||||
onClick={() => console.log("unsubscribe(this.id) -> toggleSubscribe()")}
|
||||
title={`${channel?.channel_subscribed ? "Unsubscribe from" : "Subscribe to"} ${channel?.channel_name}`}
|
||||
>
|
||||
Unsubscribe
|
||||
{channel?.channel_subscribed ? "Unsubscribe" : "Subscribe"}
|
||||
</button>
|
||||
{/* {% else %} */}
|
||||
<button
|
||||
{/* <button
|
||||
type="button"
|
||||
id="{{ channel.source.channel_id }}"
|
||||
onClick={() => console.log("subscribe(this.id)")}
|
||||
title="Subscribe to {{ channel.source.channel_name }}"
|
||||
>
|
||||
Subscribe
|
||||
</button>
|
||||
</button> */}
|
||||
{/* {% endif %} */}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -5,20 +5,24 @@ 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 { getDownloads, sendDeleteAllQueuedIgnored, sendDeleteVideoQueuedIgnored, sendMoveVideoQueuedIgnored } 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";
|
||||
import StopIcon from "../images/icon-stop.svg";
|
||||
import CloseIcon from "../images/icon-close.svg";
|
||||
import { getTAUrl } from "../lib/constants";
|
||||
|
||||
const TA_BASE_URL = getTAUrl();
|
||||
|
||||
type ViewStyle = "grid" | "list";
|
||||
type IgnoredStatus = boolean;
|
||||
type FormHidden = boolean;
|
||||
|
||||
type ErrorMessage = string;
|
||||
type PageNumber = number;
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = async (context) => {
|
||||
const queryClient = new QueryClient();
|
||||
@ -33,8 +37,8 @@ export const getServerSideProps: GetServerSideProps = async (context) => {
|
||||
};
|
||||
}
|
||||
|
||||
await queryClient.prefetchQuery(["downloads", session.ta_token.token], () =>
|
||||
getDownloads(session.ta_token.token)
|
||||
await queryClient.prefetchQuery(["downloads", session.ta_token.token, false, 1], () =>
|
||||
getDownloads(session.ta_token.token, false, 1)
|
||||
);
|
||||
|
||||
return {
|
||||
@ -47,40 +51,77 @@ export const getServerSideProps: GetServerSideProps = async (context) => {
|
||||
|
||||
const Download: NextPage = () => {
|
||||
const { data: session } = useSession();
|
||||
|
||||
const [ignoredStatus, setIgnoredStatus] = useState<IgnoredStatus>(false);
|
||||
const [pageNumber, setPageNumber] = useState<PageNumber>(1);
|
||||
|
||||
const {
|
||||
data: downloads,
|
||||
error,
|
||||
isLoading,
|
||||
refetch,
|
||||
} = useQuery(
|
||||
["downloads", session.ta_token.token],
|
||||
() => getDownloads(session.ta_token.token),
|
||||
["downloads", session.ta_token.token, ignoredStatus, pageNumber],
|
||||
() => getDownloads(session.ta_token.token, ignoredStatus, pageNumber),
|
||||
{
|
||||
enabled: !!session?.ta_token?.token,
|
||||
refetchInterval: 1500,
|
||||
refetchIntervalInBackground: false,
|
||||
}
|
||||
);
|
||||
|
||||
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 [viewStyle, setViewStyle] = useState<ViewStyle>(downloads?.config?.default_view?.downloads);
|
||||
const [errorMessage, setErrorMessage] = useState<ErrorMessage>(null);
|
||||
|
||||
const handleSetViewstyle = (selectedViewStyle: ViewStyle) => {
|
||||
setViewStyle(selectedViewStyle);
|
||||
};
|
||||
|
||||
|
||||
const handleSetIgnoredStatus = (selectedIgnoredStatus: IgnoredStatus) => {
|
||||
setIgnoredStatus(selectedIgnoredStatus);
|
||||
refetch();
|
||||
handleSetPageNumber(1);
|
||||
};
|
||||
|
||||
const handleSetFormHidden = (selectedFormHidden: FormHidden) => {
|
||||
setFormHidden(selectedFormHidden);
|
||||
};
|
||||
|
||||
const handleSetErrorMessage = (selectedErrorMessage: ErrorMessage) => {
|
||||
setErrorMessage(selectedErrorMessage);
|
||||
};
|
||||
|
||||
const handleSetPageNumber = (selectedPageNumber: PageNumber) => {
|
||||
setPageNumber(selectedPageNumber);
|
||||
};
|
||||
|
||||
const addToDownloadQueue = event => {
|
||||
event.preventDefault();
|
||||
sendDownloads(session.ta_token.token, event.target.vid_url.value);
|
||||
handleSetFormHidden(true);
|
||||
sendDownloads(session.ta_token.token, event.target.vid_url.value).then(() => {
|
||||
handleSetErrorMessage(null);
|
||||
handleSetFormHidden(true);
|
||||
})
|
||||
.catch(error => handleSetErrorMessage(error.message));
|
||||
}
|
||||
const handleMoveVideoQueuedIgnored = (session: string, youtube_id: string, status: string) => {
|
||||
sendMoveVideoQueuedIgnored(session, youtube_id, status).then(() => {
|
||||
handleSetErrorMessage(null);
|
||||
})
|
||||
.catch(error => handleSetErrorMessage(error.message));
|
||||
}
|
||||
const handleDeleteVideoQueuedIgnored = (session: string, youtube_id: string) => {
|
||||
sendDeleteVideoQueuedIgnored(session, youtube_id).then(() => {
|
||||
handleSetErrorMessage(null);
|
||||
})
|
||||
.catch(error => handleSetErrorMessage(error.message));
|
||||
}
|
||||
|
||||
const handleDeleteAllQueuedIgnored = (session: string, filter: string) => {
|
||||
sendDeleteAllQueuedIgnored(session, filter).then(() => {
|
||||
handleSetErrorMessage(null);
|
||||
})
|
||||
.catch(error => handleSetErrorMessage(error.message));
|
||||
}
|
||||
|
||||
return (
|
||||
@ -92,8 +133,66 @@ const Download: NextPage = () => {
|
||||
<div className="title-bar">
|
||||
<h1>Downloads</h1>
|
||||
</div>
|
||||
<div id="notifications"></div>
|
||||
<div id="downloadControl"></div>
|
||||
<div id="notifications">
|
||||
{(error || !downloads?.data) && !isLoading &&
|
||||
<div className="error notification">
|
||||
<h3>API Connection Error</h3>
|
||||
<p></p>
|
||||
</div>
|
||||
}
|
||||
{errorMessage &&
|
||||
<div className="error notification">
|
||||
<h3>Failed to add downloads to the queue.</h3>
|
||||
<p>{errorMessage}</p>
|
||||
</div>
|
||||
}
|
||||
{
|
||||
// <div className="info notification">
|
||||
// <h3>Adding new videos to download queue.</h3>
|
||||
// <p>Extracting lists</p>
|
||||
// <p>Progress: 0/0</p>
|
||||
// </div>
|
||||
}
|
||||
{
|
||||
// <div className="info notification">
|
||||
// <h3>Rescanning channels and playlists.</h3>
|
||||
// <p>Looking for new videos.</p>
|
||||
// </div>
|
||||
}
|
||||
{
|
||||
// <div className="info notification">
|
||||
// <h3>Downloading: `VIDEO_TITLE`</h3>
|
||||
// <p>processing</p>
|
||||
// <p>`DOWNLOADED_PERCENTAGE`% of `VIDEO_SIVE``VIDEO_SIZE_UNIT` at `DOWNLOAD_SPEED``DOWNLOAD_SPEED_UNIT` - time left: `DOWNLOAD_TIME_LEFT`</p>
|
||||
// <p>processing</p>
|
||||
// <p>Moving</p>
|
||||
// <p>Completed</p>
|
||||
// </div>
|
||||
}
|
||||
</div>
|
||||
<div id="downloadControl">
|
||||
{/* Appears when video is downloading */}
|
||||
{/* <div className="dl-control-icons">
|
||||
<NextImage
|
||||
width={30}
|
||||
height={30}
|
||||
src={StopIcon}
|
||||
alt="stop icon"
|
||||
title="Stop Download Queue"
|
||||
id="stop-icon"
|
||||
onClick={() => console.log("stopQueue()")}
|
||||
/>
|
||||
<NextImage
|
||||
width={30}
|
||||
height={30}
|
||||
src={CloseIcon}
|
||||
alt="kill icon"
|
||||
title="Kill Download Queue"
|
||||
id="kill-icon"
|
||||
onClick={() => console.log("killQueue()")}
|
||||
/>
|
||||
</div> */}
|
||||
</div>
|
||||
<div className="info-box info-box-3">
|
||||
<div className="icon-text">
|
||||
<NextImage
|
||||
@ -102,6 +201,7 @@ const Download: NextPage = () => {
|
||||
src={RescanIcon}
|
||||
alt="rescan-icon"
|
||||
title="Rescan subscriptions"
|
||||
// className="rotate-img" // Set when rescanning
|
||||
onClick={() => console.log("rescanPending()")}
|
||||
/>
|
||||
{/* <img id="rescan-icon" onclick="rescanPending()" src="{% static 'img/icon-rescan.svg' %}" alt="rescan-icon"></img> */}
|
||||
@ -114,6 +214,7 @@ const Download: NextPage = () => {
|
||||
src={DownloadIcon}
|
||||
alt="download-icon"
|
||||
title="Start download"
|
||||
// className="bounce-img" // Set when video is downloading
|
||||
onClick={() => console.log("dlPending()")}
|
||||
/>
|
||||
{/* <img id="download-icon" onclick="dlPending()" src="{% static 'img/icon-download.svg' %}" alt="download-icon"></img> */}
|
||||
@ -142,80 +243,63 @@ const Download: NextPage = () => {
|
||||
<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 className="toggleBox">
|
||||
<input
|
||||
id="show_ignored_only"
|
||||
onChange={() => handleSetIgnoredStatus(!ignoredStatus)}
|
||||
type="checkbox"
|
||||
checked={ignoredStatus}
|
||||
/>
|
||||
<label htmlFor="" className={ignoredStatus ? "onbtn" : "ofbtn"}>
|
||||
{ignoredStatus ? "On" : "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")}
|
||||
/>
|
||||
<div className="view-icons-margin">
|
||||
<NextImage
|
||||
width={30}
|
||||
height={34}
|
||||
src={GridViewIcon}
|
||||
alt="grid view"
|
||||
title="Switch to grid view"
|
||||
onClick={() => handleSetViewstyle("grid")}
|
||||
/>
|
||||
</div>
|
||||
{/* <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")}
|
||||
/>
|
||||
<div className="view-icons-margin">
|
||||
<NextImage
|
||||
width={30}
|
||||
height={34}
|
||||
src={ListViewIcon}
|
||||
alt="list view"
|
||||
title="Switch to list view"
|
||||
onClick={() => handleSetViewstyle("list")}
|
||||
/>
|
||||
</div>
|
||||
{/* <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>
|
||||
<button onClick={() => handleDeleteAllQueuedIgnored(session.ta_token.token, "ignore")} title="Remove all ignored videos.">Remove 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>
|
||||
<button onClick={() => handleDeleteAllQueuedIgnored(session.ta_token.token, "pending")} title="Remove all videos from the queue.">Remove 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 &&
|
||||
<h3>Total videos: {downloads?.paginate?.total_hits} {!downloads && !downloads?.message && !ignoredStatus && <p>No videos queued for download. Press rescan subscriptions to check if there are any new videos.</p>}</h3>
|
||||
<div className={`dl-list ${viewStyle}`}>
|
||||
{!isLoading && !error && !downloads?.message &&
|
||||
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>
|
||||
<img src={`${TA_BASE_URL.server}${video?.vid_thumb_url}`} alt="video_thumb"></img>
|
||||
{ignoredStatus && <span>ignored</span>}
|
||||
{/* {% if show_ignored_only %} */}
|
||||
{/* <span>ignored</span> */}
|
||||
@ -237,8 +321,8 @@ const Download: NextPage = () => {
|
||||
{/* <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>
|
||||
<button className="button-padding" title="Move this video to the download queue." onClick={() => handleMoveVideoQueuedIgnored(session.ta_token.token, video?.youtube_id, "pending")}>Add to queue</button>
|
||||
<button className="button-padding" title="Remove this video from the ignored list." onClick={() => handleDeleteVideoQueuedIgnored(session.ta_token.token, video?.youtube_id)}>Remove</button>
|
||||
</div>
|
||||
}
|
||||
{/* {% if show_ignored_only %} */}
|
||||
@ -246,8 +330,9 @@ const Download: NextPage = () => {
|
||||
{/* <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>
|
||||
<button className="button-padding" title="Ignore this video." onClick={() => handleMoveVideoQueuedIgnored(session.ta_token.token, video?.youtube_id, "ignore")}>Ignore</button>
|
||||
<button className="button-padding" title="Download this video now." onClick={() => console.log("downloadNow(this)")}>Download now</button>
|
||||
<button className="button-padding" title="Remove this video from the queue." onClick={() => handleDeleteVideoQueuedIgnored(session.ta_token.token, video?.youtube_id)}>Remove</button>
|
||||
</div>
|
||||
}
|
||||
{/* {% else %} */}
|
||||
@ -257,7 +342,6 @@ const Download: NextPage = () => {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
})
|
||||
}
|
||||
{/* {% if results %} */}
|
||||
@ -290,7 +374,29 @@ const Download: NextPage = () => {
|
||||
{/* </div> */}
|
||||
{/* {% endfor %} */}
|
||||
{/* {% endif %} */}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div className="boxed-content">
|
||||
<div className="pagination">
|
||||
{pageNumber != 1 ? <a className="pagination-item" onClick={() => handleSetPageNumber(1)}>First</a> : ``}
|
||||
{downloads?.paginate?.prev_pages &&
|
||||
downloads?.paginate?.prev_pages?.map((page) => {
|
||||
return (
|
||||
<a key={`${page}`} className="pagination-item" onClick={() => handleSetPageNumber(page)}>{page}</a>
|
||||
)})
|
||||
}
|
||||
{pageNumber != 1 && <span> < </span> }
|
||||
<span> Page {pageNumber}</span>
|
||||
{downloads?.paginate?.last_page && <span> > </span> }
|
||||
{downloads?.paginate?.next_pages &&
|
||||
downloads?.paginate?.next_pages?.map((page) => {
|
||||
return (
|
||||
<a key={`${page}`} className="pagination-item" onClick={() => handleSetPageNumber(page)}>{page}</a>
|
||||
)})
|
||||
}
|
||||
{downloads?.paginate?.last_page &&
|
||||
<a className="pagination-item" onClick={() => handleSetPageNumber(downloads?.paginate?.last_page)}> Last ({downloads?.paginate?.last_page}) </a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
{/* <script type="text/javascript" src="{% static 'progress.js' %}"></script> */}
|
||||
|
@ -98,6 +98,10 @@ textarea {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.button-padding {
|
||||
margin: 0 4px 0 0;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 0;
|
||||
padding: 5px 13px;
|
||||
@ -326,6 +330,13 @@ button:hover {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.view-icons-margin {
|
||||
/* width: 30px; */
|
||||
margin: 5px 10px;
|
||||
/* cursor: pointer;
|
||||
filter: var(--img-filter); */
|
||||
}
|
||||
|
||||
.view-icons img {
|
||||
width: 30px;
|
||||
margin: 5px 10px;
|
||||
@ -516,6 +527,8 @@ button:hover {
|
||||
|
||||
.pagination-item {
|
||||
padding: 5px;
|
||||
margin-right: 4px;
|
||||
cursor: pointer;
|
||||
border: 1px solid;
|
||||
}
|
||||
|
||||
|
@ -1,14 +1,26 @@
|
||||
export interface Download {
|
||||
data: Datum[];
|
||||
config: Config;
|
||||
paginate: boolean;
|
||||
}
|
||||
|
||||
export interface DownloadResponse {
|
||||
data: Datum[];
|
||||
paginate: Paginate;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface Paginate {
|
||||
page_size: number;
|
||||
page_from: number;
|
||||
prev_pages: number[];
|
||||
current_page: number;
|
||||
max_hits: boolean;
|
||||
last_page: number;
|
||||
next_pages: number[];
|
||||
total_hits: number;
|
||||
}
|
||||
|
||||
// export interface DownloadResponse {
|
||||
// data: Datum[];
|
||||
// message: string;
|
||||
// }
|
||||
|
||||
export interface Config {
|
||||
archive: Archive;
|
||||
default_view: DefaultView;
|
||||
|
Loading…
Reference in New Issue
Block a user