diff options
-rw-r--r-- | .dockerignore | 2 | ||||
-rw-r--r-- | Dockerfile | 5 | ||||
-rw-r--r-- | Makefile | 17 | ||||
-rw-r--r-- | README.md | 29 | ||||
-rwxr-xr-x | bootstrap-venv.sh | 5 | ||||
-rw-r--r-- | docker-compose.yml | 12 | ||||
-rw-r--r-- | flashcards-project/src/flashcards/scheduler_brutal.py | 2 | ||||
-rw-r--r-- | gui-project/cards.py | 17 | ||||
-rw-r--r-- | gui-project/main.py | 2 | ||||
-rw-r--r-- | gui-project/main_ui.py | 82 | ||||
-rw-r--r-- | pre-commit | 4 | ||||
-rw-r--r-- | tests/scheduler_brutal_unittest.py | 2 | ||||
-rw-r--r-- | tests/session_integrationtest.py | 2 |
13 files changed, 133 insertions, 48 deletions
diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..7c65c6e --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +venv +tests diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..1e4e089 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,5 @@ +FROM python:3.12.7-bookworm +WORKDIR /app +COPY . . +RUN pip install -r requirements.txt +ENTRYPOINT ["python3", "gui-project/main.py"] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..0f277da --- /dev/null +++ b/Makefile @@ -0,0 +1,17 @@ +test: all + ./venv/bin/pytest tests/* + +all: venv .git/hooks/pre-commit + +clean: + rm -rf venv + +.git/hooks/pre-commit: pre-commit + install -m 755 pre-commit .git/hooks/pre-commit + +venv: requirements.txt + rm -rf venv + python -m venv venv + ./venv/bin/pip install -r requirements.txt + +.PHONY: all test clean 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/bootstrap-venv.sh b/bootstrap-venv.sh deleted file mode 100755 index f3c5580..0000000 --- a/bootstrap-venv.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/zsh - -rm -r venv -python -m venv venv -./venv/bin/pip install -r requirements.txt diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..e1339bb --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,12 @@ +version: '3.1' +services: + food-planner: + container_name: flashcards + image: local/flashcards + restart: unless-stopped + user: "$SERVICE_UID:$SERVICE_GID" + build: . + volumes: + - .:/app + - ./schwiizerduutsch:/app/schwiizerduutsch + - $SERVICE_ROOT/data:/data diff --git a/flashcards-project/src/flashcards/scheduler_brutal.py b/flashcards-project/src/flashcards/scheduler_brutal.py index f2a00c2..f98729c 100644 --- a/flashcards-project/src/flashcards/scheduler_brutal.py +++ b/flashcards-project/src/flashcards/scheduler_brutal.py @@ -59,7 +59,7 @@ class SchedulerBrutal(Scheduler): """ Exposure index is a measure of how much and how recently a card has been shown """ - return sum([i + 1 for i, h in enumerate(history) if h is not None]) + return sum([pow(i + 1, 2) for i, h in enumerate(history) if h is not None]) def _schedule(self, size: int) -> list[str]: weights = range(10, 10 + HISTORY_DEPTH) diff --git a/gui-project/cards.py b/gui-project/cards.py index 42a08b0..ededff9 100644 --- a/gui-project/cards.py +++ b/gui-project/cards.py @@ -19,16 +19,13 @@ class CardComponent(): def get_back(self): return self.back - -# ui.colors(back="#c4bcdb") - - 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: @@ -38,19 +35,19 @@ class CardUI(): def init_front(self): with self.row_parent: - with ui.card().classes('bg-frontc') as self.front: + with ui.card().classes('bg-frontc w-[600px] mx-auto') as self.front: ui.markdown("**Front**") ui.separator() - ui.markdown(self.card.get_front()) + 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') as self.back: + with ui.card().classes('bg-back w-[600px] mx-auto') as self.back: ui.markdown("_Back_") ui.separator() - ui.markdown(self.card.get_back()) + 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()) @@ -65,15 +62,13 @@ class CardUI(): self.back.set_visibility(True) def revert_card(self): - ui.notify("Showing back") self.show_back() def user_clicked_correct(self): - ui.notify("You got this") + self.correctly_answered = True self.is_resolved.set() def user_clicked_incorrect(self): - ui.notify("Keep at it!") self.is_resolved.set() async def is_answered(self): diff --git a/gui-project/main.py b/gui-project/main.py index d98b2c7..4a9e159 100644 --- a/gui-project/main.py +++ b/gui-project/main.py @@ -1,8 +1,6 @@ from nicegui import ui from main_ui import MainUI -FLASHCARDS_ROOT="/home/andreear/git/schwiizertuutsch/flashcards" - @ui.page("/") def main_page(): MainUI() diff --git a/gui-project/main_ui.py b/gui-project/main_ui.py index f6351b0..6bda821 100644 --- a/gui-project/main_ui.py +++ b/gui-project/main_ui.py @@ -3,8 +3,11 @@ import os import random import sys from cards import CardUI, CardComponent -from flashcards import Session, SCHEDULERS -FLASHCARDS_ROOT="/home/andreear/git/schwiizertuutsch/flashcards" +from flashcards import Session + +FLASHCARDS_ROOT="/app/schwiizertuutsch/flashcards" +STATE_FILE="/app/state.txt" +# FLASHCARDS_ROOT="/home/andreea/git/schwiizertuutsch/flashcards" class MainUI(): def __init__(self): @@ -16,7 +19,7 @@ class MainUI(): # ========= start initializing UI =========== with ui.header() as self.header: - self.header_element = ui.label("Session Configuration") + 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: @@ -44,15 +47,14 @@ class MainUI(): 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") - + 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) @@ -68,29 +70,55 @@ class MainUI(): async def start_session(self): ui.notify("Started session") - print("My config") + # initialize cards paths cards_paths = [os.path.join(FLASHCARDS_ROOT, x) for x in self.fcards] - session = Session("brutal", cards_paths, - "/home/andreear/git/flashcards/state.txt") - - ui.colors(frontc='#bcdbc6', back="#c4bcdb") - for i, card in enumerate(session.practice(self.nb_questions)): - 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) + # 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: - card_component = CardComponent(card.back, card.front) + 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 - card_ui = CardUI(self.root_ui, card_component) - card_ui.show_front() - await card_ui.is_answered() - card_ui.hide_card() diff --git a/pre-commit b/pre-commit new file mode 100644 index 0000000..668071c --- /dev/null +++ b/pre-commit @@ -0,0 +1,4 @@ +#!/bin/sh + +make test +exit $? diff --git a/tests/scheduler_brutal_unittest.py b/tests/scheduler_brutal_unittest.py index a87e5e9..4790016 100644 --- a/tests/scheduler_brutal_unittest.py +++ b/tests/scheduler_brutal_unittest.py @@ -26,7 +26,7 @@ def test_scheduling(): "9": [None, None, None], } - expected_priority = ["9", "6", "5", "7", "8", "4", "1", "3", "2", "0"] + expected_priority = ["9", "6", "5", "8", "7", "4", "1", "3", "2", "0"] uut = UUT(cards, state) diff --git a/tests/session_integrationtest.py b/tests/session_integrationtest.py index ddabff9..267e374 100644 --- a/tests/session_integrationtest.py +++ b/tests/session_integrationtest.py @@ -1,7 +1,7 @@ import pytest import json -from flashcards.session import Session +from flashcards import Session @pytest.fixture def cardFiles(tmp_path): |