ex4nicegui 0.5.1__py3-none-any.whl → 0.5.3__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.
- ex4nicegui/__init__.py +3 -6
- ex4nicegui/bi/dataSourceFacade.py +16 -61
- ex4nicegui/bi/elements/text.py +1 -1
- ex4nicegui/bi/elements/ui_aggrid.py +2 -2
- ex4nicegui/bi/elements/ui_echarts.py +3 -3
- ex4nicegui/bi/elements/ui_slider.py +1 -1
- ex4nicegui/bi/elements/ui_table.py +2 -2
- ex4nicegui/bi/protocols.py +0 -1
- ex4nicegui/bi/types.py +0 -1
- ex4nicegui/experimental_/__init__.py +4 -0
- ex4nicegui/experimental_/gridLayout/__init__.py +7 -0
- ex4nicegui/gsap/__init__.py +11 -0
- ex4nicegui/gsap/timeline.js +56 -0
- ex4nicegui/gsap/timeline.py +78 -0
- ex4nicegui/layout/__init__.py +13 -2
- ex4nicegui/layout/gridFlex/__init__.py +1 -1
- ex4nicegui/layout/gridFlex/gridFlex.py +1 -3
- ex4nicegui/layout/rxFlex/__init__.py +0 -1
- ex4nicegui/layout/rxFlex/index.py +16 -6
- ex4nicegui/reactive/__init__.py +57 -2
- ex4nicegui/reactive/fileWatcher.py +1 -1
- ex4nicegui/reactive/local_file_picker.py +1 -1
- ex4nicegui/reactive/officials/button.py +0 -1
- ex4nicegui/reactive/officials/circular_progress.py +64 -0
- ex4nicegui/reactive/officials/column.py +19 -4
- ex4nicegui/reactive/officials/html.py +1 -1
- ex4nicegui/reactive/officials/knob.py +75 -0
- ex4nicegui/reactive/officials/label.py +0 -1
- ex4nicegui/reactive/officials/number.py +1 -2
- ex4nicegui/reactive/officials/row.py +18 -4
- ex4nicegui/reactive/q_pagination.py +33 -29
- ex4nicegui/reactive/usePagination.py +6 -4
- ex4nicegui/reactive/utils.py +42 -3
- ex4nicegui/reactive/vfor.py +19 -39
- ex4nicegui/reactive/vmodel.py +210 -0
- ex4nicegui/tools/__init__.py +0 -1
- ex4nicegui/tools/debug.py +1 -1
- ex4nicegui/utils/signals.py +12 -2
- {ex4nicegui-0.5.1.dist-info → ex4nicegui-0.5.3.dist-info}/METADATA +60 -34
- {ex4nicegui-0.5.1.dist-info → ex4nicegui-0.5.3.dist-info}/RECORD +43 -38
- {ex4nicegui-0.5.1.dist-info → ex4nicegui-0.5.3.dist-info}/LICENSE +0 -0
- {ex4nicegui-0.5.1.dist-info → ex4nicegui-0.5.3.dist-info}/WHEEL +0 -0
- {ex4nicegui-0.5.1.dist-info → ex4nicegui-0.5.3.dist-info}/top_level.txt +0 -0
|
@@ -1,18 +1,33 @@
|
|
|
1
1
|
from typing import (
|
|
2
2
|
Any,
|
|
3
3
|
)
|
|
4
|
+
from ex4nicegui.reactive.utils import ParameterClassifier
|
|
5
|
+
from ex4nicegui.utils.signals import (
|
|
6
|
+
_TMaybeRef as TMaybeRef,
|
|
7
|
+
)
|
|
4
8
|
from nicegui import ui
|
|
5
9
|
from .base import BindableUi
|
|
6
10
|
|
|
7
11
|
|
|
8
12
|
class ColumnBindableUi(BindableUi[ui.column]):
|
|
9
|
-
def __init__(
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
element = ui.column()
|
|
13
|
+
def __init__(self, *, wrap: TMaybeRef[bool] = True) -> None:
|
|
14
|
+
pc = ParameterClassifier(locals(), maybeRefs=["wrap"], events=[])
|
|
15
|
+
element = ui.column(**pc.get_values_kws())
|
|
13
16
|
|
|
14
17
|
super().__init__(element)
|
|
15
18
|
|
|
19
|
+
for key, value in pc.get_bindings().items():
|
|
20
|
+
self.bind_prop(key, value) # type: ignore
|
|
21
|
+
|
|
22
|
+
def bind_prop(self, prop: str, ref_ui: TMaybeRef):
|
|
23
|
+
if prop == "wrap":
|
|
24
|
+
return self.bind_wrap(ref_ui)
|
|
25
|
+
|
|
26
|
+
return super().bind_prop(prop, ref_ui)
|
|
27
|
+
|
|
28
|
+
def bind_wrap(self, ref_ui: TMaybeRef):
|
|
29
|
+
self.bind_classes({"wrap": ref_ui})
|
|
30
|
+
|
|
16
31
|
def __enter__(self):
|
|
17
32
|
self.element.__enter__()
|
|
18
33
|
return self
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
from typing import (
|
|
2
|
+
Any,
|
|
3
|
+
Callable,
|
|
4
|
+
Optional,
|
|
5
|
+
)
|
|
6
|
+
from ex4nicegui.reactive.utils import ParameterClassifier
|
|
7
|
+
from ex4nicegui.utils.apiEffect import ui_effect
|
|
8
|
+
|
|
9
|
+
from ex4nicegui.utils.signals import (
|
|
10
|
+
ReadonlyRef,
|
|
11
|
+
_TMaybeRef as TMaybeRef,
|
|
12
|
+
to_value,
|
|
13
|
+
)
|
|
14
|
+
from nicegui import ui
|
|
15
|
+
from .base import BindableUi, DisableableMixin
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class KnobBindableUi(
|
|
19
|
+
BindableUi[ui.knob],
|
|
20
|
+
DisableableMixin,
|
|
21
|
+
):
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
value: TMaybeRef[float] = 0.0,
|
|
25
|
+
*,
|
|
26
|
+
min: TMaybeRef[float] = 0.0, # pylint: disable=redefined-builtin
|
|
27
|
+
max: TMaybeRef[float] = 1.0, # pylint: disable=redefined-builtin
|
|
28
|
+
step: TMaybeRef[float] = 0.01,
|
|
29
|
+
color: Optional[TMaybeRef[str]] = "primary",
|
|
30
|
+
center_color: Optional[TMaybeRef[str]] = None,
|
|
31
|
+
track_color: Optional[TMaybeRef[str]] = None,
|
|
32
|
+
size: Optional[TMaybeRef[str]] = None,
|
|
33
|
+
show_value: TMaybeRef[bool] = False,
|
|
34
|
+
on_change: Optional[Callable[..., Any]] = None,
|
|
35
|
+
) -> None:
|
|
36
|
+
pc = ParameterClassifier(
|
|
37
|
+
locals(),
|
|
38
|
+
maybeRefs=[
|
|
39
|
+
"value",
|
|
40
|
+
"min",
|
|
41
|
+
"max",
|
|
42
|
+
"step",
|
|
43
|
+
"color",
|
|
44
|
+
"center_color",
|
|
45
|
+
"track_color",
|
|
46
|
+
"size",
|
|
47
|
+
"show_value",
|
|
48
|
+
],
|
|
49
|
+
v_model=("value", "on_change"),
|
|
50
|
+
events=["on_change"],
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
value_kws = pc.get_values_kws()
|
|
54
|
+
element = ui.knob(**value_kws)
|
|
55
|
+
super().__init__(element) # type: ignore
|
|
56
|
+
|
|
57
|
+
for key, value in pc.get_bindings().items():
|
|
58
|
+
self.bind_prop(key, value) # type: ignore
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def value(self):
|
|
62
|
+
return self.element.value
|
|
63
|
+
|
|
64
|
+
def bind_prop(self, prop: str, ref_ui: ReadonlyRef):
|
|
65
|
+
if prop == "value":
|
|
66
|
+
return self.bind_value(ref_ui)
|
|
67
|
+
|
|
68
|
+
return super().bind_prop(prop, ref_ui)
|
|
69
|
+
|
|
70
|
+
def bind_value(self, ref_ui: ReadonlyRef[float]):
|
|
71
|
+
@ui_effect
|
|
72
|
+
def _():
|
|
73
|
+
self.element.set_value(to_value(ref_ui))
|
|
74
|
+
|
|
75
|
+
return self
|
|
@@ -4,7 +4,6 @@ from typing import (
|
|
|
4
4
|
Optional,
|
|
5
5
|
TypeVar,
|
|
6
6
|
Dict,
|
|
7
|
-
Union,
|
|
8
7
|
)
|
|
9
8
|
from ex4nicegui.reactive.utils import ParameterClassifier
|
|
10
9
|
from ex4nicegui.utils.apiEffect import ui_effect
|
|
@@ -26,7 +25,7 @@ class NumberBindableUi(BindableUi[ui.number]):
|
|
|
26
25
|
label: Optional[TMaybeRef[str]] = None,
|
|
27
26
|
*,
|
|
28
27
|
placeholder: Optional[TMaybeRef[str]] = None,
|
|
29
|
-
value: TMaybeRef[
|
|
28
|
+
value: Optional[TMaybeRef[float]] = None,
|
|
30
29
|
min: Optional[TMaybeRef[float]] = None,
|
|
31
30
|
max: Optional[TMaybeRef[float]] = None,
|
|
32
31
|
step: Optional[TMaybeRef[float]] = None,
|
|
@@ -1,17 +1,31 @@
|
|
|
1
1
|
from typing import (
|
|
2
2
|
Any,
|
|
3
3
|
)
|
|
4
|
+
from ex4nicegui.reactive.utils import ParameterClassifier
|
|
5
|
+
from ex4nicegui.utils.signals import (
|
|
6
|
+
_TMaybeRef as TMaybeRef,
|
|
7
|
+
)
|
|
4
8
|
from nicegui import ui
|
|
5
9
|
from .base import BindableUi
|
|
6
10
|
|
|
7
11
|
|
|
8
12
|
class RowBindableUi(BindableUi[ui.row]):
|
|
9
|
-
def __init__(
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
element = ui.row()
|
|
13
|
+
def __init__(self, *, wrap: TMaybeRef[bool] = True) -> None:
|
|
14
|
+
pc = ParameterClassifier(locals(), maybeRefs=["wrap"], events=[])
|
|
15
|
+
element = ui.row(**pc.get_values_kws())
|
|
13
16
|
|
|
14
17
|
super().__init__(element)
|
|
18
|
+
for key, value in pc.get_bindings().items():
|
|
19
|
+
self.bind_prop(key, value) # type: ignore
|
|
20
|
+
|
|
21
|
+
def bind_prop(self, prop: str, ref_ui: TMaybeRef):
|
|
22
|
+
if prop == "wrap":
|
|
23
|
+
return self.bind_wrap(ref_ui)
|
|
24
|
+
|
|
25
|
+
return super().bind_prop(prop, ref_ui)
|
|
26
|
+
|
|
27
|
+
def bind_wrap(self, ref_ui: TMaybeRef):
|
|
28
|
+
self.bind_classes({"wrap": ref_ui})
|
|
15
29
|
|
|
16
30
|
def __enter__(self):
|
|
17
31
|
self.element.__enter__()
|
|
@@ -1,45 +1,49 @@
|
|
|
1
|
-
from typing import
|
|
2
|
-
from
|
|
1
|
+
from typing import Any, Optional, Callable
|
|
2
|
+
from ex4nicegui.reactive.utils import ParameterClassifier
|
|
3
3
|
from ex4nicegui.utils.signals import (
|
|
4
|
-
Ref,
|
|
5
|
-
effect,
|
|
6
4
|
to_value,
|
|
7
5
|
_TMaybeRef as TMaybeRef,
|
|
8
6
|
)
|
|
7
|
+
from nicegui import ui
|
|
8
|
+
from ex4nicegui.reactive.officials.base import BindableUi
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
class
|
|
12
|
-
VALUE_PROP: str = "model-value"
|
|
13
|
-
LOOPBACK = False
|
|
14
|
-
|
|
11
|
+
class PaginationBindableUi(BindableUi[ui.pagination]):
|
|
15
12
|
def __init__(
|
|
16
13
|
self,
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
min: TMaybeRef[int],
|
|
15
|
+
max: TMaybeRef[int],
|
|
16
|
+
*, # pylint: disable=redefined-builtin
|
|
17
|
+
direction_links: TMaybeRef[bool] = False,
|
|
18
|
+
value: TMaybeRef[int] = ..., # type: ignore
|
|
19
|
+
on_change: Optional[Callable[..., Any]] = None,
|
|
20
20
|
) -> None:
|
|
21
|
-
|
|
21
|
+
pc = ParameterClassifier(
|
|
22
|
+
locals(),
|
|
23
|
+
maybeRefs=["min", "max", "direction_links", "value"],
|
|
24
|
+
v_model=("value", "on_change"),
|
|
25
|
+
events=["on_change"],
|
|
26
|
+
)
|
|
22
27
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
self.__max = max
|
|
28
|
+
element = ui.pagination(**pc.get_values_kws())
|
|
29
|
+
super().__init__(element)
|
|
26
30
|
|
|
27
|
-
|
|
28
|
-
|
|
31
|
+
for key, value in pc.get_bindings().items():
|
|
32
|
+
self.bind_prop(key, value) # type: ignore
|
|
29
33
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
self.update()
|
|
34
|
+
@property
|
|
35
|
+
def value(self):
|
|
36
|
+
return self.element.value
|
|
34
37
|
|
|
35
|
-
|
|
38
|
+
def bind_prop(self, prop: str, ref_ui: TMaybeRef):
|
|
39
|
+
if prop == "value":
|
|
40
|
+
return self.bind_value(ref_ui)
|
|
36
41
|
|
|
37
|
-
|
|
38
|
-
def _():
|
|
39
|
-
self._props["model-value"] = to_value(value)
|
|
40
|
-
self._props["min"] = to_value(min)
|
|
42
|
+
return super().bind_prop(prop, ref_ui)
|
|
41
43
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
+
def bind_value(self, ref_ui: TMaybeRef[int]):
|
|
45
|
+
@self._ui_effect
|
|
46
|
+
def _():
|
|
47
|
+
self.element.set_value(to_value(ref_ui))
|
|
44
48
|
|
|
45
|
-
|
|
49
|
+
return self
|
|
@@ -6,10 +6,10 @@ from ex4nicegui.utils.signals import (
|
|
|
6
6
|
effect,
|
|
7
7
|
_TMaybeRef as TMaybeRef,
|
|
8
8
|
)
|
|
9
|
-
from typing import Any
|
|
9
|
+
from typing import Any
|
|
10
10
|
from typing_extensions import Protocol
|
|
11
11
|
import math
|
|
12
|
-
from ex4nicegui.reactive.q_pagination import
|
|
12
|
+
from ex4nicegui.reactive.q_pagination import PaginationBindableUi
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
def _clamp(value, min_v, max_v) -> int:
|
|
@@ -97,6 +97,8 @@ class PaginationRef:
|
|
|
97
97
|
return cp
|
|
98
98
|
|
|
99
99
|
def create_q_pagination(self):
|
|
100
|
-
page =
|
|
101
|
-
|
|
100
|
+
page = PaginationBindableUi(
|
|
101
|
+
min=1, max=self.page_count, direction_links=True, value=self.current_page
|
|
102
|
+
)
|
|
103
|
+
page.props("boundary-links")
|
|
102
104
|
return page
|
ex4nicegui/reactive/utils.py
CHANGED
|
@@ -1,9 +1,33 @@
|
|
|
1
|
-
from typing import
|
|
1
|
+
from typing import (
|
|
2
|
+
Any,
|
|
3
|
+
Callable,
|
|
4
|
+
Dict,
|
|
5
|
+
Iterable,
|
|
6
|
+
List,
|
|
7
|
+
Optional,
|
|
8
|
+
Protocol,
|
|
9
|
+
Tuple,
|
|
10
|
+
cast,
|
|
11
|
+
runtime_checkable,
|
|
12
|
+
Union,
|
|
13
|
+
)
|
|
2
14
|
|
|
3
15
|
from ex4nicegui.utils.signals import is_ref, to_value, is_setter_ref
|
|
4
16
|
from nicegui.events import handle_event
|
|
5
17
|
|
|
6
18
|
|
|
19
|
+
@runtime_checkable
|
|
20
|
+
class GetItemProtocol(Protocol):
|
|
21
|
+
def __getitem__(self, key):
|
|
22
|
+
...
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@runtime_checkable
|
|
26
|
+
class SetItemProtocol(Protocol):
|
|
27
|
+
def __setitem__(self, key, value):
|
|
28
|
+
...
|
|
29
|
+
|
|
30
|
+
|
|
7
31
|
def _convert_kws_ref2value(kws: Dict) -> Dict:
|
|
8
32
|
return {key: to_value(value) for key, value in kws.items()}
|
|
9
33
|
|
|
@@ -24,7 +48,7 @@ class ParameterClassifier:
|
|
|
24
48
|
if extend_kws:
|
|
25
49
|
exclude.append(extend_kws)
|
|
26
50
|
|
|
27
|
-
self._args = {
|
|
51
|
+
self._args: Dict[str, Any] = {
|
|
28
52
|
k: v
|
|
29
53
|
for k, v in args.items()
|
|
30
54
|
if k != "self" and k[0] != "_" and (k not in exclude)
|
|
@@ -70,7 +94,22 @@ class ParameterClassifier:
|
|
|
70
94
|
|
|
71
95
|
def get_bindings(self) -> Dict:
|
|
72
96
|
return {
|
|
73
|
-
k: v
|
|
97
|
+
k.replace("_", "-"): v
|
|
74
98
|
for k, v in self._args.items()
|
|
75
99
|
if (k in self.maybeRefs and (is_ref(v) or isinstance(v, Callable)))
|
|
76
100
|
}
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def get_attribute(obj: Union[object, GetItemProtocol], name: Union[str, int]) -> Any:
|
|
104
|
+
if isinstance(obj, (GetItemProtocol)):
|
|
105
|
+
return obj[name]
|
|
106
|
+
return getattr(obj, name) # type: ignore
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def set_attribute(
|
|
110
|
+
obj: Union[object, SetItemProtocol], name: Union[str, int], value: Any
|
|
111
|
+
) -> None:
|
|
112
|
+
if isinstance(obj, SetItemProtocol):
|
|
113
|
+
obj[name] = value
|
|
114
|
+
else:
|
|
115
|
+
setattr(obj, name, value) # type: ignore
|
ex4nicegui/reactive/vfor.py
CHANGED
|
@@ -2,13 +2,18 @@ from __future__ import annotations
|
|
|
2
2
|
from nicegui.element import Element
|
|
3
3
|
from nicegui import ui
|
|
4
4
|
from ex4nicegui.utils.clientScope import _CLIENT_SCOPE_MANAGER
|
|
5
|
-
from ex4nicegui.utils.signals import
|
|
5
|
+
from ex4nicegui.utils.signals import (
|
|
6
|
+
TReadonlyRef,
|
|
7
|
+
on,
|
|
8
|
+
to_ref,
|
|
9
|
+
to_ref_wrapper,
|
|
10
|
+
TGetterOrReadonlyRef,
|
|
11
|
+
)
|
|
6
12
|
from typing import (
|
|
7
13
|
Any,
|
|
8
14
|
Callable,
|
|
9
15
|
Dict,
|
|
10
16
|
List,
|
|
11
|
-
Mapping,
|
|
12
17
|
Optional,
|
|
13
18
|
TypeVar,
|
|
14
19
|
Generic,
|
|
@@ -17,11 +22,11 @@ from typing import (
|
|
|
17
22
|
)
|
|
18
23
|
from functools import partial
|
|
19
24
|
from dataclasses import dataclass
|
|
20
|
-
from signe.core.reactive import DictProxy as signe_DictProxy
|
|
21
25
|
from signe.core.scope import Scope
|
|
26
|
+
from .utils import get_attribute
|
|
22
27
|
|
|
23
28
|
_T = TypeVar("_T")
|
|
24
|
-
_T_data =
|
|
29
|
+
_T_data = TGetterOrReadonlyRef[List[Any]]
|
|
25
30
|
|
|
26
31
|
|
|
27
32
|
class VforStore(Generic[_T]):
|
|
@@ -33,29 +38,17 @@ class VforStore(Generic[_T]):
|
|
|
33
38
|
def row_index(self):
|
|
34
39
|
return self._data_index
|
|
35
40
|
|
|
36
|
-
def get(self
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if attr:
|
|
40
|
-
setter = None
|
|
41
|
-
if isinstance(item, signe_DictProxy):
|
|
42
|
-
|
|
43
|
-
def base_setter(value):
|
|
44
|
-
item[attr] = value
|
|
41
|
+
def get(self) -> TReadonlyRef[_T]:
|
|
42
|
+
def base_setter(value):
|
|
43
|
+
self._source.value[self._data_index.value] = value
|
|
45
44
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
_T,
|
|
52
|
-
to_ref_wrapper(
|
|
53
|
-
lambda: _get_attribute(item, attr),
|
|
54
|
-
setter,
|
|
55
|
-
),
|
|
56
|
-
)
|
|
45
|
+
wrapper = to_ref_wrapper(
|
|
46
|
+
lambda: self._source.value[self._data_index.value],
|
|
47
|
+
base_setter,
|
|
48
|
+
)
|
|
49
|
+
wrapper._is_readonly = True
|
|
57
50
|
|
|
58
|
-
return
|
|
51
|
+
return cast(TReadonlyRef, wrapper)
|
|
59
52
|
|
|
60
53
|
def update(self, index: int):
|
|
61
54
|
self._data_index.value = index
|
|
@@ -73,25 +66,12 @@ class VforContainer(Element, component="vfor.js"):
|
|
|
73
66
|
pass
|
|
74
67
|
|
|
75
68
|
|
|
76
|
-
def _get_attribute(obj: Union[object, Mapping], name: str) -> Any:
|
|
77
|
-
if isinstance(obj, Mapping):
|
|
78
|
-
return obj[name]
|
|
79
|
-
return getattr(obj, name)
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
def _set_attribute(obj: Union[object, Mapping], name: str, value: Any) -> None:
|
|
83
|
-
if isinstance(obj, dict):
|
|
84
|
-
obj[name] = value
|
|
85
|
-
else:
|
|
86
|
-
setattr(obj, name, value)
|
|
87
|
-
|
|
88
|
-
|
|
89
69
|
def _get_key_with_index(idx: int, data: Any):
|
|
90
70
|
return idx
|
|
91
71
|
|
|
92
72
|
|
|
93
73
|
def _get_key_with_getter(attr: str, idx: int, data: Any):
|
|
94
|
-
return
|
|
74
|
+
return get_attribute(data, attr)
|
|
95
75
|
|
|
96
76
|
|
|
97
77
|
class vfor(Generic[_T]):
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import inspect
|
|
3
|
+
from io import BytesIO
|
|
4
|
+
from ex4nicegui.utils.signals import (
|
|
5
|
+
RefWrapper,
|
|
6
|
+
TRef,
|
|
7
|
+
to_raw,
|
|
8
|
+
is_setter_ref,
|
|
9
|
+
to_ref_wrapper,
|
|
10
|
+
to_value,
|
|
11
|
+
is_reactive,
|
|
12
|
+
is_setter_ref,
|
|
13
|
+
to_ref_wrapper,
|
|
14
|
+
to_value,
|
|
15
|
+
)
|
|
16
|
+
from typing import (
|
|
17
|
+
Any,
|
|
18
|
+
Callable,
|
|
19
|
+
TypeVar,
|
|
20
|
+
Union,
|
|
21
|
+
cast,
|
|
22
|
+
Tuple,
|
|
23
|
+
NamedTuple,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
from types import FrameType
|
|
27
|
+
import tokenize
|
|
28
|
+
import executing
|
|
29
|
+
import ast
|
|
30
|
+
import warnings
|
|
31
|
+
from .utils import get_attribute, set_attribute
|
|
32
|
+
|
|
33
|
+
_T = TypeVar("_T")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def get_caller() -> FrameType:
|
|
37
|
+
return inspect.currentframe().f_back.f_back # type: ignore
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def get_args_code(caller: FrameType) -> str:
|
|
41
|
+
node = executing.Source.executing(caller).node
|
|
42
|
+
|
|
43
|
+
assert isinstance(node, ast.Call)
|
|
44
|
+
|
|
45
|
+
source = inspect.getsource(inspect.getmodule(caller)) # type: ignore
|
|
46
|
+
key_code = ast.get_source_segment(source, node.args[0])
|
|
47
|
+
return key_code # type: ignore
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class CodeInfo(NamedTuple):
|
|
51
|
+
model: str
|
|
52
|
+
is_ref: bool
|
|
53
|
+
keys: Tuple[Union[str, int], ...] = tuple() # type: ignore
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def normalize(token: tokenize.TokenInfo):
|
|
57
|
+
if token.type == tokenize.NUMBER:
|
|
58
|
+
return int(token.string)
|
|
59
|
+
|
|
60
|
+
if token.type == tokenize.STRING:
|
|
61
|
+
return token.string[1:-1]
|
|
62
|
+
|
|
63
|
+
return token.string
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def parse_code(code: str) -> CodeInfo:
|
|
67
|
+
tokens = [
|
|
68
|
+
t
|
|
69
|
+
for t in tokenize.tokenize(BytesIO(code.encode("utf8")).readline)
|
|
70
|
+
if t.type in (tokenize.NAME, tokenize.NUMBER, tokenize.OP, tokenize.STRING)
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
model = tokens[0].string
|
|
74
|
+
is_ref = (
|
|
75
|
+
len(tokens) >= 3
|
|
76
|
+
and tokens[1].type == tokenize.OP
|
|
77
|
+
and tokens[1].string == "."
|
|
78
|
+
and tokens[2].string == "value"
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
keys_search_start = 3 if is_ref else 1
|
|
82
|
+
|
|
83
|
+
keys = tuple(
|
|
84
|
+
normalize(token)
|
|
85
|
+
for token in tokens[keys_search_start:]
|
|
86
|
+
if token.type != tokenize.OP
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
return CodeInfo(model, is_ref, keys)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def create_writeable_wrapper(expr, ref_data, attrs: Tuple[Union[str, int], ...]):
|
|
93
|
+
if not attrs:
|
|
94
|
+
|
|
95
|
+
def maybe_ref_getter():
|
|
96
|
+
return to_value(expr)
|
|
97
|
+
|
|
98
|
+
def reactive_getter():
|
|
99
|
+
return to_raw(expr)
|
|
100
|
+
|
|
101
|
+
def setter(value):
|
|
102
|
+
pass
|
|
103
|
+
|
|
104
|
+
wrapper = to_ref_wrapper(
|
|
105
|
+
reactive_getter if is_reactive(expr) else maybe_ref_getter,
|
|
106
|
+
setter,
|
|
107
|
+
)
|
|
108
|
+
wrapper._is_readonly = False
|
|
109
|
+
return wrapper
|
|
110
|
+
|
|
111
|
+
def getter():
|
|
112
|
+
obj = get_attribute(to_value(ref_data), attrs[0])
|
|
113
|
+
for attr in attrs[1:]:
|
|
114
|
+
obj = get_attribute(obj, attr)
|
|
115
|
+
|
|
116
|
+
return obj
|
|
117
|
+
|
|
118
|
+
def setter(value):
|
|
119
|
+
obj = to_value(ref_data)
|
|
120
|
+
|
|
121
|
+
for attr in attrs[:-1]:
|
|
122
|
+
obj = get_attribute(obj, attr)
|
|
123
|
+
|
|
124
|
+
set_attribute(obj, attrs[-1], value)
|
|
125
|
+
|
|
126
|
+
wrapper = to_ref_wrapper(
|
|
127
|
+
getter,
|
|
128
|
+
setter,
|
|
129
|
+
)
|
|
130
|
+
wrapper._is_readonly = False
|
|
131
|
+
return wrapper
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def vmodel(expr: Any, *attrs: Union[str, int]) -> TRef[Any]:
|
|
135
|
+
"""Create a two-way binding on a form input element or a component.
|
|
136
|
+
|
|
137
|
+
@see - https://github.com/CrystalWindSnake/ex4nicegui/blob/main/README.en.md#vmodel
|
|
138
|
+
@中文文档 - https://gitee.com/carson_add/ex4nicegui/tree/main/#vmodel
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
expr (Any): _description_
|
|
142
|
+
|
|
143
|
+
## Examples
|
|
144
|
+
```python
|
|
145
|
+
from ex4nicegui.reactive import rxui
|
|
146
|
+
from ex4nicegui import deep_ref
|
|
147
|
+
|
|
148
|
+
data = deep_ref({"a": 1, "b": [1, 2, 3, 4]})
|
|
149
|
+
|
|
150
|
+
rxui.label(lambda: f"{data.value=!s}")
|
|
151
|
+
|
|
152
|
+
# No binding effect
|
|
153
|
+
rxui.input(value=data.value["a"])
|
|
154
|
+
|
|
155
|
+
# readonly binding
|
|
156
|
+
rxui.input(value=lambda: data.value["a"])
|
|
157
|
+
|
|
158
|
+
# two-way binding
|
|
159
|
+
rxui.input(value=rxui.vmodel(data.value["a"]))
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
assert not isinstance(expr, Callable), "argument expr cannot be a function"
|
|
165
|
+
|
|
166
|
+
if isinstance(expr, RefWrapper):
|
|
167
|
+
expr._is_readonly = False
|
|
168
|
+
|
|
169
|
+
if is_setter_ref(expr):
|
|
170
|
+
if attrs:
|
|
171
|
+
wrapper = create_writeable_wrapper(expr, expr, attrs)
|
|
172
|
+
|
|
173
|
+
return cast(
|
|
174
|
+
TRef,
|
|
175
|
+
wrapper,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
return cast(
|
|
179
|
+
TRef,
|
|
180
|
+
expr,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
caller = get_caller()
|
|
184
|
+
code = get_args_code(caller)
|
|
185
|
+
|
|
186
|
+
info = parse_code(code)
|
|
187
|
+
ref_data = caller.f_locals.get(info.model) or caller.f_globals.get(info.model)
|
|
188
|
+
assert ref_data is not None, f"{info.model} not found"
|
|
189
|
+
all_attrs = (*info.keys, *attrs)
|
|
190
|
+
|
|
191
|
+
if not all_attrs:
|
|
192
|
+
warn_mes = ""
|
|
193
|
+
if is_reactive(expr) or is_setter_ref(expr):
|
|
194
|
+
warn_mes = rf"""Expression missing the key,result is read-only binding.Maybe you meant `{code}['key']`"""
|
|
195
|
+
elif not info.is_ref:
|
|
196
|
+
warn_mes = """Maybe you don't need to use vmodel"""
|
|
197
|
+
else:
|
|
198
|
+
warn_mes = rf"""No binding.Maybe you meant `{info.model}`"""
|
|
199
|
+
|
|
200
|
+
warnings.warn(
|
|
201
|
+
warn_mes,
|
|
202
|
+
stacklevel=2,
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
wrapper = create_writeable_wrapper(expr, ref_data, all_attrs)
|
|
206
|
+
|
|
207
|
+
return cast(
|
|
208
|
+
TRef,
|
|
209
|
+
wrapper,
|
|
210
|
+
)
|
ex4nicegui/tools/__init__.py
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
from .debug import display_ref_vars_ui
|
ex4nicegui/tools/debug.py
CHANGED
|
@@ -2,7 +2,7 @@ from signe.core.effect import Effect
|
|
|
2
2
|
import ex4nicegui.reactive as rxui
|
|
3
3
|
from ex4nicegui.utils.signals import ReadonlyRef, DescReadonlyRef, Ref, ref_computed
|
|
4
4
|
from nicegui import ui
|
|
5
|
-
from typing import
|
|
5
|
+
from typing import Dict, TypeVar
|
|
6
6
|
|
|
7
7
|
T = TypeVar("T")
|
|
8
8
|
|