Compare commits
2 Commits
main
...
feat/pages
| Author | SHA1 | Date | |
|---|---|---|---|
| 62746818af | |||
| b2018f1df0 |
9
app.py
9
app.py
@@ -4,7 +4,7 @@ import datetime
|
||||
from sass import compile as compile_sass
|
||||
from flask import Flask, redirect, request, url_for, g, Response
|
||||
|
||||
from modules import autobuild, locales, helpers
|
||||
from modules import autobuild, locales, helpers, slides
|
||||
|
||||
|
||||
# ---------- APP CONFIG ------------------------------------------------------
|
||||
@@ -12,7 +12,9 @@ from modules import autobuild, locales, helpers
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
# load locales and slides
|
||||
locales.ensure_loaded()
|
||||
SLIDES = slides.load_slides(app.root_path)
|
||||
|
||||
# CSS Compilation and minification
|
||||
if app.debug:
|
||||
@@ -53,6 +55,11 @@ def _inject_autobuild_date():
|
||||
return {'autobuild_date': autobuild.autobuild_date}
|
||||
|
||||
|
||||
@app.context_processor
|
||||
def _inject_slides():
|
||||
return {"slides": SLIDES}
|
||||
|
||||
|
||||
@app.context_processor
|
||||
def inject_translations():
|
||||
def translate(text, **kwargs):
|
||||
|
||||
31
modules/slides.py
Normal file
31
modules/slides.py
Normal file
@@ -0,0 +1,31 @@
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def load_slides(root_path):
|
||||
shots_dir = Path(root_path) / "static" / "img" / "screenshots"
|
||||
if not shots_dir.exists():
|
||||
return []
|
||||
|
||||
slides = []
|
||||
for path in shots_dir.iterdir():
|
||||
if not path.is_file():
|
||||
continue
|
||||
if path.suffix.lower() not in {".png", ".jpg", ".jpeg", ".webp", ".gif"}:
|
||||
continue
|
||||
stem = path.stem
|
||||
num = int(stem) if stem.isdigit() else None
|
||||
slides.append(
|
||||
{
|
||||
"filename": f"img/screenshots/{path.name}",
|
||||
"key": stem,
|
||||
"num": num,
|
||||
}
|
||||
)
|
||||
|
||||
slides.sort(
|
||||
key=lambda item: (
|
||||
item["num"] is None,
|
||||
item["num"] if item["num"] is not None else item["key"],
|
||||
)
|
||||
)
|
||||
return slides
|
||||
135
static/script.js
135
static/script.js
@@ -1,119 +1,42 @@
|
||||
/* LANGUAGE DROPDOWN */
|
||||
|
||||
function dropdown_show(obj)
|
||||
{
|
||||
var x = y = 0;
|
||||
|
||||
while(obj)
|
||||
{
|
||||
x += obj.offsetLeft;
|
||||
y += obj.offsetTop;
|
||||
obj = obj.offsetParent;
|
||||
function dropdown_show(el) {
|
||||
let x = 0;
|
||||
let y = 0;
|
||||
while (el) {
|
||||
x += el.offsetLeft;
|
||||
y += el.offsetTop;
|
||||
el = el.offsetParent;
|
||||
}
|
||||
|
||||
ddown = document.getElementById("lang-dropdown");
|
||||
const ddown = document.getElementById("lang-dropdown");
|
||||
ddown.style.display = "block";
|
||||
ddown.style.left = (x - 72) + "px";
|
||||
|
||||
if (ddown.offsetLeft + ddown.offsetWidth +10 > document.body.offsetWidth)
|
||||
{
|
||||
ddown.style.left = document.body.offsetWidth - ddown.offsetWidth - 82 + "px";
|
||||
ddown.style.left = (x - 72) + "px";
|
||||
if (ddown.offsetLeft + ddown.offsetWidth + 10 > document.body.offsetWidth) {
|
||||
ddown.style.left = (document.body.offsetWidth - ddown.offsetWidth - 82) + "px";
|
||||
}
|
||||
|
||||
ddown.style.top = (y + 48) + "px";
|
||||
op = 0;
|
||||
appear(1);
|
||||
fade_to(ddown, 1);
|
||||
}
|
||||
|
||||
function dropdown_hide()
|
||||
{
|
||||
ddown = document.getElementById("lang-dropdown");
|
||||
ddown.style.display="none";
|
||||
}
|
||||
|
||||
function appear(x)
|
||||
{
|
||||
if(op < x)
|
||||
{
|
||||
op += 0.2;
|
||||
ddown.style.opacity = op;
|
||||
ddown.style.filter = 'alpha(opacity=' + op * 100 + ')';
|
||||
t = setTimeout('appear(' + x + ')', 20);
|
||||
function dropdown_hide(e) {
|
||||
for (let t = e && e.target; t; t = t.parentElement) {
|
||||
if (t.id === "lang-dropdown" || t.id === "lang-butt") return;
|
||||
}
|
||||
}
|
||||
|
||||
/* SCREENSHOTS GALLERY */
|
||||
|
||||
var FIRST_IMG_ID = 1;
|
||||
var LAST_IMG_ID = 6;
|
||||
var current = LAST_IMG_ID; // start with last slide so that next() shows the first
|
||||
|
||||
window.onload = function() {
|
||||
// Dynamically create dots based on number of slides
|
||||
var dots = document.getElementById("dots");
|
||||
for (var i = FIRST_IMG_ID; i <= LAST_IMG_ID; i++) {
|
||||
var dot = document.createElement("span");
|
||||
dot.className = "dot" + (i === current ? " active" : "");
|
||||
dot.setAttribute("data-slide", i);
|
||||
dot.onclick = function() {
|
||||
goToSlide(parseInt(this.getAttribute("data-slide")));
|
||||
};
|
||||
dots.appendChild(dot);
|
||||
}
|
||||
// If a carousel element exists, advance to the first slide on load
|
||||
if (document.getElementById("carousel")) next();
|
||||
};
|
||||
|
||||
function updateDots() {
|
||||
var dots = document.querySelectorAll("#dots .dot");
|
||||
dots.forEach(function(dot, index) {
|
||||
// index starts at 0 so add FIRST_IMG_ID to match your slide IDs
|
||||
dot.classList.toggle("active", (index + FIRST_IMG_ID) === current);
|
||||
const ddown = document.getElementById("lang-dropdown");
|
||||
fade_to(ddown, 0, function() {
|
||||
ddown.style.display = "none";
|
||||
});
|
||||
}
|
||||
|
||||
function goToSlide(n) {
|
||||
if (n === current) return;
|
||||
document.getElementById("slide" + current).className = "minislide";
|
||||
current = n;
|
||||
document.getElementById("slide" + current).className = "visible";
|
||||
if (document.getElementById("carousel")) {
|
||||
document.getElementById("carousel").innerHTML = document.getElementById("slide" + current).alt;
|
||||
function fade_to(el, target, done) {
|
||||
const from = parseFloat(el.style.opacity) || 0;
|
||||
const delta = target - from;
|
||||
let start;
|
||||
function step(ts) {
|
||||
if (!start) start = ts;
|
||||
const t = Math.min((ts - start) / 200, 1);
|
||||
el.style.opacity = from + delta * t;
|
||||
if (t < 1) requestAnimationFrame(step);
|
||||
else if (done) done();
|
||||
}
|
||||
updateDots();
|
||||
requestAnimationFrame(step);
|
||||
}
|
||||
|
||||
function next() {
|
||||
document.getElementById("slide" + current).className = "minislide";
|
||||
if (current >= LAST_IMG_ID) {
|
||||
current = FIRST_IMG_ID;
|
||||
} else {
|
||||
current++;
|
||||
}
|
||||
document.getElementById("slide" + current).className = "visible";
|
||||
if (document.getElementById("carousel")) {
|
||||
document.getElementById("carousel").innerHTML = document.getElementById("slide" + current).alt;
|
||||
}
|
||||
updateDots();
|
||||
}
|
||||
|
||||
function previous() {
|
||||
document.getElementById("slide" + current).className = "minislide";
|
||||
if (current <= FIRST_IMG_ID) {
|
||||
current = LAST_IMG_ID;
|
||||
} else {
|
||||
current--;
|
||||
}
|
||||
document.getElementById("slide" + current).className = "visible";
|
||||
if (document.getElementById("carousel")) {
|
||||
document.getElementById("carousel").innerHTML = document.getElementById("slide" + current).alt;
|
||||
}
|
||||
updateDots();
|
||||
}
|
||||
|
||||
function checkkey(e) {
|
||||
var keycode = window.event ? e.keyCode : e.which;
|
||||
if (keycode == 37) { previous(); }
|
||||
else if (keycode == 39) { next(); }
|
||||
else if (keycode == 27) { dropdown_hide(); }
|
||||
}
|
||||
|
||||
@@ -375,26 +375,73 @@ acronym {
|
||||
|
||||
/* SCREENS.CSS */
|
||||
|
||||
#show {
|
||||
max-width: 1280px;
|
||||
max-height: 800px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
cursor: pointer;
|
||||
border: 1px solid $c-screen-border;
|
||||
display: block;
|
||||
position: relative;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
.gallery {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
|
||||
img {
|
||||
&-input {
|
||||
display: none;
|
||||
max-width: 100%;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&.visible {
|
||||
&-slide {
|
||||
display: none;
|
||||
margin: 0;
|
||||
order: 1;
|
||||
flex: 0 0 100%;
|
||||
}
|
||||
|
||||
&-input:checked + &-slide {
|
||||
display: block;
|
||||
}
|
||||
|
||||
&-show {
|
||||
max-width: 1280px;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
cursor: pointer;
|
||||
border: 1px solid $c-screen-border;
|
||||
display: block;
|
||||
position: relative;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
|
||||
img {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&-next {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&-caption {
|
||||
text-align: center;
|
||||
margin: 1.25em auto 1em;
|
||||
}
|
||||
|
||||
&-dot {
|
||||
display: inline-block;
|
||||
order: 2;
|
||||
|
||||
.dot {
|
||||
display: inline-block;
|
||||
width: 0.625em;
|
||||
height: 0.625em;
|
||||
background: $c-dot;
|
||||
border-radius: 50%;
|
||||
margin: 0 0.25em;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
&-input:checked + &-slide + &-dot .dot {
|
||||
background: $c-text;
|
||||
}
|
||||
}
|
||||
|
||||
iframe {
|
||||
@@ -404,30 +451,6 @@ iframe {
|
||||
aspect-ratio: 16 / 9;
|
||||
}
|
||||
|
||||
#carousel {
|
||||
text-align: center;
|
||||
margin: 1.25em auto 1em;
|
||||
}
|
||||
|
||||
#dots {
|
||||
text-align: center;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.dot {
|
||||
display: inline-block;
|
||||
width: 0.625em;
|
||||
height: 0.625em;
|
||||
background: $c-dot;
|
||||
border-radius: 50%;
|
||||
margin: 0 0.25em;
|
||||
cursor: pointer;
|
||||
|
||||
&.active {
|
||||
background: $c-text;
|
||||
}
|
||||
}
|
||||
|
||||
/* FOOTER */
|
||||
|
||||
#footer {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!doctype html>
|
||||
<html lang="{{ lang }}" onmouseup="dropdown_hide()">
|
||||
<html lang="{{ lang }}" onmouseup="dropdown_hide(event)">
|
||||
|
||||
{% include 'tmpl/_header.htm' %}
|
||||
|
||||
@@ -115,20 +115,43 @@
|
||||
|
||||
<h1>{{ _('screenshots:header') }}</h1>
|
||||
|
||||
<div id="screen" onclick="next()">
|
||||
<div id="show">
|
||||
{% for i in range(1, 7) %}
|
||||
<img
|
||||
id="slide{{ i }}"
|
||||
src="{{ url_for('static', filename='img/screenshots/%d.png' % i ) }}"
|
||||
{% if i == 1 %}class="visible"{% endif %}
|
||||
alt="{{ _('screenshots:%d' % i) }}"
|
||||
{% if slides %}
|
||||
<div id="screen" class="gallery">
|
||||
{% for slide in slides %}
|
||||
<input
|
||||
type="radio"
|
||||
name="slide"
|
||||
id="slide-{{ loop.index0 }}"
|
||||
class="gallery-input"
|
||||
{% if loop.first %}checked{% endif %}
|
||||
>
|
||||
<figure class="gallery-slide">
|
||||
<div class="gallery-show">
|
||||
<img
|
||||
src="{{ url_for('static', filename=slide.filename) }}"
|
||||
alt="{{ _('screenshots:%s' % slide.key) }}"
|
||||
>
|
||||
{% set next_index = 0 if loop.last else loop.index0 + 1 %}
|
||||
<label
|
||||
class="gallery-next"
|
||||
for="slide-{{ next_index }}"
|
||||
aria-label="Next slide"
|
||||
></label>
|
||||
</div>
|
||||
<figcaption class="gallery-caption">
|
||||
{{ _('screenshots:%s' % slide.key) }}
|
||||
</figcaption>
|
||||
</figure>
|
||||
<div class="gallery-dot">
|
||||
<label
|
||||
class="dot"
|
||||
for="slide-{{ loop.index0 }}"
|
||||
aria-label="{{ _('screenshots:%s' % slide.key) }}"
|
||||
></label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div id="carousel"></div>
|
||||
<div id="dots"></div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% include 'tmpl/_footer.htm' %}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!doctype html>
|
||||
<html lang="{{ lang }}" onmouseup="dropdown_hide()">
|
||||
<html lang="{{ lang }}" onmouseup="dropdown_hide(event)">
|
||||
|
||||
{% include 'tmpl/_header.htm' %}
|
||||
|
||||
@@ -20,4 +20,4 @@
|
||||
{% include 'tmpl/_footer.htm' %}
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user