@max-null/dsh-plugin-center 0.1.7 → 0.2.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/client.js CHANGED
@@ -30,6 +30,7 @@ __export(index_exports, {
30
30
  inject: () => inject
31
31
  });
32
32
  module.exports = __toCommonJS(index_exports);
33
+ var import_react_dom = require("react-dom");
33
34
  var import_react = require("react");
34
35
  var import_jsx_runtime = require("react/jsx-runtime");
35
36
  var CSS = `
@@ -60,11 +61,27 @@ var CSS = `
60
61
  .pc-badge.local, .pc-badge.builtin { background: var(--dsw-alias-bg-module-platform); color: var(--dsw-alias-label-tertiary); }
61
62
  .pc-tag { display: inline-flex; align-items: center; height: 20px; padding: 0 8px; border-radius: 6px; font-size: 12px; line-height: 1; background: var(--dsw-alias-bg-module-platform); color: var(--dsw-alias-label-secondary); }
62
63
  .pc-tag.danger { background: var(--dsw-alias-interactive-bg-hover-danger); color: var(--dsw-alias-state-error-primary); }
63
- .pc-dot { flex: none; width: 8px; height: 8px; border-radius: 50%; background: var(--dsw-alias-state-success-primary); }
64
- .pc-dot.failed { background: var(--dsw-alias-state-error-primary); }
64
+ .pc-switch { position: relative; flex: none; width: 36px; height: 20px; border-radius: 10px; border: none; background: rgba(0,0,0,.22); cursor: pointer; transition: background .2s ease; padding: 0; }
65
+ .pc-switch::after { content: ''; position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; border-radius: 50%; background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,.3); transition: left .2s ease; }
66
+ .pc-switch.on { background: var(--dsw-alias-state-success-primary); }
67
+ .pc-switch.on::after { left: 18px; }
68
+ .pc-switch:disabled { opacity: .6; cursor: default; }
69
+ body[data-ds-dark-theme] .pc-switch { background: rgba(255,255,255,.28); }
70
+ body[data-ds-dark-theme] .pc-switch.on { background: var(--dsw-alias-state-success-primary); }
65
71
 
66
72
  .pc-toolbar { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; padding: 4px 0; }
67
- .pc-chip { height: 28px; padding: 0 12px; border-radius: 18px; border: 1px solid var(--dsw-alias-border-l2); background: var(--dsw-alias-bg-base); color: var(--dsw-alias-label-secondary); font-size: 13px; cursor: pointer; font-family: inherit; }
73
+ /* \u7B5B\u9009\u533A\u7EB5\u5411\u5BB9\u5668\uFF1AAI \u63A8\u8350\u884C / \u4E3B\u7B5B\u9009\u884C / \u5206\u7C7B\u884C\u4E09\u6BB5\uFF0C\u95F4\u8DDD\u7EDF\u4E00\u3002 */
74
+ .pc-filter { display: flex; flex-direction: column; gap: 6px; flex: none; margin-top: 2px; }
75
+ /* \u4E3B\u7B5B\u9009\u884C\uFF1A\u4E00\u884C\u653E\u641C\u7D22 + \u6765\u6E90 + \u5DE5\u4F5C\u533A\u6309\u94AE\uFF0C\u4E0D\u6362\u884C\u3002 */
76
+ .pc-toolbar-main { flex-wrap: nowrap; }
77
+ /* \u5206\u7C7B\u884C\uFF1A\u72EC\u7ACB\u4E00\u884C\u3001\u6A2A\u5411\u6EDA\u52A8\uFF08\u4E0D wrap \u6210\u6298\u884C\u4E71\u6392\uFF09\uFF0Cchip \u4E0D\u538B\u7F29\u3002 */
78
+ .pc-catbar { display: flex; align-items: center; gap: 6px; overflow-x: auto; padding: 2px 0 4px; scrollbar-width: thin; scrollbar-color: var(--dsw-alias-border-l3, rgba(0,0,0,.2)) transparent; }
79
+ .pc-catbar .pc-chip { flex: none; white-space: nowrap; }
80
+ /* \u5E02\u573A\u8D85\u91CF\u63D0\u793A\uFF1A\u5C0F\u53F7\u5F31\u5316\u6587\u5B57\uFF0C\u4E0E\u7B5B\u9009\u533A\u89C6\u89C9\u5206\u79BB\u3002 */
81
+ .pc-limit { margin: 2px 0 8px; font-size: 12px; line-height: 16px; color: var(--dsw-alias-label-caption); }
82
+ .pc-ai-row { display: flex; gap: 8px; align-items: center; }
83
+ .pc-ai-row .pc-btn { height: 28px; }
84
+ .pc-chip { height: 28px; padding: 0 12px; border-radius: 18px; border: 1px solid var(--dsw-alias-border-l2); background: var(--dsw-alias-bg-base); color: var(--dsw-alias-label-secondary); font-size: 13px; cursor: pointer; font-family: inherit; transition: background .15s ease, color .15s ease, border-color .15s ease; }
68
85
  .pc-chip:hover { background: var(--dsw-alias-interactive-bg-hover); }
69
86
  .pc-chip.active { background: var(--dsw-alias-state-business-primary); border-color: var(--dsw-alias-state-business-primary); color: var(--dsw-alias-bg-base); }
70
87
 
@@ -77,7 +94,12 @@ var CSS = `
77
94
  .pc-search { height: 28px; padding: 0 12px; border-radius: 18px; border: 1px solid var(--dsw-alias-border-l2); background: var(--dsw-alias-bg-base); color: var(--dsw-alias-label-primary); font-size: 13px; outline: none; width: 200px; font-family: inherit; }
78
95
  .pc-search:focus { border-color: var(--dsw-alias-state-business-primary); }
79
96
  .pc-search::placeholder { color: var(--dsw-alias-label-caption); }
80
- .pc-select { height: 28px; padding: 0 8px; border-radius: 18px; border: 1px solid var(--dsw-alias-border-l2); background: var(--dsw-alias-bg-base); color: var(--dsw-alias-label-secondary); font-size: 13px; cursor: pointer; font-family: inherit; }
97
+ /* \u5B9A\u5236 select\uFF1A\u5265\u79BB\u6D4F\u89C8\u5668\u539F\u751F\u5916\u89C2\uFF08\u9AD8\u5EA6/\u5185\u8FB9\u8DDD/padding \u5DEE\u5F02\u662F\u8DE8\u63A7\u4EF6\u4E0D\u9F50\u7684
98
+ \u4E3B\u56E0\uFF09\uFF0C\u7528\u80CC\u666F chevron \u66FF\u4EE3\u7CFB\u7EDF\u7BAD\u5934\uFF0C\u4E0E chip/\u641C\u7D22\u6846\u540C\u9AD8\u540C\u5706\u89D2\u3002 */
99
+ .pc-select { height: 28px; padding: 0 26px 0 12px; border-radius: 18px; border: 1px solid var(--dsw-alias-border-l2); background-color: var(--dsw-alias-bg-base); color: var(--dsw-alias-label-secondary); font-size: 13px; cursor: pointer; font-family: inherit; appearance: none; -webkit-appearance: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 10 10' fill='none' stroke='%23777777' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2 3.5l3 3 3-3'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 10px center; background-size: 10px 10px; }
100
+ .pc-select:hover { background-color: var(--dsw-alias-interactive-bg-hover); }
101
+ .pc-select:focus-visible { outline: none; border-color: var(--dsw-alias-state-business-primary); }
102
+ body[data-ds-dark-theme] .pc-select { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 10 10' fill='none' stroke='%23b0b0b0' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2 3.5l3 3 3-3'/%3E%3C/svg%3E"); }
81
103
  .pc-iconbtn { width: 28px; height: 28px; padding: 0; border-radius: 18px; border: 1px solid var(--dsw-alias-border-l2); background: var(--dsw-alias-bg-base); color: var(--dsw-alias-label-secondary); cursor: pointer; display: inline-flex; align-items: center; justify-content: center; }
82
104
  .pc-iconbtn:hover { background: var(--dsw-alias-interactive-bg-hover); }
83
105
 
@@ -97,9 +119,24 @@ var CSS = `
97
119
  .pc-wn-item { border-bottom: 1px solid var(--dsw-alias-border-l1); padding: 14px 0; }
98
120
  .pc-wn-item:last-child { border-bottom: none; }
99
121
  .pc-wn-list { margin-top: 8px; padding-left: 20px; color: var(--dsw-alias-label-secondary); font-size: 13px; }
100
- .pc-toast { position: fixed; bottom: 32px; left: 50%; transform: translateX(-50%); padding: 10px 18px; border-radius: 10px; background: var(--dsw-alias-bg-layer-1); border: 1px solid var(--dsw-alias-border-l2); color: var(--dsw-alias-label-primary); font-size: 13px; box-shadow: 0 8px 32px rgba(0,0,0,.18); z-index: 200; max-width: 80vw; }
122
+ .pc-toast { position: fixed; bottom: 32px; left: 50%; transform: translateX(-50%); padding: 10px 18px; border-radius: 10px; background: var(--dsw-alias-bg-layer-1); border: 1px solid var(--dsw-alias-border-l2); color: var(--dsw-alias-label-primary); font-size: 13px; box-shadow: 0 8px 32px rgba(0,0,0,.18); z-index: 1500; max-width: 80vw; }
101
123
  .pc-toast.ok { border-color: var(--dsw-alias-state-success-primary); }
102
124
  .pc-toast.error { border-color: var(--dsw-alias-state-error-primary); }
125
+ /* DSH 0.1.x \u8BBE\u7F6E\u5BFC\u822A\u65E0 icon \u5951\u7EA6\uFF08external section \u4E00\u5F8B\u9ED8\u8BA4\u9F7F\u8F6E\uFF09\u3002settings-nav-icon
126
+ \u6807\u8BB0\u672C\u63D2\u4EF6\u884C\u540E\uFF1A\u9690\u85CF\u58F3\u6E32\u67D3\u7684\u9F7F\u8F6E SVG\uFF0C\u7528\u4E0E\u53F3\u4E0A\u89D2 HeaderButton \u76F8\u540C\u7684 2\xD72 \u7F51\u683C
127
+ \u56FE\u6807\uFF08currentColor mask\uFF09\u66FF\u6362\uFF0C\u8DDF\u968F\u539F\u751F\u5BFC\u822A hover/active \u989C\u8272\u4E14\u4E0D\u6539\u53D8\u58F3\u7684
128
+ 16px \u56FE\u6807\u8282\u594F\u3002\u9009\u62E9\u5668\u517C\u5BB9\u56FE\u6807\u76F4\u63A5\u4E3A button \u9996\u5B50\u5143\u7D20\u4E0E\u5305\u4E00\u5C42 wrapper \u4E24\u79CD\u60C5\u51B5\u3002 */
129
+ [data-dsh-plugin-center-settings-nav] > svg:first-child,
130
+ [data-dsh-plugin-center-settings-nav] > *:first-child > svg:first-child { display: none; }
131
+ [data-dsh-plugin-center-settings-nav]::before {
132
+ content: '';
133
+ flex: none;
134
+ width: 16px;
135
+ height: 16px;
136
+ background: currentColor;
137
+ -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.5' stroke-linecap='round'%3E%3Crect x='2' y='2' width='5' height='5' rx='1'/%3E%3Crect x='9' y='2' width='5' height='5' rx='1'/%3E%3Crect x='2' y='9' width='5' height='5' rx='1'/%3E%3Crect x='9' y='9' width='5' height='5' rx='1'/%3E%3C/svg%3E") center / contain no-repeat;
138
+ mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.5' stroke-linecap='round'%3E%3Crect x='2' y='2' width='5' height='5' rx='1'/%3E%3Crect x='9' y='2' width='5' height='5' rx='1'/%3E%3Crect x='2' y='9' width='5' height='5' rx='1'/%3E%3Crect x='9' y='9' width='5' height='5' rx='1'/%3E%3C/svg%3E") center / contain no-repeat;
139
+ }
103
140
  `;
104
141
  var cssInjected = false;
105
142
  function injectCss() {
@@ -110,6 +147,30 @@ function injectCss() {
110
147
  style.textContent = CSS;
111
148
  document.head.append(style);
112
149
  }
150
+ var SETTINGS_NAV_MARKER = "data-dsh-plugin-center-settings-nav";
151
+ function registerSettingsNavIcon(label) {
152
+ let disposed = false;
153
+ const sync = () => {
154
+ if (disposed) return;
155
+ const currentLabel = label().trim();
156
+ const buttons = document.querySelectorAll('[role="dialog"] nav button');
157
+ for (const button of buttons) {
158
+ const matches = currentLabel.length > 0 && button.textContent?.trim() === currentLabel;
159
+ if (matches) button.setAttribute(SETTINGS_NAV_MARKER, "");
160
+ else button.removeAttribute(SETTINGS_NAV_MARKER);
161
+ }
162
+ };
163
+ sync();
164
+ const observer = new MutationObserver(sync);
165
+ observer.observe(document.body, { childList: true, subtree: true, characterData: true });
166
+ return () => {
167
+ disposed = true;
168
+ observer.disconnect();
169
+ document.querySelectorAll(`[${SETTINGS_NAV_MARKER}]`).forEach((element) => {
170
+ element.removeAttribute(SETTINGS_NAV_MARKER);
171
+ });
172
+ };
173
+ }
113
174
  var rpc = async () => {
114
175
  throw new Error("plugin-center: rpc not wired");
115
176
  };
@@ -164,6 +225,82 @@ function useWhatsNewOpen() {
164
225
  var readCache = {};
165
226
  var installedCache = null;
166
227
  var marketCache = {};
228
+ var countsState = { installed: 0, market: 0, dshMarket: 0, failed: 0 };
229
+ var countListeners = /* @__PURE__ */ new Set();
230
+ function setCounts(partial) {
231
+ const next = { ...countsState, ...partial };
232
+ if (next.installed === countsState.installed && next.market === countsState.market && next.dshMarket === countsState.dshMarket && next.failed === countsState.failed) return;
233
+ countsState = next;
234
+ countListeners.forEach((l) => l());
235
+ }
236
+ var updatingPlugins = /* @__PURE__ */ new Set();
237
+ var updatingListeners = /* @__PURE__ */ new Set();
238
+ function setUpdating(name, on) {
239
+ if (on) updatingPlugins.add(name);
240
+ else updatingPlugins.delete(name);
241
+ updatingListeners.forEach((l) => l());
242
+ }
243
+ var pendingToggles = /* @__PURE__ */ new Map();
244
+ var pendingListeners = /* @__PURE__ */ new Set();
245
+ function setPendingToggle(id, action) {
246
+ if (action === "disable") pendingToggles.set(id, true);
247
+ else if (action === "enable") pendingToggles.set(id, false);
248
+ else pendingToggles.delete(id);
249
+ pendingListeners.forEach((l) => l());
250
+ }
251
+ function usePendingVersion() {
252
+ const [v, setV] = (0, import_react.useState)(0);
253
+ (0, import_react.useEffect)(() => {
254
+ const l = () => {
255
+ setV((x) => x + 1);
256
+ };
257
+ pendingListeners.add(l);
258
+ return () => {
259
+ pendingListeners.delete(l);
260
+ };
261
+ }, []);
262
+ return v;
263
+ }
264
+ var installedListeners = /* @__PURE__ */ new Set();
265
+ function useInstalledVersion() {
266
+ const [v, setV] = (0, import_react.useState)(0);
267
+ (0, import_react.useEffect)(() => {
268
+ const l = () => {
269
+ setV((x) => x + 1);
270
+ };
271
+ installedListeners.add(l);
272
+ return () => {
273
+ installedListeners.delete(l);
274
+ };
275
+ }, []);
276
+ return v;
277
+ }
278
+ function useUpdatingVersion() {
279
+ const [v, setV] = (0, import_react.useState)(0);
280
+ (0, import_react.useEffect)(() => {
281
+ const l = () => {
282
+ setV((x) => x + 1);
283
+ };
284
+ updatingListeners.add(l);
285
+ return () => {
286
+ updatingListeners.delete(l);
287
+ };
288
+ }, []);
289
+ return v;
290
+ }
291
+ function useCounts() {
292
+ const [v, setV] = (0, import_react.useState)(0);
293
+ (0, import_react.useEffect)(() => {
294
+ const l = () => {
295
+ setV((x) => x + 1);
296
+ };
297
+ countListeners.add(l);
298
+ return () => {
299
+ countListeners.delete(l);
300
+ };
301
+ }, []);
302
+ return countsState;
303
+ }
167
304
  var toastState = null;
168
305
  var toastListeners = /* @__PURE__ */ new Set();
169
306
  function showToast(message, kind = "ok", duration = 3200) {
@@ -260,7 +397,32 @@ var STRINGS = {
260
397
  close: "\u5173\u95ED",
261
398
  checkFail: "\u68C0\u67E5\u66F4\u65B0\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5",
262
399
  foundUpdates: "\u53D1\u73B0 {n} \u4E2A\u53EF\u66F4\u65B0\u63D2\u4EF6",
263
- allUpToDate: "\u6240\u6709\u63D2\u4EF6\u5747\u4E3A\u6700\u65B0"
400
+ allUpToDate: "\u6240\u6709\u63D2\u4EF6\u5747\u4E3A\u6700\u65B0",
401
+ disable: "\u7981\u7528",
402
+ enable: "\u542F\u7528",
403
+ toggling: "\u5904\u7406\u4E2D\u2026",
404
+ disabledOk: "\u5DF2\u7981\u7528 {n}\uFF0C\u91CD\u542F\u540E\u751F\u6548",
405
+ enabledOk: "\u5DF2\u542F\u7528 {n}\uFF0C\u91CD\u542F\u540E\u751F\u6548",
406
+ toggleFailed: "\u64CD\u4F5C\u5931\u8D25\uFF1A{e}",
407
+ revertedDisable: "\u5DF2\u64A4\u9500\u7981\u7528 {n}",
408
+ revertedEnable: "\u5DF2\u64A4\u9500\u542F\u7528 {n}",
409
+ tabDiagnose: "\u8BCA\u65AD",
410
+ diagExport: "\u5BFC\u51FA\u8BCA\u65AD\u65E5\u5FD7",
411
+ diagCopied: "\u8BCA\u65AD\u5DF2\u590D\u5236\u5230\u526A\u8D34\u677F",
412
+ diagTitle: "\u73AF\u5883\u4E0E\u63D2\u4EF6\u8BCA\u65AD",
413
+ diagInstalled: "\u5DF2\u5B89\u88C5\u63D2\u4EF6\uFF08{n}\uFF09",
414
+ diagDisabled: "\u7981\u7528\u72B6\u6001",
415
+ diagPnpmLog: "pnpm \u65E5\u5FD7\uFF08\u5C3E\u90E8\uFF09",
416
+ aiTitle: "AI \u63A8\u8350",
417
+ aiPlaceholder: "\u63CF\u8FF0\u4F60\u7684\u9700\u6C42\uFF0C\u8BA9 AI \u63A8\u8350\u63D2\u4EF6\u2026\uFF08\u4F8B\u5982\uFF1A\u80FD\u9884\u89C8 Markdown \u7684\u63D2\u4EF6\uFF09",
418
+ aiAsk: "\u63A8\u8350",
419
+ aiAsking: "AI \u601D\u8003\u4E2D\u2026",
420
+ aiFail: "AI \u63A8\u8350\u5931\u8D25\uFF1A{e}",
421
+ scoreLabel: "\u8BC4\u5206",
422
+ screenshot: "\u622A\u56FE",
423
+ noScreenshot: "\u65E0\u622A\u56FE",
424
+ screenshotFail: "\u622A\u56FE\u83B7\u53D6\u5931\u8D25",
425
+ marketTooMany: "\u4EC5\u663E\u793A\u524D {n} \u4E2A\uFF08\u5171 {m}\uFF09\uFF0C\u641C\u7D22\u53EF\u7F29\u5C0F\u8303\u56F4"
264
426
  },
265
427
  en: {
266
428
  title: "Plugin Center",
@@ -314,7 +476,32 @@ var STRINGS = {
314
476
  close: "Close",
315
477
  checkFail: "Failed to check updates, please retry later",
316
478
  foundUpdates: "{n} updates found",
317
- allUpToDate: "All plugins are up to date"
479
+ allUpToDate: "All plugins are up to date",
480
+ disable: "Disable",
481
+ enable: "Enable",
482
+ toggling: "Working\u2026",
483
+ disabledOk: "Disabled {n}; restart to take effect",
484
+ enabledOk: "Enabled {n}; restart to take effect",
485
+ toggleFailed: "Failed: {e}",
486
+ revertedDisable: "Disable of {n} reverted",
487
+ revertedEnable: "Enable of {n} reverted",
488
+ tabDiagnose: "Diagnostics",
489
+ diagExport: "Export diagnostics",
490
+ diagCopied: "Diagnostics copied to clipboard",
491
+ diagTitle: "Environment & plugin diagnostics",
492
+ diagInstalled: "Installed plugins ({n})",
493
+ diagDisabled: "Disabled state",
494
+ diagPnpmLog: "pnpm log (tail)",
495
+ aiTitle: "AI recommendation",
496
+ aiPlaceholder: "Describe what you need \u2014 e.g. a Markdown preview plugin\u2026",
497
+ aiAsk: "Recommend",
498
+ aiAsking: "AI is thinking\u2026",
499
+ aiFail: "AI recommendation failed: {e}",
500
+ scoreLabel: "Score",
501
+ screenshot: "Screenshot",
502
+ noScreenshot: "No screenshot",
503
+ screenshotFail: "Screenshot fetch failed",
504
+ marketTooMany: "Showing first {n} of {m}; search to narrow down"
318
505
  }
319
506
  };
320
507
  var localeId = "zh";
@@ -339,15 +526,22 @@ function useT() {
339
526
  localeListeners.delete(l);
340
527
  };
341
528
  }, []);
342
- return (key, vars) => fmt(STRINGS[id][key] ?? STRINGS.zh[key], vars);
529
+ return (0, import_react.useCallback)((key, vars) => fmt(STRINGS[id][key] ?? STRINGS.zh[key], vars), [id]);
343
530
  }
344
- function InstalledView({ search, category, source }) {
531
+ function InstalledView({ search, category, source, onToggle, togglingId }) {
345
532
  const t = useT();
533
+ const installedVersion = useInstalledVersion();
534
+ usePendingVersion();
346
535
  const [items, setItems] = (0, import_react.useState)(installedCache);
347
536
  const [error, setError] = (0, import_react.useState)(null);
348
537
  (0, import_react.useEffect)(() => {
349
- if (installedCache !== null) return;
350
538
  let alive = true;
539
+ if (installedCache !== null) {
540
+ setItems(installedCache);
541
+ return () => {
542
+ alive = false;
543
+ };
544
+ }
351
545
  void rpc("listInstalled").then(
352
546
  (v) => {
353
547
  installedCache = v;
@@ -360,7 +554,11 @@ function InstalledView({ search, category, source }) {
360
554
  return () => {
361
555
  alive = false;
362
556
  };
363
- }, []);
557
+ }, [installedVersion]);
558
+ const handleToggleLocal = (p) => {
559
+ setItems((prev) => prev === null ? prev : prev.map((item) => item.entryId === p.entryId ? { ...item, enabled: !item.enabled } : item));
560
+ onToggle(p);
561
+ };
364
562
  if (error !== null) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "pc-sub", children: t("loadFailed", { e: error }) });
365
563
  if (items === null) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "pc-sub", children: t("loading") });
366
564
  const srcLabel = {
@@ -385,22 +583,42 @@ function InstalledView({ search, category, source }) {
385
583
  ] }),
386
584
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `pc-badge ${p.source}`, children: srcLabel[p.source] }),
387
585
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-spacer" }),
388
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `pc-dot${p.fiberPhase === "failed" ? " failed" : ""}` })
586
+ p.fiberPhase === "failed" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-dot failed", title: "failed" }),
587
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
588
+ "button",
589
+ {
590
+ type: "button",
591
+ role: "switch",
592
+ "aria-checked": p.enabled,
593
+ "aria-label": p.enabled ? t("disable") : t("enable"),
594
+ title: p.enabled ? t("disable") : t("enable"),
595
+ className: `pc-switch${p.enabled ? " on" : ""}`,
596
+ disabled: togglingId !== null,
597
+ onClick: () => {
598
+ handleToggleLocal(p);
599
+ }
600
+ }
601
+ )
389
602
  ] }),
390
603
  p.description !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pc-desc", children: p.description }),
391
604
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-meta", children: [
392
605
  p.categories.map((c) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-tag", children: c }, c)),
393
606
  p.compatRange !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-tag", children: t("requiresDsh", { r: p.compatRange }) }),
394
- !p.enabled && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-tag", children: t("disabledTag") })
607
+ !p.enabled && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-tag", children: t("disabledTag") }),
608
+ pendingToggles.has(p.entryId) && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-tag", children: t("pendingRestart") })
395
609
  ] })
396
610
  ] }, p.entryId)) });
397
611
  }
398
612
  function MarketView({ category, single, source, search, onCount }) {
399
613
  const t = useT();
400
- const [items, setItems] = (0, import_react.useState)(marketCache[source]?.plugins ?? []);
401
- const [done, setDone] = (0, import_react.useState)(marketCache[source]?.done ?? false);
614
+ const [items, setItems] = (0, import_react.useState)(marketCache[source]?.plugins ?? marketCache.all?.plugins ?? []);
615
+ const [done, setDone] = (0, import_react.useState)(marketCache[source]?.done ?? marketCache.all?.done ?? false);
402
616
  const [error, setError] = (0, import_react.useState)(null);
403
617
  const [busy, setBusy] = (0, import_react.useState)(null);
618
+ const [shotBusy, setShotBusy] = (0, import_react.useState)(null);
619
+ const [shotUrl, setShotUrl] = (0, import_react.useState)(null);
620
+ const [shotName, setShotName] = (0, import_react.useState)(null);
621
+ const [shotZoom, setShotZoom] = (0, import_react.useState)(false);
404
622
  (0, import_react.useEffect)(() => {
405
623
  let alive = true;
406
624
  let timer;
@@ -411,9 +629,10 @@ function MarketView({ category, single, source, search, onCount }) {
411
629
  onCount(cached.plugins.length);
412
630
  if (cached.done) return;
413
631
  } else {
414
- setItems([]);
415
- setDone(false);
416
- onCount(0);
632
+ const fallback = marketCache.all;
633
+ setItems(fallback?.plugins ?? []);
634
+ setDone(fallback?.done ?? false);
635
+ if (fallback !== void 0) onCount(fallback.plugins.length);
417
636
  }
418
637
  const poll = async () => {
419
638
  try {
@@ -441,12 +660,14 @@ function MarketView({ category, single, source, search, onCount }) {
441
660
  const matchSearch = q === "" || m.name.toLowerCase().includes(q) || (m.description.zh || m.description.en).toLowerCase().includes(q);
442
661
  return matchSearch && (category === null || m.categories.includes(category));
443
662
  });
663
+ const shown = filtered.slice(0, 200);
444
664
  const descOf = (m) => localeId === "en" ? m.description.en || m.description.zh : m.description.zh || m.description.en;
445
665
  const install = (m) => {
446
666
  setBusy(m.name);
447
667
  void rpc("install", { spec: m.spec }).then(
448
668
  (v) => {
449
- if (v !== true) throw new Error(t("installNotApplied"));
669
+ const durationMs = typeof v === "object" && v !== null ? v.durationMs : void 0;
670
+ if (v !== true && durationMs === void 0) throw new Error(t("installNotApplied"));
450
671
  setBusy(null);
451
672
  pendingInstall.add(m.spec);
452
673
  for (const key of Object.keys(marketCache)) {
@@ -454,18 +675,44 @@ function MarketView({ category, single, source, search, onCount }) {
454
675
  if (c !== void 0) c.plugins = c.plugins.map((p) => p.name === m.name ? { ...p, installed: true } : p);
455
676
  }
456
677
  setItems((prev) => prev.map((p) => p.name === m.name ? { ...p, installed: true } : p));
457
- showToast(t("installQueued", { n: m.name }), "ok", 5e3);
678
+ showToast(t("installQueued", { n: m.name }) + (durationMs !== void 0 ? `\uFF08${(durationMs / 1e3).toFixed(1)}s\uFF09` : ""), "ok", 5e3);
458
679
  },
459
680
  (e) => {
460
681
  setBusy(null);
461
- showToast(t("installFailed", { e: e instanceof Error ? e.message : String(e) }), "error", 8e3);
682
+ showToast(t("installFailed", { e: e instanceof Error ? e.message : String(e) }), "error", 15e3);
683
+ }
684
+ );
685
+ };
686
+ const showScreenshot = (m) => {
687
+ if (shotBusy !== null) return;
688
+ setShotBusy(m.name);
689
+ void rpc("screenshot", { name: m.name }).then(
690
+ (url) => {
691
+ setShotBusy(null);
692
+ if (typeof url !== "string" || url === "") {
693
+ showToast(t("noScreenshot"), "error", 6e3);
694
+ return;
695
+ }
696
+ setShotName(m.name);
697
+ setShotUrl(url);
698
+ setShotZoom(false);
699
+ },
700
+ () => {
701
+ setShotBusy(null);
702
+ showToast(t("screenshotFail"), "error", 6e3);
462
703
  }
463
704
  );
464
705
  };
465
706
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
466
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: `pc-grid ${single ? "single" : "double"}`, children: filtered.map((m) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-card", children: [
707
+ filtered.length > 200 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "pc-limit", children: t("marketTooMany", { n: shown.length, m: filtered.length }) }),
708
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: `pc-grid ${single ? "single" : "double"}`, children: shown.map((m) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-card", children: [
467
709
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-row", children: [
468
710
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-name", children: m.name }),
711
+ m.score !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "pc-tag", title: `${m.score.total}${m.score.explanation !== "" ? `\uFF1A${m.score.explanation}` : ""}`, children: [
712
+ t("scoreLabel"),
713
+ " ",
714
+ m.score.total
715
+ ] }),
469
716
  m.stars !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "pc-ver", children: [
470
717
  "\u2605 ",
471
718
  m.stars
@@ -477,18 +724,49 @@ function MarketView({ category, single, source, search, onCount }) {
477
724
  ] }),
478
725
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pc-desc", children: descOf(m) }),
479
726
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-meta", children: [
480
- m.categories.map((c) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-tag", children: c }, c)),
727
+ m.categories.slice(0, 4).map((c) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-tag", children: c }, c)),
481
728
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-spacer" }),
729
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "pc-iconbtn", title: t("screenshot"), "aria-label": t("screenshot"), disabled: shotBusy !== null, onClick: () => {
730
+ showScreenshot(m);
731
+ }, children: shotBusy === m.name ? "\u2026" : "\u{1F4F7}" }),
482
732
  m.installed || pendingInstall.has(m.spec) ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-tag", children: pendingInstall.has(m.spec) ? t("pendingRestart") : t("installedTag") }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "pc-btn primary", disabled: busy !== null, onClick: () => {
483
733
  install(m);
484
734
  }, children: busy === m.name ? t("installing") : t("install") })
485
735
  ] })
486
736
  ] }, m.name)) }),
487
- !done && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "pc-sub", children: t("loadMore", { n: items.length }) })
737
+ !done && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "pc-sub", children: t("loadMore", { n: items.length }) }),
738
+ shotUrl !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pc-overlay", role: "presentation", onClick: () => {
739
+ setShotUrl(null);
740
+ setShotName(null);
741
+ }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-panel", role: "dialog", "aria-modal": "true", onClick: (e) => {
742
+ e.stopPropagation();
743
+ }, children: [
744
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-panel-head", children: [
745
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-title", children: shotName ?? "" }),
746
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-spacer" }),
747
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "pc-close", onClick: () => {
748
+ setShotUrl(null);
749
+ setShotName(null);
750
+ }, "aria-label": t("close"), children: "\u2715" })
751
+ ] }),
752
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pc-panel-body", style: { alignItems: "center", overflow: shotZoom ? "auto" : "hidden" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
753
+ "img",
754
+ {
755
+ src: shotUrl,
756
+ alt: shotName ?? "",
757
+ onClick: () => {
758
+ setShotZoom((v) => !v);
759
+ },
760
+ title: shotZoom ? "\u7F29\u5C0F" : "\u70B9\u51FB\u653E\u5927",
761
+ style: shotZoom ? { maxWidth: "none", maxHeight: "none", width: "auto", borderRadius: 8 } : { maxWidth: "100%", maxHeight: "70vh", borderRadius: 8, objectFit: "contain" }
762
+ }
763
+ ) })
764
+ ] }) })
488
765
  ] });
489
766
  }
490
767
  function UpdatesView({ updates, refresh, updateOne, busy }) {
491
768
  const t = useT();
769
+ useUpdatingVersion();
492
770
  if (updates === null) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "pc-sub", children: t("checkingUpdates") });
493
771
  if (updates.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
494
772
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "pc-sub", children: t("noUpdates") }),
@@ -504,11 +782,101 @@ function UpdatesView({ updates, refresh, updateOne, busy }) {
504
782
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-spacer" }),
505
783
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "pc-btn primary", disabled: busy !== null, onClick: () => {
506
784
  updateOne(u.name, u.toVersion);
507
- }, children: busy === u.name || busy === "__all__" ? t("updating") : t("update") })
785
+ }, children: busy === u.name || busy === "__all__" || updatingPlugins.has(u.name) ? t("updating") : t("update") })
508
786
  ] }),
509
787
  u.changelog.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: "pc-wn-list", children: u.changelog.slice(0, 5).map((line, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: line }, i)) })
510
788
  ] }, u.name)) });
511
789
  }
790
+ function DiagnoseView() {
791
+ const t = useT();
792
+ const [report, setReport] = (0, import_react.useState)(null);
793
+ const [error, setError] = (0, import_react.useState)(null);
794
+ (0, import_react.useEffect)(() => {
795
+ let alive = true;
796
+ void rpc("diagnostics").then(
797
+ (v) => {
798
+ if (alive) setReport(v);
799
+ },
800
+ (e) => {
801
+ if (alive) setError(e instanceof Error ? e.message : String(e));
802
+ }
803
+ );
804
+ return () => {
805
+ alive = false;
806
+ };
807
+ }, []);
808
+ const textOf = () => {
809
+ if (report === null) return "no diagnostics";
810
+ const lines = [
811
+ "=== dsh-plugin-center diagnostics ===",
812
+ `dsh: ${report.dshVersion}`,
813
+ `node: ${report.node}`,
814
+ `profile: ${report.baseUrl}`,
815
+ `installed (${report.installed.length}):`,
816
+ ...report.installed.map((p) => ` ${p.enabled ? "" : "[disabled] "}${p.name}@${p.version ?? "?"} (${p.source}, ${p.fiberPhase ?? "?"})`),
817
+ "patch disabled:",
818
+ ...Object.entries(report.disabled).map(([id, v]) => ` ${id}: ${v ? "disabled" : "force-enabled"}`),
819
+ "pnpm log tail:",
820
+ report.pnpmLogTail
821
+ ];
822
+ return lines.join("\n");
823
+ };
824
+ const exportLog = () => {
825
+ const blob = new Blob([textOf()], { type: "text/plain;charset=utf-8" });
826
+ const url = URL.createObjectURL(blob);
827
+ const a = document.createElement("a");
828
+ a.href = url;
829
+ a.download = `plugin-center-diagnostics-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace(/[:T]/gu, "-")}.txt`;
830
+ a.click();
831
+ URL.revokeObjectURL(url);
832
+ };
833
+ const copy = () => {
834
+ void navigator.clipboard?.writeText(textOf()).then(() => {
835
+ showToast(t("diagCopied"), "ok", 3e3);
836
+ }).catch(() => {
837
+ });
838
+ };
839
+ if (error !== null) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "pc-sub", children: t("loadFailed", { e: error }) });
840
+ if (report === null) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "pc-sub", children: t("loading") });
841
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
842
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-toolbar", children: [
843
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "pc-btn", onClick: exportLog, children: t("diagExport") }),
844
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "pc-btn", onClick: copy, children: t("diagCopied") })
845
+ ] }),
846
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "pc-sub", children: t("diagTitle") }),
847
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-card", children: [
848
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-row", children: [
849
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-name", children: "dsh" }),
850
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-ver", children: report.dshVersion })
851
+ ] }),
852
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-row", children: [
853
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-name", children: "node" }),
854
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-ver", children: report.node })
855
+ ] }),
856
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-row", children: [
857
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-name", children: "profile" }),
858
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-ver", style: { wordBreak: "break-all" }, children: report.baseUrl })
859
+ ] })
860
+ ] }),
861
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "pc-sub", children: t("diagInstalled", { n: report.installed.length }) }),
862
+ report.installed.map((p) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pc-card", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-row", children: [
863
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-name", children: p.name }),
864
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "pc-ver", children: [
865
+ "v",
866
+ p.version ?? "?"
867
+ ] }),
868
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `pc-badge ${p.source}`, children: p.source }),
869
+ !p.enabled && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-tag", children: t("disabledTag") })
870
+ ] }) }, p.name)),
871
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "pc-sub", children: t("diagDisabled") }),
872
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pc-card", children: Object.keys(report.disabled).length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-ver", children: "\u2014" }) : Object.entries(report.disabled).map(([id, v]) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-row", children: [
873
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-name", children: id }),
874
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-ver", children: v ? "disabled" : "enabled" })
875
+ ] }, id)) }),
876
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "pc-sub", children: t("diagPnpmLog") }),
877
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", { className: "pc-desc", style: { whiteSpace: "pre-wrap", fontSize: 11 }, children: report.pnpmLogTail === "" ? "\u2014" : report.pnpmLogTail })
878
+ ] });
879
+ }
512
880
  function CenterPanel({ variant = "section" }) {
513
881
  const t = useT();
514
882
  const [view, setView] = (0, import_react.useState)("installed");
@@ -519,12 +887,65 @@ function CenterPanel({ variant = "section" }) {
519
887
  const [installedCategory, setInstalledCategory] = (0, import_react.useState)(null);
520
888
  const [marketSearch, setMarketSearch] = (0, import_react.useState)("");
521
889
  const [installedSource, setInstalledSource] = (0, import_react.useState)(null);
522
- const [installedCount, setInstalledCount] = (0, import_react.useState)(0);
523
- const [failedCount, setFailedCount] = (0, import_react.useState)(0);
524
- const [marketCount, setMarketCount] = (0, import_react.useState)(0);
890
+ const counts = useCounts();
525
891
  const [updates, setUpdates] = (0, import_react.useState)(null);
526
892
  const [busyUpdate, setBusyUpdate] = (0, import_react.useState)(null);
527
893
  const [checking, setChecking] = (0, import_react.useState)(false);
894
+ const [togglingId, setTogglingId] = (0, import_react.useState)(null);
895
+ const [aiQuery, setAiQuery] = (0, import_react.useState)("");
896
+ const [aiBusy, setAiBusy] = (0, import_react.useState)(false);
897
+ const [aiResult, setAiResult] = (0, import_react.useState)(null);
898
+ const [aiError, setAiError] = (0, import_react.useState)(null);
899
+ const handleToggle = (p) => {
900
+ if (togglingId !== null) return;
901
+ const id = p.entryId;
902
+ const nextEnabled = !p.enabled;
903
+ const wasPending = pendingToggles.has(id);
904
+ console.log("[plugin-center] toggle", { id, fromEnabled: p.enabled, toEnabled: nextEnabled, wasPending });
905
+ setTogglingId(p.name);
906
+ void rpc("toggle", { id, disabled: !nextEnabled }).then(
907
+ (v) => {
908
+ setTogglingId(null);
909
+ const nowDisabled = typeof v === "object" && v !== null ? v.nowDisabled : null;
910
+ console.log("[plugin-center] toggle result", { id, nowDisabled });
911
+ if (nowDisabled === true && !wasPending) {
912
+ setPendingToggle(id, "disable");
913
+ showToast(t("disabledOk", { n: p.name }), "ok", 5e3);
914
+ } else if (nowDisabled === false && !wasPending) {
915
+ setPendingToggle(id, "enable");
916
+ showToast(t("enabledOk", { n: p.name }), "ok", 5e3);
917
+ } else if (wasPending) {
918
+ setPendingToggle(id, null);
919
+ showToast(nowDisabled === true ? t("revertedDisable", { n: p.name }) : t("revertedEnable", { n: p.name }), "ok", 5e3);
920
+ }
921
+ if (installedCache !== null) {
922
+ installedCache = installedCache.map((item) => item.entryId === id ? { ...item, enabled: nextEnabled } : item);
923
+ }
924
+ },
925
+ (e) => {
926
+ setTogglingId(null);
927
+ console.log("[plugin-center] toggle failed", { id, error: e instanceof Error ? e.message : String(e) });
928
+ showToast(t("toggleFailed", { e: e instanceof Error ? e.message : String(e) }), "error", 15e3);
929
+ }
930
+ );
931
+ };
932
+ const askAi = () => {
933
+ const q = aiQuery.trim();
934
+ if (q === "" || aiBusy) return;
935
+ setAiBusy(true);
936
+ setAiResult(null);
937
+ setAiError(null);
938
+ void rpc("suggest", { query: q }).then(
939
+ (v) => {
940
+ setAiBusy(false);
941
+ setAiResult(v);
942
+ },
943
+ (e) => {
944
+ setAiBusy(false);
945
+ setAiError(e instanceof Error ? e.message : String(e));
946
+ }
947
+ );
948
+ };
528
949
  const refreshUpdates = (0, import_react.useCallback)((silent = false) => {
529
950
  setChecking(true);
530
951
  void rpc("checkUpdates", { since: new Date(Date.now() - 30 * 864e5).toISOString() }).then(
@@ -550,8 +971,8 @@ function CenterPanel({ variant = "section" }) {
550
971
  void rpc("listInstalled").then(
551
972
  (v) => {
552
973
  const items = v;
553
- setInstalledCount(items.length);
554
- setFailedCount(items.filter((p) => p.fiberPhase === "failed").length);
974
+ installedCache = items;
975
+ setCounts({ installed: items.length, failed: items.filter((p) => p.fiberPhase === "failed").length });
555
976
  },
556
977
  () => {
557
978
  }
@@ -560,40 +981,51 @@ function CenterPanel({ variant = "section" }) {
560
981
  }, [refreshUpdates]);
561
982
  (0, import_react.useEffect)(() => {
562
983
  let alive = true;
563
- let timer = null;
564
- const poll = async () => {
984
+ const timers = [];
985
+ const poll = async (src) => {
565
986
  if (!alive) return;
566
987
  try {
567
- const r = await rpc("listMarket", { source: "all" });
988
+ const r = await rpc("listMarket", { source: src });
568
989
  if (!alive) return;
569
- marketCache.all = { plugins: r.plugins, done: r.done };
570
- setMarketCount(r.plugins.length);
571
- if (!r.done) timer = setTimeout(() => {
572
- void poll();
573
- }, 5e3);
990
+ marketCache[src] = { plugins: r.plugins, done: r.done };
991
+ if (src === "all") setCounts({ market: r.plugins.length });
992
+ if (src === "dsh-market") setCounts({ dshMarket: r.plugins.length });
993
+ if (!r.done) timers.push(setTimeout(() => {
994
+ void poll(src);
995
+ }, 5e3));
574
996
  } catch {
575
- if (alive) timer = setTimeout(() => {
576
- void poll();
577
- }, 15e3);
997
+ if (alive) timers.push(setTimeout(() => {
998
+ void poll(src);
999
+ }, 15e3));
578
1000
  }
579
1001
  };
580
- void poll();
1002
+ void poll("all");
1003
+ void poll("awesome");
1004
+ void poll("oh-my-dsh");
581
1005
  return () => {
582
1006
  alive = false;
583
- if (timer !== null) clearTimeout(timer);
1007
+ for (const timer of timers) clearTimeout(timer);
584
1008
  };
585
1009
  }, []);
1010
+ const handleMarketCount = (0, import_react.useCallback)((n) => {
1011
+ setCounts({ market: n });
1012
+ }, []);
586
1013
  const updateOne = (name, version) => {
587
1014
  setBusyUpdate(name);
1015
+ setUpdating(name, true);
588
1016
  void rpc("update", { name, version }).then(
589
1017
  (v) => {
590
- if (v !== true) throw new Error(t("updateNotApplied"));
1018
+ const durationMs = typeof v === "object" && v !== null ? v.durationMs : void 0;
1019
+ if (v !== true && durationMs === void 0) throw new Error(t("updateNotApplied"));
1020
+ setUpdating(name, false);
591
1021
  setBusyUpdate(null);
592
- showToast(t("updatedOne", { n: name }), "ok", 5e3);
1022
+ showToast(t("updatedOne", { n: name }) + (durationMs !== void 0 ? `\uFF08${(durationMs / 1e3).toFixed(1)}s\uFF09` : ""), "ok", 5e3);
1023
+ refreshUpdates(true);
593
1024
  },
594
1025
  (e) => {
1026
+ setUpdating(name, false);
595
1027
  setBusyUpdate(null);
596
- showToast(t("updateFailed", { e: e instanceof Error ? e.message : String(e) }), "error", 8e3);
1028
+ showToast(t("updateFailed", { e: e instanceof Error ? e.message : String(e) }), "error", 15e3);
597
1029
  }
598
1030
  );
599
1031
  };
@@ -604,20 +1036,24 @@ function CenterPanel({ variant = "section" }) {
604
1036
  const okNames = [];
605
1037
  const failures = [];
606
1038
  for (const u of updates) {
1039
+ setUpdating(u.name, true);
607
1040
  try {
608
1041
  const v = await rpc("update", { name: u.name, version: u.toVersion });
609
- if (v !== true) throw new Error(t("updateNotApplied"));
1042
+ const durationMs = typeof v === "object" && v !== null ? v.durationMs : void 0;
1043
+ if (v !== true && durationMs === void 0) throw new Error(t("updateNotApplied"));
610
1044
  okCount++;
611
- okNames.push(u.name);
1045
+ okNames.push(durationMs !== void 0 ? `${u.name}\uFF08${(durationMs / 1e3).toFixed(1)}s\uFF09` : u.name);
612
1046
  } catch (e) {
613
1047
  failures.push(`${u.name}\uFF1A${e instanceof Error ? e.message : String(e)}`);
1048
+ } finally {
1049
+ setUpdating(u.name, false);
614
1050
  }
615
1051
  }
616
1052
  setBusyUpdate(null);
617
1053
  if (failures.length === 0) {
618
1054
  showToast(t("updatedMany", { n: okCount }) + `\uFF08${okNames.join("\u3001")}\uFF09`, "ok", 6e3);
619
1055
  } else {
620
- showToast(t("updateSummary", { a: okCount, b: failures.length, c: failures.join("\uFF1B") }), "error", 8e3);
1056
+ showToast(t("updateSummary", { a: okCount, b: failures.length, c: failures.join("\uFF1B") }), "error", 15e3);
621
1057
  }
622
1058
  refreshUpdates();
623
1059
  };
@@ -628,13 +1064,14 @@ function CenterPanel({ variant = "section" }) {
628
1064
  count !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-count", children: count })
629
1065
  ] }, v);
630
1066
  const tabs = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-tabs", children: [
631
- tab("installed", t("tabInstalled"), installedCount),
632
- tab("market", t("tabMarket"), marketCount),
633
- tab("updates", t("tabUpdates"), updates?.length ?? 0)
1067
+ tab("installed", t("tabInstalled"), counts.installed),
1068
+ tab("market", t("tabMarket"), counts.market),
1069
+ tab("updates", t("tabUpdates"), updates?.length ?? 0),
1070
+ tab("diagnose", t("tabDiagnose"), null)
634
1071
  ] });
635
1072
  const head = (showTitle) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-head", children: [
636
1073
  showTitle && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-title", children: t("title") }),
637
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-sub", children: t("headSummary", { a: installedCount, b: updates?.length ?? 0, c: failedCount }) }),
1074
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-sub", children: t("headSummary", { a: counts.installed, b: updates?.length ?? 0, c: counts.failed }) }),
638
1075
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-spacer" }),
639
1076
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "pc-btn", disabled: checking, onClick: () => {
640
1077
  refreshUpdates();
@@ -643,61 +1080,131 @@ function CenterPanel({ variant = "section" }) {
643
1080
  void updateAll();
644
1081
  }, children: t("updateAll", { n: updates?.length ?? 0 }) })
645
1082
  ] });
646
- const installedToolbar = view === "installed" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-toolbar", children: [
647
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { className: "pc-search", value: search, onChange: (e) => {
648
- setSearch(e.target.value);
649
- }, placeholder: t("searchInstalled") }),
650
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", { className: "pc-select", value: installedSource ?? "", onChange: (e) => {
651
- setInstalledSource(e.target.value === "" ? null : e.target.value);
652
- }, children: [
653
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "", children: t("allSources") }),
654
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "official", children: t("srcOfficial") }),
655
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "installed", children: t("srcInstalled") }),
656
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "local", children: t("srcLocal") }),
657
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "builtin", children: t("srcBuiltin") })
1083
+ const installedToolbar = view === "installed" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-filter", children: [
1084
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-toolbar pc-toolbar-main", children: [
1085
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { className: "pc-search", value: search, onChange: (e) => {
1086
+ setSearch(e.target.value);
1087
+ }, placeholder: t("searchInstalled") }),
1088
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", { className: "pc-select", value: installedSource ?? "", onChange: (e) => {
1089
+ setInstalledSource(e.target.value === "" ? null : e.target.value);
1090
+ }, children: [
1091
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "", children: t("allSources") }),
1092
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "official", children: t("srcOfficial") }),
1093
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "installed", children: t("srcInstalled") }),
1094
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "local", children: t("srcLocal") }),
1095
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "builtin", children: t("srcBuiltin") })
1096
+ ] })
658
1097
  ] }),
659
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: `pc-chip${installedCategory === null ? " active" : ""}`, onClick: () => {
660
- setInstalledCategory(null);
661
- }, children: t("all") }),
662
- CATEGORIES.map((c) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: `pc-chip${installedCategory === c ? " active" : ""}`, onClick: () => {
663
- setInstalledCategory(c);
664
- }, children: c }, c))
1098
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-catbar", children: [
1099
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: `pc-chip${installedCategory === null ? " active" : ""}`, onClick: () => {
1100
+ setInstalledCategory(null);
1101
+ }, children: t("all") }),
1102
+ CATEGORIES.map((c) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: `pc-chip${installedCategory === c ? " active" : ""}`, onClick: () => {
1103
+ setInstalledCategory(c);
1104
+ }, children: c }, c))
1105
+ ] })
665
1106
  ] }) : null;
666
- const marketToolbar = view === "market" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-toolbar", children: [
667
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { className: "pc-search", value: marketSearch, onChange: (e) => {
668
- setMarketSearch(e.target.value);
669
- }, placeholder: t("searchMarket") }),
670
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", { className: "pc-select", value: source, onChange: (e) => {
671
- setSource(e.target.value);
672
- setCategory(null);
673
- }, children: [
674
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "awesome", children: "awesome-dsh-plugin" }),
675
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "oh-my-dsh", children: "Oh-My-DSH" }),
676
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "all", children: t("allMarkets") })
1107
+ const aiBar = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 6, flex: "none" }, children: [
1108
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-ai-row", children: [
1109
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1110
+ "input",
1111
+ {
1112
+ className: "pc-search",
1113
+ style: { flex: 1 },
1114
+ value: aiQuery,
1115
+ onChange: (e) => {
1116
+ setAiQuery(e.target.value);
1117
+ },
1118
+ onKeyDown: (e) => {
1119
+ if (e.key === "Enter") askAi();
1120
+ },
1121
+ placeholder: t("aiPlaceholder")
1122
+ }
1123
+ ),
1124
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "pc-btn primary", disabled: aiBusy || aiQuery.trim() === "", onClick: askAi, children: aiBusy ? t("aiAsking") : t("aiAsk") })
677
1125
  ] }),
678
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: `pc-chip${category === null ? " active" : ""}`, onClick: () => {
679
- setCategory(null);
680
- }, children: t("all") }),
681
- CATEGORIES.map((c) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: `pc-chip${category === c ? " active" : ""}`, onClick: () => {
682
- setCategory(c);
683
- }, children: c }, c)),
684
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-spacer" }),
685
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", title: single ? t("gridDouble") : t("gridSingle"), "aria-label": single ? t("gridDouble") : t("gridSingle"), className: "pc-iconbtn", onClick: () => {
686
- setSingle((v) => !v);
687
- }, children: single ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { width: "14", height: "14", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: [
688
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "2", y: "2", width: "5", height: "5", rx: "1" }),
689
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "9", y: "2", width: "5", height: "5", rx: "1" }),
690
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "2", y: "9", width: "5", height: "5", rx: "1" }),
691
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "9", y: "9", width: "5", height: "5", rx: "1" })
692
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { width: "14", height: "14", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: [
693
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "2", y: "2", width: "12", height: "5", rx: "1" }),
694
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "2", y: "9", width: "12", height: "5", rx: "1" })
695
- ] }) })
1126
+ aiError !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "pc-sub", style: { color: "var(--dsw-alias-state-error-primary)" }, children: t("aiFail", { e: aiError }) }),
1127
+ aiResult !== null && aiResult.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pc-grid single", children: aiResult.map((item) => {
1128
+ const installed = (installedCache ?? []).some((p) => p.name === item.name || p.spec === item.spec || p.name === item.spec);
1129
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-card", children: [
1130
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-row", children: [
1131
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-name", children: item.name }),
1132
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-spacer" }),
1133
+ item.stars != null && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "pc-ver", children: [
1134
+ "\u2605 ",
1135
+ item.stars
1136
+ ] }),
1137
+ installed ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-tag", children: t("installedTag") }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1138
+ "button",
1139
+ {
1140
+ className: "pc-btn primary",
1141
+ onClick: () => {
1142
+ if (item.spec === void 0 || item.spec === null || item.spec === "") {
1143
+ showToast(t("installFailed", { e: "\u672A\u627E\u5230\u8BE5\u63D2\u4EF6\u7684\u5B89\u88C5 spec" }), "error", 8e3);
1144
+ return;
1145
+ }
1146
+ void rpc("install", { spec: item.spec }).then(
1147
+ () => {
1148
+ pendingInstall.add(item.spec);
1149
+ showToast(t("installQueued", { n: item.name }), "ok", 5e3);
1150
+ },
1151
+ (e) => showToast(t("installFailed", { e: e instanceof Error ? e.message : String(e) }), "error", 15e3)
1152
+ );
1153
+ },
1154
+ children: t("install")
1155
+ }
1156
+ )
1157
+ ] }),
1158
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pc-desc", children: item.reason })
1159
+ ] }, item.name);
1160
+ }) })
1161
+ ] });
1162
+ const marketToolbar = view === "market" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-filter", children: [
1163
+ aiBar,
1164
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-toolbar pc-toolbar-main", children: [
1165
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { className: "pc-search", style: { flex: 1, minWidth: 120 }, value: marketSearch, onChange: (e) => {
1166
+ setMarketSearch(e.target.value);
1167
+ }, placeholder: t("searchMarket") }),
1168
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", { className: "pc-select", value: source, onChange: (e) => {
1169
+ setSource(e.target.value);
1170
+ setCategory(null);
1171
+ }, children: [
1172
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "awesome", children: "awesome-dsh-plugin" }),
1173
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "oh-my-dsh", children: "Oh-My-DSH" }),
1174
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("option", { value: "dsh-market", children: [
1175
+ "dsh-market\uFF08",
1176
+ counts.dshMarket,
1177
+ "\uFF09"
1178
+ ] }),
1179
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "all", children: t("allMarkets") })
1180
+ ] }),
1181
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-spacer" }),
1182
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", title: single ? t("gridDouble") : t("gridSingle"), "aria-label": single ? t("gridDouble") : t("gridSingle"), className: "pc-iconbtn", onClick: () => {
1183
+ setSingle((v) => !v);
1184
+ }, children: single ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { width: "14", height: "14", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: [
1185
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "2", y: "2", width: "5", height: "5", rx: "1" }),
1186
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "9", y: "2", width: "5", height: "5", rx: "1" }),
1187
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "2", y: "9", width: "5", height: "5", rx: "1" }),
1188
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "9", y: "9", width: "5", height: "5", rx: "1" })
1189
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { width: "14", height: "14", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: [
1190
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "2", y: "2", width: "12", height: "5", rx: "1" }),
1191
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "2", y: "9", width: "12", height: "5", rx: "1" })
1192
+ ] }) })
1193
+ ] }),
1194
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-catbar", children: [
1195
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: `pc-chip${category === null ? " active" : ""}`, onClick: () => {
1196
+ setCategory(null);
1197
+ }, children: t("all") }),
1198
+ CATEGORIES.map((c) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: `pc-chip${category === c ? " active" : ""}`, onClick: () => {
1199
+ setCategory(c);
1200
+ }, children: c }, c))
1201
+ ] })
696
1202
  ] }) : null;
697
1203
  const body = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
698
- view === "installed" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(InstalledView, { search, category: installedCategory, source: installedSource }),
699
- view === "market" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MarketView, { category, single, source, search: marketSearch, onCount: setMarketCount }),
700
- view === "updates" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(UpdatesView, { updates, refresh: refreshUpdates, updateOne, busy: busyUpdate })
1204
+ view === "installed" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(InstalledView, { search, category: installedCategory, source: installedSource, onToggle: handleToggle, togglingId }),
1205
+ view === "market" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MarketView, { category, single, source, search: marketSearch, onCount: handleMarketCount }),
1206
+ view === "updates" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(UpdatesView, { updates, refresh: refreshUpdates, updateOne, busy: busyUpdate }),
1207
+ view === "diagnose" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DiagnoseView, {})
701
1208
  ] });
702
1209
  if (variant === "overlay") {
703
1210
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", flexDirection: "column", flex: 1, minHeight: 0 }, children: [
@@ -751,7 +1258,7 @@ function OverlayPanel() {
751
1258
  function Toast() {
752
1259
  const t = useToast();
753
1260
  if (t === null) return null;
754
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: `pc-toast ${t.kind}`, children: t.message });
1261
+ return (0, import_react_dom.createPortal)(/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: `pc-toast ${t.kind}`, children: t.message }), document.body);
755
1262
  }
756
1263
  function WhatsNewDialog() {
757
1264
  const t = useT();
@@ -822,6 +1329,7 @@ function cleanupGlobals() {
822
1329
  }
823
1330
  function apply(ctx) {
824
1331
  injectCss();
1332
+ ctx.effect?.(() => registerSettingsNavIcon(() => STRINGS[localeId].title), "dsh-plugin-center: settings navigation icon");
825
1333
  if (window.__pluginCenterGlobalsInstalled !== true) {
826
1334
  installGlobals();
827
1335
  window.addEventListener("unload", cleanupGlobals);