batrachian-toad 0.5.22__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- batrachian_toad-0.5.22.dist-info/METADATA +197 -0
- batrachian_toad-0.5.22.dist-info/RECORD +120 -0
- batrachian_toad-0.5.22.dist-info/WHEEL +4 -0
- batrachian_toad-0.5.22.dist-info/entry_points.txt +2 -0
- batrachian_toad-0.5.22.dist-info/licenses/LICENSE +661 -0
- toad/__init__.py +46 -0
- toad/__main__.py +4 -0
- toad/_loop.py +86 -0
- toad/about.py +90 -0
- toad/acp/agent.py +671 -0
- toad/acp/api.py +47 -0
- toad/acp/encode_tool_call_id.py +12 -0
- toad/acp/messages.py +138 -0
- toad/acp/prompt.py +54 -0
- toad/acp/protocol.py +426 -0
- toad/agent.py +62 -0
- toad/agent_schema.py +70 -0
- toad/agents.py +45 -0
- toad/ansi/__init__.py +1 -0
- toad/ansi/_ansi.py +1612 -0
- toad/ansi/_ansi_colors.py +264 -0
- toad/ansi/_control_codes.py +37 -0
- toad/ansi/_keys.py +251 -0
- toad/ansi/_sgr_styles.py +64 -0
- toad/ansi/_stream_parser.py +418 -0
- toad/answer.py +22 -0
- toad/app.py +557 -0
- toad/atomic.py +37 -0
- toad/cli.py +257 -0
- toad/code_analyze.py +28 -0
- toad/complete.py +34 -0
- toad/constants.py +58 -0
- toad/conversation_markdown.py +19 -0
- toad/danger.py +371 -0
- toad/data/agents/ampcode.com.toml +51 -0
- toad/data/agents/augmentcode.com.toml +40 -0
- toad/data/agents/claude.com.toml +41 -0
- toad/data/agents/docker.com.toml +59 -0
- toad/data/agents/geminicli.com.toml +28 -0
- toad/data/agents/goose.ai.toml +51 -0
- toad/data/agents/inference.huggingface.co.toml +33 -0
- toad/data/agents/kimi.com.toml +35 -0
- toad/data/agents/openai.com.toml +53 -0
- toad/data/agents/opencode.ai.toml +61 -0
- toad/data/agents/openhands.dev.toml +44 -0
- toad/data/agents/stakpak.dev.toml +61 -0
- toad/data/agents/vibe.mistral.ai.toml +27 -0
- toad/data/agents/vtcode.dev.toml +62 -0
- toad/data/images/frog.png +0 -0
- toad/data/sounds/turn-over.wav +0 -0
- toad/db.py +5 -0
- toad/dec.py +332 -0
- toad/directory.py +234 -0
- toad/directory_watcher.py +96 -0
- toad/fuzzy.py +140 -0
- toad/gist.py +2 -0
- toad/history.py +138 -0
- toad/jsonrpc.py +576 -0
- toad/menus.py +14 -0
- toad/messages.py +74 -0
- toad/option_content.py +51 -0
- toad/os.py +0 -0
- toad/path_complete.py +145 -0
- toad/path_filter.py +124 -0
- toad/paths.py +71 -0
- toad/pill.py +23 -0
- toad/prompt/extract.py +19 -0
- toad/prompt/resource.py +68 -0
- toad/protocol.py +28 -0
- toad/screens/action_modal.py +94 -0
- toad/screens/agent_modal.py +172 -0
- toad/screens/command_edit_modal.py +58 -0
- toad/screens/main.py +192 -0
- toad/screens/permissions.py +390 -0
- toad/screens/permissions.tcss +72 -0
- toad/screens/settings.py +254 -0
- toad/screens/settings.tcss +101 -0
- toad/screens/store.py +476 -0
- toad/screens/store.tcss +261 -0
- toad/settings.py +354 -0
- toad/settings_schema.py +318 -0
- toad/shell.py +263 -0
- toad/shell_read.py +42 -0
- toad/slash_command.py +34 -0
- toad/toad.tcss +752 -0
- toad/version.py +80 -0
- toad/visuals/columns.py +273 -0
- toad/widgets/agent_response.py +79 -0
- toad/widgets/agent_thought.py +41 -0
- toad/widgets/command_pane.py +224 -0
- toad/widgets/condensed_path.py +93 -0
- toad/widgets/conversation.py +1626 -0
- toad/widgets/danger_warning.py +65 -0
- toad/widgets/diff_view.py +709 -0
- toad/widgets/flash.py +81 -0
- toad/widgets/future_text.py +126 -0
- toad/widgets/grid_select.py +223 -0
- toad/widgets/highlighted_textarea.py +180 -0
- toad/widgets/mandelbrot.py +294 -0
- toad/widgets/markdown_note.py +13 -0
- toad/widgets/menu.py +147 -0
- toad/widgets/non_selectable_label.py +5 -0
- toad/widgets/note.py +18 -0
- toad/widgets/path_search.py +381 -0
- toad/widgets/plan.py +180 -0
- toad/widgets/project_directory_tree.py +74 -0
- toad/widgets/prompt.py +741 -0
- toad/widgets/question.py +337 -0
- toad/widgets/shell_result.py +35 -0
- toad/widgets/shell_terminal.py +18 -0
- toad/widgets/side_bar.py +74 -0
- toad/widgets/slash_complete.py +211 -0
- toad/widgets/strike_text.py +66 -0
- toad/widgets/terminal.py +526 -0
- toad/widgets/terminal_tool.py +338 -0
- toad/widgets/throbber.py +90 -0
- toad/widgets/tool_call.py +303 -0
- toad/widgets/user_input.py +23 -0
- toad/widgets/version.py +5 -0
- toad/widgets/welcome.py +31 -0
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from typing import Awaitable, Callable
|
|
5
|
+
from textual import on
|
|
6
|
+
from textual.app import ComposeResult
|
|
7
|
+
from textual import containers
|
|
8
|
+
|
|
9
|
+
from textual import getters
|
|
10
|
+
from textual.binding import Binding
|
|
11
|
+
from textual.screen import Screen
|
|
12
|
+
from textual.reactive import var, Initialize
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
from toad.answer import Answer
|
|
16
|
+
from toad.widgets.question import Question
|
|
17
|
+
from toad.widgets.diff_view import DiffView
|
|
18
|
+
|
|
19
|
+
from textual.widgets import OptionList, Footer, Static, Select
|
|
20
|
+
from textual.widgets.option_list import Option
|
|
21
|
+
|
|
22
|
+
from toad.app import ToadApp
|
|
23
|
+
|
|
24
|
+
SOURCE1 = '''\
|
|
25
|
+
def loop_first(values: Iterable[T]) -> Iterable[tuple[bool, T]]:
|
|
26
|
+
"""Iterate and generate a tuple with a flag for first value."""
|
|
27
|
+
iter_values = iter(values)
|
|
28
|
+
try:
|
|
29
|
+
value = next(iter_values)
|
|
30
|
+
except StopIteration:
|
|
31
|
+
return
|
|
32
|
+
yield True, value
|
|
33
|
+
for value in iter_values:
|
|
34
|
+
yield False, value
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def loop_first_last(values: Iterable[T]) -> Iterable[tuple[bool, T]]:
|
|
38
|
+
"""Iterate and generate a tuple with a flag for first and last value."""
|
|
39
|
+
iter_values = iter(values)
|
|
40
|
+
try:
|
|
41
|
+
previous_value = next(iter_values)
|
|
42
|
+
except StopIteration:
|
|
43
|
+
return
|
|
44
|
+
first = True
|
|
45
|
+
for value in iter_values:
|
|
46
|
+
yield first, False, previous_value
|
|
47
|
+
first = False
|
|
48
|
+
previous_value = value
|
|
49
|
+
yield first, True, previous_value
|
|
50
|
+
|
|
51
|
+
'''
|
|
52
|
+
|
|
53
|
+
SOURCE2 = '''\
|
|
54
|
+
def loop_first(values: Iterable[T]) -> Iterable[tuple[bool, T]]:
|
|
55
|
+
"""Iterate and generate a tuple with a flag for first value.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
values: iterables of values.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
Iterable of a boolean to indicate first value, and a value from the iterable.
|
|
62
|
+
"""
|
|
63
|
+
iter_values = iter(values)
|
|
64
|
+
try:
|
|
65
|
+
value = next(iter_values)
|
|
66
|
+
except StopIteration:
|
|
67
|
+
return
|
|
68
|
+
yield True, value
|
|
69
|
+
for value in iter_values:
|
|
70
|
+
yield False, value
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def loop_last(values: Iterable[T]) -> Iterable[tuple[bool, bool, T]]:
|
|
74
|
+
"""Iterate and generate a tuple with a flag for last value."""
|
|
75
|
+
iter_values = iter(values)
|
|
76
|
+
try:
|
|
77
|
+
previous_value = next(iter_values)
|
|
78
|
+
except StopIteration:
|
|
79
|
+
return
|
|
80
|
+
for value in iter_values:
|
|
81
|
+
yield False, previous_value
|
|
82
|
+
previous_value = value
|
|
83
|
+
yield True, previous_value
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def loop_first_last(values: Iterable[ValueType]) -> Iterable[tuple[bool, bool, ValueType]]:
|
|
87
|
+
"""Iterate and generate a tuple with a flag for first and last value."""
|
|
88
|
+
iter_values = iter(values)
|
|
89
|
+
try:
|
|
90
|
+
previous_value = next(iter_values) # Get previous value
|
|
91
|
+
except StopIteration:
|
|
92
|
+
return
|
|
93
|
+
first = True
|
|
94
|
+
|
|
95
|
+
'''
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class PermissionsQuestion(Question):
|
|
99
|
+
BINDING_GROUP_TITLE = "Permissions Options"
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class ChangesOptionList(OptionList):
|
|
103
|
+
BINDING_GROUP_TITLE = "Changes list"
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class DiffViewSelect(Select):
|
|
107
|
+
BINDING_GROUP_TITLE = "Diff view select"
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class ToolScroll(containers.VerticalScroll):
|
|
111
|
+
BINDING_GROUP_TITLE = "Changes window"
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class PermissionsScreen(Screen[Answer]):
|
|
115
|
+
BINDING_GROUP_TITLE = "Permissions"
|
|
116
|
+
AUTO_FOCUS = "Question"
|
|
117
|
+
CSS_PATH = "permissions.tcss"
|
|
118
|
+
|
|
119
|
+
TAB_GROUP = Binding.Group("Focus")
|
|
120
|
+
NAVIGATION_GROUP = Binding.Group("Navigation", compact=True)
|
|
121
|
+
ALLOW_GROUP = Binding.Group("Allow once/always", compact=True)
|
|
122
|
+
REJECT_GROUP = Binding.Group("Reject once/always", compact=True)
|
|
123
|
+
BINDINGS = [
|
|
124
|
+
Binding("j", "next", "Next", group=NAVIGATION_GROUP),
|
|
125
|
+
Binding("k", "previous", "Previous", group=NAVIGATION_GROUP),
|
|
126
|
+
Binding(
|
|
127
|
+
"tab",
|
|
128
|
+
"app.focus_next",
|
|
129
|
+
"Focus next",
|
|
130
|
+
group=TAB_GROUP,
|
|
131
|
+
show=True,
|
|
132
|
+
priority=True,
|
|
133
|
+
),
|
|
134
|
+
Binding(
|
|
135
|
+
"shift+tab",
|
|
136
|
+
"app.focus_previous",
|
|
137
|
+
"Focus previous",
|
|
138
|
+
group=TAB_GROUP,
|
|
139
|
+
show=True,
|
|
140
|
+
priority=True,
|
|
141
|
+
),
|
|
142
|
+
Binding(
|
|
143
|
+
"a",
|
|
144
|
+
"select_kind(('allow_once', 'allow'))",
|
|
145
|
+
"Allow once",
|
|
146
|
+
group=ALLOW_GROUP,
|
|
147
|
+
priority=True,
|
|
148
|
+
),
|
|
149
|
+
Binding(
|
|
150
|
+
"A",
|
|
151
|
+
"select_kind('allow_always')",
|
|
152
|
+
"Allow always",
|
|
153
|
+
group=ALLOW_GROUP,
|
|
154
|
+
priority=True,
|
|
155
|
+
),
|
|
156
|
+
Binding(
|
|
157
|
+
"r",
|
|
158
|
+
"select_kind(('reject_once', 'reject'))",
|
|
159
|
+
"Reject once",
|
|
160
|
+
group=REJECT_GROUP,
|
|
161
|
+
priority=True,
|
|
162
|
+
),
|
|
163
|
+
Binding(
|
|
164
|
+
"R",
|
|
165
|
+
"select_kind('reject_always')",
|
|
166
|
+
"Reject always",
|
|
167
|
+
group=REJECT_GROUP,
|
|
168
|
+
priority=True,
|
|
169
|
+
),
|
|
170
|
+
]
|
|
171
|
+
|
|
172
|
+
tool_container = getters.query_one("#tool-container", containers.VerticalScroll)
|
|
173
|
+
navigator = getters.query_one("#navigator", OptionList)
|
|
174
|
+
question = getters.query_one(PermissionsQuestion)
|
|
175
|
+
index: var[int] = var(0)
|
|
176
|
+
|
|
177
|
+
def __init__(
|
|
178
|
+
self,
|
|
179
|
+
options: list[Answer],
|
|
180
|
+
populate_callback: Callable[["PermissionsScreen"], Awaitable] | None,
|
|
181
|
+
*,
|
|
182
|
+
name: str | None = None,
|
|
183
|
+
id: str | None = None,
|
|
184
|
+
classes: str | None = None,
|
|
185
|
+
):
|
|
186
|
+
super().__init__(name=name, id=id, classes=classes)
|
|
187
|
+
self.options = options
|
|
188
|
+
self.populate_callback = populate_callback
|
|
189
|
+
|
|
190
|
+
def get_diff_type(self) -> str:
|
|
191
|
+
app = self.app
|
|
192
|
+
diff_type = "auto"
|
|
193
|
+
if isinstance(app, ToadApp):
|
|
194
|
+
diff_type = app.settings.get("diff.view", str)
|
|
195
|
+
return diff_type
|
|
196
|
+
|
|
197
|
+
diff_type: var[str] = var(Initialize(get_diff_type))
|
|
198
|
+
|
|
199
|
+
def compose(self) -> ComposeResult:
|
|
200
|
+
with containers.Grid(classes="top"):
|
|
201
|
+
yield DiffViewSelect(
|
|
202
|
+
[
|
|
203
|
+
("Unified diff", "unified"),
|
|
204
|
+
("Split diff", "split"),
|
|
205
|
+
("Auto diff", "auto"),
|
|
206
|
+
],
|
|
207
|
+
value=self.diff_type,
|
|
208
|
+
allow_blank=False,
|
|
209
|
+
id="diff-select",
|
|
210
|
+
)
|
|
211
|
+
yield Static(
|
|
212
|
+
"[b]Approval request[/b] [dim]The Agent wishes to make the following changes",
|
|
213
|
+
id="instructions",
|
|
214
|
+
)
|
|
215
|
+
with containers.Vertical(id="nav-container"):
|
|
216
|
+
yield PermissionsQuestion("", options=self.options)
|
|
217
|
+
yield ChangesOptionList(id="navigator")
|
|
218
|
+
yield ToolScroll(id="tool-container")
|
|
219
|
+
|
|
220
|
+
yield Footer()
|
|
221
|
+
|
|
222
|
+
def action_select_kind(self, kind: str | tuple[str]) -> None:
|
|
223
|
+
self.question.action_select_kind(kind)
|
|
224
|
+
|
|
225
|
+
def check_action(self, action: str, parameters: tuple[object, ...]) -> bool | None:
|
|
226
|
+
if action == "select_kind":
|
|
227
|
+
kinds = {
|
|
228
|
+
answer.kind
|
|
229
|
+
for answer in self.question.options
|
|
230
|
+
if answer.kind is not None
|
|
231
|
+
}
|
|
232
|
+
check_kinds = set()
|
|
233
|
+
for parameter in parameters:
|
|
234
|
+
if isinstance(parameter, str):
|
|
235
|
+
check_kinds.add(parameter)
|
|
236
|
+
elif isinstance(parameter, tuple):
|
|
237
|
+
check_kinds.update(parameter)
|
|
238
|
+
|
|
239
|
+
return any(kind in kinds for kind in check_kinds)
|
|
240
|
+
|
|
241
|
+
return True
|
|
242
|
+
|
|
243
|
+
async def on_mount(self):
|
|
244
|
+
app = self.app
|
|
245
|
+
if isinstance(app, ToadApp):
|
|
246
|
+
diff_view_setting = app.settings.get("diff.view", str)
|
|
247
|
+
self.query_one("#diff-select", Select).value = diff_view_setting
|
|
248
|
+
self.navigator.highlighted = 0
|
|
249
|
+
|
|
250
|
+
if self.populate_callback is not None:
|
|
251
|
+
|
|
252
|
+
async def run_populate():
|
|
253
|
+
if self.populate_callback is not None:
|
|
254
|
+
await self.populate_callback(self)
|
|
255
|
+
|
|
256
|
+
asyncio.create_task(run_populate())
|
|
257
|
+
self.question.focus()
|
|
258
|
+
|
|
259
|
+
async def add_diff(
|
|
260
|
+
self, path1: str, path2: str, before: str | None, after: str
|
|
261
|
+
) -> None:
|
|
262
|
+
self.index += 1
|
|
263
|
+
option_id = f"item-{self.index}"
|
|
264
|
+
diff_view = DiffView(path1, path2, before or "", after, id=option_id)
|
|
265
|
+
await diff_view.prepare()
|
|
266
|
+
app = self.app
|
|
267
|
+
if isinstance(app, ToadApp):
|
|
268
|
+
diff_view_setting = app.settings.get("diff.view", str)
|
|
269
|
+
diff_view.split = diff_view_setting == "split"
|
|
270
|
+
diff_view.auto_split = diff_view_setting == "auto"
|
|
271
|
+
await self.tool_container.mount(diff_view)
|
|
272
|
+
|
|
273
|
+
option_text = f"📄 {os.path.basename(path1)}"
|
|
274
|
+
self.navigator.add_option(Option(option_text, option_id))
|
|
275
|
+
|
|
276
|
+
@on(OptionList.OptionHighlighted)
|
|
277
|
+
def on_option_highlighted(self, event: OptionList.OptionHighlighted):
|
|
278
|
+
self.tool_container.query_one(f"#{event.option_id}").scroll_visible(top=True)
|
|
279
|
+
|
|
280
|
+
@on(Question.Answer)
|
|
281
|
+
def on_question_answer(self, event: Question.Answer) -> None:
|
|
282
|
+
def dismiss():
|
|
283
|
+
self.dismiss(event.answer)
|
|
284
|
+
|
|
285
|
+
self.set_timer(0.4, dismiss)
|
|
286
|
+
|
|
287
|
+
@on(Select.Changed, "#diff-select")
|
|
288
|
+
def on_diff_select(self, event: Select.Changed) -> None:
|
|
289
|
+
diff_type = event.value
|
|
290
|
+
for diff_view in self.query(DiffView):
|
|
291
|
+
diff_view.auto_split = diff_type == "auto"
|
|
292
|
+
diff_view.split = diff_type == "split"
|
|
293
|
+
|
|
294
|
+
def action_next(self) -> None:
|
|
295
|
+
self.navigator.action_cursor_down()
|
|
296
|
+
|
|
297
|
+
def action_previous(self) -> None:
|
|
298
|
+
self.navigator.action_cursor_up()
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
if __name__ == "__main__":
|
|
302
|
+
SOURCE1 = '''\
|
|
303
|
+
def loop_first(values: Iterable[T]) -> Iterable[tuple[bool, T]]:
|
|
304
|
+
"""Iterate and generate a tuple with a flag for first value."""
|
|
305
|
+
iter_values = iter(values)
|
|
306
|
+
try:
|
|
307
|
+
value = next(iter_values)
|
|
308
|
+
except StopIteration:
|
|
309
|
+
return
|
|
310
|
+
yield True, value
|
|
311
|
+
for value in iter_values:
|
|
312
|
+
yield False, value
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def loop_first_last(values: Iterable[T]) -> Iterable[tuple[bool, T]]:
|
|
316
|
+
"""Iterate and generate a tuple with a flag for first and last value."""
|
|
317
|
+
iter_values = iter(values)
|
|
318
|
+
try:
|
|
319
|
+
previous_value = next(iter_values)
|
|
320
|
+
except StopIteration:
|
|
321
|
+
return
|
|
322
|
+
first = True
|
|
323
|
+
for value in iter_values:
|
|
324
|
+
yield first, False, previous_value
|
|
325
|
+
first = False
|
|
326
|
+
previous_value = value
|
|
327
|
+
yield first, True, previous_value
|
|
328
|
+
|
|
329
|
+
'''
|
|
330
|
+
|
|
331
|
+
SOURCE2 = '''\
|
|
332
|
+
def loop_first(values: Iterable[T]) -> Iterable[tuple[bool, T]]:
|
|
333
|
+
"""Iterate and generate a tuple with a flag for first value.
|
|
334
|
+
|
|
335
|
+
Args:
|
|
336
|
+
values: iterables of values.
|
|
337
|
+
|
|
338
|
+
Returns:
|
|
339
|
+
Iterable of a boolean to indicate first value, and a value from the iterable.
|
|
340
|
+
"""
|
|
341
|
+
iter_values = iter(values)
|
|
342
|
+
try:
|
|
343
|
+
value = next(iter_values)
|
|
344
|
+
except StopIteration:
|
|
345
|
+
return
|
|
346
|
+
yield True, value
|
|
347
|
+
for value in iter_values:
|
|
348
|
+
yield False, value
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def loop_last(values: Iterable[T]) -> Iterable[tuple[bool, bool, T]]:
|
|
352
|
+
"""Iterate and generate a tuple with a flag for last value."""
|
|
353
|
+
iter_values = iter(values)
|
|
354
|
+
try:
|
|
355
|
+
previous_value = next(iter_values)
|
|
356
|
+
except StopIteration:
|
|
357
|
+
return
|
|
358
|
+
for value in iter_values:
|
|
359
|
+
yield False, previous_value
|
|
360
|
+
previous_value = value
|
|
361
|
+
yield True, previous_value
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
def loop_first_last(values: Iterable[ValueType]) -> Iterable[tuple[bool, bool, ValueType]]:
|
|
365
|
+
"""Iterate and generate a tuple with a flag for first and last value."""
|
|
366
|
+
iter_values = iter(values)
|
|
367
|
+
try:
|
|
368
|
+
previous_value = next(iter_values) # Get previous value
|
|
369
|
+
except StopIteration:
|
|
370
|
+
return
|
|
371
|
+
first = True
|
|
372
|
+
|
|
373
|
+
'''
|
|
374
|
+
from textual import work
|
|
375
|
+
from textual.app import App
|
|
376
|
+
|
|
377
|
+
class PermissionTestApp(App):
|
|
378
|
+
@work
|
|
379
|
+
async def on_mount(self) -> None:
|
|
380
|
+
screen = PermissionsScreen(
|
|
381
|
+
[Answer("Foo", "allow_once", kind="allow_once"), Answer("Bar", "bar")],
|
|
382
|
+
None,
|
|
383
|
+
)
|
|
384
|
+
result = await self.push_screen_wait(screen)
|
|
385
|
+
self.notify(str(result))
|
|
386
|
+
# for repeat in range(5):
|
|
387
|
+
# await screen.add_diff("foo.py", "foo.py", SOURCE1, SOURCE2)
|
|
388
|
+
|
|
389
|
+
app = PermissionTestApp()
|
|
390
|
+
app.run()
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Styles for the Permissions Screen
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
PermissionsScreen {
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
background: $background;
|
|
8
|
+
|
|
9
|
+
#instructions {
|
|
10
|
+
background: black 10%;
|
|
11
|
+
border: tall black 10%;
|
|
12
|
+
padding: 0 2;
|
|
13
|
+
# margin: 0 0 1 0;
|
|
14
|
+
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.top {
|
|
18
|
+
height: 1fr;
|
|
19
|
+
background: $background;
|
|
20
|
+
margin: 1;
|
|
21
|
+
padding: 0 0;
|
|
22
|
+
grid-size: 2 2;
|
|
23
|
+
grid-columns: auto 1fr;
|
|
24
|
+
grid-rows: auto 1fr;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
#changes {
|
|
28
|
+
width: 1fr;
|
|
29
|
+
height: 1fr;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
OptionList#navigator {
|
|
33
|
+
width: auto;
|
|
34
|
+
height: 1fr;
|
|
35
|
+
min-width: 20;
|
|
36
|
+
width: 1fr;
|
|
37
|
+
expand: optimal;
|
|
38
|
+
}
|
|
39
|
+
Question {
|
|
40
|
+
width: auto;
|
|
41
|
+
expand: optimal;
|
|
42
|
+
border: tall black 10%;
|
|
43
|
+
background: black 10%;
|
|
44
|
+
# dock: bottom;
|
|
45
|
+
height: auto;
|
|
46
|
+
margin: 1 0;
|
|
47
|
+
padding: 0 2 0 1;
|
|
48
|
+
&:focus {
|
|
49
|
+
border: tall $primary;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
#tool-container {
|
|
54
|
+
background: $background;
|
|
55
|
+
height: 1fr;
|
|
56
|
+
border: tall black 10%;
|
|
57
|
+
margin-top: 1;
|
|
58
|
+
&:focus {
|
|
59
|
+
border: tall $primary;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
#nav-container {
|
|
64
|
+
width: auto;
|
|
65
|
+
height: 1fr;
|
|
66
|
+
Select {
|
|
67
|
+
width: auto;
|
|
68
|
+
height: auto;
|
|
69
|
+
expand: optimal;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|