instaui 0.1.15__py2.py3-none-any.whl
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.
- instaui/__init__.py +9 -0
- instaui/_helper/observable_helper.py +45 -0
- instaui/arco/__init__.py +191 -0
- instaui/arco/_settings.py +25 -0
- instaui/arco/_use_tools/locale.py +50 -0
- instaui/arco/component_types.py +1019 -0
- instaui/arco/components/_utils.py +22 -0
- instaui/arco/components/affix.py +29 -0
- instaui/arco/components/alert.py +42 -0
- instaui/arco/components/anchor.py +42 -0
- instaui/arco/components/auto_complete.py +96 -0
- instaui/arco/components/avatar.py +55 -0
- instaui/arco/components/back_top.py +14 -0
- instaui/arco/components/badge.py +14 -0
- instaui/arco/components/breadcrumb.py +14 -0
- instaui/arco/components/button.py +43 -0
- instaui/arco/components/calendar.py +47 -0
- instaui/arco/components/card.py +14 -0
- instaui/arco/components/carousel.py +33 -0
- instaui/arco/components/cascader.py +111 -0
- instaui/arco/components/checkbox.py +32 -0
- instaui/arco/components/collapse.py +31 -0
- instaui/arco/components/color_picker.py +45 -0
- instaui/arco/components/comment.py +14 -0
- instaui/arco/components/config_provider.py +13 -0
- instaui/arco/components/date_picker.py +111 -0
- instaui/arco/components/descriptions.py +14 -0
- instaui/arco/components/divider.py +13 -0
- instaui/arco/components/drawer.py +98 -0
- instaui/arco/components/dropdown.py +45 -0
- instaui/arco/components/empty.py +14 -0
- instaui/arco/components/form.py +55 -0
- instaui/arco/components/icon.py +17 -0
- instaui/arco/components/image.py +33 -0
- instaui/arco/components/input.py +102 -0
- instaui/arco/components/input_number.py +97 -0
- instaui/arco/components/input_password.py +38 -0
- instaui/arco/components/input_search.py +37 -0
- instaui/arco/components/input_tag.py +110 -0
- instaui/arco/components/layout.py +13 -0
- instaui/arco/components/layout_content.py +6 -0
- instaui/arco/components/layout_footer.py +6 -0
- instaui/arco/components/layout_header.py +6 -0
- instaui/arco/components/layout_sider.py +53 -0
- instaui/arco/components/link.py +36 -0
- instaui/arco/components/list.py +68 -0
- instaui/arco/components/mention.py +97 -0
- instaui/arco/components/menu.py +88 -0
- instaui/arco/components/modal.py +97 -0
- instaui/arco/components/overflow_list.py +29 -0
- instaui/arco/components/page_header.py +29 -0
- instaui/arco/components/pagination.py +45 -0
- instaui/arco/components/pop_confirm.py +58 -0
- instaui/arco/components/popover.py +32 -0
- instaui/arco/components/progress.py +14 -0
- instaui/arco/components/radio.py +40 -0
- instaui/arco/components/radio_group.py +42 -0
- instaui/arco/components/rate.py +45 -0
- instaui/arco/components/resize_box.py +62 -0
- instaui/arco/components/result.py +14 -0
- instaui/arco/components/select.py +182 -0
- instaui/arco/components/skeleton.py +14 -0
- instaui/arco/components/slider.py +38 -0
- instaui/arco/components/space.py +14 -0
- instaui/arco/components/spin.py +14 -0
- instaui/arco/components/split.py +76 -0
- instaui/arco/components/statistic.py +14 -0
- instaui/arco/components/steps.py +32 -0
- instaui/arco/components/switch.py +57 -0
- instaui/arco/components/tab_pane.py +12 -0
- instaui/arco/components/table.py +276 -0
- instaui/arco/components/tabs.py +101 -0
- instaui/arco/components/tag.py +42 -0
- instaui/arco/components/textarea.py +84 -0
- instaui/arco/components/time_picker.py +76 -0
- instaui/arco/components/timeline.py +14 -0
- instaui/arco/components/tooltip.py +29 -0
- instaui/arco/components/transfer.py +58 -0
- instaui/arco/components/tree.py +120 -0
- instaui/arco/components/tree_select.py +86 -0
- instaui/arco/components/trigger.py +58 -0
- instaui/arco/components/typography.py +142 -0
- instaui/arco/components/upload.py +71 -0
- instaui/arco/components/verification_code.py +58 -0
- instaui/arco/components/watermark.py +14 -0
- instaui/arco/locales/__init__.py +4 -0
- instaui/arco/locales/_index.py +31 -0
- instaui/arco/locales/en_us.py +227 -0
- instaui/arco/locales/zh_cn.py +224 -0
- instaui/arco/setup.py +36 -0
- instaui/arco/static/instaui-arco.css +1 -0
- instaui/arco/static/instaui-arco.js +55771 -0
- instaui/arco/types.py +24 -0
- instaui/boot_info.py +43 -0
- instaui/common/jsonable.py +37 -0
- instaui/components/__init__.py +0 -0
- instaui/components/column.py +26 -0
- instaui/components/component.py +47 -0
- instaui/components/content.py +34 -0
- instaui/components/directive.py +55 -0
- instaui/components/element.py +573 -0
- instaui/components/grid.py +213 -0
- instaui/components/html/__init__.py +49 -0
- instaui/components/html/_mixins.py +34 -0
- instaui/components/html/_preset.py +4 -0
- instaui/components/html/button.py +38 -0
- instaui/components/html/checkbox.py +35 -0
- instaui/components/html/date.py +28 -0
- instaui/components/html/div.py +7 -0
- instaui/components/html/form.py +7 -0
- instaui/components/html/heading.py +51 -0
- instaui/components/html/input.py +28 -0
- instaui/components/html/label.py +21 -0
- instaui/components/html/li.py +17 -0
- instaui/components/html/link.py +31 -0
- instaui/components/html/number.py +34 -0
- instaui/components/html/paragraph.py +29 -0
- instaui/components/html/range.py +48 -0
- instaui/components/html/select.py +69 -0
- instaui/components/html/span.py +19 -0
- instaui/components/html/table.py +36 -0
- instaui/components/html/textarea.py +28 -0
- instaui/components/html/ul.py +20 -0
- instaui/components/label.py +5 -0
- instaui/components/markdown/markdown.js +33 -0
- instaui/components/markdown/markdown.py +41 -0
- instaui/components/markdown/static/github-markdown.css +12 -0
- instaui/components/markdown/static/marked.esm.js +2579 -0
- instaui/components/match.py +108 -0
- instaui/components/row.py +17 -0
- instaui/components/shiki_code/shiki_code.js +126 -0
- instaui/components/shiki_code/shiki_code.py +99 -0
- instaui/components/shiki_code/static/langs/css.mjs +5 -0
- instaui/components/shiki_code/static/langs/markdown.mjs +5 -0
- instaui/components/shiki_code/static/langs/python.mjs +5 -0
- instaui/components/shiki_code/static/langs/shell.mjs +2 -0
- instaui/components/shiki_code/static/langs/shellscript.mjs +5 -0
- instaui/components/shiki_code/static/shiki-core.js +5784 -0
- instaui/components/shiki_code/static/shiki-style.css +179 -0
- instaui/components/shiki_code/static/shiki-transformers.js +461 -0
- instaui/components/shiki_code/static/themes/vitesse-dark.mjs +2 -0
- instaui/components/shiki_code/static/themes/vitesse-light.mjs +2 -0
- instaui/components/slot.py +81 -0
- instaui/components/transition_group.py +9 -0
- instaui/components/value_element.py +52 -0
- instaui/components/vfor.py +142 -0
- instaui/components/vif.py +42 -0
- instaui/consts.py +23 -0
- instaui/dependencies/component_dependency.py +22 -0
- instaui/dependencies/plugin_dependency.py +28 -0
- instaui/event/event_mixin.py +12 -0
- instaui/event/js_event.py +82 -0
- instaui/event/vue_event.py +66 -0
- instaui/event/web_event.py +123 -0
- instaui/experimental/__init__.py +3 -0
- instaui/experimental/debug.py +48 -0
- instaui/extra_libs/_echarts.py +3 -0
- instaui/extra_libs/_import_error.py +9 -0
- instaui/extra_libs/_mermaid.py +3 -0
- instaui/extra_libs/_shiki_code.py +3 -0
- instaui/fastapi_server/_utils.py +42 -0
- instaui/fastapi_server/_uvicorn.py +37 -0
- instaui/fastapi_server/debug_mode_router.py +60 -0
- instaui/fastapi_server/dependency_router.py +28 -0
- instaui/fastapi_server/event_router.py +58 -0
- instaui/fastapi_server/middlewares.py +19 -0
- instaui/fastapi_server/request_context.py +19 -0
- instaui/fastapi_server/resource.py +30 -0
- instaui/fastapi_server/server.py +308 -0
- instaui/fastapi_server/watch_router.py +53 -0
- instaui/handlers/_utils.py +88 -0
- instaui/handlers/event_handler.py +60 -0
- instaui/handlers/watch_handler.py +61 -0
- instaui/html_tools.py +94 -0
- instaui/inject.py +33 -0
- instaui/js/__init__.py +4 -0
- instaui/js/js_output.py +15 -0
- instaui/js/lambda_func.py +35 -0
- instaui/launch_collector.py +52 -0
- instaui/page_info.py +13 -0
- instaui/runtime/__init__.py +29 -0
- instaui/runtime/_app.py +234 -0
- instaui/runtime/_inner_helper.py +9 -0
- instaui/runtime/_link_manager.py +89 -0
- instaui/runtime/context.py +47 -0
- instaui/runtime/dataclass.py +30 -0
- instaui/runtime/resource.py +65 -0
- instaui/runtime/scope.py +133 -0
- instaui/runtime/ui_state_scope.py +15 -0
- instaui/settings/__init__.py +4 -0
- instaui/settings/__settings.py +13 -0
- instaui/shadcn_classless/_index.py +42 -0
- instaui/shadcn_classless/static/shadcn-classless.css +403 -0
- instaui/skip.py +12 -0
- instaui/spa_router/__init__.py +26 -0
- instaui/spa_router/_components.py +35 -0
- instaui/spa_router/_file_base_utils.py +273 -0
- instaui/spa_router/_functions.py +122 -0
- instaui/spa_router/_install.py +11 -0
- instaui/spa_router/_route_model.py +117 -0
- instaui/spa_router/_router_box.py +40 -0
- instaui/spa_router/_router_output.py +22 -0
- instaui/spa_router/_router_param_var.py +51 -0
- instaui/spa_router/_types.py +4 -0
- instaui/spa_router/templates/page_routes +60 -0
- instaui/static/insta-ui.css +1 -0
- instaui/static/insta-ui.esm-browser.prod.js +3717 -0
- instaui/static/insta-ui.ico +0 -0
- instaui/static/insta-ui.js.map +1 -0
- instaui/static/instaui-tools-browser.js +511 -0
- instaui/static/templates/debug/sse.html +117 -0
- instaui/static/templates/web.html +74 -0
- instaui/static/templates/webview.html +78 -0
- instaui/static/templates/zero.html +71 -0
- instaui/static/vue.esm-browser.prod.js +9 -0
- instaui/static/vue.global.prod.js +9 -0
- instaui/static/vue.runtime.esm-browser.prod.js +5 -0
- instaui/systems/file_system.py +6 -0
- instaui/systems/func_system.py +119 -0
- instaui/systems/js_system.py +22 -0
- instaui/systems/module_system.py +46 -0
- instaui/systems/pydantic_system.py +27 -0
- instaui/systems/string_system.py +10 -0
- instaui/tailwind/__init__.py +6 -0
- instaui/tailwind/_index.py +24 -0
- instaui/tailwind/static/tailwindcss-v3.min.js +62 -0
- instaui/tailwind/static/tailwindcss-v4.min.js +8 -0
- instaui/template/__init__.py +4 -0
- instaui/template/_utils.py +23 -0
- instaui/template/env.py +7 -0
- instaui/template/web_template.py +49 -0
- instaui/template/webview_template.py +48 -0
- instaui/template/zero_template.py +105 -0
- instaui/ui/__init__.py +144 -0
- instaui/ui/__init__.pyi +149 -0
- instaui/ui/events.py +25 -0
- instaui/ui_functions/input_slient_data.py +16 -0
- instaui/ui_functions/server.py +15 -0
- instaui/ui_functions/str_format.py +36 -0
- instaui/ui_functions/ui_page.py +16 -0
- instaui/ui_functions/ui_types.py +13 -0
- instaui/ui_functions/url_location.py +33 -0
- instaui/vars/_types.py +8 -0
- instaui/vars/data.py +68 -0
- instaui/vars/element_ref.py +40 -0
- instaui/vars/event_context.py +49 -0
- instaui/vars/event_extend.py +0 -0
- instaui/vars/js_computed.py +117 -0
- instaui/vars/mixin_types/common_type.py +5 -0
- instaui/vars/mixin_types/element_binding.py +16 -0
- instaui/vars/mixin_types/observable.py +7 -0
- instaui/vars/mixin_types/pathable.py +14 -0
- instaui/vars/mixin_types/py_binding.py +13 -0
- instaui/vars/mixin_types/str_format_binding.py +8 -0
- instaui/vars/mixin_types/var_type.py +5 -0
- instaui/vars/path_var.py +90 -0
- instaui/vars/ref.py +103 -0
- instaui/vars/slot_prop.py +46 -0
- instaui/vars/state.py +97 -0
- instaui/vars/types.py +24 -0
- instaui/vars/vfor_item.py +204 -0
- instaui/vars/vue_computed.py +81 -0
- instaui/vars/web_computed.py +209 -0
- instaui/vars/web_view_computed.py +1 -0
- instaui/version.py +3 -0
- instaui/watch/_types.py +4 -0
- instaui/watch/_utils.py +3 -0
- instaui/watch/js_watch.py +110 -0
- instaui/watch/vue_watch.py +77 -0
- instaui/watch/web_watch.py +181 -0
- instaui/webview/__init__.py +2 -0
- instaui/webview/_utils.py +8 -0
- instaui/webview/api.py +72 -0
- instaui/webview/func.py +114 -0
- instaui/webview/index.py +161 -0
- instaui/webview/resource.py +172 -0
- instaui/zero/__init__.py +3 -0
- instaui/zero/func.py +123 -0
- instaui/zero/scope.py +109 -0
- instaui-0.1.15.dist-info/METADATA +152 -0
- instaui-0.1.15.dist-info/RECORD +283 -0
- instaui-0.1.15.dist-info/WHEEL +5 -0
- instaui-0.1.15.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,273 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
from collections import deque
|
3
|
+
from datetime import datetime
|
4
|
+
import importlib.util
|
5
|
+
from pathlib import Path
|
6
|
+
import typing
|
7
|
+
import jinja2
|
8
|
+
import inspect
|
9
|
+
from dataclasses import dataclass, field
|
10
|
+
|
11
|
+
|
12
|
+
def build_routes_from_files(
|
13
|
+
folder_path: typing.Union[str, Path] = "pages",
|
14
|
+
module_name: str = "_routes",
|
15
|
+
route_config_var_name: str = "_route_config",
|
16
|
+
):
|
17
|
+
global_args = _GlobalArgs(
|
18
|
+
base_folder_path=_utils.get_caller_path().parent / Path(folder_path),
|
19
|
+
module_name=module_name,
|
20
|
+
route_config_var_name=route_config_var_name,
|
21
|
+
)
|
22
|
+
|
23
|
+
root = _model_utils.create_root(global_args)
|
24
|
+
_code_gen.generate_router_file(root)
|
25
|
+
|
26
|
+
print(f"Build _routes from files in {global_args.base_folder_path}...")
|
27
|
+
|
28
|
+
|
29
|
+
@dataclass
|
30
|
+
class _GlobalArgs:
|
31
|
+
base_folder_path: Path
|
32
|
+
module_name: str
|
33
|
+
route_config_var_name: str
|
34
|
+
|
35
|
+
|
36
|
+
class _model_utils:
|
37
|
+
@dataclass
|
38
|
+
class FileRouteInfo:
|
39
|
+
file: Path
|
40
|
+
base_folder: Path
|
41
|
+
global_args: _GlobalArgs
|
42
|
+
children: typing.List[_model_utils.FileRouteInfo] = field(default_factory=list)
|
43
|
+
path: str = field(init=False, default="")
|
44
|
+
name: str = field(init=False, default="")
|
45
|
+
fn_path: typing.Optional[str] = field(init=False, default=None)
|
46
|
+
|
47
|
+
params: str = field(init=False, default="")
|
48
|
+
meta: typing.Dict = field(init=False, default_factory=dict)
|
49
|
+
|
50
|
+
def __post_init__(self):
|
51
|
+
self.params = self._extract_params()
|
52
|
+
self.meta = self._extract_meta()
|
53
|
+
self.path, self.name = self._extra_path_name()
|
54
|
+
|
55
|
+
if self.file.is_file():
|
56
|
+
self.fn_path = ".".join(
|
57
|
+
self.file.relative_to(self.base_folder).with_suffix("").parts
|
58
|
+
)
|
59
|
+
|
60
|
+
def is_index_file(self):
|
61
|
+
return self.file.is_file() and self.file.stem == "index"
|
62
|
+
|
63
|
+
def change_sync_index_info(self, index_info: _model_utils.FileRouteInfo):
|
64
|
+
self.fn_path = index_info.fn_path
|
65
|
+
self.path = self.path + index_info.params
|
66
|
+
self.meta = index_info.meta
|
67
|
+
|
68
|
+
def _extract_params(self):
|
69
|
+
if self.file.is_file():
|
70
|
+
route_config = _module_utils.get_module_getter(self.file)(
|
71
|
+
self.global_args.route_config_var_name
|
72
|
+
)
|
73
|
+
if route_config:
|
74
|
+
if "params" in route_config:
|
75
|
+
return route_config["params"]
|
76
|
+
|
77
|
+
return ""
|
78
|
+
|
79
|
+
def _extract_meta(self):
|
80
|
+
if self.file.is_file():
|
81
|
+
route_config = _module_utils.get_module_getter(self.file)(
|
82
|
+
self.global_args.route_config_var_name
|
83
|
+
)
|
84
|
+
|
85
|
+
if route_config:
|
86
|
+
if "meta" in route_config:
|
87
|
+
return route_config["meta"]
|
88
|
+
|
89
|
+
return {}
|
90
|
+
|
91
|
+
def _extra_path_name(self):
|
92
|
+
name_parts = list(
|
93
|
+
self.file.relative_to(self.base_folder).with_suffix("").parts
|
94
|
+
)
|
95
|
+
|
96
|
+
is_root = len(name_parts) == 1
|
97
|
+
|
98
|
+
path = self.file.stem
|
99
|
+
if path == "index":
|
100
|
+
path = ""
|
101
|
+
|
102
|
+
name = ".".join(name_parts)
|
103
|
+
|
104
|
+
if is_root:
|
105
|
+
path = "/" + path
|
106
|
+
|
107
|
+
if self.params:
|
108
|
+
path += self.params
|
109
|
+
|
110
|
+
return path, name
|
111
|
+
|
112
|
+
def import_code(self):
|
113
|
+
if not self.fn_path:
|
114
|
+
return ""
|
115
|
+
|
116
|
+
return f"from .{self.fn_path.replace(' ','_')} import main as {self.main_fn_name()}"
|
117
|
+
|
118
|
+
def main_fn_name(self):
|
119
|
+
if not self.fn_path:
|
120
|
+
return ""
|
121
|
+
return self.name.replace(".", "_").replace(" ", "_")
|
122
|
+
|
123
|
+
@dataclass
|
124
|
+
class FileRouteRoot:
|
125
|
+
folder: str
|
126
|
+
module_name: str
|
127
|
+
infos: list[_model_utils.FileRouteInfo] = field(default_factory=list)
|
128
|
+
|
129
|
+
@staticmethod
|
130
|
+
def create_root(global_args: _GlobalArgs) -> FileRouteRoot:
|
131
|
+
base_folder = Path(global_args.base_folder_path)
|
132
|
+
infos = _model_utils._create_route_info(base_folder, global_args)
|
133
|
+
return _model_utils.FileRouteRoot(
|
134
|
+
folder=str(base_folder), module_name=global_args.module_name, infos=infos
|
135
|
+
)
|
136
|
+
|
137
|
+
@staticmethod
|
138
|
+
def _create_route_info(
|
139
|
+
base_folder: Path, global_args: _GlobalArgs
|
140
|
+
) -> typing.List[FileRouteInfo]:
|
141
|
+
result: typing.List[_model_utils.FileRouteInfo] = []
|
142
|
+
|
143
|
+
stack: deque[
|
144
|
+
typing.Tuple[typing.Optional[_model_utils.FileRouteInfo], Path]
|
145
|
+
] = deque()
|
146
|
+
stack.extendleft((None, path) for path in base_folder.iterdir())
|
147
|
+
|
148
|
+
while stack:
|
149
|
+
parent_info, item = stack.pop()
|
150
|
+
is_dir = item.is_dir()
|
151
|
+
|
152
|
+
if item.stem.startswith("_"):
|
153
|
+
continue
|
154
|
+
|
155
|
+
if is_dir:
|
156
|
+
folder_info = _model_utils.FileRouteInfo(
|
157
|
+
file=item, base_folder=base_folder, global_args=global_args
|
158
|
+
)
|
159
|
+
infos = ((folder_info, path) for path in item.iterdir())
|
160
|
+
stack.extendleft(infos)
|
161
|
+
|
162
|
+
if parent_info is None:
|
163
|
+
result.append(folder_info)
|
164
|
+
else:
|
165
|
+
parent_info.children.append(folder_info)
|
166
|
+
continue
|
167
|
+
|
168
|
+
if item.suffix != ".py":
|
169
|
+
continue
|
170
|
+
|
171
|
+
file_info = _model_utils.FileRouteInfo(
|
172
|
+
file=item, base_folder=base_folder, global_args=global_args
|
173
|
+
)
|
174
|
+
|
175
|
+
if parent_info is None:
|
176
|
+
result.append(file_info)
|
177
|
+
else:
|
178
|
+
if file_info.is_index_file():
|
179
|
+
parent_info.change_sync_index_info(file_info)
|
180
|
+
|
181
|
+
else:
|
182
|
+
parent_info.children.append(file_info)
|
183
|
+
|
184
|
+
return result
|
185
|
+
|
186
|
+
@staticmethod
|
187
|
+
def iter_route_info(infos: typing.List[FileRouteInfo]):
|
188
|
+
stack: typing.List[_model_utils.FileRouteInfo] = []
|
189
|
+
stack.extend(infos)
|
190
|
+
|
191
|
+
while stack:
|
192
|
+
info = stack.pop()
|
193
|
+
stack.extend(info.children)
|
194
|
+
yield info
|
195
|
+
|
196
|
+
|
197
|
+
class _code_gen:
|
198
|
+
_env = jinja2.Environment(
|
199
|
+
loader=jinja2.PackageLoader("instaui.spa_router", "templates"),
|
200
|
+
)
|
201
|
+
|
202
|
+
@dataclass
|
203
|
+
class TemplateModel:
|
204
|
+
update_time: datetime = field(default_factory=datetime.now)
|
205
|
+
route_names: typing.List[str] = field(default_factory=list)
|
206
|
+
routes: typing.List[_model_utils.FileRouteInfo] = field(default_factory=list)
|
207
|
+
|
208
|
+
def get_all_main_import(self):
|
209
|
+
return [
|
210
|
+
info.import_code() for info in _model_utils.iter_route_info(self.routes)
|
211
|
+
]
|
212
|
+
|
213
|
+
@staticmethod
|
214
|
+
def generate_router_file(root: _model_utils.FileRouteRoot):
|
215
|
+
_template = _code_gen._env.get_template("page_routes")
|
216
|
+
|
217
|
+
template_model = _code_gen.TemplateModel(
|
218
|
+
route_names=_code_gen._extract_all_route_names(root), routes=root.infos
|
219
|
+
)
|
220
|
+
|
221
|
+
code = _template.render(model=template_model)
|
222
|
+
Path(root.folder).joinpath(f"{root.module_name}.py").write_text(
|
223
|
+
code, encoding="utf-8"
|
224
|
+
)
|
225
|
+
|
226
|
+
@staticmethod
|
227
|
+
def _extract_all_route_names(root: _model_utils.FileRouteRoot):
|
228
|
+
return [
|
229
|
+
info.name
|
230
|
+
for info in _model_utils.iter_route_info(root.infos)
|
231
|
+
if info.fn_path
|
232
|
+
]
|
233
|
+
|
234
|
+
|
235
|
+
class _module_utils:
|
236
|
+
@staticmethod
|
237
|
+
def get_module_getter(path: Path):
|
238
|
+
if not isinstance(path, Path):
|
239
|
+
raise ValueError("Expected a Path object")
|
240
|
+
|
241
|
+
if not path.exists():
|
242
|
+
raise FileNotFoundError(f"The file {path} does not exist.")
|
243
|
+
|
244
|
+
module_name = path.stem
|
245
|
+
module_path = str(path.absolute())
|
246
|
+
|
247
|
+
spec = importlib.util.spec_from_file_location(module_name, module_path)
|
248
|
+
if spec is None:
|
249
|
+
raise ImportError(f"Cannot create a module spec for {module_path}")
|
250
|
+
|
251
|
+
module = importlib.util.module_from_spec(spec)
|
252
|
+
|
253
|
+
try:
|
254
|
+
spec.loader.exec_module(module) # type: ignore
|
255
|
+
except Exception as e:
|
256
|
+
raise ImportError(f"Failed to import {module_path}: {e}")
|
257
|
+
|
258
|
+
def getter_fn(var_name: str):
|
259
|
+
return getattr(module, var_name, None)
|
260
|
+
|
261
|
+
return getter_fn
|
262
|
+
|
263
|
+
|
264
|
+
class _utils:
|
265
|
+
@staticmethod
|
266
|
+
def get_caller_path():
|
267
|
+
current_frame = inspect.currentframe()
|
268
|
+
try:
|
269
|
+
caller_frame = current_frame.f_back.f_back # type: ignore
|
270
|
+
filename = caller_frame.f_code.co_filename # type: ignore
|
271
|
+
return Path(filename)
|
272
|
+
finally:
|
273
|
+
del current_frame
|
@@ -0,0 +1,122 @@
|
|
1
|
+
import typing
|
2
|
+
from . import _types
|
3
|
+
from instaui.runtime._app import get_app_slot
|
4
|
+
from . import _install
|
5
|
+
from ._router_param_var import RouterParamsVar
|
6
|
+
from ._router_output import RouterOutput, RouterMethod
|
7
|
+
from ._route_model import RouteItem
|
8
|
+
|
9
|
+
_ASSERT_MSG = "Router is not initialized."
|
10
|
+
|
11
|
+
|
12
|
+
def add_route(
|
13
|
+
page_fn: typing.Callable,
|
14
|
+
*,
|
15
|
+
name: typing.Optional[str] = None,
|
16
|
+
path: typing.Optional[str] = None,
|
17
|
+
children: typing.Optional[typing.List[RouteItem]] = None,
|
18
|
+
lazy_loading: bool = False,
|
19
|
+
):
|
20
|
+
_install.try_register_router_collector()
|
21
|
+
route_collector = get_app_slot()._route_collector
|
22
|
+
assert route_collector is not None, _ASSERT_MSG
|
23
|
+
route_collector.add_route(
|
24
|
+
RouteItem.create(
|
25
|
+
path=path,
|
26
|
+
component_fn=page_fn,
|
27
|
+
name=name,
|
28
|
+
children=children,
|
29
|
+
)
|
30
|
+
)
|
31
|
+
|
32
|
+
|
33
|
+
def config_router(
|
34
|
+
routes: typing.Optional[typing.List[RouteItem]] = None,
|
35
|
+
*,
|
36
|
+
history: _types.TRouterHistoryMode = "hash",
|
37
|
+
keep_alive: bool = False,
|
38
|
+
):
|
39
|
+
"""Configure the router.
|
40
|
+
|
41
|
+
Examples:
|
42
|
+
.. code-block:: python
|
43
|
+
routes = [
|
44
|
+
spa_router.RouteItem.create(path='/',component_fn=home),
|
45
|
+
spa_router.RouteItem.create(path='/user',component_fn=user_home),
|
46
|
+
]
|
47
|
+
|
48
|
+
spa_router.config_router(routes=routes)
|
49
|
+
|
50
|
+
Args:
|
51
|
+
routes (typing.Optional[typing.List[RouteItem]], optional): list of routes to be added to the router. Defaults to None.
|
52
|
+
history (_types.TRouterHistoryMode, optional): router history mode. Can be "web", "memory" or "hash". Defaults to "hash".
|
53
|
+
keep_alive (bool, optional): whether to keep the components alive when navigating to a new route.Defaults to False.
|
54
|
+
"""
|
55
|
+
|
56
|
+
_install.try_register_router_collector()
|
57
|
+
|
58
|
+
route_collector = get_app_slot()._route_collector
|
59
|
+
assert route_collector is not None, _ASSERT_MSG
|
60
|
+
|
61
|
+
route_collector.mode = history
|
62
|
+
route_collector.keep_alive = keep_alive
|
63
|
+
route_collector.routes = routes or []
|
64
|
+
|
65
|
+
|
66
|
+
def get_params(param_name: str) -> typing.Any:
|
67
|
+
return RouterParamsVar("params")[param_name]
|
68
|
+
|
69
|
+
|
70
|
+
def get_path():
|
71
|
+
return RouterParamsVar("path")
|
72
|
+
|
73
|
+
|
74
|
+
def get_full_path():
|
75
|
+
return RouterParamsVar("fullPath")
|
76
|
+
|
77
|
+
|
78
|
+
def push(
|
79
|
+
*,
|
80
|
+
path: typing.Optional[str] = None,
|
81
|
+
name: typing.Optional[str] = None,
|
82
|
+
params: typing.Optional[typing.Dict[str, typing.Any]] = None,
|
83
|
+
query: typing.Optional[typing.Dict[str, typing.Any]] = None,
|
84
|
+
hash: typing.Optional[str] = None,
|
85
|
+
):
|
86
|
+
method_params: typing.Dict = {}
|
87
|
+
|
88
|
+
if path is not None:
|
89
|
+
method_params["path"] = path
|
90
|
+
if name is not None:
|
91
|
+
method_params["name"] = name
|
92
|
+
|
93
|
+
if params is not None:
|
94
|
+
method_params["params"] = params
|
95
|
+
if query is not None:
|
96
|
+
method_params["query"] = query
|
97
|
+
if hash is not None:
|
98
|
+
method_params["hash"] = hash
|
99
|
+
|
100
|
+
return RouterMethod(
|
101
|
+
fn="push",
|
102
|
+
args=[method_params],
|
103
|
+
)
|
104
|
+
|
105
|
+
|
106
|
+
def go(n: int):
|
107
|
+
return RouterMethod(
|
108
|
+
fn="go",
|
109
|
+
args=[n],
|
110
|
+
)
|
111
|
+
|
112
|
+
|
113
|
+
def forward():
|
114
|
+
return go(1)
|
115
|
+
|
116
|
+
|
117
|
+
def back():
|
118
|
+
return go(-1)
|
119
|
+
|
120
|
+
|
121
|
+
def output():
|
122
|
+
return RouterOutput()
|
@@ -0,0 +1,11 @@
|
|
1
|
+
from instaui.runtime._app import get_app_slot
|
2
|
+
from instaui.spa_router._route_model import RouteCollector
|
3
|
+
|
4
|
+
|
5
|
+
def try_register_router_collector():
|
6
|
+
app = get_app_slot()
|
7
|
+
if app._route_collector is not None:
|
8
|
+
return
|
9
|
+
|
10
|
+
rb = RouteCollector()
|
11
|
+
app.register_router(rb)
|
@@ -0,0 +1,117 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
import typing
|
3
|
+
from pydantic import BaseModel, Field, ConfigDict, field_serializer, model_serializer
|
4
|
+
|
5
|
+
from instaui.components.html.div import Div
|
6
|
+
from instaui.runtime._app import get_app_slot, new_scope
|
7
|
+
from instaui.components.component import Component
|
8
|
+
from instaui.runtime.scope import Scope
|
9
|
+
from instaui.common.jsonable import dumps2dict
|
10
|
+
|
11
|
+
from . import _types
|
12
|
+
|
13
|
+
|
14
|
+
class RouteItem(BaseModel):
|
15
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
16
|
+
|
17
|
+
component_fn: typing.Optional[typing.Callable] = Field(exclude=True)
|
18
|
+
scope: typing.Optional[Scope] = None
|
19
|
+
vue_route_item: VueRouteItem = Field(serialization_alias="vueItem")
|
20
|
+
meta: typing.Optional[typing.Dict] = None
|
21
|
+
|
22
|
+
@model_serializer
|
23
|
+
def model_ser(self):
|
24
|
+
if self.component_fn is None and (not self.vue_route_item.path):
|
25
|
+
raise ValueError("Either component_fn or vue_route_item.path must be set")
|
26
|
+
|
27
|
+
if self.component_fn is None:
|
28
|
+
return {
|
29
|
+
"vueItem": self.vue_route_item,
|
30
|
+
}
|
31
|
+
|
32
|
+
if self.vue_route_item.path is None:
|
33
|
+
self.vue_route_item.path = f"/{'' if self.component_fn.__name__=='index' else self.component_fn.__name__}"
|
34
|
+
|
35
|
+
app = get_app_slot()
|
36
|
+
with new_scope(append_to_app=False) as scope:
|
37
|
+
with Div() as div:
|
38
|
+
self.component_fn()
|
39
|
+
|
40
|
+
app.items.pop()
|
41
|
+
|
42
|
+
self.scope = scope
|
43
|
+
self.vue_route_item.component = div._slot_manager.default._children
|
44
|
+
|
45
|
+
return {
|
46
|
+
"scope": dumps2dict(scope),
|
47
|
+
"vueItem": self.vue_route_item,
|
48
|
+
}
|
49
|
+
|
50
|
+
@classmethod
|
51
|
+
def create(
|
52
|
+
cls,
|
53
|
+
*,
|
54
|
+
component_fn: typing.Optional[typing.Callable] = None,
|
55
|
+
path: typing.Optional[str] = None,
|
56
|
+
name: typing.Optional[str] = None,
|
57
|
+
params: typing.Optional[typing.Dict[str, str]] = None,
|
58
|
+
children: typing.Optional[typing.List[RouteItem]] = None,
|
59
|
+
meta: typing.Optional[typing.Dict] = None,
|
60
|
+
):
|
61
|
+
"""Create a new RouteItem
|
62
|
+
|
63
|
+
Examples:
|
64
|
+
.. code-block:: python
|
65
|
+
routes = [
|
66
|
+
spa_router.RouteItem.create(path='/',component_fn=home),
|
67
|
+
spa_router.RouteItem.create(path='/user',component_fn=user_home),
|
68
|
+
]
|
69
|
+
|
70
|
+
spa_router.config_router(routes=routes)
|
71
|
+
|
72
|
+
Args:
|
73
|
+
component_fn (typing.Callable): function that returns a component to be rendered.
|
74
|
+
path (typing.Optional[str], optional): route path. Defaults to None.
|
75
|
+
name (typing.Optional[str], optional): route name. Defaults to None.
|
76
|
+
params (typing.Optional[typing.Dict[str, str]], optional): route params. Defaults to None.
|
77
|
+
children (typing.Optional[typing.List[RouteItem]], optional): child routes. Defaults to None.
|
78
|
+
|
79
|
+
"""
|
80
|
+
|
81
|
+
return cls(
|
82
|
+
component_fn=component_fn,
|
83
|
+
meta=meta,
|
84
|
+
vue_route_item=VueRouteItem(
|
85
|
+
path=path,
|
86
|
+
name=name,
|
87
|
+
params=params,
|
88
|
+
component=[],
|
89
|
+
children=children,
|
90
|
+
),
|
91
|
+
)
|
92
|
+
|
93
|
+
|
94
|
+
class VueRouteItem(BaseModel):
|
95
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
96
|
+
|
97
|
+
path: typing.Optional[str] = None
|
98
|
+
params: typing.Optional[typing.Dict[str, str]] = None
|
99
|
+
name: typing.Optional[str] = None
|
100
|
+
component: typing.Optional[typing.List[Component]] = []
|
101
|
+
children: typing.Optional[typing.List[RouteItem]] = None
|
102
|
+
|
103
|
+
@field_serializer("component")
|
104
|
+
def serialize_component(self, value: typing.List[Component]):
|
105
|
+
if not value:
|
106
|
+
return []
|
107
|
+
return dumps2dict(value)
|
108
|
+
|
109
|
+
|
110
|
+
class RouteCollector(BaseModel):
|
111
|
+
mode: _types.TRouterHistoryMode = "hash"
|
112
|
+
keep_alive: bool = Field(default=False, serialization_alias="kAlive")
|
113
|
+
routes: typing.List[RouteItem] = []
|
114
|
+
|
115
|
+
def add_route(self, item: RouteItem):
|
116
|
+
self.routes.append(item)
|
117
|
+
return self
|
@@ -0,0 +1,40 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
from typing import Dict, List
|
3
|
+
from instaui.common.jsonable import Jsonable
|
4
|
+
from instaui.components.component import Component
|
5
|
+
from instaui.runtime.scope import Scope
|
6
|
+
|
7
|
+
from . import _types
|
8
|
+
from ._route_model import RouteCollector
|
9
|
+
|
10
|
+
|
11
|
+
class RouterBox_(Jsonable):
|
12
|
+
def __init__(self):
|
13
|
+
self._mode: _types.TRouterHistoryMode = "web"
|
14
|
+
self._keep_alive: bool = False
|
15
|
+
self._component_map: Dict[str, Dict] = {}
|
16
|
+
self._route_collector: RouteCollector = RouteCollector()
|
17
|
+
|
18
|
+
def add_component(
|
19
|
+
self,
|
20
|
+
path: str,
|
21
|
+
components: List[Component],
|
22
|
+
scope: Scope,
|
23
|
+
lazy_loading: bool = False,
|
24
|
+
):
|
25
|
+
config = {"items": components, "scope": scope}
|
26
|
+
if lazy_loading:
|
27
|
+
config["lazy"] = True
|
28
|
+
|
29
|
+
self._component_map[path] = config
|
30
|
+
|
31
|
+
def _to_json_dict(self):
|
32
|
+
data = {}
|
33
|
+
|
34
|
+
data["routes"] = self._route_collector.model_dump(exclude_none=True)["routes"]
|
35
|
+
data["mode"] = self._mode
|
36
|
+
|
37
|
+
if self._keep_alive is not False:
|
38
|
+
data["kAlive"] = self._keep_alive
|
39
|
+
|
40
|
+
return data
|
@@ -0,0 +1,22 @@
|
|
1
|
+
from typing import List, Optional
|
2
|
+
from instaui.common.jsonable import Jsonable
|
3
|
+
from instaui.vars.mixin_types.py_binding import CanOutputMixin
|
4
|
+
from pydantic import BaseModel
|
5
|
+
|
6
|
+
|
7
|
+
class RouterOutput(Jsonable, CanOutputMixin):
|
8
|
+
def __init__(self):
|
9
|
+
self.type = "routeAct"
|
10
|
+
|
11
|
+
def _to_output_config(self):
|
12
|
+
return self._to_json_dict()
|
13
|
+
|
14
|
+
def _to_json_dict(self):
|
15
|
+
data = super()._to_json_dict()
|
16
|
+
|
17
|
+
return data
|
18
|
+
|
19
|
+
|
20
|
+
class RouterMethod(BaseModel):
|
21
|
+
fn: str
|
22
|
+
args: Optional[List]
|
@@ -0,0 +1,51 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
import typing
|
3
|
+
|
4
|
+
from instaui.common.jsonable import Jsonable
|
5
|
+
from instaui.vars.path_var import PathVar
|
6
|
+
|
7
|
+
from instaui.vars.mixin_types.element_binding import ElementBindingMixin
|
8
|
+
from instaui.vars.mixin_types.observable import ObservableMixin
|
9
|
+
from instaui.vars.mixin_types.str_format_binding import StrFormatBindingMixin
|
10
|
+
from instaui.vars.mixin_types.pathable import CanPathPropMixin
|
11
|
+
|
12
|
+
|
13
|
+
class RouterParamsVar(
|
14
|
+
Jsonable,
|
15
|
+
PathVar,
|
16
|
+
ObservableMixin,
|
17
|
+
CanPathPropMixin,
|
18
|
+
StrFormatBindingMixin,
|
19
|
+
ElementBindingMixin,
|
20
|
+
):
|
21
|
+
VAR_TYPE = "routePar"
|
22
|
+
|
23
|
+
def __init__(
|
24
|
+
self,
|
25
|
+
prop: typing.Literal["params", "path", "fullPath"] = "params",
|
26
|
+
) -> None:
|
27
|
+
super().__init__()
|
28
|
+
self._prop = prop
|
29
|
+
|
30
|
+
def __to_binding_config(self):
|
31
|
+
return self._to_json_dict()
|
32
|
+
|
33
|
+
def _to_pathable_binding_config(self) -> typing.Dict:
|
34
|
+
return self.__to_binding_config()
|
35
|
+
|
36
|
+
def _to_path_prop_binding_config(self) -> typing.Dict:
|
37
|
+
return self.__to_binding_config()
|
38
|
+
|
39
|
+
def _to_observable_config(self):
|
40
|
+
return self.__to_binding_config()
|
41
|
+
|
42
|
+
def _to_element_binding_config(self):
|
43
|
+
return self.__to_binding_config()
|
44
|
+
|
45
|
+
def _to_json_dict(self):
|
46
|
+
data = super()._to_json_dict()
|
47
|
+
data["type"] = self.VAR_TYPE
|
48
|
+
|
49
|
+
if self._prop != "params":
|
50
|
+
data["prop"] = self._prop
|
51
|
+
return data
|
@@ -0,0 +1,60 @@
|
|
1
|
+
{% macro render_fn_arg(main_fn_name) -%}
|
2
|
+
{% if main_fn_name -%}
|
3
|
+
component_fn={{main_fn_name}},
|
4
|
+
{% endif -%}
|
5
|
+
{% endmacro -%}
|
6
|
+
|
7
|
+
{% macro render_children_arg(children) -%}
|
8
|
+
{% if children -%}
|
9
|
+
children=[
|
10
|
+
{{render_routes(children)}}],
|
11
|
+
{% endif -%}
|
12
|
+
{% endmacro -%}
|
13
|
+
|
14
|
+
|
15
|
+
{% macro render_routes(routes) -%}
|
16
|
+
|
17
|
+
{% if not routes -%}
|
18
|
+
|
19
|
+
{% else -%}
|
20
|
+
{% for route in routes -%}
|
21
|
+
router.RouteItem.create(
|
22
|
+
path="{{route.path}}",
|
23
|
+
name="{{route.name}}",
|
24
|
+
meta = {{route.meta}},
|
25
|
+
{{ render_fn_arg(route.main_fn_name()) |trim}}
|
26
|
+
{{ render_children_arg(route.children) |trim}}
|
27
|
+
),
|
28
|
+
{% endfor -%}
|
29
|
+
|
30
|
+
{% endif -%}
|
31
|
+
|
32
|
+
{% endmacro -%}
|
33
|
+
|
34
|
+
"""
|
35
|
+
This file is automatically generated by InstaUI. Do not modify it manually.
|
36
|
+
|
37
|
+
update: {{model.update_time}}
|
38
|
+
"""
|
39
|
+
|
40
|
+
import typing
|
41
|
+
from instaui import spa_router as router
|
42
|
+
|
43
|
+
TNames = typing.Literal{{model.route_names}}
|
44
|
+
|
45
|
+
def link(text: str, name: TNames, params: typing.Optional[typing.Dict] = None):
|
46
|
+
return router.link.by_name(text, name=name, params=params)
|
47
|
+
|
48
|
+
def get_routes():
|
49
|
+
try:
|
50
|
+
{% for code in model.get_all_main_import() -%}
|
51
|
+
{{code}}
|
52
|
+
{% endfor -%}
|
53
|
+
|
54
|
+
return [
|
55
|
+
{{render_routes(model.routes)}}
|
56
|
+
]
|
57
|
+
except Exception as e:
|
58
|
+
print(f"router error: {e}")
|
59
|
+
|
60
|
+
|