dsh-plugin-runcat-inventory 0.3.7
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.en.md +223 -0
- package/CHANGELOG.md +193 -0
- package/LICENSE +21 -0
- package/README.en.md +120 -0
- package/README.md +120 -0
- package/assets/preview-en.png +0 -0
- package/assets/preview-zh.png +0 -0
- package/cordis.patch.yml +5 -0
- package/lib/client.js +602 -0
- package/lib/index.js +329 -0
- package/package.json +58 -0
package/lib/client.js
ADDED
|
@@ -0,0 +1,602 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-plugin-runcat-inventory —— 浏览器半端(逃咪-插件总览 / Runcat Plugin Overview)
|
|
3
|
+
*
|
|
4
|
+
* 手写 ModuleLoader bundle(无构建步骤,与 dsh-plugins-market 同款写法):
|
|
5
|
+
* - 在 设置 → 插件 的 settings.plugins.tab 槽位注册一个选项卡;
|
|
6
|
+
* - 国际化:通过 DSH 内置 locale 服务注册 zh/en 字典,界面语言跟随
|
|
7
|
+
* DSH 环境(浏览器语言 + 设置 → 通用 → Language)自动切换;
|
|
8
|
+
* - 数据全部来自同源 fetch → host 半端的 /runcat-api 路由(host 返回
|
|
9
|
+
* 错误码与来源 kind,显示文案由本端字典翻译);
|
|
10
|
+
* - 表格视图(4 列):名称(含 id+版本) / 状态 / 来源 / 操作;
|
|
11
|
+
* - 操作:启用/停用(热生效)、复制名称、详情(展开描述+配置)、复制配置;
|
|
12
|
+
* - 搜索框(联合搜索)+ 状态过滤 + 排序(配置顺序/名称↑↓);
|
|
13
|
+
* - 键盘 ←/→ 横向滚动;窄屏隐藏"来源"列。
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
window.__ModuleLoader__.load({
|
|
17
|
+
id: "dsh-plugin-runcat-inventory",
|
|
18
|
+
factory: (require) => {
|
|
19
|
+
var module = { exports: {} };
|
|
20
|
+
var exports = module.exports;
|
|
21
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
22
|
+
var React = require("react");
|
|
23
|
+
|
|
24
|
+
// ── 字典命名空间(locale 服务按此注册/绑定)──────────────────────
|
|
25
|
+
var NS = "settings.runcatInventory";
|
|
26
|
+
|
|
27
|
+
// ── 样式:注入 <style>,全部走主题 CSS 变量 ────────────────────────
|
|
28
|
+
var CSS = [
|
|
29
|
+
".rci-panel{display:flex;flex-direction:column;gap:10px;padding:2px 0 24px}",
|
|
30
|
+
".rci-toolbar{display:flex;align-items:center;gap:8px;flex-wrap:wrap}",
|
|
31
|
+
".rci-search{flex:1;min-width:160px;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-primary);border-radius:6px;padding:6px 10px;font-size:12px}",
|
|
32
|
+
".rci-search:focus{border-color:var(--dsw-alias-brand-primary);outline:none}",
|
|
33
|
+
".rci-select{background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-primary);border-radius:6px;padding:5px 8px;font-size:12px;cursor:pointer}",
|
|
34
|
+
".rci-btn{appearance:none;border:1px solid var(--dsw-alias-border-l1);background:transparent;color:var(--dsw-alias-label-primary);border-radius:6px;padding:4px 10px;font-size:12px;cursor:pointer;white-space:nowrap}",
|
|
35
|
+
".rci-btn:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,var(--dsw-alias-bg-layer-2))}",
|
|
36
|
+
".rci-btn:disabled{opacity:.5;cursor:default}",
|
|
37
|
+
".rci-btn.primary{background:var(--dsw-alias-button-primary-fill,var(--dsw-alias-label-primary));color:var(--dsw-alias-label-primary-foreground,var(--dsw-alias-bg-layer-3));border-color:transparent}",
|
|
38
|
+
".rci-btn.primary:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover,var(--dsw-alias-button-primary-fill))}",
|
|
39
|
+
".rci-btn.danger:hover:not(:disabled){border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}",
|
|
40
|
+
".rci-status{color:var(--dsw-alias-label-tertiary);font-size:12px}",
|
|
41
|
+
".rci-error{color:var(--dsw-alias-state-error-primary);font-size:12px}",
|
|
42
|
+
".rci-ok{color:var(--dsw-alias-state-success-primary);font-size:12px}",
|
|
43
|
+
// 表格容器:可横向滚动 + 键盘聚焦样式
|
|
44
|
+
".rci-tablewrap{overflow-x:auto;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-3)}",
|
|
45
|
+
".rci-tablewrap:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px;border-radius:10px}",
|
|
46
|
+
// 表格:固定布局 + 比例列宽(colgroup:名称36% / 状态15% / 来源21% / 操作28%)
|
|
47
|
+
".rci-table{width:100%;table-layout:fixed;border-collapse:separate;border-spacing:0;font-size:12px;min-width:560px}",
|
|
48
|
+
".rci-table th{color:var(--dsw-alias-label-tertiary);font-weight:500;text-align:left;padding:8px 12px;border-bottom:1px solid var(--dsw-alias-border-l2);white-space:nowrap;font-size:11px;background:var(--dsw-alias-bg-layer-3)}",
|
|
49
|
+
".rci-table td{padding:8px 12px;border-bottom:1px solid var(--dsw-alias-border-l1);vertical-align:top;background:var(--dsw-alias-bg-layer-3)}",
|
|
50
|
+
".rci-table tr:last-child td{border-bottom:none}",
|
|
51
|
+
".rci-table tr:hover td{background:var(--dsw-alias-interactive-bg-hover,var(--dsw-alias-bg-layer-2))}",
|
|
52
|
+
// 展开行:底色区分(无粘性列,无需特殊定位)
|
|
53
|
+
".rci-table td.rci-expandcell{background:var(--dsw-alias-bg-layer-2)}",
|
|
54
|
+
".rci-table tr.rci-expandrow:hover td.rci-expandcell{background:var(--dsw-alias-bg-layer-2)}",
|
|
55
|
+
".rci-name{color:var(--dsw-alias-label-primary);font-weight:600;word-break:break-all}",
|
|
56
|
+
".rci-id{color:var(--dsw-alias-label-tertiary);font-size:11px;word-break:break-all;margin-top:2px}",
|
|
57
|
+
".rci-version{color:var(--dsw-alias-label-tertiary);font-size:11px;word-break:break-all;margin-top:2px}",
|
|
58
|
+
".rci-muted{color:var(--dsw-alias-label-tertiary)}",
|
|
59
|
+
// 来源内容:强制断行,避免长 URL 溢出到相邻列
|
|
60
|
+
".rci-source{word-break:break-all;overflow-wrap:anywhere}",
|
|
61
|
+
".rci-badge{display:inline-block;font-size:10px;line-height:1;padding:3px 6px;border-radius:4px;white-space:nowrap}",
|
|
62
|
+
".rci-badge.on{background:color-mix(in srgb,var(--dsw-alias-state-success-primary) 14%,transparent);color:var(--dsw-alias-state-success-primary)}",
|
|
63
|
+
".rci-badge.off{background:color-mix(in srgb,var(--dsw-alias-state-warn-primary) 14%,transparent);color:var(--dsw-alias-state-warn-primary)}",
|
|
64
|
+
".rci-badge.active{background:color-mix(in srgb,var(--dsw-alias-state-success-primary) 14%,transparent);color:var(--dsw-alias-state-success-primary)}",
|
|
65
|
+
".rci-badge.pending,.rci-badge.loading{background:color-mix(in srgb,var(--dsw-alias-state-warn-primary) 14%,transparent);color:var(--dsw-alias-state-warn-primary)}",
|
|
66
|
+
".rci-badge.failed{background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 14%,transparent);color:var(--dsw-alias-state-error-primary)}",
|
|
67
|
+
".rci-badge.muted{background:var(--dsw-alias-bg-module-platform,var(--dsw-alias-bg-layer-2));color:var(--dsw-alias-label-tertiary)}",
|
|
68
|
+
".rci-actions{display:flex;gap:6px;align-items:center;flex-wrap:wrap}",
|
|
69
|
+
".rci-exp-sec{display:flex;flex-direction:column;gap:6px;margin:4px 0}",
|
|
70
|
+
".rci-exp-title{color:var(--dsw-alias-label-tertiary);font-size:11px;font-weight:600}",
|
|
71
|
+
".rci-exp-text{color:var(--dsw-alias-label-secondary);font-size:12px;white-space:pre-wrap;word-break:break-all}",
|
|
72
|
+
".rci-config{background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);border-radius:6px;padding:8px;margin:0;font-size:11px;overflow:auto;max-height:220px;white-space:pre-wrap;word-break:break-all;color:var(--dsw-alias-label-secondary);font-family:ui-monospace,SFMono-Regular,Consolas,monospace}",
|
|
73
|
+
".rci-empty{color:var(--dsw-alias-label-tertiary);font-size:12px;padding:16px;text-align:center}",
|
|
74
|
+
// 窄屏:隐藏"来源"列
|
|
75
|
+
"@media (max-width:900px){.rci-hide-narrow{display:none}}",
|
|
76
|
+
].join("");
|
|
77
|
+
var tagId = "dsh-plugin-runcat-inventory/styles";
|
|
78
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
79
|
+
var tag = document.createElement("style");
|
|
80
|
+
tag.dataset.plugin = "dsh-plugin-runcat-inventory";
|
|
81
|
+
tag.dataset.pluginCss = tagId;
|
|
82
|
+
tag.textContent = CSS;
|
|
83
|
+
document.head.appendChild(tag);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ── 双语字典 ───────────────────────────────────────────────────────
|
|
87
|
+
/** 简体中文(键值对即 key 源)。 */
|
|
88
|
+
var zh = {
|
|
89
|
+
tab: "逃咪-插件总览",
|
|
90
|
+
colName: "名称",
|
|
91
|
+
colStatus: "状态",
|
|
92
|
+
colSource: "来源",
|
|
93
|
+
colActions: "操作",
|
|
94
|
+
searchPlaceholder: "搜索名称 / id / 描述 / 来源…",
|
|
95
|
+
filterAll: "全部状态",
|
|
96
|
+
filterEnabled: "已启用",
|
|
97
|
+
filterDisabled: "已停用",
|
|
98
|
+
filterActive: "已挂载",
|
|
99
|
+
filterWaiting: "等待/加载中",
|
|
100
|
+
filterFailed: "挂载失败",
|
|
101
|
+
sortConfig: "配置顺序",
|
|
102
|
+
sortNameAsc: "名称 ↑",
|
|
103
|
+
sortNameDesc: "名称 ↓",
|
|
104
|
+
refresh: "刷新",
|
|
105
|
+
count: "共 {total} 个插件",
|
|
106
|
+
countMatched: ",匹配 {matched}",
|
|
107
|
+
loading: "正在读取插件…",
|
|
108
|
+
loadFailed: "读取失败:{msg}",
|
|
109
|
+
emptyNone: "暂无插件。",
|
|
110
|
+
emptyNoMatch: "没有匹配的插件。",
|
|
111
|
+
enabled: "已启用",
|
|
112
|
+
disabled: "已停用",
|
|
113
|
+
phaseTitle: "Cordis 状态",
|
|
114
|
+
phaseActive: "已挂载",
|
|
115
|
+
phasePending: "等待依赖",
|
|
116
|
+
phaseLoading: "加载中",
|
|
117
|
+
phaseFailed: "挂载失败",
|
|
118
|
+
phaseUnloading: "卸载中",
|
|
119
|
+
phaseUnobserved: "未挂载",
|
|
120
|
+
toggleOn: "停用",
|
|
121
|
+
toggleOff: "启用",
|
|
122
|
+
toggling: "处理中…",
|
|
123
|
+
copyName: "复制名称",
|
|
124
|
+
copied: "已复制",
|
|
125
|
+
details: "详情",
|
|
126
|
+
collapse: "收起",
|
|
127
|
+
descTitle: "描述",
|
|
128
|
+
configTitle: "配置",
|
|
129
|
+
copyConfig: "复制配置",
|
|
130
|
+
// 本插件自身的描述(随界面语言切换;package.json description 保持中文作为 npm 元数据)
|
|
131
|
+
selfDescription: "逃咪-插件总览:更好用的 DSH 插件列表 —— 表格视图、状态过滤、启用/停用开关(热生效)、配置查看与复制。",
|
|
132
|
+
none: "—",
|
|
133
|
+
ariaScroll: "插件列表(可用左右方向键横向滚动)",
|
|
134
|
+
toggleOk: "已{action} {name}(热生效中,刷新后确认状态)",
|
|
135
|
+
toggleDisable: "停用",
|
|
136
|
+
toggleEnable: "启用",
|
|
137
|
+
toggleFail: "切换失败:{msg}",
|
|
138
|
+
sourceLink: "本地链接 (link)",
|
|
139
|
+
sourceFile: "本地路径 (file)",
|
|
140
|
+
sourceGithub: "GitHub 安装",
|
|
141
|
+
sourceNpm: "npm 包",
|
|
142
|
+
sourceBuiltin: "内置(随 DSH 安装)",
|
|
143
|
+
sourceOther: "其他",
|
|
144
|
+
errMissingParams: "缺少 id 或 name",
|
|
145
|
+
errProfileDir: "无法定位 profile 目录",
|
|
146
|
+
errPatchRead: "无法读取 cordis.patch.yml",
|
|
147
|
+
errPatchParse: "无法解析 cordis.patch.yml(可能含 !!js 表达式,请手动处理)",
|
|
148
|
+
errPatchNotArray: "cordis.patch.yml 顶层必须是数组",
|
|
149
|
+
errYamlUnavailable: "js-yaml 不可用",
|
|
150
|
+
errYamlDump: "序列化失败",
|
|
151
|
+
errPatchWrite: "写入失败",
|
|
152
|
+
errBadRequest: "请求不合法",
|
|
153
|
+
errBadJson: "请求体不是合法 JSON",
|
|
154
|
+
errInventory: "读取插件清单失败",
|
|
155
|
+
errForbidden: "请求被拒绝",
|
|
156
|
+
errInternal: "内部错误",
|
|
157
|
+
errUnknown: "未知错误",
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
/** English dictionary checked against the Chinese key set. */
|
|
161
|
+
var en = {
|
|
162
|
+
tab: "Runcat Plugin Overview",
|
|
163
|
+
colName: "Name",
|
|
164
|
+
colStatus: "Status",
|
|
165
|
+
colSource: "Source",
|
|
166
|
+
colActions: "Actions",
|
|
167
|
+
searchPlaceholder: "Search name / id / description / source…",
|
|
168
|
+
filterAll: "All states",
|
|
169
|
+
filterEnabled: "Enabled",
|
|
170
|
+
filterDisabled: "Disabled",
|
|
171
|
+
filterActive: "Mounted",
|
|
172
|
+
filterWaiting: "Waiting / loading",
|
|
173
|
+
filterFailed: "Mount failed",
|
|
174
|
+
sortConfig: "Config order",
|
|
175
|
+
sortNameAsc: "Name ↑",
|
|
176
|
+
sortNameDesc: "Name ↓",
|
|
177
|
+
refresh: "Refresh",
|
|
178
|
+
count: "{total} plugins",
|
|
179
|
+
countMatched: " ({matched} matched)",
|
|
180
|
+
loading: "Loading plugins…",
|
|
181
|
+
loadFailed: "Failed to load: {msg}",
|
|
182
|
+
emptyNone: "No plugins.",
|
|
183
|
+
emptyNoMatch: "No matching plugins.",
|
|
184
|
+
enabled: "Enabled",
|
|
185
|
+
disabled: "Disabled",
|
|
186
|
+
phaseTitle: "Cordis status",
|
|
187
|
+
phaseActive: "Mounted",
|
|
188
|
+
phasePending: "Waiting for deps",
|
|
189
|
+
phaseLoading: "Loading",
|
|
190
|
+
phaseFailed: "Mount failed",
|
|
191
|
+
phaseUnloading: "Unloading",
|
|
192
|
+
phaseUnobserved: "Not mounted",
|
|
193
|
+
toggleOn: "Disable",
|
|
194
|
+
toggleOff: "Enable",
|
|
195
|
+
toggling: "Working…",
|
|
196
|
+
copyName: "Copy name",
|
|
197
|
+
copied: "Copied",
|
|
198
|
+
details: "Details",
|
|
199
|
+
collapse: "Collapse",
|
|
200
|
+
descTitle: "Description",
|
|
201
|
+
configTitle: "Config",
|
|
202
|
+
copyConfig: "Copy config",
|
|
203
|
+
selfDescription: "Runcat Plugin Overview: a better DSH plugin list — table view, status filters, enable/disable switches (hot-applied), config viewer and copy.",
|
|
204
|
+
none: "—",
|
|
205
|
+
ariaScroll: "Plugin list (use ←/→ arrow keys to scroll)",
|
|
206
|
+
toggleOk: "{action} {name} (applying live; refresh to confirm)",
|
|
207
|
+
toggleDisable: "Disabled",
|
|
208
|
+
toggleEnable: "Enabled",
|
|
209
|
+
toggleFail: "Toggle failed: {msg}",
|
|
210
|
+
sourceLink: "Local link",
|
|
211
|
+
sourceFile: "Local path",
|
|
212
|
+
sourceGithub: "GitHub install",
|
|
213
|
+
sourceNpm: "npm package",
|
|
214
|
+
sourceBuiltin: "Built-in (ships with DSH)",
|
|
215
|
+
sourceOther: "Other",
|
|
216
|
+
errMissingParams: "Missing id or name",
|
|
217
|
+
errProfileDir: "Cannot locate the profile directory",
|
|
218
|
+
errPatchRead: "Cannot read cordis.patch.yml",
|
|
219
|
+
errPatchParse: "Cannot parse cordis.patch.yml (may contain !!js expressions; edit manually)",
|
|
220
|
+
errPatchNotArray: "cordis.patch.yml top level must be an array",
|
|
221
|
+
errYamlUnavailable: "js-yaml is unavailable",
|
|
222
|
+
errYamlDump: "Serialization failed",
|
|
223
|
+
errPatchWrite: "Write failed",
|
|
224
|
+
errBadRequest: "Bad request",
|
|
225
|
+
errBadJson: "Request body is not valid JSON",
|
|
226
|
+
errInventory: "Failed to read the plugin inventory",
|
|
227
|
+
errForbidden: "Request rejected",
|
|
228
|
+
errInternal: "Internal error",
|
|
229
|
+
errUnknown: "Unknown error",
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
// ── React 小工具 ───────────────────────────────────────────────────
|
|
233
|
+
function el(type, props) {
|
|
234
|
+
var children = Array.prototype.slice.call(arguments, 2);
|
|
235
|
+
return React.createElement.apply(React, [type, props].concat(children));
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** 简易模板替换:tr(t, "count", {total: 3}) → "共 3 个插件"。 */
|
|
239
|
+
function tr(t, key, vars) {
|
|
240
|
+
var s = t(key);
|
|
241
|
+
if (!vars) return s;
|
|
242
|
+
for (var k in vars) s = s.replace("{" + k + "}", String(vars[k]));
|
|
243
|
+
return s;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function api(path, options) {
|
|
247
|
+
return window.fetch(path, options).then(function (res) {
|
|
248
|
+
return res.json().catch(function () {
|
|
249
|
+
return { ok: false, code: "BAD_RESPONSE", detail: "HTTP " + res.status };
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
var PHASE_KEY = {
|
|
255
|
+
active: "phaseActive",
|
|
256
|
+
pending: "phasePending",
|
|
257
|
+
loading: "phaseLoading",
|
|
258
|
+
failed: "phaseFailed",
|
|
259
|
+
unloading: "phaseUnloading",
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
var SORTS = [
|
|
263
|
+
["config", "sortConfig"],
|
|
264
|
+
["name-asc", "sortNameAsc"],
|
|
265
|
+
["name-desc", "sortNameDesc"],
|
|
266
|
+
];
|
|
267
|
+
|
|
268
|
+
/** host 错误码 → 字典 key。 */
|
|
269
|
+
var ERR_KEY = {
|
|
270
|
+
MISSING_PARAMS: "errMissingParams",
|
|
271
|
+
PROFILE_DIR_NOT_FOUND: "errProfileDir",
|
|
272
|
+
PATCH_READ_FAILED: "errPatchRead",
|
|
273
|
+
PATCH_PARSE_FAILED: "errPatchParse",
|
|
274
|
+
PATCH_NOT_ARRAY: "errPatchNotArray",
|
|
275
|
+
YAML_UNAVAILABLE: "errYamlUnavailable",
|
|
276
|
+
YAML_DUMP_FAILED: "errYamlDump",
|
|
277
|
+
PATCH_WRITE_FAILED: "errPatchWrite",
|
|
278
|
+
BAD_REQUEST: "errBadRequest",
|
|
279
|
+
BAD_JSON: "errBadJson",
|
|
280
|
+
INVENTORY_FAILED: "errInventory",
|
|
281
|
+
FORBIDDEN: "errForbidden",
|
|
282
|
+
INTERNAL: "errInternal",
|
|
283
|
+
BAD_RESPONSE: "errUnknown",
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
/** 判断配置是否"有内容":null/undefined/空对象/空数组都算没有。 */
|
|
287
|
+
function hasConfigContent(config) {
|
|
288
|
+
if (config === null || config === undefined) return false;
|
|
289
|
+
if (Array.isArray(config)) return config.length > 0;
|
|
290
|
+
if (typeof config === "object") return Object.keys(config).length > 0;
|
|
291
|
+
return true; // 标量(字符串/数字/布尔)也算有内容
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function copyText(text, done) {
|
|
295
|
+
if (typeof navigator !== "undefined" && navigator.clipboard && navigator.clipboard.writeText) {
|
|
296
|
+
navigator.clipboard.writeText(text).then(function () { done(true) }, function () { done(false) });
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
try {
|
|
300
|
+
var ta = document.createElement("textarea");
|
|
301
|
+
ta.value = text;
|
|
302
|
+
ta.style.position = "fixed";
|
|
303
|
+
ta.style.opacity = "0";
|
|
304
|
+
document.body.appendChild(ta);
|
|
305
|
+
ta.select();
|
|
306
|
+
var ok = document.execCommand && document.execCommand("copy");
|
|
307
|
+
document.body.removeChild(ta);
|
|
308
|
+
done(Boolean(ok));
|
|
309
|
+
} catch (e) {
|
|
310
|
+
done(false);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// ── 主面板(t 由槽位 inject 传入,见 apply)────────────────────────
|
|
315
|
+
function InventoryPanel(props) {
|
|
316
|
+
var t = (props && props.t) || function (key) { return key; };
|
|
317
|
+
var _state = React.useState({ loading: true, error: null, rows: [] });
|
|
318
|
+
var state = _state[0];
|
|
319
|
+
var setState = _state[1];
|
|
320
|
+
var _q = React.useState("");
|
|
321
|
+
var q = _q[0];
|
|
322
|
+
var setQ = _q[1];
|
|
323
|
+
var _filter = React.useState("all");
|
|
324
|
+
var filter = _filter[0];
|
|
325
|
+
var setFilter = _filter[1];
|
|
326
|
+
var _sort = React.useState("config");
|
|
327
|
+
var sort = _sort[0];
|
|
328
|
+
var setSort = _sort[1];
|
|
329
|
+
var _expanded = React.useState({});
|
|
330
|
+
var expanded = _expanded[0];
|
|
331
|
+
var setExpanded = _expanded[1];
|
|
332
|
+
var _copied = React.useState(null); // 复制反馈:'name:<id>' | 'config:<id>'
|
|
333
|
+
var copied = _copied[0];
|
|
334
|
+
var setCopied = _copied[1];
|
|
335
|
+
var _toggleId = React.useState(null); // 正在切换的 patchId
|
|
336
|
+
var toggleId = _toggleId[0];
|
|
337
|
+
var setToggleId = _toggleId[1];
|
|
338
|
+
var _msg = React.useState(null); // 操作结果反馈 {kind, text}
|
|
339
|
+
var msg = _msg[0];
|
|
340
|
+
var setMsg = _msg[1];
|
|
341
|
+
|
|
342
|
+
var load = function () {
|
|
343
|
+
setState(function (prev) { return { loading: true, error: null, rows: prev.rows }; });
|
|
344
|
+
api("/runcat-api/inventory").then(function (res) {
|
|
345
|
+
if (!res.ok) {
|
|
346
|
+
setState({ loading: false, error: tr(t, ERR_KEY[res.code] || "errUnknown"), rows: [] });
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
setState({ loading: false, error: null, rows: res.entries || [] });
|
|
350
|
+
}).catch(function (err) {
|
|
351
|
+
setState({ loading: false, error: tr(t, "loadFailed", { msg: String(err && err.message || err) }), rows: [] });
|
|
352
|
+
});
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
React.useEffect(function () { load(); }, []);
|
|
356
|
+
|
|
357
|
+
var toggle = function (row) {
|
|
358
|
+
setToggleId(row.patchId);
|
|
359
|
+
setMsg(null);
|
|
360
|
+
api("/runcat-api/set-enabled", {
|
|
361
|
+
method: "POST",
|
|
362
|
+
headers: { "Content-Type": "application/json" },
|
|
363
|
+
body: JSON.stringify({ id: row.patchId, name: row.name, enabled: !row.enabled }),
|
|
364
|
+
}).then(function (res) {
|
|
365
|
+
setToggleId(null);
|
|
366
|
+
if (res.ok) {
|
|
367
|
+
var action = row.enabled ? tr(t, "toggleDisable") : tr(t, "toggleEnable");
|
|
368
|
+
setMsg({ kind: "ok", text: tr(t, "toggleOk", { action: action, name: row.name }) });
|
|
369
|
+
setTimeout(load, 600); // 稍等 HMR 结算后刷新
|
|
370
|
+
} else {
|
|
371
|
+
setMsg({ kind: "err", text: tr(t, "toggleFail", { msg: tr(t, ERR_KEY[res.code] || "errUnknown") }) });
|
|
372
|
+
}
|
|
373
|
+
}).catch(function (err) {
|
|
374
|
+
setToggleId(null);
|
|
375
|
+
setMsg({ kind: "err", text: tr(t, "toggleFail", { msg: String(err && err.message || err) }) });
|
|
376
|
+
});
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
var copyName = function (row) {
|
|
380
|
+
copyText(row.name, function (ok) {
|
|
381
|
+
setCopied(ok ? "name:" + row.id : null);
|
|
382
|
+
setTimeout(function () { setCopied(null); }, 1500);
|
|
383
|
+
});
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
var copyConfig = function (row) {
|
|
387
|
+
copyText(JSON.stringify(row.config, null, 2), function (ok) {
|
|
388
|
+
setCopied(ok ? "config:" + row.id : null);
|
|
389
|
+
setTimeout(function () { setCopied(null); }, 1500);
|
|
390
|
+
});
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
var toggleExpand = function (row) {
|
|
394
|
+
setExpanded(function (prev) {
|
|
395
|
+
var next = {};
|
|
396
|
+
next[row.id] = !prev[row.id];
|
|
397
|
+
return Object.assign({}, prev, next);
|
|
398
|
+
});
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
// 过滤:关键字(名称/id/描述/来源/版本)+ 状态
|
|
402
|
+
var keyword = q.trim().toLowerCase();
|
|
403
|
+
var filtered = state.rows.filter(function (row) {
|
|
404
|
+
if (filter === "enabled" && !row.enabled) return false;
|
|
405
|
+
if (filter === "disabled" && row.enabled) return false;
|
|
406
|
+
if (filter === "failed" && row.fiberPhase !== "failed") return false;
|
|
407
|
+
if (filter === "waiting" && row.fiberPhase !== "pending" && row.fiberPhase !== "loading") return false;
|
|
408
|
+
if (filter === "active" && row.fiberPhase !== "active") return false;
|
|
409
|
+
if (keyword === "") return true;
|
|
410
|
+
var hay = [row.name, row.id, row.description, row.sourceKind, row.sourceSpec, row.version].join("\n").toLowerCase();
|
|
411
|
+
return hay.indexOf(keyword) !== -1;
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
// 排序:默认配置顺序;可选按名称
|
|
415
|
+
var rows = filtered.slice();
|
|
416
|
+
if (sort === "name-asc") {
|
|
417
|
+
rows.sort(function (a, b) { return a.name.localeCompare(b.name, "zh") });
|
|
418
|
+
} else if (sort === "name-desc") {
|
|
419
|
+
rows.sort(function (a, b) { return b.name.localeCompare(a.name, "zh") });
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
var phaseBadge = function (row) {
|
|
423
|
+
var key = row.fiberPhase === null ? "phaseUnobserved" : (PHASE_KEY[row.fiberPhase] || "phaseUnobserved");
|
|
424
|
+
var cls = row.fiberPhase === null ? "muted" : (PHASE_KEY[row.fiberPhase] ? (row.fiberPhase === "active" ? "active" : (row.fiberPhase === "failed" ? "failed" : (row.fiberPhase === "pending" || row.fiberPhase === "loading" ? row.fiberPhase : "muted"))) : "muted");
|
|
425
|
+
return el("span", { className: "rci-badge " + cls, title: t("phaseTitle") }, t(key));
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
var toggleBtn = function (row) {
|
|
429
|
+
var busy = toggleId === row.patchId;
|
|
430
|
+
return el("button", {
|
|
431
|
+
className: "rci-btn" + (row.enabled ? " danger" : " primary"),
|
|
432
|
+
disabled: busy,
|
|
433
|
+
onClick: function () { toggle(row); },
|
|
434
|
+
}, busy ? t("toggling") : t(row.enabled ? "toggleOn" : "toggleOff"));
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
var sourceText = function (row) {
|
|
438
|
+
// kind='repo':直接显示仓库 URL(无需标签前缀)
|
|
439
|
+
if (row.sourceKind === "repo") return row.sourceSpec || t("none");
|
|
440
|
+
var base = "";
|
|
441
|
+
if (row.sourceKind) {
|
|
442
|
+
base = t("source" + row.sourceKind.charAt(0).toUpperCase() + row.sourceKind.slice(1));
|
|
443
|
+
}
|
|
444
|
+
if (row.sourceSpec) base += " (" + row.sourceSpec + ")";
|
|
445
|
+
return base;
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
// 键盘 ←/→ 横向滚动(表格聚焦后)
|
|
449
|
+
var onTableKeyDown = function (ev) {
|
|
450
|
+
var box = ev.currentTarget;
|
|
451
|
+
if (ev.key === "ArrowRight") { box.scrollLeft += 120; ev.preventDefault(); }
|
|
452
|
+
else if (ev.key === "ArrowLeft") { box.scrollLeft -= 120; ev.preventDefault(); }
|
|
453
|
+
else if (ev.key === "Home") { box.scrollLeft = 0; ev.preventDefault(); }
|
|
454
|
+
else if (ev.key === "End") { box.scrollLeft = box.scrollWidth; ev.preventDefault(); }
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
var countText = tr(t, "count", { total: state.rows.length });
|
|
458
|
+
if (rows.length !== state.rows.length) countText += tr(t, "countMatched", { matched: rows.length });
|
|
459
|
+
|
|
460
|
+
return el("div", { className: "rci-panel" },
|
|
461
|
+
el("div", { className: "rci-toolbar" },
|
|
462
|
+
el("input", {
|
|
463
|
+
className: "rci-search",
|
|
464
|
+
placeholder: t("searchPlaceholder"),
|
|
465
|
+
value: q,
|
|
466
|
+
onChange: function (ev) { setQ(ev.target.value); },
|
|
467
|
+
}),
|
|
468
|
+
el("select", {
|
|
469
|
+
className: "rci-select",
|
|
470
|
+
value: filter,
|
|
471
|
+
onChange: function (ev) { setFilter(ev.target.value); },
|
|
472
|
+
},
|
|
473
|
+
el("option", { value: "all" }, t("filterAll")),
|
|
474
|
+
el("option", { value: "enabled" }, t("filterEnabled")),
|
|
475
|
+
el("option", { value: "disabled" }, t("filterDisabled")),
|
|
476
|
+
el("option", { value: "active" }, t("filterActive")),
|
|
477
|
+
el("option", { value: "waiting" }, t("filterWaiting")),
|
|
478
|
+
el("option", { value: "failed" }, t("filterFailed")),
|
|
479
|
+
),
|
|
480
|
+
el("select", {
|
|
481
|
+
className: "rci-select",
|
|
482
|
+
value: sort,
|
|
483
|
+
onChange: function (ev) { setSort(ev.target.value); },
|
|
484
|
+
}, SORTS.map(function (s) {
|
|
485
|
+
return el("option", { value: s[0], key: s[0] }, t(s[1]));
|
|
486
|
+
})),
|
|
487
|
+
el("button", { className: "rci-btn", onClick: load, disabled: state.loading }, t("refresh")),
|
|
488
|
+
el("span", { className: "rci-status" }, countText),
|
|
489
|
+
),
|
|
490
|
+
state.loading && state.rows.length === 0
|
|
491
|
+
? el("div", { className: "rci-status" }, t("loading"))
|
|
492
|
+
: state.error
|
|
493
|
+
? el("div", { className: "rci-error" }, state.error)
|
|
494
|
+
: rows.length === 0
|
|
495
|
+
? el("div", { className: "rci-empty" }, state.rows.length === 0 ? t("emptyNone") : t("emptyNoMatch"))
|
|
496
|
+
: el("div", {
|
|
497
|
+
className: "rci-tablewrap",
|
|
498
|
+
tabIndex: 0,
|
|
499
|
+
onKeyDown: onTableKeyDown,
|
|
500
|
+
"aria-label": t("ariaScroll"),
|
|
501
|
+
},
|
|
502
|
+
el("table", { className: "rci-table" },
|
|
503
|
+
el("colgroup", null,
|
|
504
|
+
el("col", { style: { width: "36%" } }),
|
|
505
|
+
el("col", { style: { width: "15%", minWidth: "100px" } }),
|
|
506
|
+
el("col", { style: { width: "21%" } }),
|
|
507
|
+
el("col", { style: { width: "28%", minWidth: "170px" } }),
|
|
508
|
+
),
|
|
509
|
+
el("thead", null,
|
|
510
|
+
el("tr", null,
|
|
511
|
+
el("th", null, t("colName")),
|
|
512
|
+
el("th", null, t("colStatus")),
|
|
513
|
+
el("th", { className: "rci-hide-narrow" }, t("colSource")),
|
|
514
|
+
el("th", null, t("colActions")),
|
|
515
|
+
),
|
|
516
|
+
),
|
|
517
|
+
el("tbody", null, rows.map(function (row) {
|
|
518
|
+
var isOpen = Boolean(expanded[row.id]);
|
|
519
|
+
var hasConfig = hasConfigContent(row.config);
|
|
520
|
+
var configText = hasConfig ? JSON.stringify(row.config, null, 2) : "";
|
|
521
|
+
// 本插件的描述随界面语言切换(字典里的 selfDescription);其余插件显示 package.json 原文
|
|
522
|
+
var descText = row.name === "dsh-plugin-runcat-inventory" ? t("selfDescription") : row.description;
|
|
523
|
+
var expandable = descText !== "" || hasConfig;
|
|
524
|
+
var src = sourceText(row);
|
|
525
|
+
return [
|
|
526
|
+
el("tr", { key: row.id },
|
|
527
|
+
el("td", null,
|
|
528
|
+
el("div", { className: "rci-name" }, row.name),
|
|
529
|
+
el("div", { className: "rci-id" }, row.id),
|
|
530
|
+
el("div", { className: "rci-version" }, row.version ? "v" + row.version : t("none")),
|
|
531
|
+
),
|
|
532
|
+
el("td", null,
|
|
533
|
+
el("div", null, el("span", { className: "rci-badge " + (row.enabled ? "on" : "off") }, t(row.enabled ? "enabled" : "disabled"))),
|
|
534
|
+
el("div", { style: { marginTop: 4 } }, phaseBadge(row)),
|
|
535
|
+
),
|
|
536
|
+
el("td", { className: "rci-hide-narrow" }, src ? el("span", { className: "rci-source" }, src) : el("span", { className: "rci-muted" }, t("none"))),
|
|
537
|
+
el("td", null,
|
|
538
|
+
el("div", { className: "rci-actions" },
|
|
539
|
+
toggleBtn(row),
|
|
540
|
+
el("button", { className: "rci-btn", onClick: function () { copyName(row); } }, copied === "name:" + row.id ? t("copied") : t("copyName")),
|
|
541
|
+
expandable
|
|
542
|
+
? el("button", { className: "rci-btn", onClick: function () { toggleExpand(row); } }, isOpen ? t("collapse") : t("details"))
|
|
543
|
+
: null,
|
|
544
|
+
),
|
|
545
|
+
),
|
|
546
|
+
),
|
|
547
|
+
isOpen && expandable
|
|
548
|
+
? el("tr", { key: row.id + "-exp", className: "rci-expandrow" },
|
|
549
|
+
el("td", { colSpan: 4, className: "rci-expandcell" },
|
|
550
|
+
descText
|
|
551
|
+
? el("div", { className: "rci-exp-sec" },
|
|
552
|
+
el("div", { className: "rci-exp-title" }, t("descTitle")),
|
|
553
|
+
el("div", { className: "rci-exp-text" }, descText),
|
|
554
|
+
)
|
|
555
|
+
: null,
|
|
556
|
+
hasConfig
|
|
557
|
+
? el("div", { className: "rci-exp-sec" },
|
|
558
|
+
el("div", { className: "rci-exp-title" }, t("configTitle")),
|
|
559
|
+
el("div", { className: "rci-actions" },
|
|
560
|
+
el("button", { className: "rci-btn", onClick: function () { copyConfig(row); } }, copied === "config:" + row.id ? t("copied") : t("copyConfig")),
|
|
561
|
+
),
|
|
562
|
+
el("pre", { className: "rci-config" }, configText),
|
|
563
|
+
)
|
|
564
|
+
: null,
|
|
565
|
+
),
|
|
566
|
+
)
|
|
567
|
+
: null,
|
|
568
|
+
];
|
|
569
|
+
})),
|
|
570
|
+
),
|
|
571
|
+
),
|
|
572
|
+
msg ? el("div", { className: msg.kind === "ok" ? "rci-ok" : "rci-error" }, msg.text) : null,
|
|
573
|
+
);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// ── 注册选项卡(locale 双语 + 官方同款扩展点)─────────────────────
|
|
577
|
+
function apply(ctx) {
|
|
578
|
+
ctx.effect(function () {
|
|
579
|
+
return ctx.locale.register(NS, { zh: zh, en: en });
|
|
580
|
+
}, "runcat-inventory: dictionaries");
|
|
581
|
+
var t = ctx.locale.bind(NS);
|
|
582
|
+
ctx.slots.inject("settings.plugins.tab", function () {
|
|
583
|
+
return ctx.slots.register(
|
|
584
|
+
{
|
|
585
|
+
name: "settings.plugins.tab",
|
|
586
|
+
id: "runcat-inventory",
|
|
587
|
+
order: 15,
|
|
588
|
+
locale: NS,
|
|
589
|
+
label: function () { return t("tab"); },
|
|
590
|
+
inject: function () { return { t: ctx.locale.bind(NS) }; },
|
|
591
|
+
},
|
|
592
|
+
function (props) { return el(InventoryPanel, props); },
|
|
593
|
+
);
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
var inject = ["slots", "locale"];
|
|
598
|
+
exports.apply = apply;
|
|
599
|
+
exports.inject = inject;
|
|
600
|
+
return module.exports;
|
|
601
|
+
},
|
|
602
|
+
});
|