Create a webpage version of a lesson

This commit is contained in:
Christopher T. Johnson
2023-11-20 13:23:05 -05:00
parent 217e31ee7a
commit d3c77f5569
17 changed files with 187 additions and 69 deletions

View File

@@ -1,4 +1,6 @@
main.py: ui/*.py main.py: ui/*.py ui/resources.rcc
%.py:%.ui %.py:%.ui
pyuic6 $< >$@ pyuic6 $< >$@
ui/%.rcc:ui/%.qrc ui/*.css ui/*/*.otf
rcc --binary $< -o $@

View File

@@ -1,16 +1,29 @@
import json import json
import os
import re
from typing import cast from typing import cast
import requests import requests
from PyQt6.QtCore import QPoint, QRect, Qt, QTimer, pyqtSlot from PyQt6.QtCore import (
QFile,
QIODeviceBase,
QPoint,
QRect,
QResource,
Qt,
QTimer,
pyqtSlot,
)
from PyQt6.QtGui import ( from PyQt6.QtGui import (
QBrush, QBrush,
QColor, QColor,
QFont, QFont,
QKeyEvent,
QMouseEvent, QMouseEvent,
QPainter, QPainter,
QPainterPath, QPainterPath,
QPaintEvent, QPaintEvent,
QTextBlockFormat,
QTextCharFormat, QTextCharFormat,
QTextCursor, QTextCursor,
QTextDocument, QTextDocument,
@@ -29,21 +42,28 @@ class EditDialog(QDialog, Ui_Dialog):
def __init__(self, person_id: int) -> None: def __init__(self, person_id: int) -> None:
super(EditDialog, self).__init__() super(EditDialog, self).__init__()
self.person_id = person_id self.person_id = person_id
if not QResource.registerResource(
os.path.join(os.path.dirname(__file__), "../ui/resources.rcc"), "/"
):
raise Exception("Unable to register resources.rcc")
styleSheet = QResource(":/display.css").data().decode("utf-8")
self.setupUi(self) self.setupUi(self)
# #
# Override UI # Override UI
# #
font = QFont() self.printBtn = QPushButton()
font.setFamily("OpenDyslexic") self.printBtn.setText("Print")
font.setPointSize(14) self.printBtn.setObjectName("printBtn")
self.paraEdit.setFont(font) self.verticalLayout.addWidget(self.printBtn)
# #
# End overrides # End overrides
# #
self.load_book(self.person_id) self.load_book(self.person_id)
blockNumber = self.block blockNumber = self.block
self.paraEdit.setReadOnly(True) self.paraEdit.setReadOnly(True)
self.paraEdit.document().setDefaultStyleSheet(styleSheet)
self.defEdit.setReadOnly(True) self.defEdit.setReadOnly(True)
self.defEdit.document().setDefaultStyleSheet(styleSheet)
self.show_section(self.section_id) self.show_section(self.section_id)
self.block = blockNumber self.block = blockNumber
self.savePosition() self.savePosition()
@@ -54,10 +74,72 @@ class EditDialog(QDialog, Ui_Dialog):
self.prevBtn.clicked.connect(self.prevAction) self.prevBtn.clicked.connect(self.prevAction)
self.nextParaBtn.clicked.connect(self.nextParaAction) self.nextParaBtn.clicked.connect(self.nextParaAction)
self.prevParaBtn.clicked.connect(self.prevParaAction) self.prevParaBtn.clicked.connect(self.prevParaAction)
self.printBtn.clicked.connect(self.printAction)
self.paraEdit.verticalScrollBar().valueChanged.connect(self.scrollSlot) self.paraEdit.verticalScrollBar().valueChanged.connect(self.scrollSlot)
self.scrollBtn.clicked.connect(self.scrollAction) self.scrollBtn.clicked.connect(self.scrollAction)
return return
def defToHtml(self, word: str, definition) -> str:
html = f'<h1 class="def-word">{word}</h1>' + "\n"
try:
html += f"<p class=\"phonetic\">{definition['phonetic']}</p>" + "\n"
except Exception:
pass
html += '<ul class="outer">' + "\n"
for meaning in definition["meanings"]:
html += f"<li>{meaning['partOfSpeech']}"
html += '<ul class="inner">'
for a_def in meaning["definitions"]:
html += f"<li>{a_def['definition']}</li>\n"
html += "</ul>\n"
html += "</ul>\n<p/>\n"
return html
@pyqtSlot()
def printAction(self) -> None:
html = "<!DOCTYPE html>\n<html>\n<head>\n"
html += (
'<link href="https://fonts.cdnfonts.com/css/open-dyslexic" rel="stylesheet">'
+ "\n"
)
html += (
'<link href="https://fonts.cdnfonts.com/css/tex-gyre-heros" rel="stylesheet">'
+ "\n"
)
html += '<style type="text/css">\n'
style = QResource(":/print.css").data().decode("utf-8")
html += style
html += "</style>\n</head>\n<body>\n"
query = QSqlQuery()
query.prepare(
"SELECT w.* FROM word_block wb "
"LEFT JOIN words w "
"ON (w.word_id = wb.word_id) "
"WHERE wb.section_id = :section_id "
"ORDER BY w.word"
)
query.bindValue(":section_id", self.section_id)
if not query.exec():
query_error(query)
while query.next():
word = query.value("word")
definition = json.loads(query.value("definition"))
html += self.defToHtml(word, definition)
html += "\n"
html += "<hr/>\n"
html += '<div class="text">' + "\n"
text = self.sections[self.section_map[self.section_id]]
text = re.sub(r"</?body>", "", text)
html += text
html += "\n</div>\n"
html += "\n</body>\n</html>\n"
qf = QFile("out.html")
if qf.open(QIODeviceBase.OpenModeFlag.WriteOnly):
qf.write(html.encode("utf-8"))
qf.close()
print("Printed!")
return
@pyqtSlot() @pyqtSlot()
def scrollAction(self) -> None: def scrollAction(self) -> None:
position = ( position = (
@@ -200,18 +282,26 @@ class EditDialog(QDialog, Ui_Dialog):
definition = json.loads(query.value("definition")) definition = json.loads(query.value("definition"))
try: try:
phonetic = definition["phonetic"] phonetic = definition["phonetic"]
except:
phonetic = ""
def_format.setToolTip( def_format.setToolTip(
f'<font size="24">{word}:<br/><font family="Tex Gyre Heros">{phonetic}</font></font>' f'<font size="24">{word}:<br/><font family="Tex Gyre Heros">{phonetic}</font></font>'
) )
cursor.setCharFormat(def_format) cursor.mergeCharFormat(def_format)
except:
pass
return return
#
# Event handlers
#
def mousePressEvent(self, event: QMouseEvent | None) -> None: def mousePressEvent(self, event: QMouseEvent | None) -> None:
return return
def keyPressEvent(self, event: QKeyEvent) -> None:
print(event)
super().keyPressEvent(event)
return
def paintEvent(self, e: QPaintEvent | None) -> None: def paintEvent(self, e: QPaintEvent | None) -> None:
idx = self.stackedWidget.currentIndex() idx = self.stackedWidget.currentIndex()
if idx > 0: if idx > 0:
@@ -328,8 +418,7 @@ class EditDialog(QDialog, Ui_Dialog):
query.bindValue(":end", end) query.bindValue(":end", end)
if not query.exec(): if not query.exec():
query_error(query) query_error(query)
if query.next(): if not query.next():
return
query.prepare( query.prepare(
"INSERT INTO word_block VALUES " "INSERT INTO word_block VALUES "
"( :word_id, :section_id, :block, :start, :end)" "( :word_id, :section_id, :block, :start, :end)"
@@ -341,6 +430,7 @@ class EditDialog(QDialog, Ui_Dialog):
query.bindValue(":end", end) query.bindValue(":end", end)
if not query.exec(): if not query.exec():
query_error(query) query_error(query)
def_format = QTextCharFormat() def_format = QTextCharFormat()
def_format.setFontUnderline(True) def_format.setFontUnderline(True)
cursor = QTextCursor(self.paraEdit.document()) cursor = QTextCursor(self.paraEdit.document())
@@ -351,7 +441,7 @@ class EditDialog(QDialog, Ui_Dialog):
cursor.setPosition( cursor.setPosition(
end + textBlock.position(), QTextCursor.MoveMode.KeepAnchor end + textBlock.position(), QTextCursor.MoveMode.KeepAnchor
) )
cursor.setCharFormat(def_format) cursor.mergeCharFormat(def_format)
return return
def display_definition(self) -> None: def display_definition(self) -> None:
@@ -377,47 +467,10 @@ class EditDialog(QDialog, Ui_Dialog):
definition = json.loads(query.value("definition")) definition = json.loads(query.value("definition"))
self.defEdit.document().clear() self.defEdit.document().clear()
cursor = self.defEdit.textCursor() cursor = self.defEdit.textCursor()
cursor.insertHtml(self.defToHtml(word,definition))
h1_size = 48
h1_weight = QFont.Weight.Bold
p_size = 16
p_weight = QFont.Weight.Normal
word_format = QTextCharFormat()
word_format.setFontPointSize(h1_size)
word_format.setFontWeight(h1_weight)
cursor.insertText(word, word_format)
typeFormat = QTextListFormat()
typeFormat.setStyle(QTextListFormat.Style.ListDisc)
typeFormat.setIndent(1)
defFormat = QTextListFormat()
defFormat.setStyle(QTextListFormat.Style.ListCircle)
defFormat.setIndent(2)
word_format.setFontWeight(p_weight)
word_format.setFontPointSize(p_size)
phonetic_format = QTextCharFormat()
phonetic_format.setFontPointSize(32)
phonetic_format.setFontFamily("TeX Gyre Heros")
try:
cursor.insertBlock()
cursor.insertText(definition["phonetic"], phonetic_format)
except Exception:
pass
cursor.setCharFormat(word_format)
for meaning in definition["meanings"]:
cursor.insertList(typeFormat)
cursor.insertText(meaning["partOfSpeech"])
cursor.insertList(defFormat)
first = True
for a_def in meaning["definitions"]:
if not first:
cursor.insertBlock()
cursor.insertText(a_def["definition"])
# TODO: synonyms
# TODO: antonyms
first = False
cursor.setPosition(0) cursor.setPosition(0)
self.defEdit.setTextCursor(cursor) self.defEdit.setTextCursor(cursor)
print(self.defEdit.document().toHtml())
return return
@pyqtSlot() @pyqtSlot()

16
main.py
View File

@@ -341,10 +341,18 @@ if __name__ == "__main__":
for fileName in os.listdir("ui"): for fileName in os.listdir("ui"):
if not fileName.endswith(".py"): if not fileName.endswith(".py"):
continue continue
uiName = fileName[:-3] + ".ui" uiName = "ui/" + fileName[:-3] + ".ui"
if os.path.getmtime("ui/" + uiName) > os.path.getmtime( rccName = "ui/" + fileName[:-3] + ".qrc"
"ui/" + fileName if not os.path.isfile(uiName) and not os.path.isfile(rccName):
): outOfDate.append(filenName)
continue
if os.path.isfile(uiName) and os.path.getmtime(
uiName
) > os.path.getmtime("ui/" + fileName):
outOfDate.append(fileName)
if os.path.isfile(rccName) and os.path.getmtime(
rccName
) > os.path.getmtime("ui/" + fileName):
outOfDate.append(fileName) outOfDate.append(fileName)
if len(outOfDate) > 0: if len(outOfDate) > 0:
print(f"UI out of date: {', '.join(outOfDate)}") print(f"UI out of date: {', '.join(outOfDate)}")

View File

@@ -118,6 +118,8 @@
</item> </item>
</layout> </layout>
</widget> </widget>
<resources/> <resources>
<include location="resources.qrc"/>
</resources>
<connections/> <connections/>
</ui> </ui>

23
ui/display.css Normal file
View File

@@ -0,0 +1,23 @@
body {
font-family: "opendyslexic", sans-serif;
}
hr {
height: 2px;
border-width: 0;
}
h1 {
font-size: 48px;
font-weight: Bold;
}
h3 {
font-size: 40px;
font-weight: Normal;
}
p, li {
max-width: 66ch;
font-size: 24px;
}
p.phonetic {
font-family: "Tex Gyre Heros", sans-serif;
font-size: 32px;
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

23
ui/print.css Normal file
View File

@@ -0,0 +1,23 @@
body {
font-family: "Open-Dyslexic", sans-serif;
}
hr {
height: 2px;
border-width: 0;
}
h1 {
font-size: 48px;
font-weight: Bold;
}
h3 {
font-size: 40px;
font-weight: Normal;
}
p, li {
max-width: 66ch;
font-size: 24px;
}
p.phonetic {
font-family: "TexGyreHeros", sans-serif;
font-size: 32px;
}

7
ui/resources.qrc Normal file
View File

@@ -0,0 +1,7 @@
<RCC>
<qresource prefix="/">
<file>print.css</file>
<file>display.css</file>
<file>opendyslexic/OpenDyslexic-Regular.otf</file>
</qresource>
</RCC>

BIN
ui/resources.rcc Normal file

Binary file not shown.