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.
- restiny/__about__.py +1 -1
- restiny/__main__.py +28 -14
- restiny/assets/style.tcss +56 -2
- restiny/consts.py +236 -0
- restiny/data/db.py +60 -0
- restiny/data/models.py +111 -0
- restiny/data/repos.py +455 -0
- restiny/data/sql/__init__.py +3 -0
- restiny/entities.py +438 -0
- restiny/enums.py +14 -5
- restiny/httpx_auths.py +52 -0
- restiny/ui/__init__.py +17 -0
- restiny/ui/app.py +586 -0
- restiny/ui/collections_area.py +594 -0
- restiny/ui/environments_screen.py +270 -0
- restiny/ui/request_area.py +602 -0
- restiny/{core → ui}/response_area.py +4 -1
- restiny/ui/settings_screen.py +73 -0
- restiny/ui/top_bar_area.py +60 -0
- restiny/{core → ui}/url_area.py +54 -38
- restiny/utils.py +52 -15
- restiny/widgets/__init__.py +15 -1
- restiny/widgets/collections_tree.py +74 -0
- restiny/widgets/confirm_prompt.py +76 -0
- restiny/widgets/custom_input.py +20 -0
- restiny/widgets/dynamic_fields.py +65 -70
- restiny/widgets/password_input.py +161 -0
- restiny/widgets/path_chooser.py +12 -12
- {restiny-0.2.1.dist-info → restiny-0.6.1.dist-info}/METADATA +7 -5
- restiny-0.6.1.dist-info/RECORD +38 -0
- restiny/core/__init__.py +0 -15
- restiny/core/app.py +0 -348
- restiny/core/request_area.py +0 -337
- restiny-0.2.1.dist-info/RECORD +0 -24
- {restiny-0.2.1.dist-info → restiny-0.6.1.dist-info}/WHEEL +0 -0
- {restiny-0.2.1.dist-info → restiny-0.6.1.dist-info}/entry_points.txt +0 -0
- {restiny-0.2.1.dist-info → restiny-0.6.1.dist-info}/licenses/LICENSE +0 -0
- {restiny-0.2.1.dist-info → restiny-0.6.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,594 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import TYPE_CHECKING, Literal
|
|
3
|
+
|
|
4
|
+
from textual import on
|
|
5
|
+
from textual.app import ComposeResult
|
|
6
|
+
from textual.binding import Binding
|
|
7
|
+
from textual.containers import Horizontal, Vertical
|
|
8
|
+
from textual.message import Message
|
|
9
|
+
from textual.screen import ModalScreen
|
|
10
|
+
from textual.widget import Widget
|
|
11
|
+
from textual.widgets import (
|
|
12
|
+
Button,
|
|
13
|
+
ContentSwitcher,
|
|
14
|
+
RadioButton,
|
|
15
|
+
RadioSet,
|
|
16
|
+
Select,
|
|
17
|
+
Static,
|
|
18
|
+
)
|
|
19
|
+
from textual.widgets.tree import TreeNode
|
|
20
|
+
|
|
21
|
+
from restiny.entities import Folder, Request
|
|
22
|
+
from restiny.enums import HTTPMethod
|
|
23
|
+
from restiny.widgets import (
|
|
24
|
+
CollectionsTree,
|
|
25
|
+
ConfirmPrompt,
|
|
26
|
+
ConfirmPromptResult,
|
|
27
|
+
CustomInput,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
if TYPE_CHECKING:
|
|
31
|
+
from restiny.ui.app import RESTinyApp
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class _AddFolderResult:
|
|
36
|
+
id: int
|
|
37
|
+
parent_id: int | None
|
|
38
|
+
name: str
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class _AddRequestResult:
|
|
43
|
+
id: int
|
|
44
|
+
folder_id: int
|
|
45
|
+
name: str
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass
|
|
49
|
+
class _UpdateFolderResult:
|
|
50
|
+
id: int
|
|
51
|
+
parent_id: int | None
|
|
52
|
+
old_parent_id: int | None
|
|
53
|
+
name: str
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass
|
|
57
|
+
class _UpdateRequestResult:
|
|
58
|
+
id: int
|
|
59
|
+
folder_id: int
|
|
60
|
+
old_folder_id: int
|
|
61
|
+
name: str
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class _BaseEditScreen(ModalScreen):
|
|
65
|
+
DEFAULT_CSS = """
|
|
66
|
+
_BaseEditScreen {
|
|
67
|
+
align: center middle;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
#modal-content {
|
|
71
|
+
border: heavy black;
|
|
72
|
+
border-title-color: gray;
|
|
73
|
+
background: $surface;
|
|
74
|
+
width: auto;
|
|
75
|
+
height: auto;
|
|
76
|
+
max-width: 40%
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
_BaseEditScreen RadioSet > RadioButton.-selected {
|
|
80
|
+
background: $surface;
|
|
81
|
+
}
|
|
82
|
+
"""
|
|
83
|
+
AUTO_FOCUS = '#name'
|
|
84
|
+
|
|
85
|
+
BINDINGS = [
|
|
86
|
+
Binding(
|
|
87
|
+
key='escape',
|
|
88
|
+
action='dismiss',
|
|
89
|
+
description='Quit the screen',
|
|
90
|
+
show=False,
|
|
91
|
+
),
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
def __init__(
|
|
95
|
+
self,
|
|
96
|
+
kind: Literal['request', 'folder'] = 'request',
|
|
97
|
+
name: str = '',
|
|
98
|
+
parents: list[tuple[str, int | None]] = [],
|
|
99
|
+
parent_id: int | None = None,
|
|
100
|
+
) -> None:
|
|
101
|
+
super().__init__()
|
|
102
|
+
self._kind = kind
|
|
103
|
+
self._name = name
|
|
104
|
+
self._parents = parents
|
|
105
|
+
self._parent_id = parent_id
|
|
106
|
+
|
|
107
|
+
def compose(self) -> ComposeResult:
|
|
108
|
+
with Vertical(id='modal-content'):
|
|
109
|
+
with Horizontal(classes='w-auto h-auto mt-1'):
|
|
110
|
+
with RadioSet(id='kind', classes='w-auto', compact=True):
|
|
111
|
+
yield RadioButton(
|
|
112
|
+
'request',
|
|
113
|
+
value=self._kind == 'request',
|
|
114
|
+
classes='w-auto',
|
|
115
|
+
)
|
|
116
|
+
yield RadioButton(
|
|
117
|
+
'folder',
|
|
118
|
+
value=self._kind == 'folder',
|
|
119
|
+
classes='w-auto',
|
|
120
|
+
)
|
|
121
|
+
with Horizontal(classes='w-auto h-auto mt-1'):
|
|
122
|
+
yield CustomInput(
|
|
123
|
+
value=self._name,
|
|
124
|
+
placeholder='Title',
|
|
125
|
+
select_on_focus=False,
|
|
126
|
+
classes='w-1fr',
|
|
127
|
+
id='name',
|
|
128
|
+
)
|
|
129
|
+
with Horizontal(classes='w-auto h-auto mt-1'):
|
|
130
|
+
yield Select(
|
|
131
|
+
self._parents,
|
|
132
|
+
value=self._parent_id,
|
|
133
|
+
tooltip='Parent',
|
|
134
|
+
allow_blank=False,
|
|
135
|
+
id='parent',
|
|
136
|
+
)
|
|
137
|
+
with Horizontal(classes='w-auto h-auto mt-1'):
|
|
138
|
+
yield Button(label='Cancel', classes='w-1fr', id='cancel')
|
|
139
|
+
yield Button(label='Confirm', classes='w-1fr', id='confirm')
|
|
140
|
+
|
|
141
|
+
def on_mount(self) -> None:
|
|
142
|
+
self.modal_content = self.query_one('#modal-content', Vertical)
|
|
143
|
+
self.kind_radio_set = self.query_one('#kind', RadioSet)
|
|
144
|
+
self.name_input = self.query_one('#name', CustomInput)
|
|
145
|
+
self.parent_select = self.query_one('#parent', Select)
|
|
146
|
+
self.cancel_button = self.query_one('#cancel', Button)
|
|
147
|
+
self.confirm_button = self.query_one('#confirm', Button)
|
|
148
|
+
|
|
149
|
+
self.modal_content.border_title = 'Create request/folder'
|
|
150
|
+
|
|
151
|
+
@on(Button.Pressed, '#cancel')
|
|
152
|
+
def _on_cancel(self, message: Button.Pressed) -> None:
|
|
153
|
+
self.dismiss(result=None)
|
|
154
|
+
|
|
155
|
+
def _common_validation(self) -> bool:
|
|
156
|
+
kind: str = self.kind_radio_set.pressed_button.label
|
|
157
|
+
name: str = self.name_input.value
|
|
158
|
+
parent_id: int | None = self.parent_select.value
|
|
159
|
+
|
|
160
|
+
if not name:
|
|
161
|
+
self.app.notify('Name is required', severity='error')
|
|
162
|
+
return False
|
|
163
|
+
if parent_id is None and kind == 'request':
|
|
164
|
+
self.app.notify(
|
|
165
|
+
'Requests must belong to a folder',
|
|
166
|
+
severity='error',
|
|
167
|
+
)
|
|
168
|
+
return False
|
|
169
|
+
|
|
170
|
+
return True
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class _AddScreen(_BaseEditScreen):
|
|
174
|
+
app: 'RESTinyApp'
|
|
175
|
+
|
|
176
|
+
@on(Button.Pressed, '#confirm')
|
|
177
|
+
def _on_confirm(self, message: Button.Pressed) -> None:
|
|
178
|
+
if not self._common_validation():
|
|
179
|
+
return
|
|
180
|
+
|
|
181
|
+
kind: str = self.kind_radio_set.pressed_button.label
|
|
182
|
+
name: str = self.name_input.value
|
|
183
|
+
parent_id: int | None = self.parent_select.value
|
|
184
|
+
|
|
185
|
+
if kind == 'folder':
|
|
186
|
+
resp = self.app.folders_repo.create(
|
|
187
|
+
Folder(name=name, parent_id=parent_id)
|
|
188
|
+
)
|
|
189
|
+
if not resp.ok:
|
|
190
|
+
self.app.notify(
|
|
191
|
+
f'Failed to create folder ({resp.status})',
|
|
192
|
+
severity='error',
|
|
193
|
+
)
|
|
194
|
+
return
|
|
195
|
+
self.app.notify('Folder created', severity='information')
|
|
196
|
+
self.dismiss(
|
|
197
|
+
result=_AddFolderResult(
|
|
198
|
+
id=resp.data.id,
|
|
199
|
+
parent_id=parent_id,
|
|
200
|
+
name=name,
|
|
201
|
+
)
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
elif kind == 'request':
|
|
205
|
+
resp = self.app.requests_repo.create(
|
|
206
|
+
Request(name=name, folder_id=parent_id)
|
|
207
|
+
)
|
|
208
|
+
if not resp.ok:
|
|
209
|
+
self.app.notify(
|
|
210
|
+
f'Failed to create request ({resp.status})',
|
|
211
|
+
severity='error',
|
|
212
|
+
)
|
|
213
|
+
return
|
|
214
|
+
self.app.notify('Request created', severity='information')
|
|
215
|
+
self.dismiss(
|
|
216
|
+
result=_AddRequestResult(
|
|
217
|
+
id=resp.data.id, folder_id=parent_id, name=name
|
|
218
|
+
)
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
class _UpdateScreen(_BaseEditScreen):
|
|
223
|
+
app: 'RESTinyApp'
|
|
224
|
+
|
|
225
|
+
def __init__(self, id: int, *args, **kwargs) -> None:
|
|
226
|
+
super().__init__(*args, **kwargs)
|
|
227
|
+
self._id = id
|
|
228
|
+
|
|
229
|
+
def on_mount(self) -> None:
|
|
230
|
+
super().on_mount()
|
|
231
|
+
self.kind_radio_set.disabled = True
|
|
232
|
+
|
|
233
|
+
@on(Button.Pressed, '#confirm')
|
|
234
|
+
def _on_confirm(self, message: Button.Pressed) -> None:
|
|
235
|
+
if not self._common_validation():
|
|
236
|
+
return
|
|
237
|
+
|
|
238
|
+
kind: str = self.kind_radio_set.pressed_button.label
|
|
239
|
+
name: str = self.name_input.value
|
|
240
|
+
parent_id: int | None = self.parent_select.value
|
|
241
|
+
old_parent_id: int | None = self._parent_id
|
|
242
|
+
|
|
243
|
+
if kind == 'folder':
|
|
244
|
+
folder = self.app.folders_repo.get_by_id(id=self._id).data
|
|
245
|
+
folder.name = name
|
|
246
|
+
folder.parent_id = parent_id
|
|
247
|
+
resp = self.app.folders_repo.update(folder=folder)
|
|
248
|
+
if not resp.ok:
|
|
249
|
+
self.app.notify(
|
|
250
|
+
f'Failed to update folder ({resp.status})',
|
|
251
|
+
severity='error',
|
|
252
|
+
)
|
|
253
|
+
return
|
|
254
|
+
self.app.notify('Folder updated', severity='information')
|
|
255
|
+
self.dismiss(
|
|
256
|
+
result=_UpdateFolderResult(
|
|
257
|
+
id=resp.data.id,
|
|
258
|
+
parent_id=parent_id,
|
|
259
|
+
old_parent_id=old_parent_id,
|
|
260
|
+
name=name,
|
|
261
|
+
)
|
|
262
|
+
)
|
|
263
|
+
elif kind == 'request':
|
|
264
|
+
request = self.app.requests_repo.get_by_id(id=self._id).data
|
|
265
|
+
request.name = name
|
|
266
|
+
request.folder_id = parent_id
|
|
267
|
+
update_resp = self.app.requests_repo.update(request)
|
|
268
|
+
if not update_resp.ok:
|
|
269
|
+
self.app.notify(
|
|
270
|
+
f'Failed to update request ({update_resp.status})',
|
|
271
|
+
severity='error',
|
|
272
|
+
)
|
|
273
|
+
return
|
|
274
|
+
self.app.notify('Request updated', severity='information')
|
|
275
|
+
self.dismiss(
|
|
276
|
+
result=_UpdateRequestResult(
|
|
277
|
+
id=update_resp.data.id,
|
|
278
|
+
folder_id=parent_id,
|
|
279
|
+
old_folder_id=old_parent_id,
|
|
280
|
+
name=name,
|
|
281
|
+
)
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
class CollectionsArea(Widget):
|
|
286
|
+
app: 'RESTinyApp'
|
|
287
|
+
|
|
288
|
+
ALLOW_MAXIMIZE = True
|
|
289
|
+
focusable = True
|
|
290
|
+
DEFAULT_CSS = """
|
|
291
|
+
CollectionsArea {
|
|
292
|
+
width: 1fr;
|
|
293
|
+
height: 1fr;
|
|
294
|
+
border: heavy black;
|
|
295
|
+
border-title-color: gray;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
Static {
|
|
299
|
+
padding: 1;
|
|
300
|
+
}
|
|
301
|
+
"""
|
|
302
|
+
|
|
303
|
+
class RequestAdded(Message):
|
|
304
|
+
def __init__(self, request_id: int) -> None:
|
|
305
|
+
super().__init__()
|
|
306
|
+
self.request_id = request_id
|
|
307
|
+
|
|
308
|
+
class RequestUpdated(Message):
|
|
309
|
+
def __init__(self, request_id: int) -> None:
|
|
310
|
+
super().__init__()
|
|
311
|
+
self.request_id = request_id
|
|
312
|
+
|
|
313
|
+
class RequestDeleted(Message):
|
|
314
|
+
def __init__(self, request_id: int) -> None:
|
|
315
|
+
super().__init__()
|
|
316
|
+
self.request_id = request_id
|
|
317
|
+
|
|
318
|
+
class RequestSelected(Message):
|
|
319
|
+
def __init__(self, request_id: int) -> None:
|
|
320
|
+
super().__init__()
|
|
321
|
+
self.request_id = request_id
|
|
322
|
+
|
|
323
|
+
class FolderAdded(Message):
|
|
324
|
+
def __init__(self, folder_id: int) -> None:
|
|
325
|
+
super().__init__()
|
|
326
|
+
self.folder_id = folder_id
|
|
327
|
+
|
|
328
|
+
class FolderUpdated(Message):
|
|
329
|
+
def __init__(self, folder_id: int) -> None:
|
|
330
|
+
super().__init__()
|
|
331
|
+
self.folder_id = folder_id
|
|
332
|
+
|
|
333
|
+
class FolderDeleted(Message):
|
|
334
|
+
def __init__(self, folder_id: int) -> None:
|
|
335
|
+
super().__init__()
|
|
336
|
+
self.folder_id = folder_id
|
|
337
|
+
|
|
338
|
+
class FolderSelected(Message):
|
|
339
|
+
def __init__(self, folder_id: int) -> None:
|
|
340
|
+
super().__init__()
|
|
341
|
+
self.folder_id = folder_id
|
|
342
|
+
|
|
343
|
+
def compose(self) -> ComposeResult:
|
|
344
|
+
with ContentSwitcher(id='switcher', initial='no-content'):
|
|
345
|
+
yield Static(
|
|
346
|
+
"[i]No collections yet. Press [b]'ctrl+n'[/] to create your first one.[/]",
|
|
347
|
+
id='no-content',
|
|
348
|
+
)
|
|
349
|
+
yield CollectionsTree('Collections', id='content')
|
|
350
|
+
|
|
351
|
+
def on_mount(self) -> None:
|
|
352
|
+
self.content_switcher = self.query_one(ContentSwitcher)
|
|
353
|
+
self.collections_tree = self.query_one(CollectionsTree)
|
|
354
|
+
self.border_title = 'Collections'
|
|
355
|
+
|
|
356
|
+
self._populate_children(node=self.collections_tree.root)
|
|
357
|
+
self._sync_content_switcher()
|
|
358
|
+
|
|
359
|
+
def prompt_add(self) -> None:
|
|
360
|
+
parents = [
|
|
361
|
+
(parent['path'], parent['id'])
|
|
362
|
+
for parent in self._resolve_all_folder_paths()
|
|
363
|
+
]
|
|
364
|
+
parent_id = self.collections_tree.current_folder.data['id']
|
|
365
|
+
self.app.push_screen(
|
|
366
|
+
screen=_AddScreen(parents=parents, parent_id=parent_id),
|
|
367
|
+
callback=self._on_prompt_add_result,
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
def prompt_update(self) -> None:
|
|
371
|
+
if not self.collections_tree.cursor_node:
|
|
372
|
+
return
|
|
373
|
+
|
|
374
|
+
node = self.collections_tree.cursor_node
|
|
375
|
+
kind = None
|
|
376
|
+
parents = []
|
|
377
|
+
if node.allow_expand:
|
|
378
|
+
kind = 'folder'
|
|
379
|
+
parents = [
|
|
380
|
+
(parent['path'], parent['id'])
|
|
381
|
+
for parent in self._resolve_all_folder_paths()
|
|
382
|
+
if parent['id'] != node.data['id']
|
|
383
|
+
]
|
|
384
|
+
else:
|
|
385
|
+
kind = 'request'
|
|
386
|
+
parents = [
|
|
387
|
+
(parent['path'], parent['id'])
|
|
388
|
+
for parent in self._resolve_all_folder_paths()
|
|
389
|
+
]
|
|
390
|
+
|
|
391
|
+
parent_id = self.collections_tree.current_parent_folder.data['id']
|
|
392
|
+
self.app.push_screen(
|
|
393
|
+
screen=_UpdateScreen(
|
|
394
|
+
kind=kind,
|
|
395
|
+
name=node.data['name'],
|
|
396
|
+
parents=parents,
|
|
397
|
+
parent_id=parent_id,
|
|
398
|
+
id=node.data['id'],
|
|
399
|
+
),
|
|
400
|
+
callback=self._on_prompt_update_result,
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
def prompt_delete(self) -> None:
|
|
404
|
+
if not self.collections_tree.cursor_node:
|
|
405
|
+
return
|
|
406
|
+
|
|
407
|
+
self.app.push_screen(
|
|
408
|
+
screen=ConfirmPrompt(
|
|
409
|
+
message='Are you sure? This action cannot be undone.'
|
|
410
|
+
),
|
|
411
|
+
callback=self._on_prompt_delete_result,
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
@on(CollectionsTree.NodeExpanded)
|
|
415
|
+
def _on_node_expanded(self, message: CollectionsTree.NodeExpanded) -> None:
|
|
416
|
+
self._populate_children(node=message.node)
|
|
417
|
+
|
|
418
|
+
@on(CollectionsTree.NodeSelected)
|
|
419
|
+
def _on_node_selected(self, message: CollectionsTree.NodeSelected) -> None:
|
|
420
|
+
if message.node.allow_expand:
|
|
421
|
+
self.post_message(
|
|
422
|
+
message=self.FolderSelected(folder_id=message.node.data['id'])
|
|
423
|
+
)
|
|
424
|
+
else:
|
|
425
|
+
self.post_message(
|
|
426
|
+
message=self.RequestSelected(
|
|
427
|
+
request_id=message.node.data['id']
|
|
428
|
+
)
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
def _on_prompt_add_result(
|
|
432
|
+
self, result: _AddFolderResult | _AddRequestResult | None
|
|
433
|
+
) -> None:
|
|
434
|
+
if result is None:
|
|
435
|
+
return
|
|
436
|
+
|
|
437
|
+
if isinstance(result, _AddRequestResult):
|
|
438
|
+
parent_node = self.collections_tree.node_by_id[result.folder_id]
|
|
439
|
+
self._populate_children(parent_node)
|
|
440
|
+
self._sync_content_switcher()
|
|
441
|
+
self.post_message(message=self.RequestAdded(request_id=result.id))
|
|
442
|
+
elif isinstance(result, _AddFolderResult):
|
|
443
|
+
parent_node = self.collections_tree.node_by_id[result.parent_id]
|
|
444
|
+
self._populate_children(parent_node)
|
|
445
|
+
self._sync_content_switcher()
|
|
446
|
+
self.post_message(message=self.FolderAdded(folder_id=result.id))
|
|
447
|
+
|
|
448
|
+
def _on_prompt_update_result(
|
|
449
|
+
self, result: _UpdateFolderResult | _UpdateRequestResult | None
|
|
450
|
+
) -> None:
|
|
451
|
+
if result is None:
|
|
452
|
+
return
|
|
453
|
+
|
|
454
|
+
if isinstance(result, _UpdateRequestResult):
|
|
455
|
+
parent_node = self.collections_tree.node_by_id[result.folder_id]
|
|
456
|
+
old_parent_node = self.collections_tree.node_by_id[
|
|
457
|
+
result.old_folder_id
|
|
458
|
+
]
|
|
459
|
+
self._populate_children(parent_node)
|
|
460
|
+
self._populate_children(old_parent_node)
|
|
461
|
+
self._sync_content_switcher()
|
|
462
|
+
self.post_message(
|
|
463
|
+
message=self.RequestUpdated(request_id=result.id)
|
|
464
|
+
)
|
|
465
|
+
elif isinstance(result, _UpdateFolderResult):
|
|
466
|
+
parent_node = self.collections_tree.node_by_id[result.parent_id]
|
|
467
|
+
old_parent_node = self.collections_tree.node_by_id[
|
|
468
|
+
result.old_parent_id
|
|
469
|
+
]
|
|
470
|
+
self._populate_children(parent_node)
|
|
471
|
+
self._populate_children(old_parent_node)
|
|
472
|
+
self._sync_content_switcher()
|
|
473
|
+
self.post_message(message=self.FolderUpdated(folder_id=result.id))
|
|
474
|
+
|
|
475
|
+
def _on_prompt_delete_result(self, result: ConfirmPromptResult) -> None:
|
|
476
|
+
if not result.confirmed:
|
|
477
|
+
return
|
|
478
|
+
|
|
479
|
+
try:
|
|
480
|
+
prev_selected_index_in_parent = (
|
|
481
|
+
self.collections_tree.cursor_node.parent.children.index(
|
|
482
|
+
self.collections_tree.cursor_node
|
|
483
|
+
)
|
|
484
|
+
)
|
|
485
|
+
except ValueError:
|
|
486
|
+
prev_selected_index_in_parent = 0
|
|
487
|
+
|
|
488
|
+
if self.collections_tree.cursor_node.allow_expand:
|
|
489
|
+
self.app.folders_repo.delete_by_id(
|
|
490
|
+
self.collections_tree.cursor_node.data['id']
|
|
491
|
+
)
|
|
492
|
+
self.notify('Folder deleted', severity='information')
|
|
493
|
+
self._populate_children(
|
|
494
|
+
node=self.collections_tree.cursor_node.parent
|
|
495
|
+
)
|
|
496
|
+
self._sync_content_switcher()
|
|
497
|
+
self.post_message(
|
|
498
|
+
message=self.FolderDeleted(
|
|
499
|
+
folder_id=self.collections_tree.cursor_node.data['id']
|
|
500
|
+
)
|
|
501
|
+
)
|
|
502
|
+
else:
|
|
503
|
+
self.app.requests_repo.delete_by_id(
|
|
504
|
+
self.collections_tree.cursor_node.data['id']
|
|
505
|
+
)
|
|
506
|
+
self.notify('Request deleted', severity='information')
|
|
507
|
+
self._populate_children(
|
|
508
|
+
node=self.collections_tree.cursor_node.parent
|
|
509
|
+
)
|
|
510
|
+
self._sync_content_switcher()
|
|
511
|
+
self.post_message(
|
|
512
|
+
message=self.RequestDeleted(
|
|
513
|
+
request_id=self.collections_tree.cursor_node.data['id']
|
|
514
|
+
)
|
|
515
|
+
)
|
|
516
|
+
|
|
517
|
+
if self.collections_tree.cursor_node.parent.children:
|
|
518
|
+
next_index_to_select = min(
|
|
519
|
+
prev_selected_index_in_parent,
|
|
520
|
+
len(self.collections_tree.cursor_node.parent.children) - 1,
|
|
521
|
+
)
|
|
522
|
+
next_node_to_select = (
|
|
523
|
+
self.collections_tree.cursor_node.parent.children[
|
|
524
|
+
next_index_to_select
|
|
525
|
+
]
|
|
526
|
+
)
|
|
527
|
+
else:
|
|
528
|
+
next_node_to_select = self.collections_tree.cursor_node.parent
|
|
529
|
+
self.call_after_refresh(
|
|
530
|
+
lambda: self.collections_tree.select_node(next_node_to_select)
|
|
531
|
+
)
|
|
532
|
+
|
|
533
|
+
def _populate_children(self, node: TreeNode) -> None:
|
|
534
|
+
folder_id = node.data['id']
|
|
535
|
+
|
|
536
|
+
folders = self.app.folders_repo.list_by_parent_id(folder_id).data
|
|
537
|
+
requests = self.app.requests_repo.list_by_folder_id(folder_id).data
|
|
538
|
+
|
|
539
|
+
def sort_requests(request: Request) -> tuple:
|
|
540
|
+
methods = [method.value for method in HTTPMethod]
|
|
541
|
+
method_order = {
|
|
542
|
+
method: index for index, method in enumerate(methods)
|
|
543
|
+
}
|
|
544
|
+
return (method_order[request.method], request.name.lower())
|
|
545
|
+
|
|
546
|
+
sorted_folders = sorted(
|
|
547
|
+
folders, key=lambda folder: folder.name.lower()
|
|
548
|
+
)
|
|
549
|
+
sorted_requests = sorted(requests, key=sort_requests)
|
|
550
|
+
|
|
551
|
+
for child_node in list(node.children):
|
|
552
|
+
self.collections_tree.remove(child_node)
|
|
553
|
+
|
|
554
|
+
for folder in sorted_folders:
|
|
555
|
+
self.collections_tree.add_folder(
|
|
556
|
+
parent_node=node, name=folder.name, id=folder.id
|
|
557
|
+
)
|
|
558
|
+
|
|
559
|
+
for request in sorted_requests:
|
|
560
|
+
self.collections_tree.add_request(
|
|
561
|
+
parent_node=node,
|
|
562
|
+
method=request.method,
|
|
563
|
+
name=request.name,
|
|
564
|
+
id=request.id,
|
|
565
|
+
)
|
|
566
|
+
|
|
567
|
+
node.refresh()
|
|
568
|
+
|
|
569
|
+
def _resolve_all_folder_paths(self) -> list[dict[str, str | int | None]]:
|
|
570
|
+
paths: list[dict[str, str | int | None]] = [{'path': '/', 'id': None}]
|
|
571
|
+
|
|
572
|
+
paths_stack: list[tuple[str, int | None]] = [('/', None)]
|
|
573
|
+
while paths_stack:
|
|
574
|
+
parent_path, parent_id = paths_stack.pop(0)
|
|
575
|
+
|
|
576
|
+
if parent_id is None:
|
|
577
|
+
children = self.app.folders_repo.list_roots().data
|
|
578
|
+
else:
|
|
579
|
+
children = self.app.folders_repo.list_by_parent_id(
|
|
580
|
+
parent_id
|
|
581
|
+
).data
|
|
582
|
+
|
|
583
|
+
for folder in children:
|
|
584
|
+
path = f'{parent_path.rstrip("/")}/{folder.name}'
|
|
585
|
+
paths.append({'path': path, 'id': folder.id})
|
|
586
|
+
paths_stack.append((path, folder.id))
|
|
587
|
+
|
|
588
|
+
return paths
|
|
589
|
+
|
|
590
|
+
def _sync_content_switcher(self) -> None:
|
|
591
|
+
if self.collections_tree.root.children:
|
|
592
|
+
self.content_switcher.current = 'content'
|
|
593
|
+
else:
|
|
594
|
+
self.content_switcher.current = 'no-content'
|