restiny 0.2.1__py3-none-any.whl → 0.6.1__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.
Files changed (38) hide show
  1. restiny/__about__.py +1 -1
  2. restiny/__main__.py +28 -14
  3. restiny/assets/style.tcss +56 -2
  4. restiny/consts.py +236 -0
  5. restiny/data/db.py +60 -0
  6. restiny/data/models.py +111 -0
  7. restiny/data/repos.py +455 -0
  8. restiny/data/sql/__init__.py +3 -0
  9. restiny/entities.py +438 -0
  10. restiny/enums.py +14 -5
  11. restiny/httpx_auths.py +52 -0
  12. restiny/ui/__init__.py +17 -0
  13. restiny/ui/app.py +586 -0
  14. restiny/ui/collections_area.py +594 -0
  15. restiny/ui/environments_screen.py +270 -0
  16. restiny/ui/request_area.py +602 -0
  17. restiny/{core → ui}/response_area.py +4 -1
  18. restiny/ui/settings_screen.py +73 -0
  19. restiny/ui/top_bar_area.py +60 -0
  20. restiny/{core → ui}/url_area.py +54 -38
  21. restiny/utils.py +52 -15
  22. restiny/widgets/__init__.py +15 -1
  23. restiny/widgets/collections_tree.py +74 -0
  24. restiny/widgets/confirm_prompt.py +76 -0
  25. restiny/widgets/custom_input.py +20 -0
  26. restiny/widgets/dynamic_fields.py +65 -70
  27. restiny/widgets/password_input.py +161 -0
  28. restiny/widgets/path_chooser.py +12 -12
  29. {restiny-0.2.1.dist-info → restiny-0.6.1.dist-info}/METADATA +7 -5
  30. restiny-0.6.1.dist-info/RECORD +38 -0
  31. restiny/core/__init__.py +0 -15
  32. restiny/core/app.py +0 -348
  33. restiny/core/request_area.py +0 -337
  34. restiny-0.2.1.dist-info/RECORD +0 -24
  35. {restiny-0.2.1.dist-info → restiny-0.6.1.dist-info}/WHEEL +0 -0
  36. {restiny-0.2.1.dist-info → restiny-0.6.1.dist-info}/entry_points.txt +0 -0
  37. {restiny-0.2.1.dist-info → restiny-0.6.1.dist-info}/licenses/LICENSE +0 -0
  38. {restiny-0.2.1.dist-info → restiny-0.6.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,602 @@
1
+ from pathlib import Path
2
+
3
+ from textual import on
4
+ from textual.app import ComposeResult
5
+ from textual.containers import Container, Horizontal
6
+ from textual.widgets import (
7
+ ContentSwitcher,
8
+ Label,
9
+ Select,
10
+ Static,
11
+ Switch,
12
+ TabbedContent,
13
+ TabPane,
14
+ )
15
+
16
+ from restiny.enums import AuthMode, BodyMode, BodyRawLanguage
17
+ from restiny.widgets import (
18
+ CustomInput,
19
+ CustomTextArea,
20
+ DynamicFields,
21
+ PasswordInput,
22
+ PathChooser,
23
+ TextDynamicField,
24
+ TextOrFileDynamicField,
25
+ )
26
+
27
+
28
+ class RequestArea(Static):
29
+ ALLOW_MAXIMIZE = True
30
+ focusable = True
31
+ BORDER_TITLE = 'Request'
32
+ DEFAULT_CSS = """
33
+ RequestArea {
34
+ width: 1fr;
35
+ height: 1fr;
36
+ border: heavy black;
37
+ border-title-color: gray;
38
+ padding: 1;
39
+ }
40
+ """
41
+
42
+ def compose(self) -> ComposeResult:
43
+ with TabbedContent():
44
+ with TabPane('Headers'):
45
+ yield DynamicFields(
46
+ fields=[TextDynamicField(enabled=False, key='', value='')],
47
+ id='headers',
48
+ )
49
+ with TabPane('Params'):
50
+ yield DynamicFields(
51
+ fields=[TextDynamicField(enabled=False, key='', value='')],
52
+ id='params',
53
+ )
54
+ with TabPane('Auth'):
55
+ with Horizontal(classes='h-auto'):
56
+ yield Switch(tooltip='Enabled', id='auth-enabled')
57
+ yield Select(
58
+ (
59
+ ('Basic', AuthMode.BASIC),
60
+ ('Bearer', AuthMode.BEARER),
61
+ ('API Key', AuthMode.API_KEY),
62
+ ('Digest', AuthMode.DIGEST),
63
+ ),
64
+ allow_blank=False,
65
+ tooltip='Auth mode',
66
+ id='auth-mode',
67
+ )
68
+ with ContentSwitcher(
69
+ initial='auth-basic', id='auth-mode-switcher'
70
+ ):
71
+ with Horizontal(id='auth-basic', classes='mt-1'):
72
+ yield CustomInput(
73
+ placeholder='Username',
74
+ select_on_focus=False,
75
+ classes='w-1fr',
76
+ id='auth-basic-username',
77
+ )
78
+ yield PasswordInput(
79
+ placeholder='Password',
80
+ select_on_focus=False,
81
+ classes='w-2fr',
82
+ id='auth-basic-password',
83
+ )
84
+ with Horizontal(id='auth-bearer', classes='mt-1'):
85
+ yield PasswordInput(
86
+ placeholder='Token',
87
+ select_on_focus=False,
88
+ id='auth-bearer-token',
89
+ )
90
+ with Horizontal(id='auth-api-key', classes='mt-1'):
91
+ yield Select(
92
+ (('Header', 'header'), ('Param', 'param')),
93
+ allow_blank=False,
94
+ tooltip='Where',
95
+ classes='w-1fr',
96
+ id='auth-api-key-where',
97
+ )
98
+ yield CustomInput(
99
+ placeholder='Key',
100
+ classes='w-2fr',
101
+ id='auth-api-key-key',
102
+ )
103
+ yield PasswordInput(
104
+ placeholder='Value',
105
+ classes='w-3fr',
106
+ id='auth-api-key-value',
107
+ )
108
+
109
+ with Horizontal(id='auth-digest', classes='mt-1'):
110
+ yield CustomInput(
111
+ placeholder='Username',
112
+ select_on_focus=False,
113
+ classes='w-1fr',
114
+ id='auth-digest-username',
115
+ )
116
+ yield PasswordInput(
117
+ placeholder='Password',
118
+ select_on_focus=False,
119
+ classes='w-2fr',
120
+ id='auth-digest-password',
121
+ )
122
+
123
+ with TabPane('Body'):
124
+ with Horizontal(classes='h-auto'):
125
+ yield Switch(id='body-enabled', tooltip='Send body?')
126
+ yield Select(
127
+ (
128
+ ('Raw', BodyMode.RAW),
129
+ ('File', BodyMode.FILE),
130
+ ('Form (urlencoded)', BodyMode.FORM_URLENCODED),
131
+ ('Form (multipart)', BodyMode.FORM_MULTIPART),
132
+ ),
133
+ allow_blank=False,
134
+ tooltip='Body mode',
135
+ id='body-mode',
136
+ )
137
+ with ContentSwitcher(
138
+ id='body-mode-switcher',
139
+ initial='body-mode-raw',
140
+ classes='h-1fr',
141
+ ):
142
+ with Container(id='body-mode-raw', classes='pt-1'):
143
+ yield Select(
144
+ (
145
+ ('Plain', BodyRawLanguage.PLAIN),
146
+ ('JSON', BodyRawLanguage.JSON),
147
+ ('YAML', BodyRawLanguage.YAML),
148
+ ('XML', BodyRawLanguage.XML),
149
+ ('HTML', BodyRawLanguage.HTML),
150
+ ),
151
+ allow_blank=False,
152
+ tooltip='Text type',
153
+ id='body-raw-language',
154
+ )
155
+ yield CustomTextArea.code_editor(
156
+ language='json', id='body-raw', classes='mt-1'
157
+ )
158
+ with Horizontal(
159
+ id='body-mode-file', classes='h-auto mt-1'
160
+ ):
161
+ yield PathChooser.file(id='body-file')
162
+ with Horizontal(
163
+ id='body-mode-form-urlencoded', classes='h-auto mt-1'
164
+ ):
165
+ yield DynamicFields(
166
+ [
167
+ TextDynamicField(
168
+ enabled=False, key='', value=''
169
+ )
170
+ ],
171
+ id='body-form-urlencoded',
172
+ )
173
+ with Horizontal(
174
+ id='body-mode-form-multipart', classes='h-auto mt-1'
175
+ ):
176
+ yield DynamicFields(
177
+ [
178
+ TextOrFileDynamicField(
179
+ enabled=False, key='', value=''
180
+ )
181
+ ],
182
+ id='body-form-multipart',
183
+ )
184
+
185
+ with TabPane('Options'):
186
+ with Horizontal(classes='h-auto'):
187
+ yield Label('Timeout', classes='pt-1 ml-1')
188
+ yield CustomInput(
189
+ '5.5',
190
+ placeholder='5.5',
191
+ select_on_focus=False,
192
+ type='number',
193
+ valid_empty=True,
194
+ classes='w-1fr',
195
+ id='options-timeout',
196
+ )
197
+ with Horizontal(classes='mt-1 h-auto'):
198
+ yield Switch(id='options-follow-redirects')
199
+ yield Label('Follow redirects', classes='pt-1')
200
+ with Horizontal(classes='h-auto'):
201
+ yield Switch(id='options-verify-ssl')
202
+ yield Label('Verify SSL', classes='pt-1')
203
+
204
+ def on_mount(self) -> None:
205
+ self.header_fields = self.query_one('#headers', DynamicFields)
206
+
207
+ self.param_fields = self.query_one('#params', DynamicFields)
208
+
209
+ self.auth_enabled_switch = self.query_one('#auth-enabled', Switch)
210
+ self.auth_mode_switcher = self.query_one(
211
+ '#auth-mode-switcher', ContentSwitcher
212
+ )
213
+ self.auth_mode_select = self.query_one('#auth-mode', Select)
214
+ self.auth_basic_username_input = self.query_one(
215
+ '#auth-basic-username', CustomInput
216
+ )
217
+ self.auth_basic_password_input = self.query_one(
218
+ '#auth-basic-password', PasswordInput
219
+ )
220
+ self.auth_bearer_token_input = self.query_one(
221
+ '#auth-bearer-token', PasswordInput
222
+ )
223
+ self.auth_api_key_key_input = self.query_one(
224
+ '#auth-api-key-key', CustomInput
225
+ )
226
+ self.auth_api_key_value_input = self.query_one(
227
+ '#auth-api-key-value', PasswordInput
228
+ )
229
+ self.auth_api_key_where_select = self.query_one(
230
+ '#auth-api-key-where', Select
231
+ )
232
+ self.auth_digest_username_input = self.query_one(
233
+ '#auth-digest-username', CustomInput
234
+ )
235
+ self.auth_digest_password_input = self.query_one(
236
+ '#auth-digest-password', PasswordInput
237
+ )
238
+
239
+ self.body_enabled_switch = self.query_one('#body-enabled', Switch)
240
+ self.body_mode_select = self.query_one('#body-mode', Select)
241
+ self.body_mode_switcher = self.query_one(
242
+ '#body-mode-switcher', ContentSwitcher
243
+ )
244
+ self.body_raw_editor = self.query_one('#body-raw', CustomTextArea)
245
+ self.body_raw_language_select = self.query_one(
246
+ '#body-raw-language', Select
247
+ )
248
+ self.body_file_path_chooser = self.query_one('#body-file', PathChooser)
249
+ self.body_form_urlencoded_fields = self.query_one(
250
+ '#body-form-urlencoded', DynamicFields
251
+ )
252
+ self.body_form_multipart_fields = self.query_one(
253
+ '#body-form-multipart', DynamicFields
254
+ )
255
+
256
+ self.options_timeout_input = self.query_one(
257
+ '#options-timeout', CustomInput
258
+ )
259
+ self.options_follow_redirects_switch = self.query_one(
260
+ '#options-follow-redirects', Switch
261
+ )
262
+ self.options_verify_ssl_switch = self.query_one(
263
+ '#options-verify-ssl', Switch
264
+ )
265
+
266
+ @property
267
+ def headers(self) -> list[dict[str, str | bool]]:
268
+ return [
269
+ {
270
+ 'enabled': header.enabled,
271
+ 'key': header.key,
272
+ 'value': header.value,
273
+ }
274
+ for header in self.header_fields.fields
275
+ if header.is_filled or header.enabled
276
+ ]
277
+
278
+ @headers.setter
279
+ def headers(self, headers: list[dict[str, str | bool]]) -> None:
280
+ for field in self.header_fields.fields:
281
+ self.header_fields.remove_field(field=field)
282
+
283
+ for header in headers:
284
+ self.run_worker(
285
+ self.header_fields.add_field(
286
+ field=TextDynamicField(
287
+ enabled=header['enabled'],
288
+ key=header['key'],
289
+ value=header['value'],
290
+ ),
291
+ before_last=True,
292
+ )
293
+ )
294
+
295
+ @property
296
+ def params(self) -> list[dict[str, str | bool]]:
297
+ return [
298
+ {
299
+ 'enabled': param.enabled,
300
+ 'key': param.key,
301
+ 'value': param.value,
302
+ }
303
+ for param in self.param_fields.fields
304
+ if param.is_filled or param.enabled
305
+ ]
306
+
307
+ @params.setter
308
+ def params(self, params: list[dict[str, str | bool]]) -> None:
309
+ for field in self.param_fields.fields:
310
+ self.param_fields.remove_field(field=field)
311
+
312
+ for param in params:
313
+ self.run_worker(
314
+ self.param_fields.add_field(
315
+ field=TextDynamicField(
316
+ enabled=param['enabled'],
317
+ key=param['key'],
318
+ value=param['value'],
319
+ ),
320
+ before_last=True,
321
+ )
322
+ )
323
+
324
+ @property
325
+ def auth_enabled(self) -> bool:
326
+ return self.auth_enabled_switch.value
327
+
328
+ @auth_enabled.setter
329
+ def auth_enabled(self, value: bool) -> None:
330
+ self.auth_enabled_switch.value = value
331
+
332
+ @property
333
+ def auth_mode(self) -> AuthMode:
334
+ return self.auth_mode_select.value
335
+
336
+ @auth_mode.setter
337
+ def auth_mode(self, value: AuthMode) -> None:
338
+ self.auth_mode_select.value = value
339
+
340
+ @property
341
+ def auth_basic_username(self) -> str:
342
+ return self.auth_basic_username_input.value
343
+
344
+ @auth_basic_username.setter
345
+ def auth_basic_username(self, value: str) -> None:
346
+ self.auth_basic_username_input.value = value
347
+
348
+ @property
349
+ def auth_basic_password(self) -> str:
350
+ return self.auth_basic_password_input.value
351
+
352
+ @auth_basic_password.setter
353
+ def auth_basic_password(self, value: str) -> None:
354
+ self.auth_basic_password_input.value = value
355
+
356
+ @property
357
+ def auth_bearer_token(self) -> str:
358
+ return self.auth_bearer_token_input.value
359
+
360
+ @auth_bearer_token.setter
361
+ def auth_bearer_token(self, value: str) -> None:
362
+ self.auth_bearer_token_input.value = value
363
+
364
+ @property
365
+ def auth_api_key_key(self) -> str:
366
+ return self.auth_api_key_key_input.value
367
+
368
+ @auth_api_key_key.setter
369
+ def auth_api_key_key(self, value: str) -> None:
370
+ self.auth_api_key_key_input.value = value
371
+
372
+ @property
373
+ def auth_api_key_value(self) -> str:
374
+ return self.auth_api_key_value_input.value
375
+
376
+ @auth_api_key_value.setter
377
+ def auth_api_key_value(self, value: str) -> None:
378
+ self.auth_api_key_value_input.value = value
379
+
380
+ @property
381
+ def auth_api_key_where(self) -> str:
382
+ return self.auth_api_key_where_select.value
383
+
384
+ @auth_api_key_where.setter
385
+ def auth_api_key_where(self, value: str) -> None:
386
+ self.auth_api_key_where_select.value = value
387
+
388
+ @property
389
+ def auth_digest_username(self) -> str:
390
+ return self.auth_digest_username_input.value
391
+
392
+ @auth_digest_username.setter
393
+ def auth_digest_username(self, value: str) -> None:
394
+ self.auth_digest_username_input.value = value
395
+
396
+ @property
397
+ def auth_digest_password(self) -> str:
398
+ return self.auth_digest_password_input.value
399
+
400
+ @auth_digest_password.setter
401
+ def auth_digest_password(self, value: str) -> None:
402
+ self.auth_digest_password_input.value = value
403
+
404
+ @property
405
+ def body_enabled(self) -> bool:
406
+ return self.body_enabled_switch.value
407
+
408
+ @body_enabled.setter
409
+ def body_enabled(self, value: bool) -> None:
410
+ self.body_enabled_switch.value = value
411
+
412
+ @property
413
+ def body_mode(self) -> BodyMode:
414
+ return self.body_mode_select.value
415
+
416
+ @body_mode.setter
417
+ def body_mode(self, value: BodyMode) -> None:
418
+ self.body_mode_select.value = value
419
+
420
+ @property
421
+ def body_raw_language(self) -> BodyRawLanguage:
422
+ return self.body_raw_language_select.value
423
+
424
+ @body_raw_language.setter
425
+ def body_raw_language(self, value: BodyRawLanguage) -> None:
426
+ self.body_raw_language_select.value = value
427
+
428
+ @property
429
+ def body_raw(self) -> str:
430
+ return self.body_raw_editor.text
431
+
432
+ @body_raw.setter
433
+ def body_raw(self, value: str) -> None:
434
+ self.body_raw_editor.text = value
435
+
436
+ @property
437
+ def body_file(self) -> Path | None:
438
+ return self.body_file_path_chooser.path
439
+
440
+ @body_file.setter
441
+ def body_file(self, value: Path | None) -> None:
442
+ self.body_file_path_chooser.path = value
443
+
444
+ @property
445
+ def body_form_urlencoded(self) -> list[dict[str, str | bool]]:
446
+ return [
447
+ {
448
+ 'enabled': field.enabled,
449
+ 'key': field.key,
450
+ 'value': field.value,
451
+ }
452
+ for field in self.body_form_urlencoded_fields.fields
453
+ if field.is_filled or field.enabled
454
+ ]
455
+
456
+ @body_form_urlencoded.setter
457
+ def body_form_urlencoded(
458
+ self, values: list[dict[str, str | bool]]
459
+ ) -> None:
460
+ for field in self.body_form_urlencoded_fields.fields:
461
+ self.body_form_urlencoded_fields.remove_field(field=field)
462
+
463
+ for value in values:
464
+ self.run_worker(
465
+ self.body_form_urlencoded_fields.add_field(
466
+ field=TextDynamicField(
467
+ enabled=value['enabled'],
468
+ key=value['key'],
469
+ value=value['value'],
470
+ ),
471
+ before_last=True,
472
+ )
473
+ )
474
+
475
+ @property
476
+ def body_form_multipart(self) -> list[dict[str, str | bool, Path | None]]:
477
+ return [
478
+ {
479
+ 'enabled': field.enabled,
480
+ 'key': field.key,
481
+ 'value': field.value,
482
+ 'value_kind': field.value_kind,
483
+ }
484
+ for field in self.body_form_multipart_fields.fields
485
+ if field.is_filled or field.enabled
486
+ ]
487
+
488
+ @body_form_multipart.setter
489
+ def body_form_multipart(
490
+ self, values: list[dict[str, str | bool, Path | None]]
491
+ ) -> None:
492
+ for field in self.body_form_multipart_fields.fields:
493
+ self.body_form_multipart_fields.remove_field(field=field)
494
+
495
+ for value in values:
496
+ self.run_worker(
497
+ self.body_form_multipart_fields.add_field(
498
+ TextOrFileDynamicField(
499
+ enabled=value['enabled'],
500
+ key=value['key'],
501
+ value=value['value'],
502
+ value_kind=value['value_kind'],
503
+ ),
504
+ before_last=True,
505
+ )
506
+ )
507
+
508
+ @property
509
+ def option_timeout(self) -> float | None:
510
+ try:
511
+ return float(self.options_timeout_input.value)
512
+ except ValueError:
513
+ return None
514
+
515
+ @option_timeout.setter
516
+ def option_timeout(self, value: float | None) -> None:
517
+ self.options_timeout_input.value = '' if value is None else str(value)
518
+
519
+ @property
520
+ def option_follow_redirects(self) -> bool:
521
+ return self.options_follow_redirects_switch.value
522
+
523
+ @option_follow_redirects.setter
524
+ def option_follow_redirects(self, value: bool) -> None:
525
+ self.options_follow_redirects_switch.value = value
526
+
527
+ @property
528
+ def option_verify_ssl(self) -> bool:
529
+ return self.options_verify_ssl_switch.value
530
+
531
+ @option_verify_ssl.setter
532
+ def option_verify_ssl(self, value: bool) -> None:
533
+ self.options_verify_ssl_switch.value = value
534
+
535
+ def clear(self) -> None:
536
+ self.headers = []
537
+ self.params = []
538
+
539
+ self.auth_enabled = False
540
+ self.auth_mode = AuthMode.BASIC
541
+ self.auth_basic_username = ''
542
+ self.auth_basic_password = ''
543
+ self.auth_bearer_token = ''
544
+ self.auth_api_key_key = ''
545
+ self.auth_api_key_value = ''
546
+ self.auth_api_key_where = 'header'
547
+ self.auth_digest_username = ''
548
+ self.auth_digest_password = ''
549
+
550
+ self.body_enabled = False
551
+ self.body_mode = BodyMode.RAW
552
+ self.body_raw_language = BodyRawLanguage.PLAIN
553
+ self.body_raw = ''
554
+ self.body_file = None
555
+ self.body_form_urlencoded = []
556
+ self.body_form_multipart = []
557
+
558
+ self.option_timeout = None
559
+ self.option_follow_redirects = False
560
+ self.option_verify_ssl = False
561
+
562
+ @on(Select.Changed, '#auth-mode')
563
+ def _on_change_auth_mode(self, message: Select.Changed) -> None:
564
+ if message.value == 'basic':
565
+ self.auth_mode_switcher.current = 'auth-basic'
566
+ elif message.value == 'bearer':
567
+ self.auth_mode_switcher.current = 'auth-bearer'
568
+ elif message.value == 'api_key':
569
+ self.auth_mode_switcher.current = 'auth-api-key'
570
+ elif message.value == 'digest':
571
+ self.auth_mode_switcher.current = 'auth-digest'
572
+
573
+ @on(Select.Changed, '#body-mode')
574
+ def _on_change_body_mode(self, message: Select.Changed) -> None:
575
+ if message.value == BodyMode.FILE:
576
+ self.body_mode_switcher.current = 'body-mode-file'
577
+ elif message.value == BodyMode.RAW:
578
+ self.body_mode_switcher.current = 'body-mode-raw'
579
+ elif message.value == BodyMode.FORM_URLENCODED:
580
+ self.body_mode_switcher.current = 'body-mode-form-urlencoded'
581
+ elif message.value == BodyMode.FORM_MULTIPART:
582
+ self.body_mode_switcher.current = 'body-mode-form-multipart'
583
+
584
+ @on(Select.Changed, '#body-raw-language')
585
+ def _on_change_body_raw_language(self, message: Select.Changed) -> None:
586
+ self.body_raw_editor.language = message.value
587
+
588
+ @on(DynamicFields.FieldFilled, '#body-form-urlencoded')
589
+ def _on_form_filled(self, message: DynamicFields.FieldFilled) -> None:
590
+ self.body_enabled_switch.value = True
591
+
592
+ @on(DynamicFields.FieldEmpty, '#body-form-urlencoded')
593
+ def _on_form_empty(self, message: DynamicFields.FieldEmpty) -> None:
594
+ if not message.control.filled_fields:
595
+ self.body_enabled_switch.value = False
596
+
597
+ @on(CustomTextArea.Changed, '#body-raw')
598
+ def _on_change_body_raw(self, message: CustomTextArea.Changed) -> None:
599
+ if self.body_raw_editor.text == '':
600
+ self.body_enabled_switch.value = False
601
+ else:
602
+ self.body_enabled_switch.value = True
@@ -92,13 +92,16 @@ class ResponseArea(Static):
92
92
 
93
93
  self.headers_data_table.add_columns('Key', 'Value')
94
94
 
95
- def set_data(self, data: ResponseAreaData | None) -> None:
95
+ def clear(self) -> None:
96
96
  self.border_title = self.BORDER_TITLE
97
97
  self.border_subtitle = ''
98
98
  self.headers_data_table.clear()
99
99
  self.body_raw_language_select.value = BodyRawLanguage.PLAIN
100
100
  self.body_raw_editor.clear()
101
101
 
102
+ def set_data(self, data: ResponseAreaData | None) -> None:
103
+ self.clear()
104
+
102
105
  if data is None:
103
106
  return
104
107
 
@@ -0,0 +1,73 @@
1
+ from typing import TYPE_CHECKING
2
+
3
+ from textual import on
4
+ from textual.app import ComposeResult
5
+ from textual.binding import Binding
6
+ from textual.containers import Horizontal, Vertical
7
+ from textual.screen import ModalScreen
8
+ from textual.widgets import Button, Label, Select
9
+
10
+ from restiny.enums import CustomThemes
11
+
12
+ if TYPE_CHECKING:
13
+ from restiny.ui.app import RESTinyApp
14
+
15
+
16
+ class SettingsScreen(ModalScreen):
17
+ app: 'RESTinyApp'
18
+
19
+ DEFAULT_CSS = """
20
+ SettingsScreen {
21
+ align: center middle;
22
+ }
23
+
24
+ #modal-content {
25
+ width: auto;
26
+ height: auto;
27
+ max-width: 40%;
28
+ border: heavy black;
29
+ border-title-color: gray;
30
+ background: $surface;
31
+ }
32
+ """
33
+ AUTO_FOCUS = '#theme'
34
+
35
+ BINDINGS = [
36
+ Binding(
37
+ key='escape',
38
+ action='dismiss',
39
+ description='Quit the screen',
40
+ show=False,
41
+ ),
42
+ ]
43
+
44
+ def compose(self) -> ComposeResult:
45
+ settings = self.app.settings_repo.get().data
46
+ with Vertical(id='modal-content'):
47
+ with Horizontal(classes='w-auto h-auto mt-1 px-1'):
48
+ yield Label('theme', classes='mt-1')
49
+ yield Select(
50
+ [(theme.value, theme.value) for theme in CustomThemes],
51
+ value=settings.theme,
52
+ allow_blank=False,
53
+ id='theme',
54
+ )
55
+ with Horizontal(classes='w-auto h-auto mt-1'):
56
+ yield Button(label='Cancel', classes='w-1fr', id='cancel')
57
+ yield Button(label='Confirm', classes='w-1fr', id='confirm')
58
+
59
+ def on_mount(self) -> None:
60
+ self.modal_content = self.query_one('#modal-content', Vertical)
61
+ self.theme_select = self.query_one('#theme', Select)
62
+ self.cancel_button = self.query_one('#cancel', Button)
63
+ self.confirm_button = self.query_one('#confirm', Button)
64
+
65
+ self.modal_content.border_title = 'Settings'
66
+
67
+ @on(Button.Pressed, '#cancel')
68
+ def _on_cancel(self, message: Button.Pressed) -> None:
69
+ self.dismiss(result=None)
70
+
71
+ @on(Button.Pressed, '#confirm')
72
+ def _on_confirm(self, message: Button.Pressed) -> None:
73
+ self.dismiss(result={'theme': self.theme_select.value})