we have a very basic search system working

This commit is contained in:
2026-02-04 19:30:41 +11:00
parent 33ce1aac67
commit de101e8dfb
6 changed files with 213 additions and 0 deletions

6
banner.txt Normal file
View File

@@ -0,0 +1,6 @@
______ _ _______ __ __ __
/_ __/_ __(_) ____(_) /_/ /_ __ __/ /_
/ / / / / / / / __/ / __/ __ \/ / / / __ \
/ / / /_/ / / /_/ / / /_/ / / / /_/ / /_/ /
/_/ \__,_/_/\____/_/\__/_/ /_/\__,_/_.___/

15
main.py Normal file
View File

@@ -0,0 +1,15 @@
from textual.app import App, ComposeResult
from screens.welcome_screen import WelcomeScreen
from screens.search_screen import SearchScreen
class TuiGithub(App):
GITEA_HOST = "https://chookspace.com/"
def on_compose(self):
self.push_screen(SearchScreen())
if __name__ == "__main__":
app = TuiGithub()
app.run()

123
screens/search_screen.py Normal file
View File

@@ -0,0 +1,123 @@
from widgets import Navbar
from textual.screen import Screen
from textual.widgets import Input, Select, LoadingIndicator, Static
from textual.containers import HorizontalGroup, ScrollableContainer
from textual.app import ComposeResult
from human_readable import time_delta
from datetime import datetime, timedelta
import requests
class SearchResult(HorizontalGroup):
DEFAULT_CSS = """
SearchResult {
padding: 0 1;
margin: 0 1;
margin-top: 1;
border: tall $surface;
Static {
width: auto;
}
}
"""
def __init__(
self,
author: str,
name: str,
description: str,
is_fork: bool,
updated_at: datetime
):
super().__init__()
self.author = author
self.repo_name = name
self.description = description
self.is_fork = is_fork
self.updated_at = updated_at.replace(tzinfo=None)
def compose(self) -> ComposeResult:
updated_string = time_delta(datetime.now() - self.updated_at)
yield Static(f"[b]{self.author}[/] / [b]{self.repo_name}[/]{' [d]\[[blue]fork[/]]' if self.is_fork else ''}\n{self.description}\n[d]Updated {updated_string} ago")
class SearchScreen(Screen):
DEFAULT_CSS = """
#search-box {
padding: 1;
padding-bottom: 0;
border-bottom: hkey $surface;
#query {
width: 1fr;
}
#search-select {
width: 25;
}
}
"""
def action_search_query(self):
query_input = self.query_one("#query")
loading = self.query_one(LoadingIndicator)
results = self.query_one(ScrollableContainer)
loading.display = True
# send off a request
response = requests.get(
url=self.app.GITEA_HOST + "api/v1/repos/search",
params={
"q": query_input.value,
"limit": 20
}
)
loading.display = False
# error handling
if not response.ok:
self.notify(response.text, title="Error while getting search results:", severity="error")
return
# display results
results.remove_children(SearchResult)
to_mount = []
print(response.json())
for result in response.json()["data"]:
to_mount.append(SearchResult(
result["owner"]["login"],
result["name"],
result["description"],
result["fork"],
datetime.fromisoformat(result["updated_at"])
))
results.mount_all(to_mount)
# self explanitory
query_input.clear()
def on_input_submitted(self, event: Input.Submitted):
if event.input.id == "query":
self.action_search_query()
def on_mount(self):
self.action_search_query()
#self.query_one(LoadingIndicator).display = False
def compose(self) -> ComposeResult:
yield Navbar()
with HorizontalGroup(id="search-box"):
yield Input(placeholder="Search repos...", id="query")
yield Select.from_values([
"Repositories",
], id="search-select", allow_blank=False)
with ScrollableContainer(id="results"):
yield LoadingIndicator()

47
screens/welcome_screen.py Normal file
View File

@@ -0,0 +1,47 @@
from textual.containers import Vertical, HorizontalGroup, Center
from textual.screen import Screen
from textual.app import ComposeResult
from textual.widgets import Static, Button
from textualeffects.widgets import EffectLabel, EffectType, effects
from widgets import Navbar
from random import choice
class WelcomeScreen(Screen):
DEFAULT_CSS = """
Center {
padding: 2;
width: 100%;
Static {
text-align: center;
}
EffectLabel {
text-style: bold;
min-width: 100%;
}
#buttons {
margin-top: 1;
align: center middle;
Button {
margin: 0 2;
}
}
}
"""
def compose(self) -> ComposeResult:
yield Navbar()
with Center():
with open("banner.txt", "r") as f:
yield EffectLabel(text=f.read(), effect=choice(list(effects.keys())))
yield Static("[d]Gitea, in your terminal.[/]")
with HorizontalGroup(id="buttons"):
yield Button("Explore", variant="primary", flat=True)
yield Button("another button lol", flat=True)

1
widgets/__init__.py Normal file
View File

@@ -0,0 +1 @@
from .navbar import Navbar

21
widgets/navbar.py Normal file
View File

@@ -0,0 +1,21 @@
from textual.widgets import Static, Button
from textual.containers import HorizontalGroup
class Navbar(HorizontalGroup):
DEFAULT_CSS = """
Navbar {
background: $surface-darken-1;
border-bottom: hkey $surface;
height: 3;
padding: 1;
padding-bottom: 0;
#hamburger-menu {
max-width: 1;
}
}
"""
def compose(self):
yield Button("", compact=True, id="hamburger-menu")