@yezack/dsh-remote-mobile 1.9.2 → 1.10.2

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/lib/client.js CHANGED
@@ -47,10 +47,10 @@ __export(index_exports, {
47
47
  inject: () => inject
48
48
  });
49
49
  module.exports = __toCommonJS(index_exports);
50
- var import_react10 = __toESM(require("react"), 1);
50
+ var import_react11 = __toESM(require("react"), 1);
51
51
 
52
52
  // src/client/components/MainSection.jsx
53
- var import_react9 = __toESM(require("react"), 1);
53
+ var import_react10 = __toESM(require("react"), 1);
54
54
 
55
55
  // src/client/api.js
56
56
  var RM_API_BASE = (function() {
@@ -103,6 +103,13 @@ function updateAdvancedSecurityOptions(payload) {
103
103
  body: JSON.stringify(payload)
104
104
  });
105
105
  }
106
+ function updateProxyConfig(payload) {
107
+ return safeFetchJson(RM_API_BASE + "update-options", {
108
+ method: "POST",
109
+ headers: { "Content-Type": "application/json" },
110
+ body: JSON.stringify(payload)
111
+ });
112
+ }
106
113
  function updateSecret(secret) {
107
114
  return safeFetchJson(RM_API_BASE + "set-secret", {
108
115
  method: "POST",
@@ -172,6 +179,13 @@ function resetStylesApi() {
172
179
  headers: { "Content-Type": "application/json" }
173
180
  });
174
181
  }
182
+ function toggleDirectoryPickerApi(enabled) {
183
+ return safeFetchJson(RM_API_BASE + "directory-picker", {
184
+ method: "POST",
185
+ headers: { "Content-Type": "application/json" },
186
+ body: JSON.stringify({ enabled: Boolean(enabled) })
187
+ });
188
+ }
175
189
 
176
190
  // src/client/qrcode.js
177
191
  var generateQrSvg = (function() {
@@ -687,6 +701,18 @@ var generateQrSvg = (function() {
687
701
  };
688
702
  })();
689
703
 
704
+ // src/client/utils/access.js
705
+ function resolveAccessPort(status) {
706
+ const port = Number(status?.proxy?.activePort);
707
+ if (!Number.isInteger(port) || port <= 0 || port > 65535) {
708
+ return null;
709
+ }
710
+ return port;
711
+ }
712
+ function buildDirectLink(host, port, token) {
713
+ return `http://${host}:${port}/auth?token=${token || ""}`;
714
+ }
715
+
690
716
  // src/client/components/NetworkCard.jsx
691
717
  var import_react = __toESM(require("react"), 1);
692
718
 
@@ -798,6 +824,30 @@ var translations = {
798
824
  saveConfigSuccessToast: "\u9AD8\u7EA7\u5B89\u5168\u53C2\u6570\u5DF2\u6210\u529F\u4FDD\u5B58\u5E76\u540C\u6B65\u5230 settings.json\uFF01",
799
825
  restoreDefaultsSuccessToast: "\u5DF2\u6210\u529F\u6062\u590D\u9ED8\u8BA4\u5B89\u5168\u53C2\u6570\u5E76\u7ACB\u5373\u751F\u6548\uFF01",
800
826
  saveConfigFailTip: "\u53C2\u6570\u5FC5\u987B\u4E3A\u5927\u4E8E 0 \u7684\u6709\u6548\u6574\u6570",
827
+ // 卡片 9: 接入代理配置
828
+ proxyConfigCardTitle: "\u63A5\u5165\u4EE3\u7406\u914D\u7F6E",
829
+ proxyConfigCardDesc: "\u63A7\u5236\u63A5\u5165\u6A21\u5F0F\u3001\u4EE3\u7406\u76D1\u542C\u5730\u5740\u4E0E\u53D7\u4FE1\u4EFB\u57DF\u540D\u3002\u4FDD\u5B58\u540E\u9700\u91CD\u542F DSH \u751F\u6548\u3002",
830
+ proxyModeLabel: "\u63A5\u5165\u6A21\u5F0F",
831
+ proxyModeHint: "proxy\uFF1A\u6838\u5FC3\u56DE\u73AF\uFF0C\u7531\u63D2\u4EF6\u8F6C\u53D1\uFF1Bdirect\uFF1A\u4E0D\u542F\u52A8\u4EE3\u7406\uFF0C\u76F4\u63A5\u8BBF\u95EE\u6838\u5FC3",
832
+ proxyModeProxy: "\u5B89\u5168\u63A5\u5165\u4EE3\u7406\uFF08proxy\uFF09",
833
+ proxyModeDirect: "\u76F4\u8FDE\u6838\u5FC3\uFF08direct\uFF09",
834
+ proxyPortLabel: "\u4EE3\u7406\u7AEF\u53E3",
835
+ proxyPortHint: "\u9ED8\u8BA4 3081\uFF1B\u7AEF\u53E3\u5360\u7528\u65F6\u4F1A\u81EA\u52A8\u9012\u589E",
836
+ proxyHostLabel: "\u4EE3\u7406\u76D1\u542C\u5730\u5740",
837
+ proxyHostHint: "0.0.0.0 \u8868\u793A\u6240\u6709\u7F51\u5361\uFF1B127.0.0.1 \u4EC5\u672C\u673A",
838
+ proxyTrustedHostsLabel: "\u53D7\u4FE1\u4EFB\u57DF\u540D",
839
+ proxyTrustedHostsHint: "\u6BCF\u884C\u4E00\u4E2A\uFF0C\u4F8B\u5982 xxx.tailnet.ts.net\uFF1B\u81EA\u5B9A\u4E49\u53CD\u4EE3\u57DF\u540D\u5FC5\u987B\u52A0\u5165",
840
+ proxyRestartNote: "\u4FDD\u5B58\u540E\u9700\u91CD\u542F DSH \u624D\u4F1A\u91CD\u65B0\u7ED1\u5B9A\u76D1\u542C\u5730\u5740/\u7AEF\u53E3\u3002",
841
+ proxySaveBtn: "\u{1F4BE} \u4FDD\u5B58\u63A5\u5165\u914D\u7F6E",
842
+ proxySavingBtn: "\u6B63\u5728\u4FDD\u5B58...",
843
+ proxyResetBtn: "\u{1F504} \u6062\u590D\u9ED8\u8BA4",
844
+ proxySaveConfirm: "\u786E\u5B9A\u4FDD\u5B58\u63A5\u5165\u914D\u7F6E\u5417\uFF1F\u4FDD\u5B58\u540E\u9700\u91CD\u542F DSH \u751F\u6548\u3002",
845
+ proxyResetConfirm: "\u786E\u5B9A\u6062\u590D\u9ED8\u8BA4\u63A5\u5165\u914D\u7F6E\uFF08proxy\u30013081\u30010.0.0.0\u3001\u65E0\u53D7\u4FE1\u4EFB\u57DF\u540D\uFF09\u5417\uFF1F",
846
+ proxySavedToast: "\u63A5\u5165\u914D\u7F6E\u5DF2\u4FDD\u5B58\uFF1B\u91CD\u542F DSH \u540E\u751F\u6548\u3002",
847
+ proxyResetToast: "\u5DF2\u6062\u590D\u9ED8\u8BA4\u63A5\u5165\u914D\u7F6E\uFF1B\u91CD\u542F DSH \u540E\u751F\u6548\u3002",
848
+ proxySaveFailTip: "\u8BF7\u68C0\u67E5\u7AEF\u53E3\u3001\u76D1\u542C\u5730\u5740\u4E0E\u53D7\u4FE1\u4EFB\u57DF\u540D\u683C\u5F0F",
849
+ proxyCurrentRuntime: "\u5F53\u524D\u8FD0\u884C",
850
+ proxyNotReady: "\u5B89\u5168\u63A5\u5165\u4EE3\u7406\u672A\u5C31\u7EEA\uFF0C\u8BF7\u5148\u542F\u52A8\u4EE3\u7406",
801
851
  // 卡片 8: 移动端样式片段(样式小插件)
802
852
  tabAccess: "\u63A5\u5165",
803
853
  tabDevices: "\u8BBE\u5907\u4E0E\u5B89\u5168",
@@ -841,6 +891,26 @@ var translations = {
841
891
  conflictReportText: "[DSH \u63D2\u4EF6\u51B2\u7A81\u8BCA\u65AD\u62A5\u544A]\n\u73B0\u8C61\uFF1Adsh-remote-mobile \u8BBE\u7F6E\u9762\u677F\u63D0\u793A\u300C\u68C0\u6D4B\u5230\u5176\u4ED6\u8FDC\u7A0B\u63A5\u5165\u63D2\u4EF6\u300D\u3002\n\u539F\u56E0\uFF1A\u53E6\u4E00\u6B3E\u8FDC\u7A0B/Web \u63A5\u5165\u7C7B\u63D2\u4EF6\u5360\u7528\u4E86 Cordis \u5171\u4EAB\u670D\u52A1 remoteWebUiPairing\uFF0Cdsh-remote-mobile \u4E3A\u4FDD\u8BC1\u6B63\u5E38\u542F\u52A8\u5DF2\u81EA\u52A8\u8BA9\u51FA\u8BE5\u670D\u52A1\uFF0C\u5F53\u524D\u4E24\u5957\u8FDC\u7A0B\u63A5\u5165\u529F\u80FD\u5E76\u5B58\u3002\n\u5360\u7528\u65B9\u63D2\u4EF6\u5305\u540D\uFF1A{plugin}\n\u5360\u7528\u65B9 loader entry id\uFF1A{entryId}\n\u5904\u7406\u65B9\u6848\uFF08\u4E8C\u9009\u4E00\uFF09\uFF1A\n\u65B9\u6848 A\uFF08\u4FDD\u7559 dsh-remote-mobile\uFF0C\u7981\u7528\u5360\u7528\u65B9\uFF09\uFF1A\n1. \u6253\u5F00\u914D\u7F6E\u6587\u4EF6 ~/.dsh/profiles/web/cordis.patch.yml\n2. \u5728\u6587\u4EF6\u672B\u5C3E\u8FFD\u52A0\u4EE5\u4E0B\u4E24\u884C\uFF08\u6CE8\u610F YAML \u7F29\u8FDB\uFF0C\u7B2C\u4E8C\u884C\u524D\u6709\u4E24\u4E2A\u7A7A\u683C\uFF09\uFF1A\n- id: {entryId}\n disabled: true\n3. \u7ED3\u675F\u5F53\u524D\u6B63\u5728\u8FD0\u884C\u7684 dsh web \u8FDB\u7A0B\uFF0C\u91CD\u65B0\u6267\u884C dsh web --no-open\n\u65B9\u6848 B\uFF08\u4FDD\u7559\u5360\u7528\u65B9\uFF0C\u5378\u8F7D dsh-remote-mobile\uFF09\uFF1A\n\u6267\u884C\u547D\u4EE4 dsh plugin --profile web remove dsh-remote-mobile \u540E\u91CD\u542F dsh web",
842
892
  conflictReportNoEntryIdText: "[DSH \u63D2\u4EF6\u51B2\u7A81\u8BCA\u65AD\u62A5\u544A]\n\u73B0\u8C61\uFF1Adsh-remote-mobile \u8BBE\u7F6E\u9762\u677F\u63D0\u793A\u300C\u68C0\u6D4B\u5230\u5176\u4ED6\u8FDC\u7A0B\u63A5\u5165\u63D2\u4EF6\u300D\u3002\n\u539F\u56E0\uFF1A\u53E6\u4E00\u6B3E\u8FDC\u7A0B/Web \u63A5\u5165\u7C7B\u63D2\u4EF6\u5360\u7528\u4E86 Cordis \u5171\u4EAB\u670D\u52A1 remoteWebUiPairing\uFF0Cdsh-remote-mobile \u4E3A\u4FDD\u8BC1\u6B63\u5E38\u542F\u52A8\u5DF2\u81EA\u52A8\u8BA9\u51FA\u8BE5\u670D\u52A1\uFF0C\u5F53\u524D\u4E24\u5957\u8FDC\u7A0B\u63A5\u5165\u529F\u80FD\u5E76\u5B58\u3002\n\u5360\u7528\u65B9\u63D2\u4EF6\u5305\u540D\uFF1A{plugin}\n\u5360\u7528\u65B9 loader entry id\uFF1A\u672A\u80FD\u81EA\u52A8\u8BC6\u522B\n\u5904\u7406\u65B9\u6848\uFF08\u4E8C\u9009\u4E00\uFF09\uFF1A\n\u65B9\u6848 A\uFF08\u4FDD\u7559 dsh-remote-mobile\uFF0C\u7981\u7528\u5360\u7528\u65B9\uFF09\uFF1A\n1. \u6267\u884C dsh --dump-config \u67E5\u770B\u5168\u90E8 loader entry\uFF0C\u627E\u5230\u5360\u7528\u65B9\u63D2\u4EF6\u5BF9\u5E94\u7684\u6761\u76EE id\n2. \u6253\u5F00\u914D\u7F6E\u6587\u4EF6 ~/.dsh/profiles/web/cordis.patch.yml\uFF0C\u5728\u6587\u4EF6\u672B\u5C3E\u8FFD\u52A0\uFF08\u6CE8\u610F YAML \u7F29\u8FDB\uFF09\uFF1A\n- id: <\u5360\u7528\u65B9\u6761\u76EEid>\n disabled: true\n3. \u7ED3\u675F\u5F53\u524D\u6B63\u5728\u8FD0\u884C\u7684 dsh web \u8FDB\u7A0B\uFF0C\u91CD\u65B0\u6267\u884C dsh web --no-open\n\u65B9\u6848 B\uFF08\u4FDD\u7559\u5360\u7528\u65B9\uFF0C\u5378\u8F7D dsh-remote-mobile\uFF09\uFF1A\n\u6267\u884C\u547D\u4EE4 dsh plugin --profile web remove dsh-remote-mobile \u540E\u91CD\u542F dsh web",
843
893
  conflictBannerDismiss: "\u77E5\u9053\u4E86",
894
+ // 移动端目录选择器(添加工作区 / 选择文件夹)提醒与一键配置
895
+ dirPickerBannerTitle: "\u624B\u673A\u7AEF\u300C\u6DFB\u52A0\u5DE5\u4F5C\u533A / \u9009\u62E9\u6587\u4EF6\u5939\u300D\u53D7\u9650\u63D0\u9192",
896
+ dirPickerBannerBody: "\u5F53\u524D DSH \u6838\u5FC3\u7ED1\u5B9A\u5728\u672C\u5730\u56DE\u73AF\uFF08127.0.0.1\uFF09\uFF0C\u5B98\u65B9\u76EE\u5F55\u9009\u62E9\u5668\uFF08dsh-host-directory-picker-auto\uFF09\u4F1A\u5C06\u6765\u81EA 3081 \u4EE3\u7406\u7684\u624B\u673A\u8BF7\u6C42\u8BEF\u5224\u4E3A\u300C\u672C\u673A\u64CD\u4F5C\u300D\uFF0C\u4ECE\u800C\u628A\u6587\u4EF6\u5939\u9009\u62E9\u7A97\u53E3\u5F39\u5728\u7535\u8111\u5C4F\u5E55\u4E0A\uFF0C\u5BFC\u81F4\u624B\u673A\u7AEF\u4E00\u76F4\u7B49\u5F85\u3001\u65E0\u6CD5\u6DFB\u52A0\u5DE5\u4F5C\u533A\u3002\n\u53EF\u70B9\u51FB\u4E0B\u65B9\u6309\u94AE\u4E00\u952E\u5199\u5165\u7F51\u9875\u5185\u76EE\u5F55\u9009\u62E9\u5668\uFF08browse \u6A21\u5F0F\uFF0C\u5DF2\u5185\u7F6E\u9632\u91CD\u590D\u53BB\u91CD\uFF09\uFF0C\u5F00\u542F\u540E\u624B\u673A\u4E0E\u7535\u8111\u5747\u53EF\u76F4\u63A5\u5728\u7F51\u9875\u5185\u6D4F\u89C8\u5E76\u9009\u62E9\u6587\u4EF6\u5939\u3002",
897
+ dirPickerEnableBtn: "\u26A1 \u4E00\u952E\u5F00\u542F\u7F51\u9875\u76EE\u5F55\u9009\u62E9\u5668",
898
+ dirPickerEnablingBtn: "\u6B63\u5728\u5199\u5165\u914D\u7F6E...",
899
+ dirPickerCopyYamlBtn: "\u{1F4CB} \u590D\u5236 YAML \u914D\u7F6E",
900
+ dirPickerCopiedYamlTip: "\u5DF2\u590D\u5236\u76EE\u5F55\u9009\u62E9\u5668 YAML \u914D\u7F6E\uFF0C\u53EF\u7C98\u8D34\u5230 cordis.patch.yml",
901
+ dirPickerBannerDismiss: "\u6682\u4E0D\u5904\u7406",
902
+ dirPickerRestartBannerTitle: "\u7F51\u9875\u76EE\u5F55\u9009\u62E9\u5668\u914D\u7F6E\u5DF2\u5199\u5165\uFF08\u7B49\u5F85\u751F\u6548\uFF09",
903
+ dirPickerRestartBannerBody: "\u5DF2\u5C06\u7F51\u9875\u5185\u76EE\u5F55\u9009\u62E9\u5668\u914D\u7F6E\u5199\u5165 {path}\u3002\u82E5 DSH \u672A\u81EA\u52A8\u70ED\u91CD\u8F7D\uFF0C\u8BF7\u91CD\u542F dsh web \u5E76\u5237\u65B0\u624B\u673A\u9875\u9762\uFF0C\u5373\u53EF\u5728\u624B\u673A\u7AEF\u6B63\u5E38\u6DFB\u52A0\u5DE5\u4F5C\u533A\u3002",
904
+ dirPickerEnabledToast: "\u5DF2\u6210\u529F\u5199\u5165\u7F51\u9875\u76EE\u5F55\u9009\u62E9\u5668\u914D\u7F6E\uFF01\u82E5\u672A\u81EA\u52A8\u70ED\u91CD\u8F7D\uFF0C\u8BF7\u91CD\u542F dsh web \u751F\u6548\u3002",
905
+ dirPickerDisabledToast: "\u5DF2\u79FB\u9664\u7F51\u9875\u76EE\u5F55\u9009\u62E9\u5668\u914D\u7F6E\uFF08\u6062\u590D PC \u539F\u751F\u5F39\u7A97\uFF09\uFF0C\u91CD\u542F dsh web \u540E\u751F\u6548\u3002",
906
+ dirPickerDisableConfirm: "\u786E\u5B9A\u8981\u6062\u590D\u4E3A PC \u539F\u751F\u7CFB\u7EDF\u76EE\u5F55\u9009\u62E9\u5668\u5417\uFF1F\n\n\u6062\u590D\u540E\uFF0C\u7535\u8111\u7AEF\u5C06\u91CD\u65B0\u5F39\u51FA macOS / Windows \u539F\u751F\u6587\u4EF6\u5939\u7A97\u53E3\uFF0C\u4F46\u624B\u673A\u7AEF\u70B9\u51FB\u300C\u6DFB\u52A0\u5DE5\u4F5C\u533A / \u9009\u62E9\u6587\u4EF6\u5939\u300D\u65F6\u4F1A\u5F39\u5728\u7535\u8111\u684C\u9762\u4E0A\uFF0C\u5BFC\u81F4\u624B\u673A\u7AEF\u65E0\u6CD5\u72EC\u7ACB\u6DFB\u52A0\u5DE5\u4F5C\u533A\u3002",
907
+ dirPickerSectionTitle: "\u{1F4C1} \u79FB\u52A8\u7AEF\u6DFB\u52A0\u5DE5\u4F5C\u533A\uFF08\u76EE\u5F55\u9009\u62E9\u5668\u6A21\u5F0F\uFF09",
908
+ dirPickerSectionDesc: "\u63A7\u5236\u70B9\u51FB\u300C\u6DFB\u52A0\u5DE5\u4F5C\u533A / \u9009\u62E9\u6587\u4EF6\u5939\u300D\u65F6\u4F7F\u7528\u7F51\u9875\u5185\u76EE\u5F55\u6D4F\u89C8\u5668\uFF08\u624B\u673A+\u7535\u8111\u901A\u7528\uFF09\u8FD8\u662F\u7535\u8111\u7CFB\u7EDF\u539F\u751F\u5F39\u7A97\uFF08\u4EC5\u7535\u8111\u53EF\u7528\uFF09\u3002\u63D2\u4EF6\u5728\u5199\u5165 cordis.patch.yml \u65F6\u4F1A\u81EA\u52A8\u6267\u884C\u4E25\u683C\u5E42\u7B49\u53BB\u91CD\u3002",
909
+ dirPickerStatusActive: "\u{1F7E2} \u5DF2\u542F\u7528\u7F51\u9875\u5185\u76EE\u5F55\u9009\u62E9\u5668\uFF08\u624B\u673A\u7AEF\u53EF\u6B63\u5E38\u6DFB\u52A0\u5DE5\u4F5C\u533A\uFF09",
910
+ dirPickerStatusPendingRestart: "\u23F3 \u5DF2\u5199\u5165\u914D\u7F6E\uFF08\u91CD\u542F DSH \u6216\u70ED\u91CD\u8F7D\u540E\u751F\u6548\uFF09",
911
+ dirPickerStatusNativeWarn: "\u26A0\uFE0F \u5F53\u524D\u4E3A\u7CFB\u7EDF\u539F\u751F\u5F39\u7A97\uFF08\u624B\u673A\u7AEF\u65E0\u6CD5\u9009\u62E9\u6587\u4EF6\u5939\uFF09",
912
+ dirPickerRestoreNativeBtn: "\u{1F504} \u6062\u590D PC \u539F\u751F\u76EE\u5F55\u9009\u62E9\u5668",
913
+ dirPickerPatchFileLabel: "\u76EE\u6807\u914D\u7F6E\u6587\u4EF6\uFF1A",
844
914
  // Toast 提示
845
915
  toastDeviceConnected: "\u{1F389} \u65B0\u8BBE\u5907\u63A5\u5165\u6210\u529F\uFF1A{name} ({ip})",
846
916
  toastDeviceOnline: "\u{1F4F6} \u8BBE\u5907\u91CD\u65B0\u4E0A\u7EBF\uFF1A{name} ({ip})",
@@ -952,6 +1022,30 @@ var translations = {
952
1022
  saveConfigSuccessToast: "Security policy successfully saved and synced to settings.json!",
953
1023
  restoreDefaultsSuccessToast: "Default security policies restored and applied immediately!",
954
1024
  saveConfigFailTip: "Parameters must be valid integers greater than 0",
1025
+ // Card 9: Access Proxy Configuration
1026
+ proxyConfigCardTitle: "Access Proxy Configuration",
1027
+ proxyConfigCardDesc: "Configure the access mode, proxy listener, and trusted hosts. Restart DSH after saving.",
1028
+ proxyModeLabel: "Access mode",
1029
+ proxyModeHint: "proxy: core stays on loopback and the plugin forwards; direct: no proxy, access the core directly",
1030
+ proxyModeProxy: "Secure access proxy (proxy)",
1031
+ proxyModeDirect: "Direct core (direct)",
1032
+ proxyPortLabel: "Proxy port",
1033
+ proxyPortHint: "Default 3081; automatically increments if occupied",
1034
+ proxyHostLabel: "Proxy listen address",
1035
+ proxyHostHint: "0.0.0.0 means all interfaces; 127.0.0.1 means local only",
1036
+ proxyTrustedHostsLabel: "Trusted hosts",
1037
+ proxyTrustedHostsHint: "One per line, e.g. xxx.tailnet.ts.net; custom reverse-proxy domains must be listed",
1038
+ proxyRestartNote: "Restart DSH after saving to rebind the listen address/port.",
1039
+ proxySaveBtn: "\u{1F4BE} Save Access Config",
1040
+ proxySavingBtn: "Saving...",
1041
+ proxyResetBtn: "\u{1F504} Restore Defaults",
1042
+ proxySaveConfirm: "Save the access configuration? A DSH restart is required.",
1043
+ proxyResetConfirm: "Restore defaults (proxy, 3081, 0.0.0.0, no trusted hosts)?",
1044
+ proxySavedToast: "Access configuration saved; restart DSH to apply.",
1045
+ proxyResetToast: "Default access configuration restored; restart DSH to apply.",
1046
+ proxySaveFailTip: "Check the port, listen address, and trusted-host format",
1047
+ proxyCurrentRuntime: "Current runtime",
1048
+ proxyNotReady: "Secure access proxy is not ready. Start the proxy first.",
955
1049
  // Card 8: Mobile Style Snippets (style mini-plugin)
956
1050
  tabAccess: "Access",
957
1051
  tabDevices: "Devices & Security",
@@ -995,6 +1089,26 @@ var translations = {
995
1089
  conflictReportText: '[DSH plugin conflict diagnosis]\nSymptom: the dsh-remote-mobile settings panel shows "Another remote-access plugin detected".\nCause: another remote/Web access plugin registered the shared Cordis service remoteWebUiPairing first; dsh-remote-mobile yielded it to keep startup safe, so both remote features now run in parallel.\nConflicting plugin package: {plugin}\nConflicting loader entry id: {entryId}\nFix (choose ONE):\nOption A (keep dsh-remote-mobile, disable the other):\n1. Open ~/.dsh/profiles/web/cordis.patch.yml\n2. Append these two lines at the end (mind YAML indentation, two spaces before disabled):\n- id: {entryId}\n disabled: true\n3. Stop the running dsh web process, then run dsh web --no-open again\nOption B (keep the other, remove dsh-remote-mobile):\nRun dsh plugin --profile web remove dsh-remote-mobile and restart dsh web',
996
1090
  conflictReportNoEntryIdText: '[DSH plugin conflict diagnosis]\nSymptom: the dsh-remote-mobile settings panel shows "Another remote-access plugin detected".\nCause: another remote/Web access plugin registered the shared Cordis service remoteWebUiPairing first; dsh-remote-mobile yielded it to keep startup safe, so both remote features now run in parallel.\nConflicting plugin package: {plugin}\nConflicting loader entry id: could not be detected automatically\nFix (choose ONE):\nOption A (keep dsh-remote-mobile, disable the other):\n1. Run dsh --dump-config to list all loader entries and find the entry id of the conflicting plugin\n2. Open ~/.dsh/profiles/web/cordis.patch.yml and append at the end (mind YAML indentation):\n- id: <the conflicting entry id>\n disabled: true\n3. Stop the running dsh web process, then run dsh web --no-open again\nOption B (keep the other, remove dsh-remote-mobile):\nRun dsh plugin --profile web remove dsh-remote-mobile and restart dsh web',
997
1091
  conflictBannerDismiss: "Got it",
1092
+ // Mobile directory picker (Add Workspace / Select Folder) reminder & one-click patch
1093
+ dirPickerBannerTitle: 'Mobile "Add Workspace / Select Folder" Limitation',
1094
+ dirPickerBannerBody: "Because the DSH core stays on loopback (127.0.0.1), the official auto directory picker (dsh-host-directory-picker-auto) mistakes mobile requests via proxy 3081 for local desktop sessions and opens the native OS folder dialog on your PC screen \u2014 leaving your phone stuck waiting and unable to add workspaces.\nClick below to enable the in-app web directory browser (browse mode, with strict deduplication) so both mobile and PC can pick folders directly inside the web UI.",
1095
+ dirPickerEnableBtn: "\u26A1 Enable In-App Directory Browser",
1096
+ dirPickerEnablingBtn: "Applying patch...",
1097
+ dirPickerCopyYamlBtn: "\u{1F4CB} Copy YAML Snippet",
1098
+ dirPickerCopiedYamlTip: "YAML snippet copied! Paste it into cordis.patch.yml",
1099
+ dirPickerBannerDismiss: "Dismiss",
1100
+ dirPickerRestartBannerTitle: "In-App Directory Browser Configured (Pending Reload)",
1101
+ dirPickerRestartBannerBody: "Configuration has been written to {path}. If DSH does not hot-reload automatically, please restart dsh web and refresh your mobile browser to add workspaces on mobile.",
1102
+ dirPickerEnabledToast: "In-app directory browser enabled! Restart dsh web if HMR did not reload automatically.",
1103
+ dirPickerDisabledToast: "Restored PC native directory picker; restart dsh web to apply.",
1104
+ dirPickerDisableConfirm: "Restore the PC native OS folder dialog?\n\nOnce restored, PC will open the native macOS / Windows folder window again, but mobile devices will NOT be able to pick folders or add workspaces.",
1105
+ dirPickerSectionTitle: "\u{1F4C1} Mobile Workspace Directory Picker Mode",
1106
+ dirPickerSectionDesc: "Choose between the in-app web directory browser (works on both mobile and PC) and the native OS folder dialog (PC only). Writing to cordis.patch.yml automatically deduplicates entries.",
1107
+ dirPickerStatusActive: "\u{1F7E2} In-App Web Directory Browser Enabled (Mobile Ready)",
1108
+ dirPickerStatusPendingRestart: "\u23F3 Configured in patch file (Restart DSH or wait for HMR)",
1109
+ dirPickerStatusNativeWarn: "\u26A0\uFE0F Native OS Dialog Active (Mobile cannot add workspaces)",
1110
+ dirPickerRestoreNativeBtn: "\u{1F504} Restore PC Native Picker",
1111
+ dirPickerPatchFileLabel: "Target patch file: ",
998
1112
  // Toast
999
1113
  toastDeviceConnected: "\u{1F389} New device connected: {name} ({ip})",
1000
1114
  toastDeviceOnline: "\u{1F4F6} Device reconnected: {name} ({ip})",
@@ -1065,7 +1179,10 @@ var import_jsx_runtime = require("react/jsx-runtime");
1065
1179
  function NetworkCard({ status, toggleTailscale, toggleLan, copyText, lang }) {
1066
1180
  const tsHost = status.tailscaleIp || "100.x.y.z";
1067
1181
  const lanHost = status.lanIp || "127.0.0.1";
1068
- const port = typeof window !== "undefined" && window.location.port || "3080";
1182
+ const port = resolveAccessPort(status);
1183
+ const portReady = port !== null;
1184
+ const tailscaleAddress = portReady ? `http://${tsHost}:${port}` : null;
1185
+ const lanAddress = portReady ? `http://${lanHost}:${port}` : null;
1069
1186
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1070
1187
  "div",
1071
1188
  {
@@ -1099,6 +1216,29 @@ function NetworkCard({ status, toggleTailscale, toggleLan, copyText, lang }) {
1099
1216
  children: t("netCardDesc", lang)
1100
1217
  }
1101
1218
  ),
1219
+ status?.proxy?.mode === "proxy" && status.proxy.running === false ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1220
+ "div",
1221
+ {
1222
+ style: {
1223
+ background: "rgba(239, 68, 68, 0.12)",
1224
+ border: "1px solid rgba(239, 68, 68, 0.35)",
1225
+ borderRadius: "8px",
1226
+ padding: "10px 12px",
1227
+ marginBottom: "16px",
1228
+ fontSize: "12px",
1229
+ color: "var(--dsw-alias-label-error, #ef4444)",
1230
+ lineHeight: "1.5",
1231
+ textAlign: "left"
1232
+ },
1233
+ children: [
1234
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("strong", { children: [
1235
+ "\u26A0\uFE0F ",
1236
+ lang === "en" ? "Secure Proxy Not Ready" : "\u5B89\u5168\u63A5\u5165\u4EE3\u7406\u672A\u5C31\u7EEA"
1237
+ ] }),
1238
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: status.proxy.error ? `${lang === "en" ? "Failure reason: " : "\u542F\u52A8\u5931\u8D25\uFF1A"}${status.proxy.error}` : lang === "en" ? "Secure proxy is initializing or failed to bind port. External devices cannot connect currently." : "\u5B89\u5168\u63A5\u5165\u4EE3\u7406\u6B63\u5728\u542F\u52A8\u6216\u7AEF\u53E3\u7ED1\u5B9A\u5931\u8D25\uFF0C\u5916\u90E8\u624B\u673A\u8BBE\u5907\u5F53\u524D\u6682\u65F6\u65E0\u6CD5\u8FDE\u63A5\u3002" })
1239
+ ]
1240
+ }
1241
+ ) : null,
1102
1242
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1103
1243
  "div",
1104
1244
  {
@@ -1237,7 +1377,7 @@ function NetworkCard({ status, toggleTailscale, toggleLan, copyText, lang }) {
1237
1377
  style: {
1238
1378
  fontSize: "13px",
1239
1379
  fontFamily: "monospace",
1240
- color: "var(--dsw-alias-brand-primary, #3b82f6)",
1380
+ color: portReady ? "var(--dsw-alias-brand-primary, #3b82f6)" : "var(--dsw-alias-label-error, #ef4444)",
1241
1381
  display: "flex",
1242
1382
  alignItems: "center",
1243
1383
  justifyContent: "space-between",
@@ -1258,22 +1398,24 @@ function NetworkCard({ status, toggleTailscale, toggleLan, copyText, lang }) {
1258
1398
  flex: 1,
1259
1399
  minWidth: 0
1260
1400
  },
1261
- children: `http://${tsHost}:${port}`
1401
+ children: tailscaleAddress || t("proxyNotReady", lang)
1262
1402
  }
1263
1403
  ),
1264
1404
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1265
1405
  "button",
1266
1406
  {
1267
1407
  type: "button",
1268
- onClick: () => copyText(`http://${tsHost}:${port}`, t("copiedTip", lang)),
1408
+ disabled: !portReady,
1409
+ onClick: () => tailscaleAddress && copyText(tailscaleAddress, t("copiedTip", lang)),
1269
1410
  style: {
1270
1411
  padding: "3px 10px",
1271
1412
  background: "var(--dsw-alias-bg-layer-3, rgba(255, 255, 255, 0.08))",
1272
1413
  border: "1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.25))",
1273
1414
  borderRadius: "4px",
1274
1415
  fontSize: "11px",
1275
- cursor: "pointer",
1416
+ cursor: portReady ? "pointer" : "not-allowed",
1276
1417
  color: "var(--dsw-alias-label-secondary, inherit)",
1418
+ opacity: portReady ? 1 : 0.4,
1277
1419
  whiteSpace: "nowrap",
1278
1420
  flexShrink: 0
1279
1421
  },
@@ -1423,7 +1565,7 @@ function NetworkCard({ status, toggleTailscale, toggleLan, copyText, lang }) {
1423
1565
  style: {
1424
1566
  fontSize: "13px",
1425
1567
  fontFamily: "monospace",
1426
- color: "var(--dsw-alias-brand-primary, #3b82f6)",
1568
+ color: portReady ? "var(--dsw-alias-brand-primary, #3b82f6)" : "var(--dsw-alias-label-error, #ef4444)",
1427
1569
  display: "flex",
1428
1570
  alignItems: "center",
1429
1571
  justifyContent: "space-between",
@@ -1444,22 +1586,24 @@ function NetworkCard({ status, toggleTailscale, toggleLan, copyText, lang }) {
1444
1586
  flex: 1,
1445
1587
  minWidth: 0
1446
1588
  },
1447
- children: `http://${lanHost}:${port}`
1589
+ children: lanAddress || t("proxyNotReady", lang)
1448
1590
  }
1449
1591
  ),
1450
1592
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1451
1593
  "button",
1452
1594
  {
1453
1595
  type: "button",
1454
- onClick: () => copyText(`http://${lanHost}:${port}`, t("copiedTip", lang)),
1596
+ disabled: !portReady,
1597
+ onClick: () => lanAddress && copyText(lanAddress, t("copiedTip", lang)),
1455
1598
  style: {
1456
1599
  padding: "3px 10px",
1457
1600
  background: "var(--dsw-alias-bg-layer-3, rgba(255, 255, 255, 0.08))",
1458
1601
  border: "1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.25))",
1459
1602
  borderRadius: "4px",
1460
1603
  fontSize: "11px",
1461
- cursor: "pointer",
1604
+ cursor: portReady ? "pointer" : "not-allowed",
1462
1605
  color: "var(--dsw-alias-label-secondary, inherit)",
1606
+ opacity: portReady ? 1 : 0.4,
1463
1607
  whiteSpace: "nowrap",
1464
1608
  flexShrink: 0
1465
1609
  },
@@ -1568,10 +1712,12 @@ function QrPairingCard({
1568
1712
  copyDirectLink,
1569
1713
  lang
1570
1714
  }) {
1571
- const port = typeof window !== "undefined" && window.location.port || "3080";
1715
+ const port = resolveAccessPort(status);
1716
+ const portReady = port !== null;
1717
+ const proxyUnavailable = status?.proxy?.mode === "proxy" && status.proxy.running === false;
1572
1718
  const isExpired = timeLeft <= 0;
1573
1719
  const currentHost = selectedTab === "lan" && status.lanIp ? status.lanIp : status.tailscaleIp || status.lanIp || typeof window !== "undefined" && window.location.hostname || "127.0.0.1";
1574
- const directLink = `http://${currentHost}:${port}/auth?token=${status.code || ""}`;
1720
+ const directLink = portReady ? buildDirectLink(currentHost, port, status.code || "") : "";
1575
1721
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1576
1722
  "div",
1577
1723
  {
@@ -1606,7 +1752,51 @@ function QrPairingCard({
1606
1752
  children: t("pairingCardDesc", lang)
1607
1753
  }
1608
1754
  ),
1609
- showQr ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1755
+ status?.proxy?.mode === "proxy" && status.proxy.running === false ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1756
+ "div",
1757
+ {
1758
+ style: {
1759
+ background: "rgba(239, 68, 68, 0.12)",
1760
+ border: "1px solid rgba(239, 68, 68, 0.35)",
1761
+ borderRadius: "8px",
1762
+ padding: "12px 14px",
1763
+ marginBottom: "14px",
1764
+ fontSize: "13px",
1765
+ color: "var(--dsw-alias-label-error, #ef4444)",
1766
+ lineHeight: "1.6",
1767
+ textAlign: "left"
1768
+ },
1769
+ children: [
1770
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("strong", { children: [
1771
+ "\u26A0\uFE0F ",
1772
+ lang === "en" ? "Secure Proxy Error" : "\u5B89\u5168\u63A5\u5165\u4EE3\u7406\u672A\u5C31\u7EEA"
1773
+ ] }),
1774
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { marginTop: "4px", fontSize: "12px" }, children: status.proxy.error ? `${lang === "en" ? "Failure reason: " : "\u542F\u52A8\u5931\u8D25\uFF1A"}${status.proxy.error}` : lang === "en" ? "Secure proxy is initializing or port binding failed. External devices cannot connect currently." : "\u5B89\u5168\u63A5\u5165\u4EE3\u7406\u6B63\u5728\u542F\u52A8\u6216\u7AEF\u53E3\u7ED1\u5B9A\u5931\u8D25\uFF0C\u5916\u90E8\u624B\u673A\u8BBE\u5907\u6682\u65F6\u65E0\u6CD5\u626B\u7801\u8FDE\u63A5\u3002" }),
1775
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { marginTop: "4px", fontSize: "11px", color: "var(--dsw-alias-label-tertiary, #888)" }, children: lang === "en" ? "Tip: Check whether the secure proxy port (default 3081) is in use or inspect background logs." : "\u6392\u67E5\u5EFA\u8BAE\uFF1A\u8BF7\u68C0\u67E5\u5B89\u5168\u63A5\u5165\u4EE3\u7406\u7AEF\u53E3\uFF08\u9ED8\u8BA4 3081\uFF09\u662F\u5426\u88AB\u5176\u4ED6\u8FDB\u7A0B\u5360\u7528\uFF0C\u6216\u68C0\u67E5\u540E\u53F0\u7EC8\u7AEF\u65E5\u5FD7\u3002" })
1776
+ ]
1777
+ }
1778
+ ) : null,
1779
+ !portReady && !status.loading && !proxyUnavailable ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1780
+ "div",
1781
+ {
1782
+ style: {
1783
+ background: "rgba(245, 158, 11, 0.12)",
1784
+ border: "1px solid rgba(245, 158, 11, 0.35)",
1785
+ borderRadius: "8px",
1786
+ padding: "12px 14px",
1787
+ marginBottom: "14px",
1788
+ fontSize: "13px",
1789
+ color: "var(--dsw-alias-label-secondary, inherit)",
1790
+ lineHeight: "1.6",
1791
+ textAlign: "left"
1792
+ },
1793
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("strong", { children: [
1794
+ "\u26A0\uFE0F ",
1795
+ t("proxyNotReady", lang)
1796
+ ] })
1797
+ }
1798
+ ) : null,
1799
+ showQr && portReady && !proxyUnavailable ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1610
1800
  "div",
1611
1801
  {
1612
1802
  style: {
@@ -1767,28 +1957,30 @@ function QrPairingCard({
1767
1957
  fontSize: "32px",
1768
1958
  fontWeight: "800",
1769
1959
  letterSpacing: showCode ? "6px" : "4px",
1770
- color: isExpired ? "var(--dsw-alias-label-error, #ef4444)" : "var(--dsw-alias-brand-primary, #3b82f6)",
1960
+ color: status?.proxy?.mode === "proxy" && status.proxy.running === false || isExpired ? "var(--dsw-alias-label-error, #ef4444)" : "var(--dsw-alias-brand-primary, #3b82f6)",
1771
1961
  fontFamily: "monospace",
1772
1962
  userSelect: "all"
1773
1963
  },
1774
- children: isExpired ? lang === "en" ? "EXPIRED" : "\u5DF2\u8FC7\u671F" : showCode ? status.code : "\u2022\u2022\u2022\u2022\u2022\u2022"
1964
+ children: status?.proxy?.mode === "proxy" && status.proxy.running === false ? lang === "en" ? "DISABLED" : "\u5DF2\u505C\u7528" : isExpired ? lang === "en" ? "EXPIRED" : "\u5DF2\u8FC7\u671F" : showCode ? status.code : "\u2022\u2022\u2022\u2022\u2022\u2022"
1775
1965
  }
1776
1966
  ),
1777
1967
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1778
1968
  "button",
1779
1969
  {
1780
1970
  type: "button",
1971
+ disabled: status?.proxy?.mode === "proxy" && status.proxy.running === false,
1781
1972
  onClick: () => setShowCode(!showCode),
1782
1973
  title: showCode ? t("clickToHide", lang) : t("clickToReveal", lang),
1783
1974
  style: {
1784
1975
  background: "transparent",
1785
1976
  border: "none",
1786
1977
  color: "var(--dsw-alias-label-secondary, #888)",
1787
- cursor: "pointer",
1978
+ cursor: status?.proxy?.mode === "proxy" && status.proxy.running === false ? "not-allowed" : "pointer",
1788
1979
  display: "flex",
1789
1980
  alignItems: "center",
1790
1981
  justifyContent: "center",
1791
- padding: "4px"
1982
+ padding: "4px",
1983
+ opacity: status?.proxy?.mode === "proxy" && status.proxy.running === false ? 0.4 : 1
1792
1984
  },
1793
1985
  children: showCode ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { style: { width: "20px", height: "20px", fill: "currentColor" }, viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z" }) }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { style: { width: "20px", height: "20px", fill: "currentColor" }, viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M12 7c2.76 0 5 2.24 5 5 0 .65-.13 1.26-.36 1.83l2.92 2.92c1.51-1.26 2.7-2.89 3.43-4.75-1.73-4.39-6-7.5-11-7.5-1.4 0-2.74.25-3.98.7l2.16 2.16C10.74 7.13 11.35 7 12 7zM2 4.27l2.28 2.28.46.46C3.08 8.3 1.78 10.02 1 12c1.73 4.39 6 7.5 11 7.5 1.55 0 3.03-.3 4.38-.84l.42.42L19.73 22 21 20.73 3.27 3 2 4.27zM7.53 9.8l1.55 1.55c-.05.21-.08.43-.08.65 0 1.66 1.34 3 3 3 .22 0 .44-.03.65-.08l1.55 1.55c-.67.33-1.41.53-2.2.53-2.76 0-5-2.24-5-5 0-.79.2-1.53.53-2.2zm4.31-.78l3.15 3.15.02-.16c0-1.66-1.34-3-3-3l-.17.01z" }) })
1794
1986
  }
@@ -1801,7 +1993,7 @@ function QrPairingCard({
1801
1993
  {
1802
1994
  style: {
1803
1995
  fontSize: "12px",
1804
- color: isExpired ? "var(--dsw-alias-label-error, #ef4444)" : "var(--dsw-alias-label-secondary, #888)",
1996
+ color: status?.proxy?.mode === "proxy" && status.proxy.running === false || isExpired ? "var(--dsw-alias-label-error, #ef4444)" : "var(--dsw-alias-label-secondary, #888)",
1805
1997
  margin: "4px 0 16px 0",
1806
1998
  display: "flex",
1807
1999
  alignItems: "center",
@@ -1817,11 +2009,11 @@ function QrPairingCard({
1817
2009
  width: "8px",
1818
2010
  height: "8px",
1819
2011
  borderRadius: "50%",
1820
- background: isExpired ? "#ef4444" : "#10b981"
2012
+ background: status?.proxy?.mode === "proxy" && status.proxy.running === false || isExpired ? "#ef4444" : "#10b981"
1821
2013
  }
1822
2014
  }
1823
2015
  ),
1824
- isExpired ? `\u26A0\uFE0F ${t("codeExpired", lang)}` : `${t("codeValidUntil", lang)}${formatCountdown(timeLeft, lang)}`
2016
+ status?.proxy?.mode === "proxy" && status.proxy.running === false ? `\u26A0\uFE0F ${lang === "en" ? "Proxy Service Error" : "\u5B89\u5168\u63A5\u5165\u4EE3\u7406\u670D\u52A1\u5F02\u5E38"}` : isExpired ? `\u26A0\uFE0F ${t("codeExpired", lang)}` : `${t("codeValidUntil", lang)}${formatCountdown(timeLeft, lang)}`
1825
2017
  ]
1826
2018
  }
1827
2019
  ),
@@ -1839,6 +2031,7 @@ function QrPairingCard({
1839
2031
  "button",
1840
2032
  {
1841
2033
  type: "button",
2034
+ disabled: proxyUnavailable || !portReady,
1842
2035
  onClick: () => setShowQr(!showQr),
1843
2036
  style: {
1844
2037
  padding: "7px 16px",
@@ -1848,7 +2041,8 @@ function QrPairingCard({
1848
2041
  borderRadius: "8px",
1849
2042
  fontSize: "13px",
1850
2043
  fontWeight: "600",
1851
- cursor: "pointer",
2044
+ cursor: proxyUnavailable || !portReady ? "not-allowed" : "pointer",
2045
+ opacity: proxyUnavailable || !portReady ? 0.4 : 1,
1852
2046
  whiteSpace: "nowrap",
1853
2047
  flexShrink: 0
1854
2048
  },
@@ -1859,6 +2053,7 @@ function QrPairingCard({
1859
2053
  "button",
1860
2054
  {
1861
2055
  type: "button",
2056
+ disabled: status?.proxy?.mode === "proxy" && status.proxy.running === false,
1862
2057
  onClick: refreshStatusAndCode,
1863
2058
  style: {
1864
2059
  padding: "7px 16px",
@@ -1868,7 +2063,8 @@ function QrPairingCard({
1868
2063
  borderRadius: "8px",
1869
2064
  fontSize: "13px",
1870
2065
  fontWeight: "600",
1871
- cursor: "pointer",
2066
+ cursor: status?.proxy?.mode === "proxy" && status.proxy.running === false ? "not-allowed" : "pointer",
2067
+ opacity: status?.proxy?.mode === "proxy" && status.proxy.running === false ? 0.4 : 1,
1872
2068
  whiteSpace: "nowrap",
1873
2069
  flexShrink: 0
1874
2070
  },
@@ -3037,21 +3233,365 @@ function ConfigCard({
3037
3233
  );
3038
3234
  }
3039
3235
 
3040
- // src/client/components/StylesCard.jsx
3236
+ // src/client/components/ProxyConfigCard.jsx
3041
3237
  var import_react8 = __toESM(require("react"), 1);
3042
3238
  var import_jsx_runtime8 = require("react/jsx-runtime");
3239
+ function ProxyConfigCard({
3240
+ status,
3241
+ modeInput,
3242
+ setModeInput,
3243
+ proxyPortInput,
3244
+ setProxyPortInput,
3245
+ proxyHostInput,
3246
+ setProxyHostInput,
3247
+ trustedHostsInput,
3248
+ setTrustedHostsInput,
3249
+ saveProxyConfig,
3250
+ resetProxyConfigDefaults,
3251
+ toggleDirectoryPicker,
3252
+ isPatchingDirPicker,
3253
+ copyText,
3254
+ isSaving,
3255
+ lang
3256
+ }) {
3257
+ const isDirect = modeInput === "direct";
3258
+ const dirPicker = status && status.directoryPicker || {};
3259
+ const isBrowseEnabled = Boolean(dirPicker.patchEnabled || dirPicker.runtimeActive);
3260
+ const fieldLabelStyle = {
3261
+ fontSize: "12px",
3262
+ fontWeight: "600",
3263
+ color: "var(--dsw-alias-label-primary, inherit)"
3264
+ };
3265
+ const fieldHintStyle = {
3266
+ fontSize: "11px",
3267
+ color: "var(--dsw-alias-label-tertiary, #888)",
3268
+ lineHeight: 1.5
3269
+ };
3270
+ const inputStyle = {
3271
+ height: "34px",
3272
+ background: "var(--dsw-alias-bg-layer-1, rgba(0,0,0,0.15))",
3273
+ border: "1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.25))",
3274
+ borderRadius: "6px",
3275
+ padding: "0 10px",
3276
+ color: "inherit",
3277
+ fontSize: "13px",
3278
+ outline: "none",
3279
+ boxSizing: "border-box",
3280
+ width: "100%"
3281
+ };
3282
+ const fieldBoxStyle = {
3283
+ display: "flex",
3284
+ flexDirection: "column",
3285
+ gap: "6px",
3286
+ background: "var(--dsw-alias-bg-layer-2, rgba(128,128,128,0.06))",
3287
+ border: "1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.12))",
3288
+ borderRadius: "8px",
3289
+ padding: "10px 12px"
3290
+ };
3291
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
3292
+ "div",
3293
+ {
3294
+ style: {
3295
+ background: "var(--dsw-alias-bg-layer-3, rgba(128,128,128,0.06))",
3296
+ border: "1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.15))",
3297
+ borderRadius: "12px",
3298
+ padding: "14px",
3299
+ display: "flex",
3300
+ flexDirection: "column",
3301
+ gap: "12px"
3302
+ },
3303
+ children: [
3304
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: "8px 12px", flexWrap: "wrap" }, children: [
3305
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
3306
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { style: { fontSize: "16px" }, children: "\u{1F50C}" }),
3307
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { style: { fontSize: "15px", fontWeight: "600", color: "var(--dsw-alias-label-primary, inherit)" }, children: t("proxyConfigCardTitle", lang) })
3308
+ ] }),
3309
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
3310
+ "span",
3311
+ {
3312
+ style: {
3313
+ fontSize: "11px",
3314
+ color: "var(--dsw-alias-label-tertiary, #888)",
3315
+ background: "var(--dsw-alias-bg-layer-2, rgba(128,128,128,0.08))",
3316
+ border: "1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.15))",
3317
+ borderRadius: "999px",
3318
+ padding: "3px 8px",
3319
+ whiteSpace: "nowrap"
3320
+ },
3321
+ children: [
3322
+ t("proxyCurrentRuntime", lang),
3323
+ ": ",
3324
+ status?.proxy?.mode || "-",
3325
+ " / ",
3326
+ status?.proxy?.activePort || "-"
3327
+ ]
3328
+ }
3329
+ )
3330
+ ] }),
3331
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: { fontSize: "12px", color: "var(--dsw-alias-label-tertiary, #888)", lineHeight: 1.5 }, children: t("proxyConfigCardDesc", lang) }),
3332
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(200px, 1fr))", gap: "12px" }, children: [
3333
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: fieldBoxStyle, children: [
3334
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("label", { style: fieldLabelStyle, children: t("proxyModeLabel", lang) }),
3335
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
3336
+ "select",
3337
+ {
3338
+ value: modeInput,
3339
+ onChange: (e) => setModeInput(e.target.value),
3340
+ style: { ...inputStyle, cursor: "pointer" },
3341
+ children: [
3342
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("option", { value: "proxy", children: t("proxyModeProxy", lang) }),
3343
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("option", { value: "direct", children: t("proxyModeDirect", lang) })
3344
+ ]
3345
+ }
3346
+ ),
3347
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { style: fieldHintStyle, children: t("proxyModeHint", lang) })
3348
+ ] }),
3349
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: fieldBoxStyle, children: [
3350
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("label", { style: fieldLabelStyle, children: t("proxyPortLabel", lang) }),
3351
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3352
+ "input",
3353
+ {
3354
+ type: "number",
3355
+ min: "1",
3356
+ max: "65535",
3357
+ value: proxyPortInput,
3358
+ onChange: (e) => setProxyPortInput(e.target.value),
3359
+ disabled: isDirect,
3360
+ style: { ...inputStyle, opacity: isDirect ? 0.55 : 1 }
3361
+ }
3362
+ ),
3363
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { style: fieldHintStyle, children: t("proxyPortHint", lang) })
3364
+ ] }),
3365
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: fieldBoxStyle, children: [
3366
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("label", { style: fieldLabelStyle, children: t("proxyHostLabel", lang) }),
3367
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3368
+ "input",
3369
+ {
3370
+ type: "text",
3371
+ value: proxyHostInput,
3372
+ onChange: (e) => setProxyHostInput(e.target.value),
3373
+ placeholder: "0.0.0.0",
3374
+ disabled: isDirect,
3375
+ style: { ...inputStyle, opacity: isDirect ? 0.55 : 1 }
3376
+ }
3377
+ ),
3378
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { style: fieldHintStyle, children: t("proxyHostHint", lang) })
3379
+ ] })
3380
+ ] }),
3381
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: fieldBoxStyle, children: [
3382
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("label", { style: fieldLabelStyle, children: t("proxyTrustedHostsLabel", lang) }),
3383
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3384
+ "textarea",
3385
+ {
3386
+ value: trustedHostsInput,
3387
+ onChange: (e) => setTrustedHostsInput(e.target.value),
3388
+ placeholder: "xxx.tailnet.ts.net\ndsh.example.com",
3389
+ rows: 3,
3390
+ spellCheck: false,
3391
+ style: {
3392
+ ...inputStyle,
3393
+ height: "auto",
3394
+ minHeight: "70px",
3395
+ padding: "8px 10px",
3396
+ resize: "vertical",
3397
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace"
3398
+ }
3399
+ }
3400
+ ),
3401
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { style: fieldHintStyle, children: t("proxyTrustedHostsHint", lang) })
3402
+ ] }),
3403
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { fontSize: "11px", color: "#f59e0b", lineHeight: 1.5 }, children: [
3404
+ "\u26A0\uFE0F ",
3405
+ t("proxyRestartNote", lang)
3406
+ ] }),
3407
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { display: "flex", alignItems: "center", justifyContent: "flex-end", gap: "10px", flexWrap: "wrap" }, children: [
3408
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3409
+ "button",
3410
+ {
3411
+ type: "button",
3412
+ onClick: resetProxyConfigDefaults,
3413
+ disabled: isSaving,
3414
+ style: {
3415
+ padding: "6px 14px",
3416
+ background: "transparent",
3417
+ border: "1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.25))",
3418
+ color: "var(--dsw-alias-label-secondary, inherit)",
3419
+ borderRadius: "6px",
3420
+ fontSize: "12px",
3421
+ cursor: isSaving ? "not-allowed" : "pointer",
3422
+ opacity: isSaving ? 0.6 : 1,
3423
+ whiteSpace: "nowrap"
3424
+ },
3425
+ children: t("proxyResetBtn", lang)
3426
+ }
3427
+ ),
3428
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3429
+ "button",
3430
+ {
3431
+ type: "button",
3432
+ onClick: saveProxyConfig,
3433
+ disabled: isSaving,
3434
+ style: {
3435
+ padding: "6px 18px",
3436
+ background: "#2563eb",
3437
+ border: "none",
3438
+ color: "#ffffff",
3439
+ borderRadius: "6px",
3440
+ fontSize: "12px",
3441
+ fontWeight: "600",
3442
+ cursor: isSaving ? "not-allowed" : "pointer",
3443
+ opacity: isSaving ? 0.7 : 1,
3444
+ whiteSpace: "nowrap",
3445
+ boxShadow: "0 2px 6px rgba(37, 99, 235, 0.3)"
3446
+ },
3447
+ children: isSaving ? t("proxySavingBtn", lang) : t("proxySaveBtn", lang)
3448
+ }
3449
+ )
3450
+ ] }),
3451
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
3452
+ "div",
3453
+ {
3454
+ style: {
3455
+ ...fieldBoxStyle,
3456
+ marginTop: "4px",
3457
+ border: isBrowseEnabled ? "1px solid rgba(16, 185, 129, 0.3)" : "1px solid rgba(245, 158, 11, 0.35)",
3458
+ background: isBrowseEnabled ? "rgba(16, 185, 129, 0.06)" : "rgba(245, 158, 11, 0.08)"
3459
+ },
3460
+ children: [
3461
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
3462
+ "div",
3463
+ {
3464
+ style: {
3465
+ display: "flex",
3466
+ alignItems: "center",
3467
+ justifyContent: "space-between",
3468
+ gap: "8px",
3469
+ flexWrap: "wrap"
3470
+ },
3471
+ children: [
3472
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { style: fieldLabelStyle, children: t("dirPickerSectionTitle", lang) }),
3473
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3474
+ "span",
3475
+ {
3476
+ style: {
3477
+ fontSize: "11px",
3478
+ fontWeight: "600",
3479
+ color: isBrowseEnabled ? "#10b981" : "#f59e0b"
3480
+ },
3481
+ children: dirPicker.restartRequired ? t("dirPickerStatusPendingRestart", lang) : isBrowseEnabled ? t("dirPickerStatusActive", lang) : t("dirPickerStatusNativeWarn", lang)
3482
+ }
3483
+ )
3484
+ ]
3485
+ }
3486
+ ),
3487
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { style: fieldHintStyle, children: t("dirPickerSectionDesc", lang) }),
3488
+ dirPicker.patchFilePath ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
3489
+ "div",
3490
+ {
3491
+ style: {
3492
+ fontSize: "11px",
3493
+ color: "var(--dsw-alias-label-tertiary, #888)",
3494
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
3495
+ wordBreak: "break-all"
3496
+ },
3497
+ children: [
3498
+ t("dirPickerPatchFileLabel", lang),
3499
+ dirPicker.patchFilePath
3500
+ ]
3501
+ }
3502
+ ) : null,
3503
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
3504
+ "div",
3505
+ {
3506
+ style: {
3507
+ display: "flex",
3508
+ alignItems: "center",
3509
+ gap: "8px",
3510
+ flexWrap: "wrap",
3511
+ marginTop: "4px"
3512
+ },
3513
+ children: [
3514
+ typeof toggleDirectoryPicker === "function" ? isBrowseEnabled ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3515
+ "button",
3516
+ {
3517
+ type: "button",
3518
+ disabled: isPatchingDirPicker,
3519
+ onClick: () => toggleDirectoryPicker(false),
3520
+ style: {
3521
+ padding: "5px 12px",
3522
+ background: "transparent",
3523
+ border: "1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.3))",
3524
+ color: "var(--dsw-alias-label-secondary, inherit)",
3525
+ borderRadius: "6px",
3526
+ fontSize: "12px",
3527
+ cursor: isPatchingDirPicker ? "not-allowed" : "pointer",
3528
+ opacity: isPatchingDirPicker ? 0.6 : 1
3529
+ },
3530
+ children: t("dirPickerRestoreNativeBtn", lang)
3531
+ }
3532
+ ) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3533
+ "button",
3534
+ {
3535
+ type: "button",
3536
+ disabled: isPatchingDirPicker,
3537
+ onClick: () => toggleDirectoryPicker(true),
3538
+ style: {
3539
+ padding: "5px 12px",
3540
+ background: "#2563eb",
3541
+ border: "none",
3542
+ color: "#ffffff",
3543
+ borderRadius: "6px",
3544
+ fontSize: "12px",
3545
+ fontWeight: "600",
3546
+ cursor: isPatchingDirPicker ? "not-allowed" : "pointer",
3547
+ opacity: isPatchingDirPicker ? 0.7 : 1
3548
+ },
3549
+ children: isPatchingDirPicker ? t("dirPickerEnablingBtn", lang) : t("dirPickerEnableBtn", lang)
3550
+ }
3551
+ ) : null,
3552
+ typeof copyText === "function" && dirPicker.yamlSnippet ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3553
+ "button",
3554
+ {
3555
+ type: "button",
3556
+ onClick: () => copyText(dirPicker.yamlSnippet, t("dirPickerCopiedYamlTip", lang)),
3557
+ style: {
3558
+ padding: "5px 10px",
3559
+ background: "transparent",
3560
+ border: "1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.25))",
3561
+ color: "var(--dsw-alias-label-secondary, inherit)",
3562
+ borderRadius: "6px",
3563
+ fontSize: "12px",
3564
+ cursor: "pointer"
3565
+ },
3566
+ children: t("dirPickerCopyYamlBtn", lang)
3567
+ }
3568
+ ) : null
3569
+ ]
3570
+ }
3571
+ )
3572
+ ]
3573
+ }
3574
+ )
3575
+ ]
3576
+ }
3577
+ );
3578
+ }
3579
+
3580
+ // src/client/components/StylesCard.jsx
3581
+ var import_react9 = __toESM(require("react"), 1);
3582
+ var import_jsx_runtime9 = require("react/jsx-runtime");
3043
3583
  function StylesCard({ lang, showToast }) {
3044
- const [snippets, setSnippets] = (0, import_react8.useState)([]);
3045
- const [loading, setLoading] = (0, import_react8.useState)(true);
3046
- const [editingId, setEditingId] = (0, import_react8.useState)(null);
3047
- const [showForm, setShowForm] = (0, import_react8.useState)(false);
3048
- const [name, setName] = (0, import_react8.useState)("");
3049
- const [css, setCss] = (0, import_react8.useState)("");
3050
- const [pcEnabled, setPcEnabled] = (0, import_react8.useState)(false);
3051
- const [mobileEnabled, setMobileEnabled] = (0, import_react8.useState)(true);
3052
- const [expanded, setExpanded] = (0, import_react8.useState)({});
3053
- const [feedback, setFeedback] = (0, import_react8.useState)("");
3054
- const showFeedback = (0, import_react8.useCallback)((msg, type = "success") => {
3584
+ const [snippets, setSnippets] = (0, import_react9.useState)([]);
3585
+ const [loading, setLoading] = (0, import_react9.useState)(true);
3586
+ const [editingId, setEditingId] = (0, import_react9.useState)(null);
3587
+ const [showForm, setShowForm] = (0, import_react9.useState)(false);
3588
+ const [name, setName] = (0, import_react9.useState)("");
3589
+ const [css, setCss] = (0, import_react9.useState)("");
3590
+ const [pcEnabled, setPcEnabled] = (0, import_react9.useState)(false);
3591
+ const [mobileEnabled, setMobileEnabled] = (0, import_react9.useState)(true);
3592
+ const [expanded, setExpanded] = (0, import_react9.useState)({});
3593
+ const [feedback, setFeedback] = (0, import_react9.useState)("");
3594
+ const showFeedback = (0, import_react9.useCallback)((msg, type = "success") => {
3055
3595
  if (typeof showToast === "function") {
3056
3596
  showToast(msg, type);
3057
3597
  } else {
@@ -3059,7 +3599,7 @@ function StylesCard({ lang, showToast }) {
3059
3599
  setTimeout(() => setFeedback(""), 3500);
3060
3600
  }
3061
3601
  }, [showToast]);
3062
- const load = (0, import_react8.useCallback)(() => {
3602
+ const load = (0, import_react9.useCallback)(() => {
3063
3603
  fetchStyles().then((data) => {
3064
3604
  setSnippets(data.snippets || []);
3065
3605
  setLoading(false);
@@ -3068,7 +3608,7 @@ function StylesCard({ lang, showToast }) {
3068
3608
  showFeedback(t("stylesLoadFail", lang), "danger");
3069
3609
  });
3070
3610
  }, [lang, showFeedback]);
3071
- (0, import_react8.useEffect)(() => {
3611
+ (0, import_react9.useEffect)(() => {
3072
3612
  load();
3073
3613
  }, [load]);
3074
3614
  const toggle = (id, scope, enabled) => {
@@ -3210,7 +3750,7 @@ function StylesCard({ lang, showToast }) {
3210
3750
  color: "var(--dsw-alias-brand-primary, #3b82f6)",
3211
3751
  border: "1px solid rgba(59,130,246,0.3)"
3212
3752
  };
3213
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
3753
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
3214
3754
  "div",
3215
3755
  {
3216
3756
  style: {
@@ -3223,15 +3763,15 @@ function StylesCard({ lang, showToast }) {
3223
3763
  gap: "14px"
3224
3764
  },
3225
3765
  children: [
3226
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: "10px", flexWrap: "wrap" }, children: [
3227
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
3228
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { style: { fontSize: "16px" }, children: "\u{1F3A8}" }),
3229
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { style: { fontSize: "15px", fontWeight: "600", color: "var(--dsw-alias-label-primary, inherit)" }, children: t("stylesCardTitle", lang) })
3766
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: "10px", flexWrap: "wrap" }, children: [
3767
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
3768
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { style: { fontSize: "16px" }, children: "\u{1F3A8}" }),
3769
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { style: { fontSize: "15px", fontWeight: "600", color: "var(--dsw-alias-label-primary, inherit)" }, children: t("stylesCardTitle", lang) })
3230
3770
  ] }),
3231
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", onClick: resetAll, style: miniBtnStyle, children: t("stylesResetAll", lang) })
3771
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", onClick: resetAll, style: miniBtnStyle, children: t("stylesResetAll", lang) })
3232
3772
  ] }),
3233
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: { fontSize: "12px", color: "var(--dsw-alias-label-tertiary, #888)", lineHeight: "1.5" }, children: t("stylesCardDesc", lang) }),
3234
- feedback && typeof showToast !== "function" ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3773
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { fontSize: "12px", color: "var(--dsw-alias-label-tertiary, #888)", lineHeight: "1.5" }, children: t("stylesCardDesc", lang) }),
3774
+ feedback && typeof showToast !== "function" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3235
3775
  "div",
3236
3776
  {
3237
3777
  style: {
@@ -3245,15 +3785,15 @@ function StylesCard({ lang, showToast }) {
3245
3785
  children: feedback
3246
3786
  }
3247
3787
  ) : null,
3248
- loading ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: { fontSize: "12px", color: "var(--dsw-alias-label-tertiary, #888)" }, children: lang === "en" ? "Loading style snippets..." : "\u6B63\u5728\u52A0\u8F7D\u6837\u5F0F\u7247\u6BB5..." }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
3249
- snippets.some((s) => s.builtin) ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
3250
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: { fontSize: "13px", fontWeight: "600", color: "var(--dsw-alias-label-secondary, inherit)" }, children: t("stylesPresetsTitle", lang) }),
3251
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: { display: "flex", flexDirection: "column", gap: "8px" }, children: snippets.filter((s) => s.builtin).map((s) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: rowStyle, children: [
3252
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { display: "flex", alignItems: "flex-start", justifyContent: "space-between", gap: "8px", flexWrap: "wrap" }, children: [
3253
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { flex: "1 1 180px", minWidth: 0, maxWidth: "100%" }, children: [
3254
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "6px", flexWrap: "wrap" }, children: [
3255
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { style: { fontSize: "12px", fontWeight: "600", color: "var(--dsw-alias-label-primary, inherit)" }, children: s.name }),
3256
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3788
+ loading ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { fontSize: "12px", color: "var(--dsw-alias-label-tertiary, #888)" }, children: lang === "en" ? "Loading style snippets..." : "\u6B63\u5728\u52A0\u8F7D\u6837\u5F0F\u7247\u6BB5..." }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
3789
+ snippets.some((s) => s.builtin) ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
3790
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { fontSize: "13px", fontWeight: "600", color: "var(--dsw-alias-label-secondary, inherit)" }, children: t("stylesPresetsTitle", lang) }),
3791
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { display: "flex", flexDirection: "column", gap: "8px" }, children: snippets.filter((s) => s.builtin).map((s) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: rowStyle, children: [
3792
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { display: "flex", alignItems: "flex-start", justifyContent: "space-between", gap: "8px", flexWrap: "wrap" }, children: [
3793
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { flex: "1 1 180px", minWidth: 0, maxWidth: "100%" }, children: [
3794
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "6px", flexWrap: "wrap" }, children: [
3795
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { style: { fontSize: "12px", fontWeight: "600", color: "var(--dsw-alias-label-primary, inherit)" }, children: s.name }),
3796
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3257
3797
  "span",
3258
3798
  {
3259
3799
  style: {
@@ -3267,7 +3807,7 @@ function StylesCard({ lang, showToast }) {
3267
3807
  children: "Preset"
3268
3808
  }
3269
3809
  ),
3270
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3810
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3271
3811
  "span",
3272
3812
  {
3273
3813
  style: {
@@ -3282,11 +3822,11 @@ function StylesCard({ lang, showToast }) {
3282
3822
  }
3283
3823
  )
3284
3824
  ] }),
3285
- s.description ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: { fontSize: "11px", color: "var(--dsw-alias-label-tertiary, #888)", marginTop: "3px" }, children: s.description }) : null
3825
+ s.description ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { fontSize: "11px", color: "var(--dsw-alias-label-tertiary, #888)", marginTop: "3px" }, children: s.description }) : null
3286
3826
  ] }),
3287
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "6px 8px", flexWrap: "wrap", maxWidth: "100%" }, children: [
3288
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { style: { display: "inline-flex", alignItems: "center", gap: "4px", fontSize: "11px", cursor: "pointer", whiteSpace: "nowrap" }, children: [
3289
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3827
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "6px 8px", flexWrap: "wrap", maxWidth: "100%" }, children: [
3828
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { style: { display: "inline-flex", alignItems: "center", gap: "4px", fontSize: "11px", cursor: "pointer", whiteSpace: "nowrap" }, children: [
3829
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3290
3830
  "input",
3291
3831
  {
3292
3832
  type: "checkbox",
@@ -3298,8 +3838,8 @@ function StylesCard({ lang, showToast }) {
3298
3838
  "\u{1F5A5}\uFE0F ",
3299
3839
  t("stylesPcShort", lang)
3300
3840
  ] }),
3301
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { style: { display: "inline-flex", alignItems: "center", gap: "4px", fontSize: "11px", cursor: "pointer", whiteSpace: "nowrap" }, children: [
3302
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3841
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { style: { display: "inline-flex", alignItems: "center", gap: "4px", fontSize: "11px", cursor: "pointer", whiteSpace: "nowrap" }, children: [
3842
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3303
3843
  "input",
3304
3844
  {
3305
3845
  type: "checkbox",
@@ -3311,13 +3851,13 @@ function StylesCard({ lang, showToast }) {
3311
3851
  "\u{1F4F1} ",
3312
3852
  t("stylesMobileShort", lang)
3313
3853
  ] }),
3314
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { display: "inline-flex", alignItems: "center", gap: "6px", flexWrap: "wrap" }, children: [
3315
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", onClick: () => toggleExpand(s.id), style: miniBtnStyle, children: expanded[s.id] ? t("stylesHideCss", lang) : t("stylesViewCss", lang) }),
3316
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", onClick: () => copyCss(s.css), style: miniBtnStyle, children: t("stylesCopyCss", lang) })
3854
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { display: "inline-flex", alignItems: "center", gap: "6px", flexWrap: "wrap" }, children: [
3855
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", onClick: () => toggleExpand(s.id), style: miniBtnStyle, children: expanded[s.id] ? t("stylesHideCss", lang) : t("stylesViewCss", lang) }),
3856
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", onClick: () => copyCss(s.css), style: miniBtnStyle, children: t("stylesCopyCss", lang) })
3317
3857
  ] })
3318
3858
  ] })
3319
3859
  ] }),
3320
- expanded[s.id] ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3860
+ expanded[s.id] ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3321
3861
  "pre",
3322
3862
  {
3323
3863
  style: {
@@ -3340,8 +3880,8 @@ function StylesCard({ lang, showToast }) {
3340
3880
  ) : null
3341
3881
  ] }, s.id)) })
3342
3882
  ] }) : null,
3343
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: { fontSize: "13px", fontWeight: "600", color: "var(--dsw-alias-label-secondary, inherit)", marginTop: "4px" }, children: t("stylesCustomTitle", lang) }),
3344
- !showForm ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3883
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { fontSize: "13px", fontWeight: "600", color: "var(--dsw-alias-label-secondary, inherit)", marginTop: "4px" }, children: t("stylesCustomTitle", lang) }),
3884
+ !showForm ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3345
3885
  "button",
3346
3886
  {
3347
3887
  type: "button",
@@ -3359,7 +3899,7 @@ function StylesCard({ lang, showToast }) {
3359
3899
  },
3360
3900
  children: t("stylesAddNew", lang)
3361
3901
  }
3362
- ) : /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
3902
+ ) : /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
3363
3903
  "div",
3364
3904
  {
3365
3905
  style: {
@@ -3372,8 +3912,8 @@ function StylesCard({ lang, showToast }) {
3372
3912
  padding: "12px"
3373
3913
  },
3374
3914
  children: [
3375
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { display: "flex", gap: "8px", flexWrap: "wrap" }, children: [
3376
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3915
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { display: "flex", gap: "8px", flexWrap: "wrap" }, children: [
3916
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3377
3917
  "input",
3378
3918
  {
3379
3919
  type: "text",
@@ -3393,8 +3933,8 @@ function StylesCard({ lang, showToast }) {
3393
3933
  }
3394
3934
  }
3395
3935
  ),
3396
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { style: { display: "inline-flex", alignItems: "center", gap: "4px", fontSize: "11px", cursor: "pointer", whiteSpace: "nowrap" }, children: [
3397
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3936
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { style: { display: "inline-flex", alignItems: "center", gap: "4px", fontSize: "11px", cursor: "pointer", whiteSpace: "nowrap" }, children: [
3937
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3398
3938
  "input",
3399
3939
  {
3400
3940
  type: "checkbox",
@@ -3406,8 +3946,8 @@ function StylesCard({ lang, showToast }) {
3406
3946
  "\u{1F5A5}\uFE0F ",
3407
3947
  t("stylesPcShort", lang)
3408
3948
  ] }),
3409
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { style: { display: "inline-flex", alignItems: "center", gap: "4px", fontSize: "11px", cursor: "pointer", whiteSpace: "nowrap" }, children: [
3410
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3949
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { style: { display: "inline-flex", alignItems: "center", gap: "4px", fontSize: "11px", cursor: "pointer", whiteSpace: "nowrap" }, children: [
3950
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3411
3951
  "input",
3412
3952
  {
3413
3953
  type: "checkbox",
@@ -3420,7 +3960,7 @@ function StylesCard({ lang, showToast }) {
3420
3960
  t("stylesMobileShort", lang)
3421
3961
  ] })
3422
3962
  ] }),
3423
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3963
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3424
3964
  "textarea",
3425
3965
  {
3426
3966
  value: css,
@@ -3443,20 +3983,20 @@ function StylesCard({ lang, showToast }) {
3443
3983
  }
3444
3984
  }
3445
3985
  ),
3446
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { display: "flex", gap: "8px", flexWrap: "wrap" }, children: [
3447
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", onClick: save, style: primaryBtnStyle, children: t("stylesSave", lang) }),
3448
- editingId !== null ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", onClick: cancelEdit, style: miniBtnStyle, children: t("stylesCancelEdit", lang) }) : null,
3449
- showForm && !editingId ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", onClick: cancelEdit, style: miniBtnStyle, children: t("stylesCancelEdit", lang) }) : null
3986
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { display: "flex", gap: "8px", flexWrap: "wrap" }, children: [
3987
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", onClick: save, style: primaryBtnStyle, children: t("stylesSave", lang) }),
3988
+ editingId !== null ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", onClick: cancelEdit, style: miniBtnStyle, children: t("stylesCancelEdit", lang) }) : null,
3989
+ showForm && !editingId ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", onClick: cancelEdit, style: miniBtnStyle, children: t("stylesCancelEdit", lang) }) : null
3450
3990
  ] })
3451
3991
  ]
3452
3992
  }
3453
3993
  ),
3454
- snippets.filter((s) => !s.builtin).length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: { fontSize: "12px", color: "var(--dsw-alias-label-tertiary, #888)" }, children: t("stylesEmptyCustom", lang) }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: { display: "flex", flexDirection: "column", gap: "8px" }, children: snippets.filter((s) => !s.builtin).map((s) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: rowStyle, children: [
3455
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { display: "flex", alignItems: "flex-start", justifyContent: "space-between", gap: "8px", flexWrap: "wrap" }, children: [
3456
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { flex: "1 1 180px", minWidth: 0, maxWidth: "100%" }, children: [
3457
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "6px", flexWrap: "wrap" }, children: [
3458
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { style: { fontSize: "12px", fontWeight: "600", color: "var(--dsw-alias-label-primary, inherit)" }, children: s.name }),
3459
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3994
+ snippets.filter((s) => !s.builtin).length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { fontSize: "12px", color: "var(--dsw-alias-label-tertiary, #888)" }, children: t("stylesEmptyCustom", lang) }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { display: "flex", flexDirection: "column", gap: "8px" }, children: snippets.filter((s) => !s.builtin).map((s) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: rowStyle, children: [
3995
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { display: "flex", alignItems: "flex-start", justifyContent: "space-between", gap: "8px", flexWrap: "wrap" }, children: [
3996
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { flex: "1 1 180px", minWidth: 0, maxWidth: "100%" }, children: [
3997
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "6px", flexWrap: "wrap" }, children: [
3998
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { style: { fontSize: "12px", fontWeight: "600", color: "var(--dsw-alias-label-primary, inherit)" }, children: s.name }),
3999
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3460
4000
  "span",
3461
4001
  {
3462
4002
  style: {
@@ -3471,11 +4011,11 @@ function StylesCard({ lang, showToast }) {
3471
4011
  }
3472
4012
  )
3473
4013
  ] }),
3474
- s.description ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: { fontSize: "11px", color: "var(--dsw-alias-label-tertiary, #888)", marginTop: "3px" }, children: s.description }) : null
4014
+ s.description ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { fontSize: "11px", color: "var(--dsw-alias-label-tertiary, #888)", marginTop: "3px" }, children: s.description }) : null
3475
4015
  ] }),
3476
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "6px 8px", flexWrap: "wrap", maxWidth: "100%" }, children: [
3477
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { style: { display: "inline-flex", alignItems: "center", gap: "4px", fontSize: "11px", cursor: "pointer", whiteSpace: "nowrap" }, children: [
3478
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
4016
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "6px 8px", flexWrap: "wrap", maxWidth: "100%" }, children: [
4017
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { style: { display: "inline-flex", alignItems: "center", gap: "4px", fontSize: "11px", cursor: "pointer", whiteSpace: "nowrap" }, children: [
4018
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3479
4019
  "input",
3480
4020
  {
3481
4021
  type: "checkbox",
@@ -3487,8 +4027,8 @@ function StylesCard({ lang, showToast }) {
3487
4027
  "\u{1F5A5}\uFE0F ",
3488
4028
  t("stylesPcShort", lang)
3489
4029
  ] }),
3490
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { style: { display: "inline-flex", alignItems: "center", gap: "4px", fontSize: "11px", cursor: "pointer", whiteSpace: "nowrap" }, children: [
3491
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
4030
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { style: { display: "inline-flex", alignItems: "center", gap: "4px", fontSize: "11px", cursor: "pointer", whiteSpace: "nowrap" }, children: [
4031
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3492
4032
  "input",
3493
4033
  {
3494
4034
  type: "checkbox",
@@ -3500,15 +4040,15 @@ function StylesCard({ lang, showToast }) {
3500
4040
  "\u{1F4F1} ",
3501
4041
  t("stylesMobileShort", lang)
3502
4042
  ] }),
3503
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { display: "inline-flex", alignItems: "center", gap: "6px", flexWrap: "wrap" }, children: [
3504
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", onClick: () => startEdit(s), style: miniBtnStyle, children: t("stylesEditBtn", lang) }),
3505
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", onClick: () => remove(s), style: { ...miniBtnStyle, color: "#ef4444" }, children: t("stylesDeleteBtn", lang) }),
3506
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", onClick: () => toggleExpand(s.id), style: miniBtnStyle, children: expanded[s.id] ? t("stylesHideCss", lang) : t("stylesViewCss", lang) }),
3507
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", onClick: () => copyCss(s.css), style: miniBtnStyle, children: t("stylesCopyCss", lang) })
4043
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { display: "inline-flex", alignItems: "center", gap: "6px", flexWrap: "wrap" }, children: [
4044
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", onClick: () => startEdit(s), style: miniBtnStyle, children: t("stylesEditBtn", lang) }),
4045
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", onClick: () => remove(s), style: { ...miniBtnStyle, color: "#ef4444" }, children: t("stylesDeleteBtn", lang) }),
4046
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", onClick: () => toggleExpand(s.id), style: miniBtnStyle, children: expanded[s.id] ? t("stylesHideCss", lang) : t("stylesViewCss", lang) }),
4047
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", onClick: () => copyCss(s.css), style: miniBtnStyle, children: t("stylesCopyCss", lang) })
3508
4048
  ] })
3509
4049
  ] })
3510
4050
  ] }),
3511
- expanded[s.id] ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
4051
+ expanded[s.id] ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3512
4052
  "pre",
3513
4053
  {
3514
4054
  style: {
@@ -3530,7 +4070,7 @@ function StylesCard({ lang, showToast }) {
3530
4070
  }
3531
4071
  ) : null
3532
4072
  ] }, s.id)) }),
3533
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: { fontSize: "11px", color: "var(--dsw-alias-label-tertiary, #888)", lineHeight: "1.6" }, children: t("stylesHint", lang) })
4073
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { fontSize: "11px", color: "var(--dsw-alias-label-tertiary, #888)", lineHeight: "1.6" }, children: t("stylesHint", lang) })
3534
4074
  ] })
3535
4075
  ]
3536
4076
  }
@@ -3538,14 +4078,14 @@ function StylesCard({ lang, showToast }) {
3538
4078
  }
3539
4079
 
3540
4080
  // src/client/version.js
3541
- var PLUGIN_VERSION = "v1.9.2";
3542
- var BUILD_TIME = "2026-10-13 13:09:49";
4081
+ var PLUGIN_VERSION = "v1.10.2";
4082
+ var BUILD_TIME = "2026-11-02 23:13:58";
3543
4083
 
3544
4084
  // src/client/components/MainSection.jsx
3545
- var import_jsx_runtime9 = require("react/jsx-runtime");
4085
+ var import_jsx_runtime10 = require("react/jsx-runtime");
3546
4086
  function TailscaleMobileSection(props) {
3547
4087
  const ctx = props && props.ctx || typeof window !== "undefined" && window.__DSH_CLIENT_CTX__ || null;
3548
- const [status, setStatus] = (0, import_react9.useState)({
4088
+ const [status, setStatus] = (0, import_react10.useState)({
3549
4089
  tailscaleIp: "",
3550
4090
  lanIp: "",
3551
4091
  code: "",
@@ -3567,36 +4107,53 @@ function TailscaleMobileSection(props) {
3567
4107
  locale: "zh",
3568
4108
  pairingBridgeMode: "pending",
3569
4109
  pairingBridgeConflict: "",
3570
- pairingBridgeConflictId: ""
4110
+ pairingBridgeConflictId: "",
4111
+ directoryPicker: {
4112
+ patchEnabled: false,
4113
+ patchPartial: false,
4114
+ runtimeActive: false,
4115
+ restartRequired: false,
4116
+ needsWarning: false,
4117
+ patchFilePath: "",
4118
+ yamlSnippet: ""
4119
+ },
4120
+ proxy: null
3571
4121
  });
3572
- const [secretInput, setSecretInput] = (0, import_react9.useState)("");
3573
- const [showSecret, setShowSecret] = (0, import_react9.useState)(false);
3574
- const [showCode, setShowCode] = (0, import_react9.useState)(false);
3575
- const [showQr, setShowQr] = (0, import_react9.useState)(false);
3576
- const [selectedTab, setSelectedTab] = (0, import_react9.useState)("lan");
3577
- const [timeLeft, setTimeLeft] = (0, import_react9.useState)(300);
3578
- const [toast, setToast] = (0, import_react9.useState)(null);
3579
- const [maxVisitsInput, setMaxVisitsInput] = (0, import_react9.useState)("60");
3580
- const [maxFailedInput, setMaxFailedInput] = (0, import_react9.useState)("5");
3581
- const [lockDurationMinsInput, setLockDurationMinsInput] = (0, import_react9.useState)("15");
3582
- const [isSaving, setIsSaving] = (0, import_react9.useState)(false);
3583
- const [showVersionTip, setShowVersionTip] = (0, import_react9.useState)(false);
3584
- const [currentLang, setCurrentLang] = (0, import_react9.useState)(resolveLocale(ctx, status));
3585
- const [npmLatestVersion, setNpmLatestVersion] = (0, import_react9.useState)("");
3586
- const [hasUpdate, setHasUpdate] = (0, import_react9.useState)(false);
3587
- const [conflictBannerDismissed, setConflictBannerDismissed] = (0, import_react9.useState)(false);
3588
- const qrTabTouchedRef = (0, import_react9.useRef)(false);
3589
- const toastTimerRef = (0, import_react9.useRef)(null);
3590
- const showToast = (0, import_react9.useCallback)((message, type = "success", duration = 2500) => {
4122
+ const [secretInput, setSecretInput] = (0, import_react10.useState)("");
4123
+ const [showSecret, setShowSecret] = (0, import_react10.useState)(false);
4124
+ const [showCode, setShowCode] = (0, import_react10.useState)(false);
4125
+ const [showQr, setShowQr] = (0, import_react10.useState)(false);
4126
+ const [selectedTab, setSelectedTab] = (0, import_react10.useState)("lan");
4127
+ const [timeLeft, setTimeLeft] = (0, import_react10.useState)(300);
4128
+ const [toast, setToast] = (0, import_react10.useState)(null);
4129
+ const [maxVisitsInput, setMaxVisitsInput] = (0, import_react10.useState)("60");
4130
+ const [maxFailedInput, setMaxFailedInput] = (0, import_react10.useState)("5");
4131
+ const [lockDurationMinsInput, setLockDurationMinsInput] = (0, import_react10.useState)("15");
4132
+ const [modeInput, setModeInput] = (0, import_react10.useState)("proxy");
4133
+ const [proxyPortInput, setProxyPortInput] = (0, import_react10.useState)("3081");
4134
+ const [proxyHostInput, setProxyHostInput] = (0, import_react10.useState)("0.0.0.0");
4135
+ const [trustedHostsInput, setTrustedHostsInput] = (0, import_react10.useState)("");
4136
+ const proxyConfigLoadedRef = (0, import_react10.useRef)(false);
4137
+ const [isSaving, setIsSaving] = (0, import_react10.useState)(false);
4138
+ const [showVersionTip, setShowVersionTip] = (0, import_react10.useState)(false);
4139
+ const [currentLang, setCurrentLang] = (0, import_react10.useState)(resolveLocale(ctx, status));
4140
+ const [npmLatestVersion, setNpmLatestVersion] = (0, import_react10.useState)("");
4141
+ const [hasUpdate, setHasUpdate] = (0, import_react10.useState)(false);
4142
+ const [conflictBannerDismissed, setConflictBannerDismissed] = (0, import_react10.useState)(false);
4143
+ const [dirPickerBannerDismissed, setDirPickerBannerDismissed] = (0, import_react10.useState)(false);
4144
+ const [isPatchingDirPicker, setIsPatchingDirPicker] = (0, import_react10.useState)(false);
4145
+ const qrTabTouchedRef = (0, import_react10.useRef)(false);
4146
+ const toastTimerRef = (0, import_react10.useRef)(null);
4147
+ const showToast = (0, import_react10.useCallback)((message, type = "success", duration = 2500) => {
3591
4148
  if (toastTimerRef.current) clearTimeout(toastTimerRef.current);
3592
4149
  setToast({ message, type });
3593
4150
  toastTimerRef.current = setTimeout(() => setToast(null), duration);
3594
4151
  }, []);
3595
- const updateQrTab = (0, import_react9.useCallback)((tab) => {
4152
+ const updateQrTab = (0, import_react10.useCallback)((tab) => {
3596
4153
  qrTabTouchedRef.current = true;
3597
4154
  setSelectedTab(tab);
3598
4155
  }, []);
3599
- const [activeTab, setActiveTab] = (0, import_react9.useState)(() => {
4156
+ const [activeTab, setActiveTab] = (0, import_react10.useState)(() => {
3600
4157
  try {
3601
4158
  const saved = localStorage.getItem("dsh-rm-settings-tab");
3602
4159
  if (saved === "access" || saved === "devices" || saved === "style" || saved === "storage") return saved;
@@ -3611,7 +4168,7 @@ function TailscaleMobileSection(props) {
3611
4168
  } catch (e) {
3612
4169
  }
3613
4170
  };
3614
- const refreshStatusOnly = (0, import_react9.useCallback)(() => {
4171
+ const refreshStatusOnly = (0, import_react10.useCallback)(() => {
3615
4172
  fetchStatus().then((data) => {
3616
4173
  if (data.tailscaleIp && !qrTabTouchedRef.current) {
3617
4174
  setSelectedTab("tailscale");
@@ -3623,6 +4180,14 @@ function TailscaleMobileSection(props) {
3623
4180
  if (data.locale) {
3624
4181
  setCurrentLang(resolveLocale(ctx, data));
3625
4182
  }
4183
+ if (!proxyConfigLoadedRef.current && data.proxy) {
4184
+ setModeInput(data.proxy.configuredMode === "direct" ? "direct" : "proxy");
4185
+ setProxyPortInput(String(data.proxy.proxyPort || 3081));
4186
+ setProxyHostInput(data.proxy.proxyHost || "0.0.0.0");
4187
+ setTrustedHostsInput(Array.isArray(data.proxy.trustedHosts) ? data.proxy.trustedHosts.join("\n") : "");
4188
+ proxyConfigLoadedRef.current = true;
4189
+ }
4190
+ const dp = data.directoryPicker || {};
3626
4191
  setStatus((prev) => ({
3627
4192
  ...prev,
3628
4193
  tailscaleIp: data.tailscaleIp || "",
@@ -3643,12 +4208,22 @@ function TailscaleMobileSection(props) {
3643
4208
  pairingBridgeMode: data.pairingBridgeMode || "pending",
3644
4209
  pairingBridgeConflict: data.pairingBridgeConflict || "",
3645
4210
  pairingBridgeConflictId: data.pairingBridgeConflictId || "",
4211
+ directoryPicker: {
4212
+ patchEnabled: Boolean(dp.patchEnabled),
4213
+ patchPartial: Boolean(dp.patchPartial),
4214
+ runtimeActive: Boolean(dp.runtimeActive),
4215
+ restartRequired: Boolean(dp.restartRequired),
4216
+ needsWarning: Boolean(dp.needsWarning),
4217
+ patchFilePath: dp.patchFilePath || "",
4218
+ yamlSnippet: dp.yamlSnippet || ""
4219
+ },
4220
+ proxy: data.proxy || null,
3646
4221
  loading: false
3647
4222
  }));
3648
4223
  }).catch(() => {
3649
4224
  });
3650
4225
  }, [ctx]);
3651
- const refreshStatusAndCode = (0, import_react9.useCallback)(() => {
4226
+ const refreshStatusAndCode = (0, import_react10.useCallback)(() => {
3652
4227
  refreshStatusOnly();
3653
4228
  generatePairCode().then((data) => {
3654
4229
  if (data.success) {
@@ -3663,7 +4238,7 @@ function TailscaleMobileSection(props) {
3663
4238
  }).catch(() => {
3664
4239
  });
3665
4240
  }, [refreshStatusOnly]);
3666
- (0, import_react9.useEffect)(() => {
4241
+ (0, import_react10.useEffect)(() => {
3667
4242
  let cancelled = false;
3668
4243
  fetch("https://registry.npmjs.org/dsh-remote-mobile/latest", { cache: "no-store" }).then((res) => res.ok ? res.json() : Promise.reject(new Error("npm registry error"))).then((data) => {
3669
4244
  if (cancelled) return;
@@ -3691,7 +4266,7 @@ function TailscaleMobileSection(props) {
3691
4266
  }
3692
4267
  return 0;
3693
4268
  };
3694
- (0, import_react9.useEffect)(() => {
4269
+ (0, import_react10.useEffect)(() => {
3695
4270
  refreshStatusAndCode();
3696
4271
  const localeUnsubs = [];
3697
4272
  try {
@@ -3759,7 +4334,7 @@ function TailscaleMobileSection(props) {
3759
4334
  }
3760
4335
  };
3761
4336
  }, [refreshStatusAndCode, refreshStatusOnly, currentLang, ctx]);
3762
- (0, import_react9.useEffect)(() => {
4337
+ (0, import_react10.useEffect)(() => {
3763
4338
  if (timeLeft <= 0) return;
3764
4339
  const timer = setInterval(() => {
3765
4340
  setTimeLeft((prev) => {
@@ -3945,14 +4520,118 @@ function TailscaleMobileSection(props) {
3945
4520
  showToast((currentLang === "en" ? "Reset failed: " : "\u91CD\u7F6E\u5931\u8D25\uFF1A") + (err?.message || err), "danger", 3500);
3946
4521
  });
3947
4522
  };
4523
+ const persistProxyConfig = (mode, port, host, hosts) => {
4524
+ setIsSaving(true);
4525
+ updateProxyConfig({
4526
+ mode,
4527
+ proxyPort: port,
4528
+ proxyHost: host,
4529
+ trustedHosts: hosts
4530
+ }).then((data) => {
4531
+ setIsSaving(false);
4532
+ if (data.success) {
4533
+ setStatus((prev) => ({
4534
+ ...prev,
4535
+ proxy: prev.proxy ? {
4536
+ ...prev.proxy,
4537
+ configuredMode: data.mode,
4538
+ proxyPort: data.proxyPort,
4539
+ proxyHost: data.proxyHost,
4540
+ trustedHosts: data.trustedHosts || [],
4541
+ mode: data.activeMode || prev.proxy.mode,
4542
+ activePort: data.activePort || prev.proxy.activePort
4543
+ } : prev.proxy
4544
+ }));
4545
+ refreshStatusOnly();
4546
+ showToast(t("proxySavedToast", currentLang), "success", 5e3);
4547
+ } else {
4548
+ showToast((currentLang === "en" ? "Save failed: " : "\u4FDD\u5B58\u5931\u8D25\uFF1A") + (data.reason || ""), "danger", 3500);
4549
+ }
4550
+ }).catch((err) => {
4551
+ setIsSaving(false);
4552
+ showToast((currentLang === "en" ? "Save failed: " : "\u4FDD\u5B58\u5931\u8D25\uFF1A") + (err?.message || err), "danger", 3500);
4553
+ });
4554
+ };
4555
+ const saveProxyConfig = () => {
4556
+ const port = parseInt(proxyPortInput, 10);
4557
+ const host = proxyHostInput.trim();
4558
+ const hosts = trustedHostsInput.split(/\r?\n/).map((h) => h.trim()).filter(Boolean);
4559
+ if (!Number.isInteger(port) || port < 1 || port > 65535 || !host) {
4560
+ alert(t("proxySaveFailTip", currentLang));
4561
+ return;
4562
+ }
4563
+ if (!confirm(t("proxySaveConfirm", currentLang))) {
4564
+ return;
4565
+ }
4566
+ persistProxyConfig(modeInput === "direct" ? "direct" : "proxy", port, host, hosts);
4567
+ };
4568
+ const resetProxyConfigDefaults = () => {
4569
+ if (!confirm(t("proxyResetConfirm", currentLang))) {
4570
+ return;
4571
+ }
4572
+ setModeInput("proxy");
4573
+ setProxyPortInput("3081");
4574
+ setProxyHostInput("0.0.0.0");
4575
+ setTrustedHostsInput("");
4576
+ persistProxyConfig("proxy", 3081, "0.0.0.0", []);
4577
+ };
4578
+ const toggleDirectoryPicker = (enable) => {
4579
+ if (!enable) {
4580
+ if (!confirm(t("dirPickerDisableConfirm", currentLang))) {
4581
+ return;
4582
+ }
4583
+ }
4584
+ setIsPatchingDirPicker(true);
4585
+ toggleDirectoryPickerApi(enable).then((res) => {
4586
+ setIsPatchingDirPicker(false);
4587
+ if (res && res.success) {
4588
+ const dp = res.directoryPicker || {};
4589
+ setStatus((prev) => ({
4590
+ ...prev,
4591
+ directoryPicker: {
4592
+ patchEnabled: Boolean(dp.patchEnabled),
4593
+ patchPartial: Boolean(dp.patchPartial),
4594
+ runtimeActive: Boolean(dp.runtimeActive),
4595
+ restartRequired: Boolean(dp.restartRequired),
4596
+ needsWarning: Boolean(dp.needsWarning),
4597
+ patchFilePath: dp.patchFilePath || "",
4598
+ yamlSnippet: dp.yamlSnippet || ""
4599
+ }
4600
+ }));
4601
+ refreshStatusOnly();
4602
+ showToast(
4603
+ t(enable ? "dirPickerEnabledToast" : "dirPickerDisabledToast", currentLang),
4604
+ "success",
4605
+ 4500
4606
+ );
4607
+ } else {
4608
+ showToast(
4609
+ (currentLang === "en" ? "Operation failed: " : "\u64CD\u4F5C\u5931\u8D25\uFF1A") + (res && res.reason || ""),
4610
+ "danger",
4611
+ 3500
4612
+ );
4613
+ }
4614
+ }).catch((err) => {
4615
+ setIsPatchingDirPicker(false);
4616
+ showToast(
4617
+ (currentLang === "en" ? "Operation failed: " : "\u64CD\u4F5C\u5931\u8D25\uFF1A") + (err?.message || err),
4618
+ "danger",
4619
+ 3500
4620
+ );
4621
+ });
4622
+ };
3948
4623
  const copyDirectLink = () => {
3949
4624
  if (timeLeft <= 0) {
3950
4625
  alert(t("codeExpired", currentLang));
3951
4626
  return;
3952
4627
  }
3953
4628
  const effectiveQrIp = selectedTab === "tailscale" && status.tailscaleIp ? status.tailscaleIp : status.lanIp || "127.0.0.1";
3954
- const port = typeof window !== "undefined" && window.location.port || "3080";
3955
- const directLink = `http://${effectiveQrIp}:${port}/auth?token=${status.code || ""}`;
4629
+ const port = resolveAccessPort(status);
4630
+ if (port === null) {
4631
+ showToast(t("proxyNotReady", currentLang), "danger", 3e3);
4632
+ return;
4633
+ }
4634
+ const directLink = buildDirectLink(effectiveQrIp, port, status.code || "");
3956
4635
  copyText(directLink, t("copiedTip", currentLang));
3957
4636
  setStatus((prev) => ({ ...prev, copied: true }));
3958
4637
  setTimeout(() => {
@@ -3997,7 +4676,7 @@ function TailscaleMobileSection(props) {
3997
4676
  }
3998
4677
  }
3999
4678
  };
4000
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
4679
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
4001
4680
  "div",
4002
4681
  {
4003
4682
  style: {
@@ -4009,7 +4688,7 @@ function TailscaleMobileSection(props) {
4009
4688
  position: "relative"
4010
4689
  },
4011
4690
  children: [
4012
- toast ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
4691
+ toast ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
4013
4692
  "div",
4014
4693
  {
4015
4694
  style: {
@@ -4034,12 +4713,12 @@ function TailscaleMobileSection(props) {
4034
4713
  pointerEvents: "none"
4035
4714
  },
4036
4715
  children: [
4037
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: toast.type === "danger" ? "\u{1F6A8}" : toast.type === "success" ? "\u2705" : "\u2139\uFE0F" }),
4038
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: toast.message })
4716
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: toast.type === "danger" ? "\u{1F6A8}" : toast.type === "success" ? "\u2705" : "\u2139\uFE0F" }),
4717
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: toast.message })
4039
4718
  ]
4040
4719
  }
4041
4720
  ) : null,
4042
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
4721
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
4043
4722
  "div",
4044
4723
  {
4045
4724
  style: {
@@ -4051,8 +4730,8 @@ function TailscaleMobileSection(props) {
4051
4730
  marginBottom: "4px"
4052
4731
  },
4053
4732
  children: [
4054
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { minWidth: "220px", flex: 1 }, children: [
4055
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
4733
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: { minWidth: "220px", flex: 1 }, children: [
4734
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
4056
4735
  "div",
4057
4736
  {
4058
4737
  style: {
@@ -4063,7 +4742,7 @@ function TailscaleMobileSection(props) {
4063
4742
  margin: "0 0 6px 0"
4064
4743
  },
4065
4744
  children: [
4066
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4745
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4067
4746
  "h3",
4068
4747
  {
4069
4748
  style: {
@@ -4075,7 +4754,7 @@ function TailscaleMobileSection(props) {
4075
4754
  children: t("title", currentLang)
4076
4755
  }
4077
4756
  ),
4078
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
4757
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
4079
4758
  "div",
4080
4759
  {
4081
4760
  style: { position: "relative", display: "inline-flex", alignItems: "center" },
@@ -4089,7 +4768,7 @@ function TailscaleMobileSection(props) {
4089
4768
  );
4090
4769
  },
4091
4770
  children: [
4092
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4771
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4093
4772
  "span",
4094
4773
  {
4095
4774
  style: {
@@ -4106,7 +4785,7 @@ function TailscaleMobileSection(props) {
4106
4785
  children: PLUGIN_VERSION
4107
4786
  }
4108
4787
  ),
4109
- showVersionTip ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4788
+ showVersionTip ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4110
4789
  "div",
4111
4790
  {
4112
4791
  style: {
@@ -4134,7 +4813,7 @@ function TailscaleMobileSection(props) {
4134
4813
  ]
4135
4814
  }
4136
4815
  ),
4137
- hasUpdate && npmLatestVersion ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
4816
+ hasUpdate && npmLatestVersion ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
4138
4817
  "a",
4139
4818
  {
4140
4819
  href: `https://www.npmjs.com/package/dsh-remote-mobile/v/${npmLatestVersion}`,
@@ -4164,7 +4843,7 @@ function TailscaleMobileSection(props) {
4164
4843
  ]
4165
4844
  }
4166
4845
  ),
4167
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4846
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4168
4847
  "div",
4169
4848
  {
4170
4849
  style: {
@@ -4175,7 +4854,7 @@ function TailscaleMobileSection(props) {
4175
4854
  }
4176
4855
  )
4177
4856
  ] }),
4178
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
4857
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
4179
4858
  "a",
4180
4859
  {
4181
4860
  href: "https://github.com/IceApriler/dsh-remote-mobile",
@@ -4197,15 +4876,15 @@ function TailscaleMobileSection(props) {
4197
4876
  cursor: "pointer"
4198
4877
  },
4199
4878
  children: [
4200
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("svg", { style: { width: "16px", height: "16px", fill: "currentColor" }, viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z" }) }),
4201
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: "dsh-remote-mobile" })
4879
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("svg", { style: { width: "16px", height: "16px", fill: "currentColor" }, viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { d: "M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z" }) }),
4880
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: "dsh-remote-mobile" })
4202
4881
  ]
4203
4882
  }
4204
4883
  )
4205
4884
  ]
4206
4885
  }
4207
4886
  ),
4208
- status.pairingBridgeConflict && !conflictBannerDismissed ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
4887
+ status.pairingBridgeConflict && !conflictBannerDismissed ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
4209
4888
  "div",
4210
4889
  {
4211
4890
  style: {
@@ -4218,9 +4897,9 @@ function TailscaleMobileSection(props) {
4218
4897
  background: "rgba(245, 158, 11, 0.12)"
4219
4898
  },
4220
4899
  children: [
4221
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { style: { fontSize: "16px", lineHeight: "20px" }, children: "\u26A0\uFE0F" }),
4222
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { flex: 1, minWidth: 0 }, children: [
4223
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4900
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { style: { fontSize: "16px", lineHeight: "20px" }, children: "\u26A0\uFE0F" }),
4901
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: { flex: 1, minWidth: 0 }, children: [
4902
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4224
4903
  "div",
4225
4904
  {
4226
4905
  style: {
@@ -4232,7 +4911,7 @@ function TailscaleMobileSection(props) {
4232
4911
  children: t("conflictBannerTitle", currentLang)
4233
4912
  }
4234
4913
  ),
4235
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4914
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4236
4915
  "div",
4237
4916
  {
4238
4917
  style: {
@@ -4246,7 +4925,7 @@ function TailscaleMobileSection(props) {
4246
4925
  })
4247
4926
  }
4248
4927
  ),
4249
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4928
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4250
4929
  "button",
4251
4930
  {
4252
4931
  type: "button",
@@ -4278,7 +4957,7 @@ function TailscaleMobileSection(props) {
4278
4957
  }
4279
4958
  )
4280
4959
  ] }),
4281
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4960
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4282
4961
  "button",
4283
4962
  {
4284
4963
  type: "button",
@@ -4304,7 +4983,192 @@ function TailscaleMobileSection(props) {
4304
4983
  ]
4305
4984
  }
4306
4985
  ) : null,
4307
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4986
+ !status.loading && status.directoryPicker.needsWarning && !dirPickerBannerDismissed ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
4987
+ "div",
4988
+ {
4989
+ style: {
4990
+ display: "flex",
4991
+ alignItems: "flex-start",
4992
+ gap: "10px",
4993
+ padding: "12px 14px",
4994
+ borderRadius: "10px",
4995
+ border: "1px solid rgba(245, 158, 11, 0.45)",
4996
+ background: "rgba(245, 158, 11, 0.12)"
4997
+ },
4998
+ children: [
4999
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { style: { fontSize: "16px", lineHeight: "20px" }, children: "\u{1F4C1}" }),
5000
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: { flex: 1, minWidth: 0 }, children: [
5001
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
5002
+ "div",
5003
+ {
5004
+ style: {
5005
+ fontSize: "13px",
5006
+ fontWeight: "700",
5007
+ marginBottom: "4px",
5008
+ color: "var(--dsw-alias-label-primary, inherit)"
5009
+ },
5010
+ children: t("dirPickerBannerTitle", currentLang)
5011
+ }
5012
+ ),
5013
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
5014
+ "div",
5015
+ {
5016
+ style: {
5017
+ fontSize: "12px",
5018
+ lineHeight: 1.6,
5019
+ whiteSpace: "pre-line",
5020
+ color: "var(--dsw-alias-label-secondary, inherit)"
5021
+ },
5022
+ children: t("dirPickerBannerBody", currentLang)
5023
+ }
5024
+ ),
5025
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: { marginTop: "10px", display: "flex", alignItems: "center", gap: "8px", flexWrap: "wrap" }, children: [
5026
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
5027
+ "button",
5028
+ {
5029
+ type: "button",
5030
+ disabled: isPatchingDirPicker,
5031
+ onClick: () => toggleDirectoryPicker(true),
5032
+ style: {
5033
+ display: "inline-flex",
5034
+ alignItems: "center",
5035
+ gap: "6px",
5036
+ padding: "6px 12px",
5037
+ fontSize: "12px",
5038
+ fontWeight: "600",
5039
+ borderRadius: "7px",
5040
+ cursor: isPatchingDirPicker ? "not-allowed" : "pointer",
5041
+ opacity: isPatchingDirPicker ? 0.7 : 1,
5042
+ border: "none",
5043
+ background: "#2563eb",
5044
+ color: "#ffffff",
5045
+ boxShadow: "0 2px 6px rgba(37, 99, 235, 0.3)"
5046
+ },
5047
+ children: isPatchingDirPicker ? t("dirPickerEnablingBtn", currentLang) : t("dirPickerEnableBtn", currentLang)
5048
+ }
5049
+ ),
5050
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
5051
+ "button",
5052
+ {
5053
+ type: "button",
5054
+ onClick: () => {
5055
+ const yaml = status.directoryPicker.yamlSnippet || "";
5056
+ if (yaml) {
5057
+ copyText(yaml, t("dirPickerCopiedYamlTip", currentLang));
5058
+ }
5059
+ },
5060
+ style: {
5061
+ display: "inline-flex",
5062
+ alignItems: "center",
5063
+ gap: "6px",
5064
+ padding: "5px 10px",
5065
+ fontSize: "12px",
5066
+ fontWeight: "600",
5067
+ borderRadius: "7px",
5068
+ cursor: "pointer",
5069
+ border: "1px solid rgba(245, 158, 11, 0.55)",
5070
+ background: "rgba(245, 158, 11, 0.18)",
5071
+ color: "var(--dsw-alias-label-primary, inherit)"
5072
+ },
5073
+ children: t("dirPickerCopyYamlBtn", currentLang)
5074
+ }
5075
+ )
5076
+ ] })
5077
+ ] }),
5078
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
5079
+ "button",
5080
+ {
5081
+ type: "button",
5082
+ onClick: () => setDirPickerBannerDismissed(true),
5083
+ title: t("dirPickerBannerDismiss", currentLang),
5084
+ style: {
5085
+ flexShrink: 0,
5086
+ width: "24px",
5087
+ height: "24px",
5088
+ display: "inline-flex",
5089
+ alignItems: "center",
5090
+ justifyContent: "center",
5091
+ border: "none",
5092
+ borderRadius: "6px",
5093
+ cursor: "pointer",
5094
+ fontSize: "12px",
5095
+ background: "rgba(128, 128, 128, 0.15)",
5096
+ color: "var(--dsw-alias-label-secondary, inherit)"
5097
+ },
5098
+ children: "\u2715"
5099
+ }
5100
+ )
5101
+ ]
5102
+ }
5103
+ ) : null,
5104
+ !status.loading && status.directoryPicker.patchEnabled && status.directoryPicker.restartRequired && !dirPickerBannerDismissed ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
5105
+ "div",
5106
+ {
5107
+ style: {
5108
+ display: "flex",
5109
+ alignItems: "flex-start",
5110
+ gap: "10px",
5111
+ padding: "12px 14px",
5112
+ borderRadius: "10px",
5113
+ border: "1px solid rgba(59, 130, 246, 0.45)",
5114
+ background: "rgba(59, 130, 246, 0.1)"
5115
+ },
5116
+ children: [
5117
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { style: { fontSize: "16px", lineHeight: "20px" }, children: "\u2705" }),
5118
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: { flex: 1, minWidth: 0 }, children: [
5119
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
5120
+ "div",
5121
+ {
5122
+ style: {
5123
+ fontSize: "13px",
5124
+ fontWeight: "700",
5125
+ marginBottom: "4px",
5126
+ color: "var(--dsw-alias-label-primary, inherit)"
5127
+ },
5128
+ children: t("dirPickerRestartBannerTitle", currentLang)
5129
+ }
5130
+ ),
5131
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
5132
+ "div",
5133
+ {
5134
+ style: {
5135
+ fontSize: "12px",
5136
+ lineHeight: 1.6,
5137
+ color: "var(--dsw-alias-label-secondary, inherit)"
5138
+ },
5139
+ children: t("dirPickerRestartBannerBody", currentLang, {
5140
+ path: status.directoryPicker.patchFilePath || "~/.dsh/profiles/web/cordis.patch.yml"
5141
+ })
5142
+ }
5143
+ )
5144
+ ] }),
5145
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
5146
+ "button",
5147
+ {
5148
+ type: "button",
5149
+ onClick: () => setDirPickerBannerDismissed(true),
5150
+ title: t("dirPickerBannerDismiss", currentLang),
5151
+ style: {
5152
+ flexShrink: 0,
5153
+ width: "24px",
5154
+ height: "24px",
5155
+ display: "inline-flex",
5156
+ alignItems: "center",
5157
+ justifyContent: "center",
5158
+ border: "none",
5159
+ borderRadius: "6px",
5160
+ cursor: "pointer",
5161
+ fontSize: "12px",
5162
+ background: "rgba(128, 128, 128, 0.15)",
5163
+ color: "var(--dsw-alias-label-secondary, inherit)"
5164
+ },
5165
+ children: "\u2715"
5166
+ }
5167
+ )
5168
+ ]
5169
+ }
5170
+ ) : null,
5171
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4308
5172
  "div",
4309
5173
  {
4310
5174
  style: {
@@ -4325,7 +5189,7 @@ function TailscaleMobileSection(props) {
4325
5189
  { id: "storage", label: t("tabStorage", currentLang) }
4326
5190
  ].map((item) => {
4327
5191
  const isActive = activeTab === item.id;
4328
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
5192
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4329
5193
  "button",
4330
5194
  {
4331
5195
  type: "button",
@@ -4352,8 +5216,8 @@ function TailscaleMobileSection(props) {
4352
5216
  })
4353
5217
  }
4354
5218
  ),
4355
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { display: activeTab === "access" ? "flex" : "none", flexDirection: "column", gap: "16px" }, children: [
4356
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
5219
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: { display: activeTab === "access" ? "flex" : "none", flexDirection: "column", gap: "16px" }, children: [
5220
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4357
5221
  NetworkCard,
4358
5222
  {
4359
5223
  status,
@@ -4363,7 +5227,28 @@ function TailscaleMobileSection(props) {
4363
5227
  lang: currentLang
4364
5228
  }
4365
5229
  ),
4366
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
5230
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
5231
+ ProxyConfigCard,
5232
+ {
5233
+ status,
5234
+ modeInput,
5235
+ setModeInput,
5236
+ proxyPortInput,
5237
+ setProxyPortInput,
5238
+ proxyHostInput,
5239
+ setProxyHostInput,
5240
+ trustedHostsInput,
5241
+ setTrustedHostsInput,
5242
+ saveProxyConfig,
5243
+ resetProxyConfigDefaults,
5244
+ toggleDirectoryPicker,
5245
+ isPatchingDirPicker,
5246
+ copyText,
5247
+ isSaving,
5248
+ lang: currentLang
5249
+ }
5250
+ ),
5251
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4367
5252
  QrPairingCard,
4368
5253
  {
4369
5254
  status,
@@ -4380,7 +5265,7 @@ function TailscaleMobileSection(props) {
4380
5265
  lang: currentLang
4381
5266
  }
4382
5267
  ),
4383
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
5268
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4384
5269
  SecretCard,
4385
5270
  {
4386
5271
  status,
@@ -4394,8 +5279,8 @@ function TailscaleMobileSection(props) {
4394
5279
  }
4395
5280
  )
4396
5281
  ] }),
4397
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { display: activeTab === "devices" ? "flex" : "none", flexDirection: "column", gap: "16px" }, children: [
4398
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
5282
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: { display: activeTab === "devices" ? "flex" : "none", flexDirection: "column", gap: "16px" }, children: [
5283
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4399
5284
  DeviceListCard,
4400
5285
  {
4401
5286
  status,
@@ -4404,7 +5289,7 @@ function TailscaleMobileSection(props) {
4404
5289
  lang: currentLang
4405
5290
  }
4406
5291
  ),
4407
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
5292
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4408
5293
  SecurityCard,
4409
5294
  {
4410
5295
  status,
@@ -4413,7 +5298,7 @@ function TailscaleMobileSection(props) {
4413
5298
  lang: currentLang
4414
5299
  }
4415
5300
  ),
4416
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
5301
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4417
5302
  ConfigCard,
4418
5303
  {
4419
5304
  maxVisitsInput,
@@ -4430,8 +5315,8 @@ function TailscaleMobileSection(props) {
4430
5315
  }
4431
5316
  )
4432
5317
  ] }),
4433
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { display: activeTab === "style" ? "flex" : "none", flexDirection: "column", gap: "16px" }, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(StylesCard, { lang: currentLang, showToast }) }),
4434
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { display: activeTab === "storage" ? "flex" : "none", flexDirection: "column", gap: "16px" }, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(StorageCard, { status, lang: currentLang, copyText }) })
5318
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { style: { display: activeTab === "style" ? "flex" : "none", flexDirection: "column", gap: "16px" }, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(StylesCard, { lang: currentLang, showToast }) }),
5319
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { style: { display: activeTab === "storage" ? "flex" : "none", flexDirection: "column", gap: "16px" }, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(StorageCard, { status, lang: currentLang, copyText }) })
4435
5320
  ]
4436
5321
  }
4437
5322
  );