Browse Source

#338 add lazy element-to-dict conversion (but not _too_ lazy)

Falko Schindler 2 years ago
parent
commit
1c6ce7558d
3 changed files with 70 additions and 26 deletions
  1. 39 11
      nicegui/element.py
  2. 30 12
      nicegui/outbox.py
  3. 1 3
      nicegui/templates/index.html

+ 39 - 11
nicegui/element.py

@@ -54,7 +54,7 @@ class Element(ABC, Visibility):
     def __exit__(self, *_):
         self.default_slot.__exit__(*_)
 
-    def to_dict(self) -> Dict:
+    def _collect_event_dict(self) -> Dict[str, Dict]:
         events: Dict[str, Dict] = {}
         for listener in self._event_listeners:
             words = listener.type.split('.')
@@ -71,16 +71,44 @@ class Element(ABC, Visibility):
                 'args': list(set(events.get(listener.type, {}).get('args', []) + listener.args)),
                 'throttle': min(events.get(listener.type, {}).get('throttle', float('inf')), listener.throttle),
             }
-        return {
-            'id': self.id,
-            'tag': self.tag,
-            'class': self._classes,
-            'style': self._style,
-            'props': self._props,
-            'events': events,
-            'text': self._text,
-            'slots': {name: [child.id for child in slot.children] for name, slot in self.slots.items()},
-        }
+        return events
+
+    def _collect_slot_dict(self) -> Dict[str, List[int]]:
+        return {name: [child.id for child in slot.children] for name, slot in self.slots.items()}
+
+    def to_dict(self, *keys: str) -> Dict:
+        if not keys:
+            return {
+                'id': self.id,
+                'tag': self.tag,
+                'class': self._classes,
+                'style': self._style,
+                'props': self._props,
+                'text': self._text,
+                'slots': self._collect_slot_dict(),
+                'events': self._collect_event_dict(),
+            }
+        dict_: Dict[str, Any] = {}
+        for key in keys:
+            if key == 'id':
+                dict_['id'] = self.id
+            elif key == 'tag':
+                dict_['tag'] = self.tag
+            elif key == 'class':
+                dict_['class'] = self._classes
+            elif key == 'style':
+                dict_['style'] = self._style
+            elif key == 'props':
+                dict_['props'] = self._props
+            elif key == 'text':
+                dict_['text'] = self._text
+            elif key == 'slots':
+                dict_['slots'] = self._collect_slot_dict()
+            elif key == 'events':
+                dict_['events'] = self._collect_event_dict()
+            else:
+                raise ValueError(f'Unknown key {key}')
+        return dict_
 
     def classes(self, add: Optional[str] = None, *, remove: Optional[str] = None, replace: Optional[str] = None):
         '''HTML classes to modify the look of the element.

+ 30 - 12
nicegui/outbox.py

@@ -1,6 +1,6 @@
 import asyncio
 from collections import deque
-from typing import TYPE_CHECKING, Any, Deque, Tuple
+from typing import TYPE_CHECKING, Any, Deque, Dict, Set, Tuple
 
 from . import globals
 
@@ -13,26 +13,44 @@ if TYPE_CHECKING:
 queue: Deque['MessageGroup'] = deque()
 
 
-def enqueue_update(element: 'Element') -> None:
-    if queue:
-        client_id, message_type, argument = queue[-1]
-        if client_id == element.client.id and message_type == 'update':
-            elements: Deque[Element] = argument
-            elements.append(element)
-            return
-    queue.append((element.client.id, 'update', deque([element])))
+def enqueue_update(element: 'Element', *attributes: str) -> None:
+    client_id, message_type, data = queue[-1] if queue else (None, None, None)
+    if client_id != element.client.id or message_type != 'update':
+        # add new message group
+        queue.append((element.client.id, 'update', {element.id: (element, set())}))
+        return
+    elements: Dict[int, Tuple[Element, Set]] = data
+    if element.id not in elements:
+        # add new element to message group
+        elements[element.id] = [element, set()]
+        return
+    if attributes:
+        # enqueue single attributes
+        elements[element.id][1].update(attributes)
+    else:
+        # enqueue all attributes
+        elements[element.id][1].clear()
 
 
 def enqueue_message(message_type: 'MessageType', data: Any, client_id: 'ClientId') -> None:
+    _convert_elements_to_dicts()
     queue.append((client_id, message_type, data))
 
 
 async def loop() -> None:
     while True:
         while queue:
+            _convert_elements_to_dicts()
             client_id, message_type, data = queue.popleft()
-            if message_type == 'update':
-                messages: Deque[Element] = data
-                data = {'elements': {e.id: e.to_dict() for e in messages}}
             await globals.sio.emit(message_type, data, room=client_id)
         await asyncio.sleep(0.01)
+
+
+def _convert_elements_to_dicts() -> None:
+    _, message_type, data = queue[-1] if queue else (None, None, None)
+    if message_type == 'update':
+        elements: Dict[int, Tuple[Element, Set]] = data
+        for id, value in elements.items():
+            if len(value) == 2:
+                element, attributes = value
+                elements[id] = element.to_dict(*attributes)

+ 1 - 3
nicegui/templates/index.html

@@ -125,9 +125,7 @@
           window.socket.on("disconnect", () => {
             document.getElementById('popup').style.opacity = 1;
           });
-          window.socket.on("update", (msg) => {
-            Object.entries(msg.elements).forEach(([id, element]) => this.elements[element.id] = element);
-          });
+          window.socket.on("update", (msg) => Object.entries(msg).forEach(([id, el]) => this.elements[el.id] = el));
           window.socket.on("run_method", (msg) => getElement(msg.id)?.[msg.name](...msg.args));
           window.socket.on("run_javascript", (msg) => runJavascript(msg['code'], msg['request_id']));
           window.socket.on("open", (msg) => (location.href = msg));