structui 0.2.0__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {structui-0.2.0 → structui-0.4.0}/PKG-INFO +76 -62
- {structui-0.2.0 → structui-0.4.0}/README.md +14 -0
- {structui-0.2.0 → structui-0.4.0}/pyproject.toml +36 -36
- {structui-0.2.0 → structui-0.4.0}/setup.cfg +4 -4
- {structui-0.2.0 → structui-0.4.0}/src/structui/__init__.py +7 -7
- {structui-0.2.0 → structui-0.4.0}/src/structui/app.py +25 -25
- {structui-0.2.0 → structui-0.4.0}/src/structui/cli.py +20 -20
- {structui-0.2.0 → structui-0.4.0}/src/structui/file_picker.py +100 -95
- {structui-0.2.0 → structui-0.4.0}/src/structui/parser.py +66 -66
- {structui-0.2.0 → structui-0.4.0}/src/structui/schema.py +136 -123
- {structui-0.2.0 → structui-0.4.0}/src/structui/state.py +139 -139
- {structui-0.2.0 → structui-0.4.0}/src/structui/ui.py +498 -464
- {structui-0.2.0 → structui-0.4.0}/src/structui/xml_parser.py +108 -108
- {structui-0.2.0 → structui-0.4.0}/src/structui.egg-info/PKG-INFO +76 -62
- {structui-0.2.0 → structui-0.4.0}/src/structui.egg-info/SOURCES.txt +4 -1
- {structui-0.2.0 → structui-0.4.0}/tests/test_app.py +62 -72
- structui-0.4.0/tests/test_cli.py +44 -0
- structui-0.4.0/tests/test_coverage_boost.py +292 -0
- {structui-0.2.0 → structui-0.4.0}/tests/test_file_picker.py +143 -120
- {structui-0.2.0 → structui-0.4.0}/tests/test_final_gap.py +131 -131
- {structui-0.2.0 → structui-0.4.0}/tests/test_parser.py +93 -93
- {structui-0.2.0 → structui-0.4.0}/tests/test_schema.py +163 -163
- {structui-0.2.0 → structui-0.4.0}/tests/test_state.py +161 -161
- {structui-0.2.0 → structui-0.4.0}/tests/test_ui.py +481 -465
- structui-0.4.0/tests/test_ui_blur.py +227 -0
- structui-0.4.0/tests/test_ui_coverage.py +160 -0
- {structui-0.2.0 → structui-0.4.0}/tests/test_ui_extra.py +146 -89
- structui-0.4.0/tests/test_ui_extra2.py +66 -0
- structui-0.4.0/tests/test_ui_final.py +506 -0
- {structui-0.2.0 → structui-0.4.0}/tests/test_xml_parser.py +154 -154
- structui-0.2.0/tests/test_cli.py +0 -22
- structui-0.2.0/tests/test_cli_entry.py +0 -18
- structui-0.2.0/tests/test_coverage_boost.py +0 -499
- {structui-0.2.0 → structui-0.4.0}/src/structui.egg-info/dependency_links.txt +0 -0
- {structui-0.2.0 → structui-0.4.0}/src/structui.egg-info/entry_points.txt +0 -0
- {structui-0.2.0 → structui-0.4.0}/src/structui.egg-info/requires.txt +0 -0
- {structui-0.2.0 → structui-0.4.0}/src/structui.egg-info/top_level.txt +0 -0
|
@@ -1,62 +1,76 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: structui
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: A format-agnostic, schema-driven, hierarchical configuration UI.
|
|
5
|
-
Author: structui contributors
|
|
6
|
-
License: MIT
|
|
7
|
-
Classifier: Development Status :: 4 - Beta
|
|
8
|
-
Classifier: Environment :: Web Environment
|
|
9
|
-
Classifier: Intended Audience :: Developers
|
|
10
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
-
Classifier: Programming Language :: Python :: 3
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
-
Requires-Python: >=3.9
|
|
17
|
-
Description-Content-Type: text/markdown
|
|
18
|
-
Requires-Dist: nicegui>=1.4.0
|
|
19
|
-
Requires-Dist: pyyaml>=6.0.1
|
|
20
|
-
Requires-Dist: pywin32>=300; sys_platform == "win32"
|
|
21
|
-
|
|
22
|
-
# StructUI
|
|
23
|
-
|
|
24
|
-
[](https://pypi.org/project/structui/)
|
|
25
|
-
[](https://github.com/MoSaeedHammad/structui/actions/workflows/ci.yml)
|
|
26
|
-
[](https://github.com/MoSaeedHammad/structui/actions/workflows/publish.yml)
|
|
27
|
-
[](https://opensource.org/licenses/MIT)
|
|
28
|
-
|
|
29
|
-
StructUI is a format-agnostic, schema-driven, hierarchical configuration UI engine built in Python. Designed as a flexible architectural backbone, it parses standard configuration files (YAML, JSON, CSV, XML) and dynamically generates a live web-based property editor based on constraints and metadata defined in a schema file.
|
|
30
|
-
|
|
31
|
-
The architecture is explicitly decoupled, making it readily extensible to strict domain-specific specifications (e.g., AUTOSAR configurators) and agent-driven programmatic workflows.
|
|
32
|
-
|
|
33
|
-
## Features
|
|
34
|
-
|
|
35
|
-
- **Pillar A: Format-Agnostic Parsers:** cleanly separate UI generation from underlying data formats. Out-of-the-box support for YAML, JSON, and XML (including schema-driven arrays and attributes), with abstract base classes extensible to CSV and others.
|
|
36
|
-
- **Pillar B: Hierarchical UI:** Dynamic tree-based rendering with full support for multidimensional containers, dynamic polymorphic list additions, and node mapping. Powered natively by NiceGUI.
|
|
37
|
-
- **Pillar C: Data Validity:** Enforces schema metadata strictly at the UI layer. Missing fields gracefully populate via defaults, required flags trigger locking, and nested typings are continuously evaluated.
|
|
38
|
-
- **Pillar D: Extensibility & Programmatic Control:** Decomposed core logic (App, Parser, State, Schema, UI) allowing external tools and wrappers (e.g. CLI, Agent Workflows) to invoke the editor or inject properties safely.
|
|
39
|
-
|
|
40
|
-
## Installation
|
|
41
|
-
|
|
42
|
-
### From PyPI (recommended)
|
|
43
|
-
|
|
44
|
-
```bash
|
|
45
|
-
pip install structui
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
### From Source (development)
|
|
49
|
-
|
|
50
|
-
```bash
|
|
51
|
-
git clone https://github.com/MoSaeedHammad/structui.git
|
|
52
|
-
cd structui
|
|
53
|
-
pip install -e .
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
## Quick Start
|
|
57
|
-
|
|
58
|
-
Launch the editor in the current directory against your local configuration files by simply typing:
|
|
59
|
-
|
|
60
|
-
```bash
|
|
61
|
-
structui --dir . --schema .structui_schema.yaml --port 8080
|
|
62
|
-
```
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: structui
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: A format-agnostic, schema-driven, hierarchical configuration UI.
|
|
5
|
+
Author: structui contributors
|
|
6
|
+
License: MIT
|
|
7
|
+
Classifier: Development Status :: 4 - Beta
|
|
8
|
+
Classifier: Environment :: Web Environment
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Requires-Python: >=3.9
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
Requires-Dist: nicegui>=1.4.0
|
|
19
|
+
Requires-Dist: pyyaml>=6.0.1
|
|
20
|
+
Requires-Dist: pywin32>=300; sys_platform == "win32"
|
|
21
|
+
|
|
22
|
+
# StructUI
|
|
23
|
+
|
|
24
|
+
[](https://pypi.org/project/structui/)
|
|
25
|
+
[](https://github.com/MoSaeedHammad/structui/actions/workflows/ci.yml)
|
|
26
|
+
[](https://github.com/MoSaeedHammad/structui/actions/workflows/publish.yml)
|
|
27
|
+
[](https://opensource.org/licenses/MIT)
|
|
28
|
+
|
|
29
|
+
StructUI is a format-agnostic, schema-driven, hierarchical configuration UI engine built in Python. Designed as a flexible architectural backbone, it parses standard configuration files (YAML, JSON, CSV, XML) and dynamically generates a live web-based property editor based on constraints and metadata defined in a schema file.
|
|
30
|
+
|
|
31
|
+
The architecture is explicitly decoupled, making it readily extensible to strict domain-specific specifications (e.g., AUTOSAR configurators) and agent-driven programmatic workflows.
|
|
32
|
+
|
|
33
|
+
## Features
|
|
34
|
+
|
|
35
|
+
- **Pillar A: Format-Agnostic Parsers:** cleanly separate UI generation from underlying data formats. Out-of-the-box support for YAML, JSON, and XML (including schema-driven arrays and attributes), with abstract base classes extensible to CSV and others.
|
|
36
|
+
- **Pillar B: Hierarchical UI:** Dynamic tree-based rendering with full support for multidimensional containers, dynamic polymorphic list additions, and node mapping. Powered natively by NiceGUI.
|
|
37
|
+
- **Pillar C: Data Validity:** Enforces schema metadata strictly at the UI layer. Missing fields gracefully populate via defaults, required flags trigger locking, and nested typings are continuously evaluated.
|
|
38
|
+
- **Pillar D: Extensibility & Programmatic Control:** Decomposed core logic (App, Parser, State, Schema, UI) allowing external tools and wrappers (e.g. CLI, Agent Workflows) to invoke the editor or inject properties safely.
|
|
39
|
+
|
|
40
|
+
## Installation
|
|
41
|
+
|
|
42
|
+
### From PyPI (recommended)
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install structui
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### From Source (development)
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
git clone https://github.com/MoSaeedHammad/structui.git
|
|
52
|
+
cd structui
|
|
53
|
+
pip install -e .
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Quick Start
|
|
57
|
+
|
|
58
|
+
Launch the editor in the current directory against your local configuration files by simply typing:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
structui --dir . --schema .structui_schema.yaml --port 8080
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Testing
|
|
65
|
+
|
|
66
|
+
To run the test suite and verify code coverage, first install the necessary development dependencies:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
pip install -e . pytest pytest-cov pytest-asyncio pyyaml nicegui
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Then, run `pytest` with coverage reporting:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
PYTHONPATH=src python3 -m pytest tests/ --cov=structui --cov-report=term-missing
|
|
76
|
+
```
|
|
@@ -39,3 +39,17 @@ Launch the editor in the current directory against your local configuration file
|
|
|
39
39
|
```bash
|
|
40
40
|
structui --dir . --schema .structui_schema.yaml --port 8080
|
|
41
41
|
```
|
|
42
|
+
|
|
43
|
+
## Testing
|
|
44
|
+
|
|
45
|
+
To run the test suite and verify code coverage, first install the necessary development dependencies:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install -e . pytest pytest-cov pytest-asyncio pyyaml nicegui
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Then, run `pytest` with coverage reporting:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
PYTHONPATH=src python3 -m pytest tests/ --cov=structui --cov-report=term-missing
|
|
55
|
+
```
|
|
@@ -1,36 +1,36 @@
|
|
|
1
|
-
[build-system]
|
|
2
|
-
requires = ["setuptools>=61.0"]
|
|
3
|
-
build-backend = "setuptools.build_meta"
|
|
4
|
-
|
|
5
|
-
[project]
|
|
6
|
-
name = "structui"
|
|
7
|
-
version = "0.
|
|
8
|
-
description = "A format-agnostic, schema-driven, hierarchical configuration UI."
|
|
9
|
-
readme = "README.md"
|
|
10
|
-
authors = [
|
|
11
|
-
{ name = "structui contributors" }
|
|
12
|
-
]
|
|
13
|
-
license = { text = "MIT" }
|
|
14
|
-
requires-python = ">=3.9"
|
|
15
|
-
dependencies = [
|
|
16
|
-
"nicegui>=1.4.0",
|
|
17
|
-
"pyyaml>=6.0.1",
|
|
18
|
-
"pywin32>=300; sys_platform == 'win32'"
|
|
19
|
-
]
|
|
20
|
-
classifiers = [
|
|
21
|
-
"Development Status :: 4 - Beta",
|
|
22
|
-
"Environment :: Web Environment",
|
|
23
|
-
"Intended Audience :: Developers",
|
|
24
|
-
"License :: OSI Approved :: MIT License",
|
|
25
|
-
"Programming Language :: Python :: 3",
|
|
26
|
-
"Programming Language :: Python :: 3.9",
|
|
27
|
-
"Programming Language :: Python :: 3.10",
|
|
28
|
-
"Programming Language :: Python :: 3.11",
|
|
29
|
-
"Programming Language :: Python :: 3.12"
|
|
30
|
-
]
|
|
31
|
-
|
|
32
|
-
[project.scripts]
|
|
33
|
-
structui = "structui.cli:main"
|
|
34
|
-
|
|
35
|
-
[tool.setuptools.packages.find]
|
|
36
|
-
where = ["src"]
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "structui"
|
|
7
|
+
version = "0.4.0"
|
|
8
|
+
description = "A format-agnostic, schema-driven, hierarchical configuration UI."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
authors = [
|
|
11
|
+
{ name = "structui contributors" }
|
|
12
|
+
]
|
|
13
|
+
license = { text = "MIT" }
|
|
14
|
+
requires-python = ">=3.9"
|
|
15
|
+
dependencies = [
|
|
16
|
+
"nicegui>=1.4.0",
|
|
17
|
+
"pyyaml>=6.0.1",
|
|
18
|
+
"pywin32>=300; sys_platform == 'win32'"
|
|
19
|
+
]
|
|
20
|
+
classifiers = [
|
|
21
|
+
"Development Status :: 4 - Beta",
|
|
22
|
+
"Environment :: Web Environment",
|
|
23
|
+
"Intended Audience :: Developers",
|
|
24
|
+
"License :: OSI Approved :: MIT License",
|
|
25
|
+
"Programming Language :: Python :: 3",
|
|
26
|
+
"Programming Language :: Python :: 3.9",
|
|
27
|
+
"Programming Language :: Python :: 3.10",
|
|
28
|
+
"Programming Language :: Python :: 3.11",
|
|
29
|
+
"Programming Language :: Python :: 3.12"
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[project.scripts]
|
|
33
|
+
structui = "structui.cli:main"
|
|
34
|
+
|
|
35
|
+
[tool.setuptools.packages.find]
|
|
36
|
+
where = ["src"]
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
[egg_info]
|
|
2
|
-
tag_build =
|
|
3
|
-
tag_date = 0
|
|
4
|
-
|
|
1
|
+
[egg_info]
|
|
2
|
+
tag_build =
|
|
3
|
+
tag_date = 0
|
|
4
|
+
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
"""StructUI - A format-agnostic, schema-driven, hierarchical configuration UI."""
|
|
2
|
-
|
|
3
|
-
__version__ = "0.1.0"
|
|
4
|
-
|
|
5
|
-
from .app import run_app
|
|
6
|
-
|
|
7
|
-
__all__ = ["run_app"]
|
|
1
|
+
"""StructUI - A format-agnostic, schema-driven, hierarchical configuration UI."""
|
|
2
|
+
|
|
3
|
+
__version__ = "0.1.0"
|
|
4
|
+
|
|
5
|
+
from .app import run_app
|
|
6
|
+
|
|
7
|
+
__all__ = ["run_app"]
|
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
from typing import Optional
|
|
2
|
-
from nicegui import ui
|
|
3
|
-
from structui.ui import StructUI
|
|
4
|
-
from structui.state import AppState
|
|
5
|
-
from structui.schema import SchemaManager
|
|
6
|
-
|
|
7
|
-
def run_app(data_dir: str = ".", schema_filepath: str = ".structui_schema.yaml", port: int = 8080, dark_mode:
|
|
8
|
-
schema_manager = SchemaManager(schema_filepath)
|
|
9
|
-
try:
|
|
10
|
-
app_state = AppState(data_dir, schema_manager)
|
|
11
|
-
load_error = None
|
|
12
|
-
except Exception as e:
|
|
13
|
-
app_state = AppState(".", schema_manager)
|
|
14
|
-
app_state.config_data = {}
|
|
15
|
-
load_error = str(e)
|
|
16
|
-
|
|
17
|
-
ui_instance = StructUI(app_state, schema_manager, dark_mode)
|
|
18
|
-
|
|
19
|
-
@ui.page('/')
|
|
20
|
-
def main_page():
|
|
21
|
-
ui_instance.render()
|
|
22
|
-
if load_error:
|
|
23
|
-
ui.notify(f"XML/Config Load Error: {load_error}", type="negative", position="top", timeout=8000)
|
|
24
|
-
|
|
25
|
-
ui.run(port=port, title="StructUI Editor", reload=False)
|
|
1
|
+
from typing import Optional
|
|
2
|
+
from nicegui import ui
|
|
3
|
+
from structui.ui import StructUI
|
|
4
|
+
from structui.state import AppState
|
|
5
|
+
from structui.schema import SchemaManager
|
|
6
|
+
|
|
7
|
+
def run_app(data_dir: str = ".", schema_filepath: str = ".structui_schema.yaml", port: int = 8080, dark_mode: bool = False):
|
|
8
|
+
schema_manager = SchemaManager(schema_filepath)
|
|
9
|
+
try:
|
|
10
|
+
app_state = AppState(data_dir, schema_manager)
|
|
11
|
+
load_error = None
|
|
12
|
+
except Exception as e:
|
|
13
|
+
app_state = AppState(".", schema_manager)
|
|
14
|
+
app_state.config_data = {}
|
|
15
|
+
load_error = str(e)
|
|
16
|
+
|
|
17
|
+
ui_instance = StructUI(app_state, schema_manager, dark_mode)
|
|
18
|
+
|
|
19
|
+
@ui.page('/')
|
|
20
|
+
def main_page():
|
|
21
|
+
ui_instance.render()
|
|
22
|
+
if load_error:
|
|
23
|
+
ui.notify(f"XML/Config Load Error: {load_error}", type="negative", position="top", timeout=8000)
|
|
24
|
+
|
|
25
|
+
ui.run(port=port, title="StructUI Editor", reload=False)
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import argparse
|
|
2
|
-
import sys
|
|
3
|
-
from structui.app import run_app
|
|
4
|
-
|
|
5
|
-
def main():
|
|
6
|
-
parser = argparse.ArgumentParser(description="StructUI Configuration Editor")
|
|
7
|
-
parser.add_argument("--dir", type=str, default=".", help="Directory containing config files")
|
|
8
|
-
parser.add_argument("--schema", type=str, default=".structui_schema.yaml", help="Path to schema file")
|
|
9
|
-
parser.add_argument("--port", type=int, default=8080, help="Port to run the UI on (default: 8080)")
|
|
10
|
-
|
|
11
|
-
args = parser.parse_args()
|
|
12
|
-
|
|
13
|
-
try:
|
|
14
|
-
run_app(data_dir=args.dir, schema_filepath=args.schema, port=args.port)
|
|
15
|
-
except Exception as e:
|
|
16
|
-
print(f"Error starting StructUI: {e}", file=sys.stderr)
|
|
17
|
-
sys.exit(1)
|
|
18
|
-
|
|
19
|
-
if __name__ in {"__main__", "__mp_main__"}:
|
|
20
|
-
main()
|
|
1
|
+
import argparse
|
|
2
|
+
import sys
|
|
3
|
+
from structui.app import run_app
|
|
4
|
+
|
|
5
|
+
def main():
|
|
6
|
+
parser = argparse.ArgumentParser(description="StructUI Configuration Editor")
|
|
7
|
+
parser.add_argument("--dir", type=str, default=".", help="Directory containing config files")
|
|
8
|
+
parser.add_argument("--schema", type=str, default=".structui_schema.yaml", help="Path to schema file")
|
|
9
|
+
parser.add_argument("--port", type=int, default=8080, help="Port to run the UI on (default: 8080)")
|
|
10
|
+
|
|
11
|
+
args = parser.parse_args()
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
run_app(data_dir=args.dir, schema_filepath=args.schema, port=args.port)
|
|
15
|
+
except Exception as e:
|
|
16
|
+
print(f"Error starting StructUI: {e}", file=sys.stderr)
|
|
17
|
+
sys.exit(1)
|
|
18
|
+
|
|
19
|
+
if __name__ in {"__main__", "__mp_main__"}: # pragma: no cover
|
|
20
|
+
main()
|
|
@@ -1,95 +1,100 @@
|
|
|
1
|
-
import platform
|
|
2
|
-
from pathlib import Path
|
|
3
|
-
from typing import Optional
|
|
4
|
-
|
|
5
|
-
from nicegui import events, ui
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class LocalFilePicker(ui.dialog):
|
|
9
|
-
|
|
10
|
-
def __init__(self, directory: str, *,
|
|
11
|
-
upper_limit: Optional[str] =
|
|
12
|
-
dirs_only: bool = False) -> None:
|
|
13
|
-
"""Local File Picker
|
|
14
|
-
|
|
15
|
-
This is a simple file picker that allows you to select a file from the local filesystem where NiceGUI is running.
|
|
16
|
-
|
|
17
|
-
:param directory: The directory to start in.
|
|
18
|
-
:param upper_limit: The directory to stop navigating up (e.g. the root directory).
|
|
19
|
-
:param multiple: Whether to allow multiple files to be selected.
|
|
20
|
-
:param show_hidden_files: Whether to show hidden files.
|
|
21
|
-
:param dirs_only: Whether to only show directories.
|
|
22
|
-
"""
|
|
23
|
-
super().__init__()
|
|
24
|
-
|
|
25
|
-
self.path = Path(directory).expanduser().resolve()
|
|
26
|
-
if upper_limit is None:
|
|
27
|
-
self.upper_limit = None
|
|
28
|
-
else:
|
|
29
|
-
self.upper_limit = Path(directory if upper_limit == ... else upper_limit).expanduser().resolve()
|
|
30
|
-
self.show_hidden_files = show_hidden_files
|
|
31
|
-
self.dirs_only = dirs_only
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
self.
|
|
36
|
-
|
|
37
|
-
'
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
ui.button('
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
self.
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
self.
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
self.
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
1
|
+
import platform
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from nicegui import events, ui
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class LocalFilePicker(ui.dialog):
|
|
9
|
+
|
|
10
|
+
def __init__(self, directory: str, *, allowed_extensions: Optional[list] = None,
|
|
11
|
+
upper_limit: Optional[str] = None, multiple: bool = False, show_hidden_files: bool = False,
|
|
12
|
+
dirs_only: bool = False) -> None:
|
|
13
|
+
"""Local File Picker
|
|
14
|
+
|
|
15
|
+
This is a simple file picker that allows you to select a file from the local filesystem where NiceGUI is running.
|
|
16
|
+
|
|
17
|
+
:param directory: The directory to start in.
|
|
18
|
+
:param upper_limit: The directory to stop navigating up (e.g. the root directory).
|
|
19
|
+
:param multiple: Whether to allow multiple files to be selected.
|
|
20
|
+
:param show_hidden_files: Whether to show hidden files.
|
|
21
|
+
:param dirs_only: Whether to only show directories.
|
|
22
|
+
"""
|
|
23
|
+
super().__init__()
|
|
24
|
+
|
|
25
|
+
self.path = Path(directory).expanduser().resolve()
|
|
26
|
+
if upper_limit is None:
|
|
27
|
+
self.upper_limit = None
|
|
28
|
+
else:
|
|
29
|
+
self.upper_limit = Path(directory if upper_limit == ... else upper_limit).expanduser().resolve()
|
|
30
|
+
self.show_hidden_files = show_hidden_files
|
|
31
|
+
self.dirs_only = dirs_only
|
|
32
|
+
self.allowed_extensions = allowed_extensions
|
|
33
|
+
|
|
34
|
+
with self, ui.card():
|
|
35
|
+
self.add_drives_toggle()
|
|
36
|
+
self.grid = ui.aggrid({
|
|
37
|
+
'columnDefs': [{'field': 'name', 'headerName': 'File'}],
|
|
38
|
+
'rowSelection': 'multiple' if multiple else 'single',
|
|
39
|
+
}, html_columns=[0]).classes('w-96').on('cellDoubleClicked', self.handle_double_click)
|
|
40
|
+
with ui.row().classes('w-full justify-end'):
|
|
41
|
+
ui.button('Cancel', on_click=self.close).props('outline')
|
|
42
|
+
ui.button('Ok', on_click=self._handle_ok)
|
|
43
|
+
self.update_grid()
|
|
44
|
+
|
|
45
|
+
def add_drives_toggle(self):
|
|
46
|
+
if platform.system() == 'Windows':
|
|
47
|
+
import win32api
|
|
48
|
+
drives = win32api.GetLogicalDriveStrings().split('\000')[:-1]
|
|
49
|
+
self.drives_toggle = ui.toggle(drives, value=drives[0], on_change=self.update_drive)
|
|
50
|
+
|
|
51
|
+
def update_drive(self):
|
|
52
|
+
self.path = Path(self.drives_toggle.value).expanduser()
|
|
53
|
+
self.update_grid()
|
|
54
|
+
|
|
55
|
+
def update_grid(self) -> None:
|
|
56
|
+
paths = list(self.path.iterdir())
|
|
57
|
+
if not self.show_hidden_files:
|
|
58
|
+
paths = [p for p in paths if not p.name.startswith('.')]
|
|
59
|
+
if self.dirs_only:
|
|
60
|
+
paths = [p for p in paths if p.is_dir()]
|
|
61
|
+
elif self.allowed_extensions:
|
|
62
|
+
allowed_exts = [ext.lower() if ext.startswith('.') else f'.{ext.lower()}' for ext in self.allowed_extensions]
|
|
63
|
+
paths = [p for p in paths if p.is_dir() or p.suffix.lower() in allowed_exts]
|
|
64
|
+
paths.sort(key=lambda p: p.name.lower())
|
|
65
|
+
|
|
66
|
+
paths.sort(key=lambda p: not p.is_dir())
|
|
67
|
+
|
|
68
|
+
self.grid.options['rowData'] = [
|
|
69
|
+
{
|
|
70
|
+
'name': f'📁 <strong>{p.name}</strong>' if p.is_dir() else p.name,
|
|
71
|
+
'path': str(p),
|
|
72
|
+
}
|
|
73
|
+
for p in paths
|
|
74
|
+
]
|
|
75
|
+
if self.upper_limit is None or self.path != self.upper_limit:
|
|
76
|
+
self.grid.options['rowData'].insert(0, {
|
|
77
|
+
'name': '📁 <strong>..</strong>',
|
|
78
|
+
'path': str(self.path.parent),
|
|
79
|
+
})
|
|
80
|
+
self.grid.update()
|
|
81
|
+
|
|
82
|
+
def handle_double_click(self, e: events.GenericEventArguments) -> None:
|
|
83
|
+
self.path = Path(e.args['data']['path'])
|
|
84
|
+
if self.path.is_dir():
|
|
85
|
+
self.update_grid()
|
|
86
|
+
else:
|
|
87
|
+
self.submit([str(self.path)])
|
|
88
|
+
|
|
89
|
+
async def _handle_ok(self):
|
|
90
|
+
try:
|
|
91
|
+
rows = await self.grid.get_selected_rows()
|
|
92
|
+
except TimeoutError:
|
|
93
|
+
rows = []
|
|
94
|
+
|
|
95
|
+
if rows:
|
|
96
|
+
self.submit([r['path'] for r in rows])
|
|
97
|
+
elif self.dirs_only:
|
|
98
|
+
self.submit([str(self.path)])
|
|
99
|
+
else:
|
|
100
|
+
ui.notify('No file selected.')
|