instaui 0.1.3__py3-none-any.whl → 0.1.5__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.
Files changed (93) hide show
  1. instaui/components/content.py +4 -4
  2. instaui/components/echarts/echarts.js +128 -0
  3. instaui/components/echarts/echarts.py +194 -0
  4. instaui/components/echarts/static/echarts.esm.min.js +45 -0
  5. instaui/components/element.py +103 -13
  6. instaui/components/html/__init__.py +31 -18
  7. instaui/components/html/_preset.py +4 -0
  8. instaui/components/html/heading.py +51 -0
  9. instaui/components/html/range.py +3 -0
  10. instaui/components/html/select.py +16 -35
  11. instaui/components/html/table.py +36 -0
  12. instaui/components/html/textarea.py +28 -0
  13. instaui/components/markdown/markdown.js +33 -0
  14. instaui/components/markdown/markdown.py +41 -0
  15. instaui/components/markdown/static/github-markdown.css +12 -0
  16. instaui/components/markdown/static/marked.esm.js +2579 -0
  17. instaui/components/shiki_code/shiki_code.js +126 -0
  18. instaui/components/shiki_code/shiki_code.py +99 -0
  19. instaui/components/shiki_code/static/langs/css.mjs +5 -0
  20. instaui/components/shiki_code/static/langs/markdown.mjs +5 -0
  21. instaui/components/shiki_code/static/langs/python.mjs +5 -0
  22. instaui/components/shiki_code/static/langs/shell.mjs +2 -0
  23. instaui/components/shiki_code/static/langs/shellscript.mjs +5 -0
  24. instaui/components/shiki_code/static/shiki-core.js +5784 -0
  25. instaui/components/shiki_code/static/shiki-style.css +175 -0
  26. instaui/components/shiki_code/static/shiki-transformers.js +461 -0
  27. instaui/components/shiki_code/static/themes/vitesse-dark.mjs +2 -0
  28. instaui/components/shiki_code/static/themes/vitesse-light.mjs +2 -0
  29. instaui/components/value_element.py +7 -3
  30. instaui/components/vfor.py +1 -1
  31. instaui/consts.py +2 -1
  32. instaui/daisyui/__init__.py +26 -0
  33. instaui/daisyui/_index.py +20 -0
  34. instaui/daisyui/button.py +38 -0
  35. instaui/daisyui/checkbox.py +17 -0
  36. instaui/daisyui/static/daisyui.css +1 -0
  37. instaui/daisyui/static/themes.css +1 -0
  38. instaui/daisyui/table.py +35 -0
  39. instaui/dependencies/component_dependency.py +11 -5
  40. instaui/event/js_event.py +1 -0
  41. instaui/event/web_event.py +6 -7
  42. instaui/fastapi_server/dependency_router.py +4 -3
  43. instaui/fastapi_server/resource.py +12 -16
  44. instaui/fastapi_server/server.py +34 -24
  45. instaui/handlers/event_handler.py +3 -1
  46. instaui/handlers/watch_handler.py +4 -0
  47. instaui/html_tools.py +44 -2
  48. instaui/inject.py +3 -3
  49. instaui/runtime/_app.py +43 -4
  50. instaui/runtime/_link_manager.py +89 -0
  51. instaui/runtime/resource.py +21 -8
  52. instaui/shadcn_classless/_index.py +42 -0
  53. instaui/shadcn_classless/static/shadcn-classless.css +403 -0
  54. instaui/spa_router/_functions.py +1 -1
  55. instaui/spa_router/_route_model.py +1 -1
  56. instaui/static/insta-ui.css +1 -1
  57. instaui/static/insta-ui.esm-browser.prod.js +1308 -1252
  58. instaui/static/insta-ui.js.map +1 -1
  59. instaui/static/instaui-tools-browser.js +511 -0
  60. instaui/static/templates/webview.html +78 -0
  61. instaui/systems/module_system.py +30 -0
  62. instaui/tailwind/__init__.py +6 -0
  63. instaui/tailwind/_index.py +24 -0
  64. instaui/{static/tailwindcss.min.js → tailwind/static/tailwindcss-v3.min.js} +62 -62
  65. instaui/tailwind/static/tailwindcss-v4.min.js +8 -0
  66. instaui/template/_utils.py +23 -0
  67. instaui/template/webview_template.py +50 -0
  68. instaui/template/zero_template.py +18 -17
  69. instaui/ui/__build_init.py +73 -0
  70. instaui/ui/__init__.py +74 -58
  71. instaui/ui/__init__.pyi +135 -0
  72. instaui/ui/events.py +1 -1
  73. instaui/ui_functions/server.py +3 -1
  74. instaui/vars/event_context.py +4 -0
  75. instaui/vars/web_computed.py +30 -30
  76. instaui/watch/web_watch.py +5 -6
  77. instaui/webview/__init__.py +1 -0
  78. instaui/webview/_utils.py +8 -0
  79. instaui/webview/api.py +72 -0
  80. instaui/webview/func.py +114 -0
  81. instaui/webview/index.py +162 -0
  82. instaui/webview/resource.py +172 -0
  83. instaui/zero/func.py +31 -23
  84. instaui/zero/scope.py +110 -4
  85. {instaui-0.1.3.dist-info → instaui-0.1.5.dist-info}/METADATA +4 -1
  86. {instaui-0.1.3.dist-info → instaui-0.1.5.dist-info}/RECORD +88 -44
  87. instaui/handlers/computed_handler.py +0 -42
  88. instaui/handlers/config_handler.py +0 -13
  89. instaui/static/insta-ui.iife.js +0 -29
  90. instaui/static/insta-ui.iife.js.map +0 -1
  91. instaui/zero/test.html +0 -44
  92. {instaui-0.1.3.dist-info → instaui-0.1.5.dist-info}/LICENSE +0 -0
  93. {instaui-0.1.3.dist-info → instaui-0.1.5.dist-info}/WHEEL +0 -0
instaui/webview/api.py ADDED
@@ -0,0 +1,72 @@
1
+ from typing import Any, Dict
2
+
3
+
4
+ from instaui.runtime._app import get_app_slot
5
+ from instaui.handlers import watch_handler
6
+ from instaui.handlers import event_handler
7
+ from instaui.skip import is_skip_output
8
+
9
+
10
+ class Api:
11
+ def watch_call(self, data: Dict):
12
+ hkey = data.pop("key")
13
+ handler_info = watch_handler.get_handler_info(hkey)
14
+ if handler_info is None:
15
+ return {"error": "watch handler not found"}
16
+
17
+ update_app_page_info(data)
18
+
19
+ result = handler_info.fn(
20
+ *handler_info.get_handler_args(_get_binds_from_data(data))
21
+ )
22
+ return response_data(handler_info.outputs_binding_count, result)
23
+
24
+ def event_call(self, data: Dict):
25
+ handler = event_handler.get_handler(data["hKey"])
26
+ if handler is None:
27
+ raise ValueError("event handler not found")
28
+
29
+ update_app_page_info(data)
30
+
31
+
32
+ args = [bind for bind in data.get("bind", [])]
33
+
34
+ result = handler.fn(*handler.get_handler_args(args))
35
+ return response_data(handler.outputs_binding_count, result)
36
+
37
+
38
+ def update_app_page_info(data: Dict):
39
+ app = get_app_slot()
40
+
41
+ page_info = data.get("page", {})
42
+ app._page_path = page_info["path"]
43
+
44
+ if "params" in page_info:
45
+ app._page_params = page_info["params"]
46
+
47
+ if "queryParams" in page_info:
48
+ app._query_params = page_info["queryParams"]
49
+
50
+
51
+ def _get_binds_from_data(data: Dict):
52
+ return data.get("input", [])
53
+
54
+
55
+ def response_data(outputs_binding_count: int, result: Any):
56
+ data = {}
57
+ if outputs_binding_count > 0:
58
+ if not isinstance(result, tuple):
59
+ result = [result]
60
+
61
+ result_infos = [(r, int(is_skip_output(r))) for r in result]
62
+
63
+ if len(result_infos) == 1 and result_infos[0][1] == 1:
64
+ return data
65
+
66
+ data["values"] = [0 if info[1] == 1 else info[0] for info in result_infos]
67
+ skips = [info[1] for info in result_infos]
68
+
69
+ if sum(skips) > 0:
70
+ data["skips"] = skips
71
+
72
+ return data
@@ -0,0 +1,114 @@
1
+ from __future__ import annotations
2
+ import itertools
3
+ from pathlib import Path
4
+ import instaui.consts as consts
5
+ from instaui.runtime._app import get_app_slot, get_default_app_slot
6
+ from instaui.template import render_zero_html
7
+ from instaui.template import zero_template
8
+ from instaui.html_tools import to_config_data
9
+ from instaui.runtime.dataclass import JsLink
10
+ from instaui.runtime.resource import HtmlResource
11
+
12
+
13
+ def get_template_model():
14
+ system_slot = get_app_slot()
15
+
16
+ merge_global_resources = (system_slot.meta or {}).get(
17
+ "merge_global_resources", True
18
+ )
19
+
20
+ default_app_slot = get_default_app_slot()
21
+ html_resource = system_slot._html_resource
22
+ default_html_resource = (
23
+ default_app_slot._html_resource
24
+ if merge_global_resources
25
+ else _empty_html_resource()
26
+ )
27
+
28
+ config_data = to_config_data()
29
+
30
+ model = zero_template.ZeroTemplateModel(
31
+ vue_js_code=consts.VUE_ES_JS_PATH,
32
+ instaui_js_code=consts.APP_ES_JS_PATH,
33
+ css_links=[
34
+ consts.APP_CSS_PATH,
35
+ ],
36
+ config_dict=config_data,
37
+ favicon=html_resource.favicon
38
+ or default_html_resource.favicon
39
+ or consts.FAVICON_PATH,
40
+ title=html_resource.title or default_html_resource.title or consts.PAGE_TITLE,
41
+ )
42
+
43
+ # register custom components
44
+ for component in system_slot._component_dependencies:
45
+ if not component.esm:
46
+ continue
47
+
48
+ model.vue_app_component.append(
49
+ zero_template.ZeroVueAppComponent(
50
+ name=component.tag_name,
51
+ url=component.esm,
52
+ )
53
+ )
54
+
55
+ if component.css:
56
+ for css_link in component.css:
57
+ model.css_links.append(css_link)
58
+
59
+ if component.externals:
60
+ for name, url in component.externals.items():
61
+ if url.is_file():
62
+ model.add_extra_import_map(name, url)
63
+
64
+ # register custom plugins
65
+ for plugin in set(
66
+ itertools.chain(
67
+ system_slot._plugin_dependencies, default_app_slot._plugin_dependencies
68
+ )
69
+ ):
70
+ if not plugin.esm:
71
+ continue
72
+
73
+ model.vue_app_use.append(plugin.name)
74
+
75
+ model.add_extra_import_map(plugin.name, plugin.esm)
76
+
77
+ if plugin.css:
78
+ for css_link in plugin.css:
79
+ model.css_links.append(css_link)
80
+
81
+ # css file link to web static link
82
+ for link in html_resource.get_valid_css_links(
83
+ default_html_resource._css_links_manager
84
+ ):
85
+ if isinstance(link, Path):
86
+ model.css_links.append(link)
87
+
88
+ # js file link to web static link
89
+ for info in html_resource.get_valid_js_links(
90
+ default_html_resource._js_links_manager
91
+ ):
92
+ if isinstance(info.link, Path):
93
+ model.js_links.append(JsLink(info.link))
94
+
95
+ for js_code in itertools.chain(
96
+ html_resource._script_tags, default_html_resource._script_tags
97
+ ):
98
+ model.script_tags.append(js_code)
99
+
100
+ for sylte_code in itertools.chain(
101
+ html_resource._style_tags, default_html_resource._style_tags
102
+ ):
103
+ model.style_tags.append(sylte_code)
104
+
105
+ return model
106
+
107
+
108
+ def to_html_str():
109
+ model = get_template_model()
110
+ return render_zero_html(model)
111
+
112
+
113
+ def _empty_html_resource():
114
+ return HtmlResource()
@@ -0,0 +1,162 @@
1
+ from __future__ import annotations
2
+ from contextlib import contextmanager
3
+ from pathlib import Path
4
+ import shutil
5
+ from typing import Any, Callable, Dict, Iterable, List, Optional, Union
6
+ from typing_extensions import TypedDict, Unpack
7
+ import webview
8
+ import webview.http as http
9
+ from webview.guilib import GUIType
10
+
11
+ from instaui.runtime._app import get_app_slot, new_app_slot, reset_app_slot
12
+ from instaui.launch_collector import get_launch_collector
13
+
14
+ from . import resource
15
+ from . import api
16
+ from . import _utils
17
+
18
+
19
+ class WebviewWrapper:
20
+ """Example usage:
21
+ .. code-block:: python
22
+ from instaui import ui
23
+
24
+ @ui.page("/")
25
+ def index_page():
26
+ ui.content("Hello, world!")
27
+
28
+ ui.webview().run()
29
+ """
30
+
31
+ def __init__(
32
+ self,
33
+ *,
34
+ assets_path: Union[str, Path] = "./webview_assets",
35
+ debug: bool = False,
36
+ auto_create_window: Union[bool, str] = "/",
37
+ on_app_mounted: Optional[Callable] = None,
38
+ ) -> None:
39
+ """Create a new webview wrapper.
40
+
41
+ Args:
42
+ assets_path (Union[str, Path], optional): Path to store assets. Defaults to "./webview_assets".
43
+ debug (bool, optional): Whether to run in debug mode. Defaults to False.
44
+ auto_create_window (Union[bool, str], optional): Whether to create a window automatically. If a string is provided, it will be used as the initial page URL. Defaults to "/".
45
+ testing (bool, optional): Whether to run in testing mode. Defaults to False.
46
+ """
47
+
48
+ self.assets_path = (
49
+ Path(self._get_assets_path(assets_path))
50
+ if isinstance(assets_path, str)
51
+ else assets_path
52
+ )
53
+ _utils.reset_dir(self.assets_path)
54
+ self.debug = debug
55
+ self.on_app_mounted = on_app_mounted
56
+
57
+ self._auto_create_window = auto_create_window
58
+
59
+ def create_window(
60
+ self,
61
+ page_url: str = "/",
62
+ ):
63
+ """Create a new window. Returns the window object of pywebview.
64
+
65
+ Args:
66
+ page_url (str, optional): Page URL to load. Defaults to "/".
67
+
68
+ """
69
+ launch_collector = get_launch_collector()
70
+ with _scope():
71
+ app = get_app_slot()
72
+ app._page_path = page_url
73
+ page_info = launch_collector._page_router[page_url]
74
+ page_info.func()
75
+
76
+ resource_info = resource.resource_to_assets(
77
+ page_url=page_url,
78
+ assets_path=self.assets_path,
79
+ on_app_mounted=self.on_app_mounted,
80
+ )
81
+
82
+ window = webview.create_window(
83
+ resource_info.title, resource_info.index_html_url, js_api=api.Api()
84
+ )
85
+
86
+ if self.on_app_mounted:
87
+
88
+ def on_app_mounted():
89
+ self.on_app_mounted(window) # type: ignore
90
+
91
+ window.expose(on_app_mounted)
92
+
93
+ return window
94
+
95
+ def run(self, **webview_start_args: Unpack[WebviewStartArgs]):
96
+ """Run the webview.
97
+
98
+ Args:
99
+ :param func: Function to invoke upon starting the GUI loop.
100
+ :param args: Function arguments. Can be either a single value or a tuple of
101
+ values.
102
+ :param localization: A dictionary with localized strings. Default strings
103
+ and their keys are defined in localization.py.
104
+ :param gui: Force a specific GUI. Allowed values are ``cef``, ``qt``,
105
+ ``gtk``, ``mshtml`` or ``edgechromium`` depending on a platform.
106
+ :param http_server: Enable built-in HTTP server. If enabled, local files
107
+ will be served using a local HTTP server on a random port. For each
108
+ window, a separate HTTP server is spawned. This option is ignored for
109
+ non-local URLs.
110
+ :param user_agent: Change user agent string.
111
+ :param private_mode: Enable private mode. In private mode, cookies and local storage are not preserved.
112
+ Default is True.
113
+ :param storage_path: Custom location for cookies and other website data
114
+ :param menu: List of menus to be included in the app menu
115
+ :param server: Server class. Defaults to BottleServer
116
+ :param server_args: Dictionary of arguments to pass through to the server instantiation
117
+ :param ssl: Enable SSL for local HTTP server. Default is False.
118
+ :param icon: Path to the icon file. Supported only on GTK/QT.
119
+ """
120
+
121
+ if self._auto_create_window:
122
+ self.create_window(
123
+ "/" if self._auto_create_window is True else self._auto_create_window
124
+ )
125
+
126
+ webview.start(**webview_start_args, debug=self.debug)
127
+
128
+ @staticmethod
129
+ def _get_assets_path(file: Union[str, Path]) -> Path:
130
+ if isinstance(file, str):
131
+ import inspect
132
+
133
+ frame = inspect.currentframe().f_back.f_back.f_back # type: ignore
134
+ assert frame is not None
135
+ script_file = inspect.getfile(frame)
136
+ file = Path(script_file).parent.joinpath(file)
137
+
138
+ return file
139
+
140
+
141
+ @contextmanager
142
+ def _scope():
143
+ token = new_app_slot("webview")
144
+ yield
145
+ reset_app_slot(token)
146
+
147
+
148
+ class WebviewStartArgs(TypedDict, total=False):
149
+ func: Union[Callable[..., None], None]
150
+ args: Union[Iterable[Any], None]
151
+ localization: Dict[str, str]
152
+ gui: Union[GUIType, None]
153
+ http_server: bool
154
+ http_port: Union[int, None]
155
+ user_agent: Union[str, None]
156
+ private_mode: bool
157
+ storage_path: Union[str, None]
158
+ menu: List[Any]
159
+ server: type[http.ServerType] # type: ignore
160
+ server_args: Dict[Any, Any]
161
+ ssl: bool
162
+ icon: Union[str, None]
@@ -0,0 +1,172 @@
1
+ from dataclasses import dataclass
2
+ import itertools
3
+ from pathlib import Path
4
+ import shutil
5
+ from typing import Callable, Optional
6
+ from urllib.parse import quote
7
+
8
+ from instaui.html_tools import to_config_data
9
+ from instaui.runtime._app import get_app_slot, get_default_app_slot
10
+ from instaui.systems import file_system
11
+ from instaui.template import webview_template
12
+ from instaui import consts
13
+
14
+ from . import _utils
15
+
16
+
17
+ _INDEX_HTML_NAME = "index.html"
18
+
19
+
20
+ @dataclass
21
+ class ResourceInfo:
22
+ title: str
23
+ index_html_url: str
24
+
25
+
26
+ def resource_to_assets(
27
+ page_url: str, assets_path: Path, on_app_mounted: Optional[Callable] = None
28
+ ):
29
+ page_dir = assets_path.joinpath(quote(page_url, safe=""))
30
+ _utils.reset_dir(page_dir)
31
+
32
+ relative_to_assets = _get_relative_to_assets(page_dir)
33
+ file_to_assets = _get_file_to_assets(page_dir)
34
+
35
+ config_data = to_config_data()
36
+
37
+ system_slot = get_app_slot()
38
+ default_app_slot = get_default_app_slot()
39
+ html_resource = system_slot._html_resource
40
+ default_html_resource = default_app_slot._html_resource
41
+
42
+ if html_resource.favicon:
43
+ favicon_url = file_to_assets(html_resource.favicon)
44
+ else:
45
+ if default_html_resource.favicon:
46
+ favicon_url = file_to_assets(default_html_resource.favicon)
47
+ else:
48
+ favicon_url = file_to_assets(consts.FAVICON_PATH)
49
+
50
+ model = webview_template.WebViewTemplateModel(
51
+ vue_js_code=file_to_assets(consts.VUE_ES_JS_PATH),
52
+ instaui_js_code=file_to_assets(consts.APP_ES_JS_PATH),
53
+ css_links=[
54
+ file_to_assets(consts.APP_CSS_PATH),
55
+ ],
56
+ config_dict=config_data,
57
+ favicon_url=favicon_url,
58
+ title=html_resource.title or default_html_resource.title or consts.PAGE_TITLE,
59
+ on_app_mounted=on_app_mounted,
60
+ )
61
+
62
+ # register custom components
63
+ for component in system_slot._component_dependencies:
64
+ if not component.esm:
65
+ continue
66
+
67
+ component_url = file_to_assets(component.esm)
68
+ model.add_extra_import_map(component_url, file_to_assets(component.esm))
69
+
70
+ model.vue_app_component.append(
71
+ webview_template.WebViewVueAppComponent(
72
+ name=component.tag_name,
73
+ url=component_url,
74
+ )
75
+ )
76
+
77
+ if component.css:
78
+ for css_link in component.css:
79
+ css_resource = file_to_assets(css_link)
80
+ if css_link.is_file():
81
+ model.css_links.append(css_resource)
82
+
83
+ if component.externals:
84
+ for name, file in component.externals.items():
85
+ model.add_extra_import_map(name, file_to_assets(file))
86
+
87
+ # register custom plugins
88
+ for plugin in set(
89
+ itertools.chain(
90
+ system_slot._plugin_dependencies, default_app_slot._plugin_dependencies
91
+ )
92
+ ):
93
+ if not plugin.esm:
94
+ continue
95
+
96
+ model.vue_app_use.append(plugin.name)
97
+
98
+ model.add_extra_import_map(plugin.name, file_to_assets(plugin.esm))
99
+
100
+ if plugin.css:
101
+ for css_link in plugin.css:
102
+ model.css_links.append(file_to_assets(css_link))
103
+
104
+ # css file link to web static link
105
+ for link in html_resource.get_valid_css_links(
106
+ default_html_resource._css_links_manager
107
+ ):
108
+ if isinstance(link, Path):
109
+ model.css_links.append(file_to_assets(link))
110
+ else:
111
+ model.css_links.append(link)
112
+
113
+ # js file link to web static link
114
+ for info in html_resource.get_valid_js_links(
115
+ default_html_resource._js_links_manager
116
+ ):
117
+ link = file_to_assets(info.link) if isinstance(info.link, Path) else info.link
118
+ model.js_links.append(link)
119
+
120
+ for js_code in itertools.chain(
121
+ html_resource._script_tags, default_html_resource._script_tags
122
+ ):
123
+ model.script_tags.append(js_code)
124
+
125
+ for sylte_code in itertools.chain(
126
+ html_resource._style_tags, default_html_resource._style_tags
127
+ ):
128
+ model.style_tags.append(sylte_code)
129
+
130
+ html_str = webview_template.render_wbeview_html(model)
131
+
132
+ index_html_path = page_dir / _INDEX_HTML_NAME
133
+ index_html_path.write_text(html_str, encoding="utf-8")
134
+ relative_to_assets(index_html_path)
135
+ index_html_url = str(index_html_path.absolute())
136
+ return ResourceInfo(
137
+ title="test",
138
+ index_html_url=index_html_url,
139
+ )
140
+
141
+
142
+ def _get_relative_to_assets(assets_path: Path):
143
+ def wrapper(file_path: Path, relative_parent=False):
144
+ return str(
145
+ file_path.relative_to(
146
+ assets_path.parent if relative_parent else assets_path
147
+ )
148
+ ).replace("\\", "/")
149
+
150
+ return wrapper
151
+
152
+
153
+ def _get_file_to_assets(assets_path: Path):
154
+ relative_to_assets = _get_relative_to_assets(assets_path)
155
+
156
+ def wrapper(file_path: Path):
157
+ hash_part = file_system.generate_hash_name_from_path(file_path.parent)
158
+ new_folder_path = assets_path.joinpath(hash_part)
159
+
160
+ if not new_folder_path.exists():
161
+ new_folder_path.mkdir(parents=True)
162
+
163
+ new_path = new_folder_path.joinpath(file_path.name)
164
+
165
+ if file_path.is_file():
166
+ shutil.copyfile(file_path, new_path)
167
+ return "./" + relative_to_assets(new_path)
168
+ else:
169
+ shutil.copytree(file_path, new_path, dirs_exist_ok=True)
170
+ return "./" + relative_to_assets(new_path) + "/"
171
+
172
+ return wrapper
instaui/zero/func.py CHANGED
@@ -1,24 +1,16 @@
1
1
  from __future__ import annotations
2
2
  import itertools
3
3
  from pathlib import Path
4
- from typing import Union
5
4
  import instaui.consts as consts
6
5
  from instaui.runtime._app import get_app_slot, get_default_app_slot
7
6
  from instaui.template import render_zero_html
8
7
  from instaui.template import zero_template
9
8
  from instaui.html_tools import to_config_data
10
9
  from instaui.runtime.dataclass import JsLink
10
+ from instaui.runtime.resource import HtmlResource
11
11
 
12
12
 
13
- def to_html(file: Union[str, Path]):
14
- if isinstance(file, str):
15
- import inspect
16
-
17
- frame = inspect.currentframe().f_back # type: ignore
18
- assert frame is not None
19
- script_file = inspect.getfile(frame)
20
- file = Path(script_file).parent.joinpath(file)
21
-
13
+ def to_html(file: Path):
22
14
  file = Path(file)
23
15
 
24
16
  raw = to_html_str()
@@ -27,11 +19,20 @@ def to_html(file: Union[str, Path]):
27
19
  return file.resolve().absolute()
28
20
 
29
21
 
30
- def to_html_str():
22
+ def get_template_model():
31
23
  system_slot = get_app_slot()
24
+
25
+ merge_global_resources = (system_slot.meta or {}).get(
26
+ "merge_global_resources", True
27
+ )
28
+
32
29
  default_app_slot = get_default_app_slot()
33
30
  html_resource = system_slot._html_resource
34
- default_html_resource = default_app_slot._html_resource
31
+ default_html_resource = (
32
+ default_app_slot._html_resource
33
+ if merge_global_resources
34
+ else _empty_html_resource()
35
+ )
35
36
 
36
37
  config_data = to_config_data()
37
38
 
@@ -48,13 +49,6 @@ def to_html_str():
48
49
  title=html_resource.title or default_html_resource.title or consts.PAGE_TITLE,
49
50
  )
50
51
 
51
- if html_resource.use_tailwind is None:
52
- if default_html_resource.use_tailwind:
53
- model.js_links.append(JsLink(consts.TAILWIND_JS_PATH))
54
- else:
55
- if html_resource.use_tailwind:
56
- model.js_links.append(JsLink(consts.TAILWIND_JS_PATH))
57
-
58
52
  # register custom components
59
53
  for component in system_slot._component_dependencies:
60
54
  if not component.esm:
@@ -71,6 +65,11 @@ def to_html_str():
71
65
  for css_link in component.css:
72
66
  model.css_links.append(css_link)
73
67
 
68
+ if component.externals:
69
+ for name, url in component.externals.items():
70
+ if url.is_file():
71
+ model.add_extra_import_map(name, url)
72
+
74
73
  # register custom plugins
75
74
  for plugin in set(
76
75
  itertools.chain(
@@ -89,15 +88,15 @@ def to_html_str():
89
88
  model.css_links.append(css_link)
90
89
 
91
90
  # css file link to web static link
92
- for link, attrs in itertools.chain(
93
- html_resource._css_links.items(), default_html_resource._css_links.items()
91
+ for link in html_resource.get_valid_css_links(
92
+ default_html_resource._css_links_manager
94
93
  ):
95
94
  if isinstance(link, Path):
96
95
  model.css_links.append(link)
97
96
 
98
97
  # js file link to web static link
99
- for info in itertools.chain(
100
- html_resource._js_links, default_html_resource._js_links
98
+ for info in html_resource.get_valid_js_links(
99
+ default_html_resource._js_links_manager
101
100
  ):
102
101
  if isinstance(info.link, Path):
103
102
  model.js_links.append(JsLink(info.link))
@@ -112,4 +111,13 @@ def to_html_str():
112
111
  ):
113
112
  model.style_tags.append(sylte_code)
114
113
 
114
+ return model
115
+
116
+
117
+ def to_html_str():
118
+ model = get_template_model()
115
119
  return render_zero_html(model)
120
+
121
+
122
+ def _empty_html_resource():
123
+ return HtmlResource()