feature: finished slide building process

This commit is contained in:
Prof. Dr. Matthias Jung DL9MJ
2025-06-24 22:04:42 +02:00
committed by Tim Grelka
parent dcfeb78bfa
commit 8a5a55371d
5 changed files with 236 additions and 92 deletions
+59 -16
View File
@@ -1,5 +1,6 @@
import json
import os
import random
import shutil
from bs4 import BeautifulSoup
@@ -9,19 +10,23 @@ from mistletoe import Document
from tqdm import tqdm
from renderer.fifty_ohm_html_renderer import FiftyOhmHtmlRenderer
from renderer.fifty_ohm_html_slide_renderer import FiftyOhmHtmlSlideRenderer
class Build:
def __init__(self):
memory = Memory("./cache", verbose=0)
self.env = Environment(loader=FileSystemLoader("templates/html"))
self.env = Environment(loader=FileSystemLoader("templates/"))
self.env.filters["shuffle_answers"] = self.__filter_shuffle_answers
self.questions = self.__parse_katalog()
# Decorate the method with memory.cache
self.__build_question = memory.cache(self.__build_question)
self.__build_chapter = memory.cache(self.__build_chapter)
self.__build_section = memory.cache(self.__build_section)
self.__build_page = memory.cache(self.__build_page)
self.__build_question = memory.cache(self.__build_question)
self.__build_question_slide = memory.cache(self.__build_question_slide)
self.__build_chapter = memory.cache(self.__build_chapter)
self.__build_section = memory.cache(self.__build_section)
self.__build_page = memory.cache(self.__build_page)
self.__build_chapter_slidedeck = memory.cache(self.__build_chapter_slidedeck)
def __parse_katalog(self):
with open("data/fragenkatalog3b.json") as fragenkatalog_file:
@@ -42,10 +47,10 @@ class Build:
return questions
# cached
def __build_question(self, input):
def __build_question(self, input, template_file="html/question.html"):
"""Combines the original question dataset from BNetzA with our internal metadata"""
question_template = self.env.get_template("question.html")
question_template = self.env.get_template(template_file)
with open("data/metadata.json") as metadata_file:
metadata = json.load(metadata_file)
@@ -89,9 +94,12 @@ class Build:
answer_pictures=answer_pictures,
)
def __build_question_slide(self, input):
return self.__build_question(input, template_file="slide/question.html")
# cached
def __build_page(self, content) :
page_template = self.env.get_template("page.html")
page_template = self.env.get_template("html/page.html")
return page_template.render(content=content)
def __picture_handler(self, id):
@@ -108,8 +116,8 @@ class Build:
# cached
def __build_chapter(self, edition, edition_name, number, chapter, next_chapter=None):
chapter_template = self.env.get_template("chapter.html")
next_chapter_template = self.env.get_template("next_chapter.html")
chapter_template = self.env.get_template("html/chapter.html")
next_chapter_template = self.env.get_template("html/next_chapter.html")
with open(f'build/{edition}_chapter_{chapter["ident"]}.html', 'w') as file:
result = chapter_template.render(
edition=edition,
@@ -129,10 +137,10 @@ class Build:
file.write(result)
# cached
def __build_section(self, edition, edition_name, section, chapter, next_section=None, next_chapter=None):
section_template = self.env.get_template("section.html")
next_section_template = self.env.get_template("next_section.html")
next_chapter_template = self.env.get_template("next_chapter.html")
def __build_section(self, edition, edition_name, section, section_id, chapter, next_section=None, next_chapter=None):
section_template = self.env.get_template("html/section.html")
next_section_template = self.env.get_template("html/next_section.html")
next_chapter_template = self.env.get_template("html/next_chapter.html")
with open(f'build/{edition}_{section["ident"]}.html', 'w') as file:
with FiftyOhmHtmlRenderer(
@@ -146,6 +154,7 @@ class Build:
edition=edition,
name=edition_name,
section=section,
section_id=section_id,
chapter=chapter,
)
@@ -164,8 +173,41 @@ class Build:
result = BeautifulSoup(result, "html.parser").prettify()
file.write(result)
def __build_chapter_slidedeck(self, edition, chapter, sections):
with open(f'build/{edition}_slide_{chapter["ident"]}.html', 'w') as file:
slide_template = self.env.get_template("slide/slide.html")
help_template = self.env.get_template("slide/help.html")
with FiftyOhmHtmlSlideRenderer(
question_renderer=self.__build_question_slide,
picture_handler=self.__picture_handler,
photo_handler=self.__photo_handler
) as renderer:
result = "<section>\n"
result += f'<section data-background="#DAEEFA">\n<h1>{chapter["title"]}</h1>\n</section>\n'
result += help_template.render()
result += "</section>\n"
for section in sections:
tmp = f'<section data-background="#DAEEFA">\n<h1>{section["title"]}</h1>\n</section>\n'
tmp += renderer.render_wrapper(section["slide"])
result += f"<section>{tmp}</section>\n"
result = slide_template.render(content=result)
file.write(result)
def __filter_shuffle_answers(self, seq):
answers = []
firstrun = True
for answer in seq:
if firstrun:
answers.append({"content": answer, "correct": True})
firstrun = False
else:
answers.append({"content": answer, "correct": False})
random.shuffle(answers)
return answers
def __build_book_index(self, book):
template = self.env.get_template("course_index.html")
template = self.env.get_template("html/course_index.html")
with open(f"build/{book['edition']}_course_index.html", "w") as file:
result = template.render(
book=book,
@@ -185,10 +227,11 @@ class Build:
for number, chapter in enumerate(tqdm(chapters, desc=f"Build Edition: {edition}"),1):
next_chapter = chapters[number] if number < len(chapters) else None
self.__build_chapter(edition, edition_name, number, chapter, next_chapter)
self.__build_chapter_slidedeck(edition, chapter, chapter["sections"])
for i, section in enumerate(chapter["sections"],1) :
next_section = chapter["sections"][i] if i < len(chapter["sections"]) else None
self.__build_section(edition, edition_name, section, chapter, next_section, next_chapter)
self.__build_section(edition, edition_name, section, i, chapter, next_section, next_chapter)
def build_assets(self):
shutil.copytree("assets", "build/assets", dirs_exist_ok=True)
+112
View File
@@ -0,0 +1,112 @@
<section>
<h3>Navigationshilfe</h3>
<p>Diese Navigationshilfe zeigt die ersten Schritte zur Verwendung der Präsention. Sie kann mit &xrarr; (Pfeiltaste
rechts) übersprungen werden.</p>
</section>
<section style="font-size: smaller;">
<h4>Navigation</h4>
<div id="left">
<p>Zwischen den Folien und Abschnitten kann man mittels der Pfeiltasten hin- und herspringen, dazu kann man auch
die Pfeiltasten am Computer nutzen.</p>
<img src="assets/images/slides/navigation.png" alt="Navigationspfeile für die Präsentation" />
</div>
<div id="right">
<ul class="list">
<li>Pfeil runter und hoch: Nächste / Vorherige Folie</li>
<li>Pfeil rechts und links: Nächster / Vorheriger Abschnitt</li>
<li>Leertaste oder „n“: Der Reihe nach alle Elemente in Folien aufdecken oder zur nächsten Folie blättern
</li>
<li>Shift-Leertaste oder „p“: Der Reihe nach Elemente rückwärts zudecken oder zur vorherigen Folie blättern
</li>
</ul>
</div>
</section>
<section style="font-size: smaller;">
<h4>Weitere Funktionen</h4>
<p>Mit ein paar Tastenkürzeln können weitere Funktionen aufgerufen werden. Die wichtigsten sind:</p>
<dl>
<dt>F1</dt>
<dd>Help / Hilfe</dd>
<dt>o</dt>
<dd>Overview / Übersicht aller Folien</dd>
<dt>s</dt>
<dd>Speaker View / Referentenansicht</dd>
<dt>f</dt>
<dd>Full Screen / Vollbildmodus</dd>
<dt>b</dt>
<dd>Break, Black, Pause / Ausblenden der Präsentation</dd>
<dt>Alt-Click</dt>
<dd>In die Folie hin- oder herauszoomen</dd>
</dl>
</section>
<section style="font-size: smaller;">
<h4>Übersicht</h4>
<div id="left">
<p>Die Präsentation ist zweidimensional aufgebaut. Dadurch sind in Spalten die einzelnen Abschnitte eines
Kapitels und in den Reihen die Folien zu den Abschnitten.</p>
<p>Tippt man ein „o“ ein, bekommt man eine Übersicht über alle Folien des jeweiligen Kapitels. Das hilft
sich zunächst einen Überblick zu verschaffen oder sich zu orientieren, wenn man das Gefühlt hat sich
„verlaufen“ zu haben. Die Navigation erfolgt über die Pfeiltasten.</p>
</div>
<div id="right">
<img src="assets/images/slides/overview.png" alt="Übersicht aller Folien" />
<p>Durch Anklicken einer Folie wird diese präsentiert.</p>
</div>
</section>
<section style="font-size: smaller;">
<h4>Referentenansicht</h4>
<div id="left">
<img src="assets/images/slides/notes.png" alt="Referentenansicht" />
<p>Tippt man ein „s“ ein, bekommt man ein neues Fenster, die Referentenansicht.</p>
<p>Indem man „Layout“ auswählt, kann man zwischen verschieden Anordnungen der Elemente auswählen.</p>
</div>
<div id="right">
<p>Die Referentenansicht bietet folgende Elemente:</p>
<ul class="list">
<li>Links sieht man die aktuelle Folie</li>
<li>Rechts oben sieht man die nächste Folie</li>
<li>Rechts in der Mitte Hilfsmittel zur Zeiteinteilung</li>
<li>Rechts unten, die „Notizen für den Vortragenden“</li>
<li>Unten die Pfeile zur Navigation</li>
</ul>
</div>
</section>
<section style="font-size: smaller;">
<h3>Praxistipps zur Referentenansicht</h3>
<ul class="list">
<li>Wenn man mit einem Projektor arbeitet, stellt man im Betriebssystem die Nutzung von 2 Monitoren ein: Die
Referentenansicht wird dann zum Beispiel auf dem Laptop angezeigt, während die Teilnehmer die
Präsentation angezeigt bekommen.</li>
<li>Bei einer Online-Präsentation, wie beispielsweise auf <a href="https://treff.darc.de/">TREFF.darc.de</a>
präsentiert man den Browser-Tab und navigiert im „Speaker View“ Fenster.</li>
<li>Die Referentenansicht bezieht sich immer auf ein Kapitel. Am Ende des Kapitels muss sie geschlossen
werden, um im neuen Kapitel eine neue Referentenansicht zu öffnen.</li>
<li>Um mit dem Mauszeiger etwas zu markieren oder den Zoom zu verwenden, muss mit der Maus auf den
Bildschirm mit der Präsentation gewechselt werden.</li>
</ul>
</section>
<section style="font-size: smaller;">
<h4>Vollbild</h4>
<p>Tippt man ein „f“ ein, wird die aktuelle Folie im Vollbild angezeigt. Mit „Esc“ kann man diesen wieder
verlassen.</p>
<p>Das ist insbesondere für den Bildschirm mit der Präsentation für das Publikum praktisch.</li>
</section>
<section style="font-size: smaller;">
<h4>Ausblenden</h4>
<p>Tippt man ein „b“ ein, wird die Präsentation ausgeblendet.</p>
<p>Sie kann wie folgte wieder eingeblendet werden:</p>
<ul class="list">
<li>Durch klicken in das Fenster.</li>
<li>Durch nochmaliges Drücken von „b“.</li>
<li>Durch klicken der Schaltfläche „Resume presentation:</li>
</ul>
<img src="assets/images/slides/resume.png" alt="Schaltfläche für Resume Presentation" />
</section>
<section style="font-size: smaller;">
<h4>Zoom</h4>
<p>Bei gedrückter Alt-Taste und einem Mausklick in der Präsentation wird in diesen Teil hineingezoomt. Das ist
praktisch, um Details von Schaltungen hervorzuheben. Durh einen nochmaligen Mausklick zusammen mit Alt wird
wieder herausgezoomt.</p>
<p>Das Zoomen funktioniert nur im ausgewählten Fenster. Die Referentenansicht ist hier nicht mit dem
Präsenationsansicht gesynct.</p>
</section>
+2 -2
View File
@@ -7,7 +7,7 @@
{% endif -%}
<div class="question-text">{{ number }}: {{ question }}</div>
{% if picture_question -%}
<div class="question-picture"><img class="r-stretch" src="picture/{{ picture_question }}.svg"></div>
<div class="question-picture"><img class="r-stretch" src="pictures/{{ picture_question }}.svg"></div>
{% endif -%}
<div class="answers">
{% if answers[0] -%}
@@ -17,7 +17,7 @@
{% endif -%}
{% if answer_pictures[0] -%}
{% for answer in answer_pictures|shuffle_answers -%}
<div class="question-picture{% if not answer.correct %} fragment semi-fade-out{% endif -%}" {% if not answer.correct -%} data-fragment-index="1" {% endif -%}><b>{{ answer_label[loop.index - 1] }}:</b><img class="r-stretch" src="picture/{{ answer.content }}.svg"></div>
<div class="question-picture{% if not answer.correct %} fragment semi-fade-out{% endif -%}" {% if not answer.correct -%} data-fragment-index="1" {% endif -%}><b>{{ answer_label[loop.index - 1] }}:</b><img class="r-stretch" src="pictures/{{ answer.content }}.svg"></div>
{% endfor -%}
{% endif -%}
</div>
+56
View File
@@ -0,0 +1,56 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>DARC Ausbildungsplattform</title>
<meta name="description" content="">
<meta name="author" content="">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="assets/reveal.js/dist/reset.css">
<link rel="stylesheet" href="assets/reveal.js/dist/reveal.css">
<link rel="stylesheet" href="assets/style-slide.css">
<!-- Theme used for syntax highlighting of code -->
<link rel="stylesheet" href="assets/reveal.js/plugin/highlight/monokai.css">
<!-- Chart plugin -->
<script src="assets/reveal.js-plugins/chart/plugin.js"></script>
<script src="assets/chartjs/4.4.1/chart.min.js"></script>
</head>
<body>
<div class="reveal">
<!-- Any section element inside of this container is displayed as a slide -->
<div class="slides">
{{ content }}
</div>
<script src="assets/reveal.js/dist/reveal.js"></script>
<script src="assets/reveal.js/plugin/zoom/zoom.js"></script>
<script src="assets/reveal.js/plugin/notes/notes.js"></script>
<script src="assets/reveal.js/plugin/search/search.js"></script>
<script src="assets/reveal.js/plugin/markdown/markdown.js"></script>
<script src="assets/reveal.js/plugin/highlight/highlight.js"></script>
<script src="assets/reveal.js/plugin/math/math.js"></script>
<script>
// Also available as an ES module, see:
// https://revealjs.com/initialization/
Reveal.initialize({
controls: true,
progress: true,
slideNumber: 'c/t',
showSlideNumber: 'speaker',
center: true,
hash: true,
transition: 'slide',
showNotes: false,
pdfSeparateFragments: false,
// Learn about plugins: https://revealjs.com/plugins/
plugins: [ RevealZoom, RevealNotes, RevealSearch, RevealMarkdown, RevealHighlight, RevealMath.KaTeX, RevealChart ]
});
</script>
</body>
</html>
+7 -74
View File
@@ -39,13 +39,6 @@ def filter_shuffle_answers(seq):
except Exception:
return seq
env = Environment(loader=FileSystemLoader("templates/"))
env.filters["shuffle_answers"] = filter_shuffle_answers
question_template = env.get_template("slide/question.html")
questions = parse_katalog()
def question_stub(input):
"""Combines the original question dataset from BNetzA with our internal metadata"""
with open("data/metadata.json") as metadata_file:
@@ -84,77 +77,17 @@ def question_stub(input):
answer_pictures=answer_pictures,
)
def print_head():
return """<! DOCTYPE html>
<html lang="de"><head>
<meta charset="utf-8">
<title>DARC Ausbildungsplattform</title>
<meta name="description" content="">
<meta name="author" content="">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="../assets/reveal.js/dist/reset.css">
<link rel="stylesheet" href="../assets/reveal.js/dist/reveal.css">
<link rel="stylesheet" href="../assets/style-slide.css">
<!-- Theme used for syntax highlighting of code -->
<link rel="stylesheet" href="../assets/reveal.js/plugin/highlight/monokai.css">
</head>
<body>
<div class="reveal">
<div class="slides">
"""
#<!-- Chart plugin -->
#<script src="../assets/reveal.js-plugins/chart/plugin.js"></script>
#<script src="../assets/chartjs/4.4.1/chart.min.js"></script>
def print_tail():
return """</div>
</div>
<script src="../assets/reveal.js/dist/reveal.js"></script>
<script src="../assets/reveal.js/plugin/zoom/zoom.js"></script>
<script src="../assets/reveal.js/plugin/notes/notes.js"></script>
<script src="../assets/reveal.js/plugin/search/search.js"></script>
<script src="../assets/reveal.js/plugin/markdown/markdown.js"></script>
<script src="../assets/reveal.js/plugin/highlight/highlight.js"></script>
<script src="../assets/reveal.js/plugin/math/math.js"></script>
<script>
// Also available as an ES module, see:
// https://revealjs.com/initialization/
Reveal.initialize({
controls: true,
progress: true,
slideNumber: 'c/t',
showSlideNumber: 'speaker',
center: true,
hash: true,
transition: 'slide',
showNotes: false,
pdfSeparateFragments: false,
// Learn about plugins: https://revealjs.com/plugins/
plugins: [ RevealZoom, RevealNotes, RevealSearch, RevealMarkdown, RevealHighlight, RevealMath.KaTeX] //, RevealChart ]
});
</script>
</body>"""
env = Environment(loader=FileSystemLoader("templates/"))
env.filters["shuffle_answers"] = filter_shuffle_answers
question_template = env.get_template("slide/question.html")
slide_template = env.get_template("slide/slide.html")
questions = parse_katalog()
def test_html_slides(capsys):
with capsys.disabled():
with open("test/acceptanceTestSlides.md") as file:
content = file.read()
with FiftyOhmHtmlSlideRenderer(question_stub) as renderer:
output = ""
output = print_head()
output += renderer.render_wrapper(content)
output += print_tail()
with FiftyOhmHtmlSlideRenderer(question_renderer=question_stub) as renderer:
output = renderer.render_wrapper(content)
with open("test/acceptanceTestSlides.html", "w") as output_file:
output_file.write(output)