Cookbook App Demo
This commit is contained in:
0
__init__.py
Normal file
0
__init__.py
Normal file
130
app.py
Normal file
130
app.py
Normal file
@@ -0,0 +1,130 @@
|
||||
import randomname
|
||||
|
||||
from textual import on, work
|
||||
from textual.app import App, ComposeResult
|
||||
from textual.events import Click
|
||||
from textual.screen import Screen
|
||||
from textual.message import Message
|
||||
from textual.css.query import NoMatches
|
||||
from textual.reactive import reactive
|
||||
from textual.containers import (
|
||||
Container,
|
||||
Center,
|
||||
Horizontal,
|
||||
VerticalScroll
|
||||
)
|
||||
from textual.widgets import (
|
||||
Button,
|
||||
Footer,
|
||||
Input,
|
||||
Label,
|
||||
Rule,
|
||||
Static
|
||||
)
|
||||
|
||||
class CardSelected(Message):
|
||||
def __init__(self, card_id: int) -> None:
|
||||
super().__init__()
|
||||
self.card_id = card_id
|
||||
|
||||
|
||||
class RecipeCard(Static):
|
||||
visible_status: bool = reactive(True)
|
||||
|
||||
def __init__(self, recipe_data: dict = {}):
|
||||
super().__init__()
|
||||
self.recipe_name = recipe_data.get('recipe_name', "<no name>")
|
||||
self.card_id = recipe_data.get('card_id', 0)
|
||||
self.border_title = f"{self.recipe_name} - {self.card_id}"
|
||||
|
||||
try:
|
||||
self.id = f"recipe-{self.card_id}"
|
||||
except:
|
||||
pass
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Label("Cook Time: 10m", classes="card")
|
||||
yield Rule(line_style="solid", classes="rule-color")
|
||||
yield Label("Description", classes="recipecard-label")
|
||||
yield Static(
|
||||
"This dish is full of nonsense and this is a long text label",
|
||||
classes="card"
|
||||
)
|
||||
|
||||
def on_click(self, event: Click) -> None:
|
||||
self.post_message(CardSelected(self.id))
|
||||
|
||||
def watch_visible_status(self, visible: bool) -> None:
|
||||
if visible:
|
||||
print(f"Card {self.id} is visible!")
|
||||
self.remove_class('invisible')
|
||||
else:
|
||||
print(f"Card {self.id} is invisible!")
|
||||
self.add_class('invisible')
|
||||
|
||||
|
||||
class SearchPageScreen(Screen):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.selected_card_id = None
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
with Container(id="background-container"):
|
||||
with Center(id="header"):
|
||||
yield Horizontal(
|
||||
Button("Back", id="button-go-back"),
|
||||
Input(placeholder="Search a recipe..", id="search-input"),
|
||||
Button("Search Recipes", id="enter-button"),
|
||||
id="header-components"
|
||||
)
|
||||
with VerticalScroll(id="recipe-list"):
|
||||
for x in range(1, 21):
|
||||
yield RecipeCard(
|
||||
recipe_data={
|
||||
"recipe_name": randomname.generate(sep=" "),
|
||||
"card_id": x
|
||||
}
|
||||
)
|
||||
yield Footer()
|
||||
|
||||
@on(CardSelected)
|
||||
def card_selected(self, message: CardSelected) -> None:
|
||||
card_id = message.card_id
|
||||
card = self.query_one(f"#{card_id}", RecipeCard)
|
||||
if card_id == self.selected_card_id:
|
||||
card.remove_class("recipe-highlighted")
|
||||
self.selected_card_id = None
|
||||
else:
|
||||
try:
|
||||
old_card = self.query_one(f"#{self.selected_card_id}", RecipeCard)
|
||||
old_card.remove_class("recipe-highlighted")
|
||||
except NoMatches:
|
||||
print(f"Card not fount {self.selected_card_id}")
|
||||
self.selected_card_id = card_id
|
||||
card.add_class('recipe-highlighted')
|
||||
|
||||
@on(Input.Changed)
|
||||
def filter_cards(self, event: Input.Changed) -> None:
|
||||
query = event.value.strip().lower()
|
||||
for card in self.query(RecipeCard):
|
||||
if query in card.recipe_name:
|
||||
card.visible_status = True
|
||||
else:
|
||||
card.visible_status = False
|
||||
self.refresh()
|
||||
|
||||
|
||||
class CookbookApp(App):
|
||||
CSS_PATH = "cookbook.css"
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.theme = "monokai"
|
||||
|
||||
def on_mount(self) -> None:
|
||||
self.push_screen(SearchPageScreen())
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = CookbookApp()
|
||||
app.run()
|
||||
|
||||
58
cookbook.css
Normal file
58
cookbook.css
Normal file
@@ -0,0 +1,58 @@
|
||||
#background-container {
|
||||
background: $background;
|
||||
}
|
||||
|
||||
#header {
|
||||
width: 100%;
|
||||
background: $warning;
|
||||
border: thick $warning-darken-1;
|
||||
padding: 0 0;
|
||||
}
|
||||
|
||||
#header-components {
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
#search-input {
|
||||
width: 50;
|
||||
}
|
||||
|
||||
#recipe-list {
|
||||
layout: grid;
|
||||
grid-size: 3;
|
||||
grid-gutter: 1;
|
||||
grid-rows: 15;
|
||||
margin: 1 0;
|
||||
|
||||
}
|
||||
|
||||
RecipeCard {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
border: thick $accent;
|
||||
background: $primary-background;
|
||||
margin: 0 1;
|
||||
}
|
||||
|
||||
.invisible {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
|
||||
.recipecard-label {
|
||||
text-style: bold italic underline;
|
||||
padding: 0 0 1 1;
|
||||
}
|
||||
|
||||
.rule-color {
|
||||
color: $accent;
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 0 2;
|
||||
}
|
||||
|
||||
.recipe-highlighted {
|
||||
background: $primary-background-lighten-2;
|
||||
}
|
||||
Reference in New Issue
Block a user