├── app ├── .python-version ├── services │ ├── __init__.py │ └── helpers.py ├── ico.ico ├── assets │ ├── images │ │ ├── logo.png │ │ └── extension_icon.png │ └── css │ │ └── global-css.css ├── config.json ├── .gitignore ├── components │ ├── data_content.py │ ├── settings_content.py │ ├── shipping_content.py │ ├── pallets_content.py │ ├── packings_content.py │ ├── orders_content.py │ ├── production_content.py │ ├── dashboard_content.py │ └── print_component.py ├── pyproject.toml ├── footer.py ├── main.py └── header.py ├── .gitignore ├── Demo.gif ├── FRYCODE_LAB.png ├── Demo_Sidebar_Open.png ├── Demo_Sidebar_Closed.png ├── docker-compose.yaml ├── Dockerfile └── README.md /app/.python-version: -------------------------------------------------------------------------------- 1 | 3.11 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/__pycache__/ 2 | **/.nicegui/ 3 | -------------------------------------------------------------------------------- /app/services/__init__.py: -------------------------------------------------------------------------------- 1 | # This file makes the services directory a Python package -------------------------------------------------------------------------------- /Demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frycodelab/nicegui-component-based/HEAD/Demo.gif -------------------------------------------------------------------------------- /app/ico.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frycodelab/nicegui-component-based/HEAD/app/ico.ico -------------------------------------------------------------------------------- /FRYCODE_LAB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frycodelab/nicegui-component-based/HEAD/FRYCODE_LAB.png -------------------------------------------------------------------------------- /app/services/helpers.py: -------------------------------------------------------------------------------- 1 | async def dummy_function(): 2 | return "Helper function called successfully!" -------------------------------------------------------------------------------- /Demo_Sidebar_Open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frycodelab/nicegui-component-based/HEAD/Demo_Sidebar_Open.png -------------------------------------------------------------------------------- /Demo_Sidebar_Closed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frycodelab/nicegui-component-based/HEAD/Demo_Sidebar_Closed.png -------------------------------------------------------------------------------- /app/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frycodelab/nicegui-component-based/HEAD/app/assets/images/logo.png -------------------------------------------------------------------------------- /app/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName" : "Production Suite", 3 | "appVersion" : "Beta 1.0", 4 | "appPort" : 8080 5 | } -------------------------------------------------------------------------------- /app/assets/images/extension_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frycodelab/nicegui-component-based/HEAD/app/assets/images/extension_icon.png -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | # Python-generated files 2 | __pycache__/ 3 | *.py[oc] 4 | build/ 5 | dist/ 6 | wheels/ 7 | *.egg-info 8 | 9 | # Virtual environments 10 | .venv 11 | -------------------------------------------------------------------------------- /app/components/data_content.py: -------------------------------------------------------------------------------- 1 | from nicegui import ui, app 2 | 3 | def content(kundennummer) -> None: 4 | 5 | error_label_c = ui.label(kundennummer).style('color: red;') 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/components/settings_content.py: -------------------------------------------------------------------------------- 1 | from nicegui import ui, app 2 | 3 | def content() -> None: 4 | with ui.row().classes('w-full'): 5 | ui.icon('settings', size='md').classes('') 6 | ui.label('Settings').style('font-size: 1.0rem; font-weight: 500;').classes('mt-1') -------------------------------------------------------------------------------- /app/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "nicegui-component-based" 3 | version = "0.1.0" 4 | description = "Add your description here" 5 | readme = "README.md" 6 | requires-python = ">=3.11" 7 | dependencies = [ 8 | "nicegui", 9 | "nicegui[highcharts]", 10 | "pyinstaller>=6.13.0", 11 | "pywebview>=5.4", 12 | ] 13 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | nicegui: 3 | image: template-dss:latest 4 | build: 5 | context: . 6 | ports: 7 | - 8080:8080 8 | volumes: 9 | - ./app:/app # mounting local app directory 10 | environment: 11 | - PUID=1000 # change this to your user id 12 | - PGID=1000 # change this to your group id 13 | - STORAGE_SECRET="change-this-to-yor-own-private-secret" -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Use the official NiceGUI image as the base image 2 | FROM zauberzeug/nicegui:latest 3 | 4 | RUN pip install nicegui[highcharts] 5 | # Set working directory 6 | WORKDIR /app 7 | 8 | # Copy the application code to the container 9 | COPY ./app /app 10 | 11 | # Expose the port your NiceGUI app runs on (default is 8080) 12 | EXPOSE 8080 13 | 14 | # Set the default command to run the NiceGUI app 15 | CMD ["python", "main.py"] -------------------------------------------------------------------------------- /app/components/shipping_content.py: -------------------------------------------------------------------------------- 1 | from nicegui import ui, app 2 | 3 | def content() -> None: 4 | 5 | ui.label('Welcome to the Shipping Center').style('font-size: 1.5rem;').style('font-weight: 400;') 6 | 7 | search_input = ui.input('Search for packets', placeholder="Type in id/number...").classes('w-2/3 q-input mb-6').props("outlined clearable rounded standout") 8 | search_input.add_slot('prepend','''''') -------------------------------------------------------------------------------- /app/components/pallets_content.py: -------------------------------------------------------------------------------- 1 | from nicegui import ui, app, events 2 | 3 | def content() -> None: 4 | 5 | ui.label('Welcome to the Pallet Center').style('font-size: 1.5rem;').style('font-weight: 400;') 6 | 7 | search_input = ui.input('Search for pallets', placeholder="Type in id/customer...").classes('w-2/3 q-input mb-6').props("outlined clearable rounded standout") 8 | search_input.add_slot('prepend','''''') 9 | 10 | -------------------------------------------------------------------------------- /app/components/packings_content.py: -------------------------------------------------------------------------------- 1 | from nicegui import ui, app, events 2 | 3 | def content() -> None: 4 | 5 | ui.label('Welcome to the Packaging Center').style('font-size: 1.5rem;').style('font-weight: 400;') 6 | 7 | search_input = ui.input('Search for packages', placeholder="Type in id/customer...").classes('w-2/3 q-input mb-6').props("outlined clearable rounded standout") 8 | search_input.add_slot('prepend','''''') 9 | 10 | -------------------------------------------------------------------------------- /app/components/orders_content.py: -------------------------------------------------------------------------------- 1 | from nicegui import ui, app 2 | from datetime import datetime 3 | 4 | def content(searchFilter=None) -> None: 5 | 6 | ui.label('Welcome to the Order Center').style('font-size: 1.5rem;').style('font-weight: 400;') 7 | 8 | search_input = ui.input('Search for cutomers', placeholder="Type in name/customer...").classes('w-2/3 q-input mb-6').props("outlined clearable rounded standout") 9 | search_input.add_slot('prepend','''''') -------------------------------------------------------------------------------- /app/components/production_content.py: -------------------------------------------------------------------------------- 1 | from nicegui import ui, app 2 | 3 | def content(searchFilter = None) -> None: 4 | 5 | ui.label('Welcome to Manufacturing').style('font-size: 1.5rem;').style('font-weight: 400;') 6 | 7 | search_input = ui.input('Search for components', placeholder="Type in id/number").classes('w-2/3 q-input mb-6').props("outlined clearable rounded standout") 8 | search_input.add_slot('prepend','''''') 9 | 10 | search_input.value = searchFilter -------------------------------------------------------------------------------- /app/footer.py: -------------------------------------------------------------------------------- 1 | from contextlib import contextmanager 2 | 3 | from nicegui import ui,app 4 | 5 | @contextmanager 6 | def frame(title: str, version : str): 7 | ui.add_head_html('') 8 | 9 | with ui.footer().classes('w-full items-center'): 10 | ui.space() 11 | ui.html("""

Copyright © 2024, All Right Reserved NiceGUI | Frycode Lab.

""") 12 | ui.space() 13 | 14 | with ui.button(on_click= lambda: ui.run_javascript("window.open('https://github.com/frycodelab','_newtab')")).props("outline").style("margin-right:4px"): 15 | ui.icon('eva-github', color="white").classes('text-5xl') 16 | ui.tooltip("Got GitHub repo @frycodelab") 17 | ui.space() -------------------------------------------------------------------------------- /app/components/dashboard_content.py: -------------------------------------------------------------------------------- 1 | from nicegui import ui, app 2 | import random 3 | import services.helpers as helpers 4 | 5 | def content() -> None: 6 | 7 | ui.label('Welcome to the Dashboard').style('font-size: 1.5rem;').style('font-weight: 400;') 8 | 9 | chart = ui.highchart({ 10 | 'title': False, 11 | 'chart': {'type': 'bar'}, 12 | 'xAxis': {'categories': ['A', 'B']}, 13 | 'series': [ 14 | {'name': 'Alpha', 'data': [0.1, 0.2]}, 15 | {'name': 'Beta', 'data': [0.3, 0.4]}, 16 | ], 17 | }).classes('w-full h-64') 18 | 19 | async def update(): 20 | chart.options['series'][0]['data'][0] = random.random() 21 | ui.notify(await helpers.dummy_function()) 22 | chart.update() 23 | 24 | with ui.row().classes('w-full items-center justify-end'): 25 | ui.button('Delete', on_click= lambda : ui.notify('Item was deleted...')).props('flat no-caps').classes('google-like-button secondary') 26 | ui.button('Cancel', on_click= lambda : ui.notify('Task was canceled...')).props('flat no-caps').classes('google-like-button tertiary') 27 | ui.button('Update', on_click=update).props("").props('flat no-caps').classes('google-like-button primary') -------------------------------------------------------------------------------- /app/components/print_component.py: -------------------------------------------------------------------------------- 1 | from nicegui import ui, app 2 | from io import BytesIO 3 | import base64 4 | import asyncio 5 | 6 | def content(data) -> None: 7 | 8 | def base64_decode(base64_string): 9 | """ 10 | Decodes a Base64 string to its original representation. 11 | 12 | Args: 13 | base64_string: The Base64 encoded string. 14 | 15 | Returns: 16 | str: The decoded string. 17 | """ 18 | try: 19 | # Decode the Base64 string to bytes 20 | decoded_bytes = base64.b64decode(base64_string) 21 | 22 | # Decode the bytes to a string (assuming UTF-8 encoding) 23 | decoded_string = decoded_bytes.decode('utf-8') 24 | 25 | return decoded_string 26 | except Exception as e: 27 | print(f"Error decoding Base64 string: {e}") 28 | return None 29 | decoded_data = base64_decode(data) 30 | 31 | ui.html( 32 | f''' 33 |
{decoded_data}
34 | ''') 35 | 36 | ui.run_javascript(''' 37 | 38 | function printWithoutBlocking() { 39 | // Create an invisible iframe 40 | const iframe = document.createElement('iframe'); 41 | iframe.style.position = 'absolute'; 42 | iframe.style.width = '0'; 43 | iframe.style.height = '0'; 44 | iframe.style.border = 'none'; 45 | document.body.appendChild(iframe); 46 | 47 | // Get the iframe's document and copy the current tab's content 48 | const iframeDoc = iframe.contentWindow.document; 49 | iframeDoc.open(); 50 | iframeDoc.write(document.documentElement.outerHTML); 51 | iframeDoc.close(); 52 | 53 | // Trigger printing from the iframe 54 | iframe.contentWindow.focus(); 55 | iframe.contentWindow.print(); 56 | 57 | // Remove the iframe after a delay to ensure the printing process completes 58 | document.body.removeChild(iframe); 59 | window.close(); 60 | } 61 | 62 | // Example usage: 63 | printWithoutBlocking(); 64 | 65 | ''') 66 | #ui.run_javascript('window.print();') 67 | -------------------------------------------------------------------------------- /app/main.py: -------------------------------------------------------------------------------- 1 | import header 2 | import components.dashboard_content 3 | import components.shipping_content 4 | import components.production_content 5 | import components.orders_content 6 | import components.pallets_content 7 | import components.packings_content 8 | import components.data_content 9 | import components.settings_content 10 | import components.print_component 11 | from pathlib import Path 12 | import json 13 | from nicegui import app, ui 14 | import os 15 | from functools import wraps 16 | 17 | with open('config.json', 'r') as file: 18 | config = json.load(file) 19 | 20 | # Read config file 21 | appName = config["appName"] 22 | appVersion = config["appVersion"] 23 | appPort = config["appPort"] 24 | 25 | app.add_static_files('/assets', "assets") 26 | 27 | # Create a global logo image instance to prevent reloading 28 | logo_image = None 29 | 30 | def get_logo_image(): 31 | global logo_image 32 | if logo_image is None: 33 | logo_image = ui.image('assets/images/logo.png').style('width: 5rem; height: auto;') 34 | return logo_image 35 | 36 | 37 | def with_base_layout(route_handler): 38 | @wraps(route_handler) 39 | def wrapper(*args, **kwargs): 40 | ui.colors(primary='#212121', secondary="#B4C3AA", positive='#53B689', accent='#111B1E') 41 | ui.add_head_html("") 42 | ui.add_head_html("") 43 | 44 | # Preload the logo image to prevent flickering 45 | ui.add_head_html('') 46 | 47 | if 'sidebar-collapsed' not in app.storage.user: 48 | app.storage.user['sidebar-collapsed'] = True 49 | print("Sidebar state initialized to False") 50 | 51 | with header.frame(title=appName, version=appVersion, get_logo_func=get_logo_image): 52 | return route_handler(*args, **kwargs) 53 | return wrapper 54 | 55 | 56 | @ui.page('/') 57 | @with_base_layout 58 | def index(): 59 | components.dashboard_content.content() 60 | 61 | 62 | @ui.page('/shipping') 63 | @with_base_layout 64 | def shipping(): 65 | components.shipping_content.content() 66 | 67 | 68 | @ui.page('/production') 69 | @with_base_layout 70 | def production(): 71 | components.production_content.content(searchFilter='') 72 | 73 | 74 | @ui.page('/production/{searchFilter}') 75 | @with_base_layout 76 | def production_search(searchFilter): 77 | components.production_content.content(searchFilter=searchFilter) 78 | 79 | 80 | @ui.page('/orders') 81 | @with_base_layout 82 | def orders(): 83 | components.orders_content.content() 84 | 85 | 86 | @ui.page('/pallets') 87 | @with_base_layout 88 | def pallets(): 89 | components.pallets_content.content() 90 | 91 | 92 | @ui.page('/packing') 93 | @with_base_layout 94 | def packing(): 95 | components.packings_content.content() 96 | 97 | 98 | @ui.page('/settings') 99 | @with_base_layout 100 | def settings(): 101 | components.settings_content.content() 102 | 103 | 104 | @ui.page('/customer/{kundennummer}') 105 | @with_base_layout 106 | def customer_page(kundennummer): 107 | components.data_content.content(kundennummer) 108 | 109 | 110 | @ui.page('/print/{data}') 111 | def print_page(data): 112 | ui.colors(primary='#212121', secondary="#B4C3AA", positive='#53B689', accent='#111B1E') 113 | ui.add_head_html("") 114 | components.print_component.content(data) 115 | 116 | 117 | # For dev 118 | ui.run(storage_secret="myStorageSecret", title=appName, port=appPort, favicon='ico.ico', reconnect_timeout=20) # log_level="debug") 119 | 120 | # For prod 121 | # ui.run(host='0.0.0.0', storage_secret="myStorageSecret", title=appName, port=appPort, favicon='ico.ico', reconnect_timeout=20, reload=False) 122 | 123 | # For native 124 | # ui.run(storage_secret="myStorageSecret", title=appName, port=appPort, favicon='🧿', reload=False, native=True, window_size=(1600,900)) 125 | 126 | # For Docker 127 | # ui.run(storage_secret=os.environ['STORAGE_SECRET'], host=os.environ['HOST'], title=appName, port=appPort, favicon='ico.ico', reconnect_timeout=20, reload=False) 128 | 129 | # python -m PyInstaller --name 'ProductionSuite' --onedir main.py --add-data 'C:\Users\Anwender\Desktop\Frycode-Lab Projekte\ProductionSuite\app\venv\Lib\site-packages\nicegui;nicegui' --noconfirm --clean #--add-data "ico.ico;." --icon="ico.ico" -------------------------------------------------------------------------------- /app/assets/css/global-css.css: -------------------------------------------------------------------------------- 1 | /* Dropdown container */ 2 | .account-dropdown { 3 | font-family: 'Roboto', sans-serif; 4 | min-width: 200px; 5 | background: #ffffff; 6 | border-radius: 12px; 7 | box-shadow: 0 6px 24px rgba(0, 0, 0, 0.12); 8 | padding: 24px 0 16px 0; 9 | margin-top: 0px; 10 | } 11 | 12 | q-menu { 13 | border-radius: 12px !important; 14 | /* Additional styles for the menu can be added here */ 15 | } 16 | /* User name / title */ 17 | .account-dropdown .account-name { 18 | font-weight: 600; 19 | font-size: 1.2rem; 20 | color: #212121; 21 | padding: 0 24px 12px 24px; 22 | letter-spacing: 0.015em; 23 | } 24 | 25 | /* Separator line */ 26 | .account-dropdown .account-separator { 27 | border-top: 1px solid #e0e0e0; 28 | margin: 0 24px 16px 24px; 29 | } 30 | 31 | /* Menu item */ 32 | .account-menu-item { 33 | display: flex; 34 | align-items: center; 35 | padding: 0px 24px; 36 | cursor: pointer; 37 | border-radius: 12px; 38 | transition: background-color 0.2s ease-in-out; 39 | font-size: 1rem; 40 | color: #333; 41 | font-weight: 500; 42 | margin-bottom: 4px; 43 | } 44 | 45 | .account-menu-item:hover { 46 | background-color: #f4f6fb; 47 | } 48 | 49 | /* Icon next to menu item */ 50 | .account-menu-item .account-icon { 51 | color: #1a73e8; 52 | font-size: 1.4rem; 53 | margin-right: 12px; 54 | } 55 | 56 | /* Logout item */ 57 | .account-menu-item.logout { 58 | color: #e53935; 59 | } 60 | 61 | .account-menu-item.logout:hover { 62 | background-color: #ffeaea; 63 | } 64 | 65 | 66 | .google-like-button { 67 | display: inline-block !important; 68 | padding: 8px 30px !important; 69 | border: none !important; 70 | border-radius: 34px !important; 71 | font-size: 14px !important; 72 | font-weight: bold !important; 73 | cursor: pointer !important; 74 | transition: background-color 0.2s ease-in-out !important; 75 | box-shadow: none !important; 76 | } 77 | 78 | 79 | .google-like-button.primary { 80 | background-color: #4285f4 !important; 81 | color: #fff !important; 82 | } 83 | 84 | .google-like-button.primary:hover { 85 | background-color: #357aed !important; 86 | } 87 | 88 | .google-like-button.secondary { 89 | background-color: #f44336 !important; 90 | color: #fff !important; 91 | } 92 | 93 | .google-like-button.secondary:hover { 94 | background-color: #d32f2f !important; 95 | } 96 | 97 | .google-like-button.tertiary { 98 | background-color: #ffffff !important; 99 | color: #4285f4 !important; 100 | border: 1px solid grey !important; 101 | padding: 7px 28px !important; 102 | } 103 | 104 | .google-like-button.tertiary:hover { 105 | background-color: #d8d8d8 !important; 106 | } 107 | 108 | .google-like-button.disabled { 109 | opacity: 0.5; 110 | cursor: default !important; 111 | } 112 | 113 | @media (min-width: 600px) { 114 | .q-dialog__inner--minimized>div { 115 | max-width: none; 116 | } 117 | } 118 | 119 | .google-print-button { 120 | background-color: #f5f5f5; 121 | /* Light gray background */ 122 | border: 1px solid #ccc; 123 | /* Light gray border */ 124 | border-radius: 3px; 125 | padding: 6px 10px; 126 | cursor: pointer; 127 | outline: none; 128 | display: inline-flex; 129 | align-items: center; 130 | justify-content: center; 131 | } 132 | 133 | .google-print-button svg { 134 | margin-right: 5px; 135 | fill: #4285f4; 136 | /* Google blue */ 137 | } 138 | 139 | .container { 140 | width: 610px; 141 | margin: 5px auto; 142 | } 143 | 144 | .text-left { 145 | font-size: 0.9rem !important; 146 | } 147 | 148 | .q-badge { 149 | font-size: 1.0rem !important; 150 | } 151 | 152 | .greyscale-opacity { 153 | filter: grayscale(0.5) opacity(0.4); 154 | } 155 | 156 | .q-info { 157 | font-size: 1.0rem !important; 158 | border-left: #357aed 5px solid !important; 159 | } 160 | 161 | .app-name { 162 | font-family: 'Helvetica Neue', Arial, sans-serif; 163 | font-size: 24px; 164 | color: #333; 165 | margin: 0; 166 | padding-left: 6px; 167 | } 168 | 169 | /* Smooth sidebar transitions */ 170 | .q-drawer { 171 | transition: width 0.3s cubic-bezier(0.4, 0.0, 0.2, 1) !important; 172 | } 173 | 174 | .sidebar-label { 175 | transition: opacity 0.3s ease-in-out, transform 0.3s ease-in-out !important; 176 | transform-origin: left center; 177 | white-space: nowrap; 178 | overflow: hidden; 179 | display: inline-block; 180 | min-width: 0; 181 | } 182 | 183 | .sidebar-label.collapsed { 184 | opacity: 0; 185 | transform: translateX(-10px); 186 | width: 0 !important; 187 | min-width: 0 !important; 188 | } 189 | 190 | .sidebar-label.expanded { 191 | opacity: 1; 192 | transform: translateX(0); 193 | width: auto; 194 | } 195 | 196 | /* Ensure row layout stability */ 197 | .q-item__section { 198 | align-items: center !important; 199 | } 200 | 201 | /* Prevent layout shifts during transitions */ 202 | .sidebar-row { 203 | min-height: 48px; 204 | align-items: center; 205 | display: flex !important; 206 | flex-wrap: nowrap !important; 207 | } -------------------------------------------------------------------------------- /app/header.py: -------------------------------------------------------------------------------- 1 | from contextlib import contextmanager 2 | from nicegui import ui, app 3 | 4 | @contextmanager 5 | def frame(title: str, version : str, get_logo_func=None): 6 | 7 | current_route = ui.context.client.page.path 8 | 9 | async def toggle_sidebar(): 10 | app.storage.user['sidebar-collapsed'] = not app.storage.user['sidebar-collapsed'] 11 | 12 | if app.storage.user['sidebar-collapsed']: 13 | # Expanding: Adjust width first, then show labels 14 | left_drawer.props("width=300") 15 | corps.text = "Collapse" 16 | corps.icon = "chevron_left" 17 | 18 | # Small delay then show labels 19 | await ui.run_javascript('new Promise(resolve => setTimeout(resolve, 50))') 20 | 21 | for label in sidebar_labels: 22 | label.classes(remove='collapsed', add='expanded') 23 | else: 24 | # Collapsing: Hide labels first, then adjust width 25 | for label in sidebar_labels: 26 | label.classes(remove='expanded', add='collapsed') 27 | 28 | # Wait for label fade out animation 29 | await ui.run_javascript('new Promise(resolve => setTimeout(resolve, 50))') 30 | 31 | left_drawer.props("width=100") 32 | corps.text = "" 33 | corps.icon = "chevron_right" 34 | 35 | """Custom page frame to share the same styling and behavior across all pages""" 36 | with ui.header().classes(replace='row items-center h-16 justify-start') as header: 37 | ui.label("").tailwind("pr-4") 38 | # Use CSS background image for better performance and no flicker 39 | ui.html('
') 40 | ui.label("").tailwind("px-0.5") 41 | ui.label(title).classes('app-name') 42 | #badge = ui.badge(version, color="grey").style("margin-left: 0.5rem;").props("outline size=0.6rem align='top'") 43 | ui.space() 44 | # Modern account dropdown styled for the app (menu look) 45 | with ui.dropdown_button('', icon='account_circle', color='#004A77').classes('mr-4 mb-2').props('flat push no-icon-animation auto-close') as account_dropdown: 46 | 47 | with ui.element('div').classes('account-dropdown'): 48 | # Display Name 49 | ui.label('John Doe').classes('account-name') 50 | 51 | # Divider 52 | ui.element('div').classes('account-separator') 53 | 54 | # Account Item 55 | with ui.row().classes('account-menu-item').style('min-height: 48px;').on('click', lambda e: ui.notify('Account clicked')): 56 | ui.icon('person').classes('account-icon') 57 | ui.label('Account') 58 | 59 | # Settings Item 60 | with ui.row().classes('account-menu-item').style('min-height: 48px;').on('click', lambda e: ui.navigate.to('/settings')): 61 | ui.icon('settings').classes('account-icon') 62 | ui.label('Settings') 63 | 64 | # Divider 65 | ui.element('div').classes('account-separator') 66 | 67 | # Logout Item 68 | with ui.row().classes('account-menu-item logout').style('min-height: 48px;').on('click', lambda e: ui.notify('Logout clicked')): 69 | ui.icon('logout').classes('account-icon') 70 | ui.label('Logout') 71 | 72 | header.style('background-color: #F8FAFD;') 73 | 74 | with ui.left_drawer().classes('text-black relative').style('background-color: #F8FAFD; transition: width 0.3s ease-in-out;').props('breakpoint=400') as left_drawer: 75 | 76 | # Store references to labels for smooth toggle 77 | sidebar_labels = [] 78 | 79 | ''' 80 | with ui.dropdown_button('Neu', icon='add', color='white').props('flat push fab no-icon-animation auto-close').style('box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3); border-radius:20px; ').classes('mb-2'): 81 | with ui.column(): 82 | with ui.button().classes('cursor-pointer w-full mt-4').props('flat').style('border-radius: 0;'): 83 | with ui.row().classes('w-full'): 84 | ui.icon('person_add') 85 | ui.label('Neues item anlegen').style('color: black; font-size: 0.8rem; font-weight: 500;') 86 | ''' 87 | 88 | with ui.link('', '/').classes(f'w-full no-underline text-black {"bg-light-blue-2" if current_route == "/" else ""}').style('border-radius: 2rem;'): 89 | with ui.row().classes('items-center mb-2 mt-2 cursor-pointer w-full no-wrap'): 90 | ui.icon('dashboard').classes(f'ml-5 text-2xl flex-shrink-0').style(f'{"color:#004A77;" if current_route == "/" else ""}') 91 | dashborad_label = ui.label('Dashboard').classes('text-lg sidebar-label ml-3 flex-shrink-0') 92 | sidebar_labels.append(dashborad_label) 93 | 94 | with ui.link('', '/shipping').classes(f'w-full no-underline text-black {"bg-light-blue-3" if current_route.startswith("/shipping") or current_route.startswith("/customer") else ""}').style('border-radius: 2rem;'): 95 | with ui.row().classes('items-center mb-2 mt-2 cursor-pointer w-full no-wrap'): 96 | ui.icon('local_shipping').classes('ml-5 text-2xl flex-shrink-0').style(f'{"color:#004A77;" if current_route.startswith("/shipping") or current_route.startswith("/customer") else ""}') 97 | shipping_label = ui.label('Shipping').classes('text-lg sidebar-label ml-3 flex-shrink-0') 98 | sidebar_labels.append(shipping_label) 99 | 100 | with ui.link('', '/production').classes(f'w-full no-underline text-black {"bg-light-blue-3" if current_route.startswith("/production") else ""}').style('border-radius: 2rem;'): 101 | with ui.row().classes('items-center mb-2 mt-2 cursor-pointer w-full no-wrap'): 102 | ui.icon('precision_manufacturing').classes('ml-5 text-2xl flex-shrink-0').style(f'{"color:#004A77;" if current_route.startswith("/production") else ""}') 103 | production_label = ui.label('Production').classes('text-lg sidebar-label ml-3 flex-shrink-0') 104 | sidebar_labels.append(production_label) 105 | 106 | with ui.link('', '/orders').classes(f'w-full no-underline text-black {"bg-light-blue-3" if current_route.startswith("/orders") else ""}').style('border-radius: 2rem;'): 107 | with ui.row().classes('items-center mb-2 mt-2 cursor-pointer w-full no-wrap'): 108 | ui.icon('fact_check').classes('ml-5 text-2xl flex-shrink-0').style(f'{"color:#004A77;" if current_route.startswith("/orders") else ""}') 109 | orders_label = ui.label('Orders').classes('text-lg sidebar-label ml-3 flex-shrink-0') 110 | sidebar_labels.append(orders_label) 111 | 112 | with ui.link('', '/pallets').classes(f'w-full no-underline text-black {"bg-light-blue-3" if current_route.startswith("/pallets") else ""}').style('border-radius: 2rem;'): 113 | with ui.row().classes('items-center mb-2 mt-2 cursor-pointer w-full no-wrap'): 114 | ui.icon('pallet').classes('ml-5 text-2xl flex-shrink-0').style(f'{"color:#004A77;" if current_route.startswith("/pallets") else ""}') 115 | pallets_label = ui.label('Paletts').classes('text-lg sidebar-label ml-3 flex-shrink-0') 116 | sidebar_labels.append(pallets_label) 117 | 118 | with ui.link('', '/packing').classes(f'w-full no-underline text-black {"bg-light-blue-3" if current_route.startswith("/packing") else ""}').style('border-radius: 2rem;'): 119 | with ui.row().classes('items-center mb-2 mt-2 cursor-pointer w-full no-wrap'): 120 | ui.icon('inventory_2').classes('ml-5 text-2xl flex-shrink-0').style(f'{"color:#004A77;" if current_route.startswith("/packing") else ""}') 121 | packing_label = ui.label('Packing').classes('text-lg sidebar-label ml-3 flex-shrink-0') 122 | sidebar_labels.append(packing_label) 123 | 124 | ui.separator() 125 | 126 | with ui.link('', '/settings').classes(f'w-full no-underline text-black {"bg-light-blue-3" if current_route.startswith("/settings") else ""}').style('border-radius: 2rem;'): 127 | with ui.row().classes('items-center mb-2 mt-2 cursor-pointer w-full no-wrap'): 128 | ui.icon('settings').classes('ml-5 text-2xl flex-shrink-0').style(f'{"color:#004A77;" if current_route.startswith("/settings") else ""}') 129 | settings_label = ui.label('Settings').classes('text-lg sidebar-label ml-3 flex-shrink-0') 130 | sidebar_labels.append(settings_label) 131 | 132 | corps = ui.button("Collapse", icon='chevron_left').classes('absolute bottom-4 right-4 transition-all duration-300').props('flat').on('click', lambda: toggle_sidebar()) 133 | 134 | # Initialize sidebar state 135 | if app.storage.user['sidebar-collapsed']: 136 | # Expanded state 137 | left_drawer.props("width=300") 138 | corps.text = "Collapse" 139 | corps.icon = "chevron_left" 140 | for label in sidebar_labels: 141 | label.classes(add='expanded') 142 | else: 143 | # Collapsed state 144 | left_drawer.props("width=100") 145 | corps.text = "" 146 | corps.icon = "chevron_right" 147 | for label in sidebar_labels: 148 | label.classes(add='collapsed') 149 | 150 | with ui.column().classes('items-center w-full'): 151 | yield -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | We are focused on developing custom software solutions for different purposes. 4 | This template is the result of the learning curve we had developing many applications. 5 | We want to share it with the community - to help NiceGUI becomming bigger. A big thank you to @zauberzeug/niceGUI for this amazing framework. 6 |
7 | 8 | 9 | # NiceGUI Component-Based Boilerplate 10 | 11 | A modern, component-based NiceGUI application boilerplate with a responsive sidebar, header wrapper, and modular architecture. Built with `uv` for fast dependency management and featuring smooth animations, optimized performance, and a clean UI. 12 | 13 | ![Python](https://img.shields.io/badge/python-v3.11+-blue.svg) 14 | ![NiceGUI](https://img.shields.io/badge/NiceGUI-latest-green.svg) 15 | ![UV](https://img.shields.io/badge/uv-package%20manager-orange.svg) 16 | ![License](https://img.shields.io/badge/license-MIT-blue.svg) 17 | 18 | 19 | 20 | ## 🚀 Features 21 | 22 | ### 🎨 UI/UX Features 23 | - **Responsive Collapsible Sidebar** with smooth animations 24 | - **Modern Header** with account dropdown menu 25 | - **Component-based Architecture** for maintainable code 26 | - **Print System** with Base64 support for documents/images 27 | - **Custom Styling** with Google-inspired button 28 | 29 | ### 🏗️ Architecture Features 30 | - **Modular Component System** - Each page is a separate component 31 | - **Service Layer** - Helper functions organized in services directory 32 | - **Configuration-driven** - Centralized config management 33 | - **Route Wrapper System** - Consistent layout across all pages 34 | - **Asset Management** - Organized CSS, images, and static files 35 | 36 | ### ⚡ Performance Optimizations 37 | - **Logo Preloading** - Prevents flickering on page loads 38 | - **Global CSS Injection** - Optimized styling delivery 39 | - **Favicon Support** - Professional branding 40 | - **Static File Serving** - Efficient asset delivery 41 | 42 | ## 📁 Project Structure 43 | 44 | ``` 45 | nicegui-base-main/ 46 | ├── main.py # Main application entry point 47 | ├── header.py # Header component with sidebar 48 | ├── footer.py # Footer component (optional) 49 | ├── pyproject.toml # UV/Python project configuration 50 | ├── uv.lock # UV lock file for reproducible builds 51 | ├── ico.ico # Application favicon 52 | ├── config.json # Application configuration 53 | │ 54 | ├── assets/ 55 | │ ├── css/ 56 | │ │ ├── global-css.css # Global application styles 57 | │ │ └── icons.css # Tabler icons CSS 58 | │ └── images/ 59 | │ ├── logo.png # Application logo 60 | │ └── extension_icon.png 61 | │ 62 | ├── components/ # Page components 63 | │ ├── dashboard_content.py 64 | │ ├── shipping_content.py 65 | │ ├── production_content.py 66 | │ ├── orders_content.py 67 | │ ├── pallets_content.py 68 | │ ├── packings_content.py 69 | │ ├── data_content.py 70 | │ ├── settings_content.py 71 | │ └── print_component.py # Special print functionality 72 | │ 73 | └── services/ # Helper functions and utilities 74 | ├── __init__.py 75 | └── helpers.py 76 | ``` 77 | 78 | ## 🛠️ Installation & Setup 79 | 80 | ### Prerequisites 81 | - Python 3.11+ 82 | - [UV Package Manager](https://github.com/astral-sh/uv) 83 | 84 | ### Quick Start 85 | 86 | 1. **Clone the repository** 87 | ```bash 88 | git clone "https://github.com/frycodelab/nicegui-component-based" 89 | cd nicegui-base-main 90 | ``` 91 | 92 | 2. **Install dependencies with UV** 93 | ```bash 94 | uv sync 95 | ``` 96 | 97 | 3. **Create your configuration file** 98 | ```json 99 | { 100 | "appName": "Your App Name", 101 | "appVersion": "1.0.0", 102 | "appPort": 8080 103 | } 104 | ``` 105 | 106 | 4. **Run the application** 107 | ```bash 108 | uv run python main.py 109 | ``` 110 | 111 | ## 🎯 Key Components 112 | 113 | ### 📱 Main Application (`main.py`) 114 | 115 | The main application file sets up the core functionality: 116 | 117 | - **Base Layout Wrapper**: `with_base_layout` decorator that applies consistent styling and layout 118 | - **Route Definitions**: All application routes with their respective components 119 | - **Global Configurations**: Colors, CSS injection, and asset management 120 | - **Logo Optimization**: Global logo instance to prevent reloading 121 | 122 | ```python 123 | # Logo optimization with global instance 124 | logo_image = None 125 | 126 | def get_logo_image(): 127 | global logo_image 128 | if logo_image is None: 129 | logo_image = ui.image('assets/images/logo.png').style('width: 5rem; height: auto;') 130 | return logo_image 131 | ``` 132 | 133 | ### 🎨 Header Component (`header.py`) 134 | 135 | Features a sophisticated sidebar with smooth animations: 136 | 137 | - **Collapsible Sidebar** with width transitions (300px ↔ 100px) 138 | - **Label Animations** with fade-in/fade-out effects 139 | - **Active Route Highlighting** with visual indicators 140 | - **Account Dropdown** with modern styling 141 | 142 | **Animation System:** 143 | ```python 144 | async def toggle_sidebar(): 145 | if app.storage.user['sidebar-collapsed']: 146 | # Expanding: Width first, then labels 147 | left_drawer.props("width=300") 148 | await ui.run_javascript('new Promise(resolve => setTimeout(resolve, 50))') 149 | for label in sidebar_labels: 150 | label.classes(remove='collapsed', add='expanded') 151 | else: 152 | # Collapsing: Labels first, then width 153 | for label in sidebar_labels: 154 | label.classes(remove='expanded', add='collapsed') 155 | await ui.run_javascript('new Promise(resolve => setTimeout(resolve, 50))') 156 | left_drawer.props("width=100") 157 | ``` 158 | 159 | ### 🖨️ Print System (`print_component.py`) 160 | 161 | Advanced printing functionality supporting various content types: 162 | 163 | - **Base64 Decoding** for encoded content 164 | - **Non-blocking Print** using invisible iframes 165 | - **Automatic Window Management** 166 | 167 | ### 🎨 Global Styling (`global-css.css`) 168 | 169 | Comprehensive styling system featuring: 170 | 171 | #### Account Dropdown Styling 172 | ```css 173 | .account-dropdown { 174 | font-family: 'Roboto', sans-serif; 175 | min-width: 200px; 176 | background: #ffffff; 177 | border-radius: 12px; 178 | box-shadow: 0 6px 24px rgba(0, 0, 0, 0.12); 179 | padding: 24px 0 16px 0; 180 | } 181 | ``` 182 | 183 | #### Google-Inspired Buttons 184 | ```css 185 | .google-like-button { 186 | padding: 8px 30px !important; 187 | border-radius: 34px !important; 188 | font-weight: bold !important; 189 | transition: background-color 0.2s ease-in-out !important; 190 | } 191 | ``` 192 | 193 | #### Smooth Sidebar Transitions 194 | ```css 195 | .sidebar-label { 196 | transition: opacity 0.3s ease-in-out, transform 0.3s ease-in-out !important; 197 | transform-origin: left center; 198 | } 199 | 200 | .sidebar-label.collapsed { 201 | opacity: 0; 202 | transform: translateX(-10px); 203 | } 204 | ``` 205 | 206 | ## 🔧 Configuration 207 | 208 | ### Application Config (`config.json`) 209 | ```json 210 | { 211 | "appName": "Your Application Name", 212 | "appVersion": "1.0.0", 213 | "appPort": 8080 214 | } 215 | ``` 216 | 217 | ### UV Configuration (`pyproject.toml`) 218 | ```toml 219 | [project] 220 | name = "nicegui-component-based" 221 | version = "0.1.0" 222 | requires-python = ">=3.11" 223 | dependencies = [ 224 | "nicegui", 225 | "nicegui[highcharts]", 226 | "pyinstaller>=6.13.0", 227 | "pywebview>=5.4", 228 | ] 229 | ``` 230 | 231 | ## 🎨 Customization 232 | 233 | ### Adding New Components 234 | 235 | 1. Create a new file in `components/` 236 | 2. Implement a `content()` function 237 | 3. Add route in `main.py` 238 | 4. Update sidebar navigation in `header.py` 239 | 240 | **Example Component:** 241 | ```python 242 | # components/new_component.py 243 | from nicegui import ui 244 | 245 | def content() -> None: 246 | ui.label('New Component').style('font-size: 1.5rem;') 247 | # Your component content here 248 | ``` 249 | 250 | ### Styling Customization 251 | 252 | - **Colors**: Modify in `main.py` `ui.colors()` call 253 | - **CSS**: Add custom styles to `global-css.css` 254 | - **Icons**: Uses Tabler Icons via `icons.css` 255 | 256 | ### Sidebar Navigation 257 | 258 | Add new menu items in `header.py`: 259 | ```python 260 | with ui.link('', '/your-route').classes(f'w-full no-underline text-black {"bg-light-blue-3" if current_route.startswith("/your-route") else ""}'): 261 | with ui.row().classes('items-center mb-2 mt-2 cursor-pointer w-full no-wrap'): 262 | ui.icon('your_icon').classes('ml-5 text-2xl flex-shrink-0') 263 | your_label = ui.label('Your Page').classes('text-lg sidebar-label ml-3 flex-shrink-0') 264 | sidebar_labels.append(your_label) 265 | ``` 266 | 267 | ## 🚀 Deployment Options 268 | 269 | ### Development 270 | ```bash 271 | uv run python main.py 272 | ``` 273 | 274 | ### Production 275 | ```python 276 | ui.run(host='0.0.0.0', storage_secret="your-secret", title=appName, 277 | port=appPort, favicon='ico.ico', reconnect_timeout=20, reload=False) 278 | ``` 279 | 280 | ### Native Application 281 | ```python 282 | ui.run(storage_secret="your-secret", title=appName, port=appPort, 283 | favicon='🧿', reload=False, native=True, window_size=(1600,900)) 284 | ``` 285 | 286 | ### Docker Deployment 287 | ```python 288 | ui.run(storage_secret=os.environ['STORAGE_SECRET'], 289 | host=os.environ['HOST'], title=appName, port=appPort, 290 | favicon='ico.ico', reconnect_timeout=20, reload=False) 291 | ``` 292 | 293 | - For **Docker** adjust `main.py` and use: 294 | 295 | ```bash 296 | #For Docker 297 | ui.run(storage_secret=os.environ['STORAGE_SECRET']) 298 | ``` 299 | 300 | Go one folder back in terminal where the **docker-compose.yaml** is located: 301 | 302 | ```bash 303 | cd .. 304 | docker compose up 305 | ``` 306 | 307 | Your container should build an image template:latest and run the container on http://localhost:8080. 308 | 309 | ### PyInstaller Build 310 | ```bash 311 | python -m PyInstaller --name 'YourApp' --onedir main.py --add-data 'venv/Lib/site-packages/nicegui;nicegui' --noconfirm --clean 312 | ``` 313 | 314 | ## 🎯 Performance Features 315 | 316 | ### Asset Management 317 | - **Static File Serving**: Efficient delivery via `app.add_static_files('/assets', "assets")` 318 | - **CSS Injection**: Inline styles for optimal loading 319 | - **Icon Fonts**: Tabler Icons for scalable iconography 320 | 321 | ## 🧩 Services Architecture 322 | 323 | The `services/` directory contains reusable helper functions: 324 | 325 | ```python 326 | # services/helpers.py 327 | async def dummy_function(): 328 | return "Helper function called successfully!" 329 | ``` 330 | 331 | Import and use in components: 332 | ```python 333 | import services.helpers as helpers 334 | result = await helpers.dummy_function() 335 | ``` 336 | 337 | ## 📄 Print System Usage 338 | 339 | The print component supports Base64 encoded content: 340 | 341 | ```python 342 | # Generate a print URL 343 | import base64 344 | content = "

Hello World

" 345 | encoded = base64.b64encode(content.encode()).decode() 346 | print_url = f"/print/{encoded}" 347 | 348 | # Navigate to print 349 | ui.navigate.to(print_url) 350 | ``` 351 | 352 | ## 🤝 Contributing 353 | 354 | 1. Fork the repository 355 | 2. Create a feature branch 356 | 3. Make your changes 357 | 4. Add tests if applicable 358 | 5. Submit a pull request 359 | 360 | ## 🙏 Acknowledgments 361 | 362 | - [NiceGUI](https://nicegui.io/) - The amazing Python web framework 363 | - [UV](https://github.com/astral-sh/uv) - Fast Python package manager 364 | - [Quasar Framework](https://quasar.dev/) - UI components underlying NiceGUI 365 | 366 | ## 📞 Support 367 | 368 | If you find this boilerplate helpful, please ⭐ star the repository! 369 | 370 | For questions and support, please open an issue on GitHub. 371 | 372 | --- 373 | 374 | **Happy coding with NiceGUI! 🚀** 375 | 376 | ## Authors 377 | 378 | - [@frycodelab](https://frycode-lab.com) 379 | --------------------------------------------------------------------------------