Falko Schindler 2 лет назад
Родитель
Сommit
52fb6b6d5c
7 измененных файлов с 45 добавлено и 1 удалено
  1. 7 0
      nicegui/client.py
  2. 26 0
      nicegui/favicon.py
  3. 2 0
      nicegui/globals.py
  4. 2 0
      nicegui/nicegui.py
  5. 5 1
      nicegui/page.py
  6. 2 0
      nicegui/run.py
  7. 1 0
      nicegui/templates/index.html

+ 7 - 0
nicegui/client.py

@@ -9,6 +9,7 @@ from fastapi.responses import HTMLResponse
 
 from . import globals, vue
 from .element import Element
+from .favicon import get_favicon_url
 from .slot import Slot
 from .task_logger import create_task
 
@@ -18,7 +19,9 @@ TEMPLATE = (Path(__file__).parent / 'templates' / 'index.html').read_text()
 class Client:
 
     def __init__(self,
+                 path: str = '/',
                  title: Optional[str] = None,
+                 favicon: Optional[str] = None,
                  dark: Optional[bool] = ...,
                  ) -> None:
         self.id = globals.next_client_id
@@ -41,7 +44,10 @@ class Client:
 
         self.head_html = ''
         self.body_html = ''
+
+        self.path = path
         self.title = title
+        self.favicon = favicon
         self.dark = dark
 
     @property
@@ -70,6 +76,7 @@ class Client:
             .replace(r'{{ vue_scripts | safe }}', vue_scripts)
             .replace(r'{{ js_imports | safe }}', vue.generate_js_imports())
             .replace(r'{{ title }}', self.title if self.title is not None else globals.title)
+            .replace(r'{{ favicon_url }}', get_favicon_url(self.path, self.favicon))
             .replace(r'{{ dark }}', str(self.dark if self.dark is not ... else globals.dark))
         )
 

+ 26 - 0
nicegui/favicon.py

@@ -0,0 +1,26 @@
+from pathlib import Path
+
+from fastapi.responses import FileResponse
+
+from . import globals
+
+
+def create_favicon_routes() -> None:
+    fallback = Path(__file__).parent / 'static' / 'favicon.ico'
+    for path, favicon in globals.favicons.items():
+        if is_remote_url(favicon):
+            continue
+        globals.app.add_route(f'{path}/favicon.ico', lambda _: FileResponse(favicon or globals.favicon or fallback))
+    if '/' not in globals.favicons:
+        globals.app.add_route('/favicon.ico', lambda _: FileResponse(globals.favicon or fallback))
+
+
+def get_favicon_url(path: str, favicon: str) -> str:
+    favicon = favicon or globals.favicon
+    if is_remote_url(favicon):
+        return favicon
+    return f'{path[1:]}/favicon.ico' if favicon else 'static/favicon.ico'
+
+
+def is_remote_url(favicon: str) -> bool:
+    return favicon and (favicon.startswith('http://') or favicon.startswith('https://'))

+ 2 - 0
nicegui/globals.py

@@ -29,6 +29,7 @@ host: str
 port: int
 reload: bool
 title: str
+favicon: Optional[str]
 dark: Optional[bool]
 binding_refresh_interval: float
 excludes: List[str]
@@ -38,6 +39,7 @@ clients: Dict[int, 'Client'] = {}
 next_client_id: int = 0
 
 page_routes: Dict[Callable, str] = {}
+favicons: Dict[str, Optional[str]] = {}
 tasks: List[asyncio.tasks.Task] = []
 
 connect_handlers: List[Union[Callable, Awaitable]] = []

+ 2 - 0
nicegui/nicegui.py

@@ -11,6 +11,7 @@ from fastapi_socketio import SocketManager
 
 from . import binding, globals, vue
 from .client import Client
+from .favicon import create_favicon_routes
 from .helpers import safe_invoke
 from .task_logger import create_task
 
@@ -42,6 +43,7 @@ def vue_dependencies(name: str):
 def on_startup() -> None:
     globals.state = globals.State.STARTING
     globals.loop = asyncio.get_running_loop()
+    create_favicon_routes()
     [safe_invoke(t) for t in globals.startup_handlers]
     create_task(binding.loop())
     globals.state = globals.State.STARTED

+ 5 - 1
nicegui/page.py

@@ -15,6 +15,7 @@ class page:
     def __init__(self,
                  path: str, *,
                  title: Optional[str] = None,
+                 favicon: Optional[str] = None,
                  dark: Optional[bool] = ...,
                  response_timeout: float = 3.0,
                  ) -> None:
@@ -28,16 +29,19 @@ class page:
         """
         self.path = path
         self.title = title
+        self.favicon = favicon
         self.dark = dark
         self.response_timeout = response_timeout
 
         # NOTE we need to remove existing routes for this path to make sure only the latest definition is used
         globals.app.routes[:] = [r for r in globals.app.routes if r.path != path]
 
+        globals.favicons[self.path] = favicon
+
     def __call__(self, func: Callable) -> Callable:
         async def decorated(*dec_args, **dec_kwargs) -> Response:
             try:
-                with Client(title=self.title, dark=self.dark) as client:
+                with Client(path=self.path, title=self.title, favicon=self.favicon, dark=self.dark) as client:
                     if any(p.name == 'client' for p in inspect.signature(func).parameters.values()):
                         dec_kwargs['client'] = client
                     result = func(*dec_args, **dec_kwargs)

+ 2 - 0
nicegui/run.py

@@ -16,6 +16,7 @@ def run(*,
         host: str = '0.0.0.0',
         port: int = 8080,
         title: str = 'NiceGUI',
+        favicon: Optional[str] = None,
         dark: Optional[bool] = False,
         binding_refresh_interval: float = 0.1,
         show: bool = True,
@@ -30,6 +31,7 @@ def run(*,
     globals.port = port
     globals.reload = reload
     globals.title = title
+    globals.favicon = favicon
     globals.dark = dark
     globals.binding_refresh_interval = binding_refresh_interval
     globals.excludes = [e.strip() for e in exclude.split(',')]

+ 1 - 0
nicegui/templates/index.html

@@ -3,6 +3,7 @@
   <head>
     <title>{{ title }}</title>
     <script src="/static/socket.io.min.js"></script>
+    <link rel="shortcut icon" href="{{ favicon_url }}" />
     <link href="/static/fonts.css" rel="stylesheet" type="text/css" />
     <link href="/static/quasar.prod.css" rel="stylesheet" type="text/css" />
     <script src="/static/tailwind.min.js"></script>