Giter VIP home page Giter VIP logo

nicegui's People

Contributors

aersam avatar artreven avatar brianlandry avatar c00lc0de avatar codingpaula avatar crystalwindsnake avatar daelonsuzuka avatar dclause avatar diegiwg avatar dronakurl avatar elik-vayyar avatar falkoschindler avatar frankvp11 avatar github-actions[bot] avatar hogmoff avatar hroemer avatar itay-raveh avatar jacoverster avatar jensogorek avatar markbaumgarten avatar michangelis avatar miek770 avatar natankeddem avatar phoskee avatar rbeeli avatar rodja avatar steweg avatar thetableman avatar tobb10001 avatar zerocool940711 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

nicegui's Issues

Radio on_change handler does not allow async handler

async def on_analogue_input_change(cls):
    pass

# later...
ui.radio({1: ''}, on_change=on_analogue_input_change).bind_value(model, 'radio_value')

I get when running the app:

Traceback (most recent call last):
  File "/home/debian/venv/lib/python3.7/site-packages/nicegui/events.py", line 234, in handle_event
    create_task(result, name=str(handler))
  File "/home/debian/venv/lib/python3.7/site-packages/nicegui/task_logger.py", line 29, in create_task
    loop = asyncio.get_running_loop()
RuntimeError: no running event loop
/home/debian/venv/lib/python3.7/site-packages/nicegui/elements/value_element.py:12: RuntimeWarning: coroutine 'on_analogue_input_change' was never awaited
  sender.change_handler, ValueChangeEventArguments(sender=sender, socket=None, value=value)))

There is no such error if on_change is a sync function.

Security alert for NumPy<=1.21.6

GitHub recommends upgrading to NumPy 1.22.
That would kill support for Python 3.7, which is officially supported until June 2023.

Should we upgrade and leave Python 3.7 behind? Or should we wait a bit longer?

Support for enums in dropdown selection control

Consider:

class GaugeGas(enum.Enum):
    unknown = (0, 'Unknown')
    nitrogen = (1, 'Nitrogen')
    argon = (2, 'Argon')

    def __init__(self, code, display_name):
        self.code, self.display_name = code, display_name

    def __str__(self):
        return self.display_name


class Model:
    def __init__(self):
        self.gauge_1_gas = GaugeGas.nitrogen

model = Model()

# later...
ui.select(GaugeGas.__members__).bind_value(model, 'gauge_1_gas')

The list is displayed correctly, however, when I try to change the value, I get the following message in the log: Problem with websocket in component update, ignoring.

Made it work by

ui.select([str(g) for g in GaugeGas]).\
                        bind_value(model, 'gauge_1_gas',
                                   forward=lambda gas_name: (gas for gas in GaugeGas if str(gas) == gas_name).__next__(),
                                   backward=lambda gas: str(gas))

but maybe there's a simpler way?

uvicorn_reload_includes does not always work

I have a script test.py and a data file test.yaml next to it. When changing the YAML file, the automatic reload works with

ui.run(uvicorn_reload_includes='*')

but not with

ui.run(uvicorn_reload_includes='*.py,*.yaml')

Triggering the reload by changing the Python file works in both cases.

Integrate pytests into GitHub Action "on push"

We want to run all our pytests for every push so no regressions creep in unnoticed. Most of the tests are based on selenium. Therefore the GithHub action "test.yml" needs to be extended accordingly.

Tasks:

  • add ChromeDriver to GitHub Action
  • run pytests
  • fix problems with custom components like scene, keyboard or joystick

Upgrade Quasar

NiceGUI seems to be using Quasar 1.9.14 (according to the JavaScript console output).
The latest Quasar version is 2.7.7. It would be great to get at least 2.x.x.

Could not find and pre-evaluate ui.run(). Starting interactive mode without auto-reload.

I've repeated the sample from your website in my project in PyCharm:

from nicegui import ui

ui.label('Hello NiceGUI!')
ui.button('BUTTON', on_click=lambda: ui.notify('button was pressed'))

ui.run()

However, when I debug it, I get the following output:

"C:\Users\azarubkin\Documents\Python Projects\evolve_web\venv\Scripts\python.exe" "C:\Program Files\JetBrains\PyCharm 2021.2.3\plugins\python\helpers\pydev\pydevd.py" --multiprocess --qt-support=auto --client 127.0.0.1 --port 63893 --file "C:/Users/azarubkin/Documents/Python Projects/evolve_web/nicegui_main.py"
Connected to pydev debugger (build 221.5591.52)
Could not find and pre-evaluate ui.run(). Starting interactive mode without auto-reload.
C:/Users/azarubkin/Documents/Python Projects/evolve_web/venv/lib/site-packages/justpy
Module directory: C:\Users\azarubkin\Documents\Python Projects\evolve_web\venv\lib\site-packages\justpy, Application directory: C:\Users\azarubkin\Documents\Python Projects\evolve_web
JustPy ready to go on http://0.0.0.0:8080

How can I fix this so that I get hot reloading?

Ability to set custom text on the button of Upload component

I'd like to have custom text on the button of Upload component. Currently it is hardcoded as "Upload".

As far as I understand, I can manually recreate the component by copying and changing its source code, but still, some customizations readily available ("Upload" button appearance too? or even custom Form component?) would be nice.

Also it would probably be useful to clear the Input component of the upload form after the upload button is clicked.

ui.open() does not work with Firefox

In the following example Firefox reloads the start page.

#!/usr/bin/env python3
from nicegui import ui

page = ui.page('/test', 'Test')
ui.button('Open', on_click=lambda: ui.open(page))

ui.run()

The console says:

Navigated to http://0.0.0.0:1234/test
Navigated to http://0.0.0.0:1234/ 

This behavior has been observed with Firefox 99 as well as 101.

ui.tree still causes page updates

Since NiceGUI 0.8 page updates are mostly avoided and UI elements are updated individually when needed.

But ui.tree still causes page updates, because there are two event handlers that return None:

view.on('update:expanded', lambda *_: self.update())
view.on('update:ticked', lambda *_: self.update())

I tried replacing them with lambda *_: self.update() or False. But then there are no UI updates at all when unfolding the tree.

Drag and Drop ?

Is it possible to have a Drag and Drop option ? Would be great to be able to drop something like a CSV and be able to process it.

Thanks!

Input control has extra blank space at the top

The input control is higher than needed, the text I enter is near the bottom of the control rather than centered. How can I remove that extra space?
I know it is used for control label, but in my case it's empty.

Optimizing startup time

It would be nice to conditionally omit imports of LinePlot and Plot classes. Doing so brought the startup time on Beaglebone Black single board computer from 20+ to 8 seconds.

Another similar thing is justpy importing its chartcomponents.py inside, but perhaps this is an issue for their own repository.

Typo in page_ready_handler

await self.page_ready_handler(msg.socket) if is_coro else self.page_ready_handler(msg.socket)

I believe msg.socket should read msg.websocket instead based on inspecting msg variable. Otherwise, the argument passed to page ready handler is empty string.

Also is it possible to provide session_id argument in page ready handler in addition to websocket instance? Although it's not high priority, as it's possible to get it as websocket.cookies['jp_token'].

Suboptimal websocket communication

I found that when I have a label with bound text from my variable, and I update the variable after button click, I have two events transmitted over websocket: page_update with 38K of data and component_update with 464 bytes. The page update event contains the entire page data, including the updated component. Thus the component data is transmitted twice, plus there is much unneeded data in the first event, it seems.
Is it an oversight or it's needed for something?

Basically, I have the following:

class Model:
    def __init__(self):
        self.current_tm = datetime.datetime.now().astimezone()

model = Model()

def refresh_evolve_time():
    model.current_tm = datetime.datetime.now().astimezone()

# ... somewhere below...

ui.label().bind_text_from(model, 'current_tm',
                                   lambda current_tm: f"Current Date/Time: {current_tm.strftime('%x %X %Z')}")
ui.button('Refresh time', on_click=refresh_time)

Is it possible to not share the state between multiple browser windows?

In the button example,

from nicegui import ui

def button_increment():
    global button_count
    button_count += 1
    button_result.set_text(f'pressed: {button_count}')

button_count = 0
ui.button('Button', on_click=button_increment)
button_result = ui.label('pressed: 0')

ui.run()

How can I have separate button counts for different multiple browser windows?

Get path/route from page object?

Hello,

thanks for providing nicegui as open source project!

I run in the following question using nicegui: Is there any way to get the path/route of a page object?

I can set up a new page with the path /other_page as follows:

from nicegui import ui

with ui.page('/other_page') as otherPage:
      (...)

However, I found now way to get the path /other_page from the page-object otherPage .
In this example the path/route is obvious, however, in my side project things are a bit more nested and it would by nice to only pass the page object itself.

Do you have any hint?

Would it be possible to add the constructor argument route as a class member to the page class?

Thanks!

Trailing semicolon in function style() raises ValueError on dict update

Update version 0.9.0 on existing code with calls tostyle() now raises:

ValueError: dictionary update sequence element #2 has length 1; 2 is required

def str_to_dict(s: Optional[str]) -> Dict[str, str]:
    return dict((word.strip() for word in part.split(':')) for part in s.split(';')) if s else {}

element.py#L72:L73

Repro

from nicegui import ui

btn = ui.button('Click me!', on_click=lambda: ui.notify(f'You clicked me!'))
btn.style(add='text-decoration: underline; text-transform: uppercase;')

ui.run()

Remove the "interactive mode"

Discussed in #71

Originally posted by falkoschindler August 26, 2022
When NiceGUI doesn't find a ui.run(), it runs in a so-called "interactive mode". Originally it was introduced to be able to use NiceGUI interactively in Python shells like python or ipython.

Although it is of little use to interactively build UIs, it might be useful to allow importing modules that depend on NiceGUI. E.g. suppose you have written a prime number checker my_prime that comes with a function is_prime(n) and a tiny UI factory method show_sieve_of_eratosthenes(). Thanks to the interactive mode you can start python, import my_prime and fiddle around with the function is_prime(n). NiceGUI is fine with not finding ui.run() and simply doesn't start a server.

The other important use case are PyTests: We might need to import modules that depend on NiceGUI, but there is no ui.run() and thus no server needs to start.

So the purpose of this mode is actually quite clear. But the following questions remain:

  • Is it the right name? Interaction is actually not possible, since ui.run() is forbidden in interactive mode ("Error: Unexpected ui.run() in interactive mode.").
  • Is the warning correct when importing NiceGUI in a Python shell? "Could not find main script. Starting interactive mode without auto-reload." Actually it is not starting anything.
  • Can we detect when running in a PyTest and avoid printing a warning at all?

Page context is sometimes wrong

In the following example we create a scene with a card. Later a moving sphere is placed in this scene.

with ui.page('/test') as page:
    card = ui.card()

with card:
    with ui.scene() as scene:
        sphere1 = scene.sphere()
        ui.timer(0.1, lambda: sphere1.move(x=time.time() % 2 - 1))

But the movement is not visible, because UI updates are sent to clients of the current page.
And the current page is "/" and not "/test", because the sphere is defined outside of page.

Maybe the problem is in Element.__init__:

self.parent_view = view_stack[-1]
self.page = page_stack[-1]

Although the sphere is placed within the parent view card, its page is read from the global page_stack.
It could instead use the page from its parent view and the inconsistency is gone.
If this solves the issue, we should also check other accesses to the page_stack and if they are definitely correct.

As a workaround one can make the page context explicit: with page, card:. But we should improve the behavior nonetheless.

Typo in static/templates/main.html prevents web page from appearing

I had an error in Developer Console complaining about undefined symbol "False". When I looked at the place of the error, it was referring to static/templates/main.html, there was a line unloading = False;.
After I changed it to unloading = false; and also True to true in the next line, the web page started working again.

wrongly thinks I am running interactive

I am using click to write a cli tool, that has an option of showing some web interactive things.

Nicegui is wrongly assuming that I am running interactively, and .run() will not work.

I note that I do not have a main.py and no file has the usual

if __name__ == '__main__':
    main()

boilerplate - in fact I dont even know if this is important to nicegui.

Provide more complex examples

We should create an examples folder with separate subfolders for each example.

Task list:

  • Map with live coordinates
  • Custom component class
  • Custom page class
  • remove obsolete examples.py
  • move custom_example into example folder

Installation Error?

Hello,

I'm fairly new to python, but I want to try and use NiceGUI. Unfortunately after i have installed the module with pip, I get an error that says ModuleNotFoundError: No module named 'nicegui'.

Thank you!

Allow creating a new page per client

Discussed in #61

Originally posted by falkoschindler August 26, 2022
Idea: ui.page can not only be used as context, but also accepts a Callable argument for re-creating the page whenever a client connects.
If the path if / or omitted, the page replaces the main page.

This is related to #6, where @me21 posted a draft for a PrivatePage, which I haven't looked into, yet.

After discussing several concepts, we tend to break with with ui.page contexts and move towards @ui.page decorators. This simplifies defining page factory functions and is similar to how Flask and FastAPI define routes. In the end, pages are more like routes rather than hierarchical UI elements. Thus defining them like routes makes a lot of sense.

Also, in the flavor of Flask, FastAPI or JustPy, pages should be "private" by default, i.e. without shared state between different clients. A page should only be "shared" when an optional decorator argument is set True.

This change has interesting consequences:

  1. Pages are no longer built using (possibly nested!) with expressions. Thus, the (internal) page_stack is obsolete. This removes one source of confusion.
  2. The pre-evaluation of ui.run is no longer needed. Since the main script primarily defines functions, we don't need to bother about re-evaluation in case of auto-reload being activated. This complies with the traditional way of running uvicorn and removes another confusing "feature" of NiceGUI.
  3. The decorated page functions can be async.

To keep NiceGUI very accessible for new Python developers and to reduce boilerplate code for simple single-page applications, we plan to automatically create an index page if there is none. So the NiceGUI hello-world example should remain unchanged:

from nicegui import ui

ui.label('Hello, world!')

ui.run()

This will be equivalent to the following:

from nicegui import ui

@ui.page('/')
def index_page():
    ui.label('Hello, world!')

ui.run()

TODOs

  • get rid of page_stack
  • remove context enter/exit from pages class
  • use a default main page for ui elements created "globally"
  • find better place to create default main page (and maybe remove code duplication with ui.page factory)
  • find way to set head and body html for each page
  • allow configuring pages through parameters for ui.page
  • find way to apply default configurations passed in ui.run to default main page (which already has been created)
  • reloading implicitly created index page causes "No page to load" and other server errors
  • show 404 page for routes which do not exist (or at least avoid 500 error)
  • allow on_connect handler with sessions for non-shared pages (see sessions demo on main.py)
  • allow passing page builder functions to ui.link and ui.open
  • test/fix await/run java script per page
  • remove pre-evaluation of ui.run
  • how to exclude costly imports without pre-evaluation of ui.run?
  • let JustPy serve Highcharts and Aggrid on demand
  • create routes for additional libraries on demand
  • introduce environment variable "MATPLOTLIB" to avoid its costly import (don't import if it's "false")
  • how to dynamically add a first custom element? routes to js files are added on startup.
  • how to dynamically add a first chart or table? js files are excluded from the template.
  • update documentation
  • fix memory leak for private pages -> this is a starlette issue
  • what to do with page routes which are created after startup?
  • rethink the strategy of page-reload after adding js dependencies
  • NiceGUI serves wrong template folder
  • dependencies are not found when running with reload=False
  • do we trigger too many reloads after adding dependencies? --> we do not reload anymore
  • how to add dependencies on private pages that loose their state when reloading? --> make excludes explicit again
  • handle new UI elements in an event callback without container (see Color Theming and JavaScript examples)
  • fix wrong title and favicon

How do I run javascript in the browser?

In JustPy, I can execute javascript and get back result as follows:

async def result_ready(self, msg):
    if msg.request_id == 'browser_datetime':
        # do whatever I want with the result in msg.result


# somewhere later on...
wp = jp.WebPage()
wp.on('result_ready', result_ready)
await wp.run_javascript("new Date().toISOString()", request_id='browser_datetime')

I've written a roughly analogous code in NiceGui:

async def result_ready(self, msg):
    if msg.request_id == 'browser_datetime':
        # do whatever I want with the result in msg.result

# somewhere later on...
with ui.card() as top_element:
    top_element.page.on('result_ready', result_ready)
    await top_element.page.run_javascript("new Date().toISOString()", request_id='browser_datetime')

Am I doing things as intended, or there's another way to do it?

Blank page

Problem

Well, i read the source code and have many words to say. In short, i don't understand the concepts you implemented in terms of OOP.

What i'd like to have is to organize my project with importing ui object from a module of initializing all the elements and some application logic which adds a few new elements after a request to a DB. Then call method ui.run() in a top level module. This approach shows a blank page.

If i keep ui object in one module everything is working, but it's a bad practice and in my case i took nicegui as a fast WUI builder for a representation layer over some logic in application layer.

Pseudo code to explain

repr_layer.py

from app_layer import logic
from nicegui import ui

def add_elements(container):
   with container:
       result = logic()
       container.label(result)
   container.update()


def initialize_ui():
    ui.label('Any label')
    row = ui.row()
    ui.button(on_click=lambda: add_elements(row))

    return ui

run.py

from repr import initialize


if __name__ == '__main__':
    ui = initialize()
    ui.run()

And nothing is displayed here, but a server is running normally.

Expectations

Hope nicegui can be used along with some custom logic beside WUI implementation? Or it's not intended for such cases by design and better to take justpy or streamlit, for exmaple?

How to modularize and customise pages?

The recent changes to page creation with the release of 0.9.0 renders the example discussed in #50 unfeasible:

class MainPage(ui.page):
    def __init__(self):
        super().__init__('/', dark=True)
        with self:
            ui.label('Hi!')

MainPage()

I have tried adapting the code (using PageBuilder) in the simple example, but failed to get it running as before.

from nicegui import ui
from nicegui.globals import page_builders
from nicegui.page import Page
from nicegui.page_builder import PageBuilder


class MainPage(Page):
    def __init__(self):
        super().__init__(dark=True)

        ui.label('Hi!')


async def page_builder() -> Page:
    return MainPage()


def index_page():
    builder = PageBuilder(shared=False, function=page_builder)
    page_builders.setdefault('/', builder)


index_page()

ui.run()

results in

    raise RuntimeError('cannot find parent view, view stack is empty')
RuntimeError: cannot find parent view, view stack is empty

I have just recently migrated a great chunks of previously justpy code to the above inheritance approach including additional element/group classes (such as header, layout, pagecontainer, ...) and am now seeking ways to make it work, again.

@falkoschindler: could you please guide me here, again? Thanks!

This issue is possibly related to #85.

Callbacks vs Streamlit-Like "if"-statements

Hi,

I like very much what you did with nicegui and how you used justpy! Very nice.

I also looked at streamlit. Could you expound a little about why that was not a good solution for you in the end?

One feature of streamlit that I find intriguing is the ability to assign the value of a component to a variable without the need of callbacks. Of course, the callbacks happen but are transparent to the user of the framework. I am trying to think how this could be implemented in JustPy. It would be a great feature for nicegui, making it even simpler to use. What is your view on this and how do you think it could be implemented?

If you prefer to have the discussion by email instead of here, my email is [email protected] or you can also open an issue in the JustPy repository.

Eli

Provide more recent Google Material Icons

The following code should show two icons, a face and the more recent "masks" icon.

ui.icon('face')
ui.icon('masks')

But only the face is shown.
Apparently NiceGUI uses an old version of the icon font and could need an update.

Cannot add file extensions to reload

Hi,

i wanted to add ".yaml" files to the reload watcher.

ui.run(host='localhost', title='Test', uvicorn_reload_includes='.py,.yaml') 

But unfortunatly it fails with following traceback:

Traceback (most recent call last):
File "c:/GIT/MySVGrenderer/SVGTest_svgwrite.py", line 2, in
from nicegui import ui
File "C:\GIT\MySVGrenderer.venv\lib\site-packages\nicegui_init_.py", line 1, in
from nicegui.nicegui import app, ui
File "C:\GIT\MySVGrenderer.venv\lib\site-packages\nicegui\nicegui.py", line 4, in
from .ui import Ui # NOTE: before justpy
File "C:\GIT\MySVGrenderer.venv\lib\site-packages\nicegui\ui.py", line 1, in
class Ui:
File "C:\GIT\MySVGrenderer.venv\lib\site-packages\nicegui\ui.py", line 2, in Ui
from .config import config # NOTE: before run
File "C:\GIT\MySVGrenderer.venv\lib\site-packages\nicegui\config.py", line 57, in
config = Config(**args)
TypeError: init() got an unexpected keyword argument 'uvicorn_reload_includes'

Graceful shutdown

Is there any way of gracefully shutting down the server after use? I am using it to have a configuration page for a larger project and once the configuration is completed the main part of the project takes over and no more configuration is needed. I would like to kill everything UI related at that point.

Requirements conflict

I tried to upgrade my virtual environment with pip-tools and got a requirements conflict:

There are incompatible versions in the resolved dependencies:
  uvicorn==0.17.6 (from nicegui==0.8.12->-r requirements.in (line 1))
  uvicorn>=0.18.3 (from justpy==0.2.8->-r requirements.in (line 2))

Currently I have justpy==0.2.3 and nicegui==0.8.10 which do not conflict.
This probably means you should restrict some justpy versions in your requirements.

Authentication example

Now that there's an idea of creating separate web pages for different users, I feel that #15 needs a working authentication example.
I tried to apply Starlette AuthenticationMiddleware to no avail. I can post what I tried if you want. I think it's due to JustPy maintaining its own routing in addition to Starlette's one?..

I made my own solution that works (please note, it includes PrivatePage class from #6):

# could load/save session info to DB
class SessionInfo(UserDict):

    def __getitem__(self, item):
        if item not in self.data:
            self.data[item] = {}
        return super().__getitem__(item)

    def __setitem__(self, key, value):
        return super().__setitem__(key, value)


session_infos = SessionInfo()


# Creating a PrivatePage for some route will make page instances with different sessions independent.
class PrivatePage(ui.page):

    def __init__(self, route: str, setup_ui: Callable, *args, **kwargs):
        self.setup_ui, self.args, self.kwargs = setup_ui, args, kwargs
        if 'on_disconnect' in kwargs:
            del kwargs['on_disconnect']
        super().__init__(route, *args, **kwargs)
        self.individual_page_viewers: dict[str, int] = {}
        self.individual_pages: dict[str, ui.page] = {}

    async def on_disconnect(self, websocket: WebSocket):
        if 'on_disconnect' in self.kwargs:
            disconnect_handler = self.kwargs['on_disconnect']
            await disconnect_handler() if is_coroutine(disconnect_handler) else disconnect_handler()
        session_id = websocket.cookies['jp_token'].split('.')[0]
        self.individual_page_viewers[session_id] -= 1
        print(f'Viewer quit, session id {session_id}, total {self.individual_page_viewers[session_id]}')
        if self.individual_page_viewers[session_id] == 0:
            self.individual_pages.pop(session_id).remove_page()  # To reclaim used memory

    #TODO: override __enter__ and __exit__ to be able to use it in context manager like ordinary page?
    async def _route_function(self, request: Request):
        # we spawn additional page instance for each session id
        if request.session_id not in self.individual_pages:
            self.individual_pages[request.session_id] = ui.page(self.route,
                                                                *self.args,
                                                                on_disconnect=self.on_disconnect,
                                                                **self.kwargs)
            jp.Route.instances.pop(0)
            self.individual_page_viewers[request.session_id] = 0
            self.setup_ui(self.individual_pages[request.session_id])
        self.individual_page_viewers[request.session_id] += 1
        print(f'New viewer, session_id {request.session_id}, viewers {self.individual_page_viewers[request.session_id]}')
        return await self.individual_pages[request.session_id]._route_function(request)


class LoggedInMixin:
    async def _route_function(self, request: Request):
        if session_infos[request.session_id].get('authenticated', False):
            return await super()._route_function(request)
        else:
            return RedirectResponse(url='/login', status_code=303)


class LoggedInPage(LoggedInMixin, ui.page):
    pass


class LoggedInPrivatePage(LoggedInMixin, PrivatePage):
    pass


def connection(request: Request, page: ui.page):
    print(f'Session id: {request.session_id}')
    page.label.text = f'Hello {session_infos[request.session_id]["user"]}!'


def ws_connection(socket):
    session_id = socket.cookies['jp_token'].split('.')[0]
    route = socket.scope['path']


def setup_main_page(page: ui.page):
    with page:
        page.label = ui.label('Hello world')


main_page = LoggedInPrivatePage('/', on_connect=connection, on_page_ready=ws_connection, setup_ui=setup_main_page)


def login(args):
    # check password
    page = args.sender.page
    username = page.username.value
    password = page.password.value
    session_id = args.socket.cookies['jp_token'].split('.')[0]
    if (username == 'user1' and password == 'pass1') or (username == 'user2' and password == 'pass2'):
        session_infos[session_id] = {'authenticated': True, 'user': username}
        ui.open('/', args.socket)
    else:
        ui.open('/login', args.socket)


def setup_login_ui(page: ui.page):
    with page:
        page.username = ui.input('User Name').classes('w-full')
        page.password = ui.input('Password').classes('w-full').props('type=password')
        ui.button('Log in', on_click=login)


login_page = PrivatePage('/login', setup_ui=setup_login_ui)

ui.run()

It maintains session info on the server.

I was wondering if this approach is sound. Maybe it's better to store session data in a cookie? But I couldn't find a way to set a cookie in NiceGui.
Also since NiceGui is based on JustPy which is based on Starlette, I thought maybe there's a way to make standard Starlette AuthenticationMiddleware work?

Any means to download the file?

I'd like to create a button which, when clicked, causes a file to be downloaded in the browser. To my understanding, in regular HTML this is handled by href attribute.

I tried: ui.button('Download configuration file').classes('my-2 mx-4').props('href=https://quasar.dev') which, according to https://quasar.dev/vue-components/button#example--links, I thought, should cause the browser to issue GET request to go to quasar.dev website. But there's no reaction after click.

Or should it be handled with ui.add_route for serving the file and on_click=lambda: ui.open(route_url) button argument?

Allow Authentication

Hi,

I'm fairly new to the python world, but found your work and am really enjoying it;-)

I have the need to secure the gui I'm working on without putting it behind a proxy and came across https://justpy.io/tutorial/sessions/. So I thought about opening this issue, maybe it's easy for you guys to implement and others also have a need like me.

cheers
Hendrik

binding_refresh_interval not having any impact

Running on a Raspberry Pi, NiceGUI is fairly CPU intensive (although still usable). I tried changing the binding_refresh_interval value, via a ui.run() parameter, but it doesn't seem to make any difference on CPU usage.

update typing-extensions > 4.1 for python 3.10

Using python 3.10.6 with pydantic I got the following conflict:

  nicegui 0.8.9 depends on typing-extensions<4.0.0 and >=3.10.0
  pydantic 1.10.1 depends on typing-extensions>=4.1.0

typing-extensions = "^3.10.0"

As far as I can assume I suspect it should be possible to change the requirements or why is <4.0.0 a necessary?

Thank you very much! ๐Ÿ˜ธ

demjson dependance breaks install

From Python 3.10, the dependent JSON package demjson fails to install.
Since stock JSON package exists and maintenance of dependent package is problematic (dmeranda/demjson#40) changing from demjson to builtin JSON would be favorable.

Fonts are not found if NiceGUI is running behind a reverse proxy

A question about component update moment in the page lifecycle

I have a page with on_connect handler. In this handler, I start some tasks with asyncio.create_task:

cls.messages_tasks = [
    asyncio.create_task(cls.handle_di_messages(cls.mqtt_client)),
    asyncio.create_task(cls.handle_ai_messages(cls.mqtt_client)),
    asyncio.create_task(cls.handle_tc_messages(cls.mqtt_client)),
    asyncio.create_task(cls.refresh_aout_values(cls.mqtt_client)),
]

Several tasks are ongoing and the last of them is single-shot. They all are async as they request and receive values from MQTT broker.
What I experience is that I receive the message from MQTT broker in the last task, but the corresponding UI elements are sometimes not updated.

I suspect this is due to the asynchronous nature of the task:

  • if the response comes quicker than the page is rendered, it's ok
  • if the response comes after the websocket connection is established, it's ok
  • but if the response comes between the page has been loaded, but before the websocket connection is up, then the component update is lost.

Please share your thoughts on the matter - am I right? Is it possible to check it somehow?

I currently fixed it by awaiting the single-shot task instead of putting it to the common list of running tasks.
Is this fix good or it's a hackish way to work around the problem?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.