diff --git a/frontends/ycdl_flask/static/js/api.js b/frontends/ycdl_flask/static/js/api.js
index 190384d..b8d0c7a 100644
--- a/frontends/ycdl_flask/static/js/api.js
+++ b/frontends/ycdl_flask/static/js/api.js
@@ -6,80 +6,100 @@ api.channels = {};
api.channels.add_channel =
function add_channel(channel_id, callback)
{
- const url = "/add_channel";
- const data = {"channel_id": channel_id};
- return common.post(url, data, callback);
+ return http.post({
+ url: "/add_channel",
+ data: {"channel_id": channel_id},
+ callback: callback,
+ });
}
api.channels.delete_channel =
function delete_channel(channel_id, callback)
{
- const url = `/channel/${channel_id}/delete`;
- const data = {};
- return common.post(url, data, callback);
+ return http.post({
+ url: `/channel/${channel_id}/delete`,
+ data: {},
+ callback: callback,
+ });
}
api.channels.refresh_channel =
function refresh_channel(channel_id, force, callback)
{
- const url = `/channel/${channel_id}/refresh`;
- const data = {"force": force};
- return common.post(url, data, callback);
+ return http.post({
+ url: `/channel/${channel_id}/refresh`,
+ data: {"force": force},
+ callback: callback,
+ });
}
api.channels.refresh_all_channels =
function refresh_all_channels(force, callback)
{
- const url = "/refresh_all_channels";
- const data = {"force": force};
- return common.post(url, data, callback);
+ return http.post({
+ url: "/refresh_all_channels",
+ data: {"force": force},
+ callback: callback,
+ });
}
api.channels.set_automark =
function set_automark(channel_id, state, callback)
{
- const url = `/channel/${channel_id}/set_automark`;
- const data = {"state": state};
- return common.post(url, data, callback);
+ return http.post({
+ url: `/channel/${channel_id}/set_automark`,
+ data: {"state": state},
+ callback: callback,
+ });
}
api.channels.set_autorefresh =
function set_autorefresh(channel_id, autorefresh, callback)
{
- const url = `/channel/${channel_id}/set_autorefresh`;
- const data = {"autorefresh": autorefresh};
- return common.post(url, data, callback);
+ return http.post({
+ url: `/channel/${channel_id}/set_autorefresh`,
+ data: {"autorefresh": autorefresh},
+ callback: callback,
+ });
}
api.channels.set_download_directory =
function set_download_directory(channel_id, download_directory, callback)
{
- const url = `/channel/${channel_id}/set_download_directory`;
- const data = {"download_directory": download_directory};
- return common.post(url, data, callback);
+ return http.post({
+ url: `/channel/${channel_id}/set_download_directory`,
+ data: {"download_directory": download_directory},
+ callback: callback,
+ });
}
api.channels.set_name =
function set_name(channel_id, name, callback)
{
- const url = `/channel/${channel_id}/set_name`;
- const data = {"name": name};
- return common.post(url, data, callback);
+ return http.post({
+ url: `/channel/${channel_id}/set_name`,
+ data: {"name": name},
+ callback: callback,
+ });
}
api.channels.set_queuefile_extension =
function set_queuefile_extension(channel_id, extension, callback)
{
- const url = `/channel/${channel_id}/set_queuefile_extension`;
- const data = {"extension": extension};
- return common.post(url, data, callback);
+ return http.post({
+ url: `/channel/${channel_id}/set_queuefile_extension`,
+ data: {"extension": extension},
+ callback: callback,
+ });
}
api.channels.show_download_directory =
function show_download_directory(channel_id, callback)
{
- const url = `/channel/${channel_id}/show_download_directory`;
- return common.post(url, null, callback);
+ return http.post({
+ url: `/channel/${channel_id}/show_download_directory`,
+ callback: callback,
+ });
}
api.channels.callback_go_to_channels =
@@ -101,15 +121,19 @@ api.videos = {};
api.videos.mark_state =
function mark_state(video_ids, state, callback)
{
- const url = "/mark_video_state";
- const data = {"video_ids": video_ids, "state": state};
- return common.post(url, data, callback);
+ return http.post({
+ url: "/mark_video_state",
+ data: {"video_ids": video_ids, "state": state},
+ callback: callback,
+ });
}
api.videos.start_download =
function start_download(video_ids, callback)
{
- const url = "/start_download";
- const data = {"video_ids": video_ids};
- return common.post(url, data, callback);
+ return http.post({
+ url: "/start_download",
+ data: {"video_ids": video_ids},
+ callback: callback,
+ });
}
diff --git a/frontends/ycdl_flask/static/js/common.js b/frontends/ycdl_flask/static/js/common.js
index 3dbaf83..20b1c90 100644
--- a/frontends/ycdl_flask/static/js/common.js
+++ b/frontends/ycdl_flask/static/js/common.js
@@ -34,6 +34,12 @@ function is_wide_mode()
return getComputedStyle(document.documentElement).getPropertyValue("--wide").trim() === "1";
}
+common.go_to_root =
+function go_to_root()
+{
+ window.location.href = "/";
+}
+
common.refresh =
function refresh()
{
@@ -51,146 +57,18 @@ function refresh_or_alert(response)
window.location.reload();
}
-////////////////////////////////////////////////////////////////////////////////////////////////////
-// HTTP ////////////////////////////////////////////////////////////////////////////////////////////
-////////////////////////////////////////////////////////////////////////////////////////////////////
-
-common.formdata =
-function formdata(data)
-{
- fd = new FormData();
- for (let [key, value] of Object.entries(data))
- {
- if (value === undefined)
- {
- continue;
- }
- if (value === null)
- {
- value = '';
- }
- fd.append(key, value);
- }
- return fd;
-}
-
-common._request =
-function _request(method, url, callback)
-{
- /*
- Perform an HTTP request and call the `callback` with the response.
-
- The response will have the following structure:
- {
- "meta": {
- "completed": true / false,
- "status": If the connection failed or request otherwise could not
- complete, `status` will be 0. If the request completed,
- `status` will be the HTTP response code.
- "json_ok": If the server responded with parseable json, `json_ok`
- will be true, and that data will be in `response.data`. If the
- server response was not parseable json, `json_ok` will be false
- and `response.data` will be undefined.
- "request_url": The URL exactly as given to this call.
- }
- "data": {JSON parsed from server response if json_ok}.
- }
-
- So, from most lenient to most strict, error catching might look like:
- if response.meta.completed
- if response.meta.json_ok
- if response.meta.status === 200
- if response.meta.status === 200 and response.meta.json_ok
- */
- const request = new XMLHttpRequest();
- const response = {
- "meta": {
- "completed": false,
- "status": 0,
- "json_ok": false,
- "request_url": url,
- },
- };
-
- request.onreadystatechange = function()
- {
- /*
- readystate values:
- 0 UNSENT / ABORTED
- 1 OPENED
- 2 HEADERS_RECEIVED
- 3 LOADING
- 4 DONE
- */
- if (request.readyState != 4)
- {return;}
-
- if (callback == null)
- {return;}
-
- response.meta.status = request.status;
-
- if (request.status != 0)
- {
- response.meta.completed = true;
- try
- {
- response.data = JSON.parse(request.responseText);
- response.meta.json_ok = true;
- }
- catch (exc)
- {
- response.meta.json_ok = false;
- }
- }
- callback(response);
- };
- const asynchronous = true;
- request.open(method, url, asynchronous);
- return request;
-}
-
-common.get =
-function get(url, callback)
-{
- request = common._request("GET", url, callback);
- request.send();
- return request;
-}
-
-common.post =
-function post(url, data, callback)
-{
- /*
- `data`:
- a FormData object which you have already filled with values, or a
- dictionary from which a FormData will be made, using common.formdata.
- */
- if (data instanceof FormData || data === null)
- {
- ;
- }
- else
- {
- data = common.formdata(data);
- }
- request = common._request("POST", url, callback);
- request.send(data);
- return request;
-}
-
////////////////////////////////////////////////////////////////////////////////////////////////////
// STRING TOOLS ////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
common.join_and_trail =
-function join_and_trail(l, s)
+function join_and_trail(list, separator)
{
- if (l.length === 0)
+ if (list.length === 0)
{
return "";
}
- return l.join(s) + s
+ return list.join(separator) + separator
}
////////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/frontends/ycdl_flask/static/js/http.js b/frontends/ycdl_flask/static/js/http.js
new file mode 100644
index 0000000..42547cf
--- /dev/null
+++ b/frontends/ycdl_flask/static/js/http.js
@@ -0,0 +1,210 @@
+const http = {};
+
+http.HEADERS = {};
+
+http.requests_in_flight = 0;
+
+http.request_queue = {};
+http.request_queue.array = [];
+
+http.request_queue.push =
+function request_queue_push(func)
+{
+ http.request_queue.array.push(func)
+ if (http.requests_in_flight == 0)
+ {
+ http.request_queue_next();
+ }
+}
+
+http.request_queue.unshift =
+function request_queue_unshift(func)
+{
+ http.request_queue.array.unshift(func)
+ if (http.requests_in_flight == 0)
+ {
+ http.request_queue_next();
+ }
+}
+http.request_queue.pushleft = http.request_queue.unshift;
+
+http.request_queue_next =
+function request_queue_next()
+{
+ if (http.requests_in_flight > 0)
+ {
+ return;
+ }
+ if (http.request_queue.array.length === 0)
+ {
+ return;
+ }
+ const func = http.request_queue.array.shift();
+ func();
+}
+
+http.formdata =
+function formdata(data)
+{
+ const fd = new FormData();
+ for (let [key, value] of Object.entries(data))
+ {
+ if (value === undefined)
+ {
+ continue;
+ }
+ if (value === null)
+ {
+ value = '';
+ }
+ fd.append(key, value);
+ }
+ return fd;
+}
+
+http._request =
+function _request(kwargs)
+{
+ /*
+ Perform an HTTP request and call the `callback` with the response.
+
+ The response will have the following structure:
+ {
+ "meta": {
+ "request": the XMLHttpRequest object,
+ "completed": true / false,
+ "status": If the connection failed or request otherwise could not
+ complete, `status` will be 0. If the request completed,
+ `status` will be the HTTP response code.
+ "json_ok": If the server responded with parseable json, `json_ok`
+ will be true, and that data will be in `response.data`. If the
+ server response was not parseable json, `json_ok` will be false
+ and `response.data` will be undefined.
+ "kwargs": The kwargs exactly as given to this call.
+ }
+ "data": {JSON parsed from server response if json_ok},
+ "retry": function you can call to retry the request.
+ }
+
+ So, from most lenient to most strict, error catching might look like:
+ if response.meta.completed
+ if response.meta.json_ok
+ if response.meta.status === 200
+ if response.meta.status === 200 and response.meta.json_ok
+ */
+ const request = new XMLHttpRequest();
+ const response = {
+ "meta": {
+ "request": request,
+ "completed": false,
+ "status": 0,
+ "json_ok": false,
+ "kwargs": kwargs,
+ },
+ "retry": function(){http._request(kwargs)},
+ };
+
+ request.onreadystatechange = function()
+ {
+ /*
+ readystate values:
+ 0 UNSENT / ABORTED
+ 1 OPENED
+ 2 HEADERS_RECEIVED
+ 3 LOADING
+ 4 DONE
+ */
+ if (request.readyState != 4)
+ {
+ return;
+ }
+
+ http.requests_in_flight -= 1;
+ setTimeout(http.request_queue_next, 0);
+
+ if (kwargs["callback"] == null)
+ {
+ return;
+ }
+
+ response.meta.status = request.status;
+
+ if (request.status != 0)
+ {
+ response.meta.completed = true;
+ try
+ {
+ response.data = JSON.parse(request.responseText);
+ response.meta.json_ok = true;
+ }
+ catch (exc)
+ {
+ response.meta.json_ok = false;
+ }
+ }
+ kwargs["callback"](response);
+ };
+
+ // Headers
+
+ const asynchronous = "asynchronous" in kwargs ? kwargs["asynchronous"] : true;
+ request.open(kwargs["method"], kwargs["url"], asynchronous);
+
+ for (const [header, value] of Object.entries(http.HEADERS))
+ {
+ request.setRequestHeader(header, value);
+ }
+
+ const more_headers = kwargs["headers"] || {};
+ for (const [header, value] of Object.entries(more_headers))
+ {
+ request.setRequestHeader(header, value);
+ }
+
+ if (kwargs["with_credentials"])
+ {
+ request.withCredentials = true;
+ }
+
+ // Send
+
+ let data = kwargs["data"];
+ if (data === undefined || data === null)
+ {
+ request.send();
+ }
+ else if (data instanceof FormData)
+ {
+ request.send(data);
+ }
+ else if (typeof(data) === "string" || data instanceof String)
+ {
+ request.send(data);
+ }
+ else
+ {
+ request.send(http.formdata(data));
+ }
+ http.requests_in_flight += 1;
+
+ return request;
+}
+
+http.get =
+function get(kwargs)
+{
+ kwargs["method"] = "GET";
+ return http._request(kwargs);
+}
+
+http.post =
+function post(kwargs)
+{
+ /*
+ `data`:
+ a FormData object which you have already filled with values, or a
+ dictionary from which a FormData will be made, using http.formdata.
+ */
+ kwargs["method"] = "POST";
+ return http._request(kwargs);
+}
diff --git a/frontends/ycdl_flask/templates/channel.html b/frontends/ycdl_flask/templates/channel.html
index 023439c..76b8d77 100644
--- a/frontends/ycdl_flask/templates/channel.html
+++ b/frontends/ycdl_flask/templates/channel.html
@@ -11,6 +11,7 @@
+