dsh-windows-mcp 0.2.0 → 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/CHANGELOG.md +24 -0
- package/README.md +12 -1
- package/README.zh.md +12 -1
- package/lib/client.js +594 -1
- package/lib/dependency.js +66 -5
- package/lib/index.js +119 -6
- package/lib/mirrors.js +9 -1
- package/lib/settings.js +251 -0
- package/lib/updater.js +9 -3
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -32,6 +32,14 @@ window.__ModuleLoader__.load({
|
|
|
32
32
|
var PREPARE_CANCEL_PATH = "/dsh-windows-mcp/prepare/cancel";
|
|
33
33
|
var BRIDGE_PATH = "/dsh-windows-mcp/bridge";
|
|
34
34
|
var UPDATE_PATH = "/dsh-windows-mcp/update";
|
|
35
|
+
var SETTINGS_PATH = "/dsh-windows-mcp/settings";
|
|
36
|
+
var SETTINGS_SLOT = "settings.section";
|
|
37
|
+
var SETTINGS_ENTRY_ID = "windows-mcp-settings";
|
|
38
|
+
var SETTINGS_ORDER = 62;
|
|
39
|
+
var SETTINGS_NAV_ICON_MARKER = "data-dwmc-nav-icon";
|
|
40
|
+
var SETTINGS_NAV_ICON_STYLE_ID = PACKAGE_ID + "-settings-nav-icon-styles";
|
|
41
|
+
var SETTINGS_NAV_ROW_SELECTOR = '[role="dialog"] nav button';
|
|
42
|
+
var REPO_URL = "https://github.com/helloHub1212/dsh-windows-mcp";
|
|
35
43
|
var STYLE_ID = PACKAGE_ID + "-computer-use-styles";
|
|
36
44
|
var LABEL = "Computer Use";
|
|
37
45
|
/** Only `slots` is injected: it is certainly provided, and a declaration for
|
|
@@ -91,6 +99,35 @@ window.__ModuleLoader__.load({
|
|
|
91
99
|
"color:var(--dsw-alias-label-secondary);white-space:pre-wrap;word-break:break-all}",
|
|
92
100
|
".dwmc-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px}",
|
|
93
101
|
"@media (prefers-reduced-motion:reduce){.dwmc-progress>i{animation:none}}",
|
|
102
|
+
/* Settings page: plain cards, one control per row, theme tokens only. */
|
|
103
|
+
".dwmc-set{display:flex;flex-direction:column;gap:18px;max-width:720px;",
|
|
104
|
+
"color:var(--dsw-alias-label-primary);font-size:13px;line-height:1.55}",
|
|
105
|
+
".dwmc-set__head{display:flex;flex-direction:column;gap:4px}",
|
|
106
|
+
".dwmc-set__title{margin:0;font-size:16px;font-weight:600}",
|
|
107
|
+
".dwmc-set__sub{margin:0;font-size:12px;color:var(--dsw-alias-label-secondary)}",
|
|
108
|
+
".dwmc-card{border:1px solid var(--dsw-alias-border-l1);border-radius:12px;",
|
|
109
|
+
"background:var(--dsw-alias-bg-layer-1);padding:14px 16px;display:flex;flex-direction:column;gap:12px}",
|
|
110
|
+
".dwmc-card__title{margin:0;font-size:13px;font-weight:600}",
|
|
111
|
+
".dwmc-field{display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap}",
|
|
112
|
+
".dwmc-field__label{display:flex;flex-direction:column;gap:2px;min-width:180px}",
|
|
113
|
+
".dwmc-field__hint{font-size:12px;color:var(--dsw-alias-label-secondary)}",
|
|
114
|
+
".dwmc-input,.dwmc-select{font:inherit;font-size:12px;color:var(--dsw-alias-label-primary);",
|
|
115
|
+
"background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l1);",
|
|
116
|
+
"border-radius:8px;padding:5px 8px;min-width:120px}",
|
|
117
|
+
".dwmc-input:focus-visible,.dwmc-select:focus-visible{outline:2px solid",
|
|
118
|
+
"color-mix(in srgb,var(--dsw-alias-brand-primary) 45%,transparent);outline-offset:1px}",
|
|
119
|
+
".dwmc-checks{display:flex;flex-wrap:wrap;gap:6px 14px}",
|
|
120
|
+
".dwmc-check{display:inline-flex;align-items:center;gap:6px;font-size:12px;",
|
|
121
|
+
"color:var(--dsw-alias-label-primary);cursor:pointer}",
|
|
122
|
+
".dwmc-check input{accent-color:var(--dsw-alias-brand-primary);margin:0}",
|
|
123
|
+
".dwmc-set__actions{display:flex;align-items:center;gap:10px;flex-wrap:wrap}",
|
|
124
|
+
".dwmc-star{display:inline-flex;align-items:center;gap:7px;height:30px;padding:0 14px;font:inherit;",
|
|
125
|
+
"font-size:12px;cursor:pointer;border-radius:8px;border:1px solid transparent;background:transparent;",
|
|
126
|
+
"color:var(--dsw-alias-brand-primary);",
|
|
127
|
+
"box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--dsw-alias-brand-primary) 42%,transparent)}",
|
|
128
|
+
".dwmc-star:hover{background:color-mix(in srgb,var(--dsw-alias-brand-primary) 12%,transparent)}",
|
|
129
|
+
".dwmc-star svg{width:14px;height:14px;display:block}",
|
|
130
|
+
".dwmc-saved{font-size:12px;color:var(--dsw-alias-state-success-primary)}",
|
|
94
131
|
].join("");
|
|
95
132
|
|
|
96
133
|
/** Inject the stylesheet once. Failures are cosmetic and never fatal. */
|
|
@@ -317,6 +354,37 @@ window.__ModuleLoader__.load({
|
|
|
317
354
|
versionInstalled: "版本已切换,重启 DSH 生效。",
|
|
318
355
|
noPrevious: "没有可回退的版本",
|
|
319
356
|
updateHint: "更新只下载新版本、旧版本原样保留;出问题一键回退,不需要重装依赖。",
|
|
357
|
+
settingsTitle: "Windows-MCP 参数",
|
|
358
|
+
settingsNav: "桌面控制",
|
|
359
|
+
settingsSub: "调整桥接与更新行为;桥接参数写入配置后需要重启 DSH 才生效。",
|
|
360
|
+
settingsRestart: "桥接参数已写入配置,重启 DSH 后生效。",
|
|
361
|
+
settingsSaved: "已保存",
|
|
362
|
+
settingsSave: "保存",
|
|
363
|
+
settingsSaving: "保存中…",
|
|
364
|
+
settingsRevert: "还原",
|
|
365
|
+
settingsFailed: "保存失败",
|
|
366
|
+
settingsIgnored: "部分取值无效,已回退为默认值:",
|
|
367
|
+
sectionBridge: "桥接(重启 DSH 生效)",
|
|
368
|
+
sectionUpdates: "更新与镜像",
|
|
369
|
+
sectionAbout: "关于",
|
|
370
|
+
timeoutLabel: "工具调用超时",
|
|
371
|
+
timeoutHint: "毫秒,5000–600000。越长越不容易被中断,卡住时也等得越久。",
|
|
372
|
+
scaleLabel: "截图缩放",
|
|
373
|
+
scaleHint: "越小越省视觉 token,细节也越少。",
|
|
374
|
+
backendLabel: "截图后端",
|
|
375
|
+
backendHint: "auto 交给上游挑;dxcam 最快,pillow 最慢但最稳。",
|
|
376
|
+
flashLabel: "截图闪光边框",
|
|
377
|
+
flashHint: "关掉可少一次桌面重绘;默认关闭。",
|
|
378
|
+
excludeLabel: "屏蔽的工具",
|
|
379
|
+
excludeHint: "屏蔽的工具不会出现在模型工具表里,能省上下文。",
|
|
380
|
+
autoCheckLabel: "启动时检查更新",
|
|
381
|
+
autoCheckHint: "每次启动后台查一次最新版,结果缓存 6 小时。",
|
|
382
|
+
mirrorLabel: "首选镜像",
|
|
383
|
+
mirrorHint: "auto 用测速最快的镜像;固定某个可避开波动。",
|
|
384
|
+
starLabel: "点个 star",
|
|
385
|
+
starHint: "觉得有用就给个 star,插件会继续维护。",
|
|
386
|
+
aboutRepo: "仓库",
|
|
387
|
+
aboutVersion: "版本",
|
|
320
388
|
},
|
|
321
389
|
en: {
|
|
322
390
|
title: "Windows desktop automation (Windows-MCP)",
|
|
@@ -360,6 +428,37 @@ window.__ModuleLoader__.load({
|
|
|
360
428
|
versionInstalled: "Version switched — restart DSH to apply it.",
|
|
361
429
|
noPrevious: "no previous version to roll back to",
|
|
362
430
|
updateHint: "An update only downloads the new build; the old one is kept as-is. Roll back with one click — no reinstall.",
|
|
431
|
+
settingsTitle: "Windows-MCP settings",
|
|
432
|
+
settingsNav: "Desktop",
|
|
433
|
+
settingsSub: "Tune the bridge and the update behaviour. Bridge values apply after a DSH restart.",
|
|
434
|
+
settingsRestart: "Bridge values were written to the patch layer — restart DSH to apply them.",
|
|
435
|
+
settingsSaved: "Saved",
|
|
436
|
+
settingsSave: "Save",
|
|
437
|
+
settingsSaving: "Saving…",
|
|
438
|
+
settingsRevert: "Revert",
|
|
439
|
+
settingsFailed: "Could not save",
|
|
440
|
+
settingsIgnored: "Some values were invalid and fell back to the defaults:",
|
|
441
|
+
sectionBridge: "Bridge (applies after a DSH restart)",
|
|
442
|
+
sectionUpdates: "Updates and mirrors",
|
|
443
|
+
sectionAbout: "About",
|
|
444
|
+
timeoutLabel: "Tool call timeout",
|
|
445
|
+
timeoutHint: "Milliseconds, 5000–600000. Longer waits are interrupted less often, and stall longer when something hangs.",
|
|
446
|
+
scaleLabel: "Screenshot scale",
|
|
447
|
+
scaleHint: "Smaller costs fewer vision tokens and shows less detail.",
|
|
448
|
+
backendLabel: "Screenshot backend",
|
|
449
|
+
backendHint: "auto lets the server choose; dxcam is fastest, pillow slowest but most compatible.",
|
|
450
|
+
flashLabel: "Screenshot flash border",
|
|
451
|
+
flashHint: "Turning it off saves one desktop repaint; off by default.",
|
|
452
|
+
excludeLabel: "Hidden tools",
|
|
453
|
+
excludeHint: "Hidden tools stay out of the model's tool table, which saves context.",
|
|
454
|
+
autoCheckLabel: "Check for updates at boot",
|
|
455
|
+
autoCheckHint: "One background check per boot, cached for six hours.",
|
|
456
|
+
mirrorLabel: "Preferred mirror",
|
|
457
|
+
mirrorHint: "auto uses the fastest probe; pinning one avoids jitter.",
|
|
458
|
+
starLabel: "Star on GitHub",
|
|
459
|
+
starHint: "If it helps, a star keeps the plugin maintained.",
|
|
460
|
+
aboutRepo: "Repository",
|
|
461
|
+
aboutVersion: "Version",
|
|
363
462
|
},
|
|
364
463
|
};
|
|
365
464
|
|
|
@@ -641,6 +740,352 @@ window.__ModuleLoader__.load({
|
|
|
641
740
|
return React.createElement(WindowsMcpPanel, { __compact: true });
|
|
642
741
|
}
|
|
643
742
|
|
|
743
|
+
/* ------------------------------------------------------------------ *
|
|
744
|
+
* settings page
|
|
745
|
+
* ------------------------------------------------------------------ */
|
|
746
|
+
|
|
747
|
+
var SCALE_CHOICES = [0.25, 0.5, 0.75, 1];
|
|
748
|
+
var BACKEND_CHOICES = ["auto", "dxcam", "mss", "pillow"];
|
|
749
|
+
var MIRROR_CHOICES = ["auto", "ustc", "aliyun", "tencent", "huawei", "nju", "tuna", "pypi"];
|
|
750
|
+
var EXCLUDE_CHOICES = ["App", "PowerShell", "FileSystem"];
|
|
751
|
+
|
|
752
|
+
/** A mutable copy of the host settings, so a form never edits state in place. */
|
|
753
|
+
function cloneSettings(input) {
|
|
754
|
+
var bridge = input && input.bridge ? input.bridge : {};
|
|
755
|
+
var plugin = input && input.plugin ? input.plugin : {};
|
|
756
|
+
return {
|
|
757
|
+
bridge: {
|
|
758
|
+
toolCallTimeoutMs: Number(bridge.toolCallTimeoutMs),
|
|
759
|
+
excludeTools: Array.isArray(bridge.excludeTools) ? bridge.excludeTools.slice() : [],
|
|
760
|
+
screenshotScale: Number(bridge.screenshotScale),
|
|
761
|
+
screenshotBackend: String(bridge.screenshotBackend || "auto"),
|
|
762
|
+
disableFlash: bridge.disableFlash === true,
|
|
763
|
+
},
|
|
764
|
+
plugin: {
|
|
765
|
+
autoCheckUpdates: plugin.autoCheckUpdates === true,
|
|
766
|
+
preferredMirror: String(plugin.preferredMirror || "auto"),
|
|
767
|
+
},
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
/** GET/POST the host settings; only the settings page mounts this. */
|
|
772
|
+
function useSettings() {
|
|
773
|
+
var pair = React.useState({ phase: "loading", view: null, error: null });
|
|
774
|
+
var view = pair[0];
|
|
775
|
+
var setView = pair[1];
|
|
776
|
+
var savedPair = React.useState({ phase: "idle", body: null, error: null });
|
|
777
|
+
var saved = savedPair[0];
|
|
778
|
+
var setSaved = savedPair[1];
|
|
779
|
+
|
|
780
|
+
var load = React.useCallback(function () {
|
|
781
|
+
return fetch(apiUrl(SETTINGS_PATH), { headers: { accept: "application/json" } })
|
|
782
|
+
.then(function (response) {
|
|
783
|
+
return response.json();
|
|
784
|
+
})
|
|
785
|
+
.then(function (body) {
|
|
786
|
+
setView({ phase: "ready", view: body, error: null });
|
|
787
|
+
return body;
|
|
788
|
+
})
|
|
789
|
+
.catch(function (error) {
|
|
790
|
+
setView({ phase: "error", view: null, error: String(error && error.message ? error.message : error) });
|
|
791
|
+
});
|
|
792
|
+
}, []);
|
|
793
|
+
|
|
794
|
+
React.useEffect(
|
|
795
|
+
function () {
|
|
796
|
+
load();
|
|
797
|
+
},
|
|
798
|
+
[load],
|
|
799
|
+
);
|
|
800
|
+
|
|
801
|
+
var save = React.useCallback(function (next) {
|
|
802
|
+
setSaved({ phase: "saving", body: null, error: null });
|
|
803
|
+
return fetch(apiUrl(SETTINGS_PATH), {
|
|
804
|
+
method: "POST",
|
|
805
|
+
headers: { accept: "application/json", "content-type": "application/json" },
|
|
806
|
+
body: JSON.stringify({ settings: next }),
|
|
807
|
+
})
|
|
808
|
+
.then(function (response) {
|
|
809
|
+
return response.json();
|
|
810
|
+
})
|
|
811
|
+
.then(function (body) {
|
|
812
|
+
setView({ phase: "ready", view: body, error: null });
|
|
813
|
+
setSaved({ phase: body && body.ok === false ? "error" : "saved", body: body, error: null });
|
|
814
|
+
return body;
|
|
815
|
+
})
|
|
816
|
+
.catch(function (error) {
|
|
817
|
+
setSaved({ phase: "error", body: null, error: String(error && error.message ? error.message : error) });
|
|
818
|
+
return null;
|
|
819
|
+
});
|
|
820
|
+
}, []);
|
|
821
|
+
|
|
822
|
+
return { view: view, saved: saved, save: save, reload: load };
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
function starIcon() {
|
|
826
|
+
return React.createElement(
|
|
827
|
+
"svg",
|
|
828
|
+
{ viewBox: "0 0 16 16", fill: "currentColor", "aria-hidden": "true" },
|
|
829
|
+
React.createElement("path", {
|
|
830
|
+
d: "M8 1.2l2.06 4.18 4.61.67-3.34 3.25.79 4.6L8 11.73l-4.12 2.17.79-4.6L1.33 6.05l4.61-.67z",
|
|
831
|
+
}),
|
|
832
|
+
);
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
/** Settings → left rail: the plugin's parameter page. */
|
|
836
|
+
function SettingsPage() {
|
|
837
|
+
var t = copy();
|
|
838
|
+
var host = useSettings();
|
|
839
|
+
var view = host.view;
|
|
840
|
+
var draftPair = React.useState(null);
|
|
841
|
+
var draft = draftPair[0];
|
|
842
|
+
var setDraft = draftPair[1];
|
|
843
|
+
|
|
844
|
+
var ready = view.phase === "ready" && view.view && view.view.settings ? view.view : null;
|
|
845
|
+
|
|
846
|
+
React.useEffect(
|
|
847
|
+
function () {
|
|
848
|
+
if (ready !== null) setDraft(cloneSettings(ready.settings));
|
|
849
|
+
},
|
|
850
|
+
[ready === null ? null : JSON.stringify(ready.settings)],
|
|
851
|
+
);
|
|
852
|
+
|
|
853
|
+
if (view.phase === "loading") return React.createElement("p", { className: "dwmc-note" }, t.checking);
|
|
854
|
+
if (view.phase === "error") return React.createElement("p", { className: "dwmc-error" }, t.hostDown + " — " + String(view.error || ""));
|
|
855
|
+
if (draft === null || ready === null) return React.createElement("p", { className: "dwmc-note" }, t.checking);
|
|
856
|
+
|
|
857
|
+
var dirty = JSON.stringify(draft) !== JSON.stringify(cloneSettings(ready.settings));
|
|
858
|
+
var saving = host.saved.phase === "saving";
|
|
859
|
+
|
|
860
|
+
var update = function (section, key, value) {
|
|
861
|
+
var next = cloneSettings(draft);
|
|
862
|
+
next[section][key] = value;
|
|
863
|
+
setDraft(next);
|
|
864
|
+
};
|
|
865
|
+
var toggleExclude = function (name) {
|
|
866
|
+
var next = cloneSettings(draft);
|
|
867
|
+
var at = next.bridge.excludeTools.indexOf(name);
|
|
868
|
+
if (at === -1) next.bridge.excludeTools.push(name);
|
|
869
|
+
else next.bridge.excludeTools.splice(at, 1);
|
|
870
|
+
setDraft(next);
|
|
871
|
+
};
|
|
872
|
+
var save = function () {
|
|
873
|
+
host.save(draft).then(function (body) {
|
|
874
|
+
if (body && body.settings) setDraft(cloneSettings(body.settings));
|
|
875
|
+
});
|
|
876
|
+
};
|
|
877
|
+
|
|
878
|
+
var field = function (label, hint, control) {
|
|
879
|
+
return React.createElement(
|
|
880
|
+
"div",
|
|
881
|
+
{ className: "dwmc-field", key: label },
|
|
882
|
+
React.createElement(
|
|
883
|
+
"div",
|
|
884
|
+
{ className: "dwmc-field__label" },
|
|
885
|
+
React.createElement("span", null, label),
|
|
886
|
+
hint ? React.createElement("span", { className: "dwmc-field__hint" }, hint) : null,
|
|
887
|
+
),
|
|
888
|
+
control,
|
|
889
|
+
);
|
|
890
|
+
};
|
|
891
|
+
var check = function (label, checked, onChange, key) {
|
|
892
|
+
return React.createElement(
|
|
893
|
+
"label",
|
|
894
|
+
{ className: "dwmc-check", key: key || label },
|
|
895
|
+
React.createElement("input", { type: "checkbox", checked: checked === true, onChange: function (event) { onChange(event.target.checked); } }),
|
|
896
|
+
label,
|
|
897
|
+
);
|
|
898
|
+
};
|
|
899
|
+
var card = function (title, children) {
|
|
900
|
+
return React.createElement(
|
|
901
|
+
"section",
|
|
902
|
+
{ className: "dwmc-card" },
|
|
903
|
+
React.createElement("h3", { className: "dwmc-card__title" }, title),
|
|
904
|
+
children,
|
|
905
|
+
);
|
|
906
|
+
};
|
|
907
|
+
|
|
908
|
+
var bridgeCard = card(t.sectionBridge, [
|
|
909
|
+
field(
|
|
910
|
+
t.timeoutLabel,
|
|
911
|
+
t.timeoutHint,
|
|
912
|
+
React.createElement("input", {
|
|
913
|
+
className: "dwmc-input",
|
|
914
|
+
type: "number",
|
|
915
|
+
min: 5000,
|
|
916
|
+
max: 600000,
|
|
917
|
+
step: 1000,
|
|
918
|
+
value: String(draft.bridge.toolCallTimeoutMs),
|
|
919
|
+
onChange: function (event) {
|
|
920
|
+
update("bridge", "toolCallTimeoutMs", Number(event.target.value));
|
|
921
|
+
},
|
|
922
|
+
}),
|
|
923
|
+
),
|
|
924
|
+
field(
|
|
925
|
+
t.scaleLabel,
|
|
926
|
+
t.scaleHint,
|
|
927
|
+
React.createElement(
|
|
928
|
+
"select",
|
|
929
|
+
{
|
|
930
|
+
className: "dwmc-select",
|
|
931
|
+
value: String(draft.bridge.screenshotScale),
|
|
932
|
+
onChange: function (event) {
|
|
933
|
+
update("bridge", "screenshotScale", Number(event.target.value));
|
|
934
|
+
},
|
|
935
|
+
},
|
|
936
|
+
SCALE_CHOICES.map(function (scale) {
|
|
937
|
+
return React.createElement("option", { key: String(scale), value: String(scale) }, String(scale));
|
|
938
|
+
}),
|
|
939
|
+
),
|
|
940
|
+
),
|
|
941
|
+
field(
|
|
942
|
+
t.backendLabel,
|
|
943
|
+
t.backendHint,
|
|
944
|
+
React.createElement(
|
|
945
|
+
"select",
|
|
946
|
+
{
|
|
947
|
+
className: "dwmc-select",
|
|
948
|
+
value: draft.bridge.screenshotBackend,
|
|
949
|
+
onChange: function (event) {
|
|
950
|
+
update("bridge", "screenshotBackend", event.target.value);
|
|
951
|
+
},
|
|
952
|
+
},
|
|
953
|
+
BACKEND_CHOICES.map(function (backend) {
|
|
954
|
+
return React.createElement("option", { key: backend, value: backend }, backend);
|
|
955
|
+
}),
|
|
956
|
+
),
|
|
957
|
+
),
|
|
958
|
+
field(
|
|
959
|
+
t.flashLabel,
|
|
960
|
+
t.flashHint,
|
|
961
|
+
React.createElement("input", {
|
|
962
|
+
type: "checkbox",
|
|
963
|
+
checked: draft.bridge.disableFlash === false ? false : true,
|
|
964
|
+
onChange: function (event) {
|
|
965
|
+
update("bridge", "disableFlash", event.target.checked);
|
|
966
|
+
},
|
|
967
|
+
}),
|
|
968
|
+
),
|
|
969
|
+
field(
|
|
970
|
+
t.excludeLabel,
|
|
971
|
+
t.excludeHint,
|
|
972
|
+
React.createElement(
|
|
973
|
+
"div",
|
|
974
|
+
{ className: "dwmc-checks" },
|
|
975
|
+
EXCLUDE_CHOICES.map(function (name) {
|
|
976
|
+
return check(name, draft.bridge.excludeTools.indexOf(name) !== -1, function () { toggleExclude(name); }, name);
|
|
977
|
+
}),
|
|
978
|
+
),
|
|
979
|
+
),
|
|
980
|
+
]);
|
|
981
|
+
|
|
982
|
+
var updatesCard = card(t.sectionUpdates, [
|
|
983
|
+
field(
|
|
984
|
+
t.autoCheckLabel,
|
|
985
|
+
t.autoCheckHint,
|
|
986
|
+
React.createElement("input", {
|
|
987
|
+
type: "checkbox",
|
|
988
|
+
checked: draft.plugin.autoCheckUpdates === true,
|
|
989
|
+
onChange: function (event) {
|
|
990
|
+
update("plugin", "autoCheckUpdates", event.target.checked);
|
|
991
|
+
},
|
|
992
|
+
}),
|
|
993
|
+
),
|
|
994
|
+
field(
|
|
995
|
+
t.mirrorLabel,
|
|
996
|
+
t.mirrorHint,
|
|
997
|
+
React.createElement(
|
|
998
|
+
"select",
|
|
999
|
+
{
|
|
1000
|
+
className: "dwmc-select",
|
|
1001
|
+
value: draft.plugin.preferredMirror,
|
|
1002
|
+
onChange: function (event) {
|
|
1003
|
+
update("plugin", "preferredMirror", event.target.value);
|
|
1004
|
+
},
|
|
1005
|
+
},
|
|
1006
|
+
MIRROR_CHOICES.map(function (mirror) {
|
|
1007
|
+
return React.createElement("option", { key: mirror, value: mirror }, mirror);
|
|
1008
|
+
}),
|
|
1009
|
+
),
|
|
1010
|
+
),
|
|
1011
|
+
]);
|
|
1012
|
+
|
|
1013
|
+
var notes = [];
|
|
1014
|
+
if (host.saved.phase === "saved") notes.push(React.createElement("span", { key: "saved", className: "dwmc-saved" }, t.settingsSaved));
|
|
1015
|
+
if (host.saved.phase === "error") notes.push(React.createElement("span", { key: "err", className: "dwmc-error" }, t.settingsFailed));
|
|
1016
|
+
if (ready.restartRequired && host.saved.phase !== "saving") {
|
|
1017
|
+
notes.push(React.createElement("span", { key: "restart", className: "dwmc-warn" }, t.settingsRestart));
|
|
1018
|
+
}
|
|
1019
|
+
if (host.saved.body && Array.isArray(host.saved.body.problems) && host.saved.body.problems.length > 0) {
|
|
1020
|
+
notes.push(React.createElement("span", { key: "ignored", className: "dwmc-warn" }, t.settingsIgnored + " " + host.saved.body.problems.join(", ")));
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
var aboutCard = card(t.sectionAbout, [
|
|
1024
|
+
React.createElement(
|
|
1025
|
+
"div",
|
|
1026
|
+
{ className: "dwmc-rows" },
|
|
1027
|
+
React.createElement("dt", null, t.aboutVersion),
|
|
1028
|
+
React.createElement("dd", { className: "dwmc-mono" }, "v" + String(ready.pluginVersion || "?")),
|
|
1029
|
+
React.createElement("dt", null, t.aboutRepo),
|
|
1030
|
+
React.createElement(
|
|
1031
|
+
"dd",
|
|
1032
|
+
null,
|
|
1033
|
+
React.createElement("a", { className: "dwmc-mono", href: REPO_URL, target: "_blank", rel: "noopener noreferrer" }, REPO_URL.replace("https://", "")),
|
|
1034
|
+
),
|
|
1035
|
+
),
|
|
1036
|
+
React.createElement(
|
|
1037
|
+
"div",
|
|
1038
|
+
{ className: "dwmc-set__actions" },
|
|
1039
|
+
React.createElement(
|
|
1040
|
+
"button",
|
|
1041
|
+
{
|
|
1042
|
+
type: "button",
|
|
1043
|
+
className: "dwmc-star",
|
|
1044
|
+
onClick: function () {
|
|
1045
|
+
try {
|
|
1046
|
+
window.open(REPO_URL, "_blank", "noopener,noreferrer");
|
|
1047
|
+
} catch (error) {
|
|
1048
|
+
/* a blocked popup is not worth failing the page over */
|
|
1049
|
+
}
|
|
1050
|
+
},
|
|
1051
|
+
},
|
|
1052
|
+
starIcon(),
|
|
1053
|
+
t.starLabel,
|
|
1054
|
+
),
|
|
1055
|
+
React.createElement("span", { className: "dwmc-note" }, t.starHint),
|
|
1056
|
+
),
|
|
1057
|
+
]);
|
|
1058
|
+
|
|
1059
|
+
return React.createElement(
|
|
1060
|
+
"div",
|
|
1061
|
+
{ className: "dwmc-set" },
|
|
1062
|
+
React.createElement(
|
|
1063
|
+
"div",
|
|
1064
|
+
{ className: "dwmc-set__head" },
|
|
1065
|
+
React.createElement("h2", { className: "dwmc-set__title" }, t.settingsTitle),
|
|
1066
|
+
React.createElement("p", { className: "dwmc-set__sub" }, t.settingsSub),
|
|
1067
|
+
),
|
|
1068
|
+
bridgeCard,
|
|
1069
|
+
updatesCard,
|
|
1070
|
+
aboutCard,
|
|
1071
|
+
React.createElement(
|
|
1072
|
+
"div",
|
|
1073
|
+
{ className: "dwmc-set__actions" },
|
|
1074
|
+
React.createElement(
|
|
1075
|
+
"button",
|
|
1076
|
+
{ type: "button", className: "dwmc-btn", onClick: save, disabled: saving || !dirty },
|
|
1077
|
+
saving ? t.settingsSaving : t.settingsSave,
|
|
1078
|
+
),
|
|
1079
|
+
React.createElement(
|
|
1080
|
+
"button",
|
|
1081
|
+
{ type: "button", className: "dwmc-btn dwmc-btn--quiet", onClick: function () { setDraft(cloneSettings(ready.settings)); }, disabled: saving || !dirty },
|
|
1082
|
+
t.settingsRevert,
|
|
1083
|
+
),
|
|
1084
|
+
notes.length > 0 ? React.createElement("span", { className: "dwmc-set__actions" }, notes) : null,
|
|
1085
|
+
),
|
|
1086
|
+
);
|
|
1087
|
+
}
|
|
1088
|
+
|
|
644
1089
|
/* ------------------------------------------------------------------ *
|
|
645
1090
|
* registration
|
|
646
1091
|
* ------------------------------------------------------------------ */
|
|
@@ -714,6 +1159,135 @@ window.__ModuleLoader__.load({
|
|
|
714
1159
|
}
|
|
715
1160
|
}
|
|
716
1161
|
|
|
1162
|
+
/**
|
|
1163
|
+
* Settings → left rail. `settings.section` is a list slot: one page per
|
|
1164
|
+
* registered entry, ordered by `order`, labelled by `label()`.
|
|
1165
|
+
*/
|
|
1166
|
+
/**
|
|
1167
|
+
* The glyph of the settings entry: a monitor with a cursor cut out of its
|
|
1168
|
+
* screen ("this page drives the desktop"). `settings.section` has no icon
|
|
1169
|
+
* field, so the entry is painted on the shell's nav row: the row is marked
|
|
1170
|
+
* by matching its visible text against our own localized label, the shell's
|
|
1171
|
+
* gear is hidden and a mask-image painted in `currentColor` takes its place.
|
|
1172
|
+
*/
|
|
1173
|
+
function settingsNavMaskSvg() {
|
|
1174
|
+
return [
|
|
1175
|
+
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="#000">',
|
|
1176
|
+
'<path fill-rule="evenodd" d="M2.6 2.6h10.8c.66 0 1.2.54 1.2 1.2v6.4c0 .66-.54 1.2-1.2 1.2H2.6',
|
|
1177
|
+
'c-.66 0-1.2-.54-1.2-1.2V3.8c0-.66.54-1.2 1.2-1.2Zm3.1 2.5 4.5 2.85-2.1.36.98 2.06-1.16.55',
|
|
1178
|
+
'-0.97-2.05-1.3 1.05z"/>',
|
|
1179
|
+
'<rect x="7" y="11.2" width="2" height="1.7" rx=".3"/>',
|
|
1180
|
+
'<rect x="5.2" y="12.9" width="5.6" height="1.4" rx=".7"/>',
|
|
1181
|
+
"</svg>",
|
|
1182
|
+
].join("");
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
/** Encoded as a CSS `mask-image` source; the mask reads alpha only. */
|
|
1186
|
+
function settingsNavMaskUrl(svg) {
|
|
1187
|
+
var source = typeof svg === "string" ? svg : settingsNavMaskSvg();
|
|
1188
|
+
return "data:image/svg+xml," + encodeURIComponent(source).replace(/'/gu, "%27");
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
/**
|
|
1192
|
+
* Whether a nav row is this plugin's own. Pure: the row whose visible text
|
|
1193
|
+
* is the label the shell currently projects, trimmed. An empty label matches
|
|
1194
|
+
* nothing, so a locale that has not resolved yet never marks the whole nav.
|
|
1195
|
+
*/
|
|
1196
|
+
function isOwnSettingsNavRow(rowText, wantedLabel) {
|
|
1197
|
+
var wanted = String(wantedLabel === null || wantedLabel === undefined ? "" : wantedLabel).trim();
|
|
1198
|
+
if (wanted.length === 0) return false;
|
|
1199
|
+
return String(rowText === null || rowText === undefined ? "" : rowText).trim() === wanted;
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
/** Stylesheet for the marked row: hide the shell icon, draw ours. */
|
|
1203
|
+
function settingsNavIconCss(maskUrl) {
|
|
1204
|
+
var marker = "[" + SETTINGS_NAV_ICON_MARKER + "]";
|
|
1205
|
+
return [
|
|
1206
|
+
marker + " > svg{display:none}",
|
|
1207
|
+
marker + "::before{content:'';flex:none;width:16px;height:16px;",
|
|
1208
|
+
"background-color:currentColor;",
|
|
1209
|
+
"-webkit-mask-image:url('" + maskUrl + "');mask-image:url('" + maskUrl + "');",
|
|
1210
|
+
"-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;",
|
|
1211
|
+
"-webkit-mask-position:center;mask-position:center;",
|
|
1212
|
+
"-webkit-mask-size:16px 16px;mask-size:16px 16px}",
|
|
1213
|
+
].join("");
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
/**
|
|
1217
|
+
* Install the nav glyph. Idempotent, and a no-op outside a browser. The
|
|
1218
|
+
* observer follows re-renders and locale switches, so the text and the
|
|
1219
|
+
* glyph can never disagree; the marker and the stylesheet belong to the
|
|
1220
|
+
* fiber when the client context offers `effect`.
|
|
1221
|
+
*/
|
|
1222
|
+
function installSettingsNavIcon(ctx) {
|
|
1223
|
+
if (typeof document === "undefined" || typeof MutationObserver !== "function") return null;
|
|
1224
|
+
var install = function () {
|
|
1225
|
+
var existing = document.getElementById(SETTINGS_NAV_ICON_STYLE_ID);
|
|
1226
|
+
if (existing !== null) return function () {};
|
|
1227
|
+
var style = document.createElement("style");
|
|
1228
|
+
style.id = SETTINGS_NAV_ICON_STYLE_ID;
|
|
1229
|
+
style.textContent = settingsNavIconCss(settingsNavMaskUrl());
|
|
1230
|
+
document.head.appendChild(style);
|
|
1231
|
+
var disposed = false;
|
|
1232
|
+
var scheduled = false;
|
|
1233
|
+
var sync = function () {
|
|
1234
|
+
scheduled = false;
|
|
1235
|
+
if (disposed) return;
|
|
1236
|
+
var wanted = copy().settingsNav;
|
|
1237
|
+
var rows = document.querySelectorAll(SETTINGS_NAV_ROW_SELECTOR);
|
|
1238
|
+
for (var index = 0; index < rows.length; index += 1) {
|
|
1239
|
+
var row = rows[index];
|
|
1240
|
+
if (isOwnSettingsNavRow(row.textContent, wanted)) row.setAttribute(SETTINGS_NAV_ICON_MARKER, "");
|
|
1241
|
+
else row.removeAttribute(SETTINGS_NAV_ICON_MARKER);
|
|
1242
|
+
}
|
|
1243
|
+
};
|
|
1244
|
+
var schedule = function () {
|
|
1245
|
+
if (scheduled || disposed) return;
|
|
1246
|
+
scheduled = true;
|
|
1247
|
+
if (typeof queueMicrotask === "function") queueMicrotask(sync);
|
|
1248
|
+
else setTimeout(sync, 0);
|
|
1249
|
+
};
|
|
1250
|
+
sync();
|
|
1251
|
+
var observer = new MutationObserver(schedule);
|
|
1252
|
+
observer.observe(document.body, { childList: true, subtree: true, characterData: true });
|
|
1253
|
+
return function () {
|
|
1254
|
+
disposed = true;
|
|
1255
|
+
observer.disconnect();
|
|
1256
|
+
var marked = document.querySelectorAll("[" + SETTINGS_NAV_ICON_MARKER + "]");
|
|
1257
|
+
for (var at = 0; at < marked.length; at += 1) marked[at].removeAttribute(SETTINGS_NAV_ICON_MARKER);
|
|
1258
|
+
if (style.parentNode !== null) style.parentNode.removeChild(style);
|
|
1259
|
+
};
|
|
1260
|
+
};
|
|
1261
|
+
if (ctx && typeof ctx.effect === "function") return ctx.effect(install, "dsh-windows-mcp: settings nav icon");
|
|
1262
|
+
return install();
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
function registerSettingsSection(ctx) {
|
|
1266
|
+
try {
|
|
1267
|
+
ctx.slots.inject(SETTINGS_SLOT, function () {
|
|
1268
|
+
return ctx.slots.register(
|
|
1269
|
+
{
|
|
1270
|
+
name: SETTINGS_SLOT,
|
|
1271
|
+
id: SETTINGS_ENTRY_ID,
|
|
1272
|
+
order: SETTINGS_ORDER,
|
|
1273
|
+
label: function () {
|
|
1274
|
+
return copy().settingsNav;
|
|
1275
|
+
},
|
|
1276
|
+
},
|
|
1277
|
+
function SettingsSection() {
|
|
1278
|
+
return React.createElement(SettingsPage, null);
|
|
1279
|
+
},
|
|
1280
|
+
);
|
|
1281
|
+
});
|
|
1282
|
+
} catch (error) {
|
|
1283
|
+
try {
|
|
1284
|
+
console.error("[" + PACKAGE_ID + "] settings section registration failed:", error);
|
|
1285
|
+
} catch (ignored) {
|
|
1286
|
+
/* nothing to do */
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
|
|
717
1291
|
/**
|
|
718
1292
|
* @param ctx - client cordis context.
|
|
719
1293
|
*/
|
|
@@ -721,11 +1295,30 @@ window.__ModuleLoader__.load({
|
|
|
721
1295
|
installStyles();
|
|
722
1296
|
registerComposerPill(ctx);
|
|
723
1297
|
registerBundleSlots(ctx);
|
|
1298
|
+
registerSettingsSection(ctx);
|
|
1299
|
+
installSettingsNavIcon(ctx);
|
|
724
1300
|
}
|
|
725
1301
|
|
|
726
1302
|
exports.apply = apply;
|
|
727
1303
|
exports.inject = inject;
|
|
728
|
-
exports.__internals = {
|
|
1304
|
+
exports.__internals = {
|
|
1305
|
+
CSS: CSS,
|
|
1306
|
+
apiUrl: apiUrl,
|
|
1307
|
+
tooltipFor: tooltipFor,
|
|
1308
|
+
LABEL: LABEL,
|
|
1309
|
+
STATE_PATH: STATE_PATH,
|
|
1310
|
+
UPDATE_PATH: UPDATE_PATH,
|
|
1311
|
+
SETTINGS_PATH: SETTINGS_PATH,
|
|
1312
|
+
REPO_URL: REPO_URL,
|
|
1313
|
+
SETTINGS_SLOT: SETTINGS_SLOT,
|
|
1314
|
+
SETTINGS_NAV_ICON: settingsNavMaskSvg(),
|
|
1315
|
+
SETTINGS_NAV_ICON_MARKER: SETTINGS_NAV_ICON_MARKER,
|
|
1316
|
+
isOwnSettingsNavRow: isOwnSettingsNavRow,
|
|
1317
|
+
settingsNavIconCss: settingsNavIconCss,
|
|
1318
|
+
settingsNavMaskUrl: settingsNavMaskUrl,
|
|
1319
|
+
cloneSettings: cloneSettings,
|
|
1320
|
+
SettingsPage: SettingsPage,
|
|
1321
|
+
};
|
|
729
1322
|
return module.exports;
|
|
730
1323
|
},
|
|
731
1324
|
});
|