summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.dockerignore2
-rw-r--r--Dockerfile5
-rw-r--r--Makefile17
-rw-r--r--README.md29
-rwxr-xr-xbootstrap-venv.sh5
-rw-r--r--docker-compose.yml12
-rw-r--r--flashcards-project/src/flashcards/scheduler_brutal.py2
-rw-r--r--gui-project/cards.py17
-rw-r--r--gui-project/main.py2
-rw-r--r--gui-project/main_ui.py82
-rw-r--r--pre-commit4
-rw-r--r--tests/scheduler_brutal_unittest.py2
-rw-r--r--tests/session_integrationtest.py2
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):