cj-tauri 2.0.0 → 2.1.0
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.
- package/README.md +113 -99
- package/bin/cj-tauri.js +287 -287
- package/cjpm.toml +63 -52
- package/cli/cjpm.toml +22 -22
- package/cli/src/doctor.cj +308 -306
- package/cli/src/main.cj +129 -129
- package/cli/src/manifest.cj +632 -558
- package/cli/src/manifest_test.cj +87 -0
- package/cli/src/project.cj +770 -770
- package/cli/src/resolve.cj +348 -348
- package/cli/src/scaffold.cj +369 -369
- package/cli/src/sync.cj +526 -526
- package/cli/src/util.cj +168 -168
- package/cli/templates/app/README.md +51 -48
- package/cli/templates/app/assets/capabilities/default.json +7 -7
- package/cli/templates/app/assets/ui/index.html +120 -118
- package/cli/templates/app/cj-tauri.toml +32 -22
- package/cli/templates/app/cjpm.toml +34 -34
- package/cli/templates/app/src/main.cj +101 -90
- package/cli/templates/app-react/README.md +56 -56
- package/cli/templates/app-react/assets/capabilities/default.json +7 -7
- package/cli/templates/app-react/assets/ui/index.html +25 -25
- package/cli/templates/app-react/assets/ui/package.json +19 -19
- package/cli/templates/app-react/assets/ui/src/App.css +67 -67
- package/cli/templates/app-react/assets/ui/src/App.jsx +124 -124
- package/cli/templates/app-react/assets/ui/src/main.jsx +6 -6
- package/cli/templates/app-react/assets/ui/vite.config.js +24 -24
- package/cli/templates/app-react/cj-tauri.toml +33 -23
- package/cli/templates/app-react/cjpm.toml +34 -34
- package/cli/templates/app-react/src/main.cj +91 -91
- package/cli/templates/app-vue/README.md +56 -56
- package/cli/templates/app-vue/assets/capabilities/default.json +7 -7
- package/cli/templates/app-vue/assets/ui/index.html +25 -25
- package/cli/templates/app-vue/assets/ui/package.json +18 -18
- package/cli/templates/app-vue/assets/ui/src/App.vue +176 -176
- package/cli/templates/app-vue/assets/ui/src/main.js +4 -4
- package/cli/templates/app-vue/assets/ui/vite.config.js +24 -24
- package/cli/templates/app-vue/cj-tauri.toml +33 -23
- package/cli/templates/app-vue/cjpm.toml +34 -34
- package/cli/templates/app-vue/src/main.cj +91 -91
- package/cli/templates/ohos-skeleton/AppScope/app.json5 +13 -13
- package/cli/templates/ohos-skeleton/AppScope/resources/base/element/string.json +8 -8
- package/cli/templates/ohos-skeleton/AppScope/resources/base/media/layered_image.json +6 -6
- package/cli/templates/ohos-skeleton/README.md +84 -79
- package/cli/templates/ohos-skeleton/build-hap.sh +15 -0
- package/cli/templates/ohos-skeleton/build-profile.json5 +46 -46
- package/cli/templates/ohos-skeleton/entry/build-profile.json5 +26 -26
- package/cli/templates/ohos-skeleton/entry/cjpm.toml +51 -43
- package/cli/templates/ohos-skeleton/entry/hvigorfile.ts +6 -6
- package/cli/templates/ohos-skeleton/entry/oh-package.json5 +9 -9
- package/cli/templates/ohos-skeleton/entry/src/main/cangjie/ability_stage.cj +17 -17
- package/cli/templates/ohos-skeleton/entry/src/main/cangjie/app_host.cj +94 -94
- package/cli/templates/ohos-skeleton/entry/src/main/cangjie/dialog_host.cj +217 -0
- package/cli/templates/ohos-skeleton/entry/src/main/cangjie/index.cj +527 -167
- package/cli/templates/ohos-skeleton/entry/src/main/cangjie/main_ability.cj +139 -139
- package/cli/templates/ohos-skeleton/entry/src/main/module.json5 +57 -49
- package/cli/templates/ohos-skeleton/entry/src/main/resources/base/element/color.json +8 -8
- package/cli/templates/ohos-skeleton/entry/src/main/resources/base/element/string.json +16 -16
- package/cli/templates/ohos-skeleton/entry/src/main/resources/base/media/layered_image.json +6 -6
- package/cli/templates/ohos-skeleton/entry/src/main/resources/base/profile/main_pages.json +2 -2
- package/cli/templates/ohos-skeleton/entry/src/main/resources/rawfile/capabilities.json +6 -6
- package/cli/templates/ohos-skeleton/entry/src/main/resources/rawfile/index.html +92 -92
- package/cli/templates/ohos-skeleton/hvigor/hvigor-config.json5 +5 -5
- package/cli/templates/ohos-skeleton/hvigorfile.ts +6 -6
- package/cli/templates/ohos-skeleton/oh-package.json5 +10 -10
- package/native/build_win.bat +62 -62
- package/native/core/bridge_core.c +1681 -1626
- package/native/core/bridge_core.h +675 -618
- package/native/core/bridge_js.h +108 -108
- package/native/platforms/harmonyos/README.md +115 -6
- package/native/platforms/linux/bridge_linux.c +2787 -2485
- package/native/platforms/windows/bridge_win.c +3740 -3248
- package/native/tests/gtk_threading_probe.c +143 -143
- package/native/tests/test_bridge_core.c +1339 -1228
- package/package.json +53 -53
- package/prebuilt/linux-x64/cj-tauri +0 -0
- package/src/api_system.cj +67 -67
- package/src/app.cj +325 -969
- package/src/app_events.cj +166 -0
- package/src/app_host_ops.cj +287 -0
- package/src/app_run.cj +283 -0
- package/src/app_spec.cj +76 -76
- package/src/arkweb_menu.cj +216 -0
- package/src/arkweb_script.cj +142 -142
- package/src/capability.cj +409 -409
- package/src/clipboard_bridge.cj +178 -0
- package/src/dialog_spec.cj +79 -0
- package/src/dragdrop.cj +26 -26
- package/src/embedded_assets.cj +98 -98
- package/src/host.cj +752 -607
- package/src/host_arkweb.cj +924 -778
- package/src/host_webkit.cj +663 -570
- package/src/host_webview2.cj +663 -570
- package/src/hotkey.cj +66 -66
- package/src/ipc_hub.cj +337 -337
- package/src/ipc_message.cj +286 -286
- package/src/launch.cj +62 -62
- package/src/logging.cj +127 -127
- package/src/menu.cj +197 -197
- package/src/platform/platform_app_context.cj +77 -77
- package/src/platform/platform_assets.cj +33 -33
- package/src/platform/platform_discover.cj +176 -176
- package/src/platform/platform_env.cj +29 -29
- package/src/platform/platform_feature.cj +31 -31
- package/src/platform/platform_page_source.cj +87 -87
- package/src/platform/platform_target_info.cj +32 -32
- package/src/platform_host_provider.cj +87 -87
- package/src/plugin.cj +333 -333
- package/src/plugins/clipboard/plugin_clipboard.cj +152 -152
- package/src/plugins/dialog/plugin_dialog.cj +209 -209
- package/src/plugins/fs/plugin_fs.cj +105 -1
- package/src/plugins/http/http_backend.cj +131 -0
- package/src/plugins/http/http_desktop.cj +195 -0
- package/src/plugins/http/http_ohos.cj +204 -0
- package/src/plugins/http/plugin_http.cj +310 -459
- package/src/plugins/log/plugin_log.cj +105 -105
- package/src/plugins/menu/plugin_menu.cj +158 -158
- package/src/plugins/notification/plugin_notification.cj +154 -154
- package/src/plugins/serial/plugin_serial.cj +458 -454
- package/src/plugins/serial/serial_bridge.cj +194 -194
- package/src/plugins/serial/serial_manager_ohos.cj +92 -0
- package/src/plugins/serial/serial_ohos.cj +529 -0
- package/src/plugins/shell/plugin_shell.cj +258 -258
- package/src/plugins/sqlite/plugin_sqlite.cj +705 -710
- package/src/plugins/sqlite/sqlite_ffi.cj +206 -211
- package/src/plugins/ws/plugin_ws.cj +488 -470
- package/src/process_hidden.cj +174 -174
- package/src/scope.cj +391 -391
- package/src/shell_event.cj +69 -69
- package/src/tests/app_spec_test.cj +126 -127
- package/src/tests/capability_test.cj +243 -243
- package/src/tests/dialog_spec_test.cj +73 -0
- package/src/tests/fake_host.cj +201 -201
- package/src/tests/ipc_hub_test.cj +487 -487
- package/src/tests/logging_test.cj +62 -62
- package/src/tests/platform_contract_test.cj +251 -251
- package/src/tests/plugin_clipboard_test.cj +222 -222
- package/src/tests/plugin_dialog_test.cj +237 -237
- package/src/tests/plugin_fs_test.cj +207 -0
- package/src/tests/plugin_http_test.cj +124 -124
- package/src/tests/plugin_notification_test.cj +205 -205
- package/src/tests/plugin_serial_test.cj +48 -0
- package/src/tests/plugin_test.cj +605 -585
- package/src/tests/plugin_ws_test.cj +40 -40
- package/src/tests/process_hidden_test.cj +129 -129
- package/src/tests/scope_test.cj +350 -350
- package/src/tests/system_host_test.cj +99 -81
- package/src/tests/urlutil_test.cj +53 -53
- package/src/tests/version_test.cj +75 -75
- package/src/tests/window_registry_test.cj +123 -123
- package/src/tray.cj +90 -90
- package/src/urlutil.cj +159 -159
- package/src/version.cj +44 -44
- package/src/window.cj +51 -51
- package/src/window_registry.cj +116 -116
- package/prebuilt/win32-x64/cj-tauri.exe +0 -0
- package/src/plugins/serial/serial_ohos_stub.cj +0 -61
- package/src/plugins/sqlite/sqlite_ohos_stub.cj +0 -91
|
@@ -1,105 +1,105 @@
|
|
|
1
|
-
package cjTauri.plugins.log
|
|
2
|
-
|
|
3
|
-
import cjTauri.*
|
|
4
|
-
|
|
5
|
-
import std.collection.*
|
|
6
|
-
import stdx.encoding.json.*
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* 官方插件 log:把**前端**的诊断并进宿主日志(RFC-003 第 5 期)
|
|
10
|
-
*
|
|
11
|
-
* 命令:log:write(追加一行)/ log:path(回读生效中的日志文件,空串=没开)
|
|
12
|
-
* 权限:**不自动放行**,应用在 capabilities 里显式声明,两种写法等价:
|
|
13
|
-
* "commands": ["log:write", "log:path"] // 明文逐条
|
|
14
|
-
* "permissions": ["log:default"] // 命名权限集(两条都放行)
|
|
15
|
-
* 前端:window.__CJ_TAURI__.log.write("...") / log.path()
|
|
16
|
-
*
|
|
17
|
-
* 为什么需要它:框架的落盘接管的是 **C 桥与仓颉侧**的 stderr,而页面里的 JS 报错在交付给
|
|
18
|
-
* 用户的单文件产物里同样无处可去(GUI 子系统、双击没有控制台)。插件把页面诊断经命令
|
|
19
|
-
* 送回宿主,落进**同一份**文件——报障时只看一个文件,不用两台机器对着时间戳拼。
|
|
20
|
-
*
|
|
21
|
-
* shim 顺带装上 `error` / `unhandledrejection` 两个钩子:页面自己没写日志也能留下异常。
|
|
22
|
-
* 钩子里**必须吞掉** invoke 的失败(命令未放行时它会 reject),否则钩子自己又造出一轮
|
|
23
|
-
* 未捕获拒绝,转成停不下来的自激。
|
|
24
|
-
*/
|
|
25
|
-
public class LogPlugin <: Plugin {
|
|
26
|
-
public init() {}
|
|
27
|
-
|
|
28
|
-
public func name(): String {
|
|
29
|
-
return "log"
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
public func commands(): HashMap<String, CommandHandler> {
|
|
33
|
-
let m = HashMap<String, CommandHandler>()
|
|
34
|
-
// 键写**短名**:前缀由框架补(写全名会得到 log:log:write,且所有命令都成未注册)
|
|
35
|
-
m.add("write", LogCommands())
|
|
36
|
-
m.add("path", LogCommands())
|
|
37
|
-
return m
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/** 命名权限集(v1.1):default = 写 + 回读路径(回读能问到本机目录布局,不该默认放宽) */
|
|
41
|
-
public func permissions(): HashMap<String, ArrayList<String>> {
|
|
42
|
-
let m = HashMap<String, ArrayList<String>>()
|
|
43
|
-
let all = ArrayList<String>()
|
|
44
|
-
all.add("write")
|
|
45
|
-
all.add("path")
|
|
46
|
-
m.add("default", all)
|
|
47
|
-
return m
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* 前端 shim:`__CJ_TAURI__.log` + 两个全局异常钩子(都在 document-start 装,早于页面脚本)
|
|
52
|
-
*
|
|
53
|
-
* 内联 JS 里**不出现反斜杠**(转义会先被仓颉吃掉,AGENTS §4);拼接用 `+`,
|
|
54
|
-
* 不用模板串,免得 `${}` 撞上仓颉的字符串插值。
|
|
55
|
-
*/
|
|
56
|
-
public func jsShim(): String {
|
|
57
|
-
return "(function () { var t = window.__CJ_TAURI__ || (window.__CJ_TAURI__ = {});" +
|
|
58
|
-
" t.log = {" +
|
|
59
|
-
" write: function (line) { return window.__CJ_TAURI__.invoke('log:write', { line: String(line) }); }," +
|
|
60
|
-
" path: function () { return window.__CJ_TAURI__.invoke('log:path', {}); }" +
|
|
61
|
-
" };" +
|
|
62
|
-
" var push = function (line) { try { var p = t.log.write(line);" +
|
|
63
|
-
" if (p && p.catch) { p.catch(function () {}); } } catch (e) {} };" +
|
|
64
|
-
" window.addEventListener('error', function (ev) {" +
|
|
65
|
-
" var where = ev.filename ? (ev.filename + ':' + ev.lineno + ':' + ev.colno) : 'unknown';" +
|
|
66
|
-
" push('[frontend] error: ' + (ev.message || 'unknown') + ' @ ' + where); });" +
|
|
67
|
-
" window.addEventListener('unhandledrejection', function (ev) {" +
|
|
68
|
-
" push('[frontend] unhandled rejection: ' + String(ev.reason)); });" +
|
|
69
|
-
" })();\n"
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/** 日志命令处理器:write 收 { line },path 无参 */
|
|
74
|
-
public class LogCommands <: CommandHandler {
|
|
75
|
-
public init() {}
|
|
76
|
-
|
|
77
|
-
public func handle(cmd: String, args: JsonObject, ipc: IpcContext): JsonValue {
|
|
78
|
-
let _ = ipc
|
|
79
|
-
match (cmd) {
|
|
80
|
-
case "log:write" =>
|
|
81
|
-
let line = this.requireString(args, "line")
|
|
82
|
-
if (line.size > CJ_LOG_MAX_LINE) {
|
|
83
|
-
// 不按字节切字符串(会把汉字切碎):超长就只记一条说明,
|
|
84
|
-
// 免得页面用一行天文数字把内存和日志文件一起撑爆
|
|
85
|
-
let note = "[frontend] log line dropped: ${line.size} bytes exceeds ${CJ_LOG_MAX_LINE}"
|
|
86
|
-
return JsonBool(writeLogLine(note))
|
|
87
|
-
}
|
|
88
|
-
// false = 没开日志(这一轮没落盘),不是错误:页面照常跑,别让它变成 reject
|
|
89
|
-
return JsonBool(writeLogLine(line))
|
|
90
|
-
case "log:path" =>
|
|
91
|
-
return JsonString(currentLogFilePath()) // 空串 = 没开
|
|
92
|
-
case _ => throw CommandException("unknown log command: ${cmd}")
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
private func requireString(args: JsonObject, key: String): String {
|
|
97
|
-
match (args.get(key)) {
|
|
98
|
-
case Some(v) => match (v.kind()) {
|
|
99
|
-
case JsonKind.JsString => return v.asString().getValue()
|
|
100
|
-
case _ => throw CommandException("log: argument \"${key}\" must be a string")
|
|
101
|
-
}
|
|
102
|
-
case None => throw CommandException("log: missing argument \"${key}\"")
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
1
|
+
package cjTauri.plugins.log
|
|
2
|
+
|
|
3
|
+
import cjTauri.*
|
|
4
|
+
|
|
5
|
+
import std.collection.*
|
|
6
|
+
import stdx.encoding.json.*
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 官方插件 log:把**前端**的诊断并进宿主日志(RFC-003 第 5 期)
|
|
10
|
+
*
|
|
11
|
+
* 命令:log:write(追加一行)/ log:path(回读生效中的日志文件,空串=没开)
|
|
12
|
+
* 权限:**不自动放行**,应用在 capabilities 里显式声明,两种写法等价:
|
|
13
|
+
* "commands": ["log:write", "log:path"] // 明文逐条
|
|
14
|
+
* "permissions": ["log:default"] // 命名权限集(两条都放行)
|
|
15
|
+
* 前端:window.__CJ_TAURI__.log.write("...") / log.path()
|
|
16
|
+
*
|
|
17
|
+
* 为什么需要它:框架的落盘接管的是 **C 桥与仓颉侧**的 stderr,而页面里的 JS 报错在交付给
|
|
18
|
+
* 用户的单文件产物里同样无处可去(GUI 子系统、双击没有控制台)。插件把页面诊断经命令
|
|
19
|
+
* 送回宿主,落进**同一份**文件——报障时只看一个文件,不用两台机器对着时间戳拼。
|
|
20
|
+
*
|
|
21
|
+
* shim 顺带装上 `error` / `unhandledrejection` 两个钩子:页面自己没写日志也能留下异常。
|
|
22
|
+
* 钩子里**必须吞掉** invoke 的失败(命令未放行时它会 reject),否则钩子自己又造出一轮
|
|
23
|
+
* 未捕获拒绝,转成停不下来的自激。
|
|
24
|
+
*/
|
|
25
|
+
public class LogPlugin <: Plugin {
|
|
26
|
+
public init() {}
|
|
27
|
+
|
|
28
|
+
public func name(): String {
|
|
29
|
+
return "log"
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public func commands(): HashMap<String, CommandHandler> {
|
|
33
|
+
let m = HashMap<String, CommandHandler>()
|
|
34
|
+
// 键写**短名**:前缀由框架补(写全名会得到 log:log:write,且所有命令都成未注册)
|
|
35
|
+
m.add("write", LogCommands())
|
|
36
|
+
m.add("path", LogCommands())
|
|
37
|
+
return m
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** 命名权限集(v1.1):default = 写 + 回读路径(回读能问到本机目录布局,不该默认放宽) */
|
|
41
|
+
public func permissions(): HashMap<String, ArrayList<String>> {
|
|
42
|
+
let m = HashMap<String, ArrayList<String>>()
|
|
43
|
+
let all = ArrayList<String>()
|
|
44
|
+
all.add("write")
|
|
45
|
+
all.add("path")
|
|
46
|
+
m.add("default", all)
|
|
47
|
+
return m
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 前端 shim:`__CJ_TAURI__.log` + 两个全局异常钩子(都在 document-start 装,早于页面脚本)
|
|
52
|
+
*
|
|
53
|
+
* 内联 JS 里**不出现反斜杠**(转义会先被仓颉吃掉,AGENTS §4);拼接用 `+`,
|
|
54
|
+
* 不用模板串,免得 `${}` 撞上仓颉的字符串插值。
|
|
55
|
+
*/
|
|
56
|
+
public func jsShim(): String {
|
|
57
|
+
return "(function () { var t = window.__CJ_TAURI__ || (window.__CJ_TAURI__ = {});" +
|
|
58
|
+
" t.log = {" +
|
|
59
|
+
" write: function (line) { return window.__CJ_TAURI__.invoke('log:write', { line: String(line) }); }," +
|
|
60
|
+
" path: function () { return window.__CJ_TAURI__.invoke('log:path', {}); }" +
|
|
61
|
+
" };" +
|
|
62
|
+
" var push = function (line) { try { var p = t.log.write(line);" +
|
|
63
|
+
" if (p && p.catch) { p.catch(function () {}); } } catch (e) {} };" +
|
|
64
|
+
" window.addEventListener('error', function (ev) {" +
|
|
65
|
+
" var where = ev.filename ? (ev.filename + ':' + ev.lineno + ':' + ev.colno) : 'unknown';" +
|
|
66
|
+
" push('[frontend] error: ' + (ev.message || 'unknown') + ' @ ' + where); });" +
|
|
67
|
+
" window.addEventListener('unhandledrejection', function (ev) {" +
|
|
68
|
+
" push('[frontend] unhandled rejection: ' + String(ev.reason)); });" +
|
|
69
|
+
" })();\n"
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** 日志命令处理器:write 收 { line },path 无参 */
|
|
74
|
+
public class LogCommands <: CommandHandler {
|
|
75
|
+
public init() {}
|
|
76
|
+
|
|
77
|
+
public func handle(cmd: String, args: JsonObject, ipc: IpcContext): JsonValue {
|
|
78
|
+
let _ = ipc
|
|
79
|
+
match (cmd) {
|
|
80
|
+
case "log:write" =>
|
|
81
|
+
let line = this.requireString(args, "line")
|
|
82
|
+
if (line.size > CJ_LOG_MAX_LINE) {
|
|
83
|
+
// 不按字节切字符串(会把汉字切碎):超长就只记一条说明,
|
|
84
|
+
// 免得页面用一行天文数字把内存和日志文件一起撑爆
|
|
85
|
+
let note = "[frontend] log line dropped: ${line.size} bytes exceeds ${CJ_LOG_MAX_LINE}"
|
|
86
|
+
return JsonBool(writeLogLine(note))
|
|
87
|
+
}
|
|
88
|
+
// false = 没开日志(这一轮没落盘),不是错误:页面照常跑,别让它变成 reject
|
|
89
|
+
return JsonBool(writeLogLine(line))
|
|
90
|
+
case "log:path" =>
|
|
91
|
+
return JsonString(currentLogFilePath()) // 空串 = 没开
|
|
92
|
+
case _ => throw CommandException("unknown log command: ${cmd}")
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private func requireString(args: JsonObject, key: String): String {
|
|
97
|
+
match (args.get(key)) {
|
|
98
|
+
case Some(v) => match (v.kind()) {
|
|
99
|
+
case JsonKind.JsString => return v.asString().getValue()
|
|
100
|
+
case _ => throw CommandException("log: argument \"${key}\" must be a string")
|
|
101
|
+
}
|
|
102
|
+
case None => throw CommandException("log: missing argument \"${key}\"")
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -1,158 +1,158 @@
|
|
|
1
|
-
package cjTauri.plugins.menu
|
|
2
|
-
|
|
3
|
-
import cjTauri.*
|
|
4
|
-
|
|
5
|
-
import std.collection.*
|
|
6
|
-
import stdx.encoding.json.*
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* 官方插件 menu:运行期改菜单项状态 + 前端命名空间(RFC-002 §5.6 / §5.9)
|
|
10
|
-
*
|
|
11
|
-
* 命令(短名 → 全名):
|
|
12
|
-
* setEnabled → menu:setEnabled `{ id, enabled }`
|
|
13
|
-
* setChecked → menu:setChecked `{ id, checked }`
|
|
14
|
-
* 事件:`click` → `menu:click`——本插件只**声明**它(给缺权限提示用),真正投递在
|
|
15
|
-
* `TauriApp` 的 shell 分发里,页面侧写 `listen("menu:click", …)` 即可。
|
|
16
|
-
* 权限:**不自动放行**,应用在 capabilities 里二选一(两种写法等价):
|
|
17
|
-
* "commands": ["menu:setEnabled", "menu:setChecked"] // 明文逐条(旧写法)
|
|
18
|
-
* "permissions": ["menu:state"] // 命名权限集(v1.1)
|
|
19
|
-
* 前端:window.__CJ_TAURI__.menu.setChecked({ id: "view.sidebar", checked: true })
|
|
20
|
-
*
|
|
21
|
-
* 为什么命令要经 `TauriApp`、而不是直接用宿主:菜单项按 id 定位到**调用方那个窗口**
|
|
22
|
-
* (`ipc.window`),而「另一维状态」存在应用层留存的菜单模型里——平台侧一次调用会同时写
|
|
23
|
-
* enabled 与 checked(见 `WebViewHost.setMenuItemState`),拿不到另一维就会把用户刚点出来的
|
|
24
|
-
* 勾选抹掉。所以处理器必须握有 app,装配时由 `setup()` 注入。
|
|
25
|
-
*/
|
|
26
|
-
public class MenuPlugin <: Plugin {
|
|
27
|
-
// 两个命令共用一个处理器实例:app 引用只要注入一次(commands() 早于 setup() 跑,见 attach)
|
|
28
|
-
private let handler: MenuCommands = MenuCommands()
|
|
29
|
-
|
|
30
|
-
public init() {}
|
|
31
|
-
|
|
32
|
-
public func name(): String {
|
|
33
|
-
return "menu"
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/** 自描述:菜单状态改动是宿主能力,仅桌面两端实现(ohos 宿主无菜单栏,命令如实报错,P-1/P-2) */
|
|
37
|
-
public func describe(): PluginManifest {
|
|
38
|
-
let hosts = ArrayList<String>()
|
|
39
|
-
hosts.add("webkitgtk")
|
|
40
|
-
hosts.add("webview2")
|
|
41
|
-
return PluginManifest(hosts, ArrayList<String>())
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
public func commands(): HashMap<String, CommandHandler> {
|
|
45
|
-
let m = HashMap<String, CommandHandler>()
|
|
46
|
-
m.add("setEnabled", this.handler)
|
|
47
|
-
m.add("setChecked", this.handler)
|
|
48
|
-
return m
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/** 事件只声明(`<插件名>:<短名>` = `menu:click`),投递在框架的 shell 分发里 */
|
|
52
|
-
public func events(): ArrayList<String> {
|
|
53
|
-
let e = ArrayList<String>()
|
|
54
|
-
e.add("click")
|
|
55
|
-
return e
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* 命名权限集(v1.1):`menu:state` = 两个改状态的命令
|
|
60
|
-
*
|
|
61
|
-
* 与 fs 的 readonly 同理——**集声明不等于放行**,清单不引用它一条也不会生效。
|
|
62
|
-
*/
|
|
63
|
-
public func permissions(): HashMap<String, ArrayList<String>> {
|
|
64
|
-
let m = HashMap<String, ArrayList<String>>()
|
|
65
|
-
let st = ArrayList<String>()
|
|
66
|
-
st.add("setEnabled")
|
|
67
|
-
st.add("setChecked")
|
|
68
|
-
m.add("state", st)
|
|
69
|
-
return m
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
public func setup(app: TauriApp): Unit {
|
|
73
|
-
this.handler.attach(app)
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* 前端 shim:往桥对象上挂一个 menu 命名空间。
|
|
78
|
-
* 只动 __CJ_TAURI__ 的一个命名空间,不碰 invoke / listen / emit,也不注册全局变量。
|
|
79
|
-
*/
|
|
80
|
-
public func jsShim(): String {
|
|
81
|
-
return "(function () { var t = window.__CJ_TAURI__ || (window.__CJ_TAURI__ = {});" +
|
|
82
|
-
" t.menu = {" +
|
|
83
|
-
" setEnabled: function (args) { return window.__CJ_TAURI__.invoke('menu:setEnabled', args); }," +
|
|
84
|
-
" setChecked: function (args) { return window.__CJ_TAURI__.invoke('menu:setChecked', args); }" +
|
|
85
|
-
" }; })();\n"
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* menu 插件的命令处理器(两个命令共用一份,按全名分派,与内置 SystemCommands / FsCommands 同形)
|
|
91
|
-
*/
|
|
92
|
-
public class MenuCommands <: CommandHandler {
|
|
93
|
-
private var app: Option<TauriApp> = None
|
|
94
|
-
|
|
95
|
-
public init() {}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* 由 MenuPlugin.setup 注入
|
|
99
|
-
*
|
|
100
|
-
* 不能靠构造函数传:`commands()`(装配时先跑,负责建处理器)拿不到 app,
|
|
101
|
-
* 而 `setup(app)` 后跑——所以只能事后注入。
|
|
102
|
-
*/
|
|
103
|
-
func attach(app: TauriApp): Unit {
|
|
104
|
-
this.app = Some(app)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
public func handle(cmd: String, args: JsonObject, ipc: IpcContext): JsonValue {
|
|
108
|
-
match (cmd) {
|
|
109
|
-
case "menu:setEnabled" => this.setEnabled(args, ipc)
|
|
110
|
-
case "menu:setChecked" => this.setChecked(args, ipc)
|
|
111
|
-
case _ => throw CommandException("unknown menu command: ${cmd}")
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/** 只改启用状态;勾选态由应用层模型补齐(见 MenuPlugin 的类注释) */
|
|
116
|
-
private func setEnabled(args: JsonObject, ipc: IpcContext): JsonValue {
|
|
117
|
-
let id = this.requireString(args, "id")
|
|
118
|
-
let enabled = this.requireBool(args, "enabled")
|
|
119
|
-
this.requireApp().setMenuItemEnabledTo(ipc.window, id, enabled)
|
|
120
|
-
return JsonBool(true)
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/** 只改勾选状态;启用态由应用层模型补齐 */
|
|
124
|
-
private func setChecked(args: JsonObject, ipc: IpcContext): JsonValue {
|
|
125
|
-
let id = this.requireString(args, "id")
|
|
126
|
-
let checked = this.requireBool(args, "checked")
|
|
127
|
-
this.requireApp().setMenuItemCheckedTo(ipc.window, id, checked)
|
|
128
|
-
return JsonBool(true)
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
private func requireApp(): TauriApp {
|
|
132
|
-
match (this.app) {
|
|
133
|
-
case Some(a) => return a
|
|
134
|
-
case None => throw CommandException("menu: 插件未装配到应用(请 TauriApp().plugin(MenuPlugin()))")
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/** 取必需的字符串参数;缺失 / 类型不对时抛业务错误,前端拿到可读原文 */
|
|
139
|
-
private func requireString(args: JsonObject, key: String): String {
|
|
140
|
-
match (args.get(key)) {
|
|
141
|
-
case Some(v) => match (v.kind()) {
|
|
142
|
-
case JsonKind.JsString => return v.asString().getValue()
|
|
143
|
-
case _ => throw CommandException("menu: argument \"${key}\" must be a string")
|
|
144
|
-
}
|
|
145
|
-
case None => throw CommandException("menu: missing argument \"${key}\"")
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
private func requireBool(args: JsonObject, key: String): Bool {
|
|
150
|
-
match (args.get(key)) {
|
|
151
|
-
case Some(v) => match (v.kind()) {
|
|
152
|
-
case JsonKind.JsBool => return v.asBool().getValue()
|
|
153
|
-
case _ => throw CommandException("menu: argument \"${key}\" must be a boolean")
|
|
154
|
-
}
|
|
155
|
-
case None => throw CommandException("menu: missing argument \"${key}\"")
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}
|
|
1
|
+
package cjTauri.plugins.menu
|
|
2
|
+
|
|
3
|
+
import cjTauri.*
|
|
4
|
+
|
|
5
|
+
import std.collection.*
|
|
6
|
+
import stdx.encoding.json.*
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 官方插件 menu:运行期改菜单项状态 + 前端命名空间(RFC-002 §5.6 / §5.9)
|
|
10
|
+
*
|
|
11
|
+
* 命令(短名 → 全名):
|
|
12
|
+
* setEnabled → menu:setEnabled `{ id, enabled }`
|
|
13
|
+
* setChecked → menu:setChecked `{ id, checked }`
|
|
14
|
+
* 事件:`click` → `menu:click`——本插件只**声明**它(给缺权限提示用),真正投递在
|
|
15
|
+
* `TauriApp` 的 shell 分发里,页面侧写 `listen("menu:click", …)` 即可。
|
|
16
|
+
* 权限:**不自动放行**,应用在 capabilities 里二选一(两种写法等价):
|
|
17
|
+
* "commands": ["menu:setEnabled", "menu:setChecked"] // 明文逐条(旧写法)
|
|
18
|
+
* "permissions": ["menu:state"] // 命名权限集(v1.1)
|
|
19
|
+
* 前端:window.__CJ_TAURI__.menu.setChecked({ id: "view.sidebar", checked: true })
|
|
20
|
+
*
|
|
21
|
+
* 为什么命令要经 `TauriApp`、而不是直接用宿主:菜单项按 id 定位到**调用方那个窗口**
|
|
22
|
+
* (`ipc.window`),而「另一维状态」存在应用层留存的菜单模型里——平台侧一次调用会同时写
|
|
23
|
+
* enabled 与 checked(见 `WebViewHost.setMenuItemState`),拿不到另一维就会把用户刚点出来的
|
|
24
|
+
* 勾选抹掉。所以处理器必须握有 app,装配时由 `setup()` 注入。
|
|
25
|
+
*/
|
|
26
|
+
public class MenuPlugin <: Plugin {
|
|
27
|
+
// 两个命令共用一个处理器实例:app 引用只要注入一次(commands() 早于 setup() 跑,见 attach)
|
|
28
|
+
private let handler: MenuCommands = MenuCommands()
|
|
29
|
+
|
|
30
|
+
public init() {}
|
|
31
|
+
|
|
32
|
+
public func name(): String {
|
|
33
|
+
return "menu"
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** 自描述:菜单状态改动是宿主能力,仅桌面两端实现(ohos 宿主无菜单栏,命令如实报错,P-1/P-2) */
|
|
37
|
+
public func describe(): PluginManifest {
|
|
38
|
+
let hosts = ArrayList<String>()
|
|
39
|
+
hosts.add("webkitgtk")
|
|
40
|
+
hosts.add("webview2")
|
|
41
|
+
return PluginManifest(hosts, ArrayList<String>())
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
public func commands(): HashMap<String, CommandHandler> {
|
|
45
|
+
let m = HashMap<String, CommandHandler>()
|
|
46
|
+
m.add("setEnabled", this.handler)
|
|
47
|
+
m.add("setChecked", this.handler)
|
|
48
|
+
return m
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** 事件只声明(`<插件名>:<短名>` = `menu:click`),投递在框架的 shell 分发里 */
|
|
52
|
+
public func events(): ArrayList<String> {
|
|
53
|
+
let e = ArrayList<String>()
|
|
54
|
+
e.add("click")
|
|
55
|
+
return e
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 命名权限集(v1.1):`menu:state` = 两个改状态的命令
|
|
60
|
+
*
|
|
61
|
+
* 与 fs 的 readonly 同理——**集声明不等于放行**,清单不引用它一条也不会生效。
|
|
62
|
+
*/
|
|
63
|
+
public func permissions(): HashMap<String, ArrayList<String>> {
|
|
64
|
+
let m = HashMap<String, ArrayList<String>>()
|
|
65
|
+
let st = ArrayList<String>()
|
|
66
|
+
st.add("setEnabled")
|
|
67
|
+
st.add("setChecked")
|
|
68
|
+
m.add("state", st)
|
|
69
|
+
return m
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
public func setup(app: TauriApp): Unit {
|
|
73
|
+
this.handler.attach(app)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* 前端 shim:往桥对象上挂一个 menu 命名空间。
|
|
78
|
+
* 只动 __CJ_TAURI__ 的一个命名空间,不碰 invoke / listen / emit,也不注册全局变量。
|
|
79
|
+
*/
|
|
80
|
+
public func jsShim(): String {
|
|
81
|
+
return "(function () { var t = window.__CJ_TAURI__ || (window.__CJ_TAURI__ = {});" +
|
|
82
|
+
" t.menu = {" +
|
|
83
|
+
" setEnabled: function (args) { return window.__CJ_TAURI__.invoke('menu:setEnabled', args); }," +
|
|
84
|
+
" setChecked: function (args) { return window.__CJ_TAURI__.invoke('menu:setChecked', args); }" +
|
|
85
|
+
" }; })();\n"
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* menu 插件的命令处理器(两个命令共用一份,按全名分派,与内置 SystemCommands / FsCommands 同形)
|
|
91
|
+
*/
|
|
92
|
+
public class MenuCommands <: CommandHandler {
|
|
93
|
+
private var app: Option<TauriApp> = None
|
|
94
|
+
|
|
95
|
+
public init() {}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* 由 MenuPlugin.setup 注入
|
|
99
|
+
*
|
|
100
|
+
* 不能靠构造函数传:`commands()`(装配时先跑,负责建处理器)拿不到 app,
|
|
101
|
+
* 而 `setup(app)` 后跑——所以只能事后注入。
|
|
102
|
+
*/
|
|
103
|
+
func attach(app: TauriApp): Unit {
|
|
104
|
+
this.app = Some(app)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
public func handle(cmd: String, args: JsonObject, ipc: IpcContext): JsonValue {
|
|
108
|
+
match (cmd) {
|
|
109
|
+
case "menu:setEnabled" => this.setEnabled(args, ipc)
|
|
110
|
+
case "menu:setChecked" => this.setChecked(args, ipc)
|
|
111
|
+
case _ => throw CommandException("unknown menu command: ${cmd}")
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** 只改启用状态;勾选态由应用层模型补齐(见 MenuPlugin 的类注释) */
|
|
116
|
+
private func setEnabled(args: JsonObject, ipc: IpcContext): JsonValue {
|
|
117
|
+
let id = this.requireString(args, "id")
|
|
118
|
+
let enabled = this.requireBool(args, "enabled")
|
|
119
|
+
this.requireApp().setMenuItemEnabledTo(ipc.window, id, enabled)
|
|
120
|
+
return JsonBool(true)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** 只改勾选状态;启用态由应用层模型补齐 */
|
|
124
|
+
private func setChecked(args: JsonObject, ipc: IpcContext): JsonValue {
|
|
125
|
+
let id = this.requireString(args, "id")
|
|
126
|
+
let checked = this.requireBool(args, "checked")
|
|
127
|
+
this.requireApp().setMenuItemCheckedTo(ipc.window, id, checked)
|
|
128
|
+
return JsonBool(true)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
private func requireApp(): TauriApp {
|
|
132
|
+
match (this.app) {
|
|
133
|
+
case Some(a) => return a
|
|
134
|
+
case None => throw CommandException("menu: 插件未装配到应用(请 TauriApp().plugin(MenuPlugin()))")
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** 取必需的字符串参数;缺失 / 类型不对时抛业务错误,前端拿到可读原文 */
|
|
139
|
+
private func requireString(args: JsonObject, key: String): String {
|
|
140
|
+
match (args.get(key)) {
|
|
141
|
+
case Some(v) => match (v.kind()) {
|
|
142
|
+
case JsonKind.JsString => return v.asString().getValue()
|
|
143
|
+
case _ => throw CommandException("menu: argument \"${key}\" must be a string")
|
|
144
|
+
}
|
|
145
|
+
case None => throw CommandException("menu: missing argument \"${key}\"")
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private func requireBool(args: JsonObject, key: String): Bool {
|
|
150
|
+
match (args.get(key)) {
|
|
151
|
+
case Some(v) => match (v.kind()) {
|
|
152
|
+
case JsonKind.JsBool => return v.asBool().getValue()
|
|
153
|
+
case _ => throw CommandException("menu: argument \"${key}\" must be a boolean")
|
|
154
|
+
}
|
|
155
|
+
case None => throw CommandException("menu: missing argument \"${key}\"")
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|