Create a webpage version of a lesson
This commit is contained in:
4
Makefile
4
Makefile
@@ -1,4 +1,6 @@
|
||||
main.py: ui/*.py
|
||||
main.py: ui/*.py ui/resources.rcc
|
||||
|
||||
%.py:%.ui
|
||||
pyuic6 $< >$@
|
||||
ui/%.rcc:ui/%.qrc ui/*.css ui/*/*.otf
|
||||
rcc --binary $< -o $@
|
||||
|
||||
179
lib/read.py
179
lib/read.py
@@ -1,16 +1,29 @@
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
from typing import cast
|
||||
|
||||
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 (
|
||||
QBrush,
|
||||
QColor,
|
||||
QFont,
|
||||
QKeyEvent,
|
||||
QMouseEvent,
|
||||
QPainter,
|
||||
QPainterPath,
|
||||
QPaintEvent,
|
||||
QTextBlockFormat,
|
||||
QTextCharFormat,
|
||||
QTextCursor,
|
||||
QTextDocument,
|
||||
@@ -29,21 +42,28 @@ class EditDialog(QDialog, Ui_Dialog):
|
||||
def __init__(self, person_id: int) -> None:
|
||||
super(EditDialog, self).__init__()
|
||||
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)
|
||||
#
|
||||
# Override UI
|
||||
#
|
||||
font = QFont()
|
||||
font.setFamily("OpenDyslexic")
|
||||
font.setPointSize(14)
|
||||
self.paraEdit.setFont(font)
|
||||
self.printBtn = QPushButton()
|
||||
self.printBtn.setText("Print")
|
||||
self.printBtn.setObjectName("printBtn")
|
||||
self.verticalLayout.addWidget(self.printBtn)
|
||||
#
|
||||
# End overrides
|
||||
#
|
||||
self.load_book(self.person_id)
|
||||
blockNumber = self.block
|
||||
self.paraEdit.setReadOnly(True)
|
||||
self.paraEdit.document().setDefaultStyleSheet(styleSheet)
|
||||
self.defEdit.setReadOnly(True)
|
||||
self.defEdit.document().setDefaultStyleSheet(styleSheet)
|
||||
self.show_section(self.section_id)
|
||||
self.block = blockNumber
|
||||
self.savePosition()
|
||||
@@ -54,10 +74,72 @@ class EditDialog(QDialog, Ui_Dialog):
|
||||
self.prevBtn.clicked.connect(self.prevAction)
|
||||
self.nextParaBtn.clicked.connect(self.nextParaAction)
|
||||
self.prevParaBtn.clicked.connect(self.prevParaAction)
|
||||
self.printBtn.clicked.connect(self.printAction)
|
||||
self.paraEdit.verticalScrollBar().valueChanged.connect(self.scrollSlot)
|
||||
self.scrollBtn.clicked.connect(self.scrollAction)
|
||||
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()
|
||||
def scrollAction(self) -> None:
|
||||
position = (
|
||||
@@ -200,18 +282,26 @@ class EditDialog(QDialog, Ui_Dialog):
|
||||
definition = json.loads(query.value("definition"))
|
||||
try:
|
||||
phonetic = definition["phonetic"]
|
||||
def_format.setToolTip(
|
||||
f'<font size="24">{word}:<br/><font family="Tex Gyre Heros">{phonetic}</font></font>'
|
||||
)
|
||||
cursor.mergeCharFormat(def_format)
|
||||
except:
|
||||
phonetic = ""
|
||||
def_format.setToolTip(
|
||||
f'<font size="24">{word}:<br/><font family="Tex Gyre Heros">{phonetic}</font></font>'
|
||||
)
|
||||
cursor.setCharFormat(def_format)
|
||||
pass
|
||||
|
||||
return
|
||||
|
||||
#
|
||||
# Event handlers
|
||||
#
|
||||
def mousePressEvent(self, event: QMouseEvent | None) -> None:
|
||||
return
|
||||
|
||||
def keyPressEvent(self, event: QKeyEvent) -> None:
|
||||
print(event)
|
||||
super().keyPressEvent(event)
|
||||
return
|
||||
|
||||
def paintEvent(self, e: QPaintEvent | None) -> None:
|
||||
idx = self.stackedWidget.currentIndex()
|
||||
if idx > 0:
|
||||
@@ -328,19 +418,19 @@ class EditDialog(QDialog, Ui_Dialog):
|
||||
query.bindValue(":end", end)
|
||||
if not query.exec():
|
||||
query_error(query)
|
||||
if query.next():
|
||||
return
|
||||
query.prepare(
|
||||
"INSERT INTO word_block VALUES "
|
||||
"( :word_id, :section_id, :block, :start, :end)"
|
||||
)
|
||||
query.bindValue(":word_id", word_id)
|
||||
query.bindValue(":section_id", self.section_id)
|
||||
query.bindValue(":block", blockNum)
|
||||
query.bindValue(":start", start)
|
||||
query.bindValue(":end", end)
|
||||
if not query.exec():
|
||||
query_error(query)
|
||||
if not query.next():
|
||||
query.prepare(
|
||||
"INSERT INTO word_block VALUES "
|
||||
"( :word_id, :section_id, :block, :start, :end)"
|
||||
)
|
||||
query.bindValue(":word_id", word_id)
|
||||
query.bindValue(":section_id", self.section_id)
|
||||
query.bindValue(":block", blockNum)
|
||||
query.bindValue(":start", start)
|
||||
query.bindValue(":end", end)
|
||||
if not query.exec():
|
||||
query_error(query)
|
||||
|
||||
def_format = QTextCharFormat()
|
||||
def_format.setFontUnderline(True)
|
||||
cursor = QTextCursor(self.paraEdit.document())
|
||||
@@ -351,7 +441,7 @@ class EditDialog(QDialog, Ui_Dialog):
|
||||
cursor.setPosition(
|
||||
end + textBlock.position(), QTextCursor.MoveMode.KeepAnchor
|
||||
)
|
||||
cursor.setCharFormat(def_format)
|
||||
cursor.mergeCharFormat(def_format)
|
||||
return
|
||||
|
||||
def display_definition(self) -> None:
|
||||
@@ -377,47 +467,10 @@ class EditDialog(QDialog, Ui_Dialog):
|
||||
definition = json.loads(query.value("definition"))
|
||||
self.defEdit.document().clear()
|
||||
cursor = self.defEdit.textCursor()
|
||||
|
||||
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.insertHtml(self.defToHtml(word,definition))
|
||||
cursor.setPosition(0)
|
||||
self.defEdit.setTextCursor(cursor)
|
||||
print(self.defEdit.document().toHtml())
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
|
||||
16
main.py
16
main.py
@@ -341,10 +341,18 @@ if __name__ == "__main__":
|
||||
for fileName in os.listdir("ui"):
|
||||
if not fileName.endswith(".py"):
|
||||
continue
|
||||
uiName = fileName[:-3] + ".ui"
|
||||
if os.path.getmtime("ui/" + uiName) > os.path.getmtime(
|
||||
"ui/" + fileName
|
||||
):
|
||||
uiName = "ui/" + fileName[:-3] + ".ui"
|
||||
rccName = "ui/" + fileName[:-3] + ".qrc"
|
||||
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)
|
||||
if len(outOfDate) > 0:
|
||||
print(f"UI out of date: {', '.join(outOfDate)}")
|
||||
|
||||
@@ -118,6 +118,8 @@
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<resources>
|
||||
<include location="resources.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
||||
|
||||
23
ui/display.css
Normal file
23
ui/display.css
Normal 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;
|
||||
}
|
||||
BIN
ui/opendyslexic/OpenDyslexic-Bold.otf
Normal file
BIN
ui/opendyslexic/OpenDyslexic-Bold.otf
Normal file
Binary file not shown.
BIN
ui/opendyslexic/OpenDyslexic-BoldItalic.otf
Normal file
BIN
ui/opendyslexic/OpenDyslexic-BoldItalic.otf
Normal file
Binary file not shown.
BIN
ui/opendyslexic/OpenDyslexic-Italic.otf
Normal file
BIN
ui/opendyslexic/OpenDyslexic-Italic.otf
Normal file
Binary file not shown.
BIN
ui/opendyslexic/OpenDyslexic-Regular.otf
Normal file
BIN
ui/opendyslexic/OpenDyslexic-Regular.otf
Normal file
Binary file not shown.
BIN
ui/opendyslexic/OpenDyslexicAlta-Bold.otf
Normal file
BIN
ui/opendyslexic/OpenDyslexicAlta-Bold.otf
Normal file
Binary file not shown.
BIN
ui/opendyslexic/OpenDyslexicAlta-BoldItalic.otf
Normal file
BIN
ui/opendyslexic/OpenDyslexicAlta-BoldItalic.otf
Normal file
Binary file not shown.
BIN
ui/opendyslexic/OpenDyslexicAlta-Italic.otf
Normal file
BIN
ui/opendyslexic/OpenDyslexicAlta-Italic.otf
Normal file
Binary file not shown.
BIN
ui/opendyslexic/OpenDyslexicAlta-Regular.otf
Normal file
BIN
ui/opendyslexic/OpenDyslexicAlta-Regular.otf
Normal file
Binary file not shown.
BIN
ui/opendyslexic/OpenDyslexicMono-Regular.otf
Normal file
BIN
ui/opendyslexic/OpenDyslexicMono-Regular.otf
Normal file
Binary file not shown.
23
ui/print.css
Normal file
23
ui/print.css
Normal 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
7
ui/resources.qrc
Normal 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
BIN
ui/resources.rcc
Normal file
Binary file not shown.
Reference in New Issue
Block a user