mirror of
https://github.com/hrfee/jellyfin-accounts.git
synced 2024-12-22 09:00:14 +00:00
Added theme toggle to Admin page
The admin can switch between the two default themes without a page reload, with a nice animation (on small screens). Preference is stored as a cookie, so the default theme setting will still apply to others.
This commit is contained in:
parent
781306f1ef
commit
a3d3d97b3b
@ -9,13 +9,13 @@ server = http://jellyfin.local:8096
|
|||||||
public_server = https://jellyf.in:443
|
public_server = https://jellyf.in:443
|
||||||
; this and below settings will show on the jellyfin dashboard when the program connects. you may as well leave them alone.
|
; this and below settings will show on the jellyfin dashboard when the program connects. you may as well leave them alone.
|
||||||
client = jf-accounts
|
client = jf-accounts
|
||||||
version = 0.3.0
|
version = 0.3.2
|
||||||
device = jf-accounts
|
device = jf-accounts
|
||||||
device_id = jf-accounts-0.3.0
|
device_id = jf-accounts-0.3.2
|
||||||
|
|
||||||
[ui]
|
[ui]
|
||||||
; settings related to the ui and program functionality.
|
; settings related to the ui and program functionality.
|
||||||
; choose the look of jellyfin-accounts.
|
; default appearance for all users.
|
||||||
theme = Jellyfin (Dark)
|
theme = Jellyfin (Dark)
|
||||||
; set 0.0.0.0 to run on localhost
|
; set 0.0.0.0 to run on localhost
|
||||||
host = 0.0.0.0
|
host = 0.0.0.0
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
__version__ = "0.3.2"
|
__version__ = "0.3.3"
|
||||||
|
|
||||||
import secrets
|
import secrets
|
||||||
import configparser
|
import configparser
|
||||||
@ -218,7 +218,7 @@ elif "Custom" in current_theme and "custom_css" in config["files"]:
|
|||||||
try:
|
try:
|
||||||
css_path = Path(config["files"]["custom_css"])
|
css_path = Path(config["files"]["custom_css"])
|
||||||
shutil.copy(css_path, (local_dir / "static" / css_path.name))
|
shutil.copy(css_path, (local_dir / "static" / css_path.name))
|
||||||
log.debug('Loaded custom CSS "{css_path.name}"')
|
log.debug(f'Loaded custom CSS "{css_path.name}"')
|
||||||
css_file = css_path.name
|
css_file = css_path.name
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
log.error(
|
log.error(
|
||||||
|
@ -71,7 +71,7 @@
|
|||||||
"description": "Settings related to the UI and program functionality."
|
"description": "Settings related to the UI and program functionality."
|
||||||
},
|
},
|
||||||
"theme": {
|
"theme": {
|
||||||
"name": "Look",
|
"name": "Default Look",
|
||||||
"required": false,
|
"required": false,
|
||||||
"requires_restart": true,
|
"requires_restart": true,
|
||||||
"type": "select",
|
"type": "select",
|
||||||
@ -81,7 +81,7 @@
|
|||||||
"Custom CSS"
|
"Custom CSS"
|
||||||
],
|
],
|
||||||
"value": "Jellyfin (Dark)",
|
"value": "Jellyfin (Dark)",
|
||||||
"description": "Choose the look of jellyfin-accounts."
|
"description": "Default appearance for all users."
|
||||||
},
|
},
|
||||||
"host": {
|
"host": {
|
||||||
"name": "Address",
|
"name": "Address",
|
||||||
|
@ -14,7 +14,39 @@
|
|||||||
<meta name="theme-color" content="#ffffff">
|
<meta name="theme-color" content="#ffffff">
|
||||||
<!-- Bootstrap CSS -->
|
<!-- Bootstrap CSS -->
|
||||||
|
|
||||||
<link rel="stylesheet" type="text/css" href="{{ css_file }}">
|
<script>
|
||||||
|
function getCookie(cname) {
|
||||||
|
var name = cname + "=";
|
||||||
|
var decodedCookie = decodeURIComponent(document.cookie);
|
||||||
|
var ca = decodedCookie.split(';');
|
||||||
|
for(var i = 0; i < ca.length; i++) {
|
||||||
|
var c = ca[i];
|
||||||
|
while (c.charAt(0) == ' ') {
|
||||||
|
c = c.substring(1);
|
||||||
|
}
|
||||||
|
if (c.indexOf(name) == 0) {
|
||||||
|
return c.substring(name.length, c.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
};
|
||||||
|
{% if bs5 %}
|
||||||
|
var bsVersion = 5;
|
||||||
|
{% else %}
|
||||||
|
var bsVersion = 4;
|
||||||
|
{% endif %}
|
||||||
|
var css = document.createElement('link');
|
||||||
|
css.setAttribute('rel', 'stylesheet');
|
||||||
|
css.setAttribute('type', 'text/css');
|
||||||
|
var cssCookie = getCookie("css");
|
||||||
|
if (cssCookie.includes('bs' + bsVersion)) {
|
||||||
|
css.setAttribute('href', cssCookie);
|
||||||
|
} else {
|
||||||
|
css.setAttribute('href', '{{ css_file }}');
|
||||||
|
};
|
||||||
|
document.head.appendChild(css);
|
||||||
|
// document.querySelectorAll('link[rel="stylesheet"][type="text/css"]')[0].href = cssCookie;
|
||||||
|
</script>
|
||||||
{% if not bs5 %}
|
{% if not bs5 %}
|
||||||
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
|
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -53,10 +85,27 @@
|
|||||||
margin-top: 5%;
|
margin-top: 5%;
|
||||||
color: grey;
|
color: grey;
|
||||||
}
|
}
|
||||||
|
.circle {
|
||||||
|
/*margin-left: 1rem;
|
||||||
|
width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
z-index: 5000;*/
|
||||||
|
-webkit-transition: all 300ms cubic-bezier(0.550, 0.055, 0.675, 0.190);
|
||||||
|
-moz-transition: all 300ms cubic-bezier(0.550, 0.055, 0.675, 0.190);
|
||||||
|
-o-transition: all 300ms cubic-bezier(0.550, 0.055, 0.675, 0.190);
|
||||||
|
transition: all 300ms cubic-bezier(0.550, 0.055, 0.675, 0.190); /* easeInCubic */
|
||||||
|
}
|
||||||
|
.smooth-transition {
|
||||||
|
-webkit-transition: all 300ms cubic-bezier(0.550, 0.055, 0.675, 0.190);
|
||||||
|
-moz-transition: all 300ms cubic-bezier(0.550, 0.055, 0.675, 0.190);
|
||||||
|
-o-transition: all 300ms cubic-bezier(0.550, 0.055, 0.675, 0.190);
|
||||||
|
transition: all 300ms cubic-bezier(0.550, 0.055, 0.675, 0.190); /* easeInCubic */
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<title>Admin</title>
|
<title>Admin</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body class="smooth-transition">
|
||||||
<div class="modal fade" id="login" role="dialog" aria-labelledby="login" aria-hidden="true" data-backdrop="static">
|
<div class="modal fade" id="login" role="dialog" aria-labelledby="login" aria-hidden="true" data-backdrop="static">
|
||||||
<div class="modal-dialog modal-dialog-centered" role="document">
|
<div class="modal-dialog modal-dialog-centered" role="document">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
@ -171,9 +220,11 @@
|
|||||||
<h1>
|
<h1>
|
||||||
Accounts admin
|
Accounts admin
|
||||||
</h1>
|
</h1>
|
||||||
<button type="button" class="btn btn-secondary" id="openSettings">
|
<div class="btn-group" role="group" id="headerButtons">
|
||||||
|
<button type="button" class="btn btn-primary" id="openSettings">
|
||||||
Settings <i class="fa fa-cog"></i>
|
Settings <i class="fa fa-cog"></i>
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
<div class="card mb-3 linkGroup">
|
<div class="card mb-3 linkGroup">
|
||||||
<div class="card-header">Current Invites</div>
|
<div class="card-header">Current Invites</div>
|
||||||
<ul class="list-group list-group-flush" id="invites">
|
<ul class="list-group list-group-flush" id="invites">
|
||||||
|
@ -1,5 +1,96 @@
|
|||||||
var bsVersion = {{ bsVersion }};
|
function getCookie(cname) {
|
||||||
|
var name = cname + "=";
|
||||||
|
var decodedCookie = decodeURIComponent(document.cookie);
|
||||||
|
var ca = decodedCookie.split(';');
|
||||||
|
for(var i = 0; i <ca.length; i++) {
|
||||||
|
var c = ca[i];
|
||||||
|
while (c.charAt(0) == ' ') {
|
||||||
|
c = c.substring(1);
|
||||||
|
}
|
||||||
|
if (c.indexOf(name) == 0) {
|
||||||
|
return c.substring(name.length, c.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
function whichTransitionEvent(){
|
||||||
|
var t;
|
||||||
|
var el = document.createElement('fakeelement');
|
||||||
|
var transitions = {
|
||||||
|
'transition':'transitionend',
|
||||||
|
'OTransition':'oTransitionEnd',
|
||||||
|
'MozTransition':'transitionend',
|
||||||
|
'WebkitTransition':'webkitTransitionEnd'
|
||||||
|
};
|
||||||
|
|
||||||
|
for(t in transitions){
|
||||||
|
if( el.style[t] !== undefined ){
|
||||||
|
return transitions[t];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
function toggleCSS() {
|
||||||
|
var cssEl = document.querySelectorAll('link[rel="stylesheet"][type="text/css"]')[0];
|
||||||
|
if (cssEl.href.includes("bs" + bsVersion + "-jf")) {
|
||||||
|
var href = "bs" + bsVersion + ".css";
|
||||||
|
} else {
|
||||||
|
var href = "bs" + bsVersion + "-jf.css";
|
||||||
|
};
|
||||||
|
cssEl.href = href;
|
||||||
|
document.cookie = "css=" + href;
|
||||||
|
};
|
||||||
|
var buttonWidth = 0;
|
||||||
|
var inTransition = false;
|
||||||
|
function toggleCSSAnim(el) {
|
||||||
|
var switchToColor = window.getComputedStyle(document.body, null).backgroundColor;
|
||||||
|
if (window.innerWidth < 1500) {
|
||||||
|
var radius = Math.sqrt(Math.pow(window.innerWidth, 2) + Math.pow(window.innerHeight, 2));
|
||||||
|
var currentRadius = el.getBoundingClientRect().width / 2;
|
||||||
|
var scale = radius / currentRadius;
|
||||||
|
buttonWidth = window.getComputedStyle(el, null).width;
|
||||||
|
document.body.classList.remove('smooth-transition');
|
||||||
|
el.style.transform = 'scale(' + scale + ')';
|
||||||
|
el.style.color = switchToColor;
|
||||||
|
var transitionEnd = whichTransitionEvent();
|
||||||
|
el.addEventListener(transitionEnd, function() {
|
||||||
|
if (this.style.transform.length != 0) {
|
||||||
|
toggleCSS();
|
||||||
|
this.style.removeProperty('transform');
|
||||||
|
document.body.classList.add('smooth-transition');
|
||||||
|
};
|
||||||
|
}, false);
|
||||||
|
} else {
|
||||||
|
toggleCSS();
|
||||||
|
el.style.color = switchToColor;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
var cssFile = "{{ css_file }}";
|
||||||
|
var buttonColor = 'custom';
|
||||||
|
if (cssFile.includes('jf')) {
|
||||||
|
buttonColor = 'rgb(255,255,255)';
|
||||||
|
} else if (cssFile.length == 7) {
|
||||||
|
buttonColor = 'rgb(16,16,16)';
|
||||||
|
}
|
||||||
|
if (buttonColor != 'custom') {
|
||||||
|
var fakeButton = document.createElement('i');
|
||||||
|
fakeButton.classList.add('fa', 'fa-circle', 'circle');
|
||||||
|
// fakeButton.style.color = buttonColor;
|
||||||
|
// fakeButton.style.marginLeft = '2rem;'
|
||||||
|
fakeButton.style = 'color: ' + buttonColor + '; margin-left: 0.4rem;';
|
||||||
|
fakeButton.id = 'fakeButton';
|
||||||
|
var switchButton = document.createElement('button');
|
||||||
|
switchButton.classList.add('btn', 'btn-secondary');
|
||||||
|
switchButton.textContent = 'Theme';
|
||||||
|
switchButton.onclick = function() {
|
||||||
|
var fb = document.getElementById('fakeButton')
|
||||||
|
toggleCSSAnim(fb);
|
||||||
|
};
|
||||||
|
var group = document.getElementById('headerButtons');
|
||||||
|
switchButton.appendChild(fakeButton);
|
||||||
|
group.appendChild(switchButton);
|
||||||
|
};
|
||||||
|
|
||||||
|
var bsVersion = {{ bsVersion }};
|
||||||
if (bsVersion == 5) {
|
if (bsVersion == 5) {
|
||||||
function createModal(id, find = false) {
|
function createModal(id, find = false) {
|
||||||
if (find) {
|
if (find) {
|
||||||
@ -22,13 +113,6 @@ if (bsVersion == 5) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// var loginModal = new bootstrap.Modal(document.getElementById('login'));
|
|
||||||
// var settingsModal = new bootstrap.Modal(document.getElementById('settingsMenu'));
|
|
||||||
// var userDefaultsModal = new bootstrap.Modal(document.getElementById('userDefaults'));
|
|
||||||
// var usersModal = new bootstrap.Modal(document.getElementById('users'));
|
|
||||||
// var restartModal = new bootstrap.Modal(document.getElementById('restartModal'));
|
|
||||||
} else if (bsVersion == 4) {
|
} else if (bsVersion == 4) {
|
||||||
document.getElementById('send_to_address_enabled').classList.remove('form-check-input');
|
document.getElementById('send_to_address_enabled').classList.remove('form-check-input');
|
||||||
function createModal(id, find = false) {
|
function createModal(id, find = false) {
|
||||||
|
@ -6,6 +6,12 @@ from jellyfin_accounts import web_log as log
|
|||||||
from jellyfin_accounts.web_api import config, checkInvite, validator
|
from jellyfin_accounts.web_api import config, checkInvite, validator
|
||||||
|
|
||||||
|
|
||||||
|
if config.getboolean("ui", "bs5"):
|
||||||
|
bsVersion = 5
|
||||||
|
else:
|
||||||
|
bsVersion = 4
|
||||||
|
|
||||||
|
|
||||||
@app.errorhandler(404)
|
@app.errorhandler(404)
|
||||||
def page_not_found(e):
|
def page_not_found(e):
|
||||||
return (
|
return (
|
||||||
@ -35,12 +41,10 @@ def admin():
|
|||||||
def static_proxy(path):
|
def static_proxy(path):
|
||||||
if "html" not in path:
|
if "html" not in path:
|
||||||
if "admin.js" in path:
|
if "admin.js" in path:
|
||||||
if config.getboolean("ui", "bs5"):
|
|
||||||
bsVersion = 5
|
|
||||||
else:
|
|
||||||
bsVersion = 4
|
|
||||||
return (
|
return (
|
||||||
render_template("admin.js", bsVersion=bsVersion),
|
render_template("admin.js",
|
||||||
|
bsVersion=bsVersion,
|
||||||
|
css_file=css_file),
|
||||||
200,
|
200,
|
||||||
{"Content-Type": "text/javascript"},
|
{"Content-Type": "text/javascript"},
|
||||||
)
|
)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "jellyfin-accounts"
|
name = "jellyfin-accounts"
|
||||||
version = "0.3.2"
|
version = "0.3.3"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
description = "A simple account management system for Jellyfin"
|
description = "A simple account management system for Jellyfin"
|
||||||
authors = ["Harvey Tindall <harveyltindall@gmail.com>"]
|
authors = ["Harvey Tindall <harveyltindall@gmail.com>"]
|
||||||
|
Loading…
Reference in New Issue
Block a user