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
|
%.py:%.ui
|
||||||
pyuic6 $< >$@
|
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 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"]
|
||||||
|
def_format.setToolTip(
|
||||||
|
f'<font size="24">{word}:<br/><font family="Tex Gyre Heros">{phonetic}</font></font>'
|
||||||
|
)
|
||||||
|
cursor.mergeCharFormat(def_format)
|
||||||
except:
|
except:
|
||||||
phonetic = ""
|
pass
|
||||||
def_format.setToolTip(
|
|
||||||
f'<font size="24">{word}:<br/><font family="Tex Gyre Heros">{phonetic}</font></font>'
|
|
||||||
)
|
|
||||||
cursor.setCharFormat(def_format)
|
|
||||||
|
|
||||||
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,19 +418,19 @@ 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)"
|
)
|
||||||
)
|
query.bindValue(":word_id", word_id)
|
||||||
query.bindValue(":word_id", word_id)
|
query.bindValue(":section_id", self.section_id)
|
||||||
query.bindValue(":section_id", self.section_id)
|
query.bindValue(":block", blockNum)
|
||||||
query.bindValue(":block", blockNum)
|
query.bindValue(":start", start)
|
||||||
query.bindValue(":start", start)
|
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
16
main.py
@@ -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)}")
|
||||||
|
|||||||
@@ -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
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