diff --git a/frontends/ycdl_flask/backend/common.py b/frontends/ycdl_flask/backend/common.py
index 532e01a..39188b9 100644
--- a/frontends/ycdl_flask/backend/common.py
+++ b/frontends/ycdl_flask/backend/common.py
@@ -3,6 +3,7 @@ Do not execute this file directly.
Use ycdl_flask_dev.py or ycdl_flask_prod.py.
'''
import flask; from flask import request
+import functools
import threading
import time
@@ -65,6 +66,29 @@ def after_request(response):
response = flasktools.gzip_response(request, response)
return response
+site.route = flasktools.decorate_and_route(
+ flask_app=site,
+ decorators=[
+ flasktools.ensure_response_type,
+ functools.partial(
+ flasktools.give_theme_cookie,
+ cookie_name='ycdl_theme',
+ default_theme='slate',
+ ),
+ ],
+)
+
+def render_template(request, template_name, **kwargs):
+ theme = request.cookies.get('ycdl_theme', None)
+
+ response = flask.render_template(
+ template_name,
+ request=request,
+ theme=theme,
+ **kwargs,
+ )
+ return response
+
####################################################################################################
# These functions will be called by the launcher, flask_dev, flask_prod.
diff --git a/frontends/ycdl_flask/backend/endpoints/basic_endpoints.py b/frontends/ycdl_flask/backend/endpoints/basic_endpoints.py
index c140321..48b04cc 100644
--- a/frontends/ycdl_flask/backend/endpoints/basic_endpoints.py
+++ b/frontends/ycdl_flask/backend/endpoints/basic_endpoints.py
@@ -6,7 +6,7 @@ site = common.site
@site.route('/')
def root():
- return flask.render_template('root.html')
+ return common.render_template(request, 'root.html')
@site.route('/favicon.ico')
@site.route('/favicon.png')
diff --git a/frontends/ycdl_flask/backend/endpoints/channel_endpoints.py b/frontends/ycdl_flask/backend/endpoints/channel_endpoints.py
index 8ed422b..a62be85 100644
--- a/frontends/ycdl_flask/backend/endpoints/channel_endpoints.py
+++ b/frontends/ycdl_flask/backend/endpoints/channel_endpoints.py
@@ -31,7 +31,7 @@ def get_all_channel_names():
@site.route('/channels')
def get_channels():
channels = common.ycdldb.get_channels()
- return flask.render_template('channels.html', channels=channels)
+ return common.render_template(request, 'channels.html', channels=channels)
def _render_videos_listing(videos, channel, state, orderby):
search_terms = request.args.get('q', '').lower().strip().replace('+', ' ').split()
@@ -55,7 +55,8 @@ def _render_videos_listing(videos, channel, state, orderby):
all_states = common.ycdldb.get_all_states()
- return flask.render_template(
+ return common.render_template(
+ request,
'channel.html',
all_states=all_states,
channel=channel,
diff --git a/frontends/ycdl_flask/static/css/theme_onyx.css b/frontends/ycdl_flask/static/css/theme_onyx.css
new file mode 100644
index 0000000..8e11a08
--- /dev/null
+++ b/frontends/ycdl_flask/static/css/theme_onyx.css
@@ -0,0 +1,71 @@
+:root
+{
+ --color_primary: #000;
+ --color_secondary: #3b4d5d;
+
+ --color_text_normal: #ccc;
+ --color_text_link: #1edeff;
+ --color_text_bubble: black;
+
+ --color_textfields: var(--color_primary);
+ --color_text_placeholder: gray;
+
+ --color_selection: rgba(0, 0, 255, 0.5);
+ --color_transparency: rgba(0, 0, 0, 0.0);
+ --color_dropshadow: rgba(0, 0, 0, 0.25);
+ --color_shadow: rgba(0, 0, 0, 0.5);
+ --color_highlight: rgba(255, 255, 255, 0.5);
+}
+
+button,
+button *
+{
+ color: black;
+}
+
+.tab_buttons button
+{
+ color: var(--color_text_normal);
+}
+
+input,
+textarea,
+.nice_link,
+.panel
+{
+ border: 1px solid var(--color_text_normal);
+}
+
+.channel_card
+{
+ padding: 10px;
+ border-radius: 4px;
+ border: 1px solid black;
+}
+
+.video_card:hover
+{
+ box-shadow: 2px 2px 5px 0px rgba(0,0,0,0.25);
+}
+.channel_card_pending,
+.video_card_pending
+{
+ border: 2px solid #ffffaa;
+ background-color: rgba(255, 255, 170, 0.25);
+}
+.video_card_ignored
+{
+ border: 2px solid #ffc886;
+ background-color: rgba(255, 200, 134, 0.25);
+}
+.video_card_selected
+{
+ border: 2px solid #13f4ff !important;
+ background-color: rgba(19, 244, 255, 0.25) !important;
+}
+.channel_card_no_pending,
+.video_card_downloaded
+{
+ border: 2px solid #aaffaa;
+ background-color: rgba(170, 255, 170, 0.25);
+}
diff --git a/frontends/ycdl_flask/static/css/theme_pearl.css b/frontends/ycdl_flask/static/css/theme_pearl.css
new file mode 100644
index 0000000..fa947a0
--- /dev/null
+++ b/frontends/ycdl_flask/static/css/theme_pearl.css
@@ -0,0 +1,62 @@
+:root
+{
+ --color_primary: #f6ffff;
+ --color_secondary: #aad7ff;
+
+ --color_text_normal: black;
+ --color_text_link: #00f;
+ --color_text_bubble: black;
+
+ --color_textfields: white;
+ --color_text_placeholder: gray;
+
+ --color_transparency: rgba(0, 0, 0, 0.1);
+ --color_dropshadow: rgba(0, 0, 0, 0.25);
+ --color_shadow: rgba(0, 0, 0, 0.5);
+ --color_highlight: rgba(255, 255, 255, 0.5);
+
+ --color_tag_card_bg: #fff;
+ --color_tag_card_fg: black;
+}
+
+.channel_card
+{
+ padding: 10px;
+ border-radius: 4px;
+ border: 1px solid black;
+}
+
+.channel_card_pending
+{
+ background-color: #ffffaa;
+}
+
+.channel_card_no_pending
+{
+ background-color: #aaffaa;
+}
+
+.video_card
+{
+ border: 1px solid #000;
+}
+.video_card:hover
+{
+ box-shadow: 2px 2px 5px 0px rgba(0,0,0,0.25);
+}
+.video_card_pending
+{
+ background-color: #ffffaa;
+}
+.video_card_ignored
+{
+ background-color: #ffc886;
+}
+.video_card_selected
+{
+ background-color: #13f4ff !important;
+}
+.video_card_downloaded
+{
+ background-color: #aaffaa;
+}
diff --git a/frontends/ycdl_flask/static/css/theme_slate.css b/frontends/ycdl_flask/static/css/theme_slate.css
new file mode 100644
index 0000000..0ff9e80
--- /dev/null
+++ b/frontends/ycdl_flask/static/css/theme_slate.css
@@ -0,0 +1,65 @@
+:root
+{
+ --color_primary: #222;
+ --color_secondary: #3b4d5d;
+
+ --color_text_normal: #efefef;
+ --color_text_link: #1edeff;
+ --color_text_bubble: black;
+
+ --color_textfields: var(--color_secondary);
+ --color_text_placeholder: gray;
+
+ --color_transparency: rgba(255, 255, 255, 0.05);
+ --color_dropshadow: rgba(0, 0, 0, 0.25);
+ --color_shadow: rgba(0, 0, 0, 0.5);
+ --color_highlight: rgba(255, 255, 255, 0.5);
+
+ --color_tag_card_bg: #e6e6e6;
+ --color_tag_card_fg: black;
+}
+
+button,
+button *
+{
+ color: black;
+}
+
+.tab_buttons button
+{
+ color: var(--color_text_normal);
+}
+
+.channel_card
+{
+ padding: 10px;
+ border-radius: 4px;
+ border: 1px solid black;
+}
+
+.video_card:hover
+{
+ box-shadow: 2px 2px 5px 0px rgba(0,0,0,0.25);
+}
+.channel_card_pending,
+.video_card_pending
+{
+ border: 2px solid #ffffaa;
+ background-color: rgba(255, 255, 170, 0.25);
+}
+.video_card_ignored
+{
+ border: 2px solid #ffc886;
+ background-color: rgba(255, 200, 134, 0.25);
+}
+.video_card_selected
+{
+ border: 2px solid #13f4ff !important;
+ background-color: rgba(19, 244, 255, 0.25) !important;
+}
+.channel_card_no_pending,
+.video_card_downloaded
+{
+ border: 2px solid #aaffaa;
+ background-color: rgba(170, 255, 170, 0.25);
+}
diff --git a/frontends/ycdl_flask/static/css/ycdl.css b/frontends/ycdl_flask/static/css/ycdl.css
index 87e3e26..09bfa4c 100644
--- a/frontends/ycdl_flask/static/css/ycdl.css
+++ b/frontends/ycdl_flask/static/css/ycdl.css
@@ -2,22 +2,9 @@
This file contains styles that apply to all pages within YCDL, but don't
belong in common.css because they are specifically for this project.
*/
-
-:root
+html
{
- --color_primary: white;
-
- --color_text_normal: black;
- --color_text_link: blue;
- --color_text_bubble: var(--color_text_normal);
-
- --color_textfields: white;
- --color_text_placeholder: gray;
-
- --color_transparency: rgba(0, 0, 0, 0.1);
- --color_dropshadow: rgba(0, 0, 0, 0.25);
- --color_shadow: rgba(0, 0, 0, 0.5);
- --color_highlight: rgba(255, 255, 255, 0.5);
+ font-family: sans-serif;
}
.navigation_link:hover
diff --git a/frontends/ycdl_flask/templates/channel.html b/frontends/ycdl_flask/templates/channel.html
index 76b8d77..f192d4e 100644
--- a/frontends/ycdl_flask/templates/channel.html
+++ b/frontends/ycdl_flask/templates/channel.html
@@ -8,6 +8,7 @@
+ {% if theme %}{% endif %}
@@ -41,27 +42,6 @@
padding: 8px;
border-radius: 4px;
- border: 1px solid #000;
-}
-.video_card:hover
-{
- box-shadow: 2px 2px 5px 0px rgba(0,0,0,0.25);
-}
-.video_card_pending
-{
- background-color: #ffffaa;
-}
-.video_card_ignored
-{
- background-color: #ffc886;
-}
-.video_card_selected
-{
- background-color: #13f4ff !important;
-}
-.video_card_downloaded
-{
- background-color: #aaffaa;
}
.video_thumbnail
@@ -409,10 +389,6 @@ function deselect_all()
video_card_first_selected = null;
for (const video_card of Array.from(video_card_selections))
{
- if (video_card.classList.contains("hidden"))
- {
- continue;
- }
video_card.classList.remove("video_card_selected");
}
}
diff --git a/frontends/ycdl_flask/templates/channels.html b/frontends/ycdl_flask/templates/channels.html
index 311ff8c..89d883e 100644
--- a/frontends/ycdl_flask/templates/channels.html
+++ b/frontends/ycdl_flask/templates/channels.html
@@ -8,6 +8,7 @@
+ {% if theme %}{% endif %}
@@ -31,17 +32,6 @@
{
padding: 10px;
border-radius: 4px;
- border: 1px solid black;
-}
-
-.channel_card_pending
-{
- background-color: #ffffaa;
-}
-
-.channel_card_no_pending
-{
- background-color: #aaffaa;
}
diff --git a/frontends/ycdl_flask/templates/root.html b/frontends/ycdl_flask/templates/root.html
index ae1d36f..80eb9f9 100644
--- a/frontends/ycdl_flask/templates/root.html
+++ b/frontends/ycdl_flask/templates/root.html
@@ -7,9 +7,10 @@
+ {% if theme %}{% endif %}