Skip to content

Commit

Permalink
Add textual demos
Browse files Browse the repository at this point in the history
  • Loading branch information
yorevs committed Apr 25, 2024
1 parent 4031c27 commit daa3309
Show file tree
Hide file tree
Showing 10 changed files with 647 additions and 0 deletions.
Empty file added src/demo/new_ui/__init__.py
Empty file.
76 changes: 76 additions & 0 deletions src/demo/new_ui/code_browser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
"""
Code browser example.
Run with:
python code_browser.py PATH
"""

import sys

from rich.syntax import Syntax
from rich.traceback import Traceback

from textual.app import App, ComposeResult
from textual.containers import Container, VerticalScroll
from textual.reactive import var
from textual.widgets import DirectoryTree, Footer, Header, Static


class CodeBrowser(App):
"""Textual code browser app."""

CSS_PATH = "code_browser.tcss"
BINDINGS = [
("f", "toggle_files", "Toggle Files"),
("q", "quit", "Quit"),
]

show_tree = var(True)

def watch_show_tree(self, show_tree: bool) -> None:
"""Called when show_tree is modified."""
self.set_class(show_tree, "-show-tree")

def compose(self) -> ComposeResult:
"""Compose our UI."""
path = "./" if len(sys.argv) < 2 else sys.argv[1]
yield Header()
with Container():
yield DirectoryTree(path, id="tree-view")
with VerticalScroll(id="code-view"):
yield Static(id="code", expand=True)
yield Footer()

def on_mount(self) -> None:
self.query_one(DirectoryTree).focus()

def on_directory_tree_file_selected(
self, event: DirectoryTree.FileSelected
) -> None:
"""Called when the user click a file in the directory tree."""
event.stop()
code_view = self.query_one("#code", Static)
try:
syntax = Syntax.from_path(
str(event.path),
line_numbers=True,
word_wrap=False,
indent_guides=True,
theme="github-dark",
)
except Exception:
code_view.update(Traceback(theme="github-dark", width=None))
self.sub_title = "ERROR"
else:
code_view.update(syntax)
self.query_one("#code-view").scroll_home(animate=False)
self.sub_title = str(event.path)

def action_toggle_files(self) -> None:
"""Called in response to key binding."""
self.show_tree = not self.show_tree


if __name__ == "__main__":
CodeBrowser().run()
29 changes: 29 additions & 0 deletions src/demo/new_ui/code_browser.tcss
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
Screen {
background: $surface-darken-1;
&:inline {
height: 50vh;
}
}

#tree-view {
display: none;
scrollbar-gutter: stable;
overflow: auto;
width: auto;
height: 100%;
dock: left;
}

CodeBrowser.-show-tree #tree-view {
display: block;
max-width: 50%;
}


#code-view {
overflow: auto scroll;
min-width: 100%;
}
#code {
width: auto;
}
76 changes: 76 additions & 0 deletions src/demo/new_ui/dictionary.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
from __future__ import annotations

try:
import httpx
except ImportError:
raise ImportError("Please install httpx with 'pip install httpx' ")


from textual import work
from textual.app import App, ComposeResult
from textual.containers import VerticalScroll
from textual.widgets import Input, Markdown


class DictionaryApp(App):
"""Searches ab dictionary API as-you-type."""

CSS_PATH = "dictionary.tcss"

def compose(self) -> ComposeResult:
yield Input(placeholder="Search for a word")
with VerticalScroll(id="results-container"):
yield Markdown(id="results")

def on_mount(self) -> None:
"""Called when app starts."""
# Give the input focus, so we can start typing straight away
self.query_one(Input).focus()

async def on_input_changed(self, message: Input.Changed) -> None:
"""A coroutine to handle a text changed message."""
if message.value:
self.lookup_word(message.value)
else:
# Clear the results
self.query_one("#results", Markdown).update("")

@work(exclusive=True)
async def lookup_word(self, word: str) -> None:
"""Looks up a word."""
url = f"https://api.dictionaryapi.dev/api/v2/entries/en/{word}"

async with httpx.AsyncClient() as client:
response = await client.get(url)
try:
results = response.json()
except Exception:
self.query_one("#results", Markdown).update(response.text)

if word == self.query_one(Input).value:
markdown = self.make_word_markdown(results)
self.query_one("#results", Markdown).update(markdown)

def make_word_markdown(self, results: object) -> str:
"""Convert the results in to markdown."""
lines = []
if isinstance(results, dict):
lines.append(f"# {results['title']}")
lines.append(results["message"])
elif isinstance(results, list):
for result in results:
lines.append(f"# {result['word']}")
lines.append("")
for meaning in result.get("meanings", []):
lines.append(f"_{meaning['partOfSpeech']}_")
lines.append("")
for definition in meaning.get("definitions", []):
lines.append(f" - {definition['definition']}")
lines.append("---")

return "\n".join(lines)


if __name__ == "__main__":
app = DictionaryApp()
app.run()
25 changes: 25 additions & 0 deletions src/demo/new_ui/dictionary.tcss
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
Screen {
background: $panel;
}

Input {
dock: top;
margin: 1 0;
}

#results {
width: 100%;
height: auto;
}

#results-container {
background: $background 50%;
margin: 0 0 1 0;
height: 100%;
overflow: hidden auto;
border: tall $background;
}

#results-container:focus {
border: tall $accent;
}
50 changes: 50 additions & 0 deletions src/demo/new_ui/markdown.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
from pathlib import Path
from sys import argv

from textual.app import App, ComposeResult
from textual.reactive import var
from textual.widgets import Footer, MarkdownViewer


class MarkdownApp(App):
BINDINGS = [
("t", "toggle_table_of_contents", "TOC"),
("b", "back", "Back"),
("f", "forward", "Forward"),
]

path = var(Path(__file__).parent / "../../../README.md")

@property
def markdown_viewer(self) -> MarkdownViewer:
"""Get the Markdown widget."""
return self.query_one(MarkdownViewer)

def compose(self) -> ComposeResult:
yield Footer()
yield MarkdownViewer()

async def on_mount(self) -> None:
self.markdown_viewer.focus()
try:
await self.markdown_viewer.go(self.path)
except FileNotFoundError:
self.exit(message=f"Unable to load {self.path!r}")

def action_toggle_table_of_contents(self) -> None:
self.markdown_viewer.show_table_of_contents = (
not self.markdown_viewer.show_table_of_contents
)

async def action_back(self) -> None:
await self.markdown_viewer.back()

async def action_forward(self) -> None:
await self.markdown_viewer.forward()


if __name__ == "__main__":
app = MarkdownApp()
if len(argv) > 1 and Path(argv[1]).exists():
app.path = Path(argv[1])
app.run()
Loading

0 comments on commit daa3309

Please sign in to comment.