Skip to content

Commit

Permalink
Merge pull request #28 from EnhancedJax/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
EnhancedJax authored Jan 10, 2025
2 parents c0a5798 + feab3b6 commit 7a14694
Show file tree
Hide file tree
Showing 22 changed files with 313 additions and 131 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## 0.2.3

- Added amounts in insight bar legends
- Autocomplete to use templates in label field
- Shift enter to create templates along with a non-transfer record
- Auto update checks
- Fixed: Inconsistent tab behaviors

## 0.2.2

- Fix: remove obscure dependencies
Expand Down
4 changes: 3 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "Bagels"
version = "0.2.2"
version = "0.2.3"
authors = [
{ name = "Jax", email = "[email protected]" }
]
Expand Down Expand Up @@ -36,6 +36,8 @@ dependencies = [
"pydantic==2.9.2",
"pygments==2.18.0",
"pyyaml==6.0.2",
"requests==2.28.0",
"packaging==23.0",
"rich==13.9.3",
"sqlalchemy==2.0.36",
"textual==0.86.1",
Expand Down
24 changes: 24 additions & 0 deletions src/bagels/__main__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
from pathlib import Path
from time import sleep


# from venv import create

import click

from bagels.locations import config_file, database_file, set_custom_root
from bagels.versioning import get_current_version, get_pypi_version, needs_update


@click.group(invoke_without_command=True)
Expand All @@ -24,6 +27,27 @@ def cli(ctx, at: click.Path | None):

load_config()

from bagels.config import CONFIG

if CONFIG.state.check_for_updates:
if needs_update():
new = get_pypi_version()
cur = get_current_version()
click.echo(
click.style(
f"New version available ({cur} -> {new})! Update with:",
fg="yellow",
)
)
click.echo(click.style("```uv tools upgrade bagels```", fg="cyan"))
click.echo(
click.style(
"You can disable this check in-app using the command palette.",
fg="bright_black",
)
)
sleep(2)

from bagels.models.database.app import init_db

init_db()
Expand Down
30 changes: 18 additions & 12 deletions src/bagels/components/autocomplete.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import annotations

from dataclasses import dataclass
from typing import Callable, ClassVar, Iterable, Mapping, cast
from typing import Callable, ClassVar, Iterable, Literal, Mapping, cast

from rich.console import Console, ConsoleOptions, RenderableType, RenderResult
from rich.style import Style
Expand Down Expand Up @@ -128,19 +128,15 @@ class InputState:
cursor_position: int


CompletionStrategy = (
"Literal['append', 'replace', 'insert'] | Callable[[str, InputState], InputState]"
)


class AutoComplete(Widget):

def __init__(
self,
input: Input,
dropdown: Dropdown,
tab_moves_focus: bool = False,
completion_strategy: CompletionStrategy = "replace",
tab_moves_focus: bool = True,
completion_strategy: "Literal['append', 'replace', 'insert'] | Callable[[str, InputState], InputState]" = "replace",
backspace_clears: bool = True,
create_action: Callable[[str], None] | None = None,
*,
id: str | None = None,
Expand Down Expand Up @@ -173,6 +169,7 @@ def __init__(
self.tab_moves_focus = tab_moves_focus
self.completion_strategy = completion_strategy
self.create_action = create_action
self.backspace_clears = backspace_clears

def compose(self) -> ComposeResult:
yield self.input
Expand Down Expand Up @@ -206,7 +203,10 @@ def on_key(self, event: events.Key) -> None:
if not self.tab_moves_focus:
event.stop() # Prevent focus change
elif key == "backspace":
self.input.action_delete_left_all()
if self.backspace_clears:
self.input.action_delete_left_all()
else:
self.input.action_delete_left()

# def on_input_submitted(self, event: Input.Submitted) -> None:
# event.prevent_default()
Expand Down Expand Up @@ -242,12 +242,15 @@ def _select_item(self):
self.input.cursor_position = new_state.cursor_position

self.dropdown.display = False
self.post_message(self.Selected(item=self.dropdown.selected_item))
self.post_message(
self.Selected(item=self.dropdown.selected_item, input=self.input)
)

class Selected(Message):
def __init__(self, item: DropdownItem):
def __init__(self, item: DropdownItem, input: Input):
super().__init__()
self.item = item
self.input = input

class Created(Message):
def __init__(self, item: DropdownItem, input: Input):
Expand Down Expand Up @@ -306,6 +309,7 @@ def __init__(
items: list[DropdownItem] | Callable[[InputState], list[DropdownItem]],
show_on_focus: bool = True,
create_option: bool = False,
show_when_empty: bool = True,
id: str | None = None,
classes: str | None = None,
):
Expand All @@ -328,6 +332,7 @@ def __init__(
self.input_widget: Input
self.show_on_focus = show_on_focus
self.create_option = create_option
self.show_when_empty = show_when_empty

def compose(self) -> ComposeResult:
self.child = DropdownChild(self.input_widget)
Expand Down Expand Up @@ -419,7 +424,8 @@ def sync_state(self, value: str, input_cursor_position: int) -> None:
# Casting to Text, since we convert to Text object in
# the __post_init__ of DropdownItem.
text = cast(Text, item.main)
if value.lower() in text.plain.lower():
should_show = self.show_when_empty or value != ""
if should_show and value.lower() in text.plain.lower():
matches.append(
DropdownItem(
left_meta=cast(Text, item.left_meta).copy(),
Expand Down
16 changes: 15 additions & 1 deletion src/bagels/components/fields.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from textual.app import ComposeResult
from textual.binding import Binding
from textual.containers import Container
from textual.widgets import Input, Label, Static, Switch

Expand Down Expand Up @@ -28,6 +29,11 @@ def compose(self) -> ComposeResult:
class Field(Static):
"""Individual form field that can be text, number, boolean, or autocomplete"""

BINDINGS = [
Binding("tab", "focus_next", "Focus next", False),
Binding("shift+tab", "focus_prev", "Focus previous", False),
]

def __init__(self, field: FormField):
super().__init__()
self.field = field
Expand All @@ -52,7 +58,7 @@ def __init__(self, field: FormField):

def on_auto_complete_selected(self, event: AutoComplete.Selected) -> None:
"""Handle autocomplete selection"""
self.screen.focus_next()
# self.screen.focus_next()

# Find matching option and set held value
for item in self.field.options.items:
Expand All @@ -67,6 +73,12 @@ def on_input_changed(self, event: Input.Changed):
num_val = parse_formula_expression(event.value)
self.query_one(".label").update(f"{self.field.title} - {num_val}")

def action_focus_next(self):
self.screen.focus_next()

def action_focus_prev(self):
self.screen.focus_previous()

def compose(self) -> ComposeResult:
if self.field.type == "hidden":
# Hidden fields just need a static widget to hold the value
Expand Down Expand Up @@ -94,13 +106,15 @@ def compose(self) -> ComposeResult:
show_on_focus=True,
id=f"dropdown-{self.field.key}",
create_option=self.field.create_action,
show_when_empty=self.field.autocomplete_selector,
)

yield AutoComplete(
self.input,
dropdown,
classes="field-autocomplete",
create_action=self.field.create_action,
backspace_clears=self.field.autocomplete_selector,
)

elif self.field.type == "boolean":
Expand Down
10 changes: 5 additions & 5 deletions src/bagels/components/modules/insights.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ def rebuild(self) -> None:
self.use_account = self.page_parent.filter["byAccount"]
period_net = self._update_labels()
items = self.get_percentage_bar_items(period_net)
self.percentage_bar.set_total(period_net, False)
self.percentage_bar.set_items(items)
data = self.get_period_barchart_data()
self.period_barchart.set_data(data)
Expand Down Expand Up @@ -112,7 +113,7 @@ def get_percentage_bar_items(
items.append(
PercentageBarItem(
name=category.name,
count=int(category.amount / period_net * 100),
count=int(category.amount),
color=category.color,
)
)
Expand All @@ -122,16 +123,15 @@ def get_percentage_bar_items(
items.append(
PercentageBarItem(
name=category.name,
count=int(category.amount / period_net * 100),
count=int(category.amount),
color=category.color,
)
)

# Sum up the amounts of remaining categories
others_amount = sum(cat.amount for cat in category_records[limit:])
others_percentage = int(others_amount / period_net * 100)
others_amount = int(sum(cat.amount for cat in category_records[limit:]))
items.append(
PercentageBarItem(name="Others", count=others_percentage, color="white")
PercentageBarItem(name="Others", count=others_amount, color="white")
)

return items
Expand Down
12 changes: 6 additions & 6 deletions src/bagels/components/modules/records.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from textual.widgets import Button
from bagels.components.datatable import DataTable
from bagels.components.indicators import EmptyIndicator
from bagels.managers.record_templates import create_template_from_record
from bagels.modals.confirmation import ConfirmationModal
from bagels.modals.record import RecordModal
from bagels.modals.transfer import TransferModal
Expand Down Expand Up @@ -73,7 +74,6 @@ def __init__(self, parent: Static, *args, **kwargs) -> None:
)
super().__setattr__("border_title", "Records")
self.page_parent = parent
self.record_form = RecordForm()
self.person_form = PersonForm()

def on_mount(self) -> None:
Expand Down Expand Up @@ -407,14 +407,16 @@ def check_result(result) -> None:
if result:
try:
create_record_and_splits(result["record"], result["splits"])
if result["createTemplate"]:
create_template_from_record(result["record"])
except Exception as e:
self.app.notify(
title="Error", message=f"{e}", severity="error", timeout=10
)
else:
self.app.notify(
title="Success",
message=f"Record created",
message=f"Record created {"and template created" if result["createTemplate"] else ""}",
severity="information",
timeout=3,
)
Expand All @@ -426,7 +428,7 @@ def check_result(result) -> None:
self.app.push_screen(
RecordModal(
f"New {type} on {account_name} for {date}",
form=self.record_form.get_form(self.page_parent.mode),
form=RecordForm().get_form(self.page_parent.mode),
splitForm=Form(),
date=self.page_parent.mode["date"],
),
Expand Down Expand Up @@ -514,9 +516,7 @@ def check_result_person(result) -> None:
callback=check_result_records,
)
else:
filled_form, filled_splits = self.record_form.get_filled_form(
record.id
)
filled_form, filled_splits = RecordForm().get_filled_form(record.id)
self.app.push_screen(
RecordModal(
"Edit Record",
Expand Down
13 changes: 9 additions & 4 deletions src/bagels/components/percentage_bar.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ class PercentageBar(Static):
"""

items: list[PercentageBarItem] = []
total: int = 0

def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
Expand All @@ -70,6 +71,11 @@ def __init__(self, *args, **kwargs) -> None:
def on_mount(self) -> None:
self.rebuild()

def set_total(self, total: int, rebuild: bool = False) -> None:
self.total = total
if rebuild:
self.rebuild()

def set_items(self, items: list[PercentageBarItem]) -> None:
self.items = items
self.rebuild()
Expand Down Expand Up @@ -104,7 +110,6 @@ def rebuild(self) -> None:
for i in range(to_remove_count):
labels[i + items_count].remove()
# we calculate the appropriate width for each item, with last item taking remaining space
total = sum(item.count for item in self.items)
for i, item in enumerate(self.items):
item_widget = Static(" ", classes="bar-item")
color = item.color
Expand All @@ -116,20 +121,20 @@ def rebuild(self) -> None:
if i == len(self.items) - 1:
self.bar_end.styles.color = background_color
# calculate percentage
percentage = round((item.count / total) * 100)
percentage = round((item.count / self.total) * 100)
if (
i + 1 > labels_count
): # if we have more items than labels, we create a new label
label_widget = Container(
Label(f"[{color}]●[/{color}] {item.name}", classes="name"),
Label(f"{percentage}%", classes="percentage"),
Label(f"{percentage}% ({item.count})", classes="percentage"),
classes="bar-label",
)
self.labels_container.mount(label_widget)
else:
label = labels[i]
label.query_one(".name").update(f"[{color}]●[/{color}] {item.name}")
label.query_one(".percentage").update(f"{percentage}%")
label.query_one(".percentage").update(f"{percentage}% ({item.count})")

width = f"{percentage}%"
if i == len(self.items) - 1:
Expand Down
1 change: 1 addition & 0 deletions src/bagels/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ class Symbols(BaseModel):

class State(BaseModel):
theme: str = "dark"
check_for_updates: bool = True


class Config(BaseModel):
Expand Down
Loading

0 comments on commit 7a14694

Please sign in to comment.