@@ -3,7 +3,7 @@ | |||||
from pathlib import Path | from pathlib import Path | ||||
from flask import Flask, flash, g, redirect, request, url_for | |||||
from flask import Flask, abort, flash, g, redirect, request, url_for | |||||
from flask_mako import MakoTemplates, render_template | from flask_mako import MakoTemplates, render_template | ||||
import calefaction | import calefaction | ||||
@@ -76,12 +76,13 @@ def logout(): | |||||
flash(Messages.LOGGED_OUT, "success") | flash(Messages.LOGGED_OUT, "success") | ||||
return redirect(url_for("index"), 303) | return redirect(url_for("index"), 303) | ||||
@app.route("/test") | |||||
@app.route("/settings/style/<style>", methods=["POST"]) | |||||
@catch_exceptions | @catch_exceptions | ||||
@route_restricted | @route_restricted | ||||
def test(): | |||||
... | |||||
return "Success! You are authenticated!" | |||||
def set_style(style): | |||||
if not auth.set_character_style(style): | |||||
abort(404) | |||||
return "", 204 | |||||
if __name__ == "__main__": | if __name__ == "__main__": | ||||
app.run(debug=True, port=8080) | app.run(debug=True, port=8080) |
@@ -196,6 +196,23 @@ class AuthManager: | |||||
g._character_props = g.db.read_character(cid) | g._character_props = g.db.read_character(cid) | ||||
return g._character_props.get(prop) | return g._character_props.get(prop) | ||||
def set_character_style(self, style): | |||||
"""Update the current user's style and return whether successful.""" | |||||
cid = self.get_character_id() | |||||
if not cid: | |||||
return False | |||||
style = style.strip().lower() | |||||
if style not in self._config.get("style.enabled"): | |||||
return False | |||||
self._debug("Setting style to %s for char id=%d", style, cid) | |||||
g.db.set_character_style(cid, style) | |||||
if hasattr(g, "_character_props"): | |||||
delattr(g, "_character_props") | |||||
return True | |||||
def is_authenticated(self): | def is_authenticated(self): | ||||
"""Return whether the user has permission to access this site. | """Return whether the user has permission to access this site. | ||||
@@ -148,6 +148,12 @@ class Database: | |||||
res = self._conn.execute(query, (cid,)).fetchall() | res = self._conn.execute(query, (cid,)).fetchall() | ||||
return {"name": res[0][0], "style": res[0][1]} if res else {} | return {"name": res[0][0], "style": res[0][1]} if res else {} | ||||
def set_character_style(self, cid, style): | |||||
"""Update a character's style setting.""" | |||||
with self._conn as conn: | |||||
conn.execute("""UPDATE character SET character_style = ? | |||||
WHERE character_id = ?""", (style, cid)) | |||||
def set_auth(self, cid, token, expires, refresh): | def set_auth(self, cid, token, expires, refresh): | ||||
"""Set the authentication info for the given character.""" | """Set the authentication info for the given character.""" | ||||
with self._conn as conn: | with self._conn as conn: | ||||
@@ -33,9 +33,14 @@ auth: | |||||
# SSO client secret: | # SSO client secret: | ||||
client_secret: XXAPGc0LM6wdOJAwSNQmliZ2QhQpoBuUutQY6Rlc | client_secret: XXAPGc0LM6wdOJAwSNQmliZ2QhQpoBuUutQY6Rlc | ||||
# Default stylesheet from static/styles/*.css; | |||||
# one of "amarr", "caldari", "gallente", "minmatar", or create your own: | |||||
style: null | |||||
style: | |||||
# Default stylesheet from static/styles/*.css: | |||||
default: null | |||||
enabled: | |||||
- amarr | |||||
- caldari | |||||
- gallente | |||||
- minmatar | |||||
welcome: |- | welcome: |- | ||||
(If you are seeing this message on the public internet, someone forgot to | (If you are seeing this message on the public internet, someone forgot to | ||||
@@ -37,19 +37,21 @@ h1 { | |||||
width: 100%; | width: 100%; | ||||
} | } | ||||
.styled-border { | |||||
border-color: #4A4A4A; | |||||
border-style: solid; | |||||
} | |||||
main, header, footer { | main, header, footer { | ||||
background-color: rgba(0, 0, 0, 0.8); | background-color: rgba(0, 0, 0, 0.8); | ||||
border-color: #4A4A4A; | |||||
} | } | ||||
main { | main { | ||||
border-width: 1px; | border-width: 1px; | ||||
border-style: solid; | |||||
} | } | ||||
header { | header { | ||||
border-bottom-width: 1px; | |||||
border-bottom-style: solid; | |||||
border-width: 0 0 1px 0; | |||||
} | } | ||||
footer { | footer { | ||||
@@ -57,8 +59,7 @@ footer { | |||||
font-size: 85%; | font-size: 85%; | ||||
text-align: center; | text-align: center; | ||||
color: #BABABA; | color: #BABABA; | ||||
border-top-width: 1px; | |||||
border-top-style: solid; | |||||
border-width: 1px 0 0; | |||||
} | } | ||||
header > div, footer > div { | header > div, footer > div { | ||||
@@ -164,18 +165,6 @@ footer ul li:not(:last-child):after { | |||||
text-decoration: none; | text-decoration: none; | ||||
} | } | ||||
#character-portrait { | |||||
height: 32px; | |||||
margin-right: 0.25em; | |||||
box-sizing: border-box; | |||||
border-width: 1px; | |||||
border-style: solid; | |||||
} | |||||
#character-summary { | |||||
font-size: 90%; | |||||
} | |||||
#flashes { | #flashes { | ||||
margin-top: 0.5em; | margin-top: 0.5em; | ||||
} | } | ||||
@@ -196,6 +185,74 @@ footer ul li:not(:last-child):after { | |||||
background-color: rgba(255, 60, 30, 0.2); | background-color: rgba(255, 60, 30, 0.2); | ||||
} | } | ||||
#character-portrait { | |||||
height: 32px; | |||||
margin-right: 0.25em; | |||||
box-sizing: border-box; | |||||
border-width: 1px; | |||||
} | |||||
#character-summary { | |||||
font-size: 90%; | |||||
} | |||||
#character-options { | |||||
position: absolute; | |||||
margin-top: 1em; | |||||
margin-left: -10px; | |||||
padding: 0.5em; | |||||
background-color: rgba(0, 0, 0, 0.8); | |||||
border-width: 1px; | |||||
} | |||||
#character-options:after, #character-options:before { | |||||
bottom: 100%; | |||||
left: 20px; | |||||
border: solid transparent; | |||||
content: " "; | |||||
height: 0; | |||||
width: 0; | |||||
position: absolute; | |||||
pointer-events: none; | |||||
} | |||||
#character-options:after { | |||||
border-bottom-color: black; | |||||
border-width: 10px; | |||||
margin-left: -10px; | |||||
} | |||||
#character-options:before { | |||||
border-bottom-color: #4A4A4A; | |||||
border-width: 12px; | |||||
margin-left: -12px; | |||||
} | |||||
#style-options { | |||||
line-height: 0; | |||||
} | |||||
#style-options form { | |||||
display: inline-block; | |||||
} | |||||
#style-options form:not(:first-child) { | |||||
margin-left: 0.25em; | |||||
} | |||||
#style-options input[type=submit] { | |||||
display: inline-block; | |||||
height: 24px; | |||||
width: 24px; | |||||
background-color: transparent; | |||||
background-size: contain; | |||||
background-position: center; | |||||
background-repeat: no-repeat; | |||||
font-size: 0; | |||||
border: 0; | |||||
cursor: pointer; | |||||
} | |||||
#error pre { | #error pre { | ||||
white-space: pre-wrap; | white-space: pre-wrap; | ||||
} | } | ||||
@@ -1,3 +1,7 @@ | |||||
var overlaps = function(needle, haystack) { | |||||
return haystack.is(needle) || haystack.has(needle).length > 0; | |||||
}; | |||||
$(function() { | $(function() { | ||||
// Install logout auto-POST form: | // Install logout auto-POST form: | ||||
$("#logout").click(function() { | $("#logout").click(function() { | ||||
@@ -7,4 +11,23 @@ $(function() { | |||||
}).appendTo($("body")).submit(); | }).appendTo($("body")).submit(); | ||||
return false; | return false; | ||||
}); | }); | ||||
// Toggle character options on click: | |||||
var charopts = $("#character-options"); | |||||
charopts.hide(); | |||||
$("#character-portrait").click(function() { | |||||
if (charopts.is(":visible")) { | |||||
charopts.hide(); | |||||
$(document).off("mouseup.charopts"); | |||||
} else { | |||||
charopts.show(); | |||||
$(document).on("mouseup.charopts", function(e) { | |||||
if (!overlaps(e.target, charopts) && | |||||
!overlaps(e.target, $("#character-portrait"))) { | |||||
charopts.hide(); | |||||
$(document).off("mouseup.charopts"); | |||||
} | |||||
}); | |||||
} | |||||
}).css("cursor", "pointer"); | |||||
}); | }); |
@@ -1,11 +1,15 @@ | |||||
body { | body { | ||||
background-image: url("/static/images/amarr.jpg?v=1"); | |||||
background-image: url("/static/images/bg/amarr.jpg?v=1"); | |||||
} | } | ||||
main, header, footer, #character-portrait { | |||||
.styled-border { | |||||
border-color: #5F4A26; | border-color: #5F4A26; | ||||
} | } | ||||
#character-options:before { | |||||
border-bottom-color: #5F4A26; | |||||
} | |||||
a { | a { | ||||
color: #FFDE78; | color: #FFDE78; | ||||
} | } | ||||
@@ -1,11 +1,15 @@ | |||||
body { | body { | ||||
background-image: url("/static/images/caldari.jpg?v=1"); | |||||
background-image: url("/static/images/bg/caldari.jpg?v=1"); | |||||
} | } | ||||
main, header, footer, #character-portrait { | |||||
.styled-border { | |||||
border-color: #364A5F; | border-color: #364A5F; | ||||
} | } | ||||
#character-options:before { | |||||
border-bottom-color: #364A5F; | |||||
} | |||||
a { | a { | ||||
color: #99CCEE; | color: #99CCEE; | ||||
} | } | ||||
@@ -1,11 +1,15 @@ | |||||
body { | body { | ||||
background-image: url("/static/images/gallente.jpg?v=1"); | |||||
background-image: url("/static/images/bg/gallente.jpg?v=1"); | |||||
} | } | ||||
main, header, footer, #character-portrait { | |||||
.styled-border { | |||||
border-color: #365F4A; | border-color: #365F4A; | ||||
} | } | ||||
#character-options:before { | |||||
border-bottom-color: #365F4A; | |||||
} | |||||
a { | a { | ||||
color: #66BB99; | color: #66BB99; | ||||
} | } | ||||
@@ -1,11 +1,15 @@ | |||||
body { | body { | ||||
background-image: url("/static/images/minmatar.jpg?v=1"); | |||||
background-image: url("/static/images/bg/minmatar.jpg?v=1"); | |||||
} | } | ||||
main, header, footer, #character-portrait { | |||||
.styled-border { | |||||
border-color: #5F3C42; | border-color: #5F3C42; | ||||
} | } | ||||
#character-options:before { | |||||
border-bottom-color: #5F3C42; | |||||
} | |||||
a { | a { | ||||
color: #D89988; | color: #D89988; | ||||
} | } | ||||
@@ -8,12 +8,12 @@ | |||||
<meta name="viewport" content="width=device-width, initial-scale=1"> | <meta name="viewport" content="width=device-width, initial-scale=1"> | ||||
<link rel="canonical" href="${g.config.scheme}://${g.config.get('site.canonical')}${request.script_root}${request.path}"> | <link rel="canonical" href="${g.config.scheme}://${g.config.get('site.canonical')}${request.script_root}${request.path}"> | ||||
<link rel="stylesheet" type="text/css" href="${url_for('staticv', filename='main.css')}"/> | <link rel="stylesheet" type="text/css" href="${url_for('staticv', filename='main.css')}"/> | ||||
% if g.config.get("style"): | |||||
<% | |||||
charstyle = g.auth.get_character_prop("style") | |||||
style = charstyle if charstyle else g.config.get("style") | |||||
stylesheet = "styles/{}.css".format(style) | |||||
%> | |||||
<% | |||||
charstyle = g.auth.get_character_prop("style") | |||||
style = charstyle if charstyle else g.config.get("style.default") | |||||
%> | |||||
% if style: | |||||
<% stylesheet = "styles/{}.css".format(style) %> | |||||
<link rel="stylesheet" type="text/css" href="${url_for('staticv', filename=stylesheet)}"/> | <link rel="stylesheet" type="text/css" href="${url_for('staticv', filename=stylesheet)}"/> | ||||
% endif | % endif | ||||
% for size in g.eve.image.corp_widths: | % for size in g.eve.image.corp_widths: | ||||
@@ -24,7 +24,7 @@ | |||||
</head> | </head> | ||||
<body> | <body> | ||||
<%block name="header"> | <%block name="header"> | ||||
<header> | |||||
<header class="styled-border"> | |||||
<div> | <div> | ||||
<div class="left"> | <div class="left"> | ||||
<%block name="lefthead"> | <%block name="lefthead"> | ||||
@@ -45,7 +45,7 @@ | |||||
<%block name="container"> | <%block name="container"> | ||||
<div id="container"> | <div id="container"> | ||||
<div> | <div> | ||||
<main> | |||||
<main class="styled-border"> | |||||
<%block name="flashes"> | <%block name="flashes"> | ||||
<% messages = get_flashed_messages(with_categories=True) %> | <% messages = get_flashed_messages(with_categories=True) %> | ||||
% if messages: | % if messages: | ||||
@@ -62,7 +62,7 @@ | |||||
</div> | </div> | ||||
</%block> | </%block> | ||||
<%block name="footer"> | <%block name="footer"> | ||||
<footer> | |||||
<footer class="styled-border"> | |||||
<div> | <div> | ||||
<ul> | <ul> | ||||
<li>YC ${g.eve.clock.now()}</li> | <li>YC ${g.eve.clock.now()}</li> | ||||
@@ -6,7 +6,16 @@ | |||||
</nav> | </nav> | ||||
</%block> | </%block> | ||||
<%block name="righthead"> | <%block name="righthead"> | ||||
<img id="character-portrait" class="aligned" title="${g.auth.get_character_prop('name')}" alt="" src="${g.eve.image.character(g.auth.get_character_id(), 256)}"/> | |||||
<img id="character-portrait" class="styled-border aligned" alt="" src="${g.eve.image.character(g.auth.get_character_id(), 256)}"/> | |||||
<div id="character-options" class="styled-border"> | |||||
<div id="style-options"> | |||||
% for style in g.config.get("style.enabled"): | |||||
<form action="${url_for('set_style', style=style)}" method="post"> | |||||
<input type="submit" title="${style.title()}" value="${style}" style="background-image: url('${url_for('staticv', filename='images/style/{}.png'.format(style))}')"> | |||||
</form> | |||||
% endfor | |||||
</div> | |||||
</div> | |||||
<span id="character-summary" class="aligned"> | <span id="character-summary" class="aligned"> | ||||
${g.auth.get_character_prop("name")} | ${g.auth.get_character_prop("name")} | ||||
<span class="sep">[</span><a id="logout" title="Log out" href="${url_for('logout')}">log out</a><span class="sep">]</span> | <span class="sep">[</span><a id="logout" title="Log out" href="${url_for('logout')}">log out</a><span class="sep">]</span> | ||||