instaui 0.1.4__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.
- instaui/components/echarts/echarts.js +128 -0
- instaui/components/echarts/echarts.py +194 -0
- instaui/components/echarts/static/echarts.esm.min.js +45 -0
- instaui/components/element.py +50 -4
- instaui/components/html/__init__.py +30 -19
- instaui/components/html/_preset.py +4 -0
- instaui/components/html/heading.py +51 -0
- instaui/components/html/range.py +3 -0
- instaui/components/html/select.py +13 -31
- instaui/components/html/table.py +36 -0
- instaui/components/markdown/static/marked.esm.js +0 -1
- 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 +175 -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/value_element.py +7 -3
- instaui/consts.py +2 -1
- instaui/daisyui/__init__.py +8 -2
- instaui/daisyui/_index.py +5 -0
- instaui/daisyui/table.py +35 -0
- instaui/event/js_event.py +1 -0
- instaui/event/web_event.py +6 -7
- instaui/fastapi_server/server.py +4 -12
- instaui/handlers/event_handler.py +3 -1
- instaui/handlers/watch_handler.py +4 -0
- instaui/html_tools.py +40 -2
- instaui/runtime/_app.py +37 -3
- instaui/runtime/_link_manager.py +89 -0
- instaui/runtime/resource.py +19 -9
- instaui/shadcn_classless/_index.py +42 -0
- instaui/shadcn_classless/static/shadcn-classless.css +403 -0
- instaui/static/insta-ui.css +1 -1
- instaui/static/insta-ui.esm-browser.prod.js +1314 -1253
- instaui/static/insta-ui.js.map +1 -1
- instaui/static/instaui-tools-browser.js +511 -0
- instaui/static/templates/webview.html +78 -0
- instaui/tailwind/__init__.py +6 -0
- instaui/tailwind/_index.py +24 -0
- instaui/{static/tailwindcss.min.js → tailwind/static/tailwindcss-v3.min.js} +62 -62
- instaui/tailwind/static/tailwindcss-v4.min.js +8 -0
- instaui/template/_utils.py +23 -0
- instaui/template/webview_template.py +50 -0
- instaui/ui/__init__.py +8 -2
- instaui/ui/__init__.pyi +7 -1
- instaui/vars/event_context.py +4 -0
- instaui/vars/web_computed.py +30 -30
- instaui/watch/web_watch.py +5 -6
- instaui/webview/__init__.py +1 -0
- instaui/webview/_utils.py +8 -0
- instaui/webview/api.py +72 -0
- instaui/webview/func.py +114 -0
- instaui/webview/index.py +162 -0
- instaui/webview/resource.py +172 -0
- instaui/zero/func.py +19 -12
- instaui/zero/scope.py +46 -28
- {instaui-0.1.4.dist-info → instaui-0.1.5.dist-info}/METADATA +4 -1
- {instaui-0.1.4.dist-info → instaui-0.1.5.dist-info}/RECORD +67 -47
- instaui/components/highlight_code/code.js +0 -63
- instaui/components/highlight_code/code.py +0 -117
- instaui/components/highlight_code/static/core.min.js +0 -307
- instaui/components/highlight_code/static/languages/css.min.js +0 -31
- instaui/components/highlight_code/static/languages/javascript.min.js +0 -81
- instaui/components/highlight_code/static/languages/json.min.js +0 -8
- instaui/components/highlight_code/static/languages/python-repl.min.js +0 -5
- instaui/components/highlight_code/static/languages/python.min.js +0 -42
- instaui/components/highlight_code/static/languages/shell.min.js +0 -5
- instaui/components/highlight_code/static/styles/default.min.css +0 -9
- instaui/components/highlight_code/static/styles/github-dark-dimmed.min.css +0 -9
- instaui/components/highlight_code/static/styles/github-dark.min.css +0 -10
- instaui/components/highlight_code/static/styles/github.min.css +0 -10
- instaui/handlers/computed_handler.py +0 -42
- instaui/handlers/config_handler.py +0 -13
- {instaui-0.1.4.dist-info → instaui-0.1.5.dist-info}/LICENSE +0 -0
- {instaui-0.1.4.dist-info → instaui-0.1.5.dist-info}/WHEEL +0 -0
@@ -0,0 +1,23 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
import typing
|
3
|
+
from pathlib import Path
|
4
|
+
from urllib.parse import quote
|
5
|
+
import base64
|
6
|
+
|
7
|
+
|
8
|
+
_JS_PREFIX = "data:text/javascript;charset=utf-8"
|
9
|
+
_CSS_PREFIX = "data:text/css;charset=utf-8"
|
10
|
+
_ICON_PREFIX = "data:image/x-icon;base64"
|
11
|
+
|
12
|
+
|
13
|
+
def normalize_path_to_dataurl(path: typing.Union[str, Path], prefix: str):
|
14
|
+
if isinstance(path, Path):
|
15
|
+
path = path.read_text(encoding="utf-8")
|
16
|
+
|
17
|
+
return f"{prefix},{quote(path)}"
|
18
|
+
|
19
|
+
|
20
|
+
def normalize_path_to_base64_url(path: typing.Optional[Path], prefix: str):
|
21
|
+
if path is None:
|
22
|
+
return None
|
23
|
+
return f"{prefix},{base64.b64encode(path.read_bytes()).decode('utf-8')}"
|
@@ -0,0 +1,50 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
import typing
|
3
|
+
from pathlib import Path
|
4
|
+
from dataclasses import dataclass, field
|
5
|
+
from instaui.common.jsonable import dumps
|
6
|
+
from instaui.runtime.dataclass import JsLink
|
7
|
+
from .env import env
|
8
|
+
|
9
|
+
|
10
|
+
_html_template = env.get_template("webview.html")
|
11
|
+
|
12
|
+
|
13
|
+
@dataclass(frozen=True)
|
14
|
+
class WebViewVueAppComponent:
|
15
|
+
name: str
|
16
|
+
url: str
|
17
|
+
|
18
|
+
|
19
|
+
@dataclass
|
20
|
+
class WebViewTemplateModel:
|
21
|
+
vue_js_code: str
|
22
|
+
instaui_js_code: str
|
23
|
+
config_dict: typing.Dict[str, typing.Any] = field(default_factory=dict)
|
24
|
+
extra_import_maps: typing.Dict[str, str] = field(default_factory=dict)
|
25
|
+
css_links: typing.List[str] = field(default_factory=list)
|
26
|
+
style_tags: typing.List[str] = field(default_factory=list)
|
27
|
+
js_links: typing.List[str] = field(default_factory=list)
|
28
|
+
script_tags: typing.List[str] = field(default_factory=list)
|
29
|
+
vue_app_use: typing.List[str] = field(default_factory=list)
|
30
|
+
vue_app_component: typing.List[WebViewVueAppComponent] = field(default_factory=list)
|
31
|
+
title: typing.Optional[str] = None
|
32
|
+
favicon_url: typing.Optional[str] = None
|
33
|
+
on_app_mounted: typing.Optional[typing.Callable] = None
|
34
|
+
|
35
|
+
def add_extra_import_map(self, name: str, code: str):
|
36
|
+
self.extra_import_maps[name] = code
|
37
|
+
|
38
|
+
@property
|
39
|
+
def import_maps_string(self):
|
40
|
+
data = {
|
41
|
+
**self.extra_import_maps,
|
42
|
+
"vue": self.vue_js_code,
|
43
|
+
"instaui": self.instaui_js_code,
|
44
|
+
}
|
45
|
+
|
46
|
+
return dumps(data)
|
47
|
+
|
48
|
+
|
49
|
+
def render_wbeview_html(model: WebViewTemplateModel) -> str:
|
50
|
+
return _html_template.render(model=model)
|
instaui/ui/__init__.py
CHANGED
@@ -61,8 +61,11 @@ __all__ = [
|
|
61
61
|
"str_format",
|
62
62
|
"url_location",
|
63
63
|
"on_page_request_lifespan",
|
64
|
+
"webview",
|
64
65
|
"code",
|
65
66
|
"markdown",
|
67
|
+
"echarts",
|
68
|
+
"use_shadcn_classless",
|
66
69
|
]
|
67
70
|
|
68
71
|
# -- static imports
|
@@ -126,5 +129,8 @@ from .events import on_page_request_lifespan
|
|
126
129
|
|
127
130
|
# -- dynamic imports
|
128
131
|
from instaui.systems.module_system import LazyModule
|
129
|
-
code = LazyModule('instaui.components.
|
130
|
-
markdown = LazyModule('instaui.components.markdown.markdown', 'Markdown')
|
132
|
+
code = LazyModule('instaui.components.shiki_code.shiki_code', 'Code')
|
133
|
+
markdown = LazyModule('instaui.components.markdown.markdown', 'Markdown')
|
134
|
+
webview = LazyModule('instaui.webview', 'WebviewWrapper')
|
135
|
+
echarts = LazyModule('instaui.components.echarts.echarts', 'ECharts')
|
136
|
+
use_shadcn_classless = LazyModule('instaui.shadcn_classless._index', 'use_shadcn_classless')
|
instaui/ui/__init__.pyi
CHANGED
@@ -61,8 +61,11 @@ __all__ = [
|
|
61
61
|
"str_format",
|
62
62
|
"url_location",
|
63
63
|
"on_page_request_lifespan",
|
64
|
+
"webview",
|
64
65
|
"code",
|
65
66
|
"markdown",
|
67
|
+
"echarts",
|
68
|
+
"use_shadcn_classless",
|
66
69
|
]
|
67
70
|
|
68
71
|
# -- static imports
|
@@ -125,5 +128,8 @@ from instaui.ui_functions.ui_types import TBindable, is_bindable
|
|
125
128
|
from .events import on_page_request_lifespan
|
126
129
|
|
127
130
|
# -- dynamic imports
|
128
|
-
from instaui.components.
|
131
|
+
from instaui.components.shiki_code.shiki_code import Code as code
|
129
132
|
from instaui.components.markdown.markdown import Markdown as markdown
|
133
|
+
from instaui.webview import WebviewWrapper as webview
|
134
|
+
from instaui.components.echarts.echarts import ECharts as echarts
|
135
|
+
from instaui.shadcn_classless._index import use_shadcn_classless
|
instaui/vars/event_context.py
CHANGED
@@ -30,6 +30,10 @@ class EventContext(Jsonable, CanInputMixin):
|
|
30
30
|
def e():
|
31
31
|
return EventContext(":e => e")
|
32
32
|
|
33
|
+
@staticmethod
|
34
|
+
def target_value():
|
35
|
+
return EventContext(":e => e.target.value")
|
36
|
+
|
33
37
|
|
34
38
|
class DatasetEventContext(Jsonable, CanInputMixin):
|
35
39
|
def __init__(self, event_context: EventContext) -> None:
|
instaui/vars/web_computed.py
CHANGED
@@ -24,6 +24,9 @@ from instaui.vars.mixin_types.observable import ObservableMixin
|
|
24
24
|
from instaui.vars.mixin_types.common_type import TObservableInput
|
25
25
|
from instaui._helper import observable_helper
|
26
26
|
|
27
|
+
_SYNC_TYPE = "sync"
|
28
|
+
_ASYNC_TYPE = "async"
|
29
|
+
|
27
30
|
P = ParamSpec("P")
|
28
31
|
R = TypeVar("R")
|
29
32
|
|
@@ -70,47 +73,44 @@ class WebComputed(
|
|
70
73
|
|
71
74
|
app = get_app_slot()
|
72
75
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
)
|
76
|
+
hkey = watch_handler.create_handler_key(
|
77
|
+
page_path=app.page_path,
|
78
|
+
handler=self._fn,
|
79
|
+
)
|
78
80
|
|
79
|
-
|
81
|
+
watch_handler.register_handler(hkey, self._fn, len(self._outputs) + 1)
|
80
82
|
|
81
|
-
|
82
|
-
watch_handler.ASYNC_URL
|
83
|
-
if inspect.iscoroutinefunction(self._fn)
|
84
|
-
else watch_handler.SYNC_URL
|
85
|
-
)
|
83
|
+
# if app.mode == "web":
|
86
84
|
|
87
|
-
|
88
|
-
|
89
|
-
|
85
|
+
data["id"] = self._id
|
86
|
+
data["sid"] = self._sid
|
87
|
+
data["type"] = self.VAR_TYPE
|
90
88
|
|
91
|
-
|
92
|
-
|
89
|
+
if self._inputs:
|
90
|
+
data["inputs"] = self._inputs
|
93
91
|
|
94
|
-
|
95
|
-
|
92
|
+
if self._outputs:
|
93
|
+
data["outputs"] = self._outputs
|
96
94
|
|
97
|
-
|
98
|
-
|
95
|
+
if sum(self._is_slient_inputs) > 0:
|
96
|
+
data["slient"] = self._is_slient_inputs
|
99
97
|
|
100
|
-
|
101
|
-
|
98
|
+
if sum(self._is_data) > 0:
|
99
|
+
data["data"] = self._is_data
|
102
100
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
101
|
+
data["fType"] = (
|
102
|
+
_ASYNC_TYPE if inspect.iscoroutinefunction(self._fn) else _SYNC_TYPE
|
103
|
+
)
|
104
|
+
data["key"] = hkey
|
105
|
+
if self._init_value is not None:
|
106
|
+
data["init"] = self._init_value
|
107
107
|
|
108
|
-
|
109
|
-
|
108
|
+
if self._evaluating:
|
109
|
+
data["running"] = self._evaluating._to_output_config()
|
110
110
|
|
111
|
-
|
111
|
+
return data
|
112
112
|
|
113
|
-
return {}
|
113
|
+
# return {}
|
114
114
|
|
115
115
|
def __to_binding_config(self):
|
116
116
|
return {
|
instaui/watch/web_watch.py
CHANGED
@@ -13,6 +13,9 @@ from instaui.vars.mixin_types.py_binding import CanOutputMixin
|
|
13
13
|
from instaui.vars.mixin_types.common_type import TObservableInput
|
14
14
|
from instaui._helper import observable_helper
|
15
15
|
|
16
|
+
_SYNC_TYPE = "sync"
|
17
|
+
_ASYNC_TYPE = "async"
|
18
|
+
|
16
19
|
P = ParamSpec("P")
|
17
20
|
R = typing.TypeVar("R")
|
18
21
|
|
@@ -59,13 +62,9 @@ class WebWatch(Jsonable, typing.Generic[P, R]):
|
|
59
62
|
|
60
63
|
watch_handler.register_handler(hkey, self._fn, len(self._outputs))
|
61
64
|
|
62
|
-
|
63
|
-
|
64
|
-
if inspect.iscoroutinefunction(self._fn)
|
65
|
-
else watch_handler.SYNC_URL
|
65
|
+
data["fType"] = (
|
66
|
+
_ASYNC_TYPE if inspect.iscoroutinefunction(self._fn) else _SYNC_TYPE
|
66
67
|
)
|
67
|
-
|
68
|
-
data["url"] = url
|
69
68
|
data["key"] = hkey
|
70
69
|
if self._inputs:
|
71
70
|
data["inputs"] = self._inputs
|
@@ -0,0 +1 @@
|
|
1
|
+
from .index import WebviewWrapper
|
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
|
instaui/webview/func.py
ADDED
@@ -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()
|
instaui/webview/index.py
ADDED
@@ -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]
|