cj-tauri 1.1.2 → 2.0.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 +64 -18
- package/cjpm.toml +52 -52
- package/cli/cjpm.toml +10 -1
- package/cli/src/doctor.cj +4 -2
- package/cli/src/main.cj +11 -3
- package/cli/src/manifest.cj +558 -0
- package/cli/src/project.cj +38 -22
- package/cli/src/scaffold.cj +369 -361
- package/cli/src/sync.cj +526 -0
- package/cli/src/util.cj +168 -159
- package/cli/templates/app/README.md +48 -80
- package/cli/templates/app/{capabilities → assets/capabilities}/default.json +7 -7
- package/cli/templates/app/{ui → assets/ui}/index.html +118 -118
- package/cli/templates/app/cj-tauri.toml +22 -0
- package/cli/templates/app/cjpm.toml +34 -17
- package/cli/templates/app/src/main.cj +90 -88
- package/cli/templates/app-react/README.md +56 -67
- package/cli/templates/app-react/{capabilities → assets/capabilities}/default.json +7 -7
- package/cli/templates/app-react/{ui → assets/ui}/package.json +19 -19
- package/cli/templates/app-react/{ui → assets/ui}/src/main.jsx +6 -6
- package/cli/templates/app-react/{ui → assets/ui}/vite.config.js +24 -24
- package/cli/templates/app-react/cj-tauri.toml +23 -0
- package/cli/templates/app-react/cjpm.toml +34 -17
- package/cli/templates/app-react/src/main.cj +91 -102
- package/cli/templates/app-vue/README.md +56 -60
- package/cli/templates/app-vue/{capabilities → assets/capabilities}/default.json +7 -7
- package/cli/templates/app-vue/{ui → assets/ui}/package.json +18 -18
- package/cli/templates/app-vue/{ui → assets/ui}/src/main.js +4 -4
- package/cli/templates/app-vue/{ui → assets/ui}/vite.config.js +24 -24
- package/cli/templates/app-vue/cj-tauri.toml +23 -0
- package/cli/templates/app-vue/cjpm.toml +34 -17
- package/cli/templates/app-vue/src/main.cj +91 -102
- package/cli/templates/{ohos-pc → ohos-skeleton}/AppScope/app.json5 +13 -13
- package/cli/templates/{ohos-pc → ohos-skeleton}/AppScope/resources/base/element/string.json +8 -8
- package/cli/templates/{ohos-pc → ohos-skeleton}/AppScope/resources/base/media/layered_image.json +6 -6
- package/cli/templates/{ohos-pc → ohos-skeleton}/README.md +79 -79
- package/cli/templates/{ohos-pc → ohos-skeleton}/build-profile.json5 +46 -46
- package/cli/templates/{ohos-pc → ohos-skeleton}/entry/build-profile.json5 +26 -26
- package/cli/templates/{ohos-pc → ohos-skeleton}/entry/cjpm.toml +43 -43
- package/cli/templates/{ohos-pc → ohos-skeleton}/entry/hvigorfile.ts +6 -6
- package/cli/templates/{ohos-pc → ohos-skeleton}/entry/oh-package.json5 +9 -9
- package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/cangjie/ability_stage.cj +17 -17
- package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/cangjie/app_host.cj +94 -94
- package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/cangjie/index.cj +167 -167
- package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/cangjie/main_ability.cj +139 -139
- package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/module.json5 +49 -49
- package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/base/element/color.json +8 -8
- package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/base/element/string.json +16 -16
- package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/base/media/layered_image.json +6 -6
- package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/base/profile/main_pages.json +2 -2
- package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/rawfile/capabilities.json +6 -6
- package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/rawfile/index.html +92 -92
- package/cli/templates/{ohos-pc → ohos-skeleton}/hvigor/hvigor-config.json5 +5 -5
- package/cli/templates/{ohos-pc → ohos-skeleton}/hvigorfile.ts +6 -6
- package/cli/templates/{ohos-pc → ohos-skeleton}/oh-package.json5 +10 -10
- package/package.json +4 -2
- package/prebuilt/win32-x64/cj-tauri.exe +0 -0
- package/src/app.cj +49 -20
- package/src/app_spec.cj +76 -0
- package/src/arkweb_script.cj +142 -154
- package/src/host.cj +20 -17
- package/src/host_arkweb.cj +778 -740
- package/src/host_webkit.cj +22 -0
- package/src/host_webview2.cj +22 -0
- package/src/launch.cj +62 -0
- package/src/platform/platform_app_context.cj +77 -0
- package/src/platform/platform_assets.cj +33 -0
- package/src/platform/platform_discover.cj +176 -0
- package/src/platform/platform_env.cj +29 -0
- package/src/platform/platform_feature.cj +31 -0
- package/src/platform/platform_page_source.cj +87 -0
- package/src/platform/platform_target_info.cj +32 -0
- package/src/platform_host_provider.cj +87 -0
- package/src/plugin.cj +36 -2
- package/src/plugins/clipboard/plugin_clipboard.cj +8 -0
- package/src/plugins/menu/plugin_menu.cj +158 -150
- package/src/plugins/notification/plugin_notification.cj +8 -0
- package/src/plugins/serial/plugin_serial.cj +454 -442
- package/src/plugins/serial/serial_bridge.cj +194 -186
- package/src/plugins/serial/serial_ohos_stub.cj +61 -0
- package/src/plugins/shell/plugin_shell.cj +258 -230
- package/src/plugins/sqlite/plugin_sqlite.cj +710 -697
- package/src/plugins/sqlite/sqlite_ffi.cj +211 -206
- package/src/plugins/sqlite/sqlite_ohos_stub.cj +91 -0
- package/src/tests/app_spec_test.cj +127 -0
- package/src/tests/fake_host.cj +23 -8
- package/src/tests/platform_contract_test.cj +251 -0
- package/src/tests/plugin_test.cj +585 -542
- package/src/version.cj +1 -1
- package/src/host_factory.cj +0 -48
- /package/cli/templates/app-react/{ui → assets/ui}/index.html +0 -0
- /package/cli/templates/app-react/{ui → assets/ui}/src/App.css +0 -0
- /package/cli/templates/app-react/{ui → assets/ui}/src/App.jsx +0 -0
- /package/cli/templates/app-vue/{ui → assets/ui}/index.html +0 -0
- /package/cli/templates/app-vue/{ui → assets/ui}/src/App.vue +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/AppScope/resources/base/media/background.png +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/AppScope/resources/base/media/foreground.png +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/build-hap.sh +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/base/media/background.png +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/base/media/foreground.png +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/base/media/startIcon.png +0 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
package cjTauri
|
|
2
|
+
|
|
3
|
+
import std.collection.ArrayList
|
|
4
|
+
import cjTauri.platform.*
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 宿主提供者注册表(RFC-002 §5.1)——多宿主入口,取代早期的单槽注入
|
|
8
|
+
*
|
|
9
|
+
* 为什么是"优先级"而不是布尔匹配:同一 os 下会有多个宿主——鸿蒙 ArkWeb 与
|
|
10
|
+
* ARM64 Linux 桌面的 os 都报 Linux,Android 与 Linux 同理;整数优先级让
|
|
11
|
+
* 「专用实现压过通用实现」成为数据而不是 if 顺序。
|
|
12
|
+
*
|
|
13
|
+
* 早期的单槽注入(`setHostFactory` / `requireInjectedHost`)已删除:装配层只经本注册表
|
|
14
|
+
* 取宿主,`resolveHost` 优先注册表、没有匹配者才回落内置宿主(见 host.cj 的 createHost)。
|
|
15
|
+
*/
|
|
16
|
+
public interface HostProvider {
|
|
17
|
+
/** 0 = 不匹配;>0 越大越优先 */
|
|
18
|
+
func priority(target: TargetInfo): Int64
|
|
19
|
+
/** 一个 label 一个宿主实例(多窗口事件按 label 路由,见 host.cj HostRoute) */
|
|
20
|
+
func create(label: String, target: TargetInfo): WebViewHost
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
private var HOST_PROVIDERS: ArrayList<HostProvider> = ArrayList<HostProvider>()
|
|
24
|
+
|
|
25
|
+
/** 注册宿主提供者:**必须在构造 TauriApp() 之前**(宿主在字段初始化时就创建,
|
|
26
|
+
* 与内置宿主同一约束);幂等性由调用方保证,重复注册按优先级取胜者。
|
|
27
|
+
* 返回注册下标(测试反注册用) */
|
|
28
|
+
public func registerHost(provider: HostProvider): Int64 {
|
|
29
|
+
let idx = HOST_PROVIDERS.size
|
|
30
|
+
HOST_PROVIDERS.add(provider)
|
|
31
|
+
return idx
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** 已注册数量(自检 / 测试用) */
|
|
35
|
+
public func hostProviderCount(): Int64 {
|
|
36
|
+
return HOST_PROVIDERS.size
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 反注册(测试用):全局注册表是进程级的,用例注册的探针宿主不清走会污染后续用例
|
|
41
|
+
* (优先级平局 / 抢占都取决于注册先后)。业务侧没有场景该调它。
|
|
42
|
+
* 接口引用没有可用的相等比较(`!=` 与 `equal` 都不提供),所以按注册下标定位。
|
|
43
|
+
*/
|
|
44
|
+
public func unregisterHost(index: Int64): Unit {
|
|
45
|
+
if (index >= 0 && index < HOST_PROVIDERS.size) {
|
|
46
|
+
// remove 收区间(start 含、end 不含):单元素 = index..index+1
|
|
47
|
+
HOST_PROVIDERS.remove(index..index + 1)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* 按 TargetInfo 选出优先级最高的宿主并创建
|
|
53
|
+
*
|
|
54
|
+
* 框架内部装配用(批次 1 的 createHost 会走这里);没有匹配者返回 None,
|
|
55
|
+
* 由调用方给出可照做的错误——与宿主装配「不静默回落」同一约定。
|
|
56
|
+
*/
|
|
57
|
+
public func pickHost(label: String, target: TargetInfo): Option<WebViewHost> {
|
|
58
|
+
var best: Option<HostProvider> = None
|
|
59
|
+
var bestPriority: Int64 = 0
|
|
60
|
+
for (p in HOST_PROVIDERS) {
|
|
61
|
+
let pr = p.priority(target)
|
|
62
|
+
if (pr > bestPriority) {
|
|
63
|
+
bestPriority = pr
|
|
64
|
+
best = Some(p)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
match (best) {
|
|
68
|
+
case Some(p) => return Some(p.create(label, target))
|
|
69
|
+
case None => return None
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* 装配层的宿主解析(批次 1 接线,RFC §5.4):注册表优先,内置实现兜底
|
|
75
|
+
*
|
|
76
|
+
* app.cj 的 TauriApp 构造与 addWindow 都走这里;原有 @When 门禁的 createHost
|
|
77
|
+
* 三分支原样保留为兜底(桌面 WebKit/WebView2、鸿蒙 ArkWeb),既有行为不变。
|
|
78
|
+
* 注册表的优先级 > 0 才参与,因此「应用显式注册」永远压过内置默认。
|
|
79
|
+
* (public:装配层与测试子包 cjTauri.tests 都要引用;业务侧正常不会直接调用。)
|
|
80
|
+
*/
|
|
81
|
+
public func resolveHost(label: String): WebViewHost {
|
|
82
|
+
let target = Platform.guessTarget()
|
|
83
|
+
match (pickHost(label, target)) {
|
|
84
|
+
case Some(h) => return h
|
|
85
|
+
case None => return createHost(label)
|
|
86
|
+
}
|
|
87
|
+
}
|
package/src/plugin.cj
CHANGED
|
@@ -56,6 +56,37 @@ public interface Plugin {
|
|
|
56
56
|
func scopeAware(): Bool {
|
|
57
57
|
return false
|
|
58
58
|
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 自描述(批次 3,RFC §4.6.7):供 sync/check 三方比对的静态数据。
|
|
62
|
+
* 默认实现 = 全宿主、无能力依赖(多数插件无需覆盖);
|
|
63
|
+
* 有平台约束的插件必须覆盖(如 SqlitePlugin 只支持 webkitgtk)。
|
|
64
|
+
*
|
|
65
|
+
* v1 曾结论「不加接口方法」(数据可从 commands()/events()/permissions() 推出);
|
|
66
|
+
* 批次 3 改自描述双通道——supportedHosts / requiredCapabilities 是命令表推不出来的
|
|
67
|
+
* 语义信息,只能由插件自报。
|
|
68
|
+
*/
|
|
69
|
+
func describe(): PluginManifest {
|
|
70
|
+
return PluginManifest(ArrayList<String>(), ArrayList<String>())
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* 插件自描述(批次 3,RFC §4.6.7):供 sync/check 三方比对的静态数据。
|
|
76
|
+
*
|
|
77
|
+
* - supportedHosts:插件实际支持的 hostKind 列表;空 = 全宿主。
|
|
78
|
+
* 不在列表的宿主上,插件必须以桩如实失败「端不支持」(P-2),不得静默缺失;
|
|
79
|
+
* - requiredCapabilities:插件依赖的能力清单 identifier;空 = 无依赖。
|
|
80
|
+
* check 拿它与 capabilities 目录的 identifier 交叉比对(缺声明 = 失败)。
|
|
81
|
+
*/
|
|
82
|
+
public class PluginManifest {
|
|
83
|
+
public let supportedHosts: ArrayList<String>
|
|
84
|
+
public let requiredCapabilities: ArrayList<String>
|
|
85
|
+
|
|
86
|
+
public init(supportedHosts: ArrayList<String>, requiredCapabilities: ArrayList<String>) {
|
|
87
|
+
this.supportedHosts = supportedHosts
|
|
88
|
+
this.requiredCapabilities = requiredCapabilities
|
|
89
|
+
}
|
|
59
90
|
}
|
|
60
91
|
|
|
61
92
|
/**
|
|
@@ -193,18 +224,21 @@ public func describePlugins(plugins: ArrayList<Plugin>): String {
|
|
|
193
224
|
* - `hasShim`:是否带前端 JS 片段(工具据此判断要不要等 `__CJ_TAURI__.<插件名>`)
|
|
194
225
|
* 数组与权限键都按字典序排:HashMap 迭代顺序不定,工具的输出 / diff 需要稳定。
|
|
195
226
|
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
227
|
+
* 数据来自 describe()(批次 3 自描述双通道):命令/事件/权限集之外,携带 supportedHosts /
|
|
228
|
+
* requiredCapabilities 两个命令表推不出来的语义项(RFC §4.6.7)。
|
|
198
229
|
*/
|
|
199
230
|
public func describePluginsJson(plugins: ArrayList<Plugin>): JsonValue {
|
|
200
231
|
let arr = ArrayList<JsonValue>()
|
|
201
232
|
for (p in plugins) {
|
|
202
233
|
var obj = JsonObject()
|
|
234
|
+
let d = p.describe()
|
|
203
235
|
obj.put("name", JsonString(p.name()))
|
|
204
236
|
obj.put("commands", jsonStringArray(pluginCommandNames(p)))
|
|
205
237
|
obj.put("events", jsonStringArray(pluginEventNames(p)))
|
|
206
238
|
obj.put("permissions", jsonPermissionSets(p))
|
|
207
239
|
obj.put("hasShim", JsonBool(p.jsShim().size > 0))
|
|
240
|
+
obj.put("supportedHosts", jsonStringArray(d.supportedHosts))
|
|
241
|
+
obj.put("requiredCapabilities", jsonStringArray(d.requiredCapabilities))
|
|
208
242
|
arr.add(obj)
|
|
209
243
|
}
|
|
210
244
|
return JsonArray(arr)
|
|
@@ -22,6 +22,14 @@ import stdx.encoding.json.*
|
|
|
22
22
|
* 读写都要宿主已就绪(start 之后);更早调用得到 false / None。
|
|
23
23
|
*/
|
|
24
24
|
public class ClipboardPlugin <: Plugin {
|
|
25
|
+
|
|
26
|
+
/** 自描述:原生桥仅桌面两端实现(ohos 侧按 P-1 落桩,批次 3-PR3) */
|
|
27
|
+
public func describe(): PluginManifest {
|
|
28
|
+
let hosts = ArrayList<String>()
|
|
29
|
+
hosts.add("webkitgtk")
|
|
30
|
+
hosts.add("webview2")
|
|
31
|
+
return PluginManifest(hosts, ArrayList<String>())
|
|
32
|
+
}
|
|
25
33
|
private let handler: ClipboardCommands = ClipboardCommands()
|
|
26
34
|
|
|
27
35
|
public init() {}
|
|
@@ -1,150 +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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
public func
|
|
45
|
-
let
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
public func
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
private func
|
|
117
|
-
let id = this.requireString(args, "id")
|
|
118
|
-
let
|
|
119
|
-
this.requireApp().
|
|
120
|
-
return JsonBool(true)
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
private func
|
|
132
|
-
match (
|
|
133
|
-
case Some(
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
+
}
|
|
@@ -21,6 +21,14 @@ import stdx.encoding.json.*
|
|
|
21
21
|
* 「发后即忘」:提交即返回,通知显示与否不回传(freedesktop 协议本就没有同步回执)。
|
|
22
22
|
*/
|
|
23
23
|
public class NotificationPlugin <: Plugin {
|
|
24
|
+
|
|
25
|
+
/** 自描述:原生桥仅桌面两端实现(ohos 侧按 P-1 落桩,批次 3-PR3) */
|
|
26
|
+
public func describe(): PluginManifest {
|
|
27
|
+
let hosts = ArrayList<String>()
|
|
28
|
+
hosts.add("webkitgtk")
|
|
29
|
+
hosts.add("webview2")
|
|
30
|
+
return PluginManifest(hosts, ArrayList<String>())
|
|
31
|
+
}
|
|
24
32
|
private let handler: NotificationCommands = NotificationCommands()
|
|
25
33
|
|
|
26
34
|
public init() {}
|