ex4nicegui 0.6.8__py3-none-any.whl → 0.7.0__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 +2 -0
- ex4nicegui/bi/dataSourceFacade.py +20 -20
- ex4nicegui/bi/index.py +20 -23
- ex4nicegui/helper/client_instance_locker.py +4 -4
- ex4nicegui/reactive/EChartsComponent/ECharts.py +9 -8
- ex4nicegui/reactive/__init__.py +12 -0
- ex4nicegui/reactive/base.py +433 -0
- ex4nicegui/reactive/deferredTask.py +3 -2
- ex4nicegui/reactive/mixins/backgroundColor.py +41 -0
- ex4nicegui/reactive/mixins/disableable.py +44 -0
- ex4nicegui/reactive/mixins/textColor.py +66 -0
- ex4nicegui/reactive/officials/aggrid.py +5 -5
- ex4nicegui/reactive/officials/base.py +4 -453
- ex4nicegui/reactive/officials/button.py +43 -10
- ex4nicegui/reactive/officials/checkbox.py +5 -5
- ex4nicegui/reactive/officials/chip.py +102 -0
- ex4nicegui/reactive/officials/circular_progress.py +9 -7
- ex4nicegui/reactive/officials/color_picker.py +7 -7
- ex4nicegui/reactive/officials/column.py +5 -5
- ex4nicegui/reactive/officials/date.py +5 -5
- ex4nicegui/reactive/officials/dialog.py +49 -0
- ex4nicegui/reactive/officials/echarts.py +49 -51
- ex4nicegui/reactive/officials/expansion.py +5 -5
- ex4nicegui/reactive/officials/grid.py +3 -2
- ex4nicegui/reactive/officials/icon.py +8 -11
- ex4nicegui/reactive/officials/image.py +5 -5
- ex4nicegui/reactive/officials/input.py +8 -8
- ex4nicegui/reactive/officials/knob.py +9 -5
- ex4nicegui/reactive/officials/label.py +8 -15
- ex4nicegui/reactive/officials/linear_progress.py +8 -11
- ex4nicegui/reactive/officials/number.py +9 -9
- ex4nicegui/reactive/officials/radio.py +8 -8
- ex4nicegui/reactive/officials/row.py +5 -5
- ex4nicegui/reactive/officials/select.py +9 -8
- ex4nicegui/reactive/officials/slider.py +6 -6
- ex4nicegui/reactive/officials/switch.py +5 -5
- ex4nicegui/reactive/officials/tab.py +0 -12
- ex4nicegui/reactive/officials/tab_panels.py +113 -7
- ex4nicegui/reactive/officials/table.py +13 -13
- ex4nicegui/reactive/officials/tabs.py +5 -5
- ex4nicegui/reactive/officials/textarea.py +5 -5
- ex4nicegui/reactive/officials/tooltip.py +40 -0
- ex4nicegui/reactive/q_pagination.py +5 -5
- ex4nicegui/reactive/scopedStyle.py +4 -1
- ex4nicegui/reactive/systems/color_system.py +132 -0
- ex4nicegui/reactive/vfor.js +14 -4
- ex4nicegui/reactive/vfor.py +128 -58
- ex4nicegui/reactive/view_model.py +160 -0
- ex4nicegui/reactive/vmodel.py +42 -12
- ex4nicegui/utils/apiEffect.py +5 -1
- ex4nicegui/utils/effect.py +3 -2
- ex4nicegui/utils/scheduler.py +20 -4
- ex4nicegui/utils/signals.py +23 -21
- {ex4nicegui-0.6.8.dist-info → ex4nicegui-0.7.0.dist-info}/METADATA +247 -48
- {ex4nicegui-0.6.8.dist-info → ex4nicegui-0.7.0.dist-info}/RECORD +57 -50
- ex4nicegui/reactive/services/color_service.py +0 -56
- {ex4nicegui-0.6.8.dist-info → ex4nicegui-0.7.0.dist-info}/LICENSE +0 -0
- {ex4nicegui-0.6.8.dist-info → ex4nicegui-0.7.0.dist-info}/WHEEL +0 -0
|
@@ -2,11 +2,14 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from typing import Literal
|
|
4
4
|
|
|
5
|
+
from nicegui import ui
|
|
5
6
|
from nicegui.elements.mixins.color_elements import (
|
|
6
7
|
QUASAR_COLORS,
|
|
7
8
|
TAILWIND_COLORS,
|
|
8
9
|
)
|
|
9
10
|
|
|
11
|
+
from functools import lru_cache
|
|
12
|
+
|
|
10
13
|
|
|
11
14
|
_color_system_type = Literal["QUASAR", "TAILWIND", "STYLE"]
|
|
12
15
|
|
|
@@ -23,3 +26,132 @@ def get_text_color_info(name: str):
|
|
|
23
26
|
pass
|
|
24
27
|
|
|
25
28
|
return system_type, name
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def get_bg_color_info(color: str):
|
|
32
|
+
system_type: _color_system_type = "STYLE"
|
|
33
|
+
|
|
34
|
+
if color in QUASAR_COLORS:
|
|
35
|
+
system_type = "QUASAR"
|
|
36
|
+
elif color in TAILWIND_COLORS:
|
|
37
|
+
system_type = "TAILWIND"
|
|
38
|
+
color = f"bg-{color}"
|
|
39
|
+
else:
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
return system_type, color
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _remove_text_color_from_quasar(element: ui.element, color: str):
|
|
46
|
+
color_prop = getattr(element, "TEXT_COLOR_PROP", None)
|
|
47
|
+
if color_prop:
|
|
48
|
+
element._props.pop(color_prop, None)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _remove_text_color_from_tailwind(element: ui.element, color: str):
|
|
52
|
+
color = f"text-{color}"
|
|
53
|
+
element._classes.remove(color)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _remove_text_color(element: ui.element, color: str):
|
|
57
|
+
element._style.pop("color", None)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _add_text_color_from_quasar(element: ui.element, color: str):
|
|
61
|
+
color_prop = getattr(element, "TEXT_COLOR_PROP", None)
|
|
62
|
+
if color_prop:
|
|
63
|
+
element._props[color_prop] = color
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _add_text_color_from_tailwind(element: ui.element, color: str):
|
|
67
|
+
color = f"text-{color}"
|
|
68
|
+
element.classes(color)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _add_text_color(element: ui.element, color: str):
|
|
72
|
+
element._style["color"] = f"{color} !important"
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@lru_cache(maxsize=10)
|
|
76
|
+
def _query_text_color(color: str, add_handler=True):
|
|
77
|
+
if color in QUASAR_COLORS:
|
|
78
|
+
return (
|
|
79
|
+
_add_text_color_from_quasar
|
|
80
|
+
if add_handler
|
|
81
|
+
else _remove_text_color_from_quasar
|
|
82
|
+
)
|
|
83
|
+
elif color in TAILWIND_COLORS:
|
|
84
|
+
return (
|
|
85
|
+
_add_text_color_from_tailwind
|
|
86
|
+
if add_handler
|
|
87
|
+
else _remove_text_color_from_tailwind
|
|
88
|
+
)
|
|
89
|
+
elif color is not None:
|
|
90
|
+
return _add_text_color if add_handler else _remove_text_color
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def remove_text_color(element: ui.element, color: str):
|
|
94
|
+
handler = _query_text_color(color, add_handler=False)
|
|
95
|
+
if handler:
|
|
96
|
+
handler(element, color)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def add_text_color(element: ui.element, color: str):
|
|
100
|
+
handler = _query_text_color(color)
|
|
101
|
+
if handler:
|
|
102
|
+
handler(element, color)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
# background color handlers
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _remove_bg_from_quasar(element: ui.element, color: str):
|
|
109
|
+
color_prop = getattr(element, "BACKGROUND_COLOR_PROP", None)
|
|
110
|
+
if color_prop:
|
|
111
|
+
element._props.pop(color_prop, None)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _remove_bg_from_tailwind(element: ui.element, color: str):
|
|
115
|
+
color = f"bg-{color}"
|
|
116
|
+
element._classes.remove(color)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _remove_background_color(element: ui.element, color: str):
|
|
120
|
+
element._style.pop("background-color", None)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _add_bg_from_quasar(element: ui.element, color: str):
|
|
124
|
+
color_prop = getattr(element, "BACKGROUND_COLOR_PROP", None)
|
|
125
|
+
if color_prop:
|
|
126
|
+
element._props[color_prop] = color
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _add_bg_from_tailwind(element: ui.element, color: str):
|
|
130
|
+
color = f"bg-{color}"
|
|
131
|
+
element.classes(color)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _add_background_color(element: ui.element, color: str):
|
|
135
|
+
element._style["background-color"] = f"{color} !important"
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
@lru_cache(maxsize=10)
|
|
139
|
+
def _query_background_color(color: str, add_handler=True):
|
|
140
|
+
if color in QUASAR_COLORS:
|
|
141
|
+
return _add_bg_from_quasar if add_handler else _remove_bg_from_quasar
|
|
142
|
+
elif color in TAILWIND_COLORS:
|
|
143
|
+
return _add_bg_from_tailwind if add_handler else _remove_bg_from_tailwind
|
|
144
|
+
elif color is not None:
|
|
145
|
+
return _add_background_color if add_handler else _remove_background_color
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def remove_background_color(element: ui.element, color: str):
|
|
149
|
+
handler = _query_background_color(color, add_handler=False)
|
|
150
|
+
if handler:
|
|
151
|
+
handler(element, color)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def add_background_color(element: ui.element, color: str):
|
|
155
|
+
handler = _query_background_color(color)
|
|
156
|
+
if handler:
|
|
157
|
+
handler(element, color)
|
ex4nicegui/reactive/vfor.js
CHANGED
|
@@ -1,13 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
props: { itemIds: Array },
|
|
1
|
+
const { h, TransitionGroup } = Vue;
|
|
3
2
|
|
|
3
|
+
export default {
|
|
4
|
+
props: { itemIds: Array, transitionProps: Object },
|
|
4
5
|
render() {
|
|
6
|
+
const is_transition = !!this.transitionProps;
|
|
5
7
|
const slotBox = this.$slots.default()
|
|
6
|
-
|
|
7
8
|
const slots = this.itemIds.map(({ elementId }) => {
|
|
8
9
|
return slotBox.find(v => v.key === elementId)
|
|
9
10
|
});
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
function beforeLeave(el) {
|
|
13
|
+
const { marginLeft, marginTop, width, height } = window.getComputedStyle(el)
|
|
14
|
+
console.log('beforeLeave');
|
|
15
|
+
el.style.left = `${el.offsetLeft - parseFloat(marginLeft, 10)}px`
|
|
16
|
+
el.style.top = `${el.offsetTop - parseFloat(marginTop, 10)}px`
|
|
17
|
+
el.style.width = width
|
|
18
|
+
el.style.height = height
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return is_transition ? h(TransitionGroup, { ...this.transitionProps, onBeforeLeave: beforeLeave }, slots) : slots;
|
|
12
22
|
}
|
|
13
23
|
}
|
ex4nicegui/reactive/vfor.py
CHANGED
|
@@ -3,7 +3,7 @@ from nicegui.element import Element
|
|
|
3
3
|
from nicegui import ui
|
|
4
4
|
from ex4nicegui.utils.clientScope import _CLIENT_SCOPE_MANAGER
|
|
5
5
|
from ex4nicegui.utils.signals import (
|
|
6
|
-
|
|
6
|
+
Ref,
|
|
7
7
|
on,
|
|
8
8
|
to_ref,
|
|
9
9
|
to_ref_wrapper,
|
|
@@ -21,7 +21,7 @@ from typing import (
|
|
|
21
21
|
TypeVar,
|
|
22
22
|
Generic,
|
|
23
23
|
Union,
|
|
24
|
-
|
|
24
|
+
Literal,
|
|
25
25
|
)
|
|
26
26
|
from functools import partial
|
|
27
27
|
from dataclasses import dataclass
|
|
@@ -38,35 +38,90 @@ class VforItem(Empty):
|
|
|
38
38
|
|
|
39
39
|
|
|
40
40
|
class VforContainer(Element, component="vfor.js"):
|
|
41
|
-
def __init__(self) -> None:
|
|
41
|
+
def __init__(self, transition_props: Optional[Dict[str, Any]] = None) -> None:
|
|
42
42
|
super().__init__()
|
|
43
43
|
self._props["itemIds"] = []
|
|
44
44
|
|
|
45
|
+
if transition_props:
|
|
46
|
+
self._props["transitionProps"] = transition_props
|
|
47
|
+
|
|
45
48
|
def update_items(self, item_ids: List[Dict]):
|
|
46
49
|
self._props["itemIds"] = item_ids
|
|
47
50
|
self.update()
|
|
48
51
|
|
|
49
52
|
|
|
53
|
+
def wrapper_getter_setter(ref: Ref, index: Ref[int], *keys: Union[str, int]):
|
|
54
|
+
proxy = ref.value
|
|
55
|
+
|
|
56
|
+
def getter():
|
|
57
|
+
item = proxy[index.value]
|
|
58
|
+
result = item
|
|
59
|
+
|
|
60
|
+
for k in keys:
|
|
61
|
+
result = result[k]
|
|
62
|
+
return result
|
|
63
|
+
|
|
64
|
+
def setter(value):
|
|
65
|
+
item = proxy[index.value]
|
|
66
|
+
|
|
67
|
+
if len(keys) == 0:
|
|
68
|
+
proxy[index.value] = value
|
|
69
|
+
|
|
70
|
+
if len(keys) == 1:
|
|
71
|
+
item[keys[0]] = value
|
|
72
|
+
return
|
|
73
|
+
|
|
74
|
+
obj = item[keys[0]]
|
|
75
|
+
for k in keys[1:-1]:
|
|
76
|
+
obj = obj[k]
|
|
77
|
+
obj[keys[-1]] = value
|
|
78
|
+
|
|
79
|
+
return getter, setter
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class VforStoreItem(Generic[_T], RefWrapper[_T]):
|
|
83
|
+
def __init__(
|
|
84
|
+
self,
|
|
85
|
+
source: _T_data,
|
|
86
|
+
index: Ref[int],
|
|
87
|
+
keys: Optional[List[Union[str, int]]] = None,
|
|
88
|
+
) -> None:
|
|
89
|
+
self._source = source
|
|
90
|
+
self._data_index = index
|
|
91
|
+
self._keys = keys or []
|
|
92
|
+
|
|
93
|
+
getter, setter = wrapper_getter_setter(source, index, *self._keys)
|
|
94
|
+
super().__init__(getter, setter)
|
|
95
|
+
|
|
96
|
+
def __getitem__(self, key: Union[str, int]):
|
|
97
|
+
return VforStoreItem(self._source, self._data_index, self._keys + [key])
|
|
98
|
+
|
|
99
|
+
|
|
50
100
|
class VforStore(Generic[_T]):
|
|
51
101
|
def __init__(self, source: _T_data, index: int) -> None:
|
|
52
102
|
self._source = source
|
|
103
|
+
self._raw_index = index
|
|
53
104
|
self._data_index = to_ref(index)
|
|
54
105
|
|
|
55
106
|
@property
|
|
56
107
|
def row_index(self):
|
|
108
|
+
"""Returns the responsive index of the current row."""
|
|
57
109
|
return self._data_index
|
|
58
110
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
111
|
+
@property
|
|
112
|
+
def raw_index(self):
|
|
113
|
+
"""Returns the index of the current row."""
|
|
114
|
+
return self._raw_index
|
|
62
115
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
)
|
|
67
|
-
wrapper._is_readonly = True
|
|
116
|
+
def get_item(self) -> _T:
|
|
117
|
+
"""Returns the current item."""
|
|
118
|
+
return to_value(self._source)[self.raw_index] # type: ignore
|
|
68
119
|
|
|
69
|
-
|
|
120
|
+
def get(self) -> Ref[_T]:
|
|
121
|
+
"""Returns a ref object of the current item.
|
|
122
|
+
Suitable for immutable types such as `int`, `str`, `float`, etc.
|
|
123
|
+
"""
|
|
124
|
+
return VforStoreItem(self._source, self._data_index) # type: ignore
|
|
70
125
|
|
|
71
126
|
def update(self, index: int):
|
|
72
127
|
self._data_index.value = index
|
|
@@ -80,10 +135,6 @@ class StoreItem:
|
|
|
80
135
|
scope: Scope
|
|
81
136
|
|
|
82
137
|
|
|
83
|
-
def _get_key_with_index(idx: int, data: Any):
|
|
84
|
-
return idx
|
|
85
|
-
|
|
86
|
-
|
|
87
138
|
def _get_key_with_getter(attr: str, idx: int, data: Any):
|
|
88
139
|
return get_attribute(data, attr)
|
|
89
140
|
|
|
@@ -97,28 +148,27 @@ class vfor(Generic[_T]):
|
|
|
97
148
|
|
|
98
149
|
|
|
99
150
|
## Examples
|
|
100
|
-
```python
|
|
101
|
-
from ex4nicegui.reactive import rxui
|
|
102
|
-
from ex4nicegui import to_ref
|
|
103
|
-
items = to_ref(
|
|
104
|
-
[
|
|
105
|
-
{"message": "foo", "done": False},
|
|
106
|
-
{"message": "bar", "done": True},
|
|
107
|
-
]
|
|
108
|
-
)
|
|
109
151
|
|
|
152
|
+
.. code-block:: python
|
|
153
|
+
from ex4nicegui.reactive import rxui
|
|
154
|
+
from ex4nicegui import to_ref
|
|
155
|
+
items = to_ref(
|
|
156
|
+
[
|
|
157
|
+
{"message": "foo", "done": False},
|
|
158
|
+
{"message": "bar", "done": True},
|
|
159
|
+
]
|
|
160
|
+
)
|
|
110
161
|
|
|
111
|
-
@rxui.vfor(items,key='message')
|
|
112
|
-
def _(store: rxui.VforStore):
|
|
113
|
-
msg_ref = store.get("message") # this is ref object
|
|
114
162
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
rxui.input(value=msg_ref)
|
|
119
|
-
rxui.checkbox(text=msg_ref, value=store.get("done"))
|
|
163
|
+
@rxui.vfor(items,key='message')
|
|
164
|
+
def _(store: rxui.VforStore):
|
|
165
|
+
msg_ref = store.get("message") # this is ref object
|
|
120
166
|
|
|
121
|
-
|
|
167
|
+
# type text into the input box and
|
|
168
|
+
# the title of the checkbox changes sync
|
|
169
|
+
with ui.card():
|
|
170
|
+
rxui.input(value=msg_ref)
|
|
171
|
+
rxui.checkbox(text=msg_ref, value=store.get("done"))
|
|
122
172
|
|
|
123
173
|
"""
|
|
124
174
|
|
|
@@ -128,9 +178,9 @@ class vfor(Generic[_T]):
|
|
|
128
178
|
*,
|
|
129
179
|
key: Optional[Union[str, Callable[[int, Any], Any]]] = None,
|
|
130
180
|
) -> None:
|
|
131
|
-
self._vfor_container = VforContainer()
|
|
132
181
|
self._data = to_ref_wrapper(lambda: data) if is_reactive(data) else data
|
|
133
|
-
self._get_key =
|
|
182
|
+
self._get_key = vfor.index_key
|
|
183
|
+
self._transition_props = {}
|
|
134
184
|
|
|
135
185
|
if isinstance(key, str):
|
|
136
186
|
self._get_key = partial(_get_key_with_getter, key)
|
|
@@ -139,11 +189,27 @@ class vfor(Generic[_T]):
|
|
|
139
189
|
|
|
140
190
|
self._store_map: Dict[Union[Any, int], StoreItem] = {}
|
|
141
191
|
|
|
192
|
+
def transition_group(
|
|
193
|
+
self,
|
|
194
|
+
css=True,
|
|
195
|
+
name: Optional[str] = "list",
|
|
196
|
+
duration: Optional[float] = None,
|
|
197
|
+
type: Optional[Literal["transition", "animation"]] = None,
|
|
198
|
+
mode: Optional[Literal["in-out", "out-in", "default"]] = None,
|
|
199
|
+
appear=False,
|
|
200
|
+
):
|
|
201
|
+
kws = {k: v for k, v in locals().items() if k != "self" and v is not None}
|
|
202
|
+
self._transition_props.update(**kws)
|
|
203
|
+
|
|
204
|
+
return self
|
|
205
|
+
|
|
142
206
|
def __call__(self, fn: Callable[[Any], None]):
|
|
207
|
+
_vfor_container = VforContainer(self._transition_props)
|
|
208
|
+
|
|
143
209
|
def build_element(index: int, value):
|
|
144
210
|
key = self._get_key(index, value)
|
|
145
211
|
|
|
146
|
-
with
|
|
212
|
+
with _vfor_container, VforItem() as element:
|
|
147
213
|
store = VforStore(self._data, index) # type: ignore
|
|
148
214
|
scope = _CLIENT_SCOPE_MANAGER.new_scope()
|
|
149
215
|
|
|
@@ -153,18 +219,27 @@ class vfor(Generic[_T]):
|
|
|
153
219
|
|
|
154
220
|
return key, element, store, scope
|
|
155
221
|
|
|
156
|
-
for idx, value in enumerate(to_value(self._data)): # type: ignore
|
|
157
|
-
key, element, store, scope = build_element(idx, value)
|
|
158
|
-
self._store_map[key] = StoreItem(store, element.id, scope)
|
|
159
|
-
|
|
160
222
|
ng_client = ui.context.client
|
|
161
223
|
|
|
162
|
-
@on(self._data, deep=True)
|
|
224
|
+
@on(self._data, deep=True, priority_level=-1)
|
|
163
225
|
def _():
|
|
164
226
|
data_map = {
|
|
165
227
|
self._get_key(idx, d): d for idx, d in enumerate(to_value(self._data))
|
|
166
228
|
}
|
|
167
229
|
|
|
230
|
+
# remove item if it's not in the new data
|
|
231
|
+
remove_items = [
|
|
232
|
+
(key, value)
|
|
233
|
+
for key, value in self._store_map.items()
|
|
234
|
+
if key not in data_map
|
|
235
|
+
]
|
|
236
|
+
|
|
237
|
+
for key, item in remove_items:
|
|
238
|
+
target = ng_client.elements.get(item.elementId)
|
|
239
|
+
_vfor_container.remove(target) # type: ignore
|
|
240
|
+
item.scope.dispose()
|
|
241
|
+
del self._store_map[key]
|
|
242
|
+
|
|
168
243
|
for idx, (key, value) in enumerate(data_map.items()):
|
|
169
244
|
store_item = self._store_map.get(key)
|
|
170
245
|
if store_item:
|
|
@@ -177,22 +252,17 @@ class vfor(Generic[_T]):
|
|
|
177
252
|
key, element, store, score = build_element(idx, value)
|
|
178
253
|
self._store_map[key] = StoreItem(store, element.id, score)
|
|
179
254
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
for key, value in self._store_map.items()
|
|
184
|
-
if key not in data_map
|
|
255
|
+
item_ids = [
|
|
256
|
+
{"key": key, "elementId": self._store_map.get(key).elementId} # type: ignore
|
|
257
|
+
for key in data_map.keys()
|
|
185
258
|
]
|
|
186
259
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
260
|
+
_vfor_container.update_items(item_ids)
|
|
261
|
+
|
|
262
|
+
@staticmethod
|
|
263
|
+
def value_key(_, data):
|
|
264
|
+
return data
|
|
192
265
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
for key in data_map.keys()
|
|
197
|
-
]
|
|
198
|
-
)
|
|
266
|
+
@staticmethod
|
|
267
|
+
def index_key(idx, _):
|
|
268
|
+
return idx
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Callable, Union, Type, TypeVar
|
|
3
|
+
from ex4nicegui.utils.signals import (
|
|
4
|
+
deep_ref,
|
|
5
|
+
is_ref,
|
|
6
|
+
to_value,
|
|
7
|
+
to_ref,
|
|
8
|
+
on,
|
|
9
|
+
to_raw,
|
|
10
|
+
ref_computed as computed,
|
|
11
|
+
Ref,
|
|
12
|
+
)
|
|
13
|
+
from ex4nicegui.utils.types import ReadonlyRef
|
|
14
|
+
from functools import partial
|
|
15
|
+
from signe.core.reactive import NoProxy
|
|
16
|
+
|
|
17
|
+
_CACHED_VARS_FLAG = "__vm_cached__"
|
|
18
|
+
|
|
19
|
+
_T = TypeVar("_T")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ViewModel(NoProxy):
|
|
23
|
+
"""A base class for reactive view models.
|
|
24
|
+
|
|
25
|
+
@see - https://github.com/CrystalWindSnake/ex4nicegui/blob/main/README.en.md#ViewModel
|
|
26
|
+
|
|
27
|
+
@中文文档 - https://gitee.com/carson_add/ex4nicegui/tree/main/#ViewModel
|
|
28
|
+
|
|
29
|
+
Example:
|
|
30
|
+
.. code-block:: python
|
|
31
|
+
from ex4nicegui import rxui
|
|
32
|
+
|
|
33
|
+
class MyVm(rxui.ViewModel):
|
|
34
|
+
count = rxui.var(0)
|
|
35
|
+
data = rxui.var(lambda: [1,2,3])
|
|
36
|
+
|
|
37
|
+
vm = MyVm()
|
|
38
|
+
|
|
39
|
+
rxui.label(vm.count)
|
|
40
|
+
rxui.number(value=vm.count)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
def __init__(self):
|
|
46
|
+
for name, value in self.__class__.__dict__.items():
|
|
47
|
+
if is_ref(value):
|
|
48
|
+
setattr(self, name, deep_ref(to_value(value)))
|
|
49
|
+
if callable(value) and hasattr(value, _CACHED_VARS_FLAG):
|
|
50
|
+
setattr(self, name, computed(partial(value, self)))
|
|
51
|
+
|
|
52
|
+
@staticmethod
|
|
53
|
+
def display(model: Union[ViewModel, Type]):
|
|
54
|
+
result = to_ref("")
|
|
55
|
+
|
|
56
|
+
watch_refs = _recursive_to_refs(model)
|
|
57
|
+
|
|
58
|
+
all_refs = {
|
|
59
|
+
key: value for key, value in model.__dict__.items() if is_ref(value)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@on(watch_refs)
|
|
63
|
+
def _():
|
|
64
|
+
result.value = str(
|
|
65
|
+
{key: _recursive_to_value(value) for key, value in all_refs.items()}
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
return result
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _recursive_to_value(value_or_model):
|
|
72
|
+
value = to_raw(to_value(value_or_model))
|
|
73
|
+
|
|
74
|
+
if isinstance(value, ViewModel):
|
|
75
|
+
all_refs = {
|
|
76
|
+
key: value for key, value in value.__dict__.items() if is_ref(value)
|
|
77
|
+
}
|
|
78
|
+
return {key: _recursive_to_value(value) for key, value in all_refs.items()}
|
|
79
|
+
elif isinstance(value, dict):
|
|
80
|
+
return {key: _recursive_to_value(val) for key, val in value.items()}
|
|
81
|
+
elif isinstance(value, list):
|
|
82
|
+
return [_recursive_to_value(val) for val in value]
|
|
83
|
+
else:
|
|
84
|
+
return value
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _recursive_to_refs(model):
|
|
88
|
+
result = []
|
|
89
|
+
stack = [model]
|
|
90
|
+
|
|
91
|
+
def handle_ref(value):
|
|
92
|
+
if is_ref(value):
|
|
93
|
+
stack.append(value.value)
|
|
94
|
+
result.append(value)
|
|
95
|
+
|
|
96
|
+
elif isinstance(value, (ViewModel, Type)):
|
|
97
|
+
stack.append(value)
|
|
98
|
+
elif isinstance(value, dict):
|
|
99
|
+
stack.append(value)
|
|
100
|
+
|
|
101
|
+
while stack:
|
|
102
|
+
current = stack.pop()
|
|
103
|
+
|
|
104
|
+
if isinstance(current, (ViewModel, Type)):
|
|
105
|
+
for key, value in current.__dict__.items():
|
|
106
|
+
handle_ref(value)
|
|
107
|
+
|
|
108
|
+
elif isinstance(current, dict):
|
|
109
|
+
for key, value in current.items():
|
|
110
|
+
handle_ref(value)
|
|
111
|
+
|
|
112
|
+
elif isinstance(current, list):
|
|
113
|
+
for value in current:
|
|
114
|
+
handle_ref(value)
|
|
115
|
+
|
|
116
|
+
return result
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
_T_Var_Value = TypeVar("_T_Var_Value")
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def var(value: Union[_T_Var_Value, Callable[[], _T_Var_Value]]) -> Ref[_T_Var_Value]:
|
|
123
|
+
"""Create a reactive variable. Only use within rxui.ViewModel.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
value (Union[_T_Var_Value, Callable[[], _T_Var_Value]]): The initial value or a function to generate the initial value.
|
|
127
|
+
|
|
128
|
+
Example:
|
|
129
|
+
.. code-block:: python
|
|
130
|
+
from ex4nicegui import rxui
|
|
131
|
+
class MyVm(rxui.ViewModel):
|
|
132
|
+
count = rxui.var(0)
|
|
133
|
+
data = rxui.var(lambda: [1,2,3])
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
"""
|
|
137
|
+
if callable(value):
|
|
138
|
+
return deep_ref(value())
|
|
139
|
+
return deep_ref(value)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def cached_var(func: Callable[..., _T]) -> ReadonlyRef[_T]:
|
|
143
|
+
"""A decorator to cache the result of a function. Only use within rxui.ViewModel.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
func (Callable): The function to cache.
|
|
147
|
+
|
|
148
|
+
Example:
|
|
149
|
+
.. code-block:: python
|
|
150
|
+
from ex4nicegui import rxui
|
|
151
|
+
class MyVm(rxui.ViewModel):
|
|
152
|
+
name = rxui.var("John")
|
|
153
|
+
|
|
154
|
+
@rxui.cached_var
|
|
155
|
+
def uppper_name(self):
|
|
156
|
+
return self.name.value.upper()
|
|
157
|
+
|
|
158
|
+
"""
|
|
159
|
+
setattr(func, _CACHED_VARS_FLAG, None)
|
|
160
|
+
return func # type: ignore
|
ex4nicegui/reactive/vmodel.py
CHANGED
|
@@ -26,6 +26,7 @@ import executing
|
|
|
26
26
|
import ast
|
|
27
27
|
import warnings
|
|
28
28
|
from ex4nicegui.reactive.systems.object_system import get_attribute, set_attribute
|
|
29
|
+
from ex4nicegui.utils.types import _TMaybeRef as TMaybeRef, Ref
|
|
29
30
|
|
|
30
31
|
_T = TypeVar("_T")
|
|
31
32
|
|
|
@@ -138,23 +139,24 @@ def vmodel(expr: Any, *attrs: Union[str, int]) -> TRef[Any]:
|
|
|
138
139
|
expr (Any): _description_
|
|
139
140
|
|
|
140
141
|
## Examples
|
|
141
|
-
```python
|
|
142
|
-
from ex4nicegui.reactive import rxui
|
|
143
|
-
from ex4nicegui import deep_ref
|
|
144
142
|
|
|
145
|
-
|
|
143
|
+
.. code-block:: python
|
|
144
|
+
from ex4nicegui.reactive import rxui
|
|
145
|
+
from ex4nicegui import deep_ref
|
|
146
146
|
|
|
147
|
-
|
|
147
|
+
data = deep_ref({"a": 1, "b": [1, 2, 3, 4]})
|
|
148
148
|
|
|
149
|
-
|
|
150
|
-
rxui.input(value=data.value["a"])
|
|
149
|
+
rxui.label(lambda: f"{data.value=!s}")
|
|
151
150
|
|
|
152
|
-
|
|
153
|
-
|
|
151
|
+
# No binding effect
|
|
152
|
+
rxui.input(value=data.value["a"])
|
|
153
|
+
|
|
154
|
+
# readonly binding
|
|
155
|
+
rxui.input(value=lambda: data.value["a"])
|
|
156
|
+
|
|
157
|
+
# two-way binding
|
|
158
|
+
rxui.input(value=rxui.vmodel(data.value,'a'))
|
|
154
159
|
|
|
155
|
-
# two-way binding
|
|
156
|
-
rxui.input(value=rxui.vmodel(data.value["a"]))
|
|
157
|
-
```
|
|
158
160
|
|
|
159
161
|
"""
|
|
160
162
|
|
|
@@ -205,3 +207,31 @@ def vmodel(expr: Any, *attrs: Union[str, int]) -> TRef[Any]:
|
|
|
205
207
|
TRef,
|
|
206
208
|
wrapper,
|
|
207
209
|
)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def vmodel_with_index(ref: Ref, index: TMaybeRef[int], *keys: Union[str, int]) -> Ref:
|
|
213
|
+
proxy = ref.value
|
|
214
|
+
|
|
215
|
+
def getter():
|
|
216
|
+
item = proxy[to_value(index)]
|
|
217
|
+
result = item
|
|
218
|
+
|
|
219
|
+
for k in keys:
|
|
220
|
+
result = get_attribute(result, k)
|
|
221
|
+
return result
|
|
222
|
+
|
|
223
|
+
def setter(value):
|
|
224
|
+
item = proxy[to_value(index)]
|
|
225
|
+
|
|
226
|
+
if len(keys) == 1:
|
|
227
|
+
set_attribute(item, keys[0], value)
|
|
228
|
+
return
|
|
229
|
+
|
|
230
|
+
obj = get_attribute(item, keys[0])
|
|
231
|
+
|
|
232
|
+
for k in keys[1:-1]:
|
|
233
|
+
set_attribute(obj, k, get_attribute(obj, k))
|
|
234
|
+
|
|
235
|
+
set_attribute(obj, keys[-1], value)
|
|
236
|
+
|
|
237
|
+
return RefWrapper(getter, setter) # type: ignore
|