@nsnanocat/preference-panes 1.2.5 → 1.3.1
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/CHANGELOG.md +31 -0
- package/README.md +66 -25
- package/dist/home.html +2208 -0
- package/dist/{module/navigation.mjs → navigation.mjs} +51 -143
- package/dist/preference-panes.mjs +913 -614
- package/package.json +4 -3
- package/src/browser/ActionMenu.mjs +7 -6
- package/src/browser/Navigation.d.mts +4 -95
- package/src/browser/Navigation.mjs +43 -14
- package/src/browser/boxjs.mjs +38 -8
- package/src/browser/client.d.mts +3 -3
- package/src/browser/client.mjs +57 -25
- package/src/browser/components.mjs +0 -44
- package/src/browser/home.d.mts +66 -0
- package/src/browser/home.html +15 -0
- package/src/browser/home.mjs +287 -0
- package/src/browser/index.d.ts +2 -0
- package/src/browser/mount.mjs +50 -91
- package/src/browser/page.mjs +325 -0
- package/src/browser/panel.mjs +118 -149
- package/src/browser/styles.css +565 -0
- package/src/index.d.ts +8 -0
- package/dist/module/index.html +0 -16
- package/dist/module/index.mjs +0 -1625
- package/src/browser/ModuleFrame.mjs +0 -122
- package/src/browser/index.mjs +0 -89
- package/src/browser/module.html +0 -16
- package/src/browser/panel.css +0 -315
package/src/browser/panel.mjs
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import { ActionMenu } from "./ActionMenu.mjs";
|
|
2
1
|
import { validValue } from "./boxjs.mjs";
|
|
3
2
|
import { PreferencesClient } from "./client.mjs";
|
|
4
|
-
import { fieldControl, element as node,
|
|
5
|
-
import { Navigation } from "./Navigation.mjs";
|
|
3
|
+
import { fieldControl, element as node, settingRow, statusView } from "./components.mjs";
|
|
6
4
|
|
|
7
5
|
/**
|
|
8
6
|
* 将字段级存储诊断转换为简短提示。
|
|
@@ -21,20 +19,24 @@ function warningMessage(warning) {
|
|
|
21
19
|
}
|
|
22
20
|
|
|
23
21
|
/**
|
|
24
|
-
*
|
|
25
|
-
* Manage the module form,
|
|
22
|
+
* 管理模块表单、操作队列和设置会话。
|
|
23
|
+
* Manage the module form, operation queue and settings session.
|
|
26
24
|
*/
|
|
27
25
|
export class PreferencesPanel {
|
|
28
26
|
#release;
|
|
27
|
+
#view;
|
|
28
|
+
#state;
|
|
29
|
+
#perform;
|
|
29
30
|
|
|
30
31
|
/**
|
|
31
32
|
* 挂载 BoxJS 定义对应的模块表单。
|
|
32
33
|
* Mount the module form described by a BoxJS definition.
|
|
33
34
|
* @param {HTMLElement} root 包内挂载元素 / Internal mount element.
|
|
34
35
|
* @param {import("../index.js").ModuleDefinition} definition 已规范化字段定义 / Normalized field definition.
|
|
36
|
+
* @param {import("./page.mjs").PageController} page 内部页面控制器 / Internal page controller.
|
|
35
37
|
*/
|
|
36
|
-
constructor(root, definition) {
|
|
37
|
-
this.#release = this.#mount(root, definition);
|
|
38
|
+
constructor(root, definition, page) {
|
|
39
|
+
this.#release = this.#mount(root, definition, page);
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
/**
|
|
@@ -42,13 +44,13 @@ export class PreferencesPanel {
|
|
|
42
44
|
* Build panel DOM, interactions, and session, then return its release operation.
|
|
43
45
|
* @param {HTMLElement} root 包内挂载元素 / Internal mount element.
|
|
44
46
|
* @param {import("../index.js").ModuleDefinition} definition 已规范化字段定义 / Normalized field definition.
|
|
47
|
+
* @param {import("./page.mjs").PageController} page 内部页面控制器 / Internal page controller.
|
|
45
48
|
* @returns {() => void} 释放操作 / Release operation.
|
|
46
49
|
*/
|
|
47
|
-
#mount(root, definition) {
|
|
50
|
+
#mount(root, definition, page) {
|
|
48
51
|
const title = definition.metadata?.name ?? definition.module;
|
|
49
52
|
const document = root.ownerDocument;
|
|
50
53
|
const window = document.defaultView;
|
|
51
|
-
const frame = window.frameElement?.dataset.preferencePanes ? window.frameElement : undefined;
|
|
52
54
|
const shell = node("div", "pp-panel");
|
|
53
55
|
shell.dataset.module = definition.module;
|
|
54
56
|
const handlers = new Map();
|
|
@@ -59,50 +61,25 @@ export class PreferencesPanel {
|
|
|
59
61
|
{ id: "reset", label: "重置设置", destructive: true },
|
|
60
62
|
];
|
|
61
63
|
const viewport = node("div", "pp-viewport");
|
|
62
|
-
|
|
63
|
-
if (frame) shell.append(viewport);
|
|
64
|
-
else {
|
|
65
|
-
const header = node("header", "pp-header");
|
|
66
|
-
back = node("button", "pp-back", "‹");
|
|
67
|
-
back.setAttribute("aria-label", "返回");
|
|
68
|
-
back.type = "button";
|
|
69
|
-
heading = node("h1", "pp-title", title);
|
|
70
|
-
menu = new ActionMenu(id => runAction(id));
|
|
71
|
-
const trailing = node("span", "pp-nav-spacer");
|
|
72
|
-
trailing.append(menu.element);
|
|
73
|
-
header.append(back, heading, trailing);
|
|
74
|
-
shell.append(header, viewport);
|
|
75
|
-
}
|
|
76
|
-
let toast;
|
|
64
|
+
shell.append(viewport);
|
|
77
65
|
root.append(shell);
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
);
|
|
88
|
-
else {
|
|
89
|
-
heading.textContent = currentTitle;
|
|
90
|
-
back.disabled = !canGoBack;
|
|
91
|
-
menu.update(actions, saving);
|
|
92
|
-
}
|
|
66
|
+
const editors = new Map();
|
|
67
|
+
this.#view = key => {
|
|
68
|
+
const editor = editors.get(key);
|
|
69
|
+
editor?.load?.();
|
|
70
|
+
return editor?.node ?? root;
|
|
71
|
+
};
|
|
72
|
+
this.#state = key => ({ title: editors.get(key)?.title ?? title, actions: handlers.size ? menuItems : [], busy: saving });
|
|
73
|
+
this.#perform = id => {
|
|
74
|
+
if (!saving && handlers.has(id)) runAction(id);
|
|
93
75
|
};
|
|
94
|
-
const
|
|
95
|
-
if (!
|
|
76
|
+
const publishNavigation = () => {
|
|
77
|
+
if (!destroyed) page.update();
|
|
96
78
|
};
|
|
97
|
-
|
|
98
|
-
let timer,
|
|
99
|
-
navigation,
|
|
100
|
-
generation = 0,
|
|
79
|
+
let generation = 0,
|
|
101
80
|
active = null,
|
|
102
81
|
saving = false,
|
|
103
|
-
destroyed = false
|
|
104
|
-
currentTitle = title,
|
|
105
|
-
canGoBack = window.history.length > 1;
|
|
82
|
+
destroyed = false;
|
|
106
83
|
/**
|
|
107
84
|
* 展示短暂通知,不刷新设置数据。
|
|
108
85
|
* Display a transient notification without refreshing settings.
|
|
@@ -112,43 +89,29 @@ export class PreferencesPanel {
|
|
|
112
89
|
const notify = event => {
|
|
113
90
|
if (destroyed) return;
|
|
114
91
|
let message;
|
|
115
|
-
switch (
|
|
116
|
-
case
|
|
92
|
+
switch (event.kind === "error" ? "error" : event.operation) {
|
|
93
|
+
case "error":
|
|
117
94
|
message = `操作失败:${event.message}`;
|
|
118
95
|
break;
|
|
119
|
-
case
|
|
96
|
+
case "delete":
|
|
120
97
|
message = "删除成功";
|
|
121
98
|
break;
|
|
122
|
-
case
|
|
99
|
+
case "clearCaches":
|
|
123
100
|
message = "Caches 已清空";
|
|
124
101
|
break;
|
|
125
|
-
case
|
|
102
|
+
case "reset":
|
|
126
103
|
message = "设置已重置";
|
|
127
104
|
break;
|
|
128
105
|
default:
|
|
129
106
|
message = "修改成功";
|
|
130
107
|
break;
|
|
131
108
|
}
|
|
132
|
-
|
|
133
|
-
// A host-owned notice creates no web Toast and starts no local timer.
|
|
134
|
-
if (frame && !frame.dispatchEvent(new frame.ownerDocument.defaultView.CustomEvent("preferencepanes:notice", { cancelable: true, detail: { kind: event.kind, message } }))) return;
|
|
135
|
-
if (!toast) {
|
|
136
|
-
toast = node("div", "pp-toast");
|
|
137
|
-
toast.setAttribute("role", "status");
|
|
138
|
-
shell.append(toast);
|
|
139
|
-
}
|
|
140
|
-
toast.textContent = message;
|
|
141
|
-
toast.dataset.kind = event.kind;
|
|
142
|
-
toast.hidden = false;
|
|
143
|
-
clearTimeout(timer);
|
|
144
|
-
timer = setTimeout(() => {
|
|
145
|
-
toast.hidden = true;
|
|
146
|
-
}, 2400);
|
|
109
|
+
page.notice({ kind: event.kind, message });
|
|
147
110
|
};
|
|
148
111
|
const client = new PreferencesClient({ definition, notify });
|
|
149
112
|
/**
|
|
150
|
-
*
|
|
151
|
-
*
|
|
113
|
+
* 菜单操作统一处理异步错误,包含宿主确认框错误。
|
|
114
|
+
* Handle asynchronous action errors, including host-dialog errors.
|
|
152
115
|
* @param {string} id 操作标识 / Action identifier.
|
|
153
116
|
* @returns {Promise<void>} 操作已处理 / Action handled.
|
|
154
117
|
*/
|
|
@@ -168,8 +131,6 @@ export class PreferencesPanel {
|
|
|
168
131
|
async function open(module) {
|
|
169
132
|
const version = ++generation;
|
|
170
133
|
active = module;
|
|
171
|
-
currentTitle = module;
|
|
172
|
-
canGoBack = window.history.length > 1;
|
|
173
134
|
publishNavigation();
|
|
174
135
|
viewport.replaceChildren(statusView("读取设置…"));
|
|
175
136
|
try {
|
|
@@ -188,7 +149,6 @@ export class PreferencesPanel {
|
|
|
188
149
|
*/
|
|
189
150
|
function controls() {
|
|
190
151
|
const { definition, values } = client.snapshot();
|
|
191
|
-
currentTitle = definition.metadata?.name || active;
|
|
192
152
|
const view = node("section", "pp-fields");
|
|
193
153
|
/**
|
|
194
154
|
* 挂载后执行的多行高度更新
|
|
@@ -196,23 +156,12 @@ export class PreferencesPanel {
|
|
|
196
156
|
* @type {Array<() => void>}
|
|
197
157
|
*/
|
|
198
158
|
const growingInputs = [];
|
|
199
|
-
|
|
159
|
+
editors.clear();
|
|
200
160
|
const summaries = [];
|
|
201
161
|
const warningRefreshers = [];
|
|
202
162
|
const groups = new Map();
|
|
203
163
|
let queue = Promise.resolve(),
|
|
204
164
|
pendingWrites = 0;
|
|
205
|
-
/**
|
|
206
|
-
* 导航组件处理页面切换,表单只更新当前标题与返回按钮。
|
|
207
|
-
* Let navigation own transitions; the form only updates the title and back button.
|
|
208
|
-
* @returns {void} 无返回值 / No return value.
|
|
209
|
-
*/
|
|
210
|
-
const updateNavigation = () => {
|
|
211
|
-
const editor = editors.get(navigation.current);
|
|
212
|
-
currentTitle = editor?.title ?? definition.metadata?.name ?? active;
|
|
213
|
-
canGoBack = !saving && navigation.canGoBack;
|
|
214
|
-
publishNavigation();
|
|
215
|
-
};
|
|
216
165
|
/**
|
|
217
166
|
* 串行执行模块操作,保持输入可编辑。
|
|
218
167
|
* Serialize module actions while keeping inputs editable.
|
|
@@ -224,7 +173,6 @@ export class PreferencesPanel {
|
|
|
224
173
|
function perform(action, success, failure = () => {}) {
|
|
225
174
|
pendingWrites++;
|
|
226
175
|
saving = true;
|
|
227
|
-
canGoBack = false;
|
|
228
176
|
publishNavigation();
|
|
229
177
|
queue = queue
|
|
230
178
|
.then(action)
|
|
@@ -239,27 +187,10 @@ export class PreferencesPanel {
|
|
|
239
187
|
.finally(() => {
|
|
240
188
|
pendingWrites--;
|
|
241
189
|
saving = pendingWrites > 0;
|
|
242
|
-
if (destroyed && !saving) client.leave();
|
|
243
|
-
canGoBack = !saving && navigation.canGoBack;
|
|
244
190
|
publishNavigation();
|
|
245
191
|
});
|
|
246
192
|
return queue;
|
|
247
193
|
}
|
|
248
|
-
const metadata = definition.metadata;
|
|
249
|
-
if (metadata) {
|
|
250
|
-
const info = node("div", "pp-module-info");
|
|
251
|
-
const details = node("div", "pp-module-details");
|
|
252
|
-
for (const description of [metadata.author, metadata.desc ?? metadata.description, ...(metadata.descs ?? [])]) if (description) details.append(node("p", "pp-description", description));
|
|
253
|
-
if (metadata.repo) {
|
|
254
|
-
const link = node("a", "pp-module-source", "项目主页");
|
|
255
|
-
link.href = resourceURL(metadata.repo);
|
|
256
|
-
link.target = "_blank";
|
|
257
|
-
link.rel = "noopener noreferrer";
|
|
258
|
-
details.append(link);
|
|
259
|
-
}
|
|
260
|
-
info.append(details);
|
|
261
|
-
view.append(info);
|
|
262
|
-
}
|
|
263
194
|
for (const field of definition.fields) {
|
|
264
195
|
const match = /^\[([^\]]+)\]\s*(.*)$/.exec(field.name);
|
|
265
196
|
const group = match?.[1] ?? "通用";
|
|
@@ -300,8 +231,8 @@ export class PreferencesPanel {
|
|
|
300
231
|
let write;
|
|
301
232
|
let inputContainer = row;
|
|
302
233
|
let eventName = "change";
|
|
303
|
-
switch (
|
|
304
|
-
case
|
|
234
|
+
switch (field.control === "url" ? "url" : field.options ? (field.type === "array" ? "checkboxes" : "selects") : field.type) {
|
|
235
|
+
case "url": {
|
|
305
236
|
const link = node("a", "pp-choice-link", "打开");
|
|
306
237
|
link.setAttribute("aria-label", field.name);
|
|
307
238
|
link.rel = "noopener noreferrer";
|
|
@@ -309,7 +240,8 @@ export class PreferencesPanel {
|
|
|
309
240
|
link.href = value;
|
|
310
241
|
};
|
|
311
242
|
link.addEventListener("click", event => {
|
|
312
|
-
|
|
243
|
+
event.preventDefault();
|
|
244
|
+
page.openURL(link.href);
|
|
313
245
|
});
|
|
314
246
|
row.append(link);
|
|
315
247
|
row.addEventListener("click", event => {
|
|
@@ -317,7 +249,7 @@ export class PreferencesPanel {
|
|
|
317
249
|
});
|
|
318
250
|
break;
|
|
319
251
|
}
|
|
320
|
-
case
|
|
252
|
+
case "selects": {
|
|
321
253
|
const select = node("select", "");
|
|
322
254
|
select.setAttribute("aria-label", field.name);
|
|
323
255
|
field.options.forEach((option, index) => {
|
|
@@ -332,13 +264,13 @@ export class PreferencesPanel {
|
|
|
332
264
|
read = () => field.options[select.selectedIndex]?.key;
|
|
333
265
|
break;
|
|
334
266
|
}
|
|
335
|
-
case
|
|
336
|
-
const
|
|
337
|
-
if (field.description)
|
|
267
|
+
case "checkboxes": {
|
|
268
|
+
const editor = node("section", "pp-panel pp-detail pp-choice-page");
|
|
269
|
+
if (field.description) editor.append(node("p", "pp-description", field.description));
|
|
338
270
|
const choices = node("div", "pp-rows");
|
|
339
|
-
|
|
271
|
+
editor.append(choices);
|
|
340
272
|
inputContainer = choices;
|
|
341
|
-
editors.set(field.key, { node:
|
|
273
|
+
editors.set(field.key, { node: editor, title: match?.[2] ?? field.name });
|
|
342
274
|
const summary = node("span", "pp-summary");
|
|
343
275
|
const link = node("button", "pp-choice-link");
|
|
344
276
|
link.type = "button";
|
|
@@ -355,7 +287,7 @@ export class PreferencesPanel {
|
|
|
355
287
|
};
|
|
356
288
|
summaries.push(refresh);
|
|
357
289
|
refresh();
|
|
358
|
-
link.onclick = () =>
|
|
290
|
+
link.onclick = () => page.open(field.key);
|
|
359
291
|
row.addEventListener("click", event => {
|
|
360
292
|
if (!link.contains(event.target)) link.click();
|
|
361
293
|
});
|
|
@@ -376,7 +308,7 @@ export class PreferencesPanel {
|
|
|
376
308
|
};
|
|
377
309
|
break;
|
|
378
310
|
}
|
|
379
|
-
case
|
|
311
|
+
case "boolean": {
|
|
380
312
|
const toggle = node("input", "pp-switch");
|
|
381
313
|
toggle.type = "checkbox";
|
|
382
314
|
toggle.setAttribute("switch", "");
|
|
@@ -414,7 +346,12 @@ export class PreferencesPanel {
|
|
|
414
346
|
growingInputs.push(grow);
|
|
415
347
|
}
|
|
416
348
|
eventName = "input";
|
|
417
|
-
if (!multiline)
|
|
349
|
+
if (!multiline) {
|
|
350
|
+
input.type = field.type === "number" ? "number" : "text";
|
|
351
|
+
input.autocapitalize = "off";
|
|
352
|
+
input.setAttribute("autocorrect", "off");
|
|
353
|
+
input.spellcheck = false;
|
|
354
|
+
}
|
|
418
355
|
write = value => {
|
|
419
356
|
input.value = field.type === "array" ? JSON.stringify(value ?? []) : (value ?? "");
|
|
420
357
|
grow();
|
|
@@ -468,12 +405,12 @@ export class PreferencesPanel {
|
|
|
468
405
|
if (eventName === "input") inputContainer.addEventListener("compositionend", event => event.target.dispatchEvent(new window.Event("input", { bubbles: true })));
|
|
469
406
|
groups.get(group).append(row);
|
|
470
407
|
}
|
|
471
|
-
const settingsPage = node("section", "pp-settings-page");
|
|
408
|
+
const settingsPage = node("section", "pp-panel pp-detail pp-settings-page");
|
|
472
409
|
const settingsOutput = node("pre", "pp-cache");
|
|
473
410
|
settingsOutput.setAttribute("aria-label", "Settings 内容");
|
|
474
411
|
settingsPage.append(settingsOutput);
|
|
475
|
-
|
|
476
|
-
|
|
412
|
+
const loadSettings = () => {
|
|
413
|
+
editors.get("$settings").load = undefined;
|
|
477
414
|
if (saving) return;
|
|
478
415
|
let value;
|
|
479
416
|
return perform(
|
|
@@ -487,17 +424,24 @@ export class PreferencesPanel {
|
|
|
487
424
|
},
|
|
488
425
|
() => {
|
|
489
426
|
settingsOutput.textContent = value === undefined ? "暂无设置" : JSON.stringify(value, null, 2);
|
|
490
|
-
|
|
427
|
+
},
|
|
428
|
+
() => {
|
|
429
|
+
settingsOutput.textContent = "读取设置失败";
|
|
491
430
|
},
|
|
492
431
|
);
|
|
432
|
+
};
|
|
433
|
+
editors.set("$settings", { node: settingsPage, title: "设置", load: loadSettings });
|
|
434
|
+
handlers.set("viewSettings", () => {
|
|
435
|
+
loadSettings();
|
|
436
|
+
page.open("$settings");
|
|
493
437
|
});
|
|
494
|
-
const cachePage = node("section", "pp-cache-page");
|
|
438
|
+
const cachePage = node("section", "pp-panel pp-detail pp-cache-page");
|
|
495
439
|
const output = node("pre", "pp-cache");
|
|
496
|
-
output.textContent = "
|
|
440
|
+
output.textContent = "正在读取缓存…";
|
|
497
441
|
output.setAttribute("aria-label", "Caches 内容");
|
|
498
442
|
cachePage.append(output);
|
|
499
|
-
|
|
500
|
-
|
|
443
|
+
const loadCaches = () => {
|
|
444
|
+
editors.get("$caches").load = undefined;
|
|
501
445
|
if (saving) return;
|
|
502
446
|
let value;
|
|
503
447
|
return perform(
|
|
@@ -511,13 +455,22 @@ export class PreferencesPanel {
|
|
|
511
455
|
},
|
|
512
456
|
() => {
|
|
513
457
|
output.textContent = value === undefined ? "暂无缓存" : JSON.stringify(value, null, 2);
|
|
514
|
-
|
|
458
|
+
},
|
|
459
|
+
() => {
|
|
460
|
+
output.textContent = "读取缓存失败";
|
|
515
461
|
},
|
|
516
462
|
);
|
|
463
|
+
};
|
|
464
|
+
editors.set("$caches", { node: cachePage, title: "缓存", load: loadCaches });
|
|
465
|
+
handlers.set("viewCaches", () => {
|
|
466
|
+
loadCaches();
|
|
467
|
+
page.open("$caches");
|
|
517
468
|
});
|
|
518
469
|
handlers.set("clearCaches", async () => {
|
|
519
470
|
if (saving) return;
|
|
520
|
-
|
|
471
|
+
const shared = definition.cachePath && definition.cachePath !== `@${definition.storageKey}.${active}.Caches`;
|
|
472
|
+
const message = shared ? `清空共享缓存 ${definition.cachePath}?使用该缓存的其它模块也会受影响。` : `清空 ${active} 的全部 Caches?`;
|
|
473
|
+
if (!(await page.confirm(message)) || destroyed || saving) return;
|
|
521
474
|
return perform(
|
|
522
475
|
() => client.clearCaches(),
|
|
523
476
|
() => {
|
|
@@ -527,42 +480,58 @@ export class PreferencesPanel {
|
|
|
527
480
|
});
|
|
528
481
|
handlers.set("reset", async () => {
|
|
529
482
|
if (saving) return;
|
|
530
|
-
|
|
483
|
+
const message = definition.resetPaths ? `重置 ${definition.metadata?.name ?? active} 的设置?只删除本页面的设置,保留共享存储中的其它设置和缓存。` : `重置 ${active} 的设置?这将删除该模块的 Settings、Caches 和其它持久化数据。`;
|
|
484
|
+
if (!(await page.confirm(message)) || destroyed || saving) return;
|
|
531
485
|
return perform(() => client.reset(), controls);
|
|
532
486
|
});
|
|
533
|
-
|
|
534
|
-
navigation = new Navigation(viewport, view, key => editors.get(key)?.node);
|
|
535
|
-
navigation.addEventListener("change", updateNavigation);
|
|
487
|
+
viewport.replaceChildren(view);
|
|
536
488
|
for (const grow of growingInputs) grow();
|
|
537
|
-
|
|
489
|
+
page.refresh();
|
|
538
490
|
}
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
* @returns {void} 无返回值 / No return value.
|
|
543
|
-
*/
|
|
544
|
-
if (back)
|
|
545
|
-
back.onclick = () => {
|
|
546
|
-
if (saving) return;
|
|
547
|
-
if (navigation) navigation.back();
|
|
548
|
-
else window.history.back();
|
|
549
|
-
};
|
|
550
|
-
open(definition.module);
|
|
491
|
+
queueMicrotask(() => {
|
|
492
|
+
if (!destroyed) open(definition.module);
|
|
493
|
+
});
|
|
551
494
|
return () => {
|
|
552
495
|
destroyed = true;
|
|
553
|
-
menu?.destroy();
|
|
554
|
-
frame?.removeEventListener("preferencepanes:action", onAction);
|
|
555
|
-
navigation?.destroy();
|
|
556
496
|
generation++;
|
|
557
|
-
|
|
558
|
-
clearTimeout(timer);
|
|
497
|
+
client.leave();
|
|
559
498
|
shell.remove();
|
|
560
499
|
};
|
|
561
500
|
}
|
|
562
501
|
|
|
563
502
|
/**
|
|
564
|
-
*
|
|
565
|
-
*
|
|
503
|
+
* 返回当前会话中的字段或操作页,不再读取配置。
|
|
504
|
+
* Return a field or action view from the current session without fetching configuration.
|
|
505
|
+
* @param {string} key 子页键 / Detail key.
|
|
506
|
+
* @returns {HTMLElement} 当前视图 / Current view.
|
|
507
|
+
*/
|
|
508
|
+
view(key) {
|
|
509
|
+
return this.#view(key);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* 提供当前页标题及菜单状态。
|
|
514
|
+
* Provide the current title and action state.
|
|
515
|
+
* @param {string} key 子页键 / Detail key.
|
|
516
|
+
* @returns {object} 页面状态 / Page state.
|
|
517
|
+
*/
|
|
518
|
+
state(key) {
|
|
519
|
+
return this.#state(key);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* 执行共用菜单中的操作。
|
|
524
|
+
* Perform an action from the shared menu.
|
|
525
|
+
* @param {string} id 操作标识 / Action identifier.
|
|
526
|
+
* @returns {void} 无返回值 / No return value.
|
|
527
|
+
*/
|
|
528
|
+
perform(id) {
|
|
529
|
+
this.#perform(id);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* 释放读取与视图;已开始的写入仍沿原队列完成。
|
|
534
|
+
* Release reads and views while allowing started writes to finish through the existing queue.
|
|
566
535
|
* @returns {void} 无返回值 / No return value.
|
|
567
536
|
*/
|
|
568
537
|
destroy() {
|