Added Theming

This commit is contained in:
Ozzie Isaacs
2026-08-08 18:39:35 +02:00
parent 5df46392d2
commit eefca6150f
7 changed files with 101 additions and 27 deletions
+7 -1
View File
@@ -44,7 +44,7 @@ from sqlalchemy.orm.attributes import flag_modified
from sqlalchemy.exc import IntegrityError, OperationalError, InvalidRequestError, ArgumentError
from sqlalchemy.sql.expression import func, or_, text
from . import constants, logger, helper, services, cli_param
from . import constants, logger, helper, services, cli_param, themes
from . import db, calibre_db, ub, web_server, config, updater_thread, gdriveutils, \
kobo_sync_status, schedule
from .helper import check_valid_domain, send_test_mail, reset_password, generate_password_hash, check_email, \
@@ -121,6 +121,8 @@ def before_request():
g.allow_anonymous = config.config_anonbrowse
g.allow_upload = config.config_uploading
g.current_theme = config.config_theme
g.themes = themes.get_available_themes()
g.theme = themes.get_theme(config.config_theme)
g.config_authors_max = config.config_authors_max
if ('/static/' not in request.path and not config.db_configured and
request.endpoint not in ('admin.ajax_db_config',
@@ -589,6 +591,10 @@ def update_view_configuration():
return view_configuration()
_config_int(to_save, "config_restricted_column")
if not themes.is_valid_theme(to_save.get("config_theme", "0")):
flash(_("Invalid Theme"), category="error")
log.debug("Invalid Theme")
return view_configuration()
_config_int(to_save, "config_theme")
_config_int(to_save, "config_random_books")
_config_int(to_save, "config_books_per_page")
+3 -2
View File
@@ -39,8 +39,9 @@
<div class="form-group">
<label for="config_theme">{{_('Theme')}}</label>
<select name="config_theme" id="config_theme" class="form-control">
<option value="0" {% if conf.config_theme == 0 %}selected{% endif %}>{{ _("Standard Theme") }}</option>
<option value="1" {% if conf.config_theme == 1 %}selected{% endif %}>{{ _("caliBlur! Dark Theme") }}</option>
{% for theme in g.themes %}
<option value="{{ theme['id'] }}" {% if conf.config_theme == theme['id'] %}selected{% endif %}>{{ _(theme['label']) }}</option>
{% endfor %}
</select>
</div>
<div class="form-group">
+3 -3
View File
@@ -15,9 +15,9 @@
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
<link href="{{ url_for('static', filename='css/libs/bootstrap.min.css') }}" rel="stylesheet" media="screen">
<link href="{{ url_for('static', filename='css/style.css') }}" rel="stylesheet" media="screen">
{% if g.current_theme == 1 %}
<link href="{{ url_for('static', filename='css/caliBlur.css') }}" rel="stylesheet" media="screen">
{% endif %}
{% for theme_css in g.theme['css_files'] %}
<link href="{{ url_for('static', filename=theme_css) }}" rel="stylesheet" media="screen">
{% endfor %}
</head>
<body>
<div class="container-fluid" style="overflow-y: auto">
+12 -16
View File
@@ -18,12 +18,11 @@
{% block header %}{% endblock %}
<link href="{{ url_for('static', filename='css/style.css') }}" rel="stylesheet" media="screen">
<link href="{{ url_for('static', filename='css/upload.css') }}" rel="stylesheet" media="screen">
{% if g.current_theme == 1 %}
<link href="{{ url_for('static', filename='css/caliBlur.css') }}" rel="stylesheet" media="screen">
<link href="{{ url_for('static', filename='css/caliBlur_override.css') }}" rel="stylesheet" media="screen">
{% endif %}
{% for theme_css in g.theme['css_files'] %}
<link href="{{ url_for('static', filename=theme_css) }}" rel="stylesheet" media="screen">
{% endfor %}
</head>
<body class="{{ page }} {{ bodyClass }}{% if g.current_theme == 1 %} blur{% endif %}" data-text="{{_('Home')}}" data-textback="{{_('Back')}}">
<body class="{{ page }} {{ bodyClass }}{% if g.theme['body_class'] %} {{ g.theme['body_class'] }}{% endif %}" data-text="{{_('Home')}}" data-textback="{{_('Back')}}">
<!-- Static navbar -->
<div class="navbar navbar-default navbar-static-top" role="navigation">
<div class="container-fluid">
@@ -36,7 +35,7 @@
</button>
<a class="navbar-brand" href="{{url_for('web.index')}}">{{instance}}</a>
</div>
{% if g.current_theme == 1 %}
{% if g.theme['show_home_shortcuts'] %}
<div class="home-btn"><a class="home-btn-tooltip" href="{{url_for("web.index", page=1)}}" data-toggle="tooltip" title="" data-placement="bottom" data-original-title="Home"></a></div>
<div class="plexBack"><a href="{{url_for('web.index')}}"></a></div>
{% endif %}
@@ -60,7 +59,7 @@
{% endif %}
<ul class="nav navbar-nav navbar-right" id="main-nav">
{% if current_user.is_authenticated or g.allow_anonymous %}
{% if g.current_theme == 1 %}
{% if g.theme['profile_dropdown'] %}
<li class="dropdown"><a href="#" class="dropdown-toggle profileDrop" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><span class="glyphicon glyphicon-user"></span></a>
<ul class="dropdown-menu profileDropli">
<li><a id="top_user" data-text="{{_('Account')}}" href="{{url_for('web.profile')}}" ><span class="glyphicon glyphicon-user"></span> <span class="hidden-sm">{{current_user.name}}</span></a></li>
@@ -92,14 +91,14 @@
{% if current_user.role_admin() %}
<li><a id="top_admin" data-text="{{_('Settings')}}" href="{{url_for('admin.admin')}}"><span class="glyphicon glyphicon-dashboard"></span> <span class="hidden-sm">{{_('Admin')}}</span></a></li>
{% endif %}
{% if g.current_theme == 0 %}
{% if not g.theme['profile_dropdown'] %}
<li><a id="top_user" data-text="{{_('Account')}}" href="{% if not current_user.is_anonymous %}{{url_for('web.profile')}}{% else %}{{url_for('web.login', next=(request.path + "?" + request.query_string.decode("utf-8")).rstrip("?"))}}{% endif %}"><span class="glyphicon glyphicon-user"></span> <span class="hidden-sm">{{current_user.name}}</span></a></li>
{% if not current_user.is_anonymous %}
<li><a id="logout" href="{{url_for('web.logout', next=(request.path + "?" + request.query_string.decode("utf-8")).rstrip("?"))}}"><span class="glyphicon glyphicon-log-out"></span> <span class="hidden-sm">{{_('Logout')}}</span></a></li>
{% endif %}
{% endif %}
{% endif %}
{% if g.allow_registration and not current_user.is_authenticated and g.current_theme == 0 %}
{% if g.allow_registration and not current_user.is_authenticated and not g.theme['profile_dropdown'] %}
<li><a id="login" href="{{url_for('web.login')}}"><span class="glyphicon glyphicon-log-in"></span> {{_('Login')}}</a></li>
<li><a id="register" href="{{url_for('web.register')}}"><span class="glyphicon glyphicon-user"></span> {{_('Register')}}</a></li>
{% endif %}
@@ -130,7 +129,7 @@
{%endif%}
{% endfor %}
{% block flash %}{% endblock %}
{% if g.current_theme == 1 %}
{% if g.theme['show_upload_loader'] %}
<div id="loader" hidden="true">
<center>
<h3>{{_('Uploading...')}}</h3>
@@ -217,12 +216,9 @@
<script src="{{ url_for('static', filename='js/libs/jquery.form.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/uploadprogress.js') }}"> </script>
<script src="{{ url_for('static', filename='js/main.js') }}"></script>
{% if g.current_theme == 1 %}
<script src="{{ url_for('static', filename='js/libs/jquery.visible.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/libs/compromise.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/libs/readmore.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/caliBlur.js') }}"></script>
{% endif %}
{% for theme_js in g.theme['js_files'] %}
<script src="{{ url_for('static', filename=theme_js) }}"></script>
{% endfor %}
{% block js %}{% endblock %}
</body>
</html>
+1 -1
View File
@@ -22,7 +22,7 @@
{% if config.config_login_type == 0 and mail%}
<button type="submit" name="forgot" value="forgot" class="btn btn-default">{{_('Forgot Password?')}}</button>
{% endif %}
{% if g.current_theme == 1 %}
{% if g.theme['login_extra_buttons'] %}
{% if g.allow_registration %}
<a class="btn btn-default" id="register" href="{{url_for('web.register')}}"><span class="glyphicon glyphicon-user"></span> {{_('Register')}}</a>
{% endif %}
+3 -4
View File
@@ -15,10 +15,9 @@
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
<link href="{{ url_for('static', filename='css/libs/bootstrap.min.css') }}" rel="stylesheet" media="screen">
<link href="{{ url_for('static', filename='css/style.css') }}" rel="stylesheet" media="screen">
{% if g.current_theme == 1 %}
<link href="{{ url_for('static', filename='css/caliBlur.css') }}" rel="stylesheet" media="screen">
<link href="{{ url_for('static', filename='css/caliBlur_override.css') }}" rel="stylesheet" media="screen">
{% endif %}
{% for theme_css in g.theme['css_files'] %}
<link href="{{ url_for('static', filename=theme_css) }}" rel="stylesheet" media="screen">
{% endfor %}
{% block header %}{% endblock %}
</head>
<body class="{{ page }} shelf-down">
+72
View File
@@ -0,0 +1,72 @@
# -*- coding: utf-8 -*-
# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
# Copyright (C) 2026
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
THEMES = (
{
"id": 0,
"label": "Standard Theme",
"css_files": (),
"js_files": (),
"body_class": "",
"show_home_shortcuts": False,
"profile_dropdown": False,
"login_extra_buttons": False,
"show_upload_loader": False
},
{
"id": 1,
"label": "caliBlur! Dark Theme",
"css_files": (
"css/caliBlur.css",
"css/caliBlur_override.css"
),
"js_files": (
"js/libs/jquery.visible.min.js",
"js/libs/compromise.min.js",
"js/libs/readmore.min.js",
"js/caliBlur.js"
),
"body_class": "blur",
"show_home_shortcuts": True,
"profile_dropdown": True,
"login_extra_buttons": True,
"show_upload_loader": True
}
)
THEMES_BY_ID = {theme["id"]: theme for theme in THEMES}
def get_available_themes():
return THEMES
def get_theme(theme_id):
try:
theme_id = int(theme_id)
except (TypeError, ValueError):
theme_id = 0
return THEMES_BY_ID.get(theme_id, THEMES_BY_ID[0])
def is_valid_theme(theme_id):
try:
theme_id = int(theme_id)
except (TypeError, ValueError):
return False
return theme_id in THEMES_BY_ID