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 textual import work from widgets import Navbar from screens.repo_view_screen import RepoViewScreen 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; link-style: bold; link-color: $primary; } } """ def __init__( self, repo_data: dict ): super().__init__() self.repo_data = repo_data self.author = repo_data["owner"] self.repo_name = repo_data["name"] self.description = repo_data["description"] self.is_fork = repo_data["fork"] self.updated_at = datetime.fromisoformat(repo_data["updated_at"]).replace(tzinfo=None) def compose(self) -> ComposeResult: updated_string = time_delta(datetime.now() - self.updated_at) yield Static(f"[@click='app.view_user({self.author["id"]})']{self.author["login"]}[/] / [@click='app.view_repo(\"{self.author["login"]}\",\"{self.repo_name}\")']{self.repo_name}[/]{' [d]\[[cyan]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; } Select { width: 25; } } """ def __init__(self): super().__init__() self.selects_changed = [] # just used to prevent some issues @work(exclusive=True) async def action_search_query(self): query_input = self.query_one("#query") loading = self.query_one(LoadingIndicator) results = self.query_one(ScrollableContainer) loading.display = True # get params we need to send sort = self.query_one("#sort-select").value order = self.query_one("#order-select").value users_or_repos = self.query_one("#search-select").value # send off a request response = requests.get( url=self.app.GITEA_HOST + "api/v1/repos/search", params={ "q": query_input.value, "limit": 20, "sort": sort, "order": order } ) 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 = [] for result in response.json()["data"]: to_mount.append(SearchResult(result)) results.mount_all(to_mount) # self explanitory query_input.clear() query_input.focus() def on_input_submitted(self, event: Input.Submitted): if event.input.id == "query": self.action_search_query() def on_select_changed(self, event: Select.Changed): if not event.select.id in self.selects_changed: self.selects_changed.append(event.select.id) return 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([ ("Alphabetical", "alpha"), ("Creation date", "created"), ("Last updated", "updated"), ("Size", "size"), ("ID", "id") ], id="sort-select", allow_blank=False, value="updated") yield Select([ ("Ascending", "asc"), ("Descending", "desc") ], id="order-select", allow_blank=False, value="desc") yield Select.from_values([ "Repositories", "Users", ], id="search-select", allow_blank=False, value="Repositories") with ScrollableContainer(id="results"): yield LoadingIndicator()