sort order added to deck
This commit is contained in:
parent
2c0c63036d
commit
ed1c6b9d4e
6 changed files with 59 additions and 18 deletions
|
|
@ -23,6 +23,7 @@ def compile_anki_package(records, output_path, deck_name):
|
||||||
Compiles database records into a bidirectional card payload package.
|
Compiles database records into a bidirectional card payload package.
|
||||||
Resolves voice models dynamically by gender selection parameters and applies
|
Resolves voice models dynamically by gender selection parameters and applies
|
||||||
global speed coefficient rates from the active configurations.
|
global speed coefficient rates from the active configurations.
|
||||||
|
Injects sort_order as field 0 for structured card sequencing.
|
||||||
"""
|
"""
|
||||||
# Generate deterministic positive 32-bit integers from deck name and model name
|
# Generate deterministic positive 32-bit integers from deck name and model name
|
||||||
# to avoid collisions across different decks while keeping imports stable
|
# to avoid collisions across different decks while keeping imports stable
|
||||||
|
|
@ -40,6 +41,7 @@ def compile_anki_package(records, output_path, deck_name):
|
||||||
model_id,
|
model_id,
|
||||||
'Spanish Bidirectional Multi-Note HTML Model',
|
'Spanish Bidirectional Multi-Note HTML Model',
|
||||||
fields=[
|
fields=[
|
||||||
|
{'name': 'SortOrder'}, # Field 0: Anki primary sort field
|
||||||
{'name': 'EnglishText'},
|
{'name': 'EnglishText'},
|
||||||
{'name': 'SpanishText'},
|
{'name': 'SpanishText'},
|
||||||
{'name': 'AnkiNotes'},
|
{'name': 'AnkiNotes'},
|
||||||
|
|
@ -85,6 +87,11 @@ def compile_anki_package(records, output_path, deck_name):
|
||||||
es_raw = record["es_text"]
|
es_raw = record["es_text"]
|
||||||
gender_flag = record.get("gender", "Female")
|
gender_flag = record.get("gender", "Female")
|
||||||
|
|
||||||
|
# Read sort_order from database record (fallback to loop index if unset)
|
||||||
|
raw_order = record.get("sort_order")
|
||||||
|
order_val = int(raw_order) if raw_order is not None else (idx + 1)
|
||||||
|
sort_order_str = f"{order_val:04d}" # Zero-padded integer string for exact sorting
|
||||||
|
|
||||||
# Sanitize text payloads for TTS engine
|
# Sanitize text payloads for TTS engine
|
||||||
en_tts_text = parse_text_for_edgetts(en_raw)
|
en_tts_text = parse_text_for_edgetts(en_raw)
|
||||||
es_tts_text = parse_text_for_edgetts(es_raw)
|
es_tts_text = parse_text_for_edgetts(es_raw)
|
||||||
|
|
@ -123,7 +130,7 @@ def compile_anki_package(records, output_path, deck_name):
|
||||||
# Clean sequential matching fields array matching schema mapping above
|
# Clean sequential matching fields array matching schema mapping above
|
||||||
note = genanki.Note(
|
note = genanki.Note(
|
||||||
model=anki_model,
|
model=anki_model,
|
||||||
fields=[en_raw, es_raw, anki_notes_html, en_audio_field, es_audio_field],
|
fields=[sort_order_str, en_raw, es_raw, anki_notes_html, en_audio_field, es_audio_field],
|
||||||
tags=note_tags
|
tags=note_tags
|
||||||
)
|
)
|
||||||
deck.add_note(note)
|
deck.add_note(note)
|
||||||
|
|
|
||||||
33
database.py
33
database.py
|
|
@ -69,7 +69,7 @@ def get_connection():
|
||||||
|
|
||||||
|
|
||||||
def ensure_database_populated():
|
def ensure_database_populated():
|
||||||
"""Initializes tables if running against a new or empty database file."""
|
"""Initializes tables and migrates schemas if running against a new or existing database file."""
|
||||||
conn = get_connection()
|
conn = get_connection()
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
try:
|
try:
|
||||||
|
|
@ -83,10 +83,17 @@ def ensure_database_populated():
|
||||||
tags TEXT,
|
tags TEXT,
|
||||||
notes TEXT,
|
notes TEXT,
|
||||||
anki_notes TEXT DEFAULT '',
|
anki_notes TEXT DEFAULT '',
|
||||||
gender TEXT DEFAULT 'Female'
|
gender TEXT DEFAULT 'Female',
|
||||||
|
sort_order INTEGER DEFAULT 0
|
||||||
);
|
);
|
||||||
""")
|
""")
|
||||||
|
|
||||||
|
# Safely migrate existing databases that do not yet have the sort_order column
|
||||||
|
cursor.execute("PRAGMA table_info(translations);")
|
||||||
|
columns = [row["name"] for row in cursor.fetchall()]
|
||||||
|
if "sort_order" not in columns:
|
||||||
|
cursor.execute("ALTER TABLE translations ADD COLUMN sort_order INTEGER DEFAULT 0;")
|
||||||
|
|
||||||
# 2. Key-value configuration table
|
# 2. Key-value configuration table
|
||||||
cursor.execute("""
|
cursor.execute("""
|
||||||
CREATE TABLE IF NOT EXISTS settings (
|
CREATE TABLE IF NOT EXISTS settings (
|
||||||
|
|
@ -141,7 +148,7 @@ def save_setting_to_db(key, value):
|
||||||
|
|
||||||
|
|
||||||
def get_all_translations_explicit():
|
def get_all_translations_explicit():
|
||||||
"""Retrieves all records using explicit, table-qualified column declarations."""
|
"""Retrieves all records using explicit, table-qualified column declarations sorted by sort_order."""
|
||||||
conn = get_connection()
|
conn = get_connection()
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
try:
|
try:
|
||||||
|
|
@ -154,9 +161,10 @@ def get_all_translations_explicit():
|
||||||
translations.tags,
|
translations.tags,
|
||||||
translations.notes,
|
translations.notes,
|
||||||
translations.anki_notes,
|
translations.anki_notes,
|
||||||
translations.gender
|
translations.gender,
|
||||||
|
translations.sort_order
|
||||||
FROM translations
|
FROM translations
|
||||||
ORDER BY translations.translation_id ASC;
|
ORDER BY translations.sort_order ASC, translations.translation_id ASC;
|
||||||
""")
|
""")
|
||||||
return [dict(row) for row in cursor.fetchall()]
|
return [dict(row) for row in cursor.fetchall()]
|
||||||
finally:
|
finally:
|
||||||
|
|
@ -178,7 +186,8 @@ def get_translation_by_id(translation_id):
|
||||||
translations.tags,
|
translations.tags,
|
||||||
translations.notes,
|
translations.notes,
|
||||||
translations.anki_notes,
|
translations.anki_notes,
|
||||||
translations.gender
|
translations.gender,
|
||||||
|
translations.sort_order
|
||||||
FROM translations
|
FROM translations
|
||||||
WHERE translations.translation_id = ?;
|
WHERE translations.translation_id = ?;
|
||||||
""",
|
""",
|
||||||
|
|
@ -199,6 +208,7 @@ def update_translation_record(
|
||||||
notes,
|
notes,
|
||||||
gender,
|
gender,
|
||||||
anki_notes,
|
anki_notes,
|
||||||
|
sort_order=0,
|
||||||
):
|
):
|
||||||
"""Saves interface edits directly back into the table using named arguments."""
|
"""Saves interface edits directly back into the table using named arguments."""
|
||||||
conn = get_connection()
|
conn = get_connection()
|
||||||
|
|
@ -214,7 +224,8 @@ def update_translation_record(
|
||||||
tags = :tags,
|
tags = :tags,
|
||||||
notes = :notes,
|
notes = :notes,
|
||||||
gender = :gender,
|
gender = :gender,
|
||||||
anki_notes = :anki
|
anki_notes = :anki,
|
||||||
|
sort_order = :sort_order
|
||||||
WHERE translation_id = :id;
|
WHERE translation_id = :id;
|
||||||
""",
|
""",
|
||||||
{
|
{
|
||||||
|
|
@ -226,6 +237,7 @@ def update_translation_record(
|
||||||
"notes": notes,
|
"notes": notes,
|
||||||
"gender": gender,
|
"gender": gender,
|
||||||
"anki": anki_notes,
|
"anki": anki_notes,
|
||||||
|
"sort_order": sort_order,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|
@ -251,7 +263,7 @@ def delete_translation_record(translation_id):
|
||||||
|
|
||||||
|
|
||||||
def insert_translation_record(
|
def insert_translation_record(
|
||||||
es_text, en_text, source_context, tags, notes, gender, anki_notes
|
es_text, en_text, source_context, tags, notes, gender, anki_notes, sort_order=0
|
||||||
):
|
):
|
||||||
"""Inserts a new record using named arguments."""
|
"""Inserts a new record using named arguments."""
|
||||||
conn = get_connection()
|
conn = get_connection()
|
||||||
|
|
@ -259,8 +271,8 @@ def insert_translation_record(
|
||||||
try:
|
try:
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
"""
|
"""
|
||||||
INSERT INTO translations (es_text, en_text, source_context, tags, notes, gender, anki_notes)
|
INSERT INTO translations (es_text, en_text, source_context, tags, notes, gender, anki_notes, sort_order)
|
||||||
VALUES (:es, :en, :ctx, :tags, :notes, :gender, :anki);
|
VALUES (:es, :en, :ctx, :tags, :notes, :gender, :anki, :sort_order);
|
||||||
""",
|
""",
|
||||||
{
|
{
|
||||||
"en": en_text,
|
"en": en_text,
|
||||||
|
|
@ -270,6 +282,7 @@ def insert_translation_record(
|
||||||
"notes": notes,
|
"notes": notes,
|
||||||
"anki": anki_notes,
|
"anki": anki_notes,
|
||||||
"gender": gender,
|
"gender": gender,
|
||||||
|
"sort_order": sort_order,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|
|
||||||
BIN
spanish_trainer-26-06-21.db
Normal file
BIN
spanish_trainer-26-06-21.db
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -6,8 +6,9 @@ import threading
|
||||||
import asyncio
|
import asyncio
|
||||||
from PyQt6.QtWidgets import (
|
from PyQt6.QtWidgets import (
|
||||||
QWidget, QVBoxLayout, QHBoxLayout, QLabel,
|
QWidget, QVBoxLayout, QHBoxLayout, QLabel,
|
||||||
QLineEdit, QPushButton, QTableWidget, QTableWidgetItem, \
|
QLineEdit, QPushButton, QTableWidget, QTableWidgetItem,
|
||||||
QHeaderView, QMessageBox, QFormLayout, QDialog, QTextEdit, QRadioButton, QButtonGroup
|
QHeaderView, QMessageBox, QFormLayout, QDialog, QTextEdit,
|
||||||
|
QRadioButton, QButtonGroup, QSpinBox
|
||||||
)
|
)
|
||||||
from PyQt6.QtCore import pyqtSignal, pyqtSlot, Qt
|
from PyQt6.QtCore import pyqtSignal, pyqtSlot, Qt
|
||||||
import edge_tts
|
import edge_tts
|
||||||
|
|
@ -119,6 +120,12 @@ class SandboxTab(QWidget):
|
||||||
self.txt_tags.setStyleSheet("padding: 6px; font-size: 14px;")
|
self.txt_tags.setStyleSheet("padding: 6px; font-size: 14px;")
|
||||||
self.txt_tags.textChanged.connect(self.apply_live_grid_filter)
|
self.txt_tags.textChanged.connect(self.apply_live_grid_filter)
|
||||||
|
|
||||||
|
# --- Sort Order Field (Added after Tags) ---
|
||||||
|
self.spn_sort_order = QSpinBox()
|
||||||
|
self.spn_sort_order.setRange(0, 999999)
|
||||||
|
self.spn_sort_order.setValue(0)
|
||||||
|
self.spn_sort_order.setStyleSheet("padding: 6px; font-size: 14px;")
|
||||||
|
|
||||||
# Modal Editor Row Buttons for Extended Notes
|
# Modal Editor Row Buttons for Extended Notes
|
||||||
editor_buttons_layout = QHBoxLayout()
|
editor_buttons_layout = QHBoxLayout()
|
||||||
self.btn_edit_notes = QPushButton("📝 Edit Notes Block")
|
self.btn_edit_notes = QPushButton("📝 Edit Notes Block")
|
||||||
|
|
@ -135,6 +142,7 @@ class SandboxTab(QWidget):
|
||||||
form_layout.addRow(QLabel("<b>Spanish Translation:</b>"), spanish_widget)
|
form_layout.addRow(QLabel("<b>Spanish Translation:</b>"), spanish_widget)
|
||||||
form_layout.addRow(QLabel("<b>Source Context:</b>"), self.txt_context)
|
form_layout.addRow(QLabel("<b>Source Context:</b>"), self.txt_context)
|
||||||
form_layout.addRow(QLabel("<b>Tags:</b>"), self.txt_tags)
|
form_layout.addRow(QLabel("<b>Tags:</b>"), self.txt_tags)
|
||||||
|
form_layout.addRow(QLabel("<b>Sort Order:</b>"), self.spn_sort_order)
|
||||||
form_layout.addRow(QLabel("<b>Extended Data Fields:</b>"), editor_buttons_layout)
|
form_layout.addRow(QLabel("<b>Extended Data Fields:</b>"), editor_buttons_layout)
|
||||||
|
|
||||||
main_layout.addWidget(form_container)
|
main_layout.addWidget(form_container)
|
||||||
|
|
@ -189,8 +197,8 @@ class SandboxTab(QWidget):
|
||||||
|
|
||||||
# --- VIEWPORT GRID TABLE ---
|
# --- VIEWPORT GRID TABLE ---
|
||||||
self.table = QTableWidget()
|
self.table = QTableWidget()
|
||||||
self.table.setColumnCount(6)
|
self.table.setColumnCount(7)
|
||||||
self.table.setHorizontalHeaderLabels(["ID", "English", "Spanish", "Context", "Tags", "Gender"])
|
self.table.setHorizontalHeaderLabels(["ID", "English", "Spanish", "Context", "Tags", "Sort Order", "Gender"])
|
||||||
self.table.setSelectionBehavior(QTableWidget.SelectionBehavior.SelectRows)
|
self.table.setSelectionBehavior(QTableWidget.SelectionBehavior.SelectRows)
|
||||||
self.table.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers)
|
self.table.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers)
|
||||||
self.table.cellClicked.connect(self.populate_form_from_grid)
|
self.table.cellClicked.connect(self.populate_form_from_grid)
|
||||||
|
|
@ -199,6 +207,7 @@ class SandboxTab(QWidget):
|
||||||
header.setSectionResizeMode(0, QHeaderView.ResizeMode.ResizeToContents)
|
header.setSectionResizeMode(0, QHeaderView.ResizeMode.ResizeToContents)
|
||||||
header.setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch)
|
header.setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch)
|
||||||
header.setSectionResizeMode(2, QHeaderView.ResizeMode.Stretch)
|
header.setSectionResizeMode(2, QHeaderView.ResizeMode.Stretch)
|
||||||
|
header.setSectionResizeMode(5, QHeaderView.ResizeMode.ResizeToContents)
|
||||||
|
|
||||||
main_layout.addWidget(self.table)
|
main_layout.addWidget(self.table)
|
||||||
self.reload_table_display()
|
self.reload_table_display()
|
||||||
|
|
@ -238,6 +247,7 @@ class SandboxTab(QWidget):
|
||||||
self.txt_spanish.clear()
|
self.txt_spanish.clear()
|
||||||
self.txt_context.clear()
|
self.txt_context.clear()
|
||||||
self.txt_tags.clear()
|
self.txt_tags.clear()
|
||||||
|
self.spn_sort_order.setValue(0)
|
||||||
self.current_notes_content = ""
|
self.current_notes_content = ""
|
||||||
self.current_anki_notes_content = ""
|
self.current_anki_notes_content = ""
|
||||||
self.rb_female.setChecked(True)
|
self.rb_female.setChecked(True)
|
||||||
|
|
@ -255,6 +265,7 @@ class SandboxTab(QWidget):
|
||||||
es_t = self.txt_spanish.text().strip()
|
es_t = self.txt_spanish.text().strip()
|
||||||
ctx_t = self.txt_context.text().strip()
|
ctx_t = self.txt_context.text().strip()
|
||||||
tag_t = self.txt_tags.text().strip()
|
tag_t = self.txt_tags.text().strip()
|
||||||
|
sort_val = self.spn_sort_order.value()
|
||||||
gender_t = "Male" if self.rb_male.isChecked() else "Female"
|
gender_t = "Male" if self.rb_male.isChecked() else "Female"
|
||||||
|
|
||||||
if not en_t or not es_t:
|
if not en_t or not es_t:
|
||||||
|
|
@ -262,9 +273,17 @@ class SandboxTab(QWidget):
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.selected_translation_id is None:
|
if self.selected_translation_id is None:
|
||||||
database.insert_translation_record(es_t, en_t, ctx_t, tag_t, self.current_notes_content, gender_t, self.current_anki_notes_content)
|
database.insert_translation_record(
|
||||||
|
es_t, en_t, ctx_t, tag_t,
|
||||||
|
self.current_notes_content, gender_t,
|
||||||
|
self.current_anki_notes_content, sort_val
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
database.update_translation_record(self.selected_translation_id, es_t, en_t, ctx_t, tag_t, self.current_notes_content, gender_t, self.current_anki_notes_content)
|
database.update_translation_record(
|
||||||
|
self.selected_translation_id, es_t, en_t, ctx_t, tag_t,
|
||||||
|
self.current_notes_content, gender_t,
|
||||||
|
self.current_anki_notes_content, sort_val
|
||||||
|
)
|
||||||
|
|
||||||
self.clear_form_fields()
|
self.clear_form_fields()
|
||||||
self.reload_table_display()
|
self.reload_table_display()
|
||||||
|
|
@ -303,6 +322,7 @@ class SandboxTab(QWidget):
|
||||||
self.txt_spanish.setText(record["es_text"])
|
self.txt_spanish.setText(record["es_text"])
|
||||||
self.txt_context.setText(record.get("source_context", ""))
|
self.txt_context.setText(record.get("source_context", ""))
|
||||||
self.txt_tags.setText(record.get("tags", ""))
|
self.txt_tags.setText(record.get("tags", ""))
|
||||||
|
self.spn_sort_order.setValue(record.get("sort_order", 0))
|
||||||
self.current_notes_content = record.get("notes", "")
|
self.current_notes_content = record.get("notes", "")
|
||||||
self.current_anki_notes_content = record.get("anki_notes", "")
|
self.current_anki_notes_content = record.get("anki_notes", "")
|
||||||
|
|
||||||
|
|
@ -343,7 +363,8 @@ class SandboxTab(QWidget):
|
||||||
self.table.setItem(visible_row_idx, 2, QTableWidgetItem(r["es_text"]))
|
self.table.setItem(visible_row_idx, 2, QTableWidgetItem(r["es_text"]))
|
||||||
self.table.setItem(visible_row_idx, 3, QTableWidgetItem(r.get("source_context", "")))
|
self.table.setItem(visible_row_idx, 3, QTableWidgetItem(r.get("source_context", "")))
|
||||||
self.table.setItem(visible_row_idx, 4, QTableWidgetItem(r.get("tags", "")))
|
self.table.setItem(visible_row_idx, 4, QTableWidgetItem(r.get("tags", "")))
|
||||||
self.table.setItem(visible_row_idx, 5, QTableWidgetItem(r.get("gender", "Female")))
|
self.table.setItem(visible_row_idx, 5, QTableWidgetItem(str(r.get("sort_order", 0))))
|
||||||
|
self.table.setItem(visible_row_idx, 6, QTableWidgetItem(r.get("gender", "Female")))
|
||||||
visible_row_idx += 1
|
visible_row_idx += 1
|
||||||
|
|
||||||
def _async_edge_speech_worker(self, text, voice, rate_modifier):
|
def _async_edge_speech_worker(self, text, voice, rate_modifier):
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue