@dsh-electron/dsh-plugin-multi-root-workspace 0.1.5 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.en.md +56 -1
- package/CHANGELOG.md +56 -1
- package/README.en.md +157 -92
- package/README.md +158 -93
- package/cordis.patch.yml +61 -0
- package/lib/client.js +2819 -115
- package/lib/client.js.map +1 -1
- package/lib/command-CESuwlVt.js +1306 -0
- package/lib/command.js +2 -2
- package/lib/{compat-82Y27Iom.js → compat-DF_yL1bB.js} +180 -4
- package/lib/compat.js +1 -1
- package/lib/contract-DZ4zczDT.js +238 -0
- package/lib/{fs-D_Sw3o5z.js → fs-9JIzGaf3.js} +55 -19
- package/lib/fs.js +2 -1
- package/lib/index.js +10 -8
- package/lib/{instructions-Cn1Jj5Xi.js → instructions-D19jRxN0.js} +109 -28
- package/lib/instructions.js +1 -1
- package/lib/lsp.js +43 -0
- package/lib/method-wrapper-CFY3p3j2.js +26 -0
- package/lib/process-fence.js +280 -0
- package/lib/providers.js +87 -0
- package/lib/{registry-DySMbjgW.js → registry-DCxC5Lay.js} +256 -35
- package/lib/registry.js +2 -2
- package/lib/{roots-DasWEBJk.js → roots-B-0l8mNG.js} +117 -20
- package/lib/sandbox-Kup4_ZIb.js +747 -0
- package/lib/sandbox.js +2 -1
- package/lib/scope.js +240 -25
- package/lib/session-directory-BLOstut1.js +9 -0
- package/lib/tool-context-CpgIFcCB.js +6 -0
- package/lib/types/client/RootSwitcher.d.ts +63 -0
- package/lib/types/client/WorkspaceFoldersAction.d.ts +41 -0
- package/lib/types/client/files-tree/FilesBody.module.css.d.ts +24 -0
- package/lib/types/client/files-tree/FilesTree.d.ts +47 -0
- package/lib/types/client/files-tree/adapter.d.ts +94 -0
- package/lib/types/client/files-tree/directory-node.d.ts +62 -0
- package/lib/types/client/files-tree/face.d.ts +107 -0
- package/lib/types/client/files-tree/locales.d.ts +10 -0
- package/lib/types/client/files-tree/primitives.d.ts +33 -0
- package/lib/types/client/files-tree/store.d.ts +81 -0
- package/lib/types/client/index.d.ts +13 -2
- package/lib/types/client/locales.d.ts +60 -0
- package/lib/types/client/open-in-app/OpenInAppAction.d.ts +27 -0
- package/lib/types/client/open-in-app/OpenTargetButton.d.ts +52 -0
- package/lib/types/client/open-in-app/OpenTargetButton.module.css.d.ts +29 -0
- package/lib/types/client/open-in-app/adapter.d.ts +46 -0
- package/lib/types/client/open-in-app/applications.d.ts +4 -0
- package/lib/types/client/open-in-app/controller.d.ts +53 -0
- package/lib/types/client/open-in-app/locales.d.ts +10 -0
- package/lib/types/client/open-in-app/open-failure-toast.d.ts +11 -0
- package/lib/types/client/open-in-app/open-path.d.ts +13 -0
- package/lib/types/client/panel-client.d.ts +2 -1
- package/lib/types/client/styles.d.ts +1 -1
- package/lib/types/command.d.ts +16 -3
- package/lib/types/compat/client-icons.d.ts +18 -0
- package/lib/types/compat/client-session.d.ts +2 -0
- package/lib/types/compat/dsh-version.d.ts +5 -3
- package/lib/types/compat/instruction-overlap.d.ts +7 -0
- package/lib/types/compat/open-in-app-routes.d.ts +38 -0
- package/lib/types/compat/process-entrypoints.d.ts +31 -0
- package/lib/types/compat/process-result.d.ts +14 -0
- package/lib/types/compat/pty-marker.d.ts +83 -0
- package/lib/types/compat/sandbox-confine.d.ts +2 -0
- package/lib/types/compat/session-directory.d.ts +4 -0
- package/lib/types/compat/storage-zod.d.ts +65 -0
- package/lib/types/compat/tool-context.d.ts +9 -0
- package/lib/types/compat/windows-deny-preflight.d.ts +21 -0
- package/lib/types/compat/windows-native.d.ts +68 -0
- package/lib/types/compat/windows-path-guard.d.ts +56 -0
- package/lib/types/compat/workspace-files.d.ts +7 -0
- package/lib/types/contract.d.ts +35 -4
- package/lib/types/dialects.d.ts +99 -3
- package/lib/types/fs.d.ts +32 -10
- package/lib/types/index.d.ts +5 -0
- package/lib/types/lsp.d.ts +4 -0
- package/lib/types/method-wrapper.d.ts +2 -0
- package/lib/types/process-fence.d.ts +54 -0
- package/lib/types/profile-isolation.d.ts +178 -0
- package/lib/types/providers.d.ts +70 -0
- package/lib/types/registry.d.ts +132 -31
- package/lib/types/roots.d.ts +100 -13
- package/lib/types/sandbox.d.ts +59 -12
- package/lib/types/scope.d.ts +203 -14
- package/lib/types/windows-broker.d.ts +17 -0
- package/lib/types/windows-deny.d.ts +1 -0
- package/lib/types/windows-profile.d.ts +10 -0
- package/lib/types/windows-runner-contract.d.ts +97 -0
- package/lib/types/windows-runner.d.ts +1 -0
- package/lib/types/workspace-files.d.ts +4 -0
- package/lib/types/workspace-routing.d.ts +11 -0
- package/lib/windows-runner-contract-CEfmZzu0.js +141 -0
- package/lib/windows-runner.js +1311 -0
- package/lib/workspace-files.js +203 -0
- package/lib/workspace-routing-YPxaQ4V3.js +26 -0
- package/package.json +116 -51
- package/scripts/windows-broker/install.ps1 +89 -0
- package/scripts/windows-broker/rights.cs +58 -0
- package/scripts/windows-broker/service.cs +323 -0
- package/scripts/windows-broker/verify-install.ps1 +80 -0
- package/lib/command-DyOWujPo.js +0 -690
- package/lib/sandbox-D8Fw95FM.js +0 -290
- package/lib/{registry-lease-win32-dy8TQMD9.js → registry-lease-win32-DTO6QpT9.js} +2 -2
package/lib/client.js
CHANGED
|
@@ -11,6 +11,15 @@ window.__ModuleLoader__.load({
|
|
|
11
11
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
12
12
|
var __getProtoOf = Object.getPrototypeOf;
|
|
13
13
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
14
|
+
var __exportAll = (all, no_symbols) => {
|
|
15
|
+
let target = {};
|
|
16
|
+
for (var name in all) __defProp(target, name, {
|
|
17
|
+
get: all[name],
|
|
18
|
+
enumerable: true
|
|
19
|
+
});
|
|
20
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
21
|
+
return target;
|
|
22
|
+
};
|
|
14
23
|
var __copyProps = (to, from, except, desc) => {
|
|
15
24
|
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
16
25
|
key = keys[i];
|
|
@@ -27,8 +36,11 @@ window.__ModuleLoader__.load({
|
|
|
27
36
|
}) : target, mod));
|
|
28
37
|
//#endregion
|
|
29
38
|
let react = require("react");
|
|
39
|
+
let react_dom = require("react-dom");
|
|
30
40
|
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
31
41
|
_deepseek_ai_dsh_client_ui_primitives = __toESM(_deepseek_ai_dsh_client_ui_primitives, 1);
|
|
42
|
+
require("@deepseek-ai/cordis");
|
|
43
|
+
let _deepseek_ai_dsh_client_store = require("@deepseek-ai/dsh-client-store");
|
|
32
44
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
33
45
|
//#region src/compat/client-session.ts
|
|
34
46
|
/**
|
|
@@ -44,6 +56,12 @@ window.__ModuleLoader__.load({
|
|
|
44
56
|
if (typeof current === "string" && current !== "") return current;
|
|
45
57
|
return mainViewSessionId(snapshot.byId);
|
|
46
58
|
}
|
|
59
|
+
/** Observe native catalog changes when this surface provides a subscribable store. */
|
|
60
|
+
function subscribeSessions(sessions, listener) {
|
|
61
|
+
if (sessions === null || typeof sessions !== "object") return () => {};
|
|
62
|
+
const list = sessions.list;
|
|
63
|
+
return typeof list?.subscribe === "function" ? list.subscribe(listener) : () => {};
|
|
64
|
+
}
|
|
47
65
|
/**
|
|
48
66
|
* The list snapshot, when the service actually exposes one.
|
|
49
67
|
*
|
|
@@ -1111,6 +1129,7 @@ window.__ModuleLoader__.load({
|
|
|
1111
1129
|
}
|
|
1112
1130
|
const anyString = /^[\s\S]{0,}$/;
|
|
1113
1131
|
const number$1 = /^-?\d+(?:\.\d+)?$/;
|
|
1132
|
+
const boolean$1 = /^(?:true|false)$/i;
|
|
1114
1133
|
const lowercase = /^[^A-Z]*$/;
|
|
1115
1134
|
const uppercase = /^[^a-z]*$/;
|
|
1116
1135
|
//#endregion
|
|
@@ -1898,6 +1917,24 @@ window.__ModuleLoader__.load({
|
|
|
1898
1917
|
$ZodCheckNumberFormat.init(inst, def);
|
|
1899
1918
|
$ZodNumber.init(inst, def);
|
|
1900
1919
|
});
|
|
1920
|
+
const $ZodBoolean = /*@__PURE__*/ $constructor("$ZodBoolean", (inst, def) => {
|
|
1921
|
+
$ZodType.init(inst, def);
|
|
1922
|
+
inst._zod.pattern = boolean$1;
|
|
1923
|
+
inst._zod.parse = (payload, _ctx) => {
|
|
1924
|
+
if (def.coerce) try {
|
|
1925
|
+
payload.value = Boolean(payload.value);
|
|
1926
|
+
} catch (_) {}
|
|
1927
|
+
const input = payload.value;
|
|
1928
|
+
if (typeof input === "boolean") return payload;
|
|
1929
|
+
payload.issues.push({
|
|
1930
|
+
expected: "boolean",
|
|
1931
|
+
code: "invalid_type",
|
|
1932
|
+
input,
|
|
1933
|
+
inst
|
|
1934
|
+
});
|
|
1935
|
+
return payload;
|
|
1936
|
+
};
|
|
1937
|
+
});
|
|
1901
1938
|
const $ZodUnknown = /*@__PURE__*/ $constructor("$ZodUnknown", (inst, def) => {
|
|
1902
1939
|
$ZodType.init(inst, def);
|
|
1903
1940
|
inst._zod.parse = (payload) => payload;
|
|
@@ -3340,6 +3377,13 @@ window.__ModuleLoader__.load({
|
|
|
3340
3377
|
});
|
|
3341
3378
|
}
|
|
3342
3379
|
// @__NO_SIDE_EFFECTS__
|
|
3380
|
+
function _boolean(Class, params) {
|
|
3381
|
+
return new Class({
|
|
3382
|
+
type: "boolean",
|
|
3383
|
+
...normalizeParams(params)
|
|
3384
|
+
});
|
|
3385
|
+
}
|
|
3386
|
+
// @__NO_SIDE_EFFECTS__
|
|
3343
3387
|
function _unknown(Class) {
|
|
3344
3388
|
return new Class({ type: "unknown" });
|
|
3345
3389
|
}
|
|
@@ -4118,6 +4162,9 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4118
4162
|
if (rest.length) json.allOf = [...json.allOf ?? [], ...rest.map((m) => ({ multipleOf: m }))];
|
|
4119
4163
|
}
|
|
4120
4164
|
};
|
|
4165
|
+
const booleanProcessor = (_schema, _ctx, json, _params) => {
|
|
4166
|
+
json.type = "boolean";
|
|
4167
|
+
};
|
|
4121
4168
|
const neverProcessor = (_schema, _ctx, json, _params) => {
|
|
4122
4169
|
json.not = {};
|
|
4123
4170
|
};
|
|
@@ -4865,6 +4912,14 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4865
4912
|
function int(params) {
|
|
4866
4913
|
return /* @__PURE__ */ _int(ZodNumberFormat, params);
|
|
4867
4914
|
}
|
|
4915
|
+
const ZodBoolean = /*@__PURE__*/ $constructor("ZodBoolean", (inst, def) => {
|
|
4916
|
+
$ZodBoolean.init(inst, def);
|
|
4917
|
+
ZodType.init(inst, def);
|
|
4918
|
+
inst._zod.processJSONSchema = (ctx, json, params) => booleanProcessor(inst, ctx, json, params);
|
|
4919
|
+
});
|
|
4920
|
+
function boolean(params) {
|
|
4921
|
+
return /* @__PURE__ */ _boolean(ZodBoolean, params);
|
|
4922
|
+
}
|
|
4868
4923
|
const ZodUnknown = /*@__PURE__*/ $constructor("ZodUnknown", (inst, def) => {
|
|
4869
4924
|
$ZodUnknown.init(inst, def);
|
|
4870
4925
|
ZodType.init(inst, def);
|
|
@@ -5208,8 +5263,9 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5208
5263
|
* - **One shape per endpoint.** {@link PanelResponseMap} is the single mapping
|
|
5209
5264
|
* from endpoint to response type, so the panel cannot silently decode one
|
|
5210
5265
|
* endpoint's answer as another's. `reveal` answers with
|
|
5211
|
-
* {@link RevealedView};
|
|
5212
|
-
* {@link RootsView}.
|
|
5266
|
+
* {@link RevealedView}; root-management endpoints answer with
|
|
5267
|
+
* {@link RootsView}.
|
|
5268
|
+
* The host builds those answers in `command.ts` and the
|
|
5213
5269
|
* panel parses them here — one definition, two consumers.
|
|
5214
5270
|
* - **Runtime validation at the boundary.** The payload arrives from a browser
|
|
5215
5271
|
* and the answer arrives from the host; neither is trusted to match the
|
|
@@ -5236,6 +5292,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5236
5292
|
id: string().optional(),
|
|
5237
5293
|
path: string().optional(),
|
|
5238
5294
|
alias: string().optional(),
|
|
5295
|
+
access: _enum([
|
|
5296
|
+
"inherit",
|
|
5297
|
+
"read-write",
|
|
5298
|
+
"read-only"
|
|
5299
|
+
]).optional(),
|
|
5239
5300
|
beforeEntry: object({
|
|
5240
5301
|
ordinal: number().int().positive(),
|
|
5241
5302
|
id: string(),
|
|
@@ -5261,12 +5322,33 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5261
5322
|
"redirected",
|
|
5262
5323
|
"invalid"
|
|
5263
5324
|
]),
|
|
5264
|
-
|
|
5325
|
+
access: _enum([
|
|
5326
|
+
"inherit",
|
|
5327
|
+
"read-write",
|
|
5328
|
+
"read-only"
|
|
5329
|
+
]).optional(),
|
|
5330
|
+
detail: string().optional(),
|
|
5331
|
+
effectiveAccess: _enum([
|
|
5332
|
+
"read-only",
|
|
5333
|
+
"read-write",
|
|
5334
|
+
"full-access",
|
|
5335
|
+
"unavailable"
|
|
5336
|
+
]).optional()
|
|
5265
5337
|
});
|
|
5266
5338
|
const rootsViewSchema = object({
|
|
5267
5339
|
primaryRoot: string(),
|
|
5268
5340
|
primaryName: string().optional(),
|
|
5341
|
+
effectiveCwd: string().optional(),
|
|
5342
|
+
mode: _enum([
|
|
5343
|
+
"read-only",
|
|
5344
|
+
"workspace-write",
|
|
5345
|
+
"danger-full-access"
|
|
5346
|
+
]).optional(),
|
|
5347
|
+
cappedTools: array(string()).optional(),
|
|
5348
|
+
toolAccessInvalid: boolean().optional(),
|
|
5269
5349
|
roots: array(rootViewSchema),
|
|
5350
|
+
limit: number().int().nonnegative().optional(),
|
|
5351
|
+
count: number().int().nonnegative().optional(),
|
|
5270
5352
|
unavailable: string().optional()
|
|
5271
5353
|
});
|
|
5272
5354
|
const revealedViewSchema = object({ revealed: string() });
|
|
@@ -5292,7 +5374,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5292
5374
|
value: {
|
|
5293
5375
|
primaryRoot: view.primaryRoot,
|
|
5294
5376
|
...view.primaryName === void 0 ? {} : { primaryName: view.primaryName },
|
|
5377
|
+
...view.effectiveCwd === void 0 ? {} : { effectiveCwd: view.effectiveCwd },
|
|
5378
|
+
...view.mode === void 0 ? {} : { mode: view.mode },
|
|
5379
|
+
...view.cappedTools === void 0 ? {} : { cappedTools: view.cappedTools },
|
|
5380
|
+
...view.toolAccessInvalid === void 0 ? {} : { toolAccessInvalid: view.toolAccessInvalid },
|
|
5295
5381
|
roots: view.roots.map(narrowRootView),
|
|
5382
|
+
limit: view.limit ?? Number.MAX_SAFE_INTEGER,
|
|
5383
|
+
count: view.count ?? view.roots.length,
|
|
5296
5384
|
...view.unavailable === void 0 ? {} : { unavailable: view.unavailable }
|
|
5297
5385
|
}
|
|
5298
5386
|
};
|
|
@@ -5334,6 +5422,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5334
5422
|
path: value.path,
|
|
5335
5423
|
addedAt: value.addedAt,
|
|
5336
5424
|
state: value.state,
|
|
5425
|
+
access: value.access ?? "inherit",
|
|
5426
|
+
...value.effectiveAccess === void 0 ? {} : { effectiveAccess: value.effectiveAccess },
|
|
5337
5427
|
...value.alias === void 0 ? {} : { alias: value.alias },
|
|
5338
5428
|
...value.detail === void 0 ? {} : { detail: value.detail }
|
|
5339
5429
|
};
|
|
@@ -5357,18 +5447,33 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5357
5447
|
* @module @dsh-electron/dsh-plugin-multi-root-workspace/client/locales
|
|
5358
5448
|
*/
|
|
5359
5449
|
/** The locale namespace this plugin owns. */
|
|
5360
|
-
const NS = "multiRootWorkspace";
|
|
5450
|
+
const NS$2 = "multiRootWorkspace";
|
|
5361
5451
|
/** Chinese copy (the canonical shape). */
|
|
5362
5452
|
const zh = {
|
|
5453
|
+
"panel.mode": "当前会话权限:",
|
|
5454
|
+
"panel.effectiveAccess": "当前有效:",
|
|
5455
|
+
"panel.toolCaps": "工具 {tools} 对附属根始终只读。",
|
|
5456
|
+
"panel.invalidCap": "工具权限配置无效,附属根写入全部被拒绝。",
|
|
5457
|
+
"mode.read-only": "只读",
|
|
5458
|
+
"mode.workspace-write": "读写",
|
|
5459
|
+
"mode.danger-full-access": "完全权限(Full-access)",
|
|
5460
|
+
"effective.read-only": "只读",
|
|
5461
|
+
"effective.read-write": "读写",
|
|
5462
|
+
"effective.full-access": "完全权限(Full-access)",
|
|
5463
|
+
"effective.unavailable": "不可用",
|
|
5363
5464
|
"action.label": "工作区目录",
|
|
5364
5465
|
"action.title": "管理本会话的附属工作目录",
|
|
5466
|
+
"switcher.manage": "管理工作区目录…",
|
|
5365
5467
|
"panel.title": "工作区目录",
|
|
5366
|
-
"panel.subtitle": "
|
|
5367
|
-
"panel.primary": "
|
|
5368
|
-
"panel.primaryNote": "
|
|
5468
|
+
"panel.subtitle": "主目录与「跟随会话」的附加目录遵循当前沙箱模式;目录也可固定为只读或读写。",
|
|
5469
|
+
"panel.primary": "主根",
|
|
5470
|
+
"panel.primaryNote": "会话原始项目,不可移除",
|
|
5471
|
+
"panel.cwd": "当前执行目录",
|
|
5472
|
+
"panel.cwdNote": "切换目录不会增加授权",
|
|
5369
5473
|
"panel.additional": "附属工作目录",
|
|
5474
|
+
"panel.limitReached": "已达到附加目录数量上限,请先移除一个再添加。",
|
|
5370
5475
|
"panel.empty": "还没有附属工作目录。",
|
|
5371
|
-
"panel.emptyHint": "添加后,Agent
|
|
5476
|
+
"panel.emptyHint": "添加后,Agent 可在同一会话中读写该目录,受限 shell 和终端同步生效。",
|
|
5372
5477
|
"panel.noSession": "当前没有活动会话",
|
|
5373
5478
|
"panel.loading": "正在读取…",
|
|
5374
5479
|
"panel.retry": "重试",
|
|
@@ -5386,9 +5491,20 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5386
5491
|
"panel.copyPath": "复制路径",
|
|
5387
5492
|
"panel.copied": "已复制",
|
|
5388
5493
|
"panel.reveal": "在文件管理器中显示",
|
|
5494
|
+
"panel.reRegister": "重新登记(保留原访问级别)",
|
|
5389
5495
|
"panel.moveUp": "上移",
|
|
5390
5496
|
"panel.moveDown": "下移",
|
|
5391
|
-
"
|
|
5497
|
+
"access.inherit": "跟随会话",
|
|
5498
|
+
"access.readWrite": "固定读写",
|
|
5499
|
+
"access.readOnly": "只读",
|
|
5500
|
+
"panel.accessLabel": "访问权限",
|
|
5501
|
+
"panel.accessInherit": "设为跟随会话",
|
|
5502
|
+
"panel.accessReadOnly": "设为只读",
|
|
5503
|
+
"panel.accessReadWrite": "设为固定读写",
|
|
5504
|
+
"panel.accessUpgradeConfirm": "将该目录的权限改为「{access}」?在某些沙箱模式下,这将允许 Agent 修改其中的文件。",
|
|
5505
|
+
"panel.accessUpgradeTitle": "扩大访问权限",
|
|
5506
|
+
"panel.accessUpgradeDo": "修改",
|
|
5507
|
+
"state.available": "已授予",
|
|
5392
5508
|
"state.missing": "目录不存在,暂不授予",
|
|
5393
5509
|
"state.redirected": "登记目录已被替换(现在指向别处),暂不授予",
|
|
5394
5510
|
"state.invalid": "登记无效",
|
|
@@ -5404,23 +5520,42 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5404
5520
|
"error.invalid-ref": "请求缺少必要的标识。",
|
|
5405
5521
|
"error.storage-unavailable": "根目录登记的存储不可用,请检查 $DSH_HOME/storages 下的文件。",
|
|
5406
5522
|
"error.registry-contended": "根目录登记正由另一个 DSH 进程占用;本进程不会授予附加根。关闭另一个进程后点重试。",
|
|
5523
|
+
"error.root-limit-reached": "已达到附加目录数量上限,请先移除一个再添加。",
|
|
5524
|
+
"error.config-invalid": "根目录登记的配置无效;本进程不会授予附加根,请修正配置后重启。",
|
|
5525
|
+
"error.root-unavailable": "该目录当前不可用,只能设为只读;请先恢复目录再调整权限。",
|
|
5407
5526
|
"error.reveal-unavailable": "无法打开系统的文件管理器;登记项不受影响,路径仍可复制。",
|
|
5408
5527
|
"error.unavailable": "无法连接到 dsh 主进程。",
|
|
5409
5528
|
"error.copy-failed": "无法写入剪贴板,请检查浏览器的剪贴板权限。",
|
|
5410
5529
|
"error.session-not-found": "找不到该会话;请先打开一个工作区会话。",
|
|
5530
|
+
"error.unsupported-path": "该路径是网络或设备路径,Windows ACL 隔离无法覆盖它。",
|
|
5411
5531
|
"error.fallback": "操作失败。"
|
|
5412
5532
|
};
|
|
5413
5533
|
/** English copy, pinned to the Chinese key set. */
|
|
5414
5534
|
const en = {
|
|
5535
|
+
"panel.mode": "Current session access:",
|
|
5536
|
+
"panel.effectiveAccess": "Effective:",
|
|
5537
|
+
"panel.toolCaps": "The tools {tools} always have read-only access to additional roots.",
|
|
5538
|
+
"panel.invalidCap": "Invalid tool access configuration refuses all additional-root writes.",
|
|
5539
|
+
"mode.read-only": "Read-only",
|
|
5540
|
+
"mode.workspace-write": "Read-write",
|
|
5541
|
+
"mode.danger-full-access": "Full-access",
|
|
5542
|
+
"effective.read-only": "Read-only",
|
|
5543
|
+
"effective.read-write": "Read-write",
|
|
5544
|
+
"effective.full-access": "Full-access",
|
|
5545
|
+
"effective.unavailable": "Unavailable",
|
|
5415
5546
|
"action.label": "Folders",
|
|
5416
5547
|
"action.title": "Manage this session's secondary working directories",
|
|
5548
|
+
"switcher.manage": "Manage workspace folders…",
|
|
5417
5549
|
"panel.title": "Workspace folders",
|
|
5418
|
-
"panel.subtitle": "The primary and
|
|
5419
|
-
"panel.primary": "Primary
|
|
5420
|
-
"panel.primaryNote": "
|
|
5550
|
+
"panel.subtitle": "The primary root and inherit folders follow the current sandbox mode; a folder can also be pinned read-only or read-write.",
|
|
5551
|
+
"panel.primary": "Primary root",
|
|
5552
|
+
"panel.primaryNote": "The session's original project; it cannot be removed",
|
|
5553
|
+
"panel.cwd": "Current working directory",
|
|
5554
|
+
"panel.cwdNote": "Changing directory does not grant access",
|
|
5421
5555
|
"panel.additional": "Secondary working directories",
|
|
5556
|
+
"panel.limitReached": "The additional-folder limit is reached; remove one before adding another.",
|
|
5422
5557
|
"panel.empty": "No secondary working directories yet.",
|
|
5423
|
-
"panel.emptyHint": "Once added, the agent can use the folder in this session
|
|
5558
|
+
"panel.emptyHint": "Once added, the agent can use the folder in this session, including confined shells and terminals.",
|
|
5424
5559
|
"panel.noSession": "No active session",
|
|
5425
5560
|
"panel.loading": "Loading…",
|
|
5426
5561
|
"panel.retry": "Retry",
|
|
@@ -5438,9 +5573,20 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5438
5573
|
"panel.copyPath": "Copy path",
|
|
5439
5574
|
"panel.copied": "Copied",
|
|
5440
5575
|
"panel.reveal": "Reveal in file manager",
|
|
5576
|
+
"panel.reRegister": "Re-register (keeps its access level)",
|
|
5441
5577
|
"panel.moveUp": "Move up",
|
|
5442
5578
|
"panel.moveDown": "Move down",
|
|
5443
|
-
"
|
|
5579
|
+
"access.inherit": "Inherit",
|
|
5580
|
+
"access.readWrite": "Read-write",
|
|
5581
|
+
"access.readOnly": "Read-only",
|
|
5582
|
+
"panel.accessLabel": "Access",
|
|
5583
|
+
"panel.accessInherit": "Make inherit",
|
|
5584
|
+
"panel.accessReadOnly": "Make read-only",
|
|
5585
|
+
"panel.accessReadWrite": "Make read-write",
|
|
5586
|
+
"panel.accessUpgradeConfirm": "Change this folder's access to \"{access}\"? Under some sandbox modes this lets the agent modify files in it.",
|
|
5587
|
+
"panel.accessUpgradeTitle": "Widen access",
|
|
5588
|
+
"panel.accessUpgradeDo": "Change",
|
|
5589
|
+
"state.available": "Granted",
|
|
5444
5590
|
"state.missing": "Directory is absent; not granted",
|
|
5445
5591
|
"state.redirected": "Registration was replaced (it now points elsewhere); not granted",
|
|
5446
5592
|
"state.invalid": "Registration is unusable",
|
|
@@ -5456,10 +5602,14 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5456
5602
|
"error.invalid-ref": "The request is missing a required identity.",
|
|
5457
5603
|
"error.storage-unavailable": "The root registry store is unavailable; check the files under $DSH_HOME/storages.",
|
|
5458
5604
|
"error.registry-contended": "The root registry is owned by another DSH process; this process grants no additional roots. Close the other process, then retry.",
|
|
5605
|
+
"error.root-limit-reached": "The additional-root limit is reached; remove one before adding another.",
|
|
5606
|
+
"error.config-invalid": "The root registry configuration is invalid; this process grants no additional roots. Fix the configuration and restart.",
|
|
5607
|
+
"error.root-unavailable": "That folder is not available right now and cannot be made writable; restore the directory first.",
|
|
5459
5608
|
"error.reveal-unavailable": "Could not open the system file manager; the registration is untouched, and the path can still be copied.",
|
|
5460
5609
|
"error.unavailable": "Could not reach the dsh host process.",
|
|
5461
5610
|
"error.copy-failed": "Could not write to the clipboard; check the browser's clipboard permission.",
|
|
5462
5611
|
"error.session-not-found": "That session is not active; open a workspace session first.",
|
|
5612
|
+
"error.unsupported-path": "That is a network or device path, which Windows ACL confinement cannot cover.",
|
|
5463
5613
|
"error.fallback": "The operation failed."
|
|
5464
5614
|
};
|
|
5465
5615
|
//#endregion
|
|
@@ -5493,10 +5643,10 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5493
5643
|
* @returns the client, with every transport failure normalized to a `PanelError`.
|
|
5494
5644
|
*/
|
|
5495
5645
|
function createPanelClient(connection) {
|
|
5496
|
-
return { async call(endpoint, payload) {
|
|
5646
|
+
return { async call(endpoint, payload, signal) {
|
|
5497
5647
|
let result;
|
|
5498
5648
|
try {
|
|
5499
|
-
result = await connection.rpc.call(PANEL_CHANNEL, endpoint, payload);
|
|
5649
|
+
result = await connection.rpc.call(PANEL_CHANNEL, endpoint, payload, signal);
|
|
5500
5650
|
} catch (error) {
|
|
5501
5651
|
throw new PanelError("unavailable", error instanceof Error ? error.message : String(error));
|
|
5502
5652
|
}
|
|
@@ -5604,6 +5754,367 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5604
5754
|
justify-content: center;
|
|
5605
5755
|
}
|
|
5606
5756
|
|
|
5757
|
+
/* Icon-only launcher inside the upstream Files tab header. Upstream's
|
|
5758
|
+
FilesBody renders .header as one flat flex row — PathLabel, then the
|
|
5759
|
+
reload button, then the files.actions slot occupants — and each of those
|
|
5760
|
+
children carries a stable data-files-* attribute on the flex item itself
|
|
5761
|
+
(data-files-root on the pane, data-files-path, data-files-reload).
|
|
5762
|
+
A registration order sorts only INSIDE the actions slot and can never
|
|
5763
|
+
overtake the reload button, so the launcher is moved with the flex order
|
|
5764
|
+
property instead: this wrapper is the slot's root element and therefore a
|
|
5765
|
+
direct flex item of .header, and order: -1 puts it ahead of every
|
|
5766
|
+
order-0 sibling while the :has() rule below keeps the path first. */
|
|
5767
|
+
.mrfw-fileLauncher {
|
|
5768
|
+
order: -1;
|
|
5769
|
+
flex: none;
|
|
5770
|
+
display: flex;
|
|
5771
|
+
align-items: center;
|
|
5772
|
+
}
|
|
5773
|
+
|
|
5774
|
+
/* Pin the upstream path label ahead of the launcher — but only while this
|
|
5775
|
+
plugin is in the row: the :has() guard keeps a rule that names an
|
|
5776
|
+
upstream element inert whenever the launcher is absent. The
|
|
5777
|
+
:not([data-files-entry]) qualifier is load-bearing, not decorative:
|
|
5778
|
+
upstream puts data-files-path on EVERY tree row too (the row's own
|
|
5779
|
+
absolute path), so an unqualified selector would match the whole tree,
|
|
5780
|
+
not just the header's PathLabel. Both selectors depend on upstream's DOM
|
|
5781
|
+
shape, which is not part of the slot contract; if it changes, the rules
|
|
5782
|
+
simply stop matching and the launcher falls back to its in-slot position —
|
|
5783
|
+
a silent degradation, never a broken header. */
|
|
5784
|
+
[data-files-root]:has(.mrfw-fileLauncher) [data-files-path]:not([data-files-entry]) {
|
|
5785
|
+
order: -2;
|
|
5786
|
+
}
|
|
5787
|
+
|
|
5788
|
+
/* The root switcher is the launcher's grown-up form on runtimes whose
|
|
5789
|
+
files.actions owner carries absolutePath. It takes the path label's flex
|
|
5790
|
+
position and stretch: order: -1 ahead of every order-0 sibling, and a
|
|
5791
|
+
growing min-width:0 item so both the trigger label and the PathLabel
|
|
5792
|
+
inside it can ellipsize instead of pushing the header controls out. */
|
|
5793
|
+
.mrfw-rootSwitcher {
|
|
5794
|
+
order: -1;
|
|
5795
|
+
flex: 1 1 auto;
|
|
5796
|
+
min-width: 0;
|
|
5797
|
+
display: flex;
|
|
5798
|
+
align-items: center;
|
|
5799
|
+
/* Same 4px the upstream .header puts between its own flex items, so the
|
|
5800
|
+
takeover controls (reload, open-in-app fork) sit at the same offsets the
|
|
5801
|
+
hidden native ones did — no drift between primary and additional state. */
|
|
5802
|
+
gap: 4px;
|
|
5803
|
+
}
|
|
5804
|
+
|
|
5805
|
+
/* While the switcher exists it hides the upstream path label — the
|
|
5806
|
+
:has() guard again keeps the upstream-naming rule inert unless this
|
|
5807
|
+
plugin is actually in the row. The switcher draws its own PathLabel of
|
|
5808
|
+
the selected root next to the trigger (className mrfw-switcherPath, no
|
|
5809
|
+
data-files-* attributes), so the header keeps a path in the same spot.
|
|
5810
|
+
:not([data-files-entry]) is what confines this rule to the header:
|
|
5811
|
+
every tree row carries data-files-path with its own path as the value,
|
|
5812
|
+
and only the PathLabel carries it bare. Without the qualifier this
|
|
5813
|
+
display:none would blank the entire native tree. */
|
|
5814
|
+
[data-files-root]:has(.mrfw-rootSwitcher) [data-files-path]:not([data-files-entry]) {
|
|
5815
|
+
display: none;
|
|
5816
|
+
}
|
|
5817
|
+
|
|
5818
|
+
/* While the switcher names an additional root, the native tree body and its
|
|
5819
|
+
reload hide: the reload refreshes the very tree that is no longer
|
|
5820
|
+
visible, and the plugin's forked tree is portaled in as the body's sibling.
|
|
5821
|
+
The header still offers a reload — the switcher's own .mrfw-switcherReload
|
|
5822
|
+
calls the forked face's refresh. The native tree keeps watching in the
|
|
5823
|
+
background, so switching back to the primary root restores it instantly. */
|
|
5824
|
+
[data-files-root]:has(.mrfw-rootSwitcher[data-root-kind="additional"]) [data-files-body],
|
|
5825
|
+
[data-files-root]:has(.mrfw-rootSwitcher[data-root-kind="additional"]) [data-files-reload] {
|
|
5826
|
+
display: none;
|
|
5827
|
+
}
|
|
5828
|
+
|
|
5829
|
+
/* While the switcher names an additional root, the native open-in-app split
|
|
5830
|
+
button hides too: its slot props carry the pane's primary root, so it
|
|
5831
|
+
would always open the primary root no matter what the switcher selected.
|
|
5832
|
+
The header gets the plugin's own fork of that same control instead (the
|
|
5833
|
+
open-in-app directory), wired to the selected root.
|
|
5834
|
+
|
|
5835
|
+
The slot's render site is wrapped in the renderer's own anchor —
|
|
5836
|
+
<div data-slot="sidebar.right.tab.files.actions" style="display:contents">
|
|
5837
|
+
— so the header's flex items inside it are the occupants themselves, and
|
|
5838
|
+
the native item is the ui-primitives Menu anchor span wrapping the marked
|
|
5839
|
+
.split. The first selector hides that occupant item through the data-slot
|
|
5840
|
+
anchor: hiding only the inner control would leave the empty anchor span
|
|
5841
|
+
as a zero-width flex item whose header gap pushes the fork 4px left of
|
|
5842
|
+
the native position. The wrapper itself can never be the target — its
|
|
5843
|
+
inline display:contents beats any stylesheet rule, and hiding it would
|
|
5844
|
+
take the switcher with it. The second selector is the fallback for an
|
|
5845
|
+
upstream that moves the attribute off the item. The fork survives both:
|
|
5846
|
+
the only data-open-target inside .mrfw-rootSwitcher is its own
|
|
5847
|
+
.mrfw-openSplit, which :not() excludes. */
|
|
5848
|
+
[data-files-root]:has(.mrfw-rootSwitcher[data-root-kind="additional"]) [data-slot="sidebar.right.tab.files.actions"] > *:has([data-open-target="directory"]:not(.mrfw-openSplit)),
|
|
5849
|
+
[data-files-root]:has(.mrfw-rootSwitcher[data-root-kind="additional"]) [data-open-target="directory"]:not(.mrfw-openSplit) {
|
|
5850
|
+
display: none;
|
|
5851
|
+
}
|
|
5852
|
+
|
|
5853
|
+
/* The trigger borrows the path label's look: quiet text with a folder
|
|
5854
|
+
glyph, the hover of the neighboring header buttons, and the overflow
|
|
5855
|
+
behavior PathLabel itself uses in a narrow header. It may shrink but
|
|
5856
|
+
never grows — the PathLabel after it owns the free header space. */
|
|
5857
|
+
.mrfw-rootSwitchBtn {
|
|
5858
|
+
flex: 0 1 auto;
|
|
5859
|
+
min-width: 0;
|
|
5860
|
+
display: inline-flex;
|
|
5861
|
+
align-items: center;
|
|
5862
|
+
gap: 4px;
|
|
5863
|
+
height: 28px;
|
|
5864
|
+
margin: 0;
|
|
5865
|
+
padding: 0 4px;
|
|
5866
|
+
border: none;
|
|
5867
|
+
border-radius: 8px;
|
|
5868
|
+
background: transparent;
|
|
5869
|
+
color: var(--dsw-alias-label-primary);
|
|
5870
|
+
font-family: inherit;
|
|
5871
|
+
font-size: inherit;
|
|
5872
|
+
line-height: 20px;
|
|
5873
|
+
text-align: left;
|
|
5874
|
+
cursor: pointer;
|
|
5875
|
+
transition: background-color var(--ds-transition-duration-fast) var(--ds-ease-in-out);
|
|
5876
|
+
}
|
|
5877
|
+
|
|
5878
|
+
.mrfw-rootSwitchBtn:hover {
|
|
5879
|
+
background: var(--dsw-alias-interactive-bg-hover);
|
|
5880
|
+
}
|
|
5881
|
+
|
|
5882
|
+
.mrfw-rootSwitchBtn .mrfw-triggerIcon {
|
|
5883
|
+
color: var(--dsw-alias-label-secondary);
|
|
5884
|
+
}
|
|
5885
|
+
|
|
5886
|
+
.mrfw-rootSwitchLabel {
|
|
5887
|
+
min-width: 0;
|
|
5888
|
+
overflow: hidden;
|
|
5889
|
+
text-overflow: ellipsis;
|
|
5890
|
+
white-space: nowrap;
|
|
5891
|
+
}
|
|
5892
|
+
|
|
5893
|
+
.mrfw-rootSwitchChevron {
|
|
5894
|
+
margin-left: auto;
|
|
5895
|
+
color: var(--dsw-alias-label-tertiary);
|
|
5896
|
+
}
|
|
5897
|
+
|
|
5898
|
+
/* The selected root's full path sits between the trigger and the reload —
|
|
5899
|
+
the very PathLabel upstream draws there, so it brings its own grow,
|
|
5900
|
+
left-clip and hover-title behavior; only the gutters are ours. The right
|
|
5901
|
+
margin plus the container gap reproduces upstream's .path margin-right
|
|
5902
|
+
12px + header gap 4px; the left gutter comes from the same container gap. */
|
|
5903
|
+
.mrfw-switcherPath {
|
|
5904
|
+
margin: 0 12px 0 0;
|
|
5905
|
+
}
|
|
5906
|
+
|
|
5907
|
+
/* The takeover's own reload, standing exactly where the hidden native one
|
|
5908
|
+
did: the upstream .tool recipe verbatim (28px hit area, 15px glyph,
|
|
5909
|
+
secondary voice, hover fills). It calls the forked face's refresh — an
|
|
5910
|
+
in-place re-list, not a remount. */
|
|
5911
|
+
.mrfw-switcherReload {
|
|
5912
|
+
flex: none;
|
|
5913
|
+
display: inline-flex;
|
|
5914
|
+
align-items: center;
|
|
5915
|
+
justify-content: center;
|
|
5916
|
+
width: 28px;
|
|
5917
|
+
height: 28px;
|
|
5918
|
+
padding: 6px;
|
|
5919
|
+
border: none;
|
|
5920
|
+
border-radius: var(--dsw-radius-sm);
|
|
5921
|
+
background: transparent;
|
|
5922
|
+
color: var(--dsw-alias-label-secondary);
|
|
5923
|
+
line-height: 1;
|
|
5924
|
+
cursor: pointer;
|
|
5925
|
+
}
|
|
5926
|
+
|
|
5927
|
+
.mrfw-switcherReload:hover {
|
|
5928
|
+
color: var(--dsw-alias-label-primary);
|
|
5929
|
+
background: var(--dsw-alias-interactive-bg-hover);
|
|
5930
|
+
}
|
|
5931
|
+
|
|
5932
|
+
/* ── files-tree fork (verbatim rules, renamed classes) ─────────────────────
|
|
5933
|
+
Copied from upstream's FilesBody.module.css (ui-sidebar-files @
|
|
5934
|
+
0.2.1-alpha.2) with local names mapped to mrfw-ft* so the rules cannot
|
|
5935
|
+
collide with host DOM — and so the fork's own body (which must NOT carry
|
|
5936
|
+
data-files-body) stays visible while the rules above hide the native one.
|
|
5937
|
+
The header-only rules (root/header/path/tool/status/statusLine/titleIcon)
|
|
5938
|
+
belong to the upstream shell this fork drops and are not mapped. Fork sync
|
|
5939
|
+
re-diffs the upstream file and re-applies the rename — see
|
|
5940
|
+
src/client/files-tree/FORK.md. */
|
|
5941
|
+
|
|
5942
|
+
/* The pane's own .root already supplies the flex column, color, and font;
|
|
5943
|
+
the fork's body takes the same stretch item the hidden native body held,
|
|
5944
|
+
including the workspace list's 2px scrollbar offset and stable gutter. */
|
|
5945
|
+
.mrfw-ftBody {
|
|
5946
|
+
flex: 1 1 auto;
|
|
5947
|
+
min-height: 0;
|
|
5948
|
+
margin-right: 2px;
|
|
5949
|
+
padding: 8px 0 8px 8px;
|
|
5950
|
+
overflow: auto;
|
|
5951
|
+
scrollbar-gutter: stable;
|
|
5952
|
+
}
|
|
5953
|
+
|
|
5954
|
+
.mrfw-ftBody::-webkit-scrollbar-track {
|
|
5955
|
+
margin: 2px;
|
|
5956
|
+
}
|
|
5957
|
+
|
|
5958
|
+
.mrfw-ftLevel {
|
|
5959
|
+
margin: 0;
|
|
5960
|
+
padding: 0;
|
|
5961
|
+
list-style: none;
|
|
5962
|
+
}
|
|
5963
|
+
|
|
5964
|
+
.mrfw-ftLevel .mrfw-ftLevel {
|
|
5965
|
+
padding-left: 18px;
|
|
5966
|
+
}
|
|
5967
|
+
|
|
5968
|
+
.mrfw-ftItem {
|
|
5969
|
+
margin: 0;
|
|
5970
|
+
padding: 0;
|
|
5971
|
+
}
|
|
5972
|
+
|
|
5973
|
+
/* Rows abut, so the hover fill is the whole row; the tree's air is this
|
|
5974
|
+
inset, not a gap between rows. */
|
|
5975
|
+
.mrfw-ftRow {
|
|
5976
|
+
display: flex;
|
|
5977
|
+
gap: 6px;
|
|
5978
|
+
align-items: center;
|
|
5979
|
+
width: 100%;
|
|
5980
|
+
min-width: 0;
|
|
5981
|
+
padding: 5px 10px;
|
|
5982
|
+
color: inherit;
|
|
5983
|
+
font: inherit;
|
|
5984
|
+
text-align: left;
|
|
5985
|
+
background: transparent;
|
|
5986
|
+
border: 0;
|
|
5987
|
+
border-radius: var(--dsw-radius-md);
|
|
5988
|
+
cursor: pointer;
|
|
5989
|
+
}
|
|
5990
|
+
|
|
5991
|
+
.mrfw-ftRow:hover {
|
|
5992
|
+
background: var(--dsw-alias-interactive-bg-hover);
|
|
5993
|
+
}
|
|
5994
|
+
|
|
5995
|
+
/* The folder glyph rides the tertiary ink, as the workspace tree's does. */
|
|
5996
|
+
.mrfw-ftIcon {
|
|
5997
|
+
flex: 0 0 auto;
|
|
5998
|
+
color: var(--dsw-alias-label-tertiary);
|
|
5999
|
+
}
|
|
6000
|
+
|
|
6001
|
+
/* The type's coloured sheet at the folder glyph's 16px; its colour is its own. */
|
|
6002
|
+
.mrfw-ftFileIcon {
|
|
6003
|
+
flex: 0 0 auto;
|
|
6004
|
+
}
|
|
6005
|
+
|
|
6006
|
+
.mrfw-ftName {
|
|
6007
|
+
min-width: 0;
|
|
6008
|
+
overflow: hidden;
|
|
6009
|
+
white-space: nowrap;
|
|
6010
|
+
text-overflow: ellipsis;
|
|
6011
|
+
}
|
|
6012
|
+
|
|
6013
|
+
/* Neither a file nor a directory: shown so the directory is reported whole,
|
|
6014
|
+
greyed so nobody tries to open it. */
|
|
6015
|
+
.mrfw-ftOther {
|
|
6016
|
+
color: var(--dsw-alias-label-tertiary);
|
|
6017
|
+
cursor: default;
|
|
6018
|
+
}
|
|
6019
|
+
|
|
6020
|
+
.mrfw-ftOther:hover {
|
|
6021
|
+
background: transparent;
|
|
6022
|
+
}
|
|
6023
|
+
|
|
6024
|
+
.mrfw-ftNote {
|
|
6025
|
+
margin: 0;
|
|
6026
|
+
padding: 3px 10px;
|
|
6027
|
+
color: var(--dsw-alias-label-tertiary);
|
|
6028
|
+
font-size: 12px;
|
|
6029
|
+
}
|
|
6030
|
+
|
|
6031
|
+
/* ── open-in-app fork (verbatim rules, renamed classes) ───────────────────
|
|
6032
|
+
Copied from upstream's OpenTargetButton.module.css
|
|
6033
|
+
(dsh-client-ui-open-in-app @ 0.2.1-alpha.2) with local names mapped to
|
|
6034
|
+
mrfw-open* so the rules cannot collide with host DOM. Fork sync re-diffs
|
|
6035
|
+
the upstream file and re-applies the rename — see
|
|
6036
|
+
src/client/open-in-app/FORK.md. */
|
|
6037
|
+
|
|
6038
|
+
/* Sized to sit in the document header's 24px control row beside its round
|
|
6039
|
+
icon buttons: a hairline pill with a 12px label and a narrow chevron half. */
|
|
6040
|
+
.mrfw-openMenuAnchor {
|
|
6041
|
+
display: inline-flex;
|
|
6042
|
+
flex: none;
|
|
6043
|
+
align-self: center;
|
|
6044
|
+
}
|
|
6045
|
+
|
|
6046
|
+
.mrfw-openSplit {
|
|
6047
|
+
display: inline-flex;
|
|
6048
|
+
align-items: stretch;
|
|
6049
|
+
box-sizing: border-box;
|
|
6050
|
+
height: 24px;
|
|
6051
|
+
overflow: hidden;
|
|
6052
|
+
border: 0.5px solid var(--dsw-alias-border-l4);
|
|
6053
|
+
border-radius: var(--dsw-radius-sm);
|
|
6054
|
+
font-family: var(--dsw-font-family);
|
|
6055
|
+
}
|
|
6056
|
+
|
|
6057
|
+
.mrfw-openMain,
|
|
6058
|
+
.mrfw-openChevron {
|
|
6059
|
+
display: inline-flex;
|
|
6060
|
+
align-items: center;
|
|
6061
|
+
justify-content: center;
|
|
6062
|
+
border: 0;
|
|
6063
|
+
background: none;
|
|
6064
|
+
color: var(--dsw-alias-label-primary);
|
|
6065
|
+
font-size: 11px;
|
|
6066
|
+
line-height: 16px;
|
|
6067
|
+
white-space: nowrap;
|
|
6068
|
+
cursor: pointer;
|
|
6069
|
+
}
|
|
6070
|
+
|
|
6071
|
+
.mrfw-openMain {
|
|
6072
|
+
gap: 4px;
|
|
6073
|
+
padding: 3px 5px;
|
|
6074
|
+
}
|
|
6075
|
+
|
|
6076
|
+
.mrfw-openChevron {
|
|
6077
|
+
padding: 3px 4px 3px 3px;
|
|
6078
|
+
border-left: 0.5px solid var(--dsw-alias-border-l4);
|
|
6079
|
+
color: var(--dsw-alias-label-secondary);
|
|
6080
|
+
}
|
|
6081
|
+
|
|
6082
|
+
.mrfw-openMain:hover:not(:disabled),
|
|
6083
|
+
.mrfw-openMain:focus-visible,
|
|
6084
|
+
.mrfw-openChevron:hover:not(:disabled),
|
|
6085
|
+
.mrfw-openChevron:focus-visible {
|
|
6086
|
+
background: var(--dsw-alias-interactive-bg-hover);
|
|
6087
|
+
}
|
|
6088
|
+
|
|
6089
|
+
/* Pending disables clicks without a restyle; only hover feedback pauses. */
|
|
6090
|
+
.mrfw-openMain:disabled,
|
|
6091
|
+
.mrfw-openChevron:disabled {
|
|
6092
|
+
cursor: default;
|
|
6093
|
+
}
|
|
6094
|
+
|
|
6095
|
+
.mrfw-openAppIcon {
|
|
6096
|
+
object-fit: contain;
|
|
6097
|
+
}
|
|
6098
|
+
|
|
6099
|
+
/* The unavailable preview emphasizes the same actions with a labeled button. */
|
|
6100
|
+
.mrfw-openSplit[data-size='large'] {
|
|
6101
|
+
height: 36px;
|
|
6102
|
+
border-radius: var(--dsw-radius-md);
|
|
6103
|
+
border-color: var(--dsw-alias-border-l2);
|
|
6104
|
+
}
|
|
6105
|
+
|
|
6106
|
+
.mrfw-openSplit[data-size='large'] .mrfw-openMain {
|
|
6107
|
+
gap: 6px;
|
|
6108
|
+
padding: 6px 14px;
|
|
6109
|
+
font-size: 14px;
|
|
6110
|
+
}
|
|
6111
|
+
|
|
6112
|
+
.mrfw-openSplit[data-size='large'] .mrfw-openChevron {
|
|
6113
|
+
padding: 6px 10px;
|
|
6114
|
+
}
|
|
6115
|
+
|
|
6116
|
+
.mrfw-openSkeleton { display: inline-block; flex: none; border-radius: var(--dsw-radius-xs); background: var(--dsw-alias-interactive-bg-hover); }
|
|
6117
|
+
|
|
5607
6118
|
.mrfw-overlay {
|
|
5608
6119
|
position: fixed;
|
|
5609
6120
|
inset: 0;
|
|
@@ -5748,16 +6259,27 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5748
6259
|
min-width: 0;
|
|
5749
6260
|
}
|
|
5750
6261
|
|
|
6262
|
+
/* The name line is a flex row: the truncating name text first, the access
|
|
6263
|
+
badge beside it never shrinking — baseline-aligned so the badge's smaller
|
|
6264
|
+
voice sits on the name's baseline rather than its middle. */
|
|
5751
6265
|
.mrfw-rootName {
|
|
6266
|
+
display: flex;
|
|
6267
|
+
align-items: baseline;
|
|
6268
|
+
gap: 6px;
|
|
6269
|
+
min-width: 0;
|
|
5752
6270
|
overflow: hidden;
|
|
5753
|
-
text-overflow: ellipsis;
|
|
5754
|
-
white-space: nowrap;
|
|
5755
6271
|
font-size: 14px;
|
|
5756
6272
|
line-height: 20px;
|
|
5757
6273
|
font-weight: 500;
|
|
5758
6274
|
color: var(--dsw-alias-label-primary);
|
|
5759
6275
|
}
|
|
5760
6276
|
|
|
6277
|
+
.mrfw-rootNameText {
|
|
6278
|
+
overflow: hidden;
|
|
6279
|
+
text-overflow: ellipsis;
|
|
6280
|
+
white-space: nowrap;
|
|
6281
|
+
}
|
|
6282
|
+
|
|
5761
6283
|
.mrfw-rootPath {
|
|
5762
6284
|
font-family: var(--ds-font-family-code);
|
|
5763
6285
|
font-size: 12px;
|
|
@@ -5766,6 +6288,35 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5766
6288
|
color: var(--dsw-alias-label-tertiary);
|
|
5767
6289
|
}
|
|
5768
6290
|
|
|
6291
|
+
/* The path doubles as the copy control: an unstyled button that keeps the
|
|
6292
|
+
text recipe and makes the whole string clickable — the whole region, not
|
|
6293
|
+
just the icon, copies on click. */
|
|
6294
|
+
.mrfw-pathCopy {
|
|
6295
|
+
display: block;
|
|
6296
|
+
width: 100%;
|
|
6297
|
+
padding: 0;
|
|
6298
|
+
border: 0;
|
|
6299
|
+
background: transparent;
|
|
6300
|
+
text-align: left;
|
|
6301
|
+
cursor: pointer;
|
|
6302
|
+
}
|
|
6303
|
+
|
|
6304
|
+
/* The trailing icon stays in flow but invisible until the path is hovered
|
|
6305
|
+
or focused: appearing never re-wraps the text, and the icon inherits the
|
|
6306
|
+
path's own tertiary ink. */
|
|
6307
|
+
.mrfw-pathCopyIcon {
|
|
6308
|
+
display: inline-flex;
|
|
6309
|
+
margin-left: 4px;
|
|
6310
|
+
vertical-align: -2px;
|
|
6311
|
+
opacity: 0;
|
|
6312
|
+
transition: opacity var(--ds-transition-duration-fast) var(--ds-ease-in-out);
|
|
6313
|
+
}
|
|
6314
|
+
|
|
6315
|
+
.mrfw-pathCopy:hover .mrfw-pathCopyIcon,
|
|
6316
|
+
.mrfw-pathCopy:focus-visible .mrfw-pathCopyIcon {
|
|
6317
|
+
opacity: 1;
|
|
6318
|
+
}
|
|
6319
|
+
|
|
5769
6320
|
.mrfw-note {
|
|
5770
6321
|
margin: 0;
|
|
5771
6322
|
font-size: 12px;
|
|
@@ -5811,8 +6362,19 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5811
6362
|
background: var(--dsw-alias-interactive-bg-active);
|
|
5812
6363
|
}
|
|
5813
6364
|
|
|
6365
|
+
/* The solid destructive action of the capsule family: the upgrade confirm's
|
|
6366
|
+
red button. state-error-primary is the error ink upstream also uses as a
|
|
6367
|
+
fill (StateDot); the hover mixes in the host's primary label token to
|
|
6368
|
+
follow its theme and contrast choices. Neither color is a guessed token:
|
|
6369
|
+
an undefined var() would fall back
|
|
6370
|
+
to transparent and white-out the whole button. */
|
|
6371
|
+
.mrfw-btnDanger {
|
|
6372
|
+
background: var(--dsw-alias-state-error-primary);
|
|
6373
|
+
color: var(--dsw-alias-label-primary-foreground);
|
|
6374
|
+
}
|
|
6375
|
+
|
|
5814
6376
|
.mrfw-btnDanger:hover:not(:disabled) {
|
|
5815
|
-
background: var(--dsw-alias-
|
|
6377
|
+
background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 85%, var(--dsw-alias-label-primary));
|
|
5816
6378
|
}
|
|
5817
6379
|
|
|
5818
6380
|
.mrfw-btnOutline {
|
|
@@ -5870,6 +6432,24 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5870
6432
|
flex: 1 1 auto;
|
|
5871
6433
|
}
|
|
5872
6434
|
|
|
6435
|
+
.mrfw-accessSelect {
|
|
6436
|
+
flex: 0 0 auto;
|
|
6437
|
+
}
|
|
6438
|
+
|
|
6439
|
+
/* The access dropdown's capsule trigger: the Menu anchor wrapper is the flex
|
|
6440
|
+
item, the button inside keeps the control row's 32px capsule recipe. */
|
|
6441
|
+
.mrfw-accessTrigger {
|
|
6442
|
+
padding: 0 12px;
|
|
6443
|
+
}
|
|
6444
|
+
|
|
6445
|
+
.mrfw-accessBadge,
|
|
6446
|
+
.mrfw-effectiveAccess {
|
|
6447
|
+
flex: none;
|
|
6448
|
+
font-size: 12px;
|
|
6449
|
+
line-height: 18px;
|
|
6450
|
+
color: var(--dsw-alias-label-tertiary);
|
|
6451
|
+
}
|
|
6452
|
+
|
|
5873
6453
|
.mrfw-aliasEditor {
|
|
5874
6454
|
display: flex;
|
|
5875
6455
|
flex-wrap: wrap;
|
|
@@ -5905,7 +6485,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5905
6485
|
* Named imports cannot spell both releases: each name is absent from the
|
|
5906
6486
|
* other release's declaration, so the compiler is told once, here.
|
|
5907
6487
|
*/
|
|
5908
|
-
const installed = _deepseek_ai_dsh_client_ui_primitives;
|
|
6488
|
+
const installed$1 = _deepseek_ai_dsh_client_ui_primitives;
|
|
5909
6489
|
/**
|
|
5910
6490
|
* Pick the first export name this release actually provides.
|
|
5911
6491
|
* @param module - the icon module to read.
|
|
@@ -5921,9 +6501,29 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5921
6501
|
}
|
|
5922
6502
|
throw new Error(`multi-root workspace: @deepseek-ai/dsh-client-ui-primitives exports none of ${names.join(", ")}; the workspace-folders panel cannot render its icons. Adapt src/compat/client-icons.ts to the installed release.`);
|
|
5923
6503
|
}
|
|
6504
|
+
/**
|
|
6505
|
+
* The same by-name probe for a decorative glyph a release may simply not
|
|
6506
|
+
* carry: absence degrades to no icon, not to a boot error. Required icons go
|
|
6507
|
+
* through `selectIcon` and fail loud; optional ones go through here.
|
|
6508
|
+
*/
|
|
6509
|
+
function maybeIcon(names) {
|
|
6510
|
+
for (const name of names) {
|
|
6511
|
+
const found = installed$1[name];
|
|
6512
|
+
if (found !== void 0) return found;
|
|
6513
|
+
}
|
|
6514
|
+
}
|
|
6515
|
+
/**
|
|
6516
|
+
* The same by-name probe for a whole component a release may not carry:
|
|
6517
|
+
* `PathLabel` arrived in `0.1.7-alpha.1`, so pixel-era runtimes degrade to a
|
|
6518
|
+
* plain span where the header invites one. Absence degrades to no component,
|
|
6519
|
+
* not to a boot error.
|
|
6520
|
+
*/
|
|
6521
|
+
function maybeComponent(names) {
|
|
6522
|
+
return maybeIcon(names);
|
|
6523
|
+
}
|
|
5924
6524
|
/** Resolved once, against the installed module. */
|
|
5925
6525
|
function icon(pixelName, regularName) {
|
|
5926
|
-
return selectIcon(installed, [pixelName, regularName]);
|
|
6526
|
+
return selectIcon(installed$1, [pixelName, regularName]);
|
|
5927
6527
|
}
|
|
5928
6528
|
const IconChevronDown = icon("IconChevronDownOutline14", "IconChevronDownOutlineRegular");
|
|
5929
6529
|
const IconChevronUp = icon("IconChevronUpOutline14", "IconChevronUpOutlineRegular");
|
|
@@ -5933,34 +6533,1537 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5933
6533
|
const IconFolderClose = icon("IconFolderClose16", "IconFolderCloseRegular");
|
|
5934
6534
|
const IconFolderOpen = icon("IconFolderOpenOutline16", "IconFolderOpenOutlineRegular");
|
|
5935
6535
|
const IconPlus = icon("IconPlusOutline16", "IconPlusOutlineRegular");
|
|
6536
|
+
const IconRefresh = icon("IconRefreshOutline16", "IconRefreshOutlineRegular");
|
|
6537
|
+
const IconSettings = icon("IconSettingsOutline16", "IconSettingsOutlineRegular");
|
|
5936
6538
|
const IconTrash = icon("IconTrashOutline16", "IconTrashOutlineRegular");
|
|
6539
|
+
const IconAccessReadWrite = maybeIcon(["IconWorkspaceWriteRegular"]);
|
|
6540
|
+
const IconAccessReadOnly = maybeIcon(["IconReadOnlyRegular"]);
|
|
6541
|
+
const IconAccessInherit = maybeIcon(["IconWorkspaceTreeOutline16", "IconWorkspaceTreeOutlineRegular"]);
|
|
5937
6542
|
//#endregion
|
|
5938
|
-
//#region
|
|
6543
|
+
//#region node_modules/.pnpm/@deepseek-ai+dsh-typert-protocol@0.2.1-rc.2_@deepseek-ai+cordis@4.0.5/node_modules/@deepseek-ai/dsh-typert-protocol/lib/index.js
|
|
6544
|
+
/** The one Remote failure class shared by owners, the Gateway, and consumers. */
|
|
5939
6545
|
/**
|
|
5940
|
-
*
|
|
5941
|
-
*
|
|
5942
|
-
*
|
|
5943
|
-
*
|
|
5944
|
-
*
|
|
5945
|
-
* Why a footer action and its own dialog, instead of a first-class panel: the
|
|
5946
|
-
* slots that can host a full panel (`sidebar.panellist` + the keyed `main`
|
|
5947
|
-
* panel) exist only in the pinned 0.1.5 surface, while `sidebar.footer.action`
|
|
5948
|
-
* exists in both supported runtimes — and `sidebar.workspaces` is a single slot
|
|
5949
|
-
* already owned by the workspace browser, so it cannot be extended in place.
|
|
5950
|
-
*
|
|
5951
|
-
* Styling is the host's, not this file's: the elements carry `mrfw-*` classes
|
|
5952
|
-
* resolved by the stylesheet the client entry injects (`styles.ts`), and every
|
|
5953
|
-
* color in that sheet is a host `--dsw-*` token, so the panel inherits the
|
|
5954
|
-
* native look — sidebar trigger, modal card, capsule buttons — in both themes
|
|
5955
|
-
* without naming a single color here (see ADR-0006).
|
|
5956
|
-
*
|
|
5957
|
-
* @module @dsh-electron/dsh-plugin-multi-root-workspace/client/WorkspaceFoldersAction
|
|
6546
|
+
* One Remote call failure: a real Error carrying its stable code and typed
|
|
6547
|
+
* details. Owners throw it at the failure point; the Host Gateway encodes it
|
|
6548
|
+
* onto the wire unchanged; the Client face rebuilds an instance for the
|
|
6549
|
+
* `RemoteResult` error branch, so `throw result.error` keeps throw semantics.
|
|
6550
|
+
* Discrimination is always by `code`, never by instanceof.
|
|
5958
6551
|
*/
|
|
5959
|
-
|
|
5960
|
-
|
|
5961
|
-
|
|
5962
|
-
|
|
5963
|
-
|
|
6552
|
+
var RemoteError = class extends Error {
|
|
6553
|
+
code;
|
|
6554
|
+
details;
|
|
6555
|
+
/** Structural marker: cross-realm/bundle identification never uses instanceof. */
|
|
6556
|
+
isDSHRemoteError = true;
|
|
6557
|
+
/**
|
|
6558
|
+
* @param code - stable failure code declared in {@link RemoteErrorDetailsMap}.
|
|
6559
|
+
* @param message - human diagnostic carried across the wire.
|
|
6560
|
+
* @param details - structured payload typed by the code.
|
|
6561
|
+
* @param options - standard Error options (`cause` survives in-process only).
|
|
6562
|
+
*/
|
|
6563
|
+
constructor(code, message, details, options) {
|
|
6564
|
+
super(message, options);
|
|
6565
|
+
this.code = code;
|
|
6566
|
+
this.details = details;
|
|
6567
|
+
this.name = "RemoteError";
|
|
6568
|
+
}
|
|
6569
|
+
};
|
|
6570
|
+
//#endregion
|
|
6571
|
+
//#region src/client/files-tree/directory-node.ts
|
|
6572
|
+
/** One open directory and its active child nodes; cached view preferences live in the store. */
|
|
6573
|
+
var DirectoryNode = class DirectoryNode {
|
|
6574
|
+
path;
|
|
6575
|
+
load;
|
|
6576
|
+
watch;
|
|
6577
|
+
failed;
|
|
6578
|
+
restore;
|
|
6579
|
+
/** Open direct-child directories, keyed by absolute path. */
|
|
6580
|
+
children = /* @__PURE__ */ new Map();
|
|
6581
|
+
controller = new AbortController();
|
|
6582
|
+
signal;
|
|
6583
|
+
task;
|
|
6584
|
+
reading;
|
|
6585
|
+
dirty = false;
|
|
6586
|
+
initialized = false;
|
|
6587
|
+
automatic = true;
|
|
6588
|
+
constructor(path, load, watch, failed, lifetime, restore = []) {
|
|
6589
|
+
this.path = path;
|
|
6590
|
+
this.load = load;
|
|
6591
|
+
this.watch = watch;
|
|
6592
|
+
this.failed = failed;
|
|
6593
|
+
this.restore = restore;
|
|
6594
|
+
this.signal = AbortSignal.any([lifetime, this.controller.signal]);
|
|
6595
|
+
}
|
|
6596
|
+
/**
|
|
6597
|
+
* Start observation once; readiness triggers the initial listing.
|
|
6598
|
+
* @returns this node.
|
|
6599
|
+
*/
|
|
6600
|
+
open() {
|
|
6601
|
+
this.task ??= this.follow();
|
|
6602
|
+
return this;
|
|
6603
|
+
}
|
|
6604
|
+
/**
|
|
6605
|
+
* Find an active node in this subtree.
|
|
6606
|
+
* @param path - absolute directory path.
|
|
6607
|
+
* @returns the matching node, or undefined when that directory is closed.
|
|
6608
|
+
*/
|
|
6609
|
+
find(path) {
|
|
6610
|
+
if (path === this.path) return this;
|
|
6611
|
+
for (const child of this.children.values()) {
|
|
6612
|
+
const found = child.find(path);
|
|
6613
|
+
if (found !== void 0) return found;
|
|
6614
|
+
}
|
|
6615
|
+
}
|
|
6616
|
+
/**
|
|
6617
|
+
* Update the expansion preferences used by pending directory listings.
|
|
6618
|
+
* @param expanded - latest expansion preferences from the store.
|
|
6619
|
+
*/
|
|
6620
|
+
setExpanded(expanded) {
|
|
6621
|
+
this.restore = expanded;
|
|
6622
|
+
for (const child of this.children.values()) child.setExpanded(expanded);
|
|
6623
|
+
}
|
|
6624
|
+
/**
|
|
6625
|
+
* Open a direct child using this node's lifetime and automatic-refresh setting.
|
|
6626
|
+
* @param path - absolute direct-child directory path.
|
|
6627
|
+
* @param restore - descendant expansion preferences to restore after listing.
|
|
6628
|
+
* @returns the active child, or undefined after cancellation.
|
|
6629
|
+
*/
|
|
6630
|
+
expand(path, restore = []) {
|
|
6631
|
+
if (this.signal.aborted) return void 0;
|
|
6632
|
+
let child = this.children.get(path);
|
|
6633
|
+
if (child === void 0) {
|
|
6634
|
+
child = new DirectoryNode(path, this.load, this.watch, this.failed, this.signal, restore);
|
|
6635
|
+
child.automatic = this.automatic;
|
|
6636
|
+
this.children.set(path, child);
|
|
6637
|
+
}
|
|
6638
|
+
return child.open();
|
|
6639
|
+
}
|
|
6640
|
+
/**
|
|
6641
|
+
* Remove a child and its pending restoration preferences.
|
|
6642
|
+
* @param path - absolute direct-child directory path.
|
|
6643
|
+
* @returns once the child subtree's reads and watches have ended.
|
|
6644
|
+
*/
|
|
6645
|
+
async collapse(path) {
|
|
6646
|
+
this.restore = this.restore.filter((value) => value !== path && !value.startsWith(`${path}/`));
|
|
6647
|
+
const child = this.children.get(path);
|
|
6648
|
+
this.children.delete(path);
|
|
6649
|
+
await child?.close();
|
|
6650
|
+
}
|
|
6651
|
+
/**
|
|
6652
|
+
* Control subtree rereads without ending subscriptions.
|
|
6653
|
+
* @param enabled - refresh dirty nodes automatically.
|
|
6654
|
+
*/
|
|
6655
|
+
setAutomatic(enabled) {
|
|
6656
|
+
this.automatic = enabled;
|
|
6657
|
+
if (enabled && this.dirty) this.refresh();
|
|
6658
|
+
for (const child of this.children.values()) child.setAutomatic(enabled);
|
|
6659
|
+
}
|
|
6660
|
+
/** Queue a reread, coalescing with active work. @returns completion of the active read and its coalesced rereads. */
|
|
6661
|
+
refresh() {
|
|
6662
|
+
this.dirty = true;
|
|
6663
|
+
this.reading ??= this.read();
|
|
6664
|
+
return this.reading;
|
|
6665
|
+
}
|
|
6666
|
+
/** Refresh this node and its open descendants. @returns once their listings settle. */
|
|
6667
|
+
async refreshTree() {
|
|
6668
|
+
await this.refresh();
|
|
6669
|
+
await Promise.all([...this.children.values()].map((child) => child.refreshTree()));
|
|
6670
|
+
}
|
|
6671
|
+
/** Cancel the active subtree. @returns once all owned reads and watches have ended. */
|
|
6672
|
+
async close() {
|
|
6673
|
+
this.controller.abort();
|
|
6674
|
+
await Promise.all([
|
|
6675
|
+
this.task,
|
|
6676
|
+
this.reading,
|
|
6677
|
+
...[...this.children.values()].map((child) => child.close())
|
|
6678
|
+
]);
|
|
6679
|
+
this.children.clear();
|
|
6680
|
+
}
|
|
6681
|
+
async follow() {
|
|
6682
|
+
try {
|
|
6683
|
+
for await (const _event of this.watch(this.path, this.signal)) {
|
|
6684
|
+
this.dirty = true;
|
|
6685
|
+
if (!this.initialized || this.automatic) this.refresh();
|
|
6686
|
+
}
|
|
6687
|
+
} catch (error) {
|
|
6688
|
+
if (!this.signal.aborted) {
|
|
6689
|
+
if (!this.initialized) await this.refresh();
|
|
6690
|
+
if (!(typeof error === "object" && error !== null && "code" in error && error.code === "workspace-file/watch-unsupported")) this.failed(this.path, error);
|
|
6691
|
+
}
|
|
6692
|
+
}
|
|
6693
|
+
}
|
|
6694
|
+
async read() {
|
|
6695
|
+
const readAgain = () => this.dirty && this.automatic && !this.signal.aborted;
|
|
6696
|
+
try {
|
|
6697
|
+
do {
|
|
6698
|
+
this.dirty = false;
|
|
6699
|
+
const level = await this.load(this.path, this.signal);
|
|
6700
|
+
if (this.signal.aborted || level === void 0) return;
|
|
6701
|
+
this.initialized = true;
|
|
6702
|
+
const directories = new Set(level.entries.filter((entry) => entry.type === "directory").map((entry) => `${this.path.replace(/[/\\]+$/, "")}/${entry.name}`));
|
|
6703
|
+
for (const path of this.children.keys()) if (!directories.has(path)) await this.collapse(path);
|
|
6704
|
+
for (const path of directories) if (this.restore.includes(path)) this.expand(path, this.restore);
|
|
6705
|
+
this.restore = [];
|
|
6706
|
+
} while (readAgain());
|
|
6707
|
+
} finally {
|
|
6708
|
+
this.reading = void 0;
|
|
6709
|
+
}
|
|
6710
|
+
}
|
|
6711
|
+
};
|
|
6712
|
+
//#endregion
|
|
6713
|
+
//#region src/client/files-tree/face.ts
|
|
6714
|
+
/**
|
|
6715
|
+
* Bind directory observation to the Remote stream supervisor.
|
|
6716
|
+
* @param remote - Client Remote with workspace file streams.
|
|
6717
|
+
* @returns a watcher that awaits stream disposal when its node ends.
|
|
6718
|
+
*/
|
|
6719
|
+
function createWatch(remote) {
|
|
6720
|
+
return async function* (sessionId, path, signal) {
|
|
6721
|
+
const aborted = () => signal.aborted;
|
|
6722
|
+
if (aborted()) return;
|
|
6723
|
+
const stream = remote.$stream({
|
|
6724
|
+
name: `directory ${path}`,
|
|
6725
|
+
open: (lifetime) => remote.workspaceFiles.changes(sessionId, path, lifetime),
|
|
6726
|
+
ended: () => /* @__PURE__ */ new Error(`Directory watch ended: ${path}`)
|
|
6727
|
+
});
|
|
6728
|
+
const abort = () => {
|
|
6729
|
+
stream.dispose();
|
|
6730
|
+
};
|
|
6731
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
6732
|
+
try {
|
|
6733
|
+
for await (const item of stream) {
|
|
6734
|
+
if (aborted()) return;
|
|
6735
|
+
if (item.value.kind === "ready") item.accept();
|
|
6736
|
+
yield item.value.kind;
|
|
6737
|
+
}
|
|
6738
|
+
} finally {
|
|
6739
|
+
signal.removeEventListener("abort", abort);
|
|
6740
|
+
await stream.dispose();
|
|
6741
|
+
}
|
|
6742
|
+
};
|
|
6743
|
+
}
|
|
6744
|
+
/**
|
|
6745
|
+
* Bind the listing to one Remote face, keeping only what the tree stores.
|
|
6746
|
+
* @param remote - the Client Remote face carrying the `workspaceFiles` namespace.
|
|
6747
|
+
* @returns the listing the tree's face performs.
|
|
6748
|
+
*/
|
|
6749
|
+
function createList(remote) {
|
|
6750
|
+
return async (sessionId, path, signal) => {
|
|
6751
|
+
const result = await remote.workspaceFiles.list(sessionId, path, signal);
|
|
6752
|
+
if (!result.ok) return result;
|
|
6753
|
+
return {
|
|
6754
|
+
ok: true,
|
|
6755
|
+
value: {
|
|
6756
|
+
entries: result.value.entries,
|
|
6757
|
+
truncated: result.value.truncated
|
|
6758
|
+
}
|
|
6759
|
+
};
|
|
6760
|
+
};
|
|
6761
|
+
}
|
|
6762
|
+
/**
|
|
6763
|
+
* The absolute path of one child entry.
|
|
6764
|
+
*
|
|
6765
|
+
* Joined with `/` whatever the parent's separators: the Host resolves mixed
|
|
6766
|
+
* separators, and the tree only needs a stable key.
|
|
6767
|
+
* @param parent - absolute path of the listed directory.
|
|
6768
|
+
* @param name - the entry's basename.
|
|
6769
|
+
* @returns the child's absolute path.
|
|
6770
|
+
*/
|
|
6771
|
+
function childPath(parent, name) {
|
|
6772
|
+
return `${parent.replace(/[/\\]+$/, "")}/${name}`;
|
|
6773
|
+
}
|
|
6774
|
+
/**
|
|
6775
|
+
* Bind the tree's face to one directory listing.
|
|
6776
|
+
* @param list - the bound `workspaceFiles.list` call.
|
|
6777
|
+
* @param watch - target-scoped directory observation.
|
|
6778
|
+
* @returns the Slot `inject` factory: session and bound actions in, face out.
|
|
6779
|
+
*/
|
|
6780
|
+
function filesFace(list, watch) {
|
|
6781
|
+
return (sessionId, actions) => {
|
|
6782
|
+
/** Per tab, per absolute path: the listing generation a settlement must match; the latest request wins. */
|
|
6783
|
+
const generations = /* @__PURE__ */ new Map();
|
|
6784
|
+
const roots = /* @__PURE__ */ new Map();
|
|
6785
|
+
const nextGeneration = (tabId, path) => {
|
|
6786
|
+
const byPath = generations.get(tabId) ?? /* @__PURE__ */ new Map();
|
|
6787
|
+
generations.set(tabId, byPath);
|
|
6788
|
+
const generation = (byPath.get(path) ?? 0) + 1;
|
|
6789
|
+
byPath.set(path, generation);
|
|
6790
|
+
return generation;
|
|
6791
|
+
};
|
|
6792
|
+
const load = async (tabId, path, signal) => {
|
|
6793
|
+
if (signal.aborted) return;
|
|
6794
|
+
const generation = nextGeneration(tabId, path);
|
|
6795
|
+
actions.loading(tabId, path);
|
|
6796
|
+
return list(sessionId, path, signal).then((result) => {
|
|
6797
|
+
if (signal.aborted || generations.get(tabId)?.get(path) !== generation) return;
|
|
6798
|
+
if (result.ok) actions.loaded(tabId, path, result.value);
|
|
6799
|
+
else actions.failed(tabId, path, result.error);
|
|
6800
|
+
return result.ok ? result.value : void 0;
|
|
6801
|
+
});
|
|
6802
|
+
};
|
|
6803
|
+
return {
|
|
6804
|
+
refresh: (tabId) => {
|
|
6805
|
+
roots.get(tabId)?.refreshTree();
|
|
6806
|
+
},
|
|
6807
|
+
setAutoRefresh: (tabId, enabled) => {
|
|
6808
|
+
actions.autoRefresh(tabId, enabled);
|
|
6809
|
+
roots.get(tabId)?.setAutomatic(enabled);
|
|
6810
|
+
},
|
|
6811
|
+
start(tabId, root, signal) {
|
|
6812
|
+
actions.start(tabId, root);
|
|
6813
|
+
signal.addEventListener("abort", () => {
|
|
6814
|
+
roots.get(tabId)?.close();
|
|
6815
|
+
roots.delete(tabId);
|
|
6816
|
+
generations.delete(tabId);
|
|
6817
|
+
actions.forget(tabId);
|
|
6818
|
+
}, { once: true });
|
|
6819
|
+
roots.set(tabId, new DirectoryNode(root, (path, lifetime) => load(tabId, path, lifetime), (path, lifetime) => watch(sessionId, path, lifetime), (path, error) => {
|
|
6820
|
+
if (!signal.aborted) actions.failed(tabId, path, new RemoteError("gateway/internal", error instanceof Error ? error.message : String(error), {}));
|
|
6821
|
+
}, signal).open());
|
|
6822
|
+
},
|
|
6823
|
+
load: (tabId, path, signal) => {
|
|
6824
|
+
load(tabId, path, signal);
|
|
6825
|
+
},
|
|
6826
|
+
toggle(tabId, parentPath, path, expanded, signal) {
|
|
6827
|
+
if (signal.aborted) return;
|
|
6828
|
+
const root = roots.get(tabId);
|
|
6829
|
+
if (root === void 0) return;
|
|
6830
|
+
const parent = root.find(parentPath);
|
|
6831
|
+
if (parent === void 0 && !expanded.includes(parentPath)) return;
|
|
6832
|
+
const collapsing = expanded.includes(path);
|
|
6833
|
+
const next = collapsing ? expanded.filter((value) => value !== path) : [...expanded, path];
|
|
6834
|
+
root.setExpanded(next);
|
|
6835
|
+
if (collapsing) parent?.collapse(path);
|
|
6836
|
+
else parent?.expand(path, next);
|
|
6837
|
+
actions.toggled(tabId, path);
|
|
6838
|
+
}
|
|
6839
|
+
};
|
|
6840
|
+
};
|
|
6841
|
+
}
|
|
6842
|
+
//#endregion
|
|
6843
|
+
//#region src/client/files-tree/store.ts
|
|
6844
|
+
/**
|
|
6845
|
+
* The file tree's view state: which directories are expanded, what each
|
|
6846
|
+
* loaded level contains, and where the body is scrolled to.
|
|
6847
|
+
*
|
|
6848
|
+
* The tree is not one resource. A directory listing per level, expanded lazily,
|
|
6849
|
+
* is state the type owns — so it lives in a Slot-standard exclusive store
|
|
6850
|
+
* (one instance per session), bucketed by tab id because two tabs of this kind
|
|
6851
|
+
* in one session expand independently.
|
|
6852
|
+
*
|
|
6853
|
+
* Writers run between `start` and `forget`: the owner's `signal` is what ends a
|
|
6854
|
+
* bucket's life, and the face stops dispatching once it aborts.
|
|
6855
|
+
*/
|
|
6856
|
+
/**
|
|
6857
|
+
* One tab's bucket, which every writer after `start` relies on: the face only
|
|
6858
|
+
* dispatches while the record's signal is live, and `forget` runs on its abort.
|
|
6859
|
+
* @param state - the draft.
|
|
6860
|
+
* @param tabId - the tab being written.
|
|
6861
|
+
* @returns the tab's tree.
|
|
6862
|
+
*/
|
|
6863
|
+
function bucket(state, tabId) {
|
|
6864
|
+
const tree = state.byTab[tabId];
|
|
6865
|
+
if (tree === void 0) throw new Error(`ui-sidebar-files: no tree for tab "${tabId}"`);
|
|
6866
|
+
return tree;
|
|
6867
|
+
}
|
|
6868
|
+
/**
|
|
6869
|
+
* Declare the file tree's store.
|
|
6870
|
+
*
|
|
6871
|
+
* A factory rather than a shared handle: the registration declares it as an
|
|
6872
|
+
* exclusive store, so the framework mints one instance per session.
|
|
6873
|
+
* @returns the store handle to declare on the registration.
|
|
6874
|
+
*/
|
|
6875
|
+
function createFilesStore() {
|
|
6876
|
+
return (0, _deepseek_ai_dsh_client_store.defineStore)({
|
|
6877
|
+
init: () => ({ byTab: {} }),
|
|
6878
|
+
actions: {
|
|
6879
|
+
autoRefresh: (d, tabId, enabled) => {
|
|
6880
|
+
bucket(d, tabId).autoRefresh = enabled;
|
|
6881
|
+
},
|
|
6882
|
+
/**
|
|
6883
|
+
* Seed one tab's tree at its workspace root, with the root expanded.
|
|
6884
|
+
* @param d - draft state.
|
|
6885
|
+
* @param tabId - the tab being drawn.
|
|
6886
|
+
* @param root - absolute path of the workspace root.
|
|
6887
|
+
*/
|
|
6888
|
+
start: (d, tabId, root) => {
|
|
6889
|
+
d.byTab[tabId] = {
|
|
6890
|
+
root,
|
|
6891
|
+
levels: {},
|
|
6892
|
+
expanded: [root],
|
|
6893
|
+
scrollTop: 0,
|
|
6894
|
+
autoRefresh: true
|
|
6895
|
+
};
|
|
6896
|
+
},
|
|
6897
|
+
/**
|
|
6898
|
+
* Mark one directory as being listed.
|
|
6899
|
+
* @param d - draft state.
|
|
6900
|
+
* @param tabId - the tab being drawn.
|
|
6901
|
+
* @param path - absolute directory path.
|
|
6902
|
+
*/
|
|
6903
|
+
loading: (d, tabId, path) => {
|
|
6904
|
+
const state = bucket(d, tabId);
|
|
6905
|
+
if (state.levels[path]?.kind !== "ready") state.levels[path] = { kind: "loading" };
|
|
6906
|
+
},
|
|
6907
|
+
/**
|
|
6908
|
+
* Record one directory's contents.
|
|
6909
|
+
* @param d - draft state.
|
|
6910
|
+
* @param tabId - the tab being drawn.
|
|
6911
|
+
* @param path - absolute directory path.
|
|
6912
|
+
* @param level - the listing to show under it.
|
|
6913
|
+
*/
|
|
6914
|
+
loaded: (d, tabId, path, level) => {
|
|
6915
|
+
const state = bucket(d, tabId);
|
|
6916
|
+
const previous = state.levels[path];
|
|
6917
|
+
if (previous?.kind === "ready") {
|
|
6918
|
+
const directories = new Set(level.entries.filter((entry) => entry.type === "directory").map((entry) => entry.name));
|
|
6919
|
+
for (const entry of previous.level.entries) {
|
|
6920
|
+
if (entry.type !== "directory" || directories.has(entry.name)) continue;
|
|
6921
|
+
const removed = `${path.replace(/[/\\]+$/, "")}/${entry.name}`;
|
|
6922
|
+
state.expanded = state.expanded.filter((value) => value !== removed && !value.startsWith(`${removed}/`));
|
|
6923
|
+
state.levels = Object.fromEntries(Object.entries(state.levels).filter(([key]) => key !== removed && !key.startsWith(`${removed}/`)));
|
|
6924
|
+
}
|
|
6925
|
+
}
|
|
6926
|
+
state.levels[path] = {
|
|
6927
|
+
kind: "ready",
|
|
6928
|
+
level
|
|
6929
|
+
};
|
|
6930
|
+
},
|
|
6931
|
+
/**
|
|
6932
|
+
* Record why one directory could not be listed.
|
|
6933
|
+
* @param d - draft state.
|
|
6934
|
+
* @param tabId - the tab being drawn.
|
|
6935
|
+
* @param path - absolute directory path.
|
|
6936
|
+
* @param failure - the settled Remote failure.
|
|
6937
|
+
*/
|
|
6938
|
+
failed: (d, tabId, path, failure) => {
|
|
6939
|
+
const level = bucket(d, tabId).levels[path];
|
|
6940
|
+
bucket(d, tabId).levels[path] = level?.kind === "ready" ? {
|
|
6941
|
+
...level,
|
|
6942
|
+
failure
|
|
6943
|
+
} : {
|
|
6944
|
+
kind: "failed",
|
|
6945
|
+
failure
|
|
6946
|
+
};
|
|
6947
|
+
},
|
|
6948
|
+
/**
|
|
6949
|
+
* Open a collapsed directory, or collapse an open one.
|
|
6950
|
+
*
|
|
6951
|
+
* A collapsed level keeps what it loaded, so reopening it draws at once.
|
|
6952
|
+
* @param d - draft state.
|
|
6953
|
+
* @param tabId - the tab being drawn.
|
|
6954
|
+
* @param path - absolute directory path.
|
|
6955
|
+
*/
|
|
6956
|
+
toggled: (d, tabId, path) => {
|
|
6957
|
+
const state = bucket(d, tabId);
|
|
6958
|
+
const at = state.expanded.indexOf(path);
|
|
6959
|
+
if (at >= 0) state.expanded.splice(at, 1);
|
|
6960
|
+
else state.expanded.push(path);
|
|
6961
|
+
},
|
|
6962
|
+
/**
|
|
6963
|
+
* Record where one tab's body is scrolled to.
|
|
6964
|
+
* @param d - draft state.
|
|
6965
|
+
* @param tabId - the tab being drawn.
|
|
6966
|
+
* @param scrollTop - the body's scroll offset, in px.
|
|
6967
|
+
*/
|
|
6968
|
+
scrolled: (d, tabId, scrollTop) => {
|
|
6969
|
+
bucket(d, tabId).scrollTop = scrollTop;
|
|
6970
|
+
},
|
|
6971
|
+
/**
|
|
6972
|
+
* Drop every loaded level, keeping what is expanded.
|
|
6973
|
+
*
|
|
6974
|
+
* This is the reload gesture's first half: the expanded set says which
|
|
6975
|
+
* levels to fetch again.
|
|
6976
|
+
* @param d - draft state.
|
|
6977
|
+
* @param tabId - the tab being drawn.
|
|
6978
|
+
*/
|
|
6979
|
+
reset: (d, tabId) => {
|
|
6980
|
+
bucket(d, tabId).levels = {};
|
|
6981
|
+
},
|
|
6982
|
+
/**
|
|
6983
|
+
* Forget one tab's tree, for a tab record that is gone.
|
|
6984
|
+
* @param d - draft state.
|
|
6985
|
+
* @param tabId - the tab that went away.
|
|
6986
|
+
*/
|
|
6987
|
+
forget: (d, tabId) => {
|
|
6988
|
+
d.byTab = Object.fromEntries(Object.entries(d.byTab).filter(([id]) => id !== tabId));
|
|
6989
|
+
}
|
|
6990
|
+
}
|
|
6991
|
+
});
|
|
6992
|
+
}
|
|
6993
|
+
//#endregion
|
|
6994
|
+
//#region src/client/files-tree/primitives.ts
|
|
6995
|
+
/**
|
|
6996
|
+
* Runtime probes for the `@deepseek-ai/dsh-client-ui-primitives` exports the
|
|
6997
|
+
* verbatim Files-tree files spell.
|
|
6998
|
+
*
|
|
6999
|
+
* The upstream imports name the `*Regular` glyph family (0.1.7+; the pixel
|
|
7000
|
+
* names are kept as fallbacks for the same reason `compat/client-icons`
|
|
7001
|
+
* dual-names them) plus `FileTypeIcon` and `classifyFileType`, which every
|
|
7002
|
+
* supported release carries. A named import cannot spell two release eras at
|
|
7003
|
+
* once, so the resolution goes through `maybeIcon`/`maybeComponent` — the
|
|
7004
|
+
* same probe the open-in-app fork uses.
|
|
7005
|
+
*
|
|
7006
|
+
* The exported bindings are typed definite because the adapter mounts the
|
|
7007
|
+
* tree only after {@link filesTreeSurfaceSupported} passes; reaching for a
|
|
7008
|
+
* binding on a composition that failed the gate is a caller bug, and the
|
|
7009
|
+
* gate — not this module — is what a missing export degrades into.
|
|
7010
|
+
*
|
|
7011
|
+
* @module @dsh-electron/dsh-plugin-multi-root-workspace/client/files-tree/primitives
|
|
7012
|
+
*/
|
|
7013
|
+
/** Exports the fork's row rendering needs, keyed by their upstream names. */
|
|
7014
|
+
const probed = {
|
|
7015
|
+
folderClose: maybeIcon(["IconFolderCloseRegular", "IconFolderClose16"]),
|
|
7016
|
+
folderOpen: maybeIcon(["IconFolderOpenRegular", "IconFolderOpen16"]),
|
|
7017
|
+
fileTypeIcon: maybeComponent(["FileTypeIcon"]),
|
|
7018
|
+
classifyFileType: maybeComponent(["classifyFileType"])
|
|
7019
|
+
};
|
|
7020
|
+
/**
|
|
7021
|
+
* Whether this composition's primitives carry everything the forked rows
|
|
7022
|
+
* spell. The adapter consults this once: `false` means the additional-root
|
|
7023
|
+
* menu stays menu-only and no fork mount is attempted — the same absence
|
|
7024
|
+
* upstream's own no-such-export state would produce.
|
|
7025
|
+
*/
|
|
7026
|
+
function filesTreeSurfaceSupported() {
|
|
7027
|
+
return probed.folderClose !== void 0 && probed.folderOpen !== void 0 && probed.fileTypeIcon !== void 0 && probed.classifyFileType !== void 0;
|
|
7028
|
+
}
|
|
7029
|
+
const IconFolderCloseRegular = probed.folderClose;
|
|
7030
|
+
const IconFolderOpenRegular = probed.folderOpen;
|
|
7031
|
+
const FileTypeIcon = probed.fileTypeIcon;
|
|
7032
|
+
const classifyFileType = probed.classifyFileType;
|
|
7033
|
+
//#endregion
|
|
7034
|
+
//#region src/client/files-tree/locales.ts
|
|
7035
|
+
/** Dictionary namespace owned by upstream's plugin (verbatim). */
|
|
7036
|
+
const NS$1 = "sidebarFiles";
|
|
7037
|
+
//#endregion
|
|
7038
|
+
//#region node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs
|
|
7039
|
+
function r(e) {
|
|
7040
|
+
var t, f, n = "";
|
|
7041
|
+
if ("string" == typeof e || "number" == typeof e) n += e;
|
|
7042
|
+
else if ("object" == typeof e) if (Array.isArray(e)) {
|
|
7043
|
+
var o = e.length;
|
|
7044
|
+
for (t = 0; t < o; t++) e[t] && (f = r(e[t])) && (n && (n += " "), n += f);
|
|
7045
|
+
} else for (f in e) e[f] && (n && (n += " "), n += f);
|
|
7046
|
+
return n;
|
|
7047
|
+
}
|
|
7048
|
+
function clsx() {
|
|
7049
|
+
for (var e, t, f = 0, n = "", o = arguments.length; f < o; f++) (e = arguments[f]) && (t = r(e)) && (n && (n += " "), n += t);
|
|
7050
|
+
return n;
|
|
7051
|
+
}
|
|
7052
|
+
//#endregion
|
|
7053
|
+
//#region node_modules/.pnpm/@deepseek-ai+dsh-util-workspace-path@0.2.1-rc.2_@deepseek-ai+cordis@4.0.5/node_modules/@deepseek-ai/dsh-util-workspace-path/lib/index.js
|
|
7054
|
+
/**
|
|
7055
|
+
* The `dsh-resource://file/…` address grammar: how a file is named across the
|
|
7056
|
+
* Sidebar and the resource model, built and parsed without touching a
|
|
7057
|
+
* filesystem.
|
|
7058
|
+
* @module
|
|
7059
|
+
*/
|
|
7060
|
+
/** The scheme and type every file address opens with. */
|
|
7061
|
+
const FILE_ADDRESS_PREFIX = "dsh-resource://file/";
|
|
7062
|
+
/** Component-encode one id or path segment, keeping `:` literal for drive letters. */
|
|
7063
|
+
function encodeSegment(segment) {
|
|
7064
|
+
return encodeURIComponent(segment).replace(/%3A/gi, ":");
|
|
7065
|
+
}
|
|
7066
|
+
/** Encode a `/`-separated path segment by segment. */
|
|
7067
|
+
function encodePath(path) {
|
|
7068
|
+
return path.split("/").map(encodeSegment).join("/");
|
|
7069
|
+
}
|
|
7070
|
+
/**
|
|
7071
|
+
* Build the address of a file read through one Session.
|
|
7072
|
+
* @param sessionId - the Session whose Host workspace resolves the path.
|
|
7073
|
+
* @param path - absolute or workspace-relative path; backslashes are normalized to `/`, and leading `./` prefixes are dropped.
|
|
7074
|
+
* @returns the `dsh-resource://file/session/<sessionId>/<path>` address.
|
|
7075
|
+
*/
|
|
7076
|
+
function sessionFileAddress(sessionId, path) {
|
|
7077
|
+
const normalized = path.replace(/\\/g, "/").replace(/^(?:\.\/)+/, "");
|
|
7078
|
+
return `${FILE_ADDRESS_PREFIX}session/${encodeSegment(sessionId)}/${encodePath(normalized)}`;
|
|
7079
|
+
}
|
|
7080
|
+
/**
|
|
7081
|
+
* Browser-safe Workspace path and display helpers.
|
|
7082
|
+
* @module @deepseek-ai/dsh-util-workspace-path
|
|
7083
|
+
*/
|
|
7084
|
+
/** Whether a path uses a Windows drive or UNC prefix. */
|
|
7085
|
+
function isWindowsStylePath(value) {
|
|
7086
|
+
return /^[A-Za-z]:[/\\]/.test(value) || value.startsWith("\\\\");
|
|
7087
|
+
}
|
|
7088
|
+
/**
|
|
7089
|
+
* Whether a path is absolute in either spelling the Host accepts: POSIX (`/a/b`) or Windows drive or UNC.
|
|
7090
|
+
* @param path - the path to classify.
|
|
7091
|
+
* @returns `true` for an absolute path; `false` for a Workspace-relative one.
|
|
7092
|
+
*/
|
|
7093
|
+
function isAbsoluteWorkspacePath(path) {
|
|
7094
|
+
return path.startsWith("/") || isWindowsStylePath(path);
|
|
7095
|
+
}
|
|
7096
|
+
/**
|
|
7097
|
+
* The address for a path as a caller holds it: a relative path, or an absolute
|
|
7098
|
+
* path inside the Session's workspace, becomes a `session`-scoped address; an
|
|
7099
|
+
* absolute path outside it, or one whose workspace root is unknown, keeps its
|
|
7100
|
+
* absolute path in that Session's address.
|
|
7101
|
+
* @param sessionId - the Session the path is read in.
|
|
7102
|
+
* @param cwd - that Session's workspace root, when known.
|
|
7103
|
+
* @param path - absolute or workspace-relative path, in either separator spelling.
|
|
7104
|
+
* @returns the `dsh-resource://file/…` address.
|
|
7105
|
+
*/
|
|
7106
|
+
function fileAddressFor(sessionId, cwd, path) {
|
|
7107
|
+
const normalized = path.replace(/\\/g, "/");
|
|
7108
|
+
if (!isAbsoluteWorkspacePath(normalized)) return sessionFileAddress(sessionId, normalized);
|
|
7109
|
+
const root = cwd === void 0 ? "" : cwd.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
7110
|
+
if (root !== "" && normalized === root) return sessionFileAddress(sessionId, "");
|
|
7111
|
+
if (root !== "" && normalized.startsWith(`${root}/`)) return sessionFileAddress(sessionId, normalized.slice(root.length + 1));
|
|
7112
|
+
return sessionFileAddress(sessionId, normalized);
|
|
7113
|
+
}
|
|
7114
|
+
//#endregion
|
|
7115
|
+
//#region src/client/files-tree/FilesBody.module.css.ts
|
|
7116
|
+
/**
|
|
7117
|
+
* Shim for the CSS-module virtual module `./FilesBody.module.css`.
|
|
7118
|
+
*
|
|
7119
|
+
* This toolchain resolves real files, not CSS-module virtual modules — so
|
|
7120
|
+
* the forked body imports this file instead (the one-line specifier delta
|
|
7121
|
+
* recorded in FORK.md). It exports the same `css.<name>` map, pointed at the
|
|
7122
|
+
* plugin-prefixed `mrfw-ft*` classes whose rules live in `styles.ts`; the
|
|
7123
|
+
* upstream `FilesBody.module.css` sits beside it verbatim as the diffable
|
|
7124
|
+
* source. Only the names the fork's JSX actually reads are mapped — the
|
|
7125
|
+
* header-only entries (`root`, `header`, `path`, `tool`, `status`,
|
|
7126
|
+
* `statusLine`, `titleIcon`) belong to the shell this fork drops.
|
|
7127
|
+
*/
|
|
7128
|
+
const css$1 = {
|
|
7129
|
+
body: "mrfw-ftBody",
|
|
7130
|
+
level: "mrfw-ftLevel",
|
|
7131
|
+
item: "mrfw-ftItem",
|
|
7132
|
+
row: "mrfw-ftRow",
|
|
7133
|
+
icon: "mrfw-ftIcon",
|
|
7134
|
+
fileIcon: "mrfw-ftFileIcon",
|
|
7135
|
+
name: "mrfw-ftName",
|
|
7136
|
+
other: "mrfw-ftOther",
|
|
7137
|
+
note: "mrfw-ftNote"
|
|
7138
|
+
};
|
|
7139
|
+
//#endregion
|
|
7140
|
+
//#region src/client/files-tree/FilesTree.tsx
|
|
7141
|
+
/**
|
|
7142
|
+
* The file tree's body, forked to a caller-supplied root: one additional
|
|
7143
|
+
* workspace root, listed one level at a time.
|
|
7144
|
+
*
|
|
7145
|
+
* Everything the tree keeps lives in its store, keyed by tab; everything it
|
|
7146
|
+
* asks for goes through its injected face. The component itself only decides
|
|
7147
|
+
* what to draw for each absolute path and what a click means: a directory
|
|
7148
|
+
* toggles, a file opens through the owner's open callback for a `file:`
|
|
7149
|
+
* viewer to claim, and anything else is shown but refuses to open.
|
|
7150
|
+
*
|
|
7151
|
+
* The rows below the exported shell are byte-identical to upstream's
|
|
7152
|
+
* `FilesBody.tsx`; the shell is the adaptation — upstream's version derives
|
|
7153
|
+
* the root from the session's `cwd`, takes its tab identity and lifetime from
|
|
7154
|
+
* `useTabInfo()`, opens resources through `tab.actions.openResource`, and
|
|
7155
|
+
* draws the header (PathLabel, auto-refresh, reload, actions slot) that this
|
|
7156
|
+
* plugin's switcher already owns. This fork takes all four as props and
|
|
7157
|
+
* draws only the body. See FORK.md.
|
|
7158
|
+
*/
|
|
7159
|
+
/** Natural, case-insensitive name order, so `file2` precedes `file10`. */
|
|
7160
|
+
const byName = new Intl.Collator(void 0, {
|
|
7161
|
+
numeric: true,
|
|
7162
|
+
sensitivity: "base"
|
|
7163
|
+
});
|
|
7164
|
+
/**
|
|
7165
|
+
* Order one level's entries for display: directories first, then everything
|
|
7166
|
+
* else, each group by name. The endpoint's order is a listing fact; this is the
|
|
7167
|
+
* reader's.
|
|
7168
|
+
* @param entries - the listing as the endpoint returned it.
|
|
7169
|
+
* @returns a new array, directories first, then by name within each group.
|
|
7170
|
+
*/
|
|
7171
|
+
function orderEntries(entries) {
|
|
7172
|
+
return [...entries].sort((left, right) => {
|
|
7173
|
+
const group = Number(right.type === "directory") - Number(left.type === "directory");
|
|
7174
|
+
return group !== 0 ? group : byName.compare(left.name, right.name);
|
|
7175
|
+
});
|
|
7176
|
+
}
|
|
7177
|
+
/**
|
|
7178
|
+
* Say why a directory could not be listed, in terms of the directory.
|
|
7179
|
+
* @param t - namespace-bound translate.
|
|
7180
|
+
* @param failure - the settled Remote failure.
|
|
7181
|
+
* @returns the line to show under the directory.
|
|
7182
|
+
*/
|
|
7183
|
+
function failureLine(t, failure) {
|
|
7184
|
+
switch (failure.code) {
|
|
7185
|
+
case "workspace-file/not-found": return t("error.notFound");
|
|
7186
|
+
case "workspace-file/outside-workspace": return t("error.outsideWorkspace");
|
|
7187
|
+
case "workspace-file/not-directory": return t("error.notDirectory");
|
|
7188
|
+
default: return t("error.unavailable", { message: failure.message });
|
|
7189
|
+
}
|
|
7190
|
+
}
|
|
7191
|
+
/** One entry's row, and its children when it is an expanded directory. */
|
|
7192
|
+
function Entry({ parent, entry, tree }) {
|
|
7193
|
+
const path = childPath(parent, entry.name);
|
|
7194
|
+
if (entry.type === "directory") {
|
|
7195
|
+
const expanded = tree.state.expanded.includes(path);
|
|
7196
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
7197
|
+
className: css$1.item,
|
|
7198
|
+
"data-files-entry": "directory",
|
|
7199
|
+
"data-files-path": path,
|
|
7200
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
7201
|
+
type: "button",
|
|
7202
|
+
className: css$1.row,
|
|
7203
|
+
"aria-expanded": expanded,
|
|
7204
|
+
onClick: () => {
|
|
7205
|
+
tree.onToggle(parent, path);
|
|
7206
|
+
},
|
|
7207
|
+
children: [expanded ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconFolderOpenRegular, { className: css$1.icon }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconFolderCloseRegular, { className: css$1.icon }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7208
|
+
className: css$1.name,
|
|
7209
|
+
children: entry.name
|
|
7210
|
+
})]
|
|
7211
|
+
}), expanded && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
7212
|
+
className: css$1.level,
|
|
7213
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Level, {
|
|
7214
|
+
path,
|
|
7215
|
+
tree
|
|
7216
|
+
})
|
|
7217
|
+
})]
|
|
7218
|
+
});
|
|
7219
|
+
}
|
|
7220
|
+
if (entry.type === "file") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
|
|
7221
|
+
className: css$1.item,
|
|
7222
|
+
"data-files-entry": "file",
|
|
7223
|
+
"data-files-path": path,
|
|
7224
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
7225
|
+
type: "button",
|
|
7226
|
+
className: css$1.row,
|
|
7227
|
+
onClick: () => {
|
|
7228
|
+
tree.onOpen(path);
|
|
7229
|
+
},
|
|
7230
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(FileTypeIcon, {
|
|
7231
|
+
kind: classifyFileType(entry.name),
|
|
7232
|
+
size: 16,
|
|
7233
|
+
className: css$1.fileIcon
|
|
7234
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7235
|
+
className: css$1.name,
|
|
7236
|
+
children: entry.name
|
|
7237
|
+
})]
|
|
7238
|
+
})
|
|
7239
|
+
});
|
|
7240
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
|
|
7241
|
+
className: css$1.item,
|
|
7242
|
+
"data-files-entry": "other",
|
|
7243
|
+
"data-files-path": path,
|
|
7244
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7245
|
+
className: clsx(css$1.row, css$1.other),
|
|
7246
|
+
"aria-disabled": "true",
|
|
7247
|
+
title: tree.t("entry.other"),
|
|
7248
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7249
|
+
className: css$1.name,
|
|
7250
|
+
children: entry.name
|
|
7251
|
+
})
|
|
7252
|
+
})
|
|
7253
|
+
});
|
|
7254
|
+
}
|
|
7255
|
+
/** One directory's rows: its state while listing, its entries once listed. */
|
|
7256
|
+
function Level({ path, tree }) {
|
|
7257
|
+
const { state, t } = tree;
|
|
7258
|
+
const level = state.levels[path];
|
|
7259
|
+
if (level === void 0 || level.kind === "loading") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
|
|
7260
|
+
className: css$1.note,
|
|
7261
|
+
"data-files-row": "loading",
|
|
7262
|
+
children: t("loading")
|
|
7263
|
+
});
|
|
7264
|
+
if (level.kind === "failed") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
|
|
7265
|
+
className: css$1.note,
|
|
7266
|
+
"data-files-row": "failed",
|
|
7267
|
+
"data-files-code": level.failure.code,
|
|
7268
|
+
children: failureLine(t, level.failure)
|
|
7269
|
+
});
|
|
7270
|
+
const entries = orderEntries(level.level.entries);
|
|
7271
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
7272
|
+
level.failure !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
|
|
7273
|
+
className: css$1.note,
|
|
7274
|
+
"data-files-row": "failed",
|
|
7275
|
+
children: failureLine(t, level.failure)
|
|
7276
|
+
}),
|
|
7277
|
+
entries.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
|
|
7278
|
+
className: css$1.note,
|
|
7279
|
+
"data-files-row": "empty",
|
|
7280
|
+
children: t("empty")
|
|
7281
|
+
}),
|
|
7282
|
+
entries.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Entry, {
|
|
7283
|
+
parent: path,
|
|
7284
|
+
entry,
|
|
7285
|
+
tree
|
|
7286
|
+
}, entry.name)),
|
|
7287
|
+
level.level.truncated && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
|
|
7288
|
+
className: css$1.note,
|
|
7289
|
+
"data-files-row": "truncated",
|
|
7290
|
+
children: t("truncated")
|
|
7291
|
+
})
|
|
7292
|
+
] });
|
|
7293
|
+
}
|
|
7294
|
+
/**
|
|
7295
|
+
* The additional-root tree's body: the selected root and whatever the reader
|
|
7296
|
+
* has opened under it — the upstream body's scroll, seed, and render logic
|
|
7297
|
+
* verbatim, with the root arriving as a prop instead of `useSessions(cwd)`.
|
|
7298
|
+
*/
|
|
7299
|
+
function FilesTree({ tabId, sessionId, root, signal, useStore, actions, start, toggle, onOpen, t }) {
|
|
7300
|
+
const state = useStore((store) => store.byTab[tabId]);
|
|
7301
|
+
const bodyRef = (0, react.useRef)(null);
|
|
7302
|
+
const scrollTopRef = (0, react.useRef)(0);
|
|
7303
|
+
const seeded = state !== void 0;
|
|
7304
|
+
(0, react.useLayoutEffect)(() => {
|
|
7305
|
+
const body = bodyRef.current;
|
|
7306
|
+
if (seeded && body !== null) {
|
|
7307
|
+
body.scrollTop = state.scrollTop;
|
|
7308
|
+
scrollTopRef.current = body.scrollTop;
|
|
7309
|
+
}
|
|
7310
|
+
}, [seeded]);
|
|
7311
|
+
(0, react.useEffect)(() => () => {
|
|
7312
|
+
if (seeded && !signal.aborted) actions.scrolled(tabId, scrollTopRef.current);
|
|
7313
|
+
}, [
|
|
7314
|
+
seeded,
|
|
7315
|
+
signal,
|
|
7316
|
+
tabId,
|
|
7317
|
+
actions
|
|
7318
|
+
]);
|
|
7319
|
+
(0, react.useEffect)(() => {
|
|
7320
|
+
if (state !== void 0 || signal.aborted) return;
|
|
7321
|
+
start(tabId, root, signal);
|
|
7322
|
+
}, [
|
|
7323
|
+
state,
|
|
7324
|
+
root,
|
|
7325
|
+
tabId,
|
|
7326
|
+
signal,
|
|
7327
|
+
start
|
|
7328
|
+
]);
|
|
7329
|
+
if (state === void 0) return null;
|
|
7330
|
+
const tree = {
|
|
7331
|
+
state,
|
|
7332
|
+
onToggle: (parent, path) => {
|
|
7333
|
+
toggle(tabId, parent, path, state.expanded, signal);
|
|
7334
|
+
},
|
|
7335
|
+
onOpen: (path) => {
|
|
7336
|
+
onOpen(fileAddressFor(sessionId, void 0, path));
|
|
7337
|
+
},
|
|
7338
|
+
t
|
|
7339
|
+
};
|
|
7340
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7341
|
+
ref: bodyRef,
|
|
7342
|
+
className: css$1.body,
|
|
7343
|
+
onScroll: (event) => {
|
|
7344
|
+
scrollTopRef.current = event.currentTarget.scrollTop;
|
|
7345
|
+
},
|
|
7346
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
7347
|
+
className: css$1.level,
|
|
7348
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Level, {
|
|
7349
|
+
path: state.root,
|
|
7350
|
+
tree
|
|
7351
|
+
})
|
|
7352
|
+
})
|
|
7353
|
+
});
|
|
7354
|
+
}
|
|
7355
|
+
//#endregion
|
|
7356
|
+
//#region src/client/files-tree/adapter.tsx
|
|
7357
|
+
/** The failure every lazy-remote path reports when the carrier is absent. */
|
|
7358
|
+
const unavailable = () => new RemoteError("gateway/internal", "the remote service is not available in this composition", {});
|
|
7359
|
+
/**
|
|
7360
|
+
* Fold an unexpected exception into the Remote failure a dead carrier would
|
|
7361
|
+
* produce. A generated Remote call cannot reject — but the lazy indirection
|
|
7362
|
+
* can (a `remote` whose `workspaceFiles` namespace is not mounted leaves
|
|
7363
|
+
* `.list` throwing a TypeError). The verbatim `load` awaits `list` through
|
|
7364
|
+
* `.then` without `.catch`, so an escaped rejection would die inside
|
|
7365
|
+
* `DirectoryNode.task` and pin the level at `loading` forever; folding it
|
|
7366
|
+
* here keeps the failure a settled Remote result — the visible failure row.
|
|
7367
|
+
*/
|
|
7368
|
+
const failureOf = (error) => typeof error === "object" && error !== null && "code" in error ? error : new RemoteError("gateway/internal", error instanceof Error ? error.message : String(error), {});
|
|
7369
|
+
/** An AsyncIterable whose first read throws the absent-carrier failure. */
|
|
7370
|
+
const deadStream = (error = unavailable()) => ({
|
|
7371
|
+
[Symbol.asyncIterator]: () => ({ next: () => Promise.reject(failureOf(error)) }),
|
|
7372
|
+
dispose: () => Promise.resolve()
|
|
7373
|
+
});
|
|
7374
|
+
/**
|
|
7375
|
+
* A `ClientRemote` stand-in that resolves the real service per call.
|
|
7376
|
+
*
|
|
7377
|
+
* The verbatim `createList`/`createWatch` hold one remote face for the
|
|
7378
|
+
* session's life; resolving lazily keeps the fork correct on compositions
|
|
7379
|
+
* that acquire `remote` after this plugin activates, and a composition that
|
|
7380
|
+
* never acquires it still answers every call — with the same failure kind a
|
|
7381
|
+
* dead carrier returns — instead of crashing the slot. Every method upholds
|
|
7382
|
+
* the Remote contract the face relies on: `list` resolves a `RemoteResult`
|
|
7383
|
+
* and never rejects; `changes`/`$stream` surface synchronous lookup failures
|
|
7384
|
+
* through the stream's own failure path.
|
|
7385
|
+
*/
|
|
7386
|
+
function lazyFilesRemote(ctx) {
|
|
7387
|
+
const get = (name) => ctx.get(name);
|
|
7388
|
+
const namespace = () => get("remote.workspaceFiles");
|
|
7389
|
+
return {
|
|
7390
|
+
workspaceFiles: {
|
|
7391
|
+
list: (sessionId, path, signal) => {
|
|
7392
|
+
try {
|
|
7393
|
+
const ns = namespace();
|
|
7394
|
+
if (ns === void 0) return Promise.resolve({
|
|
7395
|
+
ok: false,
|
|
7396
|
+
error: unavailable()
|
|
7397
|
+
});
|
|
7398
|
+
return Promise.resolve(ns.list(sessionId, path, signal)).catch((error) => ({
|
|
7399
|
+
ok: false,
|
|
7400
|
+
error: failureOf(error)
|
|
7401
|
+
}));
|
|
7402
|
+
} catch (error) {
|
|
7403
|
+
return Promise.resolve({
|
|
7404
|
+
ok: false,
|
|
7405
|
+
error: failureOf(error)
|
|
7406
|
+
});
|
|
7407
|
+
}
|
|
7408
|
+
},
|
|
7409
|
+
changes: (sessionId, path, signal) => {
|
|
7410
|
+
try {
|
|
7411
|
+
const ns = namespace();
|
|
7412
|
+
return ns === void 0 ? deadStream() : ns.changes(sessionId, path, signal);
|
|
7413
|
+
} catch (error) {
|
|
7414
|
+
return deadStream(error);
|
|
7415
|
+
}
|
|
7416
|
+
}
|
|
7417
|
+
},
|
|
7418
|
+
$stream: ((options) => {
|
|
7419
|
+
try {
|
|
7420
|
+
const remote = get("remote");
|
|
7421
|
+
return remote === void 0 ? deadStream() : remote.$stream(options);
|
|
7422
|
+
} catch (error) {
|
|
7423
|
+
return deadStream(error);
|
|
7424
|
+
}
|
|
7425
|
+
})
|
|
7426
|
+
};
|
|
7427
|
+
}
|
|
7428
|
+
/**
|
|
7429
|
+
* The registration's `store`/`inject` declarations, built once in `apply`.
|
|
7430
|
+
*
|
|
7431
|
+
* The inject face nests the verbatim files face under `face` and adds the
|
|
7432
|
+
* session-bound `openFile` — keeping `FilesInjected` untouched means the
|
|
7433
|
+
* fork's call sites and the sync diff stay upstream-shaped.
|
|
7434
|
+
* @param ctx - the client root context (the remote/sidebarRight lookups).
|
|
7435
|
+
* @returns the store handle and the inject factory for `register`.
|
|
7436
|
+
*/
|
|
7437
|
+
function createFilesTreeSeats(ctx) {
|
|
7438
|
+
const store = createFilesStore();
|
|
7439
|
+
const remote = lazyFilesRemote(ctx);
|
|
7440
|
+
const face = filesFace(createList(remote), createWatch(remote));
|
|
7441
|
+
const inject = (sessionId, actions) => ({
|
|
7442
|
+
face: face(sessionId, actions),
|
|
7443
|
+
openFile: (address) => {
|
|
7444
|
+
ctx.get("sidebarRight")?.openResourceIn?.(sessionId, address);
|
|
7445
|
+
}
|
|
7446
|
+
});
|
|
7447
|
+
return {
|
|
7448
|
+
store,
|
|
7449
|
+
inject
|
|
7450
|
+
};
|
|
7451
|
+
}
|
|
7452
|
+
/**
|
|
7453
|
+
* Assemble the props `RootSwitcher` portals into `FilesTree`, or `undefined`
|
|
7454
|
+
* when this composition cannot render the fork — the caller then keeps the
|
|
7455
|
+
* additional-root rows out of the menu entirely, which is the same absence
|
|
7456
|
+
* upstream produces when its own tree cannot draw.
|
|
7457
|
+
* @param props - the occupant's composed props (store, inject, session shares).
|
|
7458
|
+
* @param t - the `sidebarFiles`-bound translate.
|
|
7459
|
+
* @returns the stable surface, or `undefined` when the fork cannot mount.
|
|
7460
|
+
*/
|
|
7461
|
+
function createFilesTreeSurface(props, t) {
|
|
7462
|
+
if (!filesTreeSurfaceSupported()) return void 0;
|
|
7463
|
+
const { sessionId, useStore, actions, face, openFile } = props;
|
|
7464
|
+
if (face === void 0 || openFile === void 0 || useStore === void 0 || actions === void 0) return void 0;
|
|
7465
|
+
return {
|
|
7466
|
+
sessionId,
|
|
7467
|
+
useStore,
|
|
7468
|
+
actions,
|
|
7469
|
+
face,
|
|
7470
|
+
openFile,
|
|
7471
|
+
t
|
|
7472
|
+
};
|
|
7473
|
+
}
|
|
7474
|
+
//#endregion
|
|
7475
|
+
//#region node_modules/.pnpm/@deepseek-ai+dsh-host-open-in-app@0.2.1-rc.2_@deepseek-ai+cordis@4.0.5_@deepseek-ai+dsh_90dfa79a7f5aae0c8b5a66b41d5c318b/node_modules/@deepseek-ai/dsh-host-open-in-app/lib/types/shared.js
|
|
7476
|
+
var shared_exports = /* @__PURE__ */ __exportAll({
|
|
7477
|
+
OPEN_IN_APP_APPS_PATH: () => OPEN_IN_APP_APPS_PATH,
|
|
7478
|
+
OPEN_IN_APP_APPS_ROUTE: () => OPEN_IN_APP_APPS_ROUTE,
|
|
7479
|
+
OPEN_IN_APP_ICON_PREFIX_PATH: () => OPEN_IN_APP_ICON_PREFIX_PATH,
|
|
7480
|
+
OPEN_IN_APP_ICON_PREFIX_ROUTE: () => OPEN_IN_APP_ICON_PREFIX_ROUTE,
|
|
7481
|
+
OPEN_IN_APP_OPEN_PATH: () => OPEN_IN_APP_OPEN_PATH,
|
|
7482
|
+
OPEN_IN_APP_OPEN_ROUTE: () => OPEN_IN_APP_OPEN_ROUTE
|
|
7483
|
+
});
|
|
7484
|
+
/**
|
|
7485
|
+
* Route paths and wire payloads shared verbatim by the host routes and the
|
|
7486
|
+
* browser package (`@deepseek-ai/dsh-client-ui-open-in-app`), published as
|
|
7487
|
+
* the `./shared` subpath. Browser-safe: constants and types only. Each route
|
|
7488
|
+
* carries the absolute pathname the Host registers beside the
|
|
7489
|
+
* document-relative form the browser addresses; see
|
|
7490
|
+
* .agents/notes/implemented/architecture/2026-09-14-web-document-relative-app-routes.md.
|
|
7491
|
+
*/
|
|
7492
|
+
/** GET route path serving the probed application ids. */
|
|
7493
|
+
const OPEN_IN_APP_APPS_PATH = "/open-in-app/apps";
|
|
7494
|
+
/** Browser-relative form of {@link OPEN_IN_APP_APPS_PATH}. */
|
|
7495
|
+
const OPEN_IN_APP_APPS_ROUTE = OPEN_IN_APP_APPS_PATH.slice(1);
|
|
7496
|
+
/** GET prefix path serving one PNG bundle icon per application id. */
|
|
7497
|
+
const OPEN_IN_APP_ICON_PREFIX_PATH = "/open-in-app/icon";
|
|
7498
|
+
/** Browser-relative form of {@link OPEN_IN_APP_ICON_PREFIX_PATH}. */
|
|
7499
|
+
const OPEN_IN_APP_ICON_PREFIX_ROUTE = OPEN_IN_APP_ICON_PREFIX_PATH.slice(1);
|
|
7500
|
+
/** POST route path launching one application on one workspace directory. */
|
|
7501
|
+
const OPEN_IN_APP_OPEN_PATH = "/open-in-app/open";
|
|
7502
|
+
/** Browser-relative form of {@link OPEN_IN_APP_OPEN_PATH}. */
|
|
7503
|
+
const OPEN_IN_APP_OPEN_ROUTE = OPEN_IN_APP_OPEN_PATH.slice(1);
|
|
7504
|
+
//#endregion
|
|
7505
|
+
//#region src/compat/open-in-app-routes.ts
|
|
7506
|
+
/**
|
|
7507
|
+
* Compatibility adapter for the route constants of
|
|
7508
|
+
* `@deepseek-ai/dsh-host-open-in-app/shared`.
|
|
7509
|
+
*
|
|
7510
|
+
* ```text
|
|
7511
|
+
* 0.1.5-rc.2 / 0.1.6 OPEN_IN_APP_ICON_PREFIX = '/open-in-app/icon' (absolute pathname)
|
|
7512
|
+
* 0.1.7-alpha.1 + OPEN_IN_APP_ICON_PREFIX_ROUTE = 'open-in-app/icon' (document-relative)
|
|
7513
|
+
* ```
|
|
7514
|
+
*
|
|
7515
|
+
* `0.1.7-alpha.1` split every shared route constant into a `*_PATH` spelling
|
|
7516
|
+
* the Host registers and a `*_ROUTE` spelling the browser addresses, retiring
|
|
7517
|
+
* the old names. The fork's `iconUrl` needs the browser-facing form — which a
|
|
7518
|
+
* pixel-era release spells without the `_ROUTE` suffix, so a named import of
|
|
7519
|
+
* the new name does not even BUILD against those manifests (MISSING_EXPORT at
|
|
7520
|
+
* bundle time, not a runtime undefined). The lookup is by export name, not by
|
|
7521
|
+
* release string.
|
|
7522
|
+
*
|
|
7523
|
+
* @module @dsh-electron/dsh-plugin-multi-root-workspace/compat/open-in-app-routes
|
|
7524
|
+
*/
|
|
7525
|
+
/**
|
|
7526
|
+
* The installed shared module, addressed by export name.
|
|
7527
|
+
*
|
|
7528
|
+
* Named imports cannot spell both eras: each name is absent from the other
|
|
7529
|
+
* era's declaration, so the compiler is told once, here.
|
|
7530
|
+
*/
|
|
7531
|
+
const installed = shared_exports;
|
|
7532
|
+
/**
|
|
7533
|
+
* Pick the first export name this release actually provides.
|
|
7534
|
+
* @param module - the shared module to read.
|
|
7535
|
+
* @param names - candidate export names, preferred first.
|
|
7536
|
+
* @returns the route string.
|
|
7537
|
+
* @throws {Error} when none of the names is exported. That is a compatibility
|
|
7538
|
+
* break to adapt here, not a missing optional peer.
|
|
7539
|
+
*/
|
|
7540
|
+
function selectSharedRoute(module, names) {
|
|
7541
|
+
for (const name of names) {
|
|
7542
|
+
const found = module[name];
|
|
7543
|
+
if (found !== void 0) return found;
|
|
7544
|
+
}
|
|
7545
|
+
throw new Error(`multi-root workspace: @deepseek-ai/dsh-host-open-in-app/shared exports none of ${names.join(", ")}; the open-in-app fork cannot address its icon route. Adapt src/compat/open-in-app-routes.ts to the installed release.`);
|
|
7546
|
+
}
|
|
7547
|
+
/**
|
|
7548
|
+
* The browser-facing icon-prefix route, whichever spelling this release
|
|
7549
|
+
* exports. Resolved once at module load: the fork's adapter only reaches
|
|
7550
|
+
* `iconUrl` on runtimes whose glyph gate already passed, and on the pixel era
|
|
7551
|
+
* the absolute form is exactly what upstream's own client fetched.
|
|
7552
|
+
*/
|
|
7553
|
+
const OPEN_IN_APP_ICON_URL_PREFIX = selectSharedRoute(installed, ["OPEN_IN_APP_ICON_PREFIX_ROUTE", "OPEN_IN_APP_ICON_PREFIX"]);
|
|
7554
|
+
//#endregion
|
|
7555
|
+
//#region src/client/open-in-app/applications.ts
|
|
7556
|
+
/** Label keys for applications supported by this client. */
|
|
7557
|
+
const APP_LABEL_KEY = {
|
|
7558
|
+
finder: "app.finder",
|
|
7559
|
+
explorer: "app.explorer",
|
|
7560
|
+
filemanager: "app.filemanager",
|
|
7561
|
+
cursor: "app.cursor",
|
|
7562
|
+
vscode: "app.vscode",
|
|
7563
|
+
vscodeinsiders: "app.vscodeinsiders",
|
|
7564
|
+
windsurf: "app.windsurf",
|
|
7565
|
+
zed: "app.zed",
|
|
7566
|
+
sublimetext: "app.sublimetext",
|
|
7567
|
+
xcode: "app.xcode",
|
|
7568
|
+
androidstudio: "app.androidstudio",
|
|
7569
|
+
intellij: "app.intellij",
|
|
7570
|
+
pycharm: "app.pycharm",
|
|
7571
|
+
webstorm: "app.webstorm",
|
|
7572
|
+
phpstorm: "app.phpstorm",
|
|
7573
|
+
goland: "app.goland",
|
|
7574
|
+
rider: "app.rider",
|
|
7575
|
+
rustrover: "app.rustrover",
|
|
7576
|
+
fork: "app.fork",
|
|
7577
|
+
sourcetree: "app.sourcetree",
|
|
7578
|
+
github: "app.github",
|
|
7579
|
+
tower: "app.tower",
|
|
7580
|
+
gitkraken: "app.gitkraken",
|
|
7581
|
+
smartgit: "app.smartgit",
|
|
7582
|
+
sublimemerge: "app.sublimemerge",
|
|
7583
|
+
ghostty: "app.ghostty",
|
|
7584
|
+
warp: "app.warp",
|
|
7585
|
+
iterm: "app.iterm",
|
|
7586
|
+
kitty: "app.kitty",
|
|
7587
|
+
terminal: "app.terminal",
|
|
7588
|
+
windowsterminal: "app.windowsterminal",
|
|
7589
|
+
gitbash: "app.gitbash",
|
|
7590
|
+
gnometerminal: "app.gnometerminal",
|
|
7591
|
+
konsole: "app.konsole"
|
|
7592
|
+
};
|
|
7593
|
+
//#endregion
|
|
7594
|
+
//#region src/client/open-in-app/controller.ts
|
|
7595
|
+
/** Browser availability/choice state and the launch carrier for the split button. */
|
|
7596
|
+
/**
|
|
7597
|
+
* Owns the once-per-page availability read, the persisted last choice, and
|
|
7598
|
+
* the launch POST. Availability and choice publish through uSES-safe sources
|
|
7599
|
+
* so every Session header shares one truth.
|
|
7600
|
+
*/
|
|
7601
|
+
var OpenInAppController = class {
|
|
7602
|
+
fetcher;
|
|
7603
|
+
/** Installed app ids in host menu order; null until the host answered. */
|
|
7604
|
+
apps = (0, _deepseek_ai_dsh_client_store.createSnapshotStore)(null);
|
|
7605
|
+
/** Last chosen app id, or empty before the first choice, shared across sessions and browser restarts. */
|
|
7606
|
+
choice = (0, _deepseek_ai_dsh_client_store.createSnapshotStore)("", { persist: { name: "dsh.open-in-app.choice" } });
|
|
7607
|
+
/** Current launch, shared by pointer and keyboard gestures. */
|
|
7608
|
+
operation = (0, _deepseek_ai_dsh_client_store.createSnapshotStore)({
|
|
7609
|
+
phase: "idle",
|
|
7610
|
+
path: null
|
|
7611
|
+
});
|
|
7612
|
+
/**
|
|
7613
|
+
* Resolve the remembered nameable installed application, with the button's first-app fallback.
|
|
7614
|
+
* @returns the installed app id, or undefined while unavailable.
|
|
7615
|
+
*/
|
|
7616
|
+
currentApp() {
|
|
7617
|
+
const apps = (this.apps.getSnapshot() ?? []).filter((id) => APP_LABEL_KEY[id] !== void 0);
|
|
7618
|
+
const choice = this.choice.getSnapshot();
|
|
7619
|
+
return apps.includes(choice) ? choice : apps[0];
|
|
7620
|
+
}
|
|
7621
|
+
loading;
|
|
7622
|
+
/**
|
|
7623
|
+
* @param fetcher - HTTP carrier for the apps read and the launch POST.
|
|
7624
|
+
*/
|
|
7625
|
+
constructor(fetcher = (input, init) => fetch(input, init)) {
|
|
7626
|
+
this.fetcher = fetcher;
|
|
7627
|
+
}
|
|
7628
|
+
/**
|
|
7629
|
+
* Read availability once per controller life; concurrent calls share the read.
|
|
7630
|
+
* A failed read publishes an empty list, which renders no button at all.
|
|
7631
|
+
* @returns after availability is published.
|
|
7632
|
+
*/
|
|
7633
|
+
load() {
|
|
7634
|
+
this.loading ??= this.run();
|
|
7635
|
+
return this.loading;
|
|
7636
|
+
}
|
|
7637
|
+
/**
|
|
7638
|
+
* Remember one picked app id.
|
|
7639
|
+
* @param appId - catalog id from the availability list.
|
|
7640
|
+
*/
|
|
7641
|
+
choose(appId) {
|
|
7642
|
+
if (this.operation.getSnapshot().phase !== "busy") this.choice.set(appId);
|
|
7643
|
+
}
|
|
7644
|
+
/**
|
|
7645
|
+
* Launch one installed app on a workspace directory.
|
|
7646
|
+
* @param appId - catalog id from the availability list.
|
|
7647
|
+
* @param path - the session's absolute workspace directory.
|
|
7648
|
+
* Concurrent gestures are ignored until the current Host request settles.
|
|
7649
|
+
* @returns after the host acknowledged the launch; rejects on any failure.
|
|
7650
|
+
*/
|
|
7651
|
+
async launch(appId, path) {
|
|
7652
|
+
if (this.operation.getSnapshot().phase === "busy") return;
|
|
7653
|
+
this.operation.set({
|
|
7654
|
+
phase: "busy",
|
|
7655
|
+
path
|
|
7656
|
+
});
|
|
7657
|
+
const body = {
|
|
7658
|
+
app: appId,
|
|
7659
|
+
path
|
|
7660
|
+
};
|
|
7661
|
+
try {
|
|
7662
|
+
const response = await this.fetcher(OPEN_IN_APP_OPEN_ROUTE, {
|
|
7663
|
+
method: "POST",
|
|
7664
|
+
headers: { "content-type": "application/json" },
|
|
7665
|
+
body: JSON.stringify(body)
|
|
7666
|
+
});
|
|
7667
|
+
if (!response.ok) throw new Error(`open failed: HTTP ${String(response.status)}`);
|
|
7668
|
+
this.operation.set({
|
|
7669
|
+
phase: "idle",
|
|
7670
|
+
path
|
|
7671
|
+
});
|
|
7672
|
+
} catch (error) {
|
|
7673
|
+
this.operation.set({
|
|
7674
|
+
phase: "error",
|
|
7675
|
+
path
|
|
7676
|
+
});
|
|
7677
|
+
throw error;
|
|
7678
|
+
}
|
|
7679
|
+
}
|
|
7680
|
+
async run() {
|
|
7681
|
+
let apps = [];
|
|
7682
|
+
try {
|
|
7683
|
+
const response = await this.fetcher(OPEN_IN_APP_APPS_ROUTE, { headers: { accept: "application/json" } });
|
|
7684
|
+
if (response.ok) {
|
|
7685
|
+
const payload = await response.json();
|
|
7686
|
+
if (Array.isArray(payload.apps)) apps = payload.apps.filter((id) => typeof id === "string");
|
|
7687
|
+
}
|
|
7688
|
+
} catch {}
|
|
7689
|
+
this.apps.set(apps);
|
|
7690
|
+
}
|
|
7691
|
+
};
|
|
7692
|
+
//#endregion
|
|
7693
|
+
//#region src/client/open-in-app/locales.ts
|
|
7694
|
+
/** Dictionary namespace owned by upstream's plugin (verbatim). */
|
|
7695
|
+
const NS = "open-in-app";
|
|
7696
|
+
//#endregion
|
|
7697
|
+
//#region src/client/open-in-app/open-failure-toast.tsx
|
|
7698
|
+
/** Per-control failure banner for path gestures. */
|
|
7699
|
+
/**
|
|
7700
|
+
* Transient failure banner owned by the control that initiated the gesture,
|
|
7701
|
+
* so one failed request announces once, from the control the user pressed.
|
|
7702
|
+
* @returns `toast` owned by the control and `show` to announce one
|
|
7703
|
+
* failure with resolved copy; announcing again replays the banner.
|
|
7704
|
+
*/
|
|
7705
|
+
function useOpenFailureToast() {
|
|
7706
|
+
const seq = (0, react.useRef)(0);
|
|
7707
|
+
const [banner, setBanner] = (0, react.useState)(null);
|
|
7708
|
+
const dismiss = (0, react.useCallback)(() => {
|
|
7709
|
+
setBanner(null);
|
|
7710
|
+
}, []);
|
|
7711
|
+
return {
|
|
7712
|
+
toast: banner === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
|
|
7713
|
+
text: banner.text,
|
|
7714
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutlineRegular, {}),
|
|
7715
|
+
onDone: dismiss
|
|
7716
|
+
}, banner.seq),
|
|
7717
|
+
show: (text) => {
|
|
7718
|
+
seq.current += 1;
|
|
7719
|
+
setBanner({
|
|
7720
|
+
seq: seq.current,
|
|
7721
|
+
text
|
|
7722
|
+
});
|
|
7723
|
+
}
|
|
7724
|
+
};
|
|
7725
|
+
}
|
|
7726
|
+
//#endregion
|
|
7727
|
+
//#region src/client/open-in-app/OpenTargetButton.module.css.ts
|
|
7728
|
+
/**
|
|
7729
|
+
* Fork-facing shim for upstream `OpenTargetButton.module.css`.
|
|
7730
|
+
*
|
|
7731
|
+
* This plugin's bundle has no CSS-module pipeline: the `<style>` element it
|
|
7732
|
+
* installs carries one flat sheet (`src/client/styles.ts`). The upstream
|
|
7733
|
+
* component reads `css.<name>` so the forked file needs one importable
|
|
7734
|
+
* module exporting that same key map — this file, reached through the
|
|
7735
|
+
* verbatim `./OpenTargetButton.module.css.ts` specifier (the one sanctioned
|
|
7736
|
+
* fork edit: the specifier gains the `.ts` this toolchain resolves
|
|
7737
|
+
* everywhere — tsc, tsdown, vitest). The specifier text is spelled without
|
|
7738
|
+
* its `import` clause on purpose: the packed-artifact check scans every
|
|
7739
|
+
* bundle comment too, and quoting the full statement would read as a
|
|
7740
|
+
* relative import the tarball does not carry.
|
|
7741
|
+
*
|
|
7742
|
+
* Each upstream local class maps to an `mrfw-open*` name so the copied
|
|
7743
|
+
* rules cannot collide with host DOM; the rules themselves live in
|
|
7744
|
+
* styles.ts verbatim apart from that rename. `data-open-target` and the
|
|
7745
|
+
* other data attributes are literals in the component and stay byte-exact.
|
|
7746
|
+
* See FORK.md.
|
|
7747
|
+
*/
|
|
7748
|
+
const css = {
|
|
7749
|
+
menuAnchor: "mrfw-openMenuAnchor",
|
|
7750
|
+
split: "mrfw-openSplit",
|
|
7751
|
+
main: "mrfw-openMain",
|
|
7752
|
+
chevron: "mrfw-openChevron",
|
|
7753
|
+
appIcon: "mrfw-openAppIcon",
|
|
7754
|
+
skeleton: "mrfw-openSkeleton"
|
|
7755
|
+
};
|
|
7756
|
+
//#endregion
|
|
7757
|
+
//#region src/client/open-in-app/OpenTargetButton.tsx
|
|
7758
|
+
/** Shared file and directory opener: one default action, application menu, and per-gesture feedback. */
|
|
7759
|
+
/**
|
|
7760
|
+
* Serialize gestures and announce their failures through the initiating control's toast.
|
|
7761
|
+
* @param execute - target adapter that returns the failure to announce, or null.
|
|
7762
|
+
* @param t - localized control copy.
|
|
7763
|
+
* @returns the pending state, feedback, and guarded action callback.
|
|
7764
|
+
*/
|
|
7765
|
+
function useOpenTargetGesture(execute, t) {
|
|
7766
|
+
const [pending, setPending] = (0, react.useState)(false);
|
|
7767
|
+
const inFlight = (0, react.useRef)(false);
|
|
7768
|
+
const { toast, show } = useOpenFailureToast();
|
|
7769
|
+
return {
|
|
7770
|
+
pending,
|
|
7771
|
+
toast,
|
|
7772
|
+
act: (operation) => {
|
|
7773
|
+
if (inFlight.current) return;
|
|
7774
|
+
inFlight.current = true;
|
|
7775
|
+
setPending(true);
|
|
7776
|
+
execute(operation).then((failure) => {
|
|
7777
|
+
if (failure !== null) show(t(`path.${failure}`));
|
|
7778
|
+
}).finally(() => {
|
|
7779
|
+
inFlight.current = false;
|
|
7780
|
+
setPending(false);
|
|
7781
|
+
});
|
|
7782
|
+
}
|
|
7783
|
+
};
|
|
7784
|
+
}
|
|
7785
|
+
/** One application image with a per-image fallback, shared by main and menu buttons. */
|
|
7786
|
+
function ApplicationIcon({ source, size = 14 }) {
|
|
7787
|
+
const [failed, setFailed] = (0, react.useState)(false);
|
|
7788
|
+
return source === null || failed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconRightUpOutlineRegular, { size }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
|
|
7789
|
+
src: source,
|
|
7790
|
+
width: size,
|
|
7791
|
+
height: size,
|
|
7792
|
+
className: css.appIcon,
|
|
7793
|
+
alt: "",
|
|
7794
|
+
draggable: false,
|
|
7795
|
+
onError: () => {
|
|
7796
|
+
setFailed(true);
|
|
7797
|
+
}
|
|
7798
|
+
});
|
|
7799
|
+
}
|
|
7800
|
+
/**
|
|
7801
|
+
* Render identical split buttons for files and directories. File reveal always
|
|
7802
|
+
* stays last; it is the default only when no application is registered.
|
|
7803
|
+
* @param props - target applications, default selection, and operations.
|
|
7804
|
+
* @returns the control and its transient failure feedback.
|
|
7805
|
+
*/
|
|
7806
|
+
function OpenTargetButton(props) {
|
|
7807
|
+
const { applications, defaultId, kind, t } = props;
|
|
7808
|
+
const [menuOpen, setMenuOpen] = (0, react.useState)(false);
|
|
7809
|
+
const { pending, toast, act } = useOpenTargetGesture(props.execute, t);
|
|
7810
|
+
const preferred = kind === "file" ? applications.find((app) => app.id === defaultId) ?? applications[0] : applications.find((app) => app.id === defaultId);
|
|
7811
|
+
const disabled = pending || props.busy === true || props.loading === true;
|
|
7812
|
+
const hasMenu = props.loading === true || props.failed || applications.length + (kind === "file" ? 1 : 0) > 1;
|
|
7813
|
+
const revealDefault = kind === "file" && preferred === void 0 && props.loading !== true;
|
|
7814
|
+
const primaryLabel = preferred === void 0 ? t("path.reveal") : t("open.title", { app: preferred.name });
|
|
7815
|
+
const run = (operation) => {
|
|
7816
|
+
setMenuOpen(false);
|
|
7817
|
+
act(operation);
|
|
7818
|
+
};
|
|
7819
|
+
const primary = () => {
|
|
7820
|
+
if (revealDefault) {
|
|
7821
|
+
run({ kind: "reveal" });
|
|
7822
|
+
return;
|
|
7823
|
+
}
|
|
7824
|
+
if (preferred !== void 0 && preferred.id !== defaultId) {
|
|
7825
|
+
run({
|
|
7826
|
+
kind: "application",
|
|
7827
|
+
id: preferred.id
|
|
7828
|
+
});
|
|
7829
|
+
return;
|
|
7830
|
+
}
|
|
7831
|
+
run({ kind: "default" });
|
|
7832
|
+
};
|
|
7833
|
+
const icon = props.loading === true && preferred === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7834
|
+
className: css.skeleton,
|
|
7835
|
+
"data-open-target-skeleton": true,
|
|
7836
|
+
"aria-hidden": "true",
|
|
7837
|
+
style: {
|
|
7838
|
+
width: props.prominent ? 18 : 13,
|
|
7839
|
+
height: props.prominent ? 18 : 13
|
|
7840
|
+
}
|
|
7841
|
+
}) : revealDefault ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderOpenOutlineRegular, { size: props.prominent ? 18 : 13 }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ApplicationIcon, {
|
|
7842
|
+
source: preferred?.icon ?? null,
|
|
7843
|
+
size: props.prominent ? 18 : 13
|
|
7844
|
+
}, preferred?.icon);
|
|
7845
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
|
|
7846
|
+
className: css.menuAnchor,
|
|
7847
|
+
open: menuOpen && !disabled && hasMenu,
|
|
7848
|
+
autoFocus: true,
|
|
7849
|
+
portal: true,
|
|
7850
|
+
dense: true,
|
|
7851
|
+
align: "end",
|
|
7852
|
+
onClose: () => {
|
|
7853
|
+
setMenuOpen(false);
|
|
7854
|
+
},
|
|
7855
|
+
items: [...applications.map((app) => ({
|
|
7856
|
+
id: `app:${app.id}`,
|
|
7857
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ApplicationIcon, { source: app.icon }, app.icon),
|
|
7858
|
+
label: app.id === preferred?.id ? t("path.appDefault", { app: app.name }) : app.name
|
|
7859
|
+
})), ...props.failed ? [{
|
|
7860
|
+
id: "unavailable",
|
|
7861
|
+
label: t("path.appsError"),
|
|
7862
|
+
disabled: true
|
|
7863
|
+
}] : []],
|
|
7864
|
+
footer: kind === "file" ? [{
|
|
7865
|
+
id: "reveal",
|
|
7866
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderOpenOutlineRegular, {}),
|
|
7867
|
+
label: revealDefault ? t("path.appDefault", { app: t("path.reveal") }) : t("path.reveal")
|
|
7868
|
+
}] : [],
|
|
7869
|
+
onSelect: (id) => {
|
|
7870
|
+
run(id === "reveal" ? { kind: "reveal" } : {
|
|
7871
|
+
kind: "application",
|
|
7872
|
+
id: id.slice(4)
|
|
7873
|
+
});
|
|
7874
|
+
},
|
|
7875
|
+
anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7876
|
+
className: css.split,
|
|
7877
|
+
"data-open-target": kind,
|
|
7878
|
+
"data-size": props.prominent ? "large" : "compact",
|
|
7879
|
+
"data-open-path": kind === "file" && !props.prominent ? "" : void 0,
|
|
7880
|
+
"data-state": disabled ? "busy" : "idle",
|
|
7881
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
|
|
7882
|
+
portal: true,
|
|
7883
|
+
label: primaryLabel,
|
|
7884
|
+
shortcutKeys: props.shortcut?.keys,
|
|
7885
|
+
side: "bottom",
|
|
7886
|
+
delayMs: 500,
|
|
7887
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
7888
|
+
type: "button",
|
|
7889
|
+
className: css.main,
|
|
7890
|
+
disabled,
|
|
7891
|
+
"aria-label": props.prominent ? void 0 : primaryLabel,
|
|
7892
|
+
"aria-keyshortcuts": props.shortcut?.aria,
|
|
7893
|
+
"data-open-path-open": kind === "file" && !props.prominent ? "" : void 0,
|
|
7894
|
+
"data-open-path-unpreviewable": props.prominent ? "" : void 0,
|
|
7895
|
+
onClick: primary,
|
|
7896
|
+
children: [icon, props.prominent && (revealDefault ? t("path.reveal") : t("path.open"))]
|
|
7897
|
+
})
|
|
7898
|
+
}), hasMenu && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
7899
|
+
type: "button",
|
|
7900
|
+
className: css.chevron,
|
|
7901
|
+
disabled,
|
|
7902
|
+
"aria-haspopup": "menu",
|
|
7903
|
+
"aria-expanded": menuOpen && !disabled,
|
|
7904
|
+
"aria-label": t("path.more"),
|
|
7905
|
+
"data-open-path-more": kind === "file" && !props.prominent ? "" : void 0,
|
|
7906
|
+
onClick: () => {
|
|
7907
|
+
if (!menuOpen) props.refresh?.();
|
|
7908
|
+
setMenuOpen((value) => !value);
|
|
7909
|
+
},
|
|
7910
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { size: props.prominent ? 14 : 10 })
|
|
7911
|
+
})]
|
|
7912
|
+
})
|
|
7913
|
+
}), toast] });
|
|
7914
|
+
}
|
|
7915
|
+
//#endregion
|
|
7916
|
+
//#region src/client/open-in-app/OpenInAppAction.tsx
|
|
7917
|
+
/**
|
|
7918
|
+
* Adapt the installed directory catalog to the shared opening control.
|
|
7919
|
+
* @param props - displayed directory, installed catalog, and launch operations.
|
|
7920
|
+
* @returns the shared control, or null without an eligible application.
|
|
7921
|
+
*/
|
|
7922
|
+
function OpenInAppAction(props) {
|
|
7923
|
+
const { absolutePath, useOpenInAppApps, useOpenInAppChoice, t } = props;
|
|
7924
|
+
const available = useOpenInAppApps((apps) => apps);
|
|
7925
|
+
const choice = useOpenInAppChoice((id) => id);
|
|
7926
|
+
const operation = props.useOpenInAppLaunch((value) => value);
|
|
7927
|
+
const shortcut = props.useShortcuts((rows) => rows.find((row) => row.id === "workspace.openLocal"));
|
|
7928
|
+
const apps = (available ?? []).flatMap((id) => {
|
|
7929
|
+
const key = APP_LABEL_KEY[id];
|
|
7930
|
+
return key === void 0 ? [] : [{
|
|
7931
|
+
id,
|
|
7932
|
+
name: t(key),
|
|
7933
|
+
icon: props.iconUrl(id)
|
|
7934
|
+
}];
|
|
7935
|
+
});
|
|
7936
|
+
const preferred = apps.find((app) => app.id === choice) ?? apps[0];
|
|
7937
|
+
if (preferred === void 0) return null;
|
|
7938
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OpenTargetButton, {
|
|
7939
|
+
kind: "directory",
|
|
7940
|
+
applications: apps,
|
|
7941
|
+
defaultId: preferred.id,
|
|
7942
|
+
failed: false,
|
|
7943
|
+
t,
|
|
7944
|
+
busy: operation.phase === "busy",
|
|
7945
|
+
shortcut,
|
|
7946
|
+
execute: async (operation) => {
|
|
7947
|
+
const id = operation.kind === "application" ? operation.id : preferred.id;
|
|
7948
|
+
try {
|
|
7949
|
+
await props.launch(id, absolutePath);
|
|
7950
|
+
} catch (_error) {
|
|
7951
|
+
return "openError";
|
|
7952
|
+
}
|
|
7953
|
+
if (operation.kind === "application") props.choose(id);
|
|
7954
|
+
return null;
|
|
7955
|
+
}
|
|
7956
|
+
}, absolutePath);
|
|
7957
|
+
}
|
|
7958
|
+
//#endregion
|
|
7959
|
+
//#region src/client/open-in-app/adapter.tsx
|
|
7960
|
+
/**
|
|
7961
|
+
* The plugin-owned wiring around the verbatim open-in-app fork.
|
|
7962
|
+
*
|
|
7963
|
+
* Upstream builds the same props in its `apply` (`directoryInjected`): one
|
|
7964
|
+
* controller per page, its SnapshotStores bound by the slot renderer into
|
|
7965
|
+
* `use*` props, and a `t` bound to the `open-in-app` namespace. This module
|
|
7966
|
+
* reproduces exactly that — minus slot machinery the fork never sees —
|
|
7967
|
+
* plus the two degradations only a fork needs:
|
|
7968
|
+
*
|
|
7969
|
+
* - the glyph set the fork spells is the `*Regular` family (0.1.7+); a
|
|
7970
|
+
* pixel-name runtime (0.1.5/0.1.6) leaves the adapter undefined, so the
|
|
7971
|
+
* header simply keeps the native button, mirroring upstream's own
|
|
7972
|
+
* "no apps → no button" absence;
|
|
7973
|
+
* - `ctx.locale.bind('open-in-app')` consumes upstream's registered
|
|
7974
|
+
* dictionary verbatim — same strings, upstream's updates included;
|
|
7975
|
+
* - the icon route comes through `compat/open-in-app-routes`: the `*_ROUTE`
|
|
7976
|
+
* spelling is another 0.1.7+ name, so the constant is probed by export
|
|
7977
|
+
* name rather than imported (a pixel-era manifest lacks it entirely —
|
|
7978
|
+
* a named import would fail the bundle's own build).
|
|
7979
|
+
*
|
|
7980
|
+
* Everything here is ours; nothing in this file needs upstream sync.
|
|
7981
|
+
*
|
|
7982
|
+
* @module @dsh-electron/dsh-plugin-multi-root-workspace/client/open-in-app/adapter
|
|
7983
|
+
*/
|
|
7984
|
+
/**
|
|
7985
|
+
* Turn one observable into the renderer-bound `use*` prop shape:
|
|
7986
|
+
* `useSyncExternalStore` under the hood, exactly like the slot renderer's
|
|
7987
|
+
* own binding. Built once per store so `subscribe` stays referentially
|
|
7988
|
+
* stable across renders.
|
|
7989
|
+
* @param source - the observable to bind.
|
|
7990
|
+
* @returns the bound selector hook.
|
|
7991
|
+
*/
|
|
7992
|
+
function bindSnapshot(source) {
|
|
7993
|
+
const subscribe = (listener) => source.subscribe(listener);
|
|
7994
|
+
const getSnapshot = () => source.getSnapshot();
|
|
7995
|
+
return function useBound(selector) {
|
|
7996
|
+
return (0, react.useSyncExternalStore)(subscribe, () => selector(getSnapshot()));
|
|
7997
|
+
};
|
|
7998
|
+
}
|
|
7999
|
+
/** Glyphs the forked components spell verbatim — all `*Regular`, all probed. */
|
|
8000
|
+
const REQUIRED_GLYPHS = [
|
|
8001
|
+
"IconChevronDownOutlineRegular",
|
|
8002
|
+
"IconRightUpOutlineRegular",
|
|
8003
|
+
"IconFolderOpenOutlineRegular",
|
|
8004
|
+
"IconWarningOutlineRegular"
|
|
8005
|
+
];
|
|
8006
|
+
/** The empty catalog stand-in when the composition carries no shortcuts service. */
|
|
8007
|
+
const NO_SHORTCUTS = {
|
|
8008
|
+
getSnapshot: () => [],
|
|
8009
|
+
subscribe: () => () => {}
|
|
8010
|
+
};
|
|
8011
|
+
/**
|
|
8012
|
+
* Build the open-in-app props upstream hands its own registration.
|
|
8013
|
+
* @param deps - the namespace-bound translate for `open-in-app`, and the
|
|
8014
|
+
* composition's shortcuts catalog when it exists.
|
|
8015
|
+
* @returns the adapter, or `undefined` when this runtime cannot render the
|
|
8016
|
+
* fork (pixel-era icon names — the forked files would crash on missing
|
|
8017
|
+
* exports, so absence mirrors upstream's own no-button path).
|
|
8018
|
+
*/
|
|
8019
|
+
function createOpenInAppAdapter(deps) {
|
|
8020
|
+
if (!REQUIRED_GLYPHS.every((name) => maybeIcon([name]) !== void 0)) return void 0;
|
|
8021
|
+
const controller = new OpenInAppController();
|
|
8022
|
+
controller.load();
|
|
8023
|
+
return {
|
|
8024
|
+
useOpenInAppApps: bindSnapshot(controller.apps),
|
|
8025
|
+
useOpenInAppChoice: bindSnapshot(controller.choice),
|
|
8026
|
+
useOpenInAppLaunch: bindSnapshot(controller.operation),
|
|
8027
|
+
useShortcuts: bindSnapshot(deps.shortcuts ?? NO_SHORTCUTS),
|
|
8028
|
+
launch: (appId, path) => controller.launch(appId, path),
|
|
8029
|
+
choose: (appId) => {
|
|
8030
|
+
controller.choose(appId);
|
|
8031
|
+
},
|
|
8032
|
+
iconUrl: (appId) => `${OPEN_IN_APP_ICON_URL_PREFIX}/${appId}`,
|
|
8033
|
+
t: deps.t
|
|
8034
|
+
};
|
|
8035
|
+
}
|
|
8036
|
+
//#endregion
|
|
8037
|
+
//#region src/client/WorkspaceFoldersAction.tsx
|
|
8038
|
+
/**
|
|
8039
|
+
* The Workspace Folders panel: one sidebar footer action that opens a dialog
|
|
8040
|
+
* listing this session's workspace root and the additional roots registered for
|
|
8041
|
+
* it, with the actions the milestone requires — Add (through the composed
|
|
8042
|
+
* directory picker), Remove, Alias, Copy Path, Reveal, and reordering.
|
|
8043
|
+
*
|
|
8044
|
+
* Why a footer action and its own dialog, instead of a first-class panel: the
|
|
8045
|
+
* slots that can host a full panel (`sidebar.panellist` + the keyed `main`
|
|
8046
|
+
* panel) exist only in the pinned 0.1.5 surface, while `sidebar.footer.action`
|
|
8047
|
+
* exists in both supported runtimes — and `sidebar.workspaces` is a single slot
|
|
8048
|
+
* already owned by the workspace browser, so it cannot be extended in place.
|
|
8049
|
+
*
|
|
8050
|
+
* Styling is the host's, not this file's: the elements carry `mrfw-*` classes
|
|
8051
|
+
* resolved by the stylesheet the client entry injects (`styles.ts`), and every
|
|
8052
|
+
* color in that sheet is a host `--dsw-*` token, so the panel inherits the
|
|
8053
|
+
* native look — sidebar trigger, modal card, capsule buttons — in both themes
|
|
8054
|
+
* without naming a single color here (see ADR-0006).
|
|
8055
|
+
*
|
|
8056
|
+
* The dialog itself is also exported: the Files-tab root switcher opens the
|
|
8057
|
+
* same manager through its trailing menu item instead of carrying a second
|
|
8058
|
+
* dialog.
|
|
8059
|
+
*
|
|
8060
|
+
* @module @dsh-electron/dsh-plugin-multi-root-workspace/client/WorkspaceFoldersAction
|
|
8061
|
+
*/
|
|
8062
|
+
const VARIANT_CLASS = {
|
|
8063
|
+
ghost: "mrfw-btnGhost",
|
|
8064
|
+
outline: "mrfw-btnOutline",
|
|
8065
|
+
primary: "mrfw-btnPrimary",
|
|
8066
|
+
danger: "mrfw-btnDanger"
|
|
5964
8067
|
};
|
|
5965
8068
|
/** One small capsule button matching the panel's own styling. */
|
|
5966
8069
|
function Action(props) {
|
|
@@ -6003,6 +8106,27 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6003
8106
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M4 4l8 8M12 4l-8 8" })
|
|
6004
8107
|
});
|
|
6005
8108
|
}
|
|
8109
|
+
/**
|
|
8110
|
+
* One path that copies itself: text that paints like the surrounding copy
|
|
8111
|
+
* and reveals a trailing icon on hover — clicking the text or the icon
|
|
8112
|
+
* copies the same canonical path. The accessible name swaps to "copied"
|
|
8113
|
+
* for the feedback window, the icon button's old recipe. Shared by the
|
|
8114
|
+
* primary row, the current-directory line, and every additional root.
|
|
8115
|
+
*/
|
|
8116
|
+
function PathCopy(props) {
|
|
8117
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
8118
|
+
type: "button",
|
|
8119
|
+
className: "mrfw-rootPath mrfw-pathCopy",
|
|
8120
|
+
title: props.path,
|
|
8121
|
+
"aria-label": props.copied ? props.t("panel.copied") : props.t("panel.copyPath"),
|
|
8122
|
+
onClick: props.onCopy,
|
|
8123
|
+
children: [props.path, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
8124
|
+
className: "mrfw-pathCopyIcon",
|
|
8125
|
+
"aria-hidden": "true",
|
|
8126
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconCopy, { size: 14 })
|
|
8127
|
+
})]
|
|
8128
|
+
});
|
|
8129
|
+
}
|
|
6006
8130
|
/** The locale key for one reported root state. */
|
|
6007
8131
|
function stateKey(state) {
|
|
6008
8132
|
switch (state) {
|
|
@@ -6012,6 +8136,25 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6012
8136
|
case "invalid": return "state.invalid";
|
|
6013
8137
|
}
|
|
6014
8138
|
}
|
|
8139
|
+
/** The locale key for one registration's access level. */
|
|
8140
|
+
function accessKey(access) {
|
|
8141
|
+
switch (access) {
|
|
8142
|
+
case "inherit": return "access.inherit";
|
|
8143
|
+
case "read-only": return "access.readOnly";
|
|
8144
|
+
case "read-write": return "access.readWrite";
|
|
8145
|
+
}
|
|
8146
|
+
}
|
|
8147
|
+
/**
|
|
8148
|
+
* Whether moving FROM `from` TO `to` could widen the agent's write authority
|
|
8149
|
+
* under some session mode. The three levels are not a total order — `inherit`
|
|
8150
|
+
* is weaker than `read-write` under a read-only session but never weaker
|
|
8151
|
+
* under the others — so the check is spelled out per pair rather than
|
|
8152
|
+
* computed from a rank. Only a widening change asks for confirmation.
|
|
8153
|
+
*/
|
|
8154
|
+
function widensAccess(from, to) {
|
|
8155
|
+
if (from === "read-only") return to !== "read-only";
|
|
8156
|
+
return from === "inherit" && to === "read-write" || from === "read-write" && to === "inherit";
|
|
8157
|
+
}
|
|
6015
8158
|
/** The final segment of an absolute path, on either platform separator. */
|
|
6016
8159
|
function basename(path) {
|
|
6017
8160
|
const segments = path.split(/[\\/]/).filter((segment) => segment !== "");
|
|
@@ -6029,11 +8172,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6029
8172
|
const [open, setOpen] = (0, react.useState)(false);
|
|
6030
8173
|
const rail = props.wide === false;
|
|
6031
8174
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
6032
|
-
className: rail ? "mrfw-triggerRow mrfw-railRow" : "mrfw-triggerRow",
|
|
8175
|
+
className: props.compact ? "mrfw-fileLauncher" : rail ? "mrfw-triggerRow mrfw-railRow" : "mrfw-triggerRow",
|
|
6033
8176
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
6034
8177
|
type: "button",
|
|
6035
|
-
className: rail ? "mrfw-trigger mrfw-triggerRail" : "mrfw-trigger",
|
|
8178
|
+
className: props.compact ? "mrfw-btn mrfw-btnGhost mrfw-iconBtn" : rail ? "mrfw-trigger mrfw-triggerRail" : "mrfw-trigger",
|
|
6036
8179
|
title: props.t("action.title"),
|
|
8180
|
+
"aria-label": props.compact === true ? props.t("action.title") : void 0,
|
|
6037
8181
|
"aria-haspopup": "dialog",
|
|
6038
8182
|
"aria-expanded": open,
|
|
6039
8183
|
onClick: () => {
|
|
@@ -6042,8 +8186,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6042
8186
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6043
8187
|
className: "mrfw-triggerIcon",
|
|
6044
8188
|
"aria-hidden": "true",
|
|
6045
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
6046
|
-
}), rail ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
8189
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconSettings, { size: rail ? 18 : 16 })
|
|
8190
|
+
}), rail || props.compact === true ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6047
8191
|
className: "mrfw-triggerLabel",
|
|
6048
8192
|
children: props.t("action.label")
|
|
6049
8193
|
})]
|
|
@@ -6055,6 +8199,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6055
8199
|
}
|
|
6056
8200
|
}) : null] });
|
|
6057
8201
|
}
|
|
8202
|
+
/** The exact row reference the browsing endpoints and mutations carry. */
|
|
6058
8203
|
function entryOf(root) {
|
|
6059
8204
|
return {
|
|
6060
8205
|
ordinal: root.ordinal,
|
|
@@ -6084,6 +8229,18 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6084
8229
|
noSession: false
|
|
6085
8230
|
});
|
|
6086
8231
|
const [manualPath, setManualPath] = (0, react.useState)("");
|
|
8232
|
+
/** The access the Add controls register new roots with; `inherit` is the default. */
|
|
8233
|
+
const [addAccess, setAddAccess] = (0, react.useState)("inherit");
|
|
8234
|
+
/**
|
|
8235
|
+
* Whether the operator actually picked a level. The select shows its default
|
|
8236
|
+
* before they do, and sending that default would be an explicit value the
|
|
8237
|
+
* operator never named: on a revive the registry upgrades a read-only record
|
|
8238
|
+
* to it. Only a chosen level may travel in the request.
|
|
8239
|
+
*/
|
|
8240
|
+
const [accessChosen, setAccessChosen] = (0, react.useState)(false);
|
|
8241
|
+
const [accessMenuOpen, setAccessMenuOpen] = (0, react.useState)(false);
|
|
8242
|
+
/** The widening access change waiting on the confirmation's red button. */
|
|
8243
|
+
const [upgradeFor, setUpgradeFor] = (0, react.useState)(void 0);
|
|
6087
8244
|
const [aliasFor, setAliasFor] = (0, react.useState)(void 0);
|
|
6088
8245
|
const [aliasDraft, setAliasDraft] = (0, react.useState)("");
|
|
6089
8246
|
const [copiedId, setCopiedId] = (0, react.useState)(void 0);
|
|
@@ -6091,9 +8248,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6091
8248
|
const dialogRef = (0, react.useRef)(null);
|
|
6092
8249
|
const requestEpoch = (0, react.useRef)(0);
|
|
6093
8250
|
const mounted = (0, react.useRef)(true);
|
|
6094
|
-
(0, react.useEffect)(() =>
|
|
6095
|
-
mounted.current =
|
|
6096
|
-
|
|
8251
|
+
(0, react.useEffect)(() => {
|
|
8252
|
+
mounted.current = true;
|
|
8253
|
+
return () => {
|
|
8254
|
+
mounted.current = false;
|
|
8255
|
+
requestEpoch.current += 1;
|
|
8256
|
+
};
|
|
6097
8257
|
}, []);
|
|
6098
8258
|
const requestBase = (0, react.useCallback)(() => {
|
|
6099
8259
|
const current = props.sessionId?.();
|
|
@@ -6104,15 +8264,16 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6104
8264
|
}, []);
|
|
6105
8265
|
const onDialogKeyDown = (event) => {
|
|
6106
8266
|
if (event.key === "Escape") {
|
|
6107
|
-
if (menuFor !== void 0) return;
|
|
8267
|
+
if (menuFor !== void 0 || accessMenuOpen || upgradeFor !== void 0) return;
|
|
6108
8268
|
event.stopPropagation();
|
|
6109
8269
|
props.onClose();
|
|
6110
8270
|
return;
|
|
6111
8271
|
}
|
|
6112
8272
|
if (event.key !== "Tab") return;
|
|
8273
|
+
if (upgradeFor !== void 0) return;
|
|
6113
8274
|
const dialog = dialogRef.current;
|
|
6114
8275
|
if (dialog === null) return;
|
|
6115
|
-
const focusable = Array.from(dialog.querySelectorAll("button, input, [href], [tabindex]:not([tabindex=\"-1\"])")).filter((element) => !element.hasAttribute("disabled"));
|
|
8276
|
+
const focusable = Array.from(dialog.querySelectorAll("button, input, select, [href], [tabindex]:not([tabindex=\"-1\"])")).filter((element) => !element.hasAttribute("disabled"));
|
|
6116
8277
|
if (focusable.length === 0) {
|
|
6117
8278
|
event.preventDefault();
|
|
6118
8279
|
return;
|
|
@@ -6156,7 +8317,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6156
8317
|
}));
|
|
6157
8318
|
try {
|
|
6158
8319
|
const view = await panel.call("list", base);
|
|
6159
|
-
if (!mounted.current || epoch !== requestEpoch.current) return;
|
|
8320
|
+
if (!mounted.current || epoch !== requestEpoch.current || props.sessionId?.() !== base.sessionId) return;
|
|
6160
8321
|
setState({
|
|
6161
8322
|
view,
|
|
6162
8323
|
error: void 0,
|
|
@@ -6164,7 +8325,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6164
8325
|
noSession: false
|
|
6165
8326
|
});
|
|
6166
8327
|
} catch (error) {
|
|
6167
|
-
if (!mounted.current || epoch !== requestEpoch.current) return;
|
|
8328
|
+
if (!mounted.current || epoch !== requestEpoch.current || props.sessionId?.() !== base.sessionId) return;
|
|
6168
8329
|
setState({
|
|
6169
8330
|
view: void 0,
|
|
6170
8331
|
error: asPanelError(error),
|
|
@@ -6176,6 +8337,28 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6176
8337
|
(0, react.useEffect)(() => {
|
|
6177
8338
|
refresh();
|
|
6178
8339
|
}, [refresh]);
|
|
8340
|
+
(0, react.useEffect)(() => {
|
|
8341
|
+
let previous = props.sessionId?.();
|
|
8342
|
+
return props.subscribeSession?.(() => {
|
|
8343
|
+
const current = props.sessionId?.();
|
|
8344
|
+
if (current === previous) return;
|
|
8345
|
+
previous = current;
|
|
8346
|
+
requestEpoch.current += 1;
|
|
8347
|
+
setState({
|
|
8348
|
+
view: void 0,
|
|
8349
|
+
error: void 0,
|
|
8350
|
+
busy: false,
|
|
8351
|
+
noSession: current === void 0
|
|
8352
|
+
});
|
|
8353
|
+
setMenuFor(void 0);
|
|
8354
|
+
setAliasFor(void 0);
|
|
8355
|
+
refresh();
|
|
8356
|
+
});
|
|
8357
|
+
}, [
|
|
8358
|
+
props.subscribeSession,
|
|
8359
|
+
props.sessionId,
|
|
8360
|
+
refresh
|
|
8361
|
+
]);
|
|
6179
8362
|
/**
|
|
6180
8363
|
* Submit one change to the host and adopt the view it answers with.
|
|
6181
8364
|
* @returns whether the host accepted it (the caller decides whether to clear
|
|
@@ -6204,7 +8387,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6204
8387
|
...base,
|
|
6205
8388
|
...payload
|
|
6206
8389
|
});
|
|
6207
|
-
if (!mounted.current || epoch !== requestEpoch.current) return false;
|
|
8390
|
+
if (!mounted.current || epoch !== requestEpoch.current || props.sessionId?.() !== base.sessionId) return false;
|
|
6208
8391
|
setState({
|
|
6209
8392
|
view,
|
|
6210
8393
|
error: void 0,
|
|
@@ -6213,7 +8396,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6213
8396
|
});
|
|
6214
8397
|
return true;
|
|
6215
8398
|
} catch (error) {
|
|
6216
|
-
if (!mounted.current || epoch !== requestEpoch.current) return false;
|
|
8399
|
+
if (!mounted.current || epoch !== requestEpoch.current || props.sessionId?.() !== base.sessionId) return false;
|
|
6217
8400
|
setState((previous) => ({
|
|
6218
8401
|
...previous,
|
|
6219
8402
|
error: asPanelError(error),
|
|
@@ -6241,8 +8424,16 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6241
8424
|
return;
|
|
6242
8425
|
}
|
|
6243
8426
|
if (picked === null || picked.trim() === "") return;
|
|
6244
|
-
await mutate("add", {
|
|
6245
|
-
|
|
8427
|
+
await mutate("add", {
|
|
8428
|
+
path: picked,
|
|
8429
|
+
...accessChosen ? { access: addAccess } : {}
|
|
8430
|
+
});
|
|
8431
|
+
}, [
|
|
8432
|
+
mutate,
|
|
8433
|
+
props,
|
|
8434
|
+
addAccess,
|
|
8435
|
+
accessChosen
|
|
8436
|
+
]);
|
|
6246
8437
|
/**
|
|
6247
8438
|
* Add the path in the manual field — the one the operator typed. The picker
|
|
6248
8439
|
* plays no part here: this is the entry point the confirm button and the
|
|
@@ -6251,8 +8442,27 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6251
8442
|
const addManualPath = (0, react.useCallback)(async () => {
|
|
6252
8443
|
const path = manualPath.trim();
|
|
6253
8444
|
if (path === "") return;
|
|
6254
|
-
if (await mutate("add", {
|
|
6255
|
-
|
|
8445
|
+
if (await mutate("add", {
|
|
8446
|
+
path,
|
|
8447
|
+
...accessChosen ? { access: addAccess } : {}
|
|
8448
|
+
})) setManualPath("");
|
|
8449
|
+
}, [
|
|
8450
|
+
manualPath,
|
|
8451
|
+
mutate,
|
|
8452
|
+
addAccess,
|
|
8453
|
+
accessChosen
|
|
8454
|
+
]);
|
|
8455
|
+
/**
|
|
8456
|
+
* Re-register one withheld or unusable root in place. The request carries
|
|
8457
|
+
* the stored path and deliberately NO access: `registry.add` treats the call
|
|
8458
|
+
* as a revive — it keeps the record's access, alias, and identity instead of
|
|
8459
|
+
* creating a new registration — which is also why the root limit must not
|
|
8460
|
+
* gate this button: a revive consumes no slot, and it is the panel's only
|
|
8461
|
+
* repair route for a redirected entry.
|
|
8462
|
+
*/
|
|
8463
|
+
const reRegister = (0, react.useCallback)(async (root) => {
|
|
8464
|
+
await mutate("add", { path: root.path });
|
|
8465
|
+
}, [mutate]);
|
|
6256
8466
|
/** Reveal one root: the answer is the revealed path, not a new view. */
|
|
6257
8467
|
const reveal = (0, react.useCallback)(async (root) => {
|
|
6258
8468
|
if (panel === void 0) return;
|
|
@@ -6277,14 +8487,14 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6277
8487
|
...base,
|
|
6278
8488
|
entry: entryOf(root)
|
|
6279
8489
|
});
|
|
6280
|
-
if (!mounted.current || epoch !== requestEpoch.current) return;
|
|
8490
|
+
if (!mounted.current || epoch !== requestEpoch.current || props.sessionId?.() !== base.sessionId) return;
|
|
6281
8491
|
setState((previous) => ({
|
|
6282
8492
|
...previous,
|
|
6283
8493
|
error: void 0,
|
|
6284
8494
|
busy: false
|
|
6285
8495
|
}));
|
|
6286
8496
|
} catch (error) {
|
|
6287
|
-
if (!mounted.current || epoch !== requestEpoch.current) return;
|
|
8497
|
+
if (!mounted.current || epoch !== requestEpoch.current || props.sessionId?.() !== base.sessionId) return;
|
|
6288
8498
|
setState((previous) => ({
|
|
6289
8499
|
...previous,
|
|
6290
8500
|
error: asPanelError(error),
|
|
@@ -6292,10 +8502,9 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6292
8502
|
}));
|
|
6293
8503
|
}
|
|
6294
8504
|
}, [panel, requestBase]);
|
|
6295
|
-
const copyPath = (0, react.useCallback)(async (
|
|
8505
|
+
const copyPath = (0, react.useCallback)(async (key, path) => {
|
|
6296
8506
|
try {
|
|
6297
|
-
await navigator.clipboard.writeText(
|
|
6298
|
-
const key = rowKey(root);
|
|
8507
|
+
await navigator.clipboard.writeText(path);
|
|
6299
8508
|
setCopiedId(key);
|
|
6300
8509
|
setState((previous) => ({
|
|
6301
8510
|
...previous,
|
|
@@ -6319,7 +8528,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6319
8528
|
});
|
|
6320
8529
|
}, [mutate]);
|
|
6321
8530
|
const roots = state.view?.roots ?? [];
|
|
6322
|
-
|
|
8531
|
+
const atLimit = state.view !== void 0 && state.view.count >= state.view.limit;
|
|
8532
|
+
const IconAccessTrigger = addAccess === "read-only" ? IconAccessReadOnly : addAccess === "read-write" ? IconAccessReadWrite : IconAccessInherit;
|
|
8533
|
+
const primaryPath = state.view?.primaryRoot;
|
|
8534
|
+
const cwdPath = state.view?.effectiveCwd;
|
|
8535
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6323
8536
|
className: "mrfw-overlay",
|
|
6324
8537
|
role: "presentation",
|
|
6325
8538
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
@@ -6359,6 +8572,21 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6359
8572
|
className: "mrfw-description",
|
|
6360
8573
|
children: t("panel.subtitle")
|
|
6361
8574
|
}),
|
|
8575
|
+
state.view?.mode === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
8576
|
+
className: "mrfw-note",
|
|
8577
|
+
children: [
|
|
8578
|
+
t("panel.mode"),
|
|
8579
|
+
" ",
|
|
8580
|
+
t(`mode.${state.view.mode}`)
|
|
8581
|
+
]
|
|
8582
|
+
}),
|
|
8583
|
+
state.view?.toolAccessInvalid ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
8584
|
+
className: "mrfw-note",
|
|
8585
|
+
children: t("panel.invalidCap")
|
|
8586
|
+
}) : (state.view?.cappedTools?.length ?? 0) > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
8587
|
+
className: "mrfw-note",
|
|
8588
|
+
children: t("panel.toolCaps", { tools: state.view.cappedTools.join(", ") })
|
|
8589
|
+
}) : null,
|
|
6362
8590
|
state.error === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
6363
8591
|
role: "alert",
|
|
6364
8592
|
className: "mrfw-alert",
|
|
@@ -6386,14 +8614,44 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6386
8614
|
className: "mrfw-rootText",
|
|
6387
8615
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6388
8616
|
className: "mrfw-rootName",
|
|
6389
|
-
children:
|
|
6390
|
-
|
|
8617
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
8618
|
+
className: "mrfw-rootNameText",
|
|
8619
|
+
children: state.view === void 0 ? "…" : state.view.primaryName ?? basename(state.view.primaryRoot)
|
|
8620
|
+
})
|
|
8621
|
+
}), primaryPath === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6391
8622
|
className: "mrfw-rootPath",
|
|
6392
|
-
children:
|
|
8623
|
+
children: "…"
|
|
8624
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PathCopy, {
|
|
8625
|
+
path: primaryPath,
|
|
8626
|
+
copied: copiedId === "primary",
|
|
8627
|
+
onCopy: () => {
|
|
8628
|
+
copyPath("primary", primaryPath);
|
|
8629
|
+
},
|
|
8630
|
+
t
|
|
6393
8631
|
})]
|
|
6394
8632
|
})
|
|
6395
8633
|
})]
|
|
6396
8634
|
}),
|
|
8635
|
+
cwdPath !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
8636
|
+
className: "mrfw-section",
|
|
8637
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
8638
|
+
className: "mrfw-sectionTitleRow",
|
|
8639
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
8640
|
+
className: "mrfw-sectionTitle",
|
|
8641
|
+
children: t("panel.cwd")
|
|
8642
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
8643
|
+
className: "mrfw-sectionNote",
|
|
8644
|
+
children: t("panel.cwdNote")
|
|
8645
|
+
})]
|
|
8646
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PathCopy, {
|
|
8647
|
+
path: cwdPath,
|
|
8648
|
+
copied: copiedId === "cwd",
|
|
8649
|
+
onCopy: () => {
|
|
8650
|
+
copyPath("cwd", cwdPath);
|
|
8651
|
+
},
|
|
8652
|
+
t
|
|
8653
|
+
})]
|
|
8654
|
+
}),
|
|
6397
8655
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
6398
8656
|
className: "mrfw-section",
|
|
6399
8657
|
children: [
|
|
@@ -6423,12 +8681,33 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6423
8681
|
children: [
|
|
6424
8682
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6425
8683
|
className: "mrfw-rootText",
|
|
6426
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.
|
|
8684
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
6427
8685
|
className: "mrfw-rootName",
|
|
6428
|
-
children:
|
|
6429
|
-
|
|
6430
|
-
|
|
6431
|
-
|
|
8686
|
+
children: [
|
|
8687
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
8688
|
+
className: "mrfw-rootNameText",
|
|
8689
|
+
children: displayNameOf(root)
|
|
8690
|
+
}),
|
|
8691
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
8692
|
+
className: "mrfw-accessBadge",
|
|
8693
|
+
children: t(accessKey(root.access))
|
|
8694
|
+
}),
|
|
8695
|
+
root.effectiveAccess === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
8696
|
+
className: "mrfw-effectiveAccess",
|
|
8697
|
+
children: [
|
|
8698
|
+
t("panel.effectiveAccess"),
|
|
8699
|
+
" ",
|
|
8700
|
+
t(`effective.${root.effectiveAccess}`)
|
|
8701
|
+
]
|
|
8702
|
+
})
|
|
8703
|
+
]
|
|
8704
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PathCopy, {
|
|
8705
|
+
path: root.path,
|
|
8706
|
+
copied: copiedId === key,
|
|
8707
|
+
onCopy: () => {
|
|
8708
|
+
copyPath(key, root.path);
|
|
8709
|
+
},
|
|
8710
|
+
t
|
|
6432
8711
|
})]
|
|
6433
8712
|
}),
|
|
6434
8713
|
root.state === "available" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
@@ -6436,12 +8715,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6436
8715
|
className: "mrfw-stateWarn",
|
|
6437
8716
|
children: t(stateKey(root.state))
|
|
6438
8717
|
}),
|
|
6439
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconButton, {
|
|
8718
|
+
root.state === "available" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconButton, {
|
|
8719
|
+
disabled: state.busy,
|
|
6440
8720
|
onClick: () => {
|
|
6441
|
-
|
|
8721
|
+
reRegister(root);
|
|
6442
8722
|
},
|
|
6443
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
6444
|
-
label:
|
|
8723
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconRefresh, {}),
|
|
8724
|
+
label: t("panel.reRegister")
|
|
6445
8725
|
}),
|
|
6446
8726
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconButton, {
|
|
6447
8727
|
disabled: state.busy,
|
|
@@ -6472,23 +8752,55 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6472
8752
|
onClose: () => {
|
|
6473
8753
|
setMenuFor(void 0);
|
|
6474
8754
|
},
|
|
6475
|
-
items: [
|
|
6476
|
-
|
|
6477
|
-
|
|
6478
|
-
|
|
6479
|
-
|
|
6480
|
-
|
|
6481
|
-
|
|
6482
|
-
|
|
6483
|
-
|
|
6484
|
-
|
|
6485
|
-
|
|
8755
|
+
items: [
|
|
8756
|
+
{
|
|
8757
|
+
id: "rename",
|
|
8758
|
+
label: t("panel.rename"),
|
|
8759
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconEdit, {})
|
|
8760
|
+
},
|
|
8761
|
+
{
|
|
8762
|
+
id: "access:inherit",
|
|
8763
|
+
label: t("panel.accessInherit"),
|
|
8764
|
+
disabled: state.busy || root.access === "inherit"
|
|
8765
|
+
},
|
|
8766
|
+
{
|
|
8767
|
+
id: "access:read-only",
|
|
8768
|
+
label: t("panel.accessReadOnly"),
|
|
8769
|
+
disabled: state.busy || root.access === "read-only"
|
|
8770
|
+
},
|
|
8771
|
+
{
|
|
8772
|
+
id: "access:read-write",
|
|
8773
|
+
label: t("panel.accessReadWrite"),
|
|
8774
|
+
disabled: state.busy || root.access === "read-write"
|
|
8775
|
+
},
|
|
8776
|
+
{
|
|
8777
|
+
id: "remove",
|
|
8778
|
+
label: t("panel.remove"),
|
|
8779
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconTrash, {}),
|
|
8780
|
+
danger: true,
|
|
8781
|
+
disabled: state.busy
|
|
8782
|
+
}
|
|
8783
|
+
],
|
|
6486
8784
|
onSelect: (id) => {
|
|
6487
8785
|
setMenuFor(void 0);
|
|
6488
8786
|
if (id === "rename") {
|
|
6489
8787
|
setAliasFor(key);
|
|
6490
8788
|
setAliasDraft(root.alias ?? "");
|
|
6491
8789
|
}
|
|
8790
|
+
if (id.startsWith("access:")) {
|
|
8791
|
+
const access = id.slice(7);
|
|
8792
|
+
if (widensAccess(root.access, access)) {
|
|
8793
|
+
setUpgradeFor({
|
|
8794
|
+
root,
|
|
8795
|
+
access
|
|
8796
|
+
});
|
|
8797
|
+
return;
|
|
8798
|
+
}
|
|
8799
|
+
mutate("setAccess", {
|
|
8800
|
+
entry: entryOf(root),
|
|
8801
|
+
access
|
|
8802
|
+
});
|
|
8803
|
+
}
|
|
6492
8804
|
if (id === "remove") mutate("remove", { entry: entryOf(root) });
|
|
6493
8805
|
},
|
|
6494
8806
|
portal: true,
|
|
@@ -6544,31 +8856,92 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6544
8856
|
children: [
|
|
6545
8857
|
state.noSession || props.pickDirectory === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconButton, {
|
|
6546
8858
|
variant: "outline",
|
|
6547
|
-
disabled: state.busy,
|
|
8859
|
+
disabled: state.busy || atLimit,
|
|
6548
8860
|
onClick: () => {
|
|
6549
8861
|
addViaPicker();
|
|
6550
8862
|
},
|
|
6551
8863
|
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconPlus, {}),
|
|
6552
|
-
label: t("panel.add")
|
|
8864
|
+
label: atLimit ? t("panel.limitReached") : t("panel.add")
|
|
6553
8865
|
}),
|
|
6554
|
-
state.noSession ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
6555
|
-
|
|
6556
|
-
|
|
6557
|
-
|
|
6558
|
-
|
|
6559
|
-
|
|
6560
|
-
|
|
6561
|
-
|
|
6562
|
-
|
|
6563
|
-
|
|
6564
|
-
|
|
6565
|
-
|
|
6566
|
-
|
|
6567
|
-
|
|
6568
|
-
|
|
6569
|
-
|
|
6570
|
-
|
|
6571
|
-
|
|
8866
|
+
state.noSession ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
8867
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
8868
|
+
className: "mrfw-input",
|
|
8869
|
+
value: manualPath,
|
|
8870
|
+
placeholder: t("panel.addManual"),
|
|
8871
|
+
disabled: atLimit,
|
|
8872
|
+
onChange: (event) => {
|
|
8873
|
+
setManualPath(event.target.value);
|
|
8874
|
+
},
|
|
8875
|
+
onKeyDown: (event) => {
|
|
8876
|
+
if (event.key === "Enter") addManualPath();
|
|
8877
|
+
}
|
|
8878
|
+
}),
|
|
8879
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
|
|
8880
|
+
open: accessMenuOpen,
|
|
8881
|
+
onClose: () => {
|
|
8882
|
+
setAccessMenuOpen(false);
|
|
8883
|
+
},
|
|
8884
|
+
className: "mrfw-accessSelect",
|
|
8885
|
+
items: [
|
|
8886
|
+
{
|
|
8887
|
+
id: "inherit",
|
|
8888
|
+
label: t("access.inherit"),
|
|
8889
|
+
icon: IconAccessInherit === void 0 ? void 0 : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconAccessInherit, {})
|
|
8890
|
+
},
|
|
8891
|
+
{
|
|
8892
|
+
id: "read-write",
|
|
8893
|
+
label: t("access.readWrite"),
|
|
8894
|
+
icon: IconAccessReadWrite === void 0 ? void 0 : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconAccessReadWrite, {})
|
|
8895
|
+
},
|
|
8896
|
+
{
|
|
8897
|
+
id: "read-only",
|
|
8898
|
+
label: t("access.readOnly"),
|
|
8899
|
+
icon: IconAccessReadOnly === void 0 ? void 0 : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconAccessReadOnly, {})
|
|
8900
|
+
}
|
|
8901
|
+
],
|
|
8902
|
+
selectedId: addAccess,
|
|
8903
|
+
onSelect: (id) => {
|
|
8904
|
+
setAccessMenuOpen(false);
|
|
8905
|
+
if (id === "inherit" || id === "read-write" || id === "read-only") {
|
|
8906
|
+
setAccessChosen(true);
|
|
8907
|
+
setAddAccess(id);
|
|
8908
|
+
}
|
|
8909
|
+
},
|
|
8910
|
+
portal: true,
|
|
8911
|
+
side: "top",
|
|
8912
|
+
align: "end",
|
|
8913
|
+
closeOnPointerLeave: true,
|
|
8914
|
+
anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
8915
|
+
type: "button",
|
|
8916
|
+
className: "mrfw-btn mrfw-btnOutline mrfw-accessTrigger",
|
|
8917
|
+
title: t("panel.accessLabel"),
|
|
8918
|
+
"aria-label": t("panel.accessLabel"),
|
|
8919
|
+
"aria-haspopup": "menu",
|
|
8920
|
+
"aria-expanded": accessMenuOpen,
|
|
8921
|
+
disabled: state.busy || atLimit,
|
|
8922
|
+
onClick: () => {
|
|
8923
|
+
setAccessMenuOpen((open) => !open);
|
|
8924
|
+
},
|
|
8925
|
+
children: [
|
|
8926
|
+
IconAccessTrigger === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconAccessTrigger, {}),
|
|
8927
|
+
t(accessKey(addAccess)),
|
|
8928
|
+
accessMenuOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconChevronUp, { size: 14 }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconChevronDown, { size: 14 })
|
|
8929
|
+
]
|
|
8930
|
+
})
|
|
8931
|
+
}),
|
|
8932
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Action, {
|
|
8933
|
+
onClick: () => {
|
|
8934
|
+
addManualPath();
|
|
8935
|
+
},
|
|
8936
|
+
disabled: state.busy || atLimit || manualPath.trim() === "",
|
|
8937
|
+
variant: "primary",
|
|
8938
|
+
children: t("panel.addConfirm")
|
|
8939
|
+
})
|
|
8940
|
+
] }),
|
|
8941
|
+
atLimit ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
8942
|
+
className: "mrfw-note",
|
|
8943
|
+
children: t("panel.limitReached")
|
|
8944
|
+
}) : null,
|
|
6572
8945
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Action, {
|
|
6573
8946
|
onClick: () => {
|
|
6574
8947
|
refresh();
|
|
@@ -6581,7 +8954,33 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6581
8954
|
})]
|
|
6582
8955
|
})]
|
|
6583
8956
|
})]
|
|
6584
|
-
})
|
|
8957
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
8958
|
+
open: upgradeFor !== void 0,
|
|
8959
|
+
onClose: () => {
|
|
8960
|
+
setUpgradeFor(void 0);
|
|
8961
|
+
},
|
|
8962
|
+
title: t("panel.accessUpgradeTitle"),
|
|
8963
|
+
description: upgradeFor === void 0 ? "" : t("panel.accessUpgradeConfirm", { access: t(accessKey(upgradeFor.access)) }),
|
|
8964
|
+
closeLabel: t("panel.close"),
|
|
8965
|
+
footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Action, {
|
|
8966
|
+
onClick: () => {
|
|
8967
|
+
setUpgradeFor(void 0);
|
|
8968
|
+
},
|
|
8969
|
+
children: t("panel.aliasCancel")
|
|
8970
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Action, {
|
|
8971
|
+
variant: "danger",
|
|
8972
|
+
disabled: state.busy,
|
|
8973
|
+
onClick: () => {
|
|
8974
|
+
const pending = upgradeFor;
|
|
8975
|
+
setUpgradeFor(void 0);
|
|
8976
|
+
if (pending !== void 0) mutate("setAccess", {
|
|
8977
|
+
entry: entryOf(pending.root),
|
|
8978
|
+
access: pending.access
|
|
8979
|
+
});
|
|
8980
|
+
},
|
|
8981
|
+
children: t("panel.accessUpgradeDo")
|
|
8982
|
+
})] })
|
|
8983
|
+
})] });
|
|
6585
8984
|
}
|
|
6586
8985
|
/** Normalize any thrown value into a `PanelError` the dialog can render. */
|
|
6587
8986
|
function asPanelError(error) {
|
|
@@ -6589,6 +8988,275 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6589
8988
|
return new PanelError("fallback", error instanceof Error ? error.message : String(error));
|
|
6590
8989
|
}
|
|
6591
8990
|
//#endregion
|
|
8991
|
+
//#region src/client/RootSwitcher.tsx
|
|
8992
|
+
/**
|
|
8993
|
+
* The Files-tab root switcher — the entry that took over the header position
|
|
8994
|
+
* the P1 gear launcher first claimed. It poses as the pane's path label: a
|
|
8995
|
+
* trigger that opens a menu of this session's roots (primary first, the
|
|
8996
|
+
* registered additional roots next, and the Workspace Folders manager last),
|
|
8997
|
+
* followed by a `PathLabel` of the selected root's full path — the same
|
|
8998
|
+
* component upstream draws in that spot, so both primary and additional
|
|
8999
|
+
* selections read exactly like the native header. While an additional root
|
|
9000
|
+
* is selected it also lends the pane's body area to this plugin's verbatim
|
|
9001
|
+
* fork of the native Files tree (`files-tree/`), so an additional root reads
|
|
9002
|
+
* and behaves exactly like the primary one.
|
|
9003
|
+
*
|
|
9004
|
+
* Two upstream DOM hooks make the takeover work, and both degrade silently:
|
|
9005
|
+
*
|
|
9006
|
+
* - The stylesheet rules: while `.mrfw-rootSwitcher` exists inside a
|
|
9007
|
+
* `[data-files-root]` pane the upstream `data-files-path` is hidden and the
|
|
9008
|
+
* switcher takes its flex position; while the switcher carries
|
|
9009
|
+
* `data-root-kind="additional"` the native `data-files-body`,
|
|
9010
|
+
* `data-files-reload`, and `data-open-target="directory"` split button
|
|
9011
|
+
* hide too (the reload refreshes the very tree that is no longer
|
|
9012
|
+
* visible, and the native open-in-app button only ever receives the
|
|
9013
|
+
* primary root through its slot props — the header gets a
|
|
9014
|
+
* `.mrfw-switcherReload` replacement and the plugin's verbatim fork of
|
|
9015
|
+
* the open-in-app control, pointed at the selected root). Upstream
|
|
9016
|
+
* changing that DOM shape simply stops the selectors from matching — the
|
|
9017
|
+
* native header keeps working and the switcher is just another actions
|
|
9018
|
+
* occupant.
|
|
9019
|
+
* - `closest('[data-files-root]')` from the switcher's own element locates
|
|
9020
|
+
* the pane the forked Files tree is portaled into, as a sibling of
|
|
9021
|
+
* `data-files-body`. No match → no portal: the component keeps working as
|
|
9022
|
+
* a menu and the native tree keeps its area. The tree's tab id comes from
|
|
9023
|
+
* the same element's `closest('[data-dockkit-content]')` — the dockkit
|
|
9024
|
+
* tabHost section — with a generated fallback where the hook is absent.
|
|
9025
|
+
*
|
|
9026
|
+
* The selected root is component state on purpose: every tab render
|
|
9027
|
+
* occurrence gets its own, and reopening the pane resets to the primary
|
|
9028
|
+
* root. There is no cross-tab or persisted selection.
|
|
9029
|
+
*
|
|
9030
|
+
* @module @dsh-electron/dsh-plugin-multi-root-workspace/client/RootSwitcher
|
|
9031
|
+
*/
|
|
9032
|
+
/**
|
|
9033
|
+
* The subdued full-path label upstream's Files header draws — it exists from
|
|
9034
|
+
* `0.1.7-alpha.1` on. A pixel-era runtime has no such export, so the probe
|
|
9035
|
+
* returns `undefined` and the header falls back to a plain titled span: the
|
|
9036
|
+
* path stays readable and hoverable, only the dir/file typography differs.
|
|
9037
|
+
*/
|
|
9038
|
+
const PathLabel = maybeComponent(["PathLabel"]);
|
|
9039
|
+
/** The Files-header switcher: root menu, dialog entry, and the portal tree. */
|
|
9040
|
+
function RootSwitcher(props) {
|
|
9041
|
+
const { panel, t, primaryPath, filesTree } = props;
|
|
9042
|
+
const [menuOpen, setMenuOpen] = (0, react.useState)(false);
|
|
9043
|
+
const [dialogOpen, setDialogOpen] = (0, react.useState)(false);
|
|
9044
|
+
const [view, setView] = (0, react.useState)(void 0);
|
|
9045
|
+
const [selected, setSelected] = (0, react.useState)("primary");
|
|
9046
|
+
const [takeover, setTakeover] = (0, react.useState)(null);
|
|
9047
|
+
const wrapperRef = (0, react.useRef)(null);
|
|
9048
|
+
const fallbackTabId = (0, react.useRef)(void 0);
|
|
9049
|
+
const [pane, setPane] = (0, react.useState)(null);
|
|
9050
|
+
const mounted = (0, react.useRef)(true);
|
|
9051
|
+
const requestEpoch = (0, react.useRef)(0);
|
|
9052
|
+
(0, react.useEffect)(() => {
|
|
9053
|
+
mounted.current = true;
|
|
9054
|
+
return () => {
|
|
9055
|
+
mounted.current = false;
|
|
9056
|
+
requestEpoch.current += 1;
|
|
9057
|
+
};
|
|
9058
|
+
}, []);
|
|
9059
|
+
(0, react.useEffect)(() => {
|
|
9060
|
+
setPane(wrapperRef.current?.closest("[data-files-root]") ?? null);
|
|
9061
|
+
}, []);
|
|
9062
|
+
const refresh = (0, react.useCallback)(async () => {
|
|
9063
|
+
if (panel === void 0) {
|
|
9064
|
+
setView(void 0);
|
|
9065
|
+
return;
|
|
9066
|
+
}
|
|
9067
|
+
const session = props.sessionId?.();
|
|
9068
|
+
if (session === void 0 || session === "") {
|
|
9069
|
+
setView(void 0);
|
|
9070
|
+
return;
|
|
9071
|
+
}
|
|
9072
|
+
const epoch = ++requestEpoch.current;
|
|
9073
|
+
try {
|
|
9074
|
+
const next = await panel.call("list", { sessionId: session });
|
|
9075
|
+
if (!mounted.current || epoch !== requestEpoch.current || props.sessionId?.() !== session) return;
|
|
9076
|
+
setView(next);
|
|
9077
|
+
} catch {
|
|
9078
|
+
if (!mounted.current || epoch !== requestEpoch.current || props.sessionId?.() !== session) return;
|
|
9079
|
+
setView(void 0);
|
|
9080
|
+
}
|
|
9081
|
+
}, [panel, props.sessionId]);
|
|
9082
|
+
(0, react.useEffect)(() => {
|
|
9083
|
+
refresh();
|
|
9084
|
+
}, [refresh]);
|
|
9085
|
+
(0, react.useEffect)(() => {
|
|
9086
|
+
let previous = props.sessionId?.();
|
|
9087
|
+
return props.subscribeSession?.(() => {
|
|
9088
|
+
const current = props.sessionId?.();
|
|
9089
|
+
if (current === previous) return;
|
|
9090
|
+
previous = current;
|
|
9091
|
+
requestEpoch.current += 1;
|
|
9092
|
+
setSelected("primary");
|
|
9093
|
+
setView(void 0);
|
|
9094
|
+
refresh();
|
|
9095
|
+
});
|
|
9096
|
+
}, [
|
|
9097
|
+
props.subscribeSession,
|
|
9098
|
+
props.sessionId,
|
|
9099
|
+
refresh
|
|
9100
|
+
]);
|
|
9101
|
+
(0, react.useEffect)(() => {
|
|
9102
|
+
if (selected === "primary") return;
|
|
9103
|
+
const current = view?.roots.find((root) => root.id === selected.id);
|
|
9104
|
+
if (current === void 0 || current.state !== "available") {
|
|
9105
|
+
setSelected("primary");
|
|
9106
|
+
return;
|
|
9107
|
+
}
|
|
9108
|
+
if (current !== selected) setSelected(current);
|
|
9109
|
+
}, [view, selected]);
|
|
9110
|
+
const roots = (0, react.useMemo)(() => view?.roots ?? [], [view]);
|
|
9111
|
+
const primaryLabel = view?.primaryName ?? basename(view?.primaryRoot ?? primaryPath);
|
|
9112
|
+
const triggerLabel = selected === "primary" ? primaryLabel : displayNameOf(selected);
|
|
9113
|
+
const triggerPath = selected === "primary" ? view?.primaryRoot ?? primaryPath : selected.path;
|
|
9114
|
+
const treeVisible = selected !== "primary" && panel !== void 0 && pane !== null && filesTree !== void 0;
|
|
9115
|
+
const selectedPath = selected === "primary" ? void 0 : selected.path;
|
|
9116
|
+
(0, react.useEffect)(() => {
|
|
9117
|
+
if (!treeVisible || selectedPath === void 0) {
|
|
9118
|
+
setTakeover(null);
|
|
9119
|
+
return;
|
|
9120
|
+
}
|
|
9121
|
+
const controller = new AbortController();
|
|
9122
|
+
const tabId = (wrapperRef.current?.closest("[data-dockkit-content]")?.dataset.dockkitContent ?? "") || (fallbackTabId.current ??= `mrfw-${Math.random().toString(36).slice(2)}`);
|
|
9123
|
+
setTakeover({
|
|
9124
|
+
signal: controller.signal,
|
|
9125
|
+
tabId,
|
|
9126
|
+
root: selectedPath
|
|
9127
|
+
});
|
|
9128
|
+
return () => {
|
|
9129
|
+
controller.abort();
|
|
9130
|
+
};
|
|
9131
|
+
}, [treeVisible, selectedPath]);
|
|
9132
|
+
const items = (0, react.useMemo)(() => [{
|
|
9133
|
+
id: "primary",
|
|
9134
|
+
label: `${primaryLabel} · ${t("panel.primary")}`,
|
|
9135
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconFolderOpen, {})
|
|
9136
|
+
}, ...(filesTree === void 0 ? [] : roots).map((root) => ({
|
|
9137
|
+
id: `root:${root.id}`,
|
|
9138
|
+
label: root.state === "available" ? displayNameOf(root) : `${displayNameOf(root)} · ${t(stateKey(root.state))}`,
|
|
9139
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconFolderClose, {}),
|
|
9140
|
+
disabled: root.state !== "available"
|
|
9141
|
+
}))], [
|
|
9142
|
+
roots,
|
|
9143
|
+
primaryLabel,
|
|
9144
|
+
t,
|
|
9145
|
+
filesTree
|
|
9146
|
+
]);
|
|
9147
|
+
const onSelect = (id) => {
|
|
9148
|
+
setMenuOpen(false);
|
|
9149
|
+
if (id === "manage") {
|
|
9150
|
+
setDialogOpen(true);
|
|
9151
|
+
return;
|
|
9152
|
+
}
|
|
9153
|
+
if (id === "primary") {
|
|
9154
|
+
setSelected("primary");
|
|
9155
|
+
return;
|
|
9156
|
+
}
|
|
9157
|
+
const root = roots.find((candidate) => `root:${candidate.id}` === id);
|
|
9158
|
+
if (root !== void 0) setSelected(root);
|
|
9159
|
+
};
|
|
9160
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
9161
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
9162
|
+
ref: wrapperRef,
|
|
9163
|
+
className: "mrfw-rootSwitcher",
|
|
9164
|
+
"data-root-kind": treeVisible ? "additional" : "primary",
|
|
9165
|
+
children: [
|
|
9166
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
|
|
9167
|
+
open: menuOpen,
|
|
9168
|
+
onClose: () => {
|
|
9169
|
+
setMenuOpen(false);
|
|
9170
|
+
},
|
|
9171
|
+
items,
|
|
9172
|
+
footer: [{
|
|
9173
|
+
id: "manage",
|
|
9174
|
+
label: t("switcher.manage"),
|
|
9175
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconSettings, {})
|
|
9176
|
+
}],
|
|
9177
|
+
selectedId: selected === "primary" ? "primary" : `root:${selected.id}`,
|
|
9178
|
+
onSelect,
|
|
9179
|
+
portal: true,
|
|
9180
|
+
align: "start",
|
|
9181
|
+
anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
9182
|
+
type: "button",
|
|
9183
|
+
className: "mrfw-rootSwitchBtn",
|
|
9184
|
+
title: triggerPath,
|
|
9185
|
+
"aria-haspopup": "menu",
|
|
9186
|
+
"aria-expanded": menuOpen,
|
|
9187
|
+
onClick: () => {
|
|
9188
|
+
const opening = !menuOpen;
|
|
9189
|
+
setMenuOpen(opening);
|
|
9190
|
+
if (opening) refresh();
|
|
9191
|
+
},
|
|
9192
|
+
children: [
|
|
9193
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
9194
|
+
className: "mrfw-triggerIcon",
|
|
9195
|
+
"aria-hidden": "true",
|
|
9196
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconFolderClose, { size: 16 })
|
|
9197
|
+
}),
|
|
9198
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
9199
|
+
className: "mrfw-rootSwitchLabel",
|
|
9200
|
+
children: triggerLabel
|
|
9201
|
+
}),
|
|
9202
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
9203
|
+
className: "mrfw-triggerIcon mrfw-rootSwitchChevron",
|
|
9204
|
+
"aria-hidden": "true",
|
|
9205
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconChevronDown, { size: 14 })
|
|
9206
|
+
})
|
|
9207
|
+
]
|
|
9208
|
+
})
|
|
9209
|
+
}),
|
|
9210
|
+
PathLabel === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
9211
|
+
className: "mrfw-switcherPath",
|
|
9212
|
+
title: triggerPath,
|
|
9213
|
+
children: triggerPath
|
|
9214
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PathLabel, {
|
|
9215
|
+
path: triggerPath,
|
|
9216
|
+
className: "mrfw-switcherPath"
|
|
9217
|
+
}),
|
|
9218
|
+
treeVisible && filesTree !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
9219
|
+
type: "button",
|
|
9220
|
+
className: "mrfw-switcherReload",
|
|
9221
|
+
"aria-label": filesTree.t("reload"),
|
|
9222
|
+
title: filesTree.t("reload"),
|
|
9223
|
+
onClick: () => {
|
|
9224
|
+
if (takeover !== null) filesTree.face.refresh(takeover.tabId);
|
|
9225
|
+
},
|
|
9226
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconRefresh, { size: 15 })
|
|
9227
|
+
}) : null,
|
|
9228
|
+
treeVisible && props.openInApp !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OpenInAppAction, {
|
|
9229
|
+
...props.openInApp,
|
|
9230
|
+
absolutePath: triggerPath
|
|
9231
|
+
}) : null
|
|
9232
|
+
]
|
|
9233
|
+
}),
|
|
9234
|
+
treeVisible && takeover !== null && filesTree !== void 0 ? (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(FilesTree, {
|
|
9235
|
+
tabId: takeover.tabId,
|
|
9236
|
+
sessionId: filesTree.sessionId,
|
|
9237
|
+
root: takeover.root,
|
|
9238
|
+
signal: takeover.signal,
|
|
9239
|
+
useStore: filesTree.useStore,
|
|
9240
|
+
actions: filesTree.actions,
|
|
9241
|
+
start: filesTree.face.start,
|
|
9242
|
+
toggle: filesTree.face.toggle,
|
|
9243
|
+
onOpen: filesTree.openFile,
|
|
9244
|
+
t: filesTree.t
|
|
9245
|
+
}), pane) : null,
|
|
9246
|
+
dialogOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WorkspaceFoldersDialog, {
|
|
9247
|
+
panel: props.panel,
|
|
9248
|
+
pickDirectory: props.pickDirectory,
|
|
9249
|
+
...props.sessionId === void 0 ? {} : { sessionId: props.sessionId },
|
|
9250
|
+
...props.subscribeSession === void 0 ? {} : { subscribeSession: props.subscribeSession },
|
|
9251
|
+
t,
|
|
9252
|
+
onClose: () => {
|
|
9253
|
+
setDialogOpen(false);
|
|
9254
|
+
refresh();
|
|
9255
|
+
}
|
|
9256
|
+
}) : null
|
|
9257
|
+
] });
|
|
9258
|
+
}
|
|
9259
|
+
//#endregion
|
|
6592
9260
|
//#region src/client/index.ts
|
|
6593
9261
|
/** The footer-action cell this plugin owns. */
|
|
6594
9262
|
const SLOT_ID = "multi-root-folders";
|
|
@@ -6605,7 +9273,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6605
9273
|
* @param ctx - the client root context.
|
|
6606
9274
|
*/
|
|
6607
9275
|
function apply(ctx) {
|
|
6608
|
-
ctx.effect(() => ctx.locale.register(NS, {
|
|
9276
|
+
ctx.effect(() => ctx.locale.register(NS$2, {
|
|
6609
9277
|
zh,
|
|
6610
9278
|
en
|
|
6611
9279
|
}), "multi-root-workspace: dictionaries");
|
|
@@ -6631,23 +9299,59 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6631
9299
|
return await workspace.pickDirectory();
|
|
6632
9300
|
};
|
|
6633
9301
|
const sessionId = () => currentSessionIdOf(readService(ctx, "sessions"));
|
|
6634
|
-
const
|
|
9302
|
+
const subscribeSession = (listener) => subscribeSessions(readService(ctx, "sessions"), listener);
|
|
9303
|
+
const t = ctx.locale.bind(NS$2);
|
|
9304
|
+
const openInApp = createOpenInAppAdapter({
|
|
9305
|
+
t: ctx.locale.bind(NS),
|
|
9306
|
+
shortcuts: readService(ctx, "shortcuts")?.catalog
|
|
9307
|
+
});
|
|
9308
|
+
const filesTreeSeats = createFilesTreeSeats(ctx);
|
|
9309
|
+
const filesTreeT = ctx.locale.bind(NS$1);
|
|
6635
9310
|
ctx.slots.inject("sidebar.footer.action", () => ctx.slots.register({
|
|
6636
9311
|
name: "sidebar.footer.action",
|
|
6637
9312
|
id: SLOT_ID,
|
|
6638
9313
|
order: 50,
|
|
6639
9314
|
label: () => t("action.label"),
|
|
6640
|
-
locale: NS
|
|
9315
|
+
locale: NS$2
|
|
6641
9316
|
}, (owner) => {
|
|
6642
9317
|
const wide = owner?.wide;
|
|
6643
9318
|
return (0, react.createElement)(WorkspaceFoldersAction, {
|
|
6644
9319
|
panel,
|
|
6645
9320
|
pickDirectory,
|
|
6646
9321
|
sessionId,
|
|
9322
|
+
subscribeSession,
|
|
6647
9323
|
t,
|
|
6648
9324
|
...wide === void 0 ? {} : { wide }
|
|
6649
9325
|
});
|
|
6650
9326
|
}));
|
|
9327
|
+
ctx.slots.inject("sidebar.right.tab.files.actions", () => ctx.slots.register({
|
|
9328
|
+
name: "sidebar.right.tab.files.actions",
|
|
9329
|
+
id: SLOT_ID,
|
|
9330
|
+
order: 50,
|
|
9331
|
+
store: filesTreeSeats.store,
|
|
9332
|
+
inject: filesTreeSeats.inject
|
|
9333
|
+
}, function FilesActionsOccupant(props) {
|
|
9334
|
+
const absolutePath = props?.absolutePath;
|
|
9335
|
+
const paneSessionId = () => props?.sessionId ?? sessionId();
|
|
9336
|
+
const filesTree = createFilesTreeSurface(props, filesTreeT);
|
|
9337
|
+
return absolutePath === void 0 ? (0, react.createElement)(WorkspaceFoldersAction, {
|
|
9338
|
+
panel,
|
|
9339
|
+
pickDirectory,
|
|
9340
|
+
sessionId: paneSessionId,
|
|
9341
|
+
subscribeSession,
|
|
9342
|
+
t,
|
|
9343
|
+
compact: true
|
|
9344
|
+
}) : (0, react.createElement)(RootSwitcher, {
|
|
9345
|
+
primaryPath: absolutePath,
|
|
9346
|
+
panel,
|
|
9347
|
+
pickDirectory,
|
|
9348
|
+
sessionId: paneSessionId,
|
|
9349
|
+
subscribeSession,
|
|
9350
|
+
t,
|
|
9351
|
+
openInApp,
|
|
9352
|
+
filesTree
|
|
9353
|
+
});
|
|
9354
|
+
}));
|
|
6651
9355
|
}
|
|
6652
9356
|
/**
|
|
6653
9357
|
* Read one optional service by name.
|