Co-authored-by: mco-system <michael.costa@mcos.nc>
Reviewed-on: #4
This commit was merged in pull request #4.
This commit is contained in:
2023-04-20 15:09:57 +02:00
parent 4c32d8b7a5
commit ee43e98385
53 changed files with 2198 additions and 179 deletions

202
src/thsf/__init__.py Normal file
View File

@@ -0,0 +1,202 @@
import re
import sys
import json
import logging
from logging import config
import yaml
from flask import Flask, render_template, redirect, request, url_for
from flask_minify import minify
from thsf.backend import Backend
from thsf.schedule import Schedule
from thsf.navbar import Navbar
# ------------------------------------------------------------------------------
# -- Configuration
# ------------------------------------------------------------------------------
class AppConfig:
""" Flask application config """
CONFIG_FILENAME = "config.yml"
# ------------------------------------------------------------------------------
# -- Application
# ------------------------------------------------------------------------------
logger = logging.getLogger('wsgi')
app = Flask(__name__)
# ------------------------------------------------------------------------------
# -- Local configuration
# ------------------------------------------------------------------------------
app.config.from_object(__name__ + '.AppConfig')
try:
with open(app.config["CONFIG_FILENAME"], mode="r", encoding="utf-8") as local_config_file:
app.local_config = yaml.load(local_config_file, Loader=yaml.SafeLoader)
app.config["SECRET_KEY"] = app.local_config["app"]["secret_key"]
app.config["LANGUAGES"] = app.local_config["app"]["languages"]
config.dictConfig(app.local_config["log"])
backend = Backend(url=app.local_config["pretalx"]["url"],
apiprefix=app.local_config["pretalx"]["apiprefix"],
apikey=app.local_config["pretalx"]["apikey"])
schedule = Schedule()
navbar = Navbar(config=app.local_config["navbar"])
except Exception as err:
logger.critical("[{}] {}".format(err.__class__, str(err)))
sys.exit(1)
if app.local_config["log"]["root"]["level"] != "DEBUG":
minify(app=app, html=True, js=True, cssless=True)
# ------------------------------------------------------------------------------
# -- Tools
# ------------------------------------------------------------------------------
@app.errorhandler(404)
def page_not_found(err):
return redirect(url_for('index'))
def get_slots():
return backend.get(endpoint=f"events/{app.local_config['pretalx']['event']}/schedules/{app.local_config['pretalx']['schedule']}/").json()
def get_speaker_biography(name):
try:
speaker_info = backend.get(endpoint=f"events/{app.local_config['pretalx']['event']}/speakers/", params={"q": name}).json()
logging.info(speaker_info)
return speaker_info["results"][0]["biography"].strip()
except Exception as err:
logging.error(f"UnknownSpeakerError: {name}")
return None
# ------------------------------------------------------------------------------
# -- Custom filters
# ------------------------------------------------------------------------------
@app.template_filter('date2dmyhm')
def date2dmyhm(date):
splitted_date = date.split("-")
splitted_time = splitted_date[2].split("T")[1].split(":")
year, month, day = (splitted_date[0],
splitted_date[1],
splitted_date[2].split("T")[0])
hour, minutes = (splitted_time[0], splitted_time[1].split("+")[0])
return f"{day}/{month} {hour}:{minutes}"
@app.template_filter('date2dayclass')
def date2dayclass(date):
classes = {"26/05": "bg1",
"27/05": "bg2",
"28/05": "bg3",}
splitted_date = date.split("-")
month, day = (splitted_date[1],
splitted_date[2].split("T")[0])
return classes[f"{day}/{month}"]
@app.template_filter('toicon')
def date2dmyhm(slot_type):
slot_types = {"Projection": "fa-solid fa-film",
"Presentation Courte": "fa-solid fa-person-chalkboard",
"DJ Set": "fa-solid fa-guitar",
"Concert": "fa-solid fa-guitar",
"Présentation": "fa-solid fa-person-chalkboard",
"Table Ronde": "fa-solid fa-users-line",
"Atelier": "fa-solid fa-screwdriver-wrench",
"Exposition": "fa-solid fa-palette"}
return slot_types[slot_type]
# ------------------------------------------------------------------------------
# -- Routes
# ------------------------------------------------------------------------------
@app.route('/favicon.ico', methods=['GET'])
def favicon():
return redirect(url_for('static', filename='images/favicon.png'))
@app.route('/', methods=['GET'])
def index():
return render_template("index.html",
navbar=navbar.get_from_page(page="/"))
@app.route('/planning', methods=['GET'])
def planning():
slots = get_slots()
for slot in slots.get("slots"):
for speaker in slot.get("speakers"):
speaker["biography"] = get_speaker_biography(speaker.get("name"))
return render_template("planning.html",
slots=sorted(slots.get("slots"),
key=lambda slot: slot.get("slot").get("start")),
navbar=navbar.get_from_page(page="/planning"))
@app.route('/place', methods=['GET'])
def place():
return render_template("index.html",
navbar=navbar.get_from_page(page="/place"))
@app.route('/food', methods=['GET'])
def food():
return render_template("index.html",
navbar=navbar.get_from_page(page="/food"))
@app.route('/goodies', methods=['GET'])
def goodies():
return render_template("goodies.html",
navbar=navbar.get_from_page(page="/goodies"))
@app.route('/concerts', methods=['GET'])
def concerts():
slots = get_slots()
return render_template("planning.html",
slots=sorted(slots.get("slots"),
key=lambda slot: slot.get("slot").get("start")),
navbar=navbar.get_from_page(page="/concerts"),
filter=["concert", "dj set"])
@app.route('/workshops', methods=['GET'])
def workshops():
slots = get_slots()
return render_template("planning.html",
slots=sorted(slots.get("slots"),
key=lambda slot: slot.get("slot").get("start")),
navbar=navbar.get_from_page(page="/workshops"),
filter=["workshop"])
@app.route('/screenings', methods=['GET'])
def screenings():
slots = get_slots()
return render_template("planning.html",
slots=sorted(slots.get("slots"),
key=lambda slot: slot.get("slot").get("start")),
navbar=navbar.get_from_page(page="/screenings"),
filter=["screening"])
@app.route('/discussions', methods=['GET'])
def discussions():
slots = get_slots()
return render_template("planning.html",
slots=sorted(slots.get("slots"),
key=lambda slot: slot.get("slot").get("start")),
navbar=navbar.get_from_page(page="/discussions"),
filter=["panel discussion"])
@app.route('/exhibitions', methods=['GET'])
def exhibitions():
slots = get_slots()
return render_template("planning.html",
slots=sorted(slots.get("slots"),
key=lambda slot: slot.get("slot").get("start")),
navbar=navbar.get_from_page(page="/exhibitions"),
filter=["exhibition"])
@app.route('/talks', methods=['GET'])
def talks():
slots = get_slots()
return render_template("planning.html",
slots=sorted(slots.get("slots"),
key=lambda slot: slot.get("slot").get("start")),
navbar=navbar.get_from_page(page="/talks"),
filter=["talk", "light talk"])
# ------------------------------------------------------------------------------
# -- Main
# ------------------------------------------------------------------------------
if __name__ == '__main__':
app.run(host='127.0.0.1', port=5000, debug=True)

View File

@@ -0,0 +1,15 @@
import requests
import logging
class Backend:
def __init__(self, url, apiprefix, apikey):
self.url = url
self.apiprefix = apiprefix
self.apikey = apikey
self.session = requests.Session()
def get(self, endpoint, params=None):
url = f"{self.url}/{self.apiprefix}/{endpoint}"
headers = {"Authorization": f"Token {self.apikey}",
"Accept": "application/json"}
return self.session.get(url, params=params, headers=headers)

View File

@@ -0,0 +1,6 @@
class Navbar:
def __init__(self, config):
self.config = config
def get_from_page(self, page):
return [item for item in self.config["items"] if item["url"] != page]

View File

@@ -0,0 +1,6 @@
class Schedule:
def __init__(self):
self.slots = list()
def set_slots(self, slots):
self.slots = slots

View File

@@ -0,0 +1,49 @@
@font-face {
font-family: pfdintextcomppromedium;
src: url(../fonts/PFDinTextCompPro-Medium.ttf);
}
@font-face {
font-family: pfdintextcompprothin;
src: url(../fonts/PFDinTextCompPro-Thin.ttf);
}
:root {
--main-bg-color: #e6007e;
--alt-bg-color: #E59730;
--alt2-bg-color: #9EBF43;
--alt3-bg-color: #3096E5;
--main-color: #ffffff;
--alt-main-color: #1A000D;
}
.white {
color: var(--main-color);
}
.black {
color: var(--alt-main-color);
}
.thin {
font-family: pfdintextcompprothin;
}
.bold {
font-family: pfdintextcomppromedium;
}
.bg1 {
background-color: var(--alt-bg-color);
border-color: var(--alt-bg-color);
}
.bg2 {
background-color: var(--alt2-bg-color);
border-color: var(--alt2-bg-color);
}
.bg3 {
background-color: var(--alt3-bg-color);
border-color: var(--alt3-bg-color);
}

View File

@@ -0,0 +1,109 @@
@media screen and (min-width: 45em) {
.header {
display: flex;
flex-direction: row;
justify-content: center;
gap: 0;
text-align: center;
font-size: 9.75em;
font-weight: bold;
padding: 0;
}
.logo {
width: inherit;
}
.header > span {
margin: 0;
}
.subheader {
margin: -1em 0 0 0;
font-size: 3.47em;
}
.place {
margin: 0;
font-size: 2.145em;
}
.important {
font-family: pfdintextcomppromedium;
}
.left {
float: left;
margin-right: 0.5em;
}
.right {
float: right;
margin-left: 0.5em;
}
.logo_partner {
max-width: 250px;
max-height: 250px;
margin: 1em;
}
.button {
font-size: 2.5em;
transition-property: color;
transition-duration: 1s;
}
.button:hover {
color: var(--main-color);
cursor: pointer;
}
}
@media screen and (max-width: 44em) {
.header {
display: flex;
flex-direction: row;
justify-content: center;
gap: 0;
text-align: center;
font-size: 6.75em;
font-weight: bold;
padding: 0;
}
.header > span {
margin: 0;
}
.logo {
width: inherit;
}
.subheader {
margin: -1em 0 0 0;
font-size: 2.30em;
}
.place {
margin: 0;
font-size: 1.2em;
}
.important {
font-family: pfdintextcomppromedium;
}
.left {
float: left;
margin-right: 0.25em;
}
.right {
float: right;
margin-left: 0.25em;
}
.logo_partner {
max-width: 125px;
max-height: 125px;
margin: 0.25em;
}
.button {
font-size: 1.2em;
transition-property: color;
transition-duration: 1s;
}
.button:hover {
color: var(--main-color);
cursor: pointer;
}
.goodies {
text-align: justify;
}
.goodies_pic {
max-width: 5em;
}
}

View File

@@ -0,0 +1,18 @@
a {
font-family: pfdintextcomppromedium;
font-weight: 250;
color: var(--alt-main-color);
transition-property: color;
transition-duration: 1s;
text-decoration: wavy;
}
a:hover {
color: var(--main-color);
cursor: pointer;
}
.content > p,
.content > h2 {
margin-top: 1.5em;
}

View File

@@ -0,0 +1,66 @@
.slot {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
align-content: center;
margin-bottom: 1em;
width: 20em;
}
.slot_header {
margin: 0;
width: 20em;
font-family: pfdintextcompprothin;
font-weight: bold;
}
.slot_title {
border-radius: 1em 0 0 0;
color: var(--main-color);
padding: 0.5em;
text-align: center;
display: flex;
flex-direction: row;
justify-content: flex-start;
align-content: flex-start;
align-items: flex-start;
width: 20em;
cursor: pointer;
}
.slot_title .title{
margin-left: 0.5em;
}
.slot_info {
background-color: var(--main-color);
color: var(--alt-main-color);
margin: 0;
padding: 0.5em;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-content: flex-start;
align-items: flex-start;
border-radius: 0 0 0 1em;
}
.speaker {
cursor: pointer;
}
.speakers > .speaker > .name > span {
text-decoration: underline;
}
.speaker_details,
.slot_details {
visibility: hidden;
display: none;
}
.speaker_img,
.slot_img {
width: 18em;
}

View File

@@ -0,0 +1,15 @@
* {
box-sizing: border-box;
}
body {
background-color: var(--main-bg-color);
font-family: pfdintextcomppromedium;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
align-content: center;
text-align: center;
/* background-color: yellow; */
}

View File

@@ -0,0 +1,57 @@
.tooltip,
.slot_tooltip {
position: relative;
display: inline-block;
}
.tooltip .tooltiptext {
visibility: hidden;
background-color: var(--alt-main-color);
color: var(--main-color);
text-align: center;
border-radius: 6px;
padding: 5px 0;
position: absolute;
z-index: 1;
bottom: 0;
left: 0;
opacity: 0;
transform: translateX(-25%) translateY(-2em);
transition: opacity 1s;
}
.slot_tooltip .slot_tooltiptext {
visibility: hidden;
background-color: var(--main-color);
color: var(--alt-main-color);
text-align: center;
border-radius: 6px;
padding: 5px 0;
position: absolute;
z-index: 1;
bottom: 0;
left: 0;
opacity: 0;
transform: translateX(2em);
transition: opacity 1s;
}
.tooltip .tooltiptext::after,
.slot_tooltip .slot_tooltiptext::after {
content: "";
position: absolute;
top: 100%;
left: 50%;
margin-left: -5px;
border-width: 5px;
}
.tooltip:hover .tooltiptext,
.slot_tooltip:hover .slot_tooltiptext {
visibility: visible;
opacity: 1;
font-size: 0.7em;
padding: 0.2em;
}

View File

@@ -0,0 +1,72 @@
@media screen and (min-width: 45em) {
.wemakeporn {
background-color: #FFD036;
color: #000000;
font-family: Arial, Helvetica, sans-serif;
border-radius: 1em;
border-width: 1em;
border-style: solid;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
align-content: center;
padding: 0.5em;
transform: rotate(-20deg) translate(2em, -6em);
visibility: hidden;
position: fixed;
}
.wemakeporn:hover{
cursor: default;
}
.wemake {
font-size: 7em;
font-weight: bold;
flex-direction: row;
justify-content: center;
align-items: center;
align-content: center;
}
.porn {
font-size: 12em;
font-weight: bold;
flex-direction: row;
justify-content: center;
align-items: center;
align-content: center;
}
}
@media screen and (max-width: 44em) {
.wemakeporn {
background-color: #FFD036;
color: #000000;
font-family: Arial, Helvetica, sans-serif;
border-radius: 0.5em;
border-width: 0.5em;
border-style: solid;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
align-content: center;
padding: 0.5em;
transform: rotate(-20deg) translate(-1.5em, 8em);
visibility: hidden;
position: fixed;
}
.wemakeporn:hover{
cursor: default;
}
.wemake {
font-size: 3.5em;
font-weight: bold;
}
.porn {
font-size: 6em;
font-weight: bold;
}
}

View File

@@ -0,0 +1,125 @@
@media screen and (min-width: 45em) {
.page_wrapper {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
align-content: center;
text-align: center;
padding-bottom: 5em;
/* background-color: green; */
}
.center_wrapper,
.header_wrapper {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
}
.logo_wrapper {
margin: 1em 0 1em 0;
width: 40em;
}
.navbar_wrapper {
position: fixed;
bottom: 0;
padding: 1em 0;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
gap: 1.4em;
background-color: var(--main-bg-color);
width: 40em;
transform: translateZ(2);
}
.content {
font-size: 2em;
font-family: pfdintextcompprothin;
color: var(--main-color);
width: 20em;
text-align:justify;
text-justify: inter-word;
}
.partners {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
margin-bottom: 2em;
}
.subpartners {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
text-align: center;
}
}
@media screen and (max-width: 44em) {
.page_wrapper {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
align-content: center;
text-align: center;
padding-bottom: 2em;
width: 21em;
/* background-color: green; */
}
.center_wrapper ,
.header_wrapper {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
width: 21em;
/* background-color: orangered; */
}
.logo_wrapper {
margin-top: 0.5em;
width: 21em;
/* background-color: red; */
}
.navbar_wrapper {
position: fixed;
bottom: 0;
padding: 0.5em 0;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
gap: 1em;
background-color: var(--main-bg-color);
width: 21em;
transform: translateZ(2);
/* background-color: white; */
}
.content {
font-size: 1em;
font-family: pfdintextcompprothin;
color: var(--main-color);
width: 21em;
text-align:justify;
text-justify: inter-word;
}
.partners {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
margin-bottom: 2em;
}
.subpartners {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
text-align: center;
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

@@ -0,0 +1,14 @@
function switch_visibility(item){
console.log(item.style.visibility);
console.log(item.style.display);
if (item.style.visibility == "hidden" || item.style.display == "none") {
item.style.visibility = "visible";
item.style.display = "block";
} else if (item.style.visibility == "" && item.style.display == "") {
item.style.visibility = "visible";
item.style.display = "block";
} else {
item.style.visibility = "hidden";
item.style.display = "none";
}
}

View File

@@ -0,0 +1,95 @@
<!DOCTYPE html>
<html lang='zxx'>
<head>
<title>THSF 2023: S/Extraire</title>
<meta name="viewport" content="initial-scale=1.0">
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<script src="{{ url_for('static', filename='scripts/thsf23.js') }}"></script>
<link rel="stylesheet"
href="{{ url_for('static', filename='css/colors.css') }}">
<link rel="stylesheet"
href="{{ url_for('static', filename='css/wemakeporn.css') }}">
<link rel="stylesheet"
href="{{ url_for('static', filename='css/wrappers.css') }}">
<link rel="stylesheet"
href="{{ url_for('static', filename='css/custom.css') }}">
<link rel="stylesheet"
href="{{ url_for('static', filename='css/elements.css') }}">
<link rel="stylesheet"
href="{{ url_for('static', filename='css/style.css') }}">
<link rel="stylesheet"
href="{{ url_for('static', filename='css/tooltip.css') }}">
<link rel="stylesheet"
href="{{ url_for('static', filename='css/slot.css') }}">
<link rel="icon"
type="image/svg+xml"
href="{{ url_for('static', filename='images/logo.svg') }}">
<link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
{% block headers %}
{% endblock %}
</head>
<body>
<div class="page_wrapper">
<div class="center_wrapper">
<div class="header_wrapper">
<div class="header">
<span class="black bold">THSF</span>
<span class="white thin">2023</span>
</div>
<div class="subheader">
<span class="white thin">Toulouse Hacker Space Factory</span>
</div>
<div class="place">
<span class="black thin">26 28 mai 2023 - </span>
<span class="white bold">CINÉMA UTOPIA BORDEROUGE</span>
</div>
</div>
{% block content %}
{% endblock %}
<div class="partners">
<div class="subpartners">
<a href="https://www.tetalab.org/" target="_new">
<img src="{{ url_for('static', filename='images/tetalab.png')}}"
alt="Tetalab"
title="Tetalab"
class="logo_partner">
</a>
<a href="https://www.librairie-terranova.fr/" target="_new">
<img src="{{ url_for('static', filename='images/terranova.jpg')}}"
alt="Librairie Terra Nova"
title="Librairie Terra Nova"
class="logo_partner">
</a>
</div>
<div class="subpartners">
<a href="https://www.tetaneutral.net/" target="_new">
<img src="{{ url_for('static', filename='images/tetaneutral.png')}}"
alt="Tetaneutral"
title="Tetaneutral"
class="logo_partner">
</a>
</div>
<div class="subpartners">
<a href="https://clutchmag.fr/" target="_new">
<img src="{{ url_for('static', filename='images/clutch.png')}}"
alt="Clutch"
title="Clutch"
class="logo_partner">
</a>
<a href="https://www.antistatik.store/" target="_new">
<img src="{{ url_for('static', filename='images/antistatik.png')}}"
alt="Antistatik"
title="Antistatik"
class="logo_partner">
</a>
</div>
</div>
{% block navbar %}
{% include "navbar.html" %}
{% endblock %}
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,33 @@
{% extends "base.html" %}
{% block content %}
<div class="logo_wrapper">
<img class="logo"
src="{{ url_for('static', filename='images/logo.svg') }}"
alt="THSF 2023 - S/Extraire"
title="THSF 2023 - S/Extraire">
</div>
<div class="content" class="goodies">
<h2>Nous avons besoin de votre soutien</h2>
<p class="goodies">
<img class="left"
src="{{ url_for('static', filename='images/stickers.webp') }}"
alt="Stickers"
title="Stickers">
Pour faire du <strong>Toulouse Hacker Space Factory</strong> un événement toujours différent des autres festivals <strong>votre soutien financier</strong> est nécessaire.
</p>
<p class="goodies">
<img class="right"
src="{{ url_for('static', filename='images/tshirt.webp') }}"
alt="T-Shirt"
title="T-Shirt">
Pour faciliter la collecte des dons et vous remercier de votre participation, nous avons mis en place <a href="https://www.leetchi.com/c/thsf23" target="_new">une cagnote Leetchi</a> qui nous permettra de recenser les dons et vous permettra de suivre l'évolution du financement de notre festival.
</p>
<p class="goodies">
<img class="left"
src="{{ url_for('static', filename='images/sweatshirt.webp') }}"
alt="Sweat shirt"
title="Sweat shirt">
Nous sommes également conscients que la situation actuelle est particulièrement difficile. C'est pourquoi nous nous engageons à ce que <span class="important">toutes les sommes qui dépasseront notre objectif de financement de 2 000 € seront reversées aux caisses des grévistes de la réforme des retraites</span>. Ainsi, votre contribution permettra également de soutenir une cause importante et de <span class="important">faire une différence dans la vie de ceux qui se battent pour nos droits</span>.
</p>
</div>
{% endblock %}

View File

@@ -0,0 +1,27 @@
{% extends "base.html" %}
{% block content %}
<div class="logo_wrapper">
<img class="logo"
src="{{ url_for('static', filename='images/logo.svg') }}"
alt="THSF 2023 - S/Extraire"
title="THSF 2023 - S/Extraire"
onclick="document.getElementById('wemakeporn').style.visibility='visible';">
<div id="wemakeporn" class="wemakeporn"
onclick="document.getElementById('wemakeporn').style.visibility='hidden';">
<div class="wemake">WE MAKE</div>
<div class="porn">PORN</div>
</div>
</div>
<div class="content">
<h2>Le THSF est enfin de retour !</h2>
<p>Nous vous invitons à passer un week-end de 3 jours à <a href="https://www.cinemas-utopia.org/toulouse/" target="_new">Utopia Borderouge Toulouse</a> pour partager avec vous nos projets, nos réflexions, nos performances, nos poésies et nos doutes sur la technologie.</p>
<p>Après avoir été soutenu et accueilli pendant 10 ans par <a href="https://vive.mixart-myrys.org/" target="_new">Mix'Art Myrys</a>, le <strong>Toulouse HackerSpace Factory</strong> se tient désormais dans un autre lieu où l'utopie nécessaire est inscrite programme.</p>
<p>Cette année nous mettons en avant des réflexions sur <a href="https://fr.wikipedia.org/wiki/Extractivisme" target="_new">l'extractivisme des ressources</a> planétaires, des données, de la valeur travail.</p>
<p>Comme toujours, notre objectif est de créer un festival qui poétise les bifurcations de nos idées communes et qui réinvente le sens de certains schémas imposés par notre époque. Rejoignez-nous pour une expérience enrichissante et pleine de surprises !</p>
<p>Consulter <a href="/planning">le programme du THSF</a>.</p>
</div>
{% endblock %}

View File

@@ -0,0 +1,8 @@
<div class="navbar_wrapper">
{% for item in navbar %}
<i class="button tooltip black {{ item.classes }}"
onclick="document.location='{{item.url}}'">
<span class="tooltiptext thin">{{item.name}}</span>
</i>
{% endfor %}
</div>

View File

@@ -0,0 +1,15 @@
{% extends "base.html" %}
{% block content %}
<div class="content">
{% for slot in slots %}
{% set loop_index = loop.index %}
{% if filter %}
{% if slot.submission_type.en | lower in filter %}
{% include "slot.html" %}
{% endif %}
{% else %}
{% include "slot.html" %}
{% endif %}
{% endfor %}
</div>
{% endblock %}

View File

@@ -0,0 +1,64 @@
<div class="slot">
<div class="slot_header">
<div class="slot_title {{ slot.slot.start | date2dayclass}}"
onclick="switch_visibility(document.getElementById('{{slot.code}}_{{loop_index}}'))">
<i class="black {{slot.submission_type.fr | toicon }}"></i>
<span class="title">{{slot.title}}</span>
</div>
<div class="slot_info">
<div class="start">
<i class="fa-solid fa-caret-right"></i>
{{slot.slot.start | date2dmyhm}} - {{slot.duration}} minutes ({{slot.content_locale | capitalize}})
</div>
<div class="room">
<i class="fa-solid fa-caret-right"></i>
{{slot.slot.room.fr}}
</div>
<div class="speakers">
{% for speaker in slot.speakers %}
<div class="speaker">
<div class="name" onclick="switch_visibility(document.getElementById('{{speaker.code}}_{{loop_index}}'))">
<i class="fa-solid fa-user"></i>
<span>{{speaker.name | title}}</span>
</div>
{% if speaker.avatar or speaker.biography %}
<div id="{{speaker.code}}_{{loop_index}}" class="speaker_details">
{% if speaker.avatar %}
<div class="speaker_avatar">
<img class="speaker_img"
src="{{speaker.avatar}}"
alt="{{speaker.name | title}}"
title="{{speaker.name | title}}">
</div>
{% endif %}
{% if speaker.biography %}
<div class="speaker_biography">
<div class="speaker_biography">
{{ speaker.biography }}
</div>
</div>
{% endif %}
</div>
{% endif %}
</div>
{% endfor %}
</div>
<div id="{{slot.code}}_{{loop_index}}" class="slot_details">
<div class="abstract">
{% if slot.image %}
<img class="slot_img"
src="{{slot.image}}"
alt="{{slot.name}}"
title="{{slot.name}}">
{% endif %}
{% if slot.abstract %}
<p>{{slot.abstract}}</p>
{% endif %}
{% if slot.description %}
<p>{{slot.description}}</p>
{% endif %}
</div>
</div>
</div>
</div>
</div>

5
src/thsf_wsgi.py Normal file
View File

@@ -0,0 +1,5 @@
from thsf import app
if __name__ == "__main__":
application = app
application.run(host="127.0.0.1", port=8043)