difonlib 0.4.0__tar.gz → 0.4.2__tar.gz
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.
- {difonlib-0.4.0 → difonlib-0.4.2}/PKG-INFO +1 -1
- {difonlib-0.4.0 → difonlib-0.4.2}/pyproject.toml +1 -1
- difonlib-0.4.2/src/difonlib/ng_lib.py +262 -0
- difonlib-0.4.2/src/difonlib/ng_lib_test3.py +77 -0
- {difonlib-0.4.0 → difonlib-0.4.2}/src/difonlib/tuya_devs.py +9 -2
- {difonlib-0.4.0 → difonlib-0.4.2}/src/difonlib.egg-info/PKG-INFO +1 -1
- {difonlib-0.4.0 → difonlib-0.4.2}/src/difonlib.egg-info/SOURCES.txt +1 -0
- difonlib-0.4.0/src/difonlib/ng_lib.py +0 -130
- {difonlib-0.4.0 → difonlib-0.4.2}/README.md +0 -0
- {difonlib-0.4.0 → difonlib-0.4.2}/setup.cfg +0 -0
- {difonlib-0.4.0 → difonlib-0.4.2}/src/difonlib/__init__.py +0 -0
- {difonlib-0.4.0 → difonlib-0.4.2}/src/difonlib/bt_scanner.py +0 -0
- {difonlib-0.4.0 → difonlib-0.4.2}/src/difonlib/bt_utils.py +0 -0
- {difonlib-0.4.0 → difonlib-0.4.2}/src/difonlib/conn_kbd_devs.py +0 -0
- {difonlib-0.4.0 → difonlib-0.4.2}/src/difonlib/docker-android/get_tuya_preferences.py +0 -0
- {difonlib-0.4.0 → difonlib-0.4.2}/src/difonlib/input_devs.py +0 -0
- {difonlib-0.4.0 → difonlib-0.4.2}/src/difonlib/py.typed +0 -0
- {difonlib-0.4.0 → difonlib-0.4.2}/src/difonlib/remctrl.py +0 -0
- {difonlib-0.4.0 → difonlib-0.4.2}/src/difonlib/utils.py +0 -0
- {difonlib-0.4.0 → difonlib-0.4.2}/src/difonlib.egg-info/dependency_links.txt +0 -0
- {difonlib-0.4.0 → difonlib-0.4.2}/src/difonlib.egg-info/requires.txt +0 -0
- {difonlib-0.4.0 → difonlib-0.4.2}/src/difonlib.egg-info/top_level.txt +0 -0
- {difonlib-0.4.0 → difonlib-0.4.2}/tests/test_bt_utils.py +0 -0
- {difonlib-0.4.0 → difonlib-0.4.2}/tests/test_utils.py +0 -0
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
from nicegui import ui
|
|
2
|
+
import inspect
|
|
3
|
+
import asyncio
|
|
4
|
+
from typing import Any, Callable, Awaitable, Literal
|
|
5
|
+
|
|
6
|
+
from difonlib.utils import logdbg
|
|
7
|
+
|
|
8
|
+
dbg = logdbg
|
|
9
|
+
# https://claude.ai/share/227ce46f-f825-4783-8e0e-9e774963ec84
|
|
10
|
+
# Или точечнее — только ui.label внутри диалога:
|
|
11
|
+
# python.q-dialog .q-card .q-item__label {
|
|
12
|
+
# font-size: 20px !important;
|
|
13
|
+
# }
|
|
14
|
+
# Либо ещё проще — прямо в коде диалога:
|
|
15
|
+
# ui.label(text=text).classes("text-xl")
|
|
16
|
+
# text-xl — это Tailwind, соответствует 1.25rem. Если нужно крупнее — text-2xl, text-3xl.
|
|
17
|
+
ui.add_css(
|
|
18
|
+
"""
|
|
19
|
+
.q-notification__message {
|
|
20
|
+
font-size: 20px !important;
|
|
21
|
+
}
|
|
22
|
+
.q-dialog .q-card {
|
|
23
|
+
font-size: 20px !important;
|
|
24
|
+
}
|
|
25
|
+
""",
|
|
26
|
+
shared=True,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class DialogBox:
|
|
31
|
+
async def dialog_ok(self, text: str = "Hello!)") -> None:
|
|
32
|
+
with ui.dialog().props("persistent") as dialog, ui.card():
|
|
33
|
+
ui.label(text=text)
|
|
34
|
+
ui.button("Close", on_click=dialog.close)
|
|
35
|
+
dialog.open()
|
|
36
|
+
|
|
37
|
+
async def dialog_ok_cancel(
|
|
38
|
+
self,
|
|
39
|
+
text: str,
|
|
40
|
+
on_click_ok: Callable,
|
|
41
|
+
on_click_cancel: Callable = lambda: None,
|
|
42
|
+
btn_ok: str = "OK",
|
|
43
|
+
btn_cancel: str = "Cancel",
|
|
44
|
+
) -> None:
|
|
45
|
+
with ui.dialog().props("persistent") as dialog, ui.card():
|
|
46
|
+
ui.label(text=text)
|
|
47
|
+
with ui.row():
|
|
48
|
+
ui.button(
|
|
49
|
+
btn_ok,
|
|
50
|
+
on_click=lambda: (on_click_ok(), dialog.close()),
|
|
51
|
+
)
|
|
52
|
+
ui.button(btn_cancel, on_click=lambda: (on_click_cancel(), dialog.close()))
|
|
53
|
+
dialog.open()
|
|
54
|
+
|
|
55
|
+
async def dialog_confirm(
|
|
56
|
+
self, text: str = "Are you sure?", btn_ok: str = "Yes", btn_cancel: str = "No"
|
|
57
|
+
) -> ui.dialog:
|
|
58
|
+
with ui.dialog().props("persistent") as dialog, ui.card():
|
|
59
|
+
ui.label(text=text)
|
|
60
|
+
with ui.row():
|
|
61
|
+
ui.button(btn_ok, on_click=lambda: dialog.submit(btn_ok))
|
|
62
|
+
ui.button(btn_cancel, on_click=lambda: dialog.submit(btn_cancel))
|
|
63
|
+
return await dialog
|
|
64
|
+
|
|
65
|
+
async def dialog_input(
|
|
66
|
+
self,
|
|
67
|
+
text: str,
|
|
68
|
+
input_prefix: str = "",
|
|
69
|
+
**kwrds: Any,
|
|
70
|
+
) -> ui.dialog:
|
|
71
|
+
with ui.dialog().props("persistent") as dialog, ui.card():
|
|
72
|
+
ui.label(text=text)
|
|
73
|
+
with ui.row():
|
|
74
|
+
inp: ui.input = ui.input(value=input_prefix, **kwrds).on(
|
|
75
|
+
"keydown.enter", lambda: dialog.submit(inp.value)
|
|
76
|
+
)
|
|
77
|
+
ui.button(text="Enter", on_click=lambda: dialog.submit(inp.value))
|
|
78
|
+
ui.button(text="Cancel", on_click=dialog.close)
|
|
79
|
+
return await dialog
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class CardTable:
|
|
83
|
+
_current_yes_handler: Callable[[], None | Awaitable[None]] | None = None
|
|
84
|
+
|
|
85
|
+
def __init__(
|
|
86
|
+
self,
|
|
87
|
+
title: str,
|
|
88
|
+
columns: list,
|
|
89
|
+
rows: list[dict] | None = None,
|
|
90
|
+
selection: Literal["single", "multiple"] | None = None,
|
|
91
|
+
on_selection_change: list[Callable] | None = None,
|
|
92
|
+
marked_text_color: str = "yellow",
|
|
93
|
+
marked_field: str = "_marked",
|
|
94
|
+
):
|
|
95
|
+
self.on_selection_change: list[Callable] = on_selection_change or []
|
|
96
|
+
self.buttons_on_row_select_changed: list = []
|
|
97
|
+
self.marked_text_color = marked_text_color
|
|
98
|
+
self.marked_field = marked_field
|
|
99
|
+
|
|
100
|
+
with ui.card().classes("p-4 shadow-lg") as self.card:
|
|
101
|
+
with ui.row().classes("items-center justify-between w-full mb-2"):
|
|
102
|
+
with ui.row().classes("gap-2") as self.left_table:
|
|
103
|
+
pass
|
|
104
|
+
ui.label(f"📋 {title}").classes("text-green-700 text-lg font-bold")
|
|
105
|
+
with ui.row().classes("gap-2") as self.top_table:
|
|
106
|
+
pass
|
|
107
|
+
|
|
108
|
+
self.table = ui.table(
|
|
109
|
+
columns=self._normalize_columns(columns),
|
|
110
|
+
rows=self.enum_data(rows or []),
|
|
111
|
+
row_key="sn",
|
|
112
|
+
selection=selection,
|
|
113
|
+
on_select=self._on_selection_change,
|
|
114
|
+
column_defaults={
|
|
115
|
+
"align": "left",
|
|
116
|
+
"headerClasses": "uppercase",
|
|
117
|
+
},
|
|
118
|
+
).classes("w-full shadow-lg bg-black-900 text-gray-200")
|
|
119
|
+
|
|
120
|
+
with (
|
|
121
|
+
ui.dialog().props("persistent") as self.confirm_dialog,
|
|
122
|
+
ui.card().classes("p-4"),
|
|
123
|
+
):
|
|
124
|
+
self.dialog_title = ui.label().classes("text-lg font-bold mb-4")
|
|
125
|
+
with ui.row().classes("justify-end w-full gap-2"):
|
|
126
|
+
ui.button("No", color="red", on_click=self.confirm_dialog.close)
|
|
127
|
+
ui.button("Yes", color="green", on_click=self._on_yes_clicked)
|
|
128
|
+
|
|
129
|
+
with (
|
|
130
|
+
ui.dialog().props("persistent") as self.processing_dialog,
|
|
131
|
+
ui.card().classes("p-4 items-center justify-center"),
|
|
132
|
+
):
|
|
133
|
+
with ui.row().classes("items-center gap-3"):
|
|
134
|
+
self.processing_spinner = ui.spinner(size="md")
|
|
135
|
+
self.processing_label = ui.label("Processing...").classes("text-base")
|
|
136
|
+
|
|
137
|
+
if self.marked_field:
|
|
138
|
+
color_class = f"text-{self.marked_text_color}"
|
|
139
|
+
for column in self.table.columns:
|
|
140
|
+
cell = column["name"]
|
|
141
|
+
self.table.add_slot(
|
|
142
|
+
f"body-cell-{cell}",
|
|
143
|
+
rf"""
|
|
144
|
+
<q-td :props="props">
|
|
145
|
+
<span :class="props.row.{self.marked_field} ? 'font-bold {color_class}' : ''">
|
|
146
|
+
{{{{props.value}}}}
|
|
147
|
+
</span>
|
|
148
|
+
</q-td>
|
|
149
|
+
""",
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
def _normalize_columns(self, columns: list[dict]) -> list[dict]:
|
|
153
|
+
"""Add field 'name', needed for Quasar GUI"""
|
|
154
|
+
return [{**col, "name": col.get("name", col["field"])} for col in columns]
|
|
155
|
+
|
|
156
|
+
def mark_row(self, mark: bool = True, **kwrds: Any) -> list[dict[str, Any]]:
|
|
157
|
+
"""
|
|
158
|
+
card_table.mark_row(id="123456", name="ANNNNN")
|
|
159
|
+
card_table.mark_row(ip="192.168.0.18", mark=False)
|
|
160
|
+
"""
|
|
161
|
+
found = []
|
|
162
|
+
for row in self.table.rows:
|
|
163
|
+
if all(row.get(k) == v for k, v in kwrds.items()):
|
|
164
|
+
row[self.marked_field] = mark
|
|
165
|
+
found.append(row)
|
|
166
|
+
if found:
|
|
167
|
+
self.table.update()
|
|
168
|
+
return found
|
|
169
|
+
|
|
170
|
+
def visible(self, state: bool) -> None:
|
|
171
|
+
self.table.visible = state
|
|
172
|
+
self.card.visible = state
|
|
173
|
+
|
|
174
|
+
async def _on_selection_change(self, e: Any) -> None:
|
|
175
|
+
selected = bool(e.selection)
|
|
176
|
+
for btn in self.buttons_on_row_select_changed:
|
|
177
|
+
if selected:
|
|
178
|
+
btn.classes("!bg-blue-500", remove="!bg-gray-500").enable()
|
|
179
|
+
else:
|
|
180
|
+
btn.classes("!bg-gray-500", remove="!bg-blue-500").disable()
|
|
181
|
+
for handler in self.on_selection_change:
|
|
182
|
+
await handler(e)
|
|
183
|
+
|
|
184
|
+
async def _run_with_processing(self, handler: Callable) -> None:
|
|
185
|
+
self.processing_dialog.open()
|
|
186
|
+
try:
|
|
187
|
+
if inspect.iscoroutinefunction(handler):
|
|
188
|
+
await handler()
|
|
189
|
+
else:
|
|
190
|
+
handler()
|
|
191
|
+
await asyncio.sleep(0.1)
|
|
192
|
+
finally:
|
|
193
|
+
self.processing_dialog.close()
|
|
194
|
+
|
|
195
|
+
async def _on_yes_clicked(self) -> None:
|
|
196
|
+
handler, self._current_yes_handler = self._current_yes_handler, None
|
|
197
|
+
self.confirm_dialog.close()
|
|
198
|
+
if not handler:
|
|
199
|
+
return
|
|
200
|
+
await self._run_with_processing(handler)
|
|
201
|
+
|
|
202
|
+
def enum_data(self, rows: list[dict]) -> list[dict]:
|
|
203
|
+
return [{"sn": i + 1, **row} for i, row in enumerate(rows)]
|
|
204
|
+
|
|
205
|
+
def set_rows(self, rows: list[dict]) -> None:
|
|
206
|
+
self.table.rows = self.enum_data(rows)
|
|
207
|
+
self.table.update()
|
|
208
|
+
|
|
209
|
+
def add_checkbox(
|
|
210
|
+
self,
|
|
211
|
+
chbox_txt: str,
|
|
212
|
+
on_change: Callable,
|
|
213
|
+
default_enable: bool = False,
|
|
214
|
+
) -> ui.checkbox:
|
|
215
|
+
"""
|
|
216
|
+
card_table.add_checkbox(chbox_txt="Used Only", on_change=lambda v: ui.notify(v))
|
|
217
|
+
"""
|
|
218
|
+
with self.top_table:
|
|
219
|
+
chbox = ui.checkbox(chbox_txt, value=default_enable).classes("font-bold")
|
|
220
|
+
|
|
221
|
+
async def handle_change(e: Any) -> None:
|
|
222
|
+
if inspect.iscoroutinefunction(on_change):
|
|
223
|
+
await on_change(e.sender.value)
|
|
224
|
+
else:
|
|
225
|
+
on_change(e.sender.value)
|
|
226
|
+
|
|
227
|
+
chbox.on("click", handle_change)
|
|
228
|
+
setattr(self, f"chbox{chbox_txt.replace(' ', '_')}", chbox)
|
|
229
|
+
return chbox
|
|
230
|
+
|
|
231
|
+
def add_button(
|
|
232
|
+
self,
|
|
233
|
+
btn_txt: str,
|
|
234
|
+
on_click: Callable,
|
|
235
|
+
default_enable: bool = True,
|
|
236
|
+
color: str = "blue",
|
|
237
|
+
active_on_rows_selected: bool = False,
|
|
238
|
+
use_dialog_confirm: bool = False,
|
|
239
|
+
confirm_title: str | None = None,
|
|
240
|
+
) -> ui.button:
|
|
241
|
+
with self.top_table:
|
|
242
|
+
btn = ui.button(btn_txt, color=color)
|
|
243
|
+
|
|
244
|
+
if active_on_rows_selected:
|
|
245
|
+
self.buttons_on_row_select_changed.append(btn)
|
|
246
|
+
|
|
247
|
+
if default_enable:
|
|
248
|
+
btn.classes("!bg-blue-500", remove="!bg-gray-500").enable()
|
|
249
|
+
else:
|
|
250
|
+
btn.classes("!bg-gray-500", remove="!bg-blue-500").disable()
|
|
251
|
+
|
|
252
|
+
async def handle_click() -> None:
|
|
253
|
+
if use_dialog_confirm:
|
|
254
|
+
self.dialog_title.text = confirm_title or f"Confirm {btn_txt}?"
|
|
255
|
+
self._current_yes_handler = on_click
|
|
256
|
+
self.confirm_dialog.open()
|
|
257
|
+
else:
|
|
258
|
+
await self._run_with_processing(on_click)
|
|
259
|
+
|
|
260
|
+
btn.on_click(handle_click)
|
|
261
|
+
setattr(self, f"btn{btn_txt.replace(' ','_')}", btn)
|
|
262
|
+
return btn
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from nicegui import ui
|
|
2
|
+
from ng_lib import CardTable, DialogBox
|
|
3
|
+
import functools
|
|
4
|
+
|
|
5
|
+
from difonlib.bt_utils import logdbg
|
|
6
|
+
|
|
7
|
+
dbg = logdbg
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@ui.page("/", dark=True)
|
|
11
|
+
def main() -> None:
|
|
12
|
+
|
|
13
|
+
card_table = CardTable(
|
|
14
|
+
title="Connected Devices",
|
|
15
|
+
columns=[
|
|
16
|
+
{"name": "id", "label": "id", "field": "id"},
|
|
17
|
+
{"name": "name", "label": "Name", "field": "name"},
|
|
18
|
+
{"name": "ip", "label": "IP Address", "field": "ip"},
|
|
19
|
+
],
|
|
20
|
+
selection="single",
|
|
21
|
+
rows=[
|
|
22
|
+
{"id": "123456", "name": "ANNNNN", "ip": "192.168.0.18"},
|
|
23
|
+
{"id": "987654", "name": "VNNNNN", "ip": "192.168.0.118"},
|
|
24
|
+
{"id": "123456", "name": "BNNNNN", "ip": "192.168.0.12"},
|
|
25
|
+
{"id": "003456", "name": "SNNNNN", "ip": "192.168.0.144"},
|
|
26
|
+
{"id": "129956", "name": "QNNNNN", "ip": "192.168.0.49"},
|
|
27
|
+
],
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
found = card_table.mark_row(id="123456", mark=True)
|
|
31
|
+
print(f"card_table.table.rows:{card_table.table.rows}") # //Dima
|
|
32
|
+
found = card_table.mark_row(id="123456", name="ANNNNN", mark=False)
|
|
33
|
+
found = card_table.mark_row(id="129956")
|
|
34
|
+
print(f" ==> found: {found}") # //Dima
|
|
35
|
+
|
|
36
|
+
print(f"card_table.table.rows: {card_table.table.rows}")
|
|
37
|
+
|
|
38
|
+
card_table.add_checkbox(chbox_txt="Used Only", on_change=lambda v: ui.notify(v))
|
|
39
|
+
|
|
40
|
+
dialog_box = DialogBox()
|
|
41
|
+
|
|
42
|
+
def btnok() -> None:
|
|
43
|
+
ui.notify("You press OK")
|
|
44
|
+
|
|
45
|
+
def btncancel() -> None:
|
|
46
|
+
ui.notify("You press Cancel")
|
|
47
|
+
|
|
48
|
+
card_table.add_button(
|
|
49
|
+
"test_ok_cancel",
|
|
50
|
+
on_click=functools.partial(
|
|
51
|
+
dialog_box.dialog_ok_cancel,
|
|
52
|
+
text="OK or Cancel?",
|
|
53
|
+
on_click_ok=btnok,
|
|
54
|
+
on_click_cancel=btncancel,
|
|
55
|
+
),
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
async def confirm() -> None:
|
|
59
|
+
result = await dialog_box.dialog_confirm()
|
|
60
|
+
ui.notify(f"You chose {result}")
|
|
61
|
+
|
|
62
|
+
card_table.add_button(
|
|
63
|
+
"test_confirm",
|
|
64
|
+
on_click=confirm,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
async def dialog_input() -> None:
|
|
68
|
+
inp = await dialog_box.dialog_input("Input IR_KEY_NAME", input_prefix="IR_KEY_")
|
|
69
|
+
ui.notify(f"Your input: {inp}")
|
|
70
|
+
|
|
71
|
+
card_table.add_button(
|
|
72
|
+
"test_input",
|
|
73
|
+
on_click=dialog_input,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
ui.run(port=1111)
|
|
@@ -29,6 +29,13 @@ tuya_xml_data_file = Path(
|
|
|
29
29
|
)
|
|
30
30
|
|
|
31
31
|
|
|
32
|
+
class OutletDeviceM(OutletDevice):
|
|
33
|
+
def toggle(self) -> bool | Any:
|
|
34
|
+
data = self.status()
|
|
35
|
+
switch_state: bool = data["dps"]["1"]
|
|
36
|
+
return self.set_status(not switch_state)["dps"]["1"]
|
|
37
|
+
|
|
38
|
+
|
|
32
39
|
class TuyaDevs:
|
|
33
40
|
def __init__(
|
|
34
41
|
self,
|
|
@@ -118,7 +125,7 @@ class TuyaDevs:
|
|
|
118
125
|
# ]
|
|
119
126
|
# return con_devs
|
|
120
127
|
|
|
121
|
-
def dev_is_connected(self, dev_id: str, timeout: int = 8, rescan: bool = False) -> bool
|
|
128
|
+
def dev_is_connected(self, dev_id: str, timeout: int = 8, rescan: bool = False) -> bool:
|
|
122
129
|
if rescan:
|
|
123
130
|
# update self.devs_online
|
|
124
131
|
self.scan(timeout=timeout)
|
|
@@ -176,7 +183,7 @@ class TuyaDevs:
|
|
|
176
183
|
odev.turn_off()
|
|
177
184
|
"""
|
|
178
185
|
try:
|
|
179
|
-
dev =
|
|
186
|
+
dev = OutletDeviceM(
|
|
180
187
|
dev_id=dev_id,
|
|
181
188
|
local_key=local_key,
|
|
182
189
|
persist=True,
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
from nicegui import ui
|
|
2
|
-
import inspect
|
|
3
|
-
import asyncio
|
|
4
|
-
from typing import Any, Callable, Awaitable, Literal
|
|
5
|
-
|
|
6
|
-
from difonlib.utils import logdbg
|
|
7
|
-
|
|
8
|
-
dbg = logdbg
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class CardTable:
|
|
12
|
-
_current_yes_handler: Callable[[], None | Awaitable[None]] | None = None
|
|
13
|
-
|
|
14
|
-
def __init__(
|
|
15
|
-
self,
|
|
16
|
-
title: str,
|
|
17
|
-
columns: list,
|
|
18
|
-
rows: list[dict] | None = None,
|
|
19
|
-
selection: Literal["single", "multiple"] | None = None,
|
|
20
|
-
on_selection_change: list[Callable] | None = None,
|
|
21
|
-
):
|
|
22
|
-
self.on_selection_change: list[Callable] = on_selection_change or []
|
|
23
|
-
self.buttons_on_row_select_changed: list = []
|
|
24
|
-
|
|
25
|
-
with ui.card().classes("p-4 shadow-lg") as self.card:
|
|
26
|
-
with ui.row().classes("items-center justify-between w-full mb-2"):
|
|
27
|
-
ui.label(f"📋 {title}").classes("text-green-700 text-lg font-bold")
|
|
28
|
-
with ui.row().classes("gap-2") as self.top_table:
|
|
29
|
-
pass
|
|
30
|
-
|
|
31
|
-
self.table = ui.table(
|
|
32
|
-
columns=columns,
|
|
33
|
-
rows=self.enum_data(rows or []),
|
|
34
|
-
row_key="sn",
|
|
35
|
-
selection=selection,
|
|
36
|
-
on_select=self._on_selection_change,
|
|
37
|
-
column_defaults={
|
|
38
|
-
"align": "left",
|
|
39
|
-
"headerClasses": "uppercase",
|
|
40
|
-
},
|
|
41
|
-
).classes("w-full shadow-lg bg-black-900 text-gray-200")
|
|
42
|
-
|
|
43
|
-
with (
|
|
44
|
-
ui.dialog().props("persistent") as self.confirm_dialog,
|
|
45
|
-
ui.card().classes("p-4"),
|
|
46
|
-
):
|
|
47
|
-
self.dialog_title = ui.label().classes("text-lg font-bold mb-4")
|
|
48
|
-
with ui.row().classes("justify-end w-full gap-2"):
|
|
49
|
-
ui.button("No", color="red", on_click=self.confirm_dialog.close)
|
|
50
|
-
ui.button("Yes", color="green", on_click=self._on_yes_clicked)
|
|
51
|
-
|
|
52
|
-
with (
|
|
53
|
-
ui.dialog().props("persistent") as self.processing_dialog,
|
|
54
|
-
ui.card().classes("p-4 items-center justify-center"),
|
|
55
|
-
):
|
|
56
|
-
with ui.row().classes("items-center gap-3"):
|
|
57
|
-
self.processing_spinner = ui.spinner(size="md")
|
|
58
|
-
self.processing_label = ui.label("Processing...").classes("text-base")
|
|
59
|
-
|
|
60
|
-
def visible(self, state: bool) -> None:
|
|
61
|
-
self.table.visible = state
|
|
62
|
-
self.card.visible = state
|
|
63
|
-
|
|
64
|
-
async def _on_selection_change(self, e: Any) -> None:
|
|
65
|
-
selected = bool(e.selection)
|
|
66
|
-
for btn in self.buttons_on_row_select_changed:
|
|
67
|
-
if selected:
|
|
68
|
-
btn.classes("!bg-blue-500", remove="!bg-gray-500").enable()
|
|
69
|
-
else:
|
|
70
|
-
btn.classes("!bg-gray-500", remove="!bg-blue-500").disable()
|
|
71
|
-
for handler in self.on_selection_change:
|
|
72
|
-
await handler(e)
|
|
73
|
-
|
|
74
|
-
async def _run_with_processing(self, handler: Callable) -> None:
|
|
75
|
-
self.processing_dialog.open()
|
|
76
|
-
try:
|
|
77
|
-
if inspect.iscoroutinefunction(handler):
|
|
78
|
-
await handler()
|
|
79
|
-
else:
|
|
80
|
-
handler()
|
|
81
|
-
await asyncio.sleep(0.5)
|
|
82
|
-
finally:
|
|
83
|
-
self.processing_dialog.close()
|
|
84
|
-
|
|
85
|
-
async def _on_yes_clicked(self) -> None:
|
|
86
|
-
handler, self._current_yes_handler = self._current_yes_handler, None
|
|
87
|
-
self.confirm_dialog.close()
|
|
88
|
-
if not handler:
|
|
89
|
-
return
|
|
90
|
-
await self._run_with_processing(handler)
|
|
91
|
-
|
|
92
|
-
def enum_data(self, data: list[dict]) -> list[dict]:
|
|
93
|
-
return [{"sn": i + 1, **row} for i, row in enumerate(data)]
|
|
94
|
-
|
|
95
|
-
def set_rows(self, rows: list[dict]) -> None:
|
|
96
|
-
self.table.rows = self.enum_data(rows)
|
|
97
|
-
self.table.update()
|
|
98
|
-
|
|
99
|
-
def add_button(
|
|
100
|
-
self,
|
|
101
|
-
btn_txt: str,
|
|
102
|
-
on_click: Callable,
|
|
103
|
-
default_enable: bool = True,
|
|
104
|
-
color: str = "blue",
|
|
105
|
-
active_on_rows_selected: bool = False,
|
|
106
|
-
use_dialog_confirm: bool = False,
|
|
107
|
-
confirm_title: str | None = None,
|
|
108
|
-
) -> ui.button:
|
|
109
|
-
with self.top_table:
|
|
110
|
-
btn = ui.button(btn_txt, color=color)
|
|
111
|
-
|
|
112
|
-
if active_on_rows_selected:
|
|
113
|
-
self.buttons_on_row_select_changed.append(btn)
|
|
114
|
-
|
|
115
|
-
if default_enable:
|
|
116
|
-
btn.classes("!bg-blue-500", remove="!bg-gray-500").enable()
|
|
117
|
-
else:
|
|
118
|
-
btn.classes("!bg-gray-500", remove="!bg-blue-500").disable()
|
|
119
|
-
|
|
120
|
-
async def handle_click() -> None:
|
|
121
|
-
if use_dialog_confirm:
|
|
122
|
-
self.dialog_title.text = confirm_title or f"Confirm {btn_txt}?"
|
|
123
|
-
self._current_yes_handler = on_click
|
|
124
|
-
self.confirm_dialog.open()
|
|
125
|
-
else:
|
|
126
|
-
await self._run_with_processing(on_click)
|
|
127
|
-
|
|
128
|
-
btn.on_click(handle_click)
|
|
129
|
-
setattr(self, f"btn{btn_txt}", btn)
|
|
130
|
-
return btn
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|