davanstrien HF Staff commited on
Commit
dcc9dbd
·
1 Parent(s): e498871
Files changed (4) hide show
  1. Dockerfile +2 -0
  2. app.py +17 -7
  3. calculator.py +171 -0
  4. calculator.tcss +38 -0
Dockerfile CHANGED
@@ -19,6 +19,8 @@ RUN pip install --no-cache-dir -r requirements.txt
19
 
20
  # Copy application files
21
  COPY app.py .
 
 
22
 
23
  # Expose the port that HF Spaces expects
24
  EXPOSE 7860
 
19
 
20
  # Copy application files
21
  COPY app.py .
22
+ COPY calculator.py .
23
+ COPY calculator.tcss .
24
 
25
  # Expose the port that HF Spaces expects
26
  EXPOSE 7860
app.py CHANGED
@@ -1,27 +1,37 @@
1
  #!/usr/bin/env python
2
  """
3
- Minimal Textual-Serve app for Hugging Face Spaces
4
- Serves the built-in Textual demo application
5
  """
6
 
 
7
  from textual_serve.server import Server
8
 
9
  def main():
10
- # Use the built-in Textual demo as our test application
11
- # This runs 'python -m textual' which shows the Textual demo
12
- command = "python -m textual"
 
 
 
 
 
 
 
 
13
 
14
  # Create server configured for HF Spaces
15
  server = Server(
16
  command=command,
17
  host="0.0.0.0", # Accept connections from any IP
18
  port=7860, # HF Spaces default port
19
- title="Textual Demo on HF Spaces", # Browser tab title
20
  # The public_url will be automatically set by HF Spaces
21
  )
22
 
23
  print("Starting Textual-Serve server...")
24
- print(f"Serving command: {command}")
 
25
  print(f"Server will be available on port 7860")
26
 
27
  # Start serving
 
1
  #!/usr/bin/env python
2
  """
3
+ Textual-Serve app for Hugging Face Spaces
4
+ Serves the Textual Calculator example application
5
  """
6
 
7
+ import os
8
  from textual_serve.server import Server
9
 
10
  def main():
11
+ # Default to calculator, but allow override via environment variable
12
+ app_choice = os.environ.get("TEXTUAL_APP", "calculator").lower()
13
+
14
+ if app_choice == "demo":
15
+ # Run the built-in Textual demo
16
+ command = "python -m textual"
17
+ title = "Textual Demo on HF Spaces"
18
+ else:
19
+ # Run the calculator example (default)
20
+ command = "python calculator.py"
21
+ title = "Textual Calculator on HF Spaces"
22
 
23
  # Create server configured for HF Spaces
24
  server = Server(
25
  command=command,
26
  host="0.0.0.0", # Accept connections from any IP
27
  port=7860, # HF Spaces default port
28
+ title=title, # Browser tab title
29
  # The public_url will be automatically set by HF Spaces
30
  )
31
 
32
  print("Starting Textual-Serve server...")
33
+ print(f"Serving: {title}")
34
+ print(f"Command: {command}")
35
  print(f"Server will be available on port 7860")
36
 
37
  # Start serving
calculator.py ADDED
@@ -0,0 +1,171 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ }