Avaiga / taipy-gui

Graphical User Interface generator for Taipy
Apache License 2.0
60 stars 18 forks source link

Broadcast value to all clients #680

Closed FredLL-Avaiga closed 1 year ago

FredLL-Avaiga commented 1 year ago

What would that feature address A way to send the same var/value to all connected clients

Description of the ideal solution use websocket

Acceptance Criteria

FredLL-Avaiga commented 1 year ago

Is the name unicity an issue? If so, how to return it early enough to the name consumers (typically used as a react attribute value att={name})

name unicity is "guaranteed" by PropertyType.broadcast and Gui.broadcast()

FredLL-Avaiga commented 1 year ago

Usage example:

from taipy.gui import Gui, State
from taipy.gui.extension import ElementLibrary, Element, ElementProperty, PropertyType

import typing as t

my_label = None
val = None

def on_change(state: State, var_name: str, value: str):
    print(f"on_change(state: State, {var_name}, {value})")
    state.my_label = value

def on_action(state: State):
    state._gui.broadcast("scope", state.val)

md = """
# Custom test

<|{val}|taipy_extension_example.input|multiline|rows=10|cols=80|>

<|{__ctx.get_label(my_label)}|taipy_extension_example.label|>

<|Scope Change|button|>

"""

a_gui = Gui(md)

class MyLibraryContext():
    def __init__(self, gui: Gui) -> None:
        self.gui = gui
        self.__cn = 0

    def get_label(self, label):
        self.__cn += 1
        return f"{label}{self.__cn}"

class MyLibrary(ElementLibrary):

    elts = {
        "input": Element("value", {
            "value": ElementProperty(PropertyType.dynamic_string, "Input"),
            "multiline": ElementProperty(PropertyType.boolean, False),
            "rows": ElementProperty(PropertyType.number),
            "cols": ElementProperty(PropertyType.number),
            "scope_changed": ElementProperty(PropertyType.react, "scope")
        }),
        "label": Element("value", {
            "value": ElementProperty(PropertyType.dynamic_string),
            "scope_changed": ElementProperty(PropertyType.react, "scope")
        }, "MyLabel"),
    }

    def get_name(self) -> str:
        return "taipy_extension_example"

    def get_elements(self) -> t.Dict[str, Element]:
        return MyLibrary.elts

    def get_scripts(self) -> t.List[str]:
        return ["demo-extension-front/dist/taipy.extension.example.js"]

    def on_init(self, gui: Gui) -> t.Optional[t.Tuple[str, t.Any]]:
        return ("__ctx", MyLibraryContext(gui))

Gui.add_library(MyLibrary())
a_gui.run()
FredLL-Avaiga commented 1 year ago

Broadcast example with Producer/Consumer

from taipy.gui import Gui, State
from taipy.gui.extension import ElementLibrary, Element, ElementProperty, PropertyType

import typing as t

import threading
import time
import logging
import queue

logging.basicConfig(level=logging.DEBUG,
                    format='(%(threadName)-9s) %(message)s',)

BUF_SIZE = 10
q: queue.Queue = queue.Queue(BUF_SIZE)

class ProducerThread(threading.Thread):
    def __init__(self, group=None, target=None, name=None,
                 args=(), kwargs=None, verbose=None):
        super(ProducerThread, self).__init__()
        self.target = target
        self.name = name

    def run(self):
        while True:
            if not q.full():
                q.put(round(time.time() * 1000))
                logging.debug(f'Putting time: {q.qsize()} items in queue')
                time.sleep(0.8)
        return

val = None
my_label = None

def on_change(state: State, var_name: str, value: str):
    print(f"on_change(state: State, {var_name}, {value})")
    state.my_label = value

md = """
# Broadcast Time

<|taipy_extension_example.label|>
"""

a_gui = Gui(md)

class MyLibraryContext(threading.Thread):
    def __init__(self, gui: Gui, group=None, target=None, name=None, args=(), kwargs=None, verbose=None):
        super(MyLibraryContext, self).__init__()
        self.target = target
        self.name = name
        self.gui = gui

    def run(self):
        while True:
            if not q.empty():
                item = q.get()
                logging.debug('Getting ' + str(item)
                              + ' : ' + str(q.qsize()) + ' items in queue')
                self.gui.broadcast("ttime", item)
                time.sleep(0.6)

class MyLibrary(ElementLibrary):

    elts = {
        "label": Element("value", {
            "value": ElementProperty(PropertyType.dynamic_string),
            "time": ElementProperty(PropertyType.broadcast, "ttime")
        }, "MyLabel")
    }

    def get_name(self) -> str:
        return "taipy_extension_example"

    def get_elements(self) -> t.Dict[str, Element]:
        return MyLibrary.elts

    def get_scripts(self) -> t.List[str]:
        return ["demo-extension-front/dist/taipy.extension.example.js"]

    def on_init(self, gui: Gui) -> t.Optional[t.Tuple[str, t.Any]]:
        ctx = MyLibraryContext(gui, name='libcontext')
        ctx.start()
        return None

Gui.add_library(MyLibrary())

if __name__ == '__main__':
    p = ProducerThread(name='producer')
    p.start()

    a_gui.run()