diff options
-rw-r--r-- | README.md | 29 | ||||
-rw-r--r-- | gui-project/cards.py | 78 | ||||
-rw-r--r-- | gui-project/main.py | 10 | ||||
-rw-r--r-- | gui-project/main_ui.py | 123 | ||||
-rw-r--r-- | requirements.txt | 1 |
5 files changed, 241 insertions, 0 deletions
diff --git a/README.md b/README.md new file mode 100644 index 0000000..1404c25 --- /dev/null +++ b/README.md @@ -0,0 +1,29 @@ +## Overview + +Repository containing implementation for flashcard-based applications. + +### Set up + +- Need to have `python3.12` preinstalled +- Run `./bootstrap-venv.sh` to create the environment and install the packages +- Need to have flashcard files (can clone the schwiizertuutsch repo for this): `git clone git.0xf7.com:schwiizertuutsch` + +### Run applications + +#### Running cli-based flashcards + +Call `./venv/bin/python cli-project/flashcard_cli.py practice <STATE_FILE> <PATHS_TO_FLASHCARDS>` to start practicing + +> STATE_FILE - is a txt file where the progression state is kept - for a more efficient learning with the flashcards, based on how often you got them right or wrong + +> PATH_TO_FLASHCARDS - paths to the `.fcard` files containing the flashcards contents + + +#### Running GUI application + +To run the GUI application, you need to update paths in `gui-project/main_ui.py`: + - `FLASHCARDS_ROOT` = path to folder containing `.fcard` files + - `STATE_FILE` = path to file for storing and using the state + + Then run: + `./venv/bin/python gui-project/main.py` diff --git a/gui-project/cards.py b/gui-project/cards.py new file mode 100644 index 0000000..ededff9 --- /dev/null +++ b/gui-project/cards.py @@ -0,0 +1,78 @@ +import asyncio + +from nicegui import ui + +class CardComponent(): + def __init__(self, front : str, back : str): + if front is None or front == "": + raise Exception("Cannot have empty front on the card") + + if back is None or back == "": + raise Exception("Cannot have empty back on the card") + + self.front = front + self.back = back + + def get_front(self): + return self.front + + def get_back(self): + return self.back + +class CardUI(): + def __init__(self, parent_ui, card : CardComponent): + self.parent_ui = parent_ui + self.card = card + self.is_resolved = asyncio.Event() + self.init_card() + self.correctly_answered = False + + def init_card(self): + with self.parent_ui: + self.row_parent = ui.row() + self.init_front() + self.init_back() + + def init_front(self): + with self.row_parent: + with ui.card().classes('bg-frontc w-[600px] mx-auto') as self.front: + ui.markdown("**Front**") + ui.separator() + ui.markdown(self.card.get_front()).style('white-space: pre-wrap') + ui.button("Revert", on_click=lambda: self.revert_card()) + self.front.set_visibility(False) + + def init_back(self): + with self.row_parent: + with ui.card().classes('bg-back w-[600px] mx-auto') as self.back: + ui.markdown("_Back_") + ui.separator() + ui.markdown(self.card.get_back()).style('white-space: pre-wrap') + with ui.button_group(): + ui.button("Got it", on_click=lambda: self.user_clicked_correct()) + ui.button("Ain't got it", on_click=lambda: self.user_clicked_incorrect()) + self.back.set_visibility(False) + + def show_front(self): + self.back.set_visibility(False) + self.front.set_visibility(True) + + def show_back(self): + self.front.set_visibility(False) + self.back.set_visibility(True) + + def revert_card(self): + self.show_back() + + def user_clicked_correct(self): + self.correctly_answered = True + self.is_resolved.set() + + def user_clicked_incorrect(self): + self.is_resolved.set() + + async def is_answered(self): + await self.is_resolved.wait() + + def hide_card(self): + self.row_parent.delete() diff --git a/gui-project/main.py b/gui-project/main.py new file mode 100644 index 0000000..d98b2c7 --- /dev/null +++ b/gui-project/main.py @@ -0,0 +1,10 @@ +from nicegui import ui +from main_ui import MainUI + +FLASHCARDS_ROOT="/home/andreear/git/schwiizertuutsch/flashcards" + +@ui.page("/") +def main_page(): + MainUI() + +ui.run(port=3011)
\ No newline at end of file diff --git a/gui-project/main_ui.py b/gui-project/main_ui.py new file mode 100644 index 0000000..fcb1f5a --- /dev/null +++ b/gui-project/main_ui.py @@ -0,0 +1,123 @@ +from nicegui import ui +import os +import random +import sys +from cards import CardUI, CardComponent +from flashcards import Session + +FLASHCARDS_ROOT="/home/andreear/git/schwiizertuutsch/flashcards" +STATE_FILE="/home/andreear/git/flashcards/state.txt" + +class MainUI(): + def __init__(self): + # ===== default value for a session ======== + self.nb_questions = 1 + self.prompt_type = "Front" + self.fcards = ["saetze-allgemein.fcard"] + self.run_mode = "practice" + + # ========= start initializing UI =========== + with ui.header() as self.header: + self.header_element = ui.markdown("Session Configuration") + self.root_ui = ui.row().classes("fixed-center") + with self.root_ui: + with ui.card(align_items="baseline") as self.session_configuration: + self.initialize_session_ui() + + def initialize_session_ui(self): + all_fcards = os.listdir(FLASHCARDS_ROOT) + + with ui.row(align_items="center"): + ui.label("Number of questions:") + self.nb_questions_ui = ui.number(placeholder='e.g.: 20', min=1, step=1, precision=0, value=15).props( + 'rounded outlined dense') + with ui.row(align_items="center"): + ui.label("Prompt type:") + self.prompt_sel_ui = ui.select(["Front", "Back", "Random"], value="Back") + with ui.row(align_items="center"): + ui.label("Select cards to play from:") + self.cards_sel_ui = ui.select(all_fcards, multiple=True, value=all_fcards[:2]) \ + .classes('w-64').props('use-chips') + with ui.button_group(): + ui.button("Practice mode", color="positive", + on_click=lambda: self.prepare_session("practice")) + ui.button("Test mode", color="negative", + on_click=lambda: self.prepare_session("test")) + + async def prepare_session(self, run_mode : str): + self.header.clear() + self.run_mode = run_mode + if run_mode == "practice": + self.header.classes("bg-positive") + else: + self.header.classes("bg-negative") + + with self.header: + self.header_element = ui.markdown(f"Currently in **{run_mode}** mode\nQuestion 1/{self.nb_questions_ui.value}") + self.run_mode = run_mode + # read values from UI and set session configuration + self.nb_questions = int(self.nb_questions_ui.value) + self.prompt_type = self.prompt_sel_ui.value + self.fcards = self.cards_sel_ui.value + + assert type(self.fcards) is list + assert type(self.nb_questions) is int + assert type(self.prompt_type) is str + + self.session_configuration.set_visibility(False) + await self.start_session() + + async def start_session(self): + ui.notify("Started session") + # initialize cards paths + cards_paths = [os.path.join(FLASHCARDS_ROOT, x) for x in self.fcards] + # initialize session + session = Session("brutal", cards_paths, STATE_FILE) + # initialize final score + final_score = 0 + # set ui.colors to be used + ui.colors(frontc='#bcdbc6', back="#c4bcdb", final="#9cfcfc") + + # Start session + if self.run_mode == "practice": + for i, card in enumerate(session.practice(self.nb_questions)): + correctly_answered = await self.showCard(i, card) + final_score += 1 if correctly_answered else 0 + elif self.run_mode == "test": + for i, (card, correct) in enumerate(session.test(self.nb_questions)): + correctly_answered = await self.showCard(i, card) + correct(correctly_answered) + final_score += 1 if correctly_answered else 0 + + # Wrap up session + with self.root_ui: + with ui.card(align_items="center").classes("bg-final"): + ui.markdown("## Finished") + ui.label(f"Your final score is: {final_score}/{self.nb_questions}") + ui.label("To start again, refresh the page") + + async def showCard(self, idx : int, card): + self.header_element.set_content( + f"Currently in **{self.run_mode}** mode\n\nQuestion {idx + 1}/{int(self.nb_questions_ui.value)}") + prompt_type_binary = self.prompt_type.lower() + if self.prompt_type.lower() == "random": + random_nb = random.randint(0, sys.maxsize) + if random_nb % 2 == 0: + prompt_type_binary = "front" + else: + prompt_type_binary = "back" + + if prompt_type_binary == "front": + card_component = CardComponent(card.front, card.back) + else: + card_component = CardComponent(card.back, card.front) + + card_ui = CardUI(self.root_ui, card_component) + + card_ui.show_front() + await card_ui.is_answered() + card_ui.hide_card() + return card_ui.correctly_answered + + + diff --git a/requirements.txt b/requirements.txt index 17a9a3d..efd1b01 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ click pytest +nicegui -e flashcards-project -e cli-project |