├── 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 | 
14 | 
15 | 
16 | 
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 |
--------------------------------------------------------------------------------