davanstrien HF Staff commited on
Commit
4d1d929
·
1 Parent(s): 76e1ee7

switch example

Browse files
Files changed (5) hide show
  1. calculator.py +0 -171
  2. calculator.tcss +0 -38
  3. dictionary.py +77 -0
  4. dictionary.tcss +25 -0
  5. serve.py +2 -24
calculator.py DELETED
@@ -1,171 +0,0 @@
1
- """
2
- An implementation of a classic calculator, with a layout inspired by macOS calculator.
3
-
4
- Works like a real calculator. Click the buttons or press the equivalent keys.
5
- """
6
-
7
- from decimal import Decimal
8
-
9
- from textual import events, on
10
- from textual.app import App, ComposeResult
11
- from textual.containers import Container
12
- from textual.css.query import NoMatches
13
- from textual.reactive import var
14
- from textual.widgets import Button, Digits
15
-
16
-
17
- class CalculatorApp(App):
18
- """A working 'desktop' calculator."""
19
-
20
- CSS_PATH = "calculator.tcss"
21
-
22
- numbers = var("0")
23
- show_ac = var(True)
24
- left = var(Decimal("0"))
25
- right = var(Decimal("0"))
26
- value = var("")
27
- operator = var("plus")
28
-
29
- # Maps button IDs on to the corresponding key name
30
- NAME_MAP = {
31
- "asterisk": "multiply",
32
- "slash": "divide",
33
- "underscore": "plus-minus",
34
- "full_stop": "point",
35
- "plus_minus_sign": "plus-minus",
36
- "percent_sign": "percent",
37
- "equals_sign": "equals",
38
- "minus": "minus",
39
- "plus": "plus",
40
- }
41
-
42
- def watch_numbers(self, value: str) -> None:
43
- """Called when numbers is updated."""
44
- self.query_one("#numbers", Digits).update(value)
45
-
46
- def compute_show_ac(self) -> bool:
47
- """Compute switch to show AC or C button"""
48
- return self.value in ("", "0") and self.numbers == "0"
49
-
50
- def watch_show_ac(self, show_ac: bool) -> None:
51
- """Called when show_ac changes."""
52
- self.query_one("#c").display = not show_ac
53
- self.query_one("#ac").display = show_ac
54
-
55
- def compose(self) -> ComposeResult:
56
- """Add our buttons."""
57
- with Container(id="calculator"):
58
- yield Digits(id="numbers")
59
- yield Button("AC", id="ac", variant="primary")
60
- yield Button("C", id="c", variant="primary")
61
- yield Button("+/-", id="plus-minus", variant="primary")
62
- yield Button("%", id="percent", variant="primary")
63
- yield Button("÷", id="divide", variant="warning")
64
- yield Button("7", id="number-7", classes="number")
65
- yield Button("8", id="number-8", classes="number")
66
- yield Button("9", id="number-9", classes="number")
67
- yield Button("×", id="multiply", variant="warning")
68
- yield Button("4", id="number-4", classes="number")
69
- yield Button("5", id="number-5", classes="number")
70
- yield Button("6", id="number-6", classes="number")
71
- yield Button("-", id="minus", variant="warning")
72
- yield Button("1", id="number-1", classes="number")
73
- yield Button("2", id="number-2", classes="number")
74
- yield Button("3", id="number-3", classes="number")
75
- yield Button("+", id="plus", variant="warning")
76
- yield Button("0", id="number-0", classes="number")
77
- yield Button(".", id="point")
78
- yield Button("=", id="equals", variant="warning")
79
-
80
- def on_key(self, event: events.Key) -> None:
81
- """Called when the user presses a key."""
82
-
83
- def press(button_id: str) -> None:
84
- """Press a button, should it exist."""
85
- try:
86
- self.query_one(f"#{button_id}", Button).press()
87
- except NoMatches:
88
- pass
89
-
90
- key = event.key
91
- if key.isdecimal():
92
- press(f"number-{key}")
93
- elif key == "c":
94
- press("c")
95
- press("ac")
96
- else:
97
- button_id = self.NAME_MAP.get(key)
98
- if button_id is not None:
99
- press(self.NAME_MAP.get(key, key))
100
-
101
- @on(Button.Pressed, ".number")
102
- def number_pressed(self, event: Button.Pressed) -> None:
103
- """Pressed a number."""
104
- assert event.button.id is not None
105
- number = event.button.id.partition("-")[-1]
106
- self.numbers = self.value = self.value.lstrip("0") + number
107
-
108
- @on(Button.Pressed, "#plus-minus")
109
- def plus_minus_pressed(self) -> None:
110
- """Pressed + / -"""
111
- self.numbers = self.value = str(Decimal(self.value or "0") * -1)
112
-
113
- @on(Button.Pressed, "#percent")
114
- def percent_pressed(self) -> None:
115
- """Pressed %"""
116
- self.numbers = self.value = str(Decimal(self.value or "0") / Decimal(100))
117
-
118
- @on(Button.Pressed, "#point")
119
- def pressed_point(self) -> None:
120
- """Pressed ."""
121
- if "." not in self.value:
122
- self.numbers = self.value = (self.value or "0") + "."
123
-
124
- @on(Button.Pressed, "#ac")
125
- def pressed_ac(self) -> None:
126
- """Pressed AC"""
127
- self.value = ""
128
- self.left = self.right = Decimal(0)
129
- self.operator = "plus"
130
- self.numbers = "0"
131
-
132
- @on(Button.Pressed, "#c")
133
- def pressed_c(self) -> None:
134
- """Pressed C"""
135
- self.value = ""
136
- self.numbers = "0"
137
-
138
- def _do_math(self) -> None:
139
- """Does the math: LEFT OPERATOR RIGHT"""
140
- try:
141
- if self.operator == "plus":
142
- self.left += self.right
143
- elif self.operator == "minus":
144
- self.left -= self.right
145
- elif self.operator == "divide":
146
- self.left /= self.right
147
- elif self.operator == "multiply":
148
- self.left *= self.right
149
- self.numbers = str(self.left)
150
- self.value = ""
151
- except Exception:
152
- self.numbers = "Error"
153
-
154
- @on(Button.Pressed, "#plus,#minus,#divide,#multiply")
155
- def pressed_op(self, event: Button.Pressed) -> None:
156
- """Pressed one of the arithmetic operations."""
157
- self.right = Decimal(self.value or "0")
158
- self._do_math()
159
- assert event.button.id is not None
160
- self.operator = event.button.id
161
-
162
- @on(Button.Pressed, "#equals")
163
- def pressed_equals(self) -> None:
164
- """Pressed ="""
165
- if self.value:
166
- self.right = Decimal(self.value)
167
- self._do_math()
168
-
169
-
170
- if __name__ == "__main__":
171
- CalculatorApp().run(inline=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
calculator.tcss DELETED
@@ -1,38 +0,0 @@
1
- Screen {
2
- overflow: auto;
3
- }
4
-
5
- #calculator {
6
- layout: grid;
7
- grid-size: 4;
8
- grid-gutter: 1 2;
9
- grid-columns: 1fr;
10
- grid-rows: 2fr 1fr 1fr 1fr 1fr 1fr;
11
- margin: 1 2;
12
- min-height: 25;
13
- min-width: 26;
14
- height: 100%;
15
-
16
- &:inline {
17
- margin: 0 2;
18
- }
19
- }
20
-
21
- Button {
22
- width: 100%;
23
- height: 100%;
24
- }
25
-
26
- #numbers {
27
- column-span: 4;
28
- padding: 0 1;
29
- height: 100%;
30
- background: $panel;
31
- color: $text;
32
- content-align: center middle;
33
- text-align: right;
34
- }
35
-
36
- #number-0 {
37
- column-span: 2;
38
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
dictionary.py ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ try:
4
+ import httpx
5
+ except ImportError:
6
+ raise ImportError("Please install httpx with 'pip install httpx' ")
7
+
8
+
9
+ from textual import work
10
+ from textual.app import App, ComposeResult
11
+ from textual.containers import VerticalScroll
12
+ from textual.widgets import Input, Markdown
13
+
14
+
15
+ class DictionaryApp(App[None]):
16
+ """Searches a dictionary API as-you-type."""
17
+
18
+ CSS_PATH = "dictionary.tcss"
19
+
20
+ def compose(self) -> ComposeResult:
21
+ yield Input(placeholder="Search for a word")
22
+ with VerticalScroll(id="results-container"):
23
+ yield Markdown(id="results")
24
+
25
+ # def on_mount(self) -> None:
26
+ # """Called when app starts."""
27
+ # # Give the input focus, so we can start typing straight away
28
+ # self.query_one(Input).focus()
29
+
30
+ async def on_input_changed(self, message: Input.Changed) -> None:
31
+ """A coroutine to handle a text changed message."""
32
+ if message.value:
33
+ self.lookup_word(message.value)
34
+ else:
35
+ # Clear the results
36
+ await self.query_one("#results", Markdown).update("")
37
+
38
+ @work(exclusive=True)
39
+ async def lookup_word(self, word: str) -> None:
40
+ """Looks up a word."""
41
+ url = f"https://api.dictionaryapi.dev/api/v2/entries/en/{word}"
42
+
43
+ async with httpx.AsyncClient() as client:
44
+ response = await client.get(url)
45
+ try:
46
+ results = response.json()
47
+ except Exception:
48
+ self.query_one("#results", Markdown).update(response.text)
49
+ return
50
+
51
+ if word == self.query_one(Input).value:
52
+ markdown = self.make_word_markdown(results)
53
+ self.query_one("#results", Markdown).update(markdown)
54
+
55
+ def make_word_markdown(self, results: object) -> str:
56
+ """Convert the results in to markdown."""
57
+ lines = []
58
+ if isinstance(results, dict):
59
+ lines.append(f"# {results['title']}")
60
+ lines.append(results["message"])
61
+ elif isinstance(results, list):
62
+ for result in results:
63
+ lines.append(f"# {result['word']}")
64
+ lines.append("")
65
+ for meaning in result.get("meanings", []):
66
+ lines.append(f"_{meaning['partOfSpeech']}_")
67
+ lines.append("")
68
+ for definition in meaning.get("definitions", []):
69
+ lines.append(f" - {definition['definition']}")
70
+ lines.append("---")
71
+
72
+ return "\n".join(lines)
73
+
74
+
75
+ if __name__ == "__main__":
76
+ app = DictionaryApp()
77
+ app.run()
dictionary.tcss ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Screen {
2
+ background: $panel;
3
+ }
4
+
5
+ Input {
6
+ dock: top;
7
+ margin: 1 0;
8
+ }
9
+
10
+ #results {
11
+ width: 100%;
12
+ height: auto;
13
+ }
14
+
15
+ #results-container {
16
+ background: $background 50%;
17
+ margin: 0 0 1 0;
18
+ height: 100%;
19
+ overflow: hidden auto;
20
+ border: tall $background;
21
+ }
22
+
23
+ #results-container:focus {
24
+ border: tall $accent;
25
+ }
serve.py CHANGED
@@ -1,26 +1,4 @@
1
  from textual_serve.server import Server
2
- import os
3
 
4
- # Configure server with host, port, and public_url
5
- # For local testing, use localhost. For HF Spaces, use the Space URL
6
- space_host = os.environ.get("SPACE_HOST")
7
- if space_host:
8
- # HF Spaces provides the domain without protocol, so add https://
9
- if not space_host.startswith(("http://", "https://")):
10
- public_url = f"https://{space_host}"
11
- else:
12
- public_url = space_host
13
- else:
14
- # Default for local testing
15
- public_url = "http://localhost:7860"
16
-
17
- print(f"Using public_url: {public_url}")
18
-
19
- server = Server(
20
- "python -m textual",
21
- host="0.0.0.0",
22
- port=7860,
23
- public_url=public_url, # This fixes the WebSocket URL issue
24
- title="Textual Demo"
25
- )
26
- server.serve()
 
1
  from textual_serve.server import Server
 
2
 
3
+ server = Server("python dictionary.py")
4
+ server.serve(debug=False)