dsh-group-status 0.4.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +128 -0
- package/README.en.md +10 -6
- package/README.md +12 -6
- package/lib/client.js +1913 -1804
- package/lib/client.js.map +1 -1
- package/lib/index.js +38 -8
- package/lib/types/build-stamp.d.ts +4 -0
- package/lib/types/build-stamp.d.ts.map +1 -0
- package/lib/types/build-stamp.js +23 -0
- package/lib/types/build-stamp.js.map +1 -0
- package/lib/types/client/BadgeSettingsCard.d.ts.map +1 -1
- package/lib/types/client/BadgeSettingsCard.js +8 -8
- package/lib/types/client/BadgeSettingsCard.js.map +1 -1
- package/lib/types/client/badge-field.d.ts +33 -10
- package/lib/types/client/badge-field.d.ts.map +1 -1
- package/lib/types/client/badge-field.js +35 -16
- package/lib/types/client/badge-field.js.map +1 -1
- package/lib/types/client/badge-labels.d.ts +3 -3
- package/lib/types/client/badge-labels.js +3 -3
- package/lib/types/client/badge-settings.d.ts +0 -25
- package/lib/types/client/badge-settings.d.ts.map +1 -1
- package/lib/types/client/badge-settings.js +0 -18
- package/lib/types/client/badge-settings.js.map +1 -1
- package/lib/types/client/facets.d.ts +39 -5
- package/lib/types/client/facets.d.ts.map +1 -1
- package/lib/types/client/facets.js +48 -4
- package/lib/types/client/facets.js.map +1 -1
- package/lib/types/client/group-status.d.ts +5 -0
- package/lib/types/client/group-status.d.ts.map +1 -1
- package/lib/types/client/group-status.js +25 -0
- package/lib/types/client/group-status.js.map +1 -1
- package/lib/types/client/locales.d.ts +4 -8
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/locales.js +4 -8
- package/lib/types/client/locales.js.map +1 -1
- package/lib/types/client/rows/EmptySessions.d.ts +4 -0
- package/lib/types/client/rows/EmptySessions.d.ts.map +1 -0
- package/lib/types/client/rows/EmptySessions.js +10 -0
- package/lib/types/client/rows/EmptySessions.js.map +1 -0
- package/lib/types/client/rows/GroupBadge.d.ts +4 -4
- package/lib/types/client/rows/GroupBadge.d.ts.map +1 -1
- package/lib/types/client/rows/GroupBadge.js +10 -7
- package/lib/types/client/rows/GroupBadge.js.map +1 -1
- package/lib/types/client/rows/ListView.d.ts +24 -0
- package/lib/types/client/rows/ListView.d.ts.map +1 -0
- package/lib/types/client/rows/ListView.js +81 -0
- package/lib/types/client/rows/ListView.js.map +1 -0
- package/lib/types/client/rows/SessionTree.d.ts +4 -0
- package/lib/types/client/rows/SessionTree.d.ts.map +1 -0
- package/lib/types/client/rows/SessionTree.js +381 -0
- package/lib/types/client/rows/SessionTree.js.map +1 -0
- package/lib/types/client/rows/ViewOptionsMenu.d.ts +14 -0
- package/lib/types/client/rows/ViewOptionsMenu.d.ts.map +1 -0
- package/lib/types/client/rows/ViewOptionsMenu.js +50 -0
- package/lib/types/client/rows/ViewOptionsMenu.js.map +1 -0
- package/lib/types/client/rows/WorkspaceBrowser.d.ts +0 -5
- package/lib/types/client/rows/WorkspaceBrowser.d.ts.map +1 -1
- package/lib/types/client/rows/WorkspaceBrowser.js +8 -557
- package/lib/types/client/rows/WorkspaceBrowser.js.map +1 -1
- package/lib/types/client/rows/browser-shared.d.ts +105 -0
- package/lib/types/client/rows/browser-shared.d.ts.map +1 -0
- package/lib/types/client/rows/browser-shared.js +93 -0
- package/lib/types/client/rows/browser-shared.js.map +1 -0
- package/lib/types/client/session-actions/derived.d.ts.map +1 -1
- package/lib/types/client/session-actions/derived.js +13 -2
- package/lib/types/client/session-actions/derived.js.map +1 -1
- package/lib/types/client/session-watermarks.d.ts +6 -1
- package/lib/types/client/session-watermarks.d.ts.map +1 -1
- package/lib/types/client/session-watermarks.js +31 -11
- package/lib/types/client/session-watermarks.js.map +1 -1
- package/lib/types/client/toast/register.d.ts.map +1 -1
- package/lib/types/client/toast/register.js +102 -132
- package/lib/types/client/toast/register.js.map +1 -1
- package/lib/types/client/tree.d.ts +16 -1
- package/lib/types/client/tree.d.ts.map +1 -1
- package/lib/types/client/tree.js +20 -8
- package/lib/types/client/tree.js.map +1 -1
- package/lib/types/client/use-registered-facets.d.ts +11 -0
- package/lib/types/client/use-registered-facets.d.ts.map +1 -0
- package/lib/types/client/use-registered-facets.js +48 -0
- package/lib/types/client/use-registered-facets.js.map +1 -0
- package/lib/types/host-contract.d.ts +71 -0
- package/lib/types/host-contract.d.ts.map +1 -0
- package/lib/types/host-contract.js +127 -0
- package/lib/types/host-contract.js.map +1 -0
- package/lib/types/index.d.ts +13 -13
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +17 -8
- package/lib/types/index.js.map +1 -1
- package/package.json +16 -2
- package/lib/types/client/facet-visibility.d.ts +0 -17
- package/lib/types/client/facet-visibility.d.ts.map +0 -1
- package/lib/types/client/facet-visibility.js +0 -18
- package/lib/types/client/facet-visibility.js.map +0 -1
package/lib/client.js
CHANGED
|
@@ -211,30 +211,17 @@ window.__ModuleLoader__.load({
|
|
|
211
211
|
*/
|
|
212
212
|
/** Loader 条目 id —— 设置页的寻址键,也是宿主 `describe()` 的 ns。 */
|
|
213
213
|
const ENTRY_ID = "dsh-group-status";
|
|
214
|
-
/**
|
|
215
|
-
* 一个 facet 是否被设置关掉。
|
|
216
|
-
*
|
|
217
|
-
* - 没有 `settingKey` → 始终参与(缺省是"显示",与其余开关的默认值同源)。
|
|
218
|
-
* - 声明了但不是布尔开关键 → 同样按显示处理:拼错的键不该让一个状态整块消失。
|
|
219
|
-
*/
|
|
220
|
-
function facetVisibleBySettings(settings, settingKey) {
|
|
221
|
-
if (settingKey === void 0) return true;
|
|
222
|
-
const value = settings[settingKey];
|
|
223
|
-
return typeof value === "boolean" ? value : true;
|
|
224
|
-
}
|
|
225
214
|
/** 默认值:与 `Config` 各字段的 schema 默认同源(全部显示,数字阈值 2 即 count=1 时省略)。 */
|
|
226
215
|
const DEFAULT_BADGE_SETTINGS = Object.freeze({
|
|
227
216
|
showCompletedInBadge: true,
|
|
228
217
|
showRunningInBadge: true,
|
|
229
218
|
showPendingInBadge: true,
|
|
230
|
-
showScheduledInBadge: true,
|
|
231
|
-
showWaitingReviewInBadge: true,
|
|
232
219
|
showHoverDetails: true,
|
|
233
220
|
countThreshold: 2
|
|
234
221
|
});
|
|
235
222
|
/** 两个设置对象是否逐字段相等(用于发布前比较,避免无谓通知)。 */
|
|
236
223
|
function settingsEqual(a, b) {
|
|
237
|
-
return a.showCompletedInBadge === b.showCompletedInBadge && a.showRunningInBadge === b.showRunningInBadge && a.showPendingInBadge === b.showPendingInBadge && a.
|
|
224
|
+
return a.showCompletedInBadge === b.showCompletedInBadge && a.showRunningInBadge === b.showRunningInBadge && a.showPendingInBadge === b.showPendingInBadge && a.showHoverDetails === b.showHoverDetails && a.countThreshold === b.countThreshold;
|
|
238
225
|
}
|
|
239
226
|
/** 从原始快照派生:只有**显式 false** 才隐藏,缺失/非法一律显示(countThreshold 默认 2)。 */
|
|
240
227
|
function resolveFromSnapshot(snap) {
|
|
@@ -253,8 +240,6 @@ window.__ModuleLoader__.load({
|
|
|
253
240
|
showCompletedInBadge: getBool("showCompletedInBadge", true),
|
|
254
241
|
showRunningInBadge: getBool("showRunningInBadge", true),
|
|
255
242
|
showPendingInBadge: getBool("showPendingInBadge", true),
|
|
256
|
-
showScheduledInBadge: getBool("showScheduledInBadge", true),
|
|
257
|
-
showWaitingReviewInBadge: getBool("showWaitingReviewInBadge", true),
|
|
258
243
|
showHoverDetails: getBool("showHoverDetails", true),
|
|
259
244
|
countThreshold: getNumber("countThreshold", 2)
|
|
260
245
|
};
|
|
@@ -434,12 +419,52 @@ window.__ModuleLoader__.load({
|
|
|
434
419
|
};
|
|
435
420
|
}
|
|
436
421
|
/**
|
|
437
|
-
*
|
|
422
|
+
* 挂在 `globalThis` 上的登记表面的键名。
|
|
423
|
+
*
|
|
424
|
+
* 它同时是**现场诊断入口**:宿主里可以直接
|
|
425
|
+
* `globalThis.__dshGroupStatusFacetRegistry__.list().map(e => e.facet.id)` 读出"此刻到底
|
|
426
|
+
* 有哪些状态源登记上了"——「我注册了为什么没显示」不该只能靠猜(协议文档点名的那类
|
|
427
|
+
* 不可诊断失败)。
|
|
428
|
+
*/
|
|
429
|
+
const SHARED_REGISTRY_KEY = "__dshGroupStatusFacetRegistry__";
|
|
430
|
+
/**
|
|
431
|
+
* 取(并首次创建)**进程级**共享的登记表面。
|
|
432
|
+
*
|
|
433
|
+
* ## 为什么不能是模块级
|
|
434
|
+
*
|
|
435
|
+
* 提供方把 facet 注册进来是一次性的:它在自己的 `apply` 里 `require` 本插件的 client
|
|
436
|
+
* 入口、登记完就把注销函数挂在 effect 上。而宿主的热替换(HMR、`reload(id, rev)`)
|
|
437
|
+
* **只换代被替换的那个模块**——`dsh-client-modules` 的 `invalidate()` 只删自己那一行的
|
|
438
|
+
* factory 与 loadCache,不级联到"谁 require 过它"(依赖方不会重跑 apply)。
|
|
439
|
+
*
|
|
440
|
+
* 于是模块级单例在这里必然失效:本插件被换成新实例 → 新实例建一张**空表** → 提供方
|
|
441
|
+
* 那份注册留在被丢弃的旧表上 → 徽标从此少一家,且**只有刷新页面才好**。开发环境天天
|
|
442
|
+
* 重建 `lib/`,这条几乎必然发生。
|
|
443
|
+
*
|
|
444
|
+
* 挂到 `globalThis` 上之后,换代只是"再拿一次同一张表":注册跨换代存活,`facetStore()`
|
|
445
|
+
* 在新实例里 `rewire()` 时照样订阅到那些来源。提供方被换代时按同 id 覆盖登记(见
|
|
446
|
+
* {@link FacetRegistry.register} 的来源决胜),不会重复计数。
|
|
447
|
+
*
|
|
448
|
+
* @returns 全进程唯一的登记表面。
|
|
449
|
+
*/
|
|
450
|
+
function sharedFacetRegistry() {
|
|
451
|
+
const host = globalThis;
|
|
452
|
+
const existing = host[SHARED_REGISTRY_KEY];
|
|
453
|
+
if (existing !== void 0) return existing;
|
|
454
|
+
const created = createFacetRegistry();
|
|
455
|
+
host[SHARED_REGISTRY_KEY] = created;
|
|
456
|
+
return created;
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* 生产用的单例登记表面。
|
|
438
460
|
*
|
|
439
461
|
* 与 {@link createFacetRegistry} 分开,是为了让测试能对着一张干净的表断言协议行为,
|
|
440
462
|
* 而不会被别的用例或别的插件登记的 facet 污染。
|
|
463
|
+
*
|
|
464
|
+
* ⚠️ 它只是 {@link sharedFacetRegistry} 在**本模块首次加载时**取到的那份引用;跨模块实例
|
|
465
|
+
* 存活的是 globalThis 上那一张,所以运行期一律经 {@link sharedFacetRegistry} 现取。
|
|
441
466
|
*/
|
|
442
|
-
const registry =
|
|
467
|
+
const registry = sharedFacetRegistry();
|
|
443
468
|
/**
|
|
444
469
|
* 本插件 client 入口对外的登记函数(provider 侧唯一需要认识的符号)。
|
|
445
470
|
*
|
|
@@ -448,13 +473,14 @@ window.__ModuleLoader__.load({
|
|
|
448
473
|
* 返回值形状保持稳定,provider 的 dispose 路径不必区分成败。
|
|
449
474
|
*/
|
|
450
475
|
function registerGroupStatusFacet(facet) {
|
|
451
|
-
const
|
|
476
|
+
const target = sharedFacetRegistry();
|
|
477
|
+
const result = target.register(facet);
|
|
452
478
|
if (!result.ok) {
|
|
453
479
|
if (!(result.reason === "superseded" && facetOrigin(facet) === "legacy")) reportRejection(result);
|
|
454
480
|
return () => void 0;
|
|
455
481
|
}
|
|
456
482
|
return () => {
|
|
457
|
-
|
|
483
|
+
target.remove(result.id, facet);
|
|
458
484
|
};
|
|
459
485
|
}
|
|
460
486
|
/**
|
|
@@ -575,19 +601,39 @@ window.__ModuleLoader__.load({
|
|
|
575
601
|
}
|
|
576
602
|
//#endregion
|
|
577
603
|
//#region src/client/badge-field.ts
|
|
578
|
-
/**
|
|
604
|
+
/**
|
|
605
|
+
* 每个开关控制的是徽标上的哪一枚点。
|
|
606
|
+
*
|
|
607
|
+
* 单一事实来源:设置卡片里给每行画的**预览点**与折叠徽标实际画的那三枚点,都从这里取。
|
|
608
|
+
* 两处各写一套的话迟早会漂,而漂了的后果不是"多一个点少一个点",是**卡片预览与徽标说的
|
|
609
|
+
* 不是一回事**(同一族毛病:#25① 修过点行与明细的顺序,#37 又发现配置面里混着别人家的语义)。
|
|
610
|
+
*
|
|
611
|
+
* 对应关系与 `rows/GroupBadge.tsx` 一致:待审批 = warning 黄点、进行中 = ongoing 转圈、
|
|
612
|
+
* 已完成 = done 绿点。外部来源的点**不在这里** —— 它们由来源自己画(`renderIcon`),
|
|
613
|
+
* 也不在本插件的卡片上(显隐归来源自己)。
|
|
614
|
+
*/
|
|
615
|
+
const BADGE_FIELD_DOT_STATE = {
|
|
616
|
+
showPendingInBadge: "warning",
|
|
617
|
+
showRunningInBadge: "ongoing",
|
|
618
|
+
showCompletedInBadge: "done"
|
|
619
|
+
};
|
|
620
|
+
/**
|
|
621
|
+
* 卡片字段名(与 `src/index.ts` 的 `Config` 同名同义)。
|
|
622
|
+
*
|
|
623
|
+
* ⚠️ 只有本插件自己的语义。曾有 `showScheduledInBadge` / `showWaitingReviewInBadge`
|
|
624
|
+
* 两个字段替 dsh-later / dsh-gitea-dispatch 写死名字与图标形状,现已删除:外部来源的
|
|
625
|
+
* 显隐归来源自己(不注册就是不显示)。
|
|
626
|
+
*/
|
|
579
627
|
const BADGE_FIELDS = {
|
|
580
628
|
showCompletedInBadge: "showCompletedInBadge",
|
|
581
629
|
showRunningInBadge: "showRunningInBadge",
|
|
582
630
|
showPendingInBadge: "showPendingInBadge",
|
|
583
|
-
showScheduledInBadge: "showScheduledInBadge",
|
|
584
|
-
showWaitingReviewInBadge: "showWaitingReviewInBadge",
|
|
585
631
|
showHoverDetails: "showHoverDetails",
|
|
586
632
|
countThreshold: "countThreshold"
|
|
587
633
|
};
|
|
588
634
|
/**
|
|
589
|
-
*
|
|
590
|
-
*
|
|
635
|
+
* 草稿文本解析成开关状态:比较的是模型给的**文本**,不是存储值 ——
|
|
636
|
+
* 别把字符串 `'false'` 当成布尔 `false`(`'false' !== false` 恒真)。
|
|
591
637
|
*/
|
|
592
638
|
function isDraftEnabled(text) {
|
|
593
639
|
return text !== "false";
|
|
@@ -616,13 +662,21 @@ window.__ModuleLoader__.load({
|
|
|
616
662
|
} : void 0
|
|
617
663
|
};
|
|
618
664
|
}
|
|
619
|
-
/**
|
|
665
|
+
/** 各字段的字段转换规格。 */
|
|
666
|
+
const showCompletedField = makeBooleanField(BADGE_FIELDS.showCompletedInBadge, true);
|
|
667
|
+
const showRunningField = makeBooleanField(BADGE_FIELDS.showRunningInBadge, true);
|
|
668
|
+
/**
|
|
669
|
+
* 所有字段规格。
|
|
670
|
+
*
|
|
671
|
+
* ⚠️ 顺序**必须**与徽标画点的顺序一致(`rows/GroupBadge.tsx`:待审批 → 进行中 → 已完成;
|
|
672
|
+
* 外部来源排在已完成之前,但它们不在本插件的卡片上)。用户操作场景就是"我看到徽标第 N
|
|
673
|
+
* 个图标,想关掉它",卡片顺序错位会让人怀疑两处是两套事实 —— 同一族毛病 #25① 已经在
|
|
674
|
+
* 点行与明细之间修过一次。回归用例:`tests/badge-settings-card.client.spec.tsx`。
|
|
675
|
+
*/
|
|
620
676
|
const ALL_BADGE_FIELDS = [
|
|
621
|
-
makeBooleanField(BADGE_FIELDS.showCompletedInBadge, true),
|
|
622
|
-
makeBooleanField(BADGE_FIELDS.showRunningInBadge, true),
|
|
623
677
|
makeBooleanField(BADGE_FIELDS.showPendingInBadge, true),
|
|
624
|
-
|
|
625
|
-
|
|
678
|
+
showRunningField,
|
|
679
|
+
showCompletedField,
|
|
626
680
|
makeBooleanField(BADGE_FIELDS.showHoverDetails, true),
|
|
627
681
|
{
|
|
628
682
|
field: BADGE_FIELDS.countThreshold,
|
|
@@ -642,7 +696,7 @@ window.__ModuleLoader__.load({
|
|
|
642
696
|
];
|
|
643
697
|
//#endregion
|
|
644
698
|
//#region \0dsh-css:src/client/BadgeSettingsCard.module.css.mjs
|
|
645
|
-
const css$4 = ".dhp54G_section{min-width:0;padding:16px 0}.dhp54G_section:last-child{padding-bottom:0}.dhp54G_heading{color:var(--dsw-alias-label-primary);margin:0;font-size:13px;font-weight:600;line-height:1.5}.dhp54G_badgeField{gap:6px;padding:12px 0;display:grid}.dhp54G_badgeToggleRow{color:var(--dsw-alias-label-primary);justify-content:space-between;align-items:flex-start;gap:16px;font-size:13px;line-height:1.5;display:flex}.dhp54G_badgeToggleLabel{flex:1;min-width:0}.dhp54G_badgeNumberControl{align-items:center;gap:4px;display:flex}.dhp54G_badgeNumberButton{width:28px;height:28px;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-elevated,#2a2a2a);border:1px solid var(--dsw-alias-border-primary,#555);cursor:pointer;user-select:none;border-radius:6px;justify-content:center;align-items:center;padding:0;font-size:16px;font-weight:600;line-height:1;display:flex}.dhp54G_badgeNumberButton:hover:not(:disabled){border-color:var(--dsw-alias-border-focus,#0078d4);background:var(--dsw-alias-bg-hover,#3a3a3a)}.dhp54G_badgeNumberButton:active:not(:disabled){background:var(--dsw-alias-bg-active,#4a4a4a)}.dhp54G_badgeNumberButton:disabled{opacity:.3;cursor:not-allowed}.dhp54G_badgeNumberValue{text-align:center;min-width:24px;color:var(--dsw-alias-label-primary);font-size:13px;font-weight:500}.dhp54G_hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}";
|
|
699
|
+
const css$4 = ".dhp54G_section{min-width:0;padding:16px 0}.dhp54G_section:last-child{padding-bottom:0}.dhp54G_heading{color:var(--dsw-alias-label-primary);margin:0;font-size:13px;font-weight:600;line-height:1.5}.dhp54G_badgeField{gap:6px;padding:12px 0;display:grid}.dhp54G_badgeToggleRow{color:var(--dsw-alias-label-primary);justify-content:space-between;align-items:flex-start;gap:16px;font-size:13px;line-height:1.5;display:flex}.dhp54G_badgeSpecimen{vertical-align:-2px;align-items:center;margin-right:6px;display:inline-flex}.dhp54G_badgeToggleLabel{flex:1;min-width:0}.dhp54G_badgeNumberControl{align-items:center;gap:4px;display:flex}.dhp54G_badgeNumberButton{width:28px;height:28px;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-elevated,#2a2a2a);border:1px solid var(--dsw-alias-border-primary,#555);cursor:pointer;user-select:none;border-radius:6px;justify-content:center;align-items:center;padding:0;font-size:16px;font-weight:600;line-height:1;display:flex}.dhp54G_badgeNumberButton:hover:not(:disabled){border-color:var(--dsw-alias-border-focus,#0078d4);background:var(--dsw-alias-bg-hover,#3a3a3a)}.dhp54G_badgeNumberButton:active:not(:disabled){background:var(--dsw-alias-bg-active,#4a4a4a)}.dhp54G_badgeNumberButton:disabled{opacity:.3;cursor:not-allowed}.dhp54G_badgeNumberValue{text-align:center;min-width:24px;color:var(--dsw-alias-label-primary);font-size:13px;font-weight:500}.dhp54G_hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}";
|
|
646
700
|
const tagId$4 = "dsh-group-status/BadgeSettingsCard.module.css";
|
|
647
701
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$4) + "]") === null) {
|
|
648
702
|
const tag = document.createElement("style");
|
|
@@ -656,6 +710,7 @@ window.__ModuleLoader__.load({
|
|
|
656
710
|
"badgeNumberButton": "dhp54G_badgeNumberButton",
|
|
657
711
|
"badgeNumberControl": "dhp54G_badgeNumberControl",
|
|
658
712
|
"badgeNumberValue": "dhp54G_badgeNumberValue",
|
|
713
|
+
"badgeSpecimen": "dhp54G_badgeSpecimen",
|
|
659
714
|
"badgeToggleLabel": "dhp54G_badgeToggleLabel",
|
|
660
715
|
"badgeToggleRow": "dhp54G_badgeToggleRow",
|
|
661
716
|
"heading": "dhp54G_heading",
|
|
@@ -736,21 +791,29 @@ window.__ModuleLoader__.load({
|
|
|
736
791
|
id: badgeSectionId,
|
|
737
792
|
children: t("settings.badge.section")
|
|
738
793
|
}),
|
|
739
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleRow, {
|
|
740
|
-
field: BADGE_FIELDS.showRunningInBadge,
|
|
741
|
-
text: fieldText(BADGE_FIELDS.showRunningInBadge),
|
|
742
|
-
label: t("settings.badge.showRunning"),
|
|
743
|
-
hint: t("settings.badge.showRunningHint"),
|
|
744
|
-
state,
|
|
745
|
-
actions
|
|
746
|
-
}),
|
|
747
794
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleRow, {
|
|
748
795
|
field: BADGE_FIELDS.showPendingInBadge,
|
|
749
796
|
text: fieldText(BADGE_FIELDS.showPendingInBadge),
|
|
750
797
|
label: t("settings.badge.showPending"),
|
|
751
798
|
hint: t("settings.badge.showPendingHint"),
|
|
752
799
|
state,
|
|
753
|
-
actions
|
|
800
|
+
actions,
|
|
801
|
+
specimen: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, {
|
|
802
|
+
state: BADGE_FIELD_DOT_STATE.showPendingInBadge,
|
|
803
|
+
size: 12
|
|
804
|
+
})
|
|
805
|
+
}),
|
|
806
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleRow, {
|
|
807
|
+
field: BADGE_FIELDS.showRunningInBadge,
|
|
808
|
+
text: fieldText(BADGE_FIELDS.showRunningInBadge),
|
|
809
|
+
label: t("settings.badge.showRunning"),
|
|
810
|
+
hint: t("settings.badge.showRunningHint"),
|
|
811
|
+
state,
|
|
812
|
+
actions,
|
|
813
|
+
specimen: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, {
|
|
814
|
+
state: BADGE_FIELD_DOT_STATE.showRunningInBadge,
|
|
815
|
+
size: 12
|
|
816
|
+
})
|
|
754
817
|
}),
|
|
755
818
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleRow, {
|
|
756
819
|
field: BADGE_FIELDS.showCompletedInBadge,
|
|
@@ -758,23 +821,11 @@ window.__ModuleLoader__.load({
|
|
|
758
821
|
label: t("settings.badge.showCompleted"),
|
|
759
822
|
hint: t("settings.badge.showCompletedHint"),
|
|
760
823
|
state,
|
|
761
|
-
actions
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
label: t("settings.badge.showScheduled"),
|
|
767
|
-
hint: t("settings.badge.showScheduledHint"),
|
|
768
|
-
state,
|
|
769
|
-
actions
|
|
770
|
-
}),
|
|
771
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleRow, {
|
|
772
|
-
field: BADGE_FIELDS.showWaitingReviewInBadge,
|
|
773
|
-
text: fieldText(BADGE_FIELDS.showWaitingReviewInBadge),
|
|
774
|
-
label: t("settings.badge.showWaitingReview"),
|
|
775
|
-
hint: t("settings.badge.showWaitingReviewHint"),
|
|
776
|
-
state,
|
|
777
|
-
actions
|
|
824
|
+
actions,
|
|
825
|
+
specimen: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, {
|
|
826
|
+
state: BADGE_FIELD_DOT_STATE.showCompletedInBadge,
|
|
827
|
+
size: 12
|
|
828
|
+
})
|
|
778
829
|
})
|
|
779
830
|
]
|
|
780
831
|
}),
|
|
@@ -806,6 +857,8 @@ window.__ModuleLoader__.load({
|
|
|
806
857
|
text: fieldText(BADGE_FIELDS.countThreshold),
|
|
807
858
|
label: t("settings.count.threshold"),
|
|
808
859
|
hint: t("settings.count.thresholdHint"),
|
|
860
|
+
decreaseLabel: t("settings.count.decrease"),
|
|
861
|
+
increaseLabel: t("settings.count.increase"),
|
|
809
862
|
state,
|
|
810
863
|
actions
|
|
811
864
|
})]
|
|
@@ -813,15 +866,18 @@ window.__ModuleLoader__.load({
|
|
|
813
866
|
]
|
|
814
867
|
});
|
|
815
868
|
}
|
|
816
|
-
/** 单行开关:label + Switch + hint
|
|
817
|
-
function ToggleRow({ field, text, label, hint, state, actions }) {
|
|
869
|
+
/** 单行开关:label + Switch + hint。`specimen` 是行首那枚预览点(可选)。 */
|
|
870
|
+
function ToggleRow({ field, text, label, hint, state, actions, specimen }) {
|
|
818
871
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
819
872
|
className: BadgeSettingsCard_module_css_default.badgeField,
|
|
820
873
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
821
874
|
className: BadgeSettingsCard_module_css_default.badgeToggleRow,
|
|
822
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.
|
|
875
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
823
876
|
className: BadgeSettingsCard_module_css_default.badgeToggleLabel,
|
|
824
|
-
children:
|
|
877
|
+
children: [specimen !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
878
|
+
className: BadgeSettingsCard_module_css_default.badgeSpecimen,
|
|
879
|
+
children: specimen
|
|
880
|
+
}), label]
|
|
825
881
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
|
|
826
882
|
checked: isDraftEnabled(text),
|
|
827
883
|
label,
|
|
@@ -837,7 +893,7 @@ window.__ModuleLoader__.load({
|
|
|
837
893
|
});
|
|
838
894
|
}
|
|
839
895
|
/** 单行数字输入:label + 带加减按钮的数字输入 + hint。 */
|
|
840
|
-
function NumberRow({ field, text, label, hint, state, actions }) {
|
|
896
|
+
function NumberRow({ field, text, label, hint, decreaseLabel, increaseLabel, state, actions }) {
|
|
841
897
|
const disabled = !state.writable || state.saving;
|
|
842
898
|
const currentValue = Number(text) || 0;
|
|
843
899
|
const decrement = () => {
|
|
@@ -861,7 +917,7 @@ window.__ModuleLoader__.load({
|
|
|
861
917
|
className: BadgeSettingsCard_module_css_default.badgeNumberButton,
|
|
862
918
|
disabled: disabled || currentValue <= 0,
|
|
863
919
|
onClick: decrement,
|
|
864
|
-
"aria-label":
|
|
920
|
+
"aria-label": decreaseLabel,
|
|
865
921
|
children: "−"
|
|
866
922
|
}),
|
|
867
923
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
@@ -873,7 +929,7 @@ window.__ModuleLoader__.load({
|
|
|
873
929
|
className: BadgeSettingsCard_module_css_default.badgeNumberButton,
|
|
874
930
|
disabled,
|
|
875
931
|
onClick: increment,
|
|
876
|
-
"aria-label":
|
|
932
|
+
"aria-label": increaseLabel,
|
|
877
933
|
children: "+"
|
|
878
934
|
})
|
|
879
935
|
]
|
|
@@ -933,11 +989,34 @@ window.__ModuleLoader__.load({
|
|
|
933
989
|
}
|
|
934
990
|
//#endregion
|
|
935
991
|
//#region src/client/toast/register.tsx
|
|
936
|
-
/**
|
|
992
|
+
/**
|
|
993
|
+
* Wrap a business display mapping without making the slot component generic.
|
|
994
|
+
* @param display - 该键的纯文案映射;`notice` 的类型由键决定,写错字段即编译失败。
|
|
995
|
+
*/
|
|
937
996
|
function plainToast(display) {
|
|
938
997
|
return () => ({ display });
|
|
939
998
|
}
|
|
940
999
|
/**
|
|
1000
|
+
* 一条普通通知的登记数据:键与它自己的 display 成对。
|
|
1001
|
+
*
|
|
1002
|
+
* 键必须作为泛型实参先写出来,`display` 的 `notice` 才有类型——单看 display 推断不出
|
|
1003
|
+
* K,只能退回全部通知的联合。存下来的 `inject` 按 {@link KeyedToastInjected} 抹掉键
|
|
1004
|
+
* 负载:键 → 负载 的对应关系在入参这一侧已经校验过。
|
|
1005
|
+
*/
|
|
1006
|
+
function plainToastRow(key, display) {
|
|
1007
|
+
return {
|
|
1008
|
+
key,
|
|
1009
|
+
inject: plainToast(display)
|
|
1010
|
+
};
|
|
1011
|
+
}
|
|
1012
|
+
const ARCHIVE_TOASTS = [{
|
|
1013
|
+
key: "archived",
|
|
1014
|
+
text: "toast.archived"
|
|
1015
|
+
}, {
|
|
1016
|
+
key: "stoppedAndArchived",
|
|
1017
|
+
text: "toast.stoppedAndArchived"
|
|
1018
|
+
}];
|
|
1019
|
+
/**
|
|
941
1020
|
* Register notice presentations with their business-owned display fields and actions.
|
|
942
1021
|
* @param ctx - workspace plugin context.
|
|
943
1022
|
* @param viewStore - viewing store, read only by archive notices.
|
|
@@ -954,130 +1033,72 @@ window.__ModuleLoader__.load({
|
|
|
954
1033
|
actions.showArchived();
|
|
955
1034
|
}
|
|
956
1035
|
});
|
|
1036
|
+
const plainToasts = [
|
|
1037
|
+
plainToastRow("sessionIdCopied", (_notice, t) => ({
|
|
1038
|
+
text: t("toast.sessionIdCopied"),
|
|
1039
|
+
tone: "success"
|
|
1040
|
+
})),
|
|
1041
|
+
plainToastRow("sessionIdCopyFailed", (_notice, t) => ({
|
|
1042
|
+
text: t("toast.sessionIdCopyFailed"),
|
|
1043
|
+
tone: "warning"
|
|
1044
|
+
})),
|
|
1045
|
+
plainToastRow("pinFailed", (_notice, t) => ({
|
|
1046
|
+
text: t("toast.pinFailed"),
|
|
1047
|
+
tone: "warning"
|
|
1048
|
+
})),
|
|
1049
|
+
plainToastRow("unpinFailed", (_notice, t) => ({
|
|
1050
|
+
text: t("toast.unpinFailed"),
|
|
1051
|
+
tone: "warning"
|
|
1052
|
+
})),
|
|
1053
|
+
plainToastRow("archivedNotOpenable", (_notice, t) => ({
|
|
1054
|
+
text: t("toast.archivedNotOpenable"),
|
|
1055
|
+
tone: "warning"
|
|
1056
|
+
})),
|
|
1057
|
+
plainToastRow("defaultWorkspaceFailed", (_notice, t) => ({
|
|
1058
|
+
text: t("defaultWorkspace.failed"),
|
|
1059
|
+
tone: "warning"
|
|
1060
|
+
})),
|
|
1061
|
+
plainToastRow("shortcutForkFailed", (notice, t) => ({
|
|
1062
|
+
text: t(notice.reason === "unavailable" ? "shortcut.noCompletedTurn" : "shortcut.forkFailed"),
|
|
1063
|
+
tone: "plain"
|
|
1064
|
+
})),
|
|
1065
|
+
plainToastRow("createFailed", (notice, t) => ({
|
|
1066
|
+
text: t("toast.createFailed", { message: notice.message }),
|
|
1067
|
+
tone: "warning",
|
|
1068
|
+
holdMs: 6e3
|
|
1069
|
+
})),
|
|
1070
|
+
plainToastRow("forkRequiresOpen", (notice, t, dismiss) => ({
|
|
1071
|
+
text: t("toast.forkRequiresOpen"),
|
|
1072
|
+
tone: "warning",
|
|
1073
|
+
holdMs: 6e3,
|
|
1074
|
+
actions: [{
|
|
1075
|
+
label: t("toast.openForkSource"),
|
|
1076
|
+
onClick: () => {
|
|
1077
|
+
dismiss();
|
|
1078
|
+
actions.openForkSource(notice.sessionId);
|
|
1079
|
+
}
|
|
1080
|
+
}]
|
|
1081
|
+
}))
|
|
1082
|
+
];
|
|
957
1083
|
ctx.slots.registerFactory({
|
|
958
1084
|
name: "workspace.toast.content",
|
|
959
1085
|
scope: "root"
|
|
960
1086
|
}, WorkspaceToast);
|
|
961
1087
|
ctx.slots.inject("workspace.toast", function* () {
|
|
962
|
-
yield ctx.slots.register({
|
|
963
|
-
name: "workspace.toast",
|
|
964
|
-
key: "sessionIdCopied",
|
|
965
|
-
locale: "workspace",
|
|
966
|
-
inject: plainToast((_notice, t) => ({
|
|
967
|
-
text: t("toast.sessionIdCopied"),
|
|
968
|
-
tone: "success"
|
|
969
|
-
}))
|
|
970
|
-
}, toastEntry);
|
|
971
|
-
yield ctx.slots.register({
|
|
972
|
-
name: "workspace.toast",
|
|
973
|
-
key: "sessionIdCopyFailed",
|
|
974
|
-
locale: "workspace",
|
|
975
|
-
inject: plainToast((_notice, t) => ({
|
|
976
|
-
text: t("toast.sessionIdCopyFailed"),
|
|
977
|
-
tone: "warning"
|
|
978
|
-
}))
|
|
979
|
-
}, toastEntry);
|
|
980
|
-
yield ctx.slots.register({
|
|
981
|
-
name: "workspace.toast",
|
|
982
|
-
key: "pinFailed",
|
|
983
|
-
locale: "workspace",
|
|
984
|
-
inject: plainToast((_notice, t) => ({
|
|
985
|
-
text: t("toast.pinFailed"),
|
|
986
|
-
tone: "warning"
|
|
987
|
-
}))
|
|
988
|
-
}, toastEntry);
|
|
989
|
-
yield ctx.slots.register({
|
|
990
|
-
name: "workspace.toast",
|
|
991
|
-
key: "unpinFailed",
|
|
992
|
-
locale: "workspace",
|
|
993
|
-
inject: plainToast((_notice, t) => ({
|
|
994
|
-
text: t("toast.unpinFailed"),
|
|
995
|
-
tone: "warning"
|
|
996
|
-
}))
|
|
997
|
-
}, toastEntry);
|
|
998
|
-
yield ctx.slots.register({
|
|
999
|
-
name: "workspace.toast",
|
|
1000
|
-
key: "archivedNotOpenable",
|
|
1001
|
-
locale: "workspace",
|
|
1002
|
-
inject: plainToast((_notice, t) => ({
|
|
1003
|
-
text: t("toast.archivedNotOpenable"),
|
|
1004
|
-
tone: "warning"
|
|
1005
|
-
}))
|
|
1006
|
-
}, toastEntry);
|
|
1007
|
-
yield ctx.slots.register({
|
|
1008
|
-
name: "workspace.toast",
|
|
1009
|
-
key: "defaultWorkspaceFailed",
|
|
1010
|
-
locale: "workspace",
|
|
1011
|
-
inject: plainToast((_notice, t) => ({
|
|
1012
|
-
text: t("defaultWorkspace.failed"),
|
|
1013
|
-
tone: "warning"
|
|
1014
|
-
}))
|
|
1015
|
-
}, toastEntry);
|
|
1016
|
-
yield ctx.slots.register({
|
|
1017
|
-
name: "workspace.toast",
|
|
1018
|
-
key: "shortcutForkFailed",
|
|
1019
|
-
locale: "workspace",
|
|
1020
|
-
inject: plainToast((notice, t) => ({
|
|
1021
|
-
text: t(notice.reason === "unavailable" ? "shortcut.noCompletedTurn" : "shortcut.forkFailed"),
|
|
1022
|
-
tone: "plain"
|
|
1023
|
-
}))
|
|
1024
|
-
}, toastEntry);
|
|
1025
|
-
yield ctx.slots.register({
|
|
1026
|
-
name: "workspace.toast",
|
|
1027
|
-
key: "createFailed",
|
|
1028
|
-
locale: "workspace",
|
|
1029
|
-
inject: plainToast((notice, t) => ({
|
|
1030
|
-
text: t("toast.createFailed", { message: notice.message }),
|
|
1031
|
-
tone: "warning",
|
|
1032
|
-
holdMs: 6e3
|
|
1033
|
-
}))
|
|
1034
|
-
}, toastEntry);
|
|
1035
|
-
yield ctx.slots.register({
|
|
1088
|
+
for (const { key, inject } of plainToasts) yield ctx.slots.register({
|
|
1036
1089
|
name: "workspace.toast",
|
|
1037
|
-
key
|
|
1090
|
+
key,
|
|
1038
1091
|
locale: "workspace",
|
|
1039
|
-
inject
|
|
1040
|
-
text: t("toast.forkRequiresOpen"),
|
|
1041
|
-
tone: "warning",
|
|
1042
|
-
holdMs: 6e3,
|
|
1043
|
-
actions: [{
|
|
1044
|
-
label: t("toast.openForkSource"),
|
|
1045
|
-
onClick: () => {
|
|
1046
|
-
dismiss();
|
|
1047
|
-
actions.openForkSource(notice.sessionId);
|
|
1048
|
-
}
|
|
1049
|
-
}]
|
|
1050
|
-
}))
|
|
1092
|
+
inject
|
|
1051
1093
|
}, toastEntry);
|
|
1052
|
-
yield ctx.slots.register({
|
|
1053
|
-
name: "workspace.toast",
|
|
1054
|
-
key: "archived",
|
|
1055
|
-
locale: "workspace",
|
|
1056
|
-
store: viewStore,
|
|
1057
|
-
inject: () => ({
|
|
1058
|
-
display: (notice, t, dismiss) => ({
|
|
1059
|
-
text: t("toast.archived"),
|
|
1060
|
-
tone: "success",
|
|
1061
|
-
holdMs: 6e3,
|
|
1062
|
-
actions: [{
|
|
1063
|
-
label: t("toast.archivedUndo"),
|
|
1064
|
-
onClick: () => {
|
|
1065
|
-
dismiss();
|
|
1066
|
-
actions.undoArchive(notice.sessionId);
|
|
1067
|
-
}
|
|
1068
|
-
}]
|
|
1069
|
-
}),
|
|
1070
|
-
filterAction: archiveFilterAction
|
|
1071
|
-
})
|
|
1072
|
-
}, archiveToastEntry);
|
|
1073
|
-
yield ctx.slots.register({
|
|
1094
|
+
for (const { key, text } of ARCHIVE_TOASTS) yield ctx.slots.register({
|
|
1074
1095
|
name: "workspace.toast",
|
|
1075
|
-
key
|
|
1096
|
+
key,
|
|
1076
1097
|
locale: "workspace",
|
|
1077
1098
|
store: viewStore,
|
|
1078
1099
|
inject: () => ({
|
|
1079
1100
|
display: (notice, t, dismiss) => ({
|
|
1080
|
-
text: t(
|
|
1101
|
+
text: t(text),
|
|
1081
1102
|
tone: "success",
|
|
1082
1103
|
holdMs: 6e3,
|
|
1083
1104
|
actions: [{
|
|
@@ -1773,6 +1794,12 @@ window.__ModuleLoader__.load({
|
|
|
1773
1794
|
function owningGroupKey(workspaces, sessionId) {
|
|
1774
1795
|
return workspaces.find((workspace) => workspace.sessionIds.includes(sessionId))?.workspaceId ?? "";
|
|
1775
1796
|
}
|
|
1797
|
+
/** 宿主认的 kind 集合(查表用)。 */
|
|
1798
|
+
const PENDING_INTERACTION_KIND_SET = /* @__PURE__ */ new Set([
|
|
1799
|
+
"approval",
|
|
1800
|
+
"plan-review",
|
|
1801
|
+
"question"
|
|
1802
|
+
]);
|
|
1776
1803
|
function mainSessionId(list) {
|
|
1777
1804
|
return Object.values(list.byId).find((session) => (session.retainedBy.mainView ?? 0) > 0)?.id;
|
|
1778
1805
|
}
|
|
@@ -1956,12 +1983,7 @@ window.__ModuleLoader__.load({
|
|
|
1956
1983
|
}
|
|
1957
1984
|
/** Keep navigation presentation independent from domain-owned interaction objects. */
|
|
1958
1985
|
function visiblePendingKind(kind) {
|
|
1959
|
-
|
|
1960
|
-
case "approval":
|
|
1961
|
-
case "plan-review":
|
|
1962
|
-
case "question": return kind;
|
|
1963
|
-
default: return;
|
|
1964
|
-
}
|
|
1986
|
+
return kind !== void 0 && PENDING_INTERACTION_KIND_SET.has(kind) ? kind : void 0;
|
|
1965
1987
|
}
|
|
1966
1988
|
function runningChildCount(list, parentId, statuses) {
|
|
1967
1989
|
return list.projectionsBySession[parentId]?.values.subagentCatalog?.reduce((count, child) => count + ((statuses.get(child.id)?.running ?? list.byId[child.id]?.running) === true ? 1 : 0), 0) ?? 0;
|
|
@@ -2643,1293 +2665,888 @@ window.__ModuleLoader__.load({
|
|
|
2643
2665
|
function workspaceDisplayTitle(title, localizedDefault) {
|
|
2644
2666
|
return title === "default-workspace" ? localizedDefault : title;
|
|
2645
2667
|
}
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
planReview: 0,
|
|
2657
|
-
question: 0,
|
|
2658
|
-
done: 0,
|
|
2659
|
-
subagents: 0,
|
|
2660
|
-
facets: /* @__PURE__ */ new Map()
|
|
2661
|
-
});
|
|
2662
|
-
/**
|
|
2663
|
-
* 合并两份分组折果:本插件自己的计数相加,外部状态来源**按 facet 各自合并**。
|
|
2664
|
-
*
|
|
2665
|
-
* 用于多层聚合:折叠的父文件夹要覆盖整棵子树,而子树里每一层都已各自折过一遍。
|
|
2666
|
-
* `FacetFold.data` 怎么合归该 facet 自己决定,本模块从不解释。
|
|
2667
|
-
*/
|
|
2668
|
-
function mergeLiveStatus(a, b) {
|
|
2669
|
-
return {
|
|
2670
|
-
running: a.running + b.running,
|
|
2671
|
-
approval: a.approval + b.approval,
|
|
2672
|
-
planReview: a.planReview + b.planReview,
|
|
2673
|
-
question: a.question + b.question,
|
|
2674
|
-
done: a.done + b.done,
|
|
2675
|
-
subagents: a.subagents + b.subagents,
|
|
2676
|
-
facets: mergeFacets(a.facets, b.facets)
|
|
2677
|
-
};
|
|
2678
|
-
}
|
|
2679
|
-
/** 按 facet id 合并两侧折果;只有一侧有的来源原样带过。 */
|
|
2680
|
-
function mergeFacets(a, b) {
|
|
2681
|
-
if (a.size === 0) return b;
|
|
2682
|
-
if (b.size === 0) return a;
|
|
2683
|
-
const merged = new Map(a);
|
|
2684
|
-
for (const [id, entry] of b) {
|
|
2685
|
-
const own = merged.get(id);
|
|
2686
|
-
merged.set(id, own === void 0 ? entry : {
|
|
2687
|
-
facet: own.facet,
|
|
2688
|
-
fold: own.facet.merge?.(own.fold, entry.fold) ?? defaultMergeFold(own.fold, entry.fold)
|
|
2689
|
-
});
|
|
2690
|
-
}
|
|
2691
|
-
return merged;
|
|
2668
|
+
//#endregion
|
|
2669
|
+
//#region \0dsh-css:src/client/WorkspacePicker.module.css.mjs
|
|
2670
|
+
const css$3 = ".S_JYgq_modalAction{min-width:72px}.S_JYgq_modalError,.S_JYgq_menuStatus{margin-top:8px;font-size:12px;line-height:18px}.S_JYgq_modalError{color:var(--dsw-alias-state-error-primary)}.S_JYgq_menuStatus{color:var(--dsw-alias-label-secondary)}";
|
|
2671
|
+
const tagId$3 = "dsh-group-status/WorkspacePicker.module.css";
|
|
2672
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$3) + "]") === null) {
|
|
2673
|
+
const tag = document.createElement("style");
|
|
2674
|
+
tag.dataset.plugin = "dsh-group-status";
|
|
2675
|
+
tag.dataset.pluginCss = tagId$3;
|
|
2676
|
+
tag.textContent = css$3;
|
|
2677
|
+
document.head.appendChild(tag);
|
|
2692
2678
|
}
|
|
2679
|
+
var WorkspacePicker_module_css_default = {
|
|
2680
|
+
"menuStatus": "S_JYgq_menuStatus",
|
|
2681
|
+
"modalAction": "S_JYgq_modalAction",
|
|
2682
|
+
"modalError": "S_JYgq_modalError"
|
|
2683
|
+
};
|
|
2684
|
+
//#endregion
|
|
2685
|
+
//#region src/client/WorkspacePicker.tsx
|
|
2686
|
+
const ADD_WORKSPACE = "::add-workspace";
|
|
2693
2687
|
/**
|
|
2694
|
-
*
|
|
2695
|
-
*
|
|
2696
|
-
*
|
|
2697
|
-
* locale at render time.
|
|
2698
|
-
*
|
|
2699
|
-
* @param nodes - member rows of the group.
|
|
2700
|
-
* @param sessionIds - the same members' Session ids (facets address their own data by id).
|
|
2701
|
-
* @param facets - the registered status sources to fold over this group.
|
|
2702
|
-
* @param now - one clock reading for the whole pass, so sibling facets cannot disagree
|
|
2703
|
-
* about which deadlines are still ahead.
|
|
2688
|
+
* Render the pick menu plus the adoption error dialog.
|
|
2689
|
+
* @param props - owner-controlled flow props.
|
|
2690
|
+
* @returns menu + dialog elements.
|
|
2704
2691
|
*/
|
|
2705
|
-
function
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
}
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
}
|
|
2739
|
-
if (n.completed) done += 1;
|
|
2740
|
-
}
|
|
2741
|
-
const folded = /* @__PURE__ */ new Map();
|
|
2742
|
-
for (const { facet } of facets) {
|
|
2743
|
-
let snapshot;
|
|
2744
|
-
try {
|
|
2745
|
-
snapshot = facet.getSnapshot();
|
|
2746
|
-
} catch {
|
|
2747
|
-
snapshot = void 0;
|
|
2748
|
-
}
|
|
2749
|
-
try {
|
|
2750
|
-
const result = facet.fold(snapshot, sessionIds, now);
|
|
2751
|
-
if (result !== void 0 && result.count > 0) folded.set(facet.id, {
|
|
2752
|
-
facet,
|
|
2753
|
-
fold: result
|
|
2754
|
-
});
|
|
2755
|
-
} catch {}
|
|
2756
|
-
}
|
|
2757
|
-
return {
|
|
2758
|
-
running,
|
|
2759
|
-
approval,
|
|
2760
|
-
planReview,
|
|
2761
|
-
question,
|
|
2762
|
-
done,
|
|
2763
|
-
subagents,
|
|
2764
|
-
facets: folded
|
|
2765
|
-
};
|
|
2766
|
-
}
|
|
2767
|
-
/**
|
|
2768
|
-
* Measure each group's own members, as the input to {@link aggregateGroupStatuses}.
|
|
2769
|
-
*
|
|
2770
|
-
* Every group is measured, expanded ones included. A folder row shows no badge while
|
|
2771
|
-
* it is expanded, but its sessions still need attention and a collapsed ancestor must
|
|
2772
|
-
* keep counting them — so expansion is a rendering fact, never an input to the
|
|
2773
|
-
* aggregation. Short-circuiting expanded groups to `EMPTY_LIVE_STATUS` (the shape this
|
|
2774
|
-
* had before the tree-wide aggregator existed, harmless while nothing aggregated) makes
|
|
2775
|
-
* every collapsed parent undercount by the size of its expanded descendants.
|
|
2776
|
-
*
|
|
2777
|
-
* @param groups - derived groups, each with its member rows already populated.
|
|
2778
|
-
* @param facets - registered sources, already filtered by the visibility settings.
|
|
2779
|
-
* @param now - one clock reading shared by every group in the pass.
|
|
2780
|
-
*/
|
|
2781
|
-
function directStatusesForGroups(groups, facets = [], now = Date.now()) {
|
|
2782
|
-
const map = /* @__PURE__ */ new Map();
|
|
2783
|
-
for (const group of groups) map.set(group.key, summarizeGroupStatus(group.sessions, group.sessions.map((s) => s.id), facets, now));
|
|
2784
|
-
return map;
|
|
2785
|
-
}
|
|
2786
|
-
/**
|
|
2787
|
-
* Fold every group's DIRECT status up the workspace tree, so a collapsed folder
|
|
2788
|
-
* counts every descendant session. A group's own row shows no badge while it is
|
|
2789
|
-
* expanded, but its sessions still need attention and its collapsed ancestor must
|
|
2790
|
-
* keep counting them — so the walk never depends on a group's expanded state.
|
|
2791
|
-
*
|
|
2792
|
-
* @param direct - each group's own-member status, keyed by group key.
|
|
2793
|
-
* @param childrenByParent - the rendered tree: parent key (`undefined` = roots) → children.
|
|
2794
|
-
* @returns aggregated status per group key: own members plus the whole subtree.
|
|
2795
|
-
*/
|
|
2796
|
-
function aggregateGroupStatuses(direct, childrenByParent) {
|
|
2797
|
-
const aggregated = /* @__PURE__ */ new Map();
|
|
2798
|
-
const visiting = /* @__PURE__ */ new Set();
|
|
2799
|
-
const fold = (key) => {
|
|
2800
|
-
const cached = aggregated.get(key);
|
|
2801
|
-
if (cached !== void 0) return cached;
|
|
2802
|
-
if (visiting.has(key)) return direct.get(key) ?? EMPTY_LIVE_STATUS;
|
|
2803
|
-
visiting.add(key);
|
|
2804
|
-
let total = direct.get(key) ?? EMPTY_LIVE_STATUS;
|
|
2805
|
-
for (const child of childrenByParent.get(key) ?? []) total = mergeLiveStatus(total, fold(child.key));
|
|
2806
|
-
visiting.delete(key);
|
|
2807
|
-
aggregated.set(key, total);
|
|
2808
|
-
return total;
|
|
2692
|
+
function WorkspacePickFlow({ t, open, anchorRef, useWorkspaces, createWorkspace, useDirectoryFlow, renderDirectoryFlow, onPick, onClose, addOnly = false, onBusyChange, side = "bottom", selectedId }) {
|
|
2693
|
+
const workspaceSnapshot = useWorkspaces((state) => state);
|
|
2694
|
+
const workspaces = workspaceSnapshot.items;
|
|
2695
|
+
const getAnchorRect = (0, react.useCallback)(() => anchorRef?.current?.getBoundingClientRect() ?? null, [anchorRef]);
|
|
2696
|
+
const [errorOpen, setErrorOpen] = (0, react.useState)(false);
|
|
2697
|
+
const [modalError, setModalError] = (0, react.useState)(null);
|
|
2698
|
+
const [flowOpen, setFlowOpen] = (0, react.useState)(false);
|
|
2699
|
+
const [pickingFolder, setPickingFolder] = (0, react.useState)(false);
|
|
2700
|
+
const flowBusy = flowOpen || pickingFolder;
|
|
2701
|
+
(0, react.useEffect)(() => {
|
|
2702
|
+
onBusyChange?.(flowBusy);
|
|
2703
|
+
}, [flowBusy, onBusyChange]);
|
|
2704
|
+
const flowAvailable = useDirectoryFlow((occupied) => occupied);
|
|
2705
|
+
(0, react.useEffect)(() => {
|
|
2706
|
+
if (flowOpen && !flowAvailable) setFlowOpen(false);
|
|
2707
|
+
}, [flowOpen, flowAvailable]);
|
|
2708
|
+
const addEntries = flowAvailable ? [{
|
|
2709
|
+
id: ADD_WORKSPACE,
|
|
2710
|
+
label: t("menu.addWorkspace"),
|
|
2711
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutlineRegular, { size: 16 }),
|
|
2712
|
+
disabled: flowBusy
|
|
2713
|
+
}] : [];
|
|
2714
|
+
const pinAdd = !addOnly && workspaces.length > 0;
|
|
2715
|
+
const items = pinAdd ? workspaces.map((workspace) => ({
|
|
2716
|
+
id: workspace.workspaceId,
|
|
2717
|
+
label: workspaceDisplayTitle(workspace.title, t("workspace.defaultName")),
|
|
2718
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderCloseRegular, { size: 16 }),
|
|
2719
|
+
disabled: flowBusy
|
|
2720
|
+
})) : addEntries;
|
|
2721
|
+
const menuIsEmpty = items.length === 0;
|
|
2722
|
+
const closeModal = () => {
|
|
2723
|
+
setErrorOpen(false);
|
|
2724
|
+
setModalError(null);
|
|
2809
2725
|
};
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
* inside `groupLiveBadge`), but a collapsed ancestor still has to count them, so
|
|
2819
|
-
* expansion never suppresses the measurement.
|
|
2820
|
-
*
|
|
2821
|
-
* @param groups - derived rows, in the order they render.
|
|
2822
|
-
* @param direct - each group's own-member status, from {@link directStatusesForGroups}.
|
|
2823
|
-
* @param childrenByParent - the rendered tree: parent key (`undefined` = roots) → children.
|
|
2824
|
-
*/
|
|
2825
|
-
function withAggregatedStatus(groups, direct, childrenByParent) {
|
|
2826
|
-
const aggregated = aggregateGroupStatuses(direct, childrenByParent);
|
|
2827
|
-
return groups.map((group) => ({
|
|
2828
|
-
...group,
|
|
2829
|
-
liveStatus: aggregated.get(group.key) ?? EMPTY_LIVE_STATUS
|
|
2830
|
-
}));
|
|
2831
|
-
}
|
|
2832
|
-
/** The blank row plus the first `watermark` non-blank rows, in list order. */
|
|
2833
|
-
function watermarkRows(sessions, watermark) {
|
|
2834
|
-
let count = 0;
|
|
2835
|
-
const rows = sessions.filter((session) => {
|
|
2836
|
-
if (session.blank) return true;
|
|
2837
|
-
count += 1;
|
|
2838
|
-
return count <= watermark;
|
|
2726
|
+
/** Adopt a picked directory; failures land in the folder-error dialog (Choose again reopens the flow). */
|
|
2727
|
+
const adoptDirectory = (path) => createWorkspace({ path }).then((workspace) => {
|
|
2728
|
+
setFlowOpen(false);
|
|
2729
|
+
onPick(workspace.workspaceId);
|
|
2730
|
+
}).catch((reason) => {
|
|
2731
|
+
setModalError(reason instanceof Error ? reason.message : String(reason));
|
|
2732
|
+
setFlowOpen(false);
|
|
2733
|
+
setErrorOpen(true);
|
|
2839
2734
|
});
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2735
|
+
const openDirectoryFlow = (0, react.useCallback)(() => {
|
|
2736
|
+
onClose();
|
|
2737
|
+
setErrorOpen(false);
|
|
2738
|
+
setModalError(null);
|
|
2739
|
+
setFlowOpen(true);
|
|
2740
|
+
}, [onClose]);
|
|
2741
|
+
const listSettled = addOnly || workspaceSnapshot.phase === "ready";
|
|
2742
|
+
const addIsTheOnlyEntry = !pinAdd && listSettled && addEntries.length === 1;
|
|
2743
|
+
(0, react.useEffect)(() => {
|
|
2744
|
+
if (open && addIsTheOnlyEntry && !flowBusy) openDirectoryFlow();
|
|
2745
|
+
}, [
|
|
2746
|
+
open,
|
|
2747
|
+
addIsTheOnlyEntry,
|
|
2748
|
+
flowBusy,
|
|
2749
|
+
openDirectoryFlow
|
|
2750
|
+
]);
|
|
2751
|
+
/** Owner side of the flow conversation: adopt keeps the flow open (busy) until the Host answers. */
|
|
2752
|
+
const flowOwner = {
|
|
2753
|
+
open: flowOpen,
|
|
2754
|
+
busy: pickingFolder,
|
|
2755
|
+
onPicked: (path) => {
|
|
2756
|
+
setPickingFolder(true);
|
|
2757
|
+
adoptDirectory(path).finally(() => {
|
|
2758
|
+
setPickingFolder(false);
|
|
2759
|
+
});
|
|
2760
|
+
},
|
|
2761
|
+
onCancel: () => {
|
|
2762
|
+
setFlowOpen(false);
|
|
2763
|
+
},
|
|
2764
|
+
onError: (message) => {
|
|
2765
|
+
setFlowOpen(false);
|
|
2766
|
+
setModalError(message);
|
|
2767
|
+
setErrorOpen(true);
|
|
2768
|
+
}
|
|
2843
2769
|
};
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
* @param revealSessionId - Session selected from search and required to be visible.
|
|
2851
|
-
* @returns the group's baseline non-blank row count.
|
|
2852
|
-
*/
|
|
2853
|
-
function requiredWatermark(sessions, current, revealSessionId) {
|
|
2854
|
-
let index = 0;
|
|
2855
|
-
let required = 0;
|
|
2856
|
-
for (const session of sessions) {
|
|
2857
|
-
if (session.blank) continue;
|
|
2858
|
-
index += 1;
|
|
2859
|
-
if (index <= 50 && (session.running || session.runningSubagentCount > 0 || session.pendingInteraction !== void 0 || session.completed || session.id === current) || session.id === revealSessionId) required = index;
|
|
2860
|
-
}
|
|
2861
|
-
return required;
|
|
2862
|
-
}
|
|
2863
|
-
/**
|
|
2864
|
-
* Raise each listed group's watermark to its requirement; returns `marks` itself when nothing rises.
|
|
2865
|
-
* @param marks - watermarks stored by the current browser tree.
|
|
2866
|
-
* @param required - required baselines, in group order.
|
|
2867
|
-
* @returns either the unchanged input or a new object containing only raised groups.
|
|
2868
|
-
*/
|
|
2869
|
-
function raiseWatermarks(marks, required) {
|
|
2870
|
-
let raised = marks;
|
|
2871
|
-
for (const [key, watermark] of required) if (watermark > (raised[key] ?? 5)) raised = {
|
|
2872
|
-
...raised,
|
|
2873
|
-
[key]: watermark
|
|
2770
|
+
const handleSelect = (id) => {
|
|
2771
|
+
if (id === ADD_WORKSPACE) {
|
|
2772
|
+
openDirectoryFlow();
|
|
2773
|
+
return;
|
|
2774
|
+
}
|
|
2775
|
+
onPick(id);
|
|
2874
2776
|
};
|
|
2875
|
-
return
|
|
2777
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
2778
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
|
|
2779
|
+
open: open && !addIsTheOnlyEntry && !menuIsEmpty,
|
|
2780
|
+
anchor: null,
|
|
2781
|
+
items,
|
|
2782
|
+
...pinAdd ? { footer: addEntries } : {},
|
|
2783
|
+
selectedId,
|
|
2784
|
+
onSelect: handleSelect,
|
|
2785
|
+
onClose,
|
|
2786
|
+
side,
|
|
2787
|
+
portal: true,
|
|
2788
|
+
getAnchorRect
|
|
2789
|
+
}),
|
|
2790
|
+
open && !addIsTheOnlyEntry && !menuIsEmpty && workspaceSnapshot.phase === "pending" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2791
|
+
className: WorkspacePicker_module_css_default.menuStatus,
|
|
2792
|
+
role: "status",
|
|
2793
|
+
children: t("picker.loading")
|
|
2794
|
+
}),
|
|
2795
|
+
renderDirectoryFlow(flowOwner),
|
|
2796
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
2797
|
+
open: errorOpen,
|
|
2798
|
+
onClose: closeModal,
|
|
2799
|
+
closeLabel: t("close"),
|
|
2800
|
+
title: t("folderError.title"),
|
|
2801
|
+
footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
2802
|
+
variant: "outline",
|
|
2803
|
+
className: WorkspacePicker_module_css_default.modalAction,
|
|
2804
|
+
onClick: closeModal,
|
|
2805
|
+
children: t("cancel")
|
|
2806
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
2807
|
+
variant: "primary",
|
|
2808
|
+
className: WorkspacePicker_module_css_default.modalAction,
|
|
2809
|
+
disabled: !flowAvailable,
|
|
2810
|
+
onClick: openDirectoryFlow,
|
|
2811
|
+
children: t("folderError.retry")
|
|
2812
|
+
})] }),
|
|
2813
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2814
|
+
className: WorkspacePicker_module_css_default.modalError,
|
|
2815
|
+
role: "alert",
|
|
2816
|
+
children: modalError
|
|
2817
|
+
})
|
|
2818
|
+
})
|
|
2819
|
+
] });
|
|
2876
2820
|
}
|
|
2877
|
-
//#endregion
|
|
2878
|
-
//#region src/client/facet-visibility.ts
|
|
2879
2821
|
/**
|
|
2880
|
-
*
|
|
2881
|
-
*
|
|
2882
|
-
*
|
|
2883
|
-
*
|
|
2884
|
-
*
|
|
2885
|
-
* 过滤发生在**折叠之前**而不是渲染阶段:徽标数字、图标、`aria-label`、悬停明细与
|
|
2886
|
-
* "要不要画徽标"因此天然同源。放到渲染阶段会出现"label 里有、计数里没有",而那正是
|
|
2887
|
-
* 徽标最容易被怀疑撒谎的地方。
|
|
2888
|
-
*
|
|
2889
|
-
* @module dsh-group-status/client/facet-visibility
|
|
2822
|
+
* The conversation empty-state registration: adapts the owner share to the
|
|
2823
|
+
* core flow (all state and semantics live in the flow / the owner).
|
|
2824
|
+
* @param props - empty-state slot props (owner share + injected creation callback).
|
|
2825
|
+
* @returns the flow element.
|
|
2890
2826
|
*/
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2827
|
+
function WorkspacePicker({ open, anchorRef, useWorkspaces, selectedId, onPick, onClose, createWorkspace, useDirectoryFlow, renderSlot, t }) {
|
|
2828
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WorkspacePickFlow, {
|
|
2829
|
+
t,
|
|
2830
|
+
open,
|
|
2831
|
+
anchorRef,
|
|
2832
|
+
useWorkspaces,
|
|
2833
|
+
createWorkspace,
|
|
2834
|
+
useDirectoryFlow,
|
|
2835
|
+
renderDirectoryFlow: (owner) => renderSlot("conversation.hero.workspace.directoryFlow", owner),
|
|
2836
|
+
selectedId,
|
|
2837
|
+
onPick,
|
|
2838
|
+
onClose
|
|
2839
|
+
});
|
|
2894
2840
|
}
|
|
2895
2841
|
//#endregion
|
|
2896
|
-
//#region src/client/
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
function asFacetTranslate(t) {
|
|
2906
|
-
return (key, options) => t(key, options);
|
|
2907
|
-
}
|
|
2908
|
-
/** 该 facet 贡献进徽标数字的条数。 */
|
|
2909
|
-
function facetCountTotal(s) {
|
|
2910
|
-
let total = 0;
|
|
2911
|
-
for (const entry of s.facets.values()) total += entry.fold.count;
|
|
2912
|
-
return total;
|
|
2842
|
+
//#region \0dsh-css:src/client/rows/WorkspaceBrowser.module.css.mjs
|
|
2843
|
+
const css$2 = ".QAYkWG_root{--dsh-session-list-edge-inset:var(--dsh-sidebar-inline-padding);--dsh-session-list-scrollbar-width:5px;--dsh-session-list-scrollbar-offset:2px;box-sizing:border-box;min-height:0;padding-right:var(--dsh-session-list-edge-inset);flex-direction:column;flex:1;display:flex}.QAYkWG_root.QAYkWG_rail{padding-right:0}.QAYkWG_iconButton{border-radius:var(--dsw-radius-sm);cursor:pointer;width:28px;height:28px;color:var(--dsw-alias-label-secondary);background:0 0;border:none;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}.QAYkWG_iconButton:focus-visible,.QAYkWG_searchButton:focus-visible,.QAYkWG_clearButton:focus-visible{outline:var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-offset:-2px}.QAYkWG_iconButton:hover{background:var(--dsw-alias-interactive-bg-hover)}.QAYkWG_viewOptionsMenu{min-width:200px}.QAYkWG_sectionHeader{box-sizing:border-box;border-radius:var(--dsw-radius-md);height:36px;color:var(--dsw-alias-label-tertiary);flex:none;justify-content:flex-end;align-items:center;gap:4px;margin-bottom:4px;padding-left:4px;display:flex;overflow:hidden}.QAYkWG_root:not(.QAYkWG_rail) .QAYkWG_sectionHeader{margin-top:2px;margin-right:-4px}.QAYkWG_sectionLabel{white-space:nowrap;opacity:1;visibility:visible;min-width:0;max-width:45%;transition:max-width .18s var(--ds-ease-in-out), margin-right .18s var(--ds-ease-in-out), opacity .12s var(--ds-ease-in-out), transform .18s var(--ds-ease-in-out), visibility 0s linear;flex:none;line-height:20px;overflow:hidden}.QAYkWG_sectionLabelHidden{opacity:0;visibility:hidden;max-width:0;margin-right:-4px;transition-delay:0s,0s,0s,0s,.18s;transform:translate(-4px)}.QAYkWG_searchSlot{box-sizing:border-box;min-width:0;max-width:28px;transition:max-width .18s var(--ds-ease-in-out), padding-left .18s var(--ds-ease-in-out);flex:1;align-items:center;margin-left:auto;padding-left:0;display:flex}.QAYkWG_searchSlotExpanded{max-width:100%;padding-left:0}.QAYkWG_headerActions{opacity:1;visibility:visible;max-width:60px;transition:max-width .18s var(--ds-ease-in-out), opacity .12s var(--ds-ease-in-out), transform .18s var(--ds-ease-in-out), visibility 0s linear;flex:none;align-items:center;gap:4px;display:flex;overflow:hidden}.QAYkWG_headerActionsHidden{opacity:0;visibility:hidden;pointer-events:none;max-width:0;transition-delay:0s,0s,0s,.18s;transform:translate(4px)}.QAYkWG_search{box-sizing:border-box;border-radius:var(--dsw-radius-sm);cursor:text;width:100%;height:28px;color:var(--dsw-alias-label-secondary);transition:width .18s var(--ds-ease-in-out), padding .18s var(--ds-ease-in-out), border-color .18s var(--ds-ease-in-out), background-color .18s var(--ds-ease-in-out);background:0 0;border:none;flex:none;align-items:center;gap:0;margin:0;padding:0;display:flex;overflow:hidden}.QAYkWG_searchExpanded{border:.5px solid var(--dsw-alias-border-l4);border-radius:var(--dsw-radius-md);width:calc(100% + 4px);height:30px;color:var(--dsw-alias-label-caption);background:0 0;margin-inline:-2px;padding:0 4px 0 0}.QAYkWG_searchButton{border-radius:var(--dsw-radius-sm);cursor:pointer;width:28px;height:28px;color:inherit;background:0 0;border:none;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}.QAYkWG_searchExpanded .QAYkWG_searchButton{width:28px;height:30px}.QAYkWG_searchButton:hover{background:var(--dsw-alias-interactive-bg-hover)}.QAYkWG_searchExpanded .QAYkWG_searchButton:hover{background:0 0}.QAYkWG_searchInput{opacity:0;pointer-events:none;width:0;min-width:0;color:var(--dsw-alias-label-primary);transition:opacity .12s var(--ds-ease-in-out);background:0 0;border:none;outline:none;flex:1;font-size:13px;line-height:18px}.QAYkWG_searchExpanded .QAYkWG_searchInput{opacity:1;pointer-events:auto;margin-left:-2px}.QAYkWG_searchInput::placeholder{color:var(--dsw-alias-label-tertiary)}.QAYkWG_clearButton{border-radius:var(--dsw-radius-sm);cursor:pointer;width:24px;height:24px;color:var(--dsw-alias-label-secondary);background:0 0;border:none;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}.QAYkWG_clearButton:hover{background:var(--dsw-alias-interactive-bg-hover)}.QAYkWG_rail .QAYkWG_sectionHeader{justify-content:flex-start;gap:0;margin-bottom:12px;padding-left:0}.QAYkWG_rail .QAYkWG_headerActions{max-width:none}.QAYkWG_rail .QAYkWG_iconButton{border-radius:var(--dsw-radius-md);width:36px;height:36px;color:var(--dsw-alias-label-primary)}.QAYkWG_rail .QAYkWG_search{border-radius:var(--dsw-radius-md);background:0 0;border-color:#0000;gap:0;width:36px;height:36px;margin:0 0 12px;padding:0}.QAYkWG_rail .QAYkWG_searchButton{border-radius:var(--dsw-radius-md);width:36px;height:36px;color:var(--dsw-alias-label-primary)}.QAYkWG_rail .QAYkWG_searchButton:hover{background:var(--dsw-alias-interactive-bg-hover)}.QAYkWG_listArea{min-height:0;margin-left:-4px;margin-right:calc(-1 * var(--dsh-session-list-edge-inset));flex-direction:column;flex:1;padding-left:4px;display:flex;overflow:visible}.QAYkWG_rail .QAYkWG_listArea{margin-left:0;margin-right:0;padding-left:0}.QAYkWG_treeBody{flex-direction:column;flex:1;min-height:0;display:flex;position:relative}.QAYkWG_fade{left:0;right:var(--dsh-session-list-edge-inset);background:linear-gradient(to bottom, transparent, var(--dsw-specific-sidebar-fill));pointer-events:none;height:24px;position:absolute;bottom:0}[data-platform=darwin] .QAYkWG_fade{display:none}.QAYkWG_wide{animation:QAYkWG_wide-in .2s var(--ds-ease-in-out)}@keyframes QAYkWG_wide-in{0%{opacity:0}}.QAYkWG_list{min-height:0;margin-left:-4px;margin-right:var(--dsh-session-list-scrollbar-offset);padding-left:4px;padding-right:calc(var(--dsh-session-list-edge-inset) - var(--dsh-session-list-scrollbar-width) - var(--dsh-session-list-scrollbar-offset));scrollbar-gutter:stable;flex:1;padding-bottom:16px;overflow-y:auto}.QAYkWG_flatList>*+*,.QAYkWG_searchTree>[role=treeitem]+[role=treeitem],.QAYkWG_groupSection>*+*{margin-top:2px}.QAYkWG_searchStatus{color:var(--dsw-alias-label-tertiary);padding:10px 12px;font-size:12px;line-height:18px}.QAYkWG_skeletonRow{box-sizing:border-box;align-items:flex-start;gap:8px;min-height:48px;padding:6px 8px 7px;display:flex}.QAYkWG_skeletonDot{corner-shape:round;border-radius:50%;flex:none;width:16px;height:16px}.QAYkWG_skeletonBars{flex-direction:column;flex:1;gap:6px;min-width:0;display:flex}.QAYkWG_skeletonDot,.QAYkWG_skeletonBar{background:var(--dsw-alias-bg-skeleton);animation:2s cubic-bezier(.36,0,.64,1) infinite QAYkWG_search-skeleton}.QAYkWG_skeletonBar{border-radius:var(--dsw-radius-xs);width:65%;height:16px}.QAYkWG_skeletonBarWide{width:90%;height:13px}@keyframes QAYkWG_search-skeleton{0%{opacity:1}40%{opacity:.6}80%,to{opacity:1}}.QAYkWG_groupSection{position:relative}.QAYkWG_groupSection+.QAYkWG_groupSection{margin-top:4px}.QAYkWG_listTopDropIndicator,.QAYkWG_workspaceDropBefore:before,.QAYkWG_workspaceDropAfter:after{content:\"\";z-index:1;background:linear-gradient(55deg, transparent calc(50% - 1px), var(--dsw-alias-state-business-primary) calc(50% - 1px) calc(50% + 1px), transparent calc(50% + 1px)) 0 0 / 5px 7px no-repeat, linear-gradient(125deg, transparent calc(50% - 1px), var(--dsw-alias-state-business-primary) calc(50% - 1px) calc(50% + 1px), transparent calc(50% + 1px)) 0 5px / 5px 7px no-repeat, linear-gradient(var(--dsw-alias-state-business-primary) 0 0) 4px 5px / calc(100% - 4px) 2px no-repeat;pointer-events:none;height:12px;position:absolute;left:0;right:0}.QAYkWG_listTopDropIndicator{top:-8px;left:0;right:var(--dsh-session-list-edge-inset)}.QAYkWG_listTopDropActive>.QAYkWG_workspaceDropBefore:first-child:before{display:none}.QAYkWG_workspaceDropBefore:before{top:-8px}.QAYkWG_workspaceDropAfter:after{bottom:-8px}.QAYkWG_sessionOverflowButton{border-radius:var(--dsw-radius-sm);width:100%;height:28px;padding:0 12px 0 calc(28px + var(--dsh-workspace-indent,0px));cursor:pointer;text-align:left;color:var(--dsw-alias-label-tertiary);background:0 0;border:none;font-size:12px}.QAYkWG_groupSection>.QAYkWG_sessionOverflowButton{margin-top:0}.QAYkWG_sessionOverflowButton:hover{color:var(--dsw-alias-label-secondary);background:0 0}.QAYkWG_empty{color:var(--dsw-alias-label-tertiary);padding:16px 12px;font-size:13px}.QAYkWG_emptyState{color:var(--dsw-alias-label-tertiary);flex-direction:column;align-items:center;gap:8px;margin-top:80px;padding:0 12px;font-size:13px;line-height:20px;display:flex}.QAYkWG_emptyState>svg{color:var(--dsw-alias-label-caption);margin-bottom:4px}.QAYkWG_emptyAction{cursor:pointer;color:var(--dsw-alias-link);background:0 0;border:none;padding:0;font-size:13px;line-height:20px}.QAYkWG_renameInput{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l4);border-radius:var(--dsw-radius-lg);width:100%;height:44px;color:var(--dsw-alias-label-primary);background:0 0;outline:none;padding:7px 14px;font-size:14px;font-weight:400;line-height:22px}.QAYkWG_renameInput:disabled{color:var(--dsw-alias-label-dimmed)}.QAYkWG_renameError{color:var(--dsw-alias-state-error-primary);margin-top:8px;font-size:12px;line-height:18px}.QAYkWG_deleteAction:not(:disabled){color:var(--dsw-alias-state-error-primary)}.QAYkWG_deleteStatus{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}.QAYkWG_archiveActivity{color:var(--dsw-alias-label-primary);margin:0 0 8px;padding-left:18px;font-size:13px;line-height:20px}.QAYkWG_archiveActivity li{overflow-wrap:anywhere}@media (prefers-reduced-motion:reduce){.QAYkWG_wide,.QAYkWG_skeletonDot,.QAYkWG_skeletonBar{animation:none}.QAYkWG_search,.QAYkWG_sectionLabel,.QAYkWG_searchSlot,.QAYkWG_searchInput,.QAYkWG_headerActions{transition:none}}";
|
|
2844
|
+
const tagId$2 = "dsh-group-status/WorkspaceBrowser.module.css";
|
|
2845
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
|
|
2846
|
+
const tag = document.createElement("style");
|
|
2847
|
+
tag.dataset.plugin = "dsh-group-status";
|
|
2848
|
+
tag.dataset.pluginCss = tagId$2;
|
|
2849
|
+
tag.textContent = css$2;
|
|
2850
|
+
document.head.appendChild(tag);
|
|
2913
2851
|
}
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2852
|
+
var WorkspaceBrowser_module_css_default = {
|
|
2853
|
+
"archiveActivity": "QAYkWG_archiveActivity",
|
|
2854
|
+
"clearButton": "QAYkWG_clearButton",
|
|
2855
|
+
"deleteAction": "QAYkWG_deleteAction",
|
|
2856
|
+
"deleteStatus": "QAYkWG_deleteStatus",
|
|
2857
|
+
"empty": "QAYkWG_empty",
|
|
2858
|
+
"emptyAction": "QAYkWG_emptyAction",
|
|
2859
|
+
"emptyState": "QAYkWG_emptyState",
|
|
2860
|
+
"fade": "QAYkWG_fade",
|
|
2861
|
+
"flatList": "QAYkWG_flatList",
|
|
2862
|
+
"groupSection": "QAYkWG_groupSection",
|
|
2863
|
+
"headerActions": "QAYkWG_headerActions",
|
|
2864
|
+
"headerActionsHidden": "QAYkWG_headerActionsHidden",
|
|
2865
|
+
"iconButton": "QAYkWG_iconButton",
|
|
2866
|
+
"list": "QAYkWG_list",
|
|
2867
|
+
"listArea": "QAYkWG_listArea",
|
|
2868
|
+
"listTopDropActive": "QAYkWG_listTopDropActive",
|
|
2869
|
+
"listTopDropIndicator": "QAYkWG_listTopDropIndicator",
|
|
2870
|
+
"rail": "QAYkWG_rail",
|
|
2871
|
+
"renameError": "QAYkWG_renameError",
|
|
2872
|
+
"renameInput": "QAYkWG_renameInput",
|
|
2873
|
+
"root": "QAYkWG_root",
|
|
2874
|
+
"search": "QAYkWG_search",
|
|
2875
|
+
"search-skeleton": "QAYkWG_search-skeleton",
|
|
2876
|
+
"searchButton": "QAYkWG_searchButton",
|
|
2877
|
+
"searchExpanded": "QAYkWG_searchExpanded",
|
|
2878
|
+
"searchInput": "QAYkWG_searchInput",
|
|
2879
|
+
"searchSlot": "QAYkWG_searchSlot",
|
|
2880
|
+
"searchSlotExpanded": "QAYkWG_searchSlotExpanded",
|
|
2881
|
+
"searchStatus": "QAYkWG_searchStatus",
|
|
2882
|
+
"searchTree": "QAYkWG_searchTree",
|
|
2883
|
+
"sectionHeader": "QAYkWG_sectionHeader",
|
|
2884
|
+
"sectionLabel": "QAYkWG_sectionLabel",
|
|
2885
|
+
"sectionLabelHidden": "QAYkWG_sectionLabelHidden",
|
|
2886
|
+
"sessionOverflowButton": "QAYkWG_sessionOverflowButton",
|
|
2887
|
+
"skeletonBar": "QAYkWG_skeletonBar",
|
|
2888
|
+
"skeletonBarWide": "QAYkWG_skeletonBarWide",
|
|
2889
|
+
"skeletonBars": "QAYkWG_skeletonBars",
|
|
2890
|
+
"skeletonDot": "QAYkWG_skeletonDot",
|
|
2891
|
+
"skeletonRow": "QAYkWG_skeletonRow",
|
|
2892
|
+
"treeBody": "QAYkWG_treeBody",
|
|
2893
|
+
"viewOptionsMenu": "QAYkWG_viewOptionsMenu",
|
|
2894
|
+
"wide": "QAYkWG_wide",
|
|
2895
|
+
"wide-in": "QAYkWG_wide-in",
|
|
2896
|
+
"workspaceDropAfter": "QAYkWG_workspaceDropAfter",
|
|
2897
|
+
"workspaceDropBefore": "QAYkWG_workspaceDropBefore"
|
|
2898
|
+
};
|
|
2899
|
+
/** Keep controlled input and RPC payload inside the session.search wire contract. */
|
|
2900
|
+
function sanitizeSearchQuery(value) {
|
|
2901
|
+
const withoutNul = value.replaceAll("\0", "");
|
|
2902
|
+
if (withoutNul.length <= 500) return withoutNul;
|
|
2903
|
+
let end = 500;
|
|
2904
|
+
const last = withoutNul.charCodeAt(end - 1);
|
|
2905
|
+
const next = withoutNul.charCodeAt(end);
|
|
2906
|
+
if (last >= 55296 && last <= 56319 && next >= 56320 && next <= 57343) end--;
|
|
2907
|
+
return withoutNul.slice(0, end);
|
|
2931
2908
|
}
|
|
2932
2909
|
/**
|
|
2933
|
-
*
|
|
2934
|
-
*
|
|
2935
|
-
*
|
|
2936
|
-
* 按设置过滤:**关掉的外部来源根本不在折果里**(`WorkspaceBrowser` 折叠前就按
|
|
2937
|
-
* `facet.settingKey` 过滤了登记面),所以这里不必再判设置,`aria-label` 与徽标视觉
|
|
2938
|
-
* 天然一致;宿主的行级状态仍按设置逐项过滤。
|
|
2910
|
+
* Accept the native drag at document level while a row drag is active: row
|
|
2911
|
+
* hover still owns the insertion marker, and releasing outside the list must
|
|
2912
|
+
* not be rendered as a rejected drop before dragend commits that last marker.
|
|
2939
2913
|
*/
|
|
2940
|
-
function
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2914
|
+
function useNativeDragAcceptance(active) {
|
|
2915
|
+
(0, react.useEffect)(() => {
|
|
2916
|
+
if (!active) return;
|
|
2917
|
+
const acceptDrag = (event) => {
|
|
2918
|
+
event.preventDefault();
|
|
2919
|
+
if (event.dataTransfer !== null) event.dataTransfer.dropEffect = "move";
|
|
2920
|
+
};
|
|
2921
|
+
const acceptDrop = (event) => {
|
|
2922
|
+
event.preventDefault();
|
|
2923
|
+
};
|
|
2924
|
+
document.addEventListener("dragover", acceptDrag);
|
|
2925
|
+
document.addEventListener("drop", acceptDrop);
|
|
2926
|
+
return () => {
|
|
2927
|
+
document.removeEventListener("dragover", acceptDrag);
|
|
2928
|
+
document.removeEventListener("drop", acceptDrop);
|
|
2929
|
+
};
|
|
2930
|
+
}, [active]);
|
|
2954
2931
|
}
|
|
2955
|
-
/**
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
const translate = asFacetTranslate(t);
|
|
2969
|
-
const lines = [];
|
|
2970
|
-
for (const entry of s.facets.values()) {
|
|
2971
|
-
const raw = entry.facet.label?.(entry.fold, {
|
|
2972
|
-
now,
|
|
2973
|
-
t: translate
|
|
2974
|
-
});
|
|
2975
|
-
const text = raw === void 0 || raw === "" ? `${entry.fold.count} ${t("status.needsAttention")}` : raw;
|
|
2976
|
-
lines.push({
|
|
2977
|
-
state: "warning",
|
|
2978
|
-
text,
|
|
2979
|
-
glyph: facetGlyph(entry, t, now)
|
|
2980
|
-
});
|
|
2981
|
-
}
|
|
2982
|
-
return lines;
|
|
2932
|
+
/** Apply a visible drop to the complete account without removing hidden members. */
|
|
2933
|
+
function sessionDragOrder(order, rows, drag, over) {
|
|
2934
|
+
const source = rows.find((row) => row.id === drag.sessionId);
|
|
2935
|
+
const target = rows.find((row) => row.id === over.id);
|
|
2936
|
+
if (source === void 0 || target === void 0 || source.blank || source.pinned !== drag.pinned || target.pinned !== drag.pinned || source.id === target.id || !order.includes(source.id)) return;
|
|
2937
|
+
const section = rows.filter((row) => row.pinned === drag.pinned);
|
|
2938
|
+
const sourceIndex = section.findIndex((row) => row.id === source.id);
|
|
2939
|
+
if (section.filter((row) => row.id !== source.id).findIndex((row) => row.id === target.id) + (over.half === "after" ? 1 : 0) === sourceIndex) return;
|
|
2940
|
+
const next = order.filter((id) => id !== source.id);
|
|
2941
|
+
const targetIndex = next.indexOf(target.id);
|
|
2942
|
+
if (targetIndex === -1) return;
|
|
2943
|
+
next.splice(targetIndex + (over.half === "after" ? 1 : 0), 0, source.id);
|
|
2944
|
+
return pinCurrentBlank(next, rows.find((row) => row.blank)?.id);
|
|
2983
2945
|
}
|
|
2984
|
-
/**
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
* array — the fork-specific status→line logic lives here.
|
|
2989
|
-
*
|
|
2990
|
-
* 按设置过滤:关掉的来源不出现在明细行里;`showHoverDetails=false` 时整段返回空。
|
|
2991
|
-
* 明细行的顺序与徽标**完全一致**(外部来源排在会话状态之后、已完成之前):两处顺序
|
|
2992
|
-
* 不一样会让人怀疑它们是两套事实。
|
|
2993
|
-
*/
|
|
2994
|
-
function hoverStatusLines(status, t, settings, now = Date.now()) {
|
|
2995
|
-
const lines = [];
|
|
2996
|
-
if (status === void 0 || !settings.showHoverDetails) return lines;
|
|
2997
|
-
if (settings.showPendingInBadge) {
|
|
2998
|
-
if (status.approval > 0) lines.push({
|
|
2999
|
-
state: "warning",
|
|
3000
|
-
text: `${status.approval} ${t("status.waitingApproval")}`
|
|
3001
|
-
});
|
|
3002
|
-
if (status.planReview > 0) lines.push({
|
|
3003
|
-
state: "warning",
|
|
3004
|
-
text: `${status.planReview} ${t("status.planReview")}`
|
|
3005
|
-
});
|
|
3006
|
-
if (status.question > 0) lines.push({
|
|
3007
|
-
state: "warning",
|
|
3008
|
-
text: `${status.question} ${t("status.waitingAnswer")}`
|
|
3009
|
-
});
|
|
3010
|
-
}
|
|
3011
|
-
if (settings.showRunningInBadge) {
|
|
3012
|
-
if (status.running > 0) lines.push({
|
|
3013
|
-
state: "ongoing",
|
|
3014
|
-
text: `${status.running} ${t("status.running")}`
|
|
3015
|
-
});
|
|
3016
|
-
if (status.subagents > 0) lines.push({
|
|
3017
|
-
state: "ongoing",
|
|
3018
|
-
text: t(status.subagents === 1 ? "status.subagentsRunning.one" : "status.subagentsRunning.other", { n: status.subagents })
|
|
3019
|
-
});
|
|
3020
|
-
}
|
|
3021
|
-
lines.push(...facetDetailLines(status, t, now));
|
|
3022
|
-
if (settings.showCompletedInBadge && status.done > 0) lines.push({
|
|
3023
|
-
state: "done",
|
|
3024
|
-
text: `${status.done} ${t("status.completed")}`
|
|
3025
|
-
});
|
|
3026
|
-
return lines;
|
|
2946
|
+
/** Resolve an insertion side across the Workspace header, descendants, and Sessions. */
|
|
2947
|
+
function workspaceGroupHalf(e) {
|
|
2948
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
2949
|
+
return e.clientY < rect.top + rect.height / 2 ? "before" : "after";
|
|
3027
2950
|
}
|
|
3028
2951
|
/**
|
|
3029
|
-
*
|
|
3030
|
-
*
|
|
3031
|
-
* 脉冲判据仍由各 facet 自己声明(`FacetFold.pulse`),但**作用对象必须下沉到声明它的那个
|
|
3032
|
-
* 元素**。此前 `groupBadgeIsUrgent()` 只返回一个整徽标的布尔值、class 加在容器上,于是同组
|
|
3033
|
-
* 里任何一家声明 `pulse: true` 都会让别家本该静止的图标跟着呼吸:`pulse: false` 是否生效,
|
|
3034
|
-
* 取决于同组里恰好有没有别人开了闪。判断交出去了,作用对象没交出去——这是修过一次的回归
|
|
3035
|
-
* (见下)。
|
|
2952
|
+
* Session the main view currently retains, if any.
|
|
3036
2953
|
*
|
|
3037
|
-
*
|
|
3038
|
-
*
|
|
3039
|
-
*
|
|
2954
|
+
* All three list bodies and the shell need this same scan, and `Object.values`
|
|
2955
|
+
* allocates a fresh array each time; the call sites memoise it against the list
|
|
2956
|
+
* identity rather than re-scanning on every render.
|
|
2957
|
+
* @param list - always-mounted Session list snapshot.
|
|
2958
|
+
* @returns the retained Session id, or undefined when the main view holds none.
|
|
3040
2959
|
*/
|
|
3041
|
-
function
|
|
3042
|
-
return
|
|
2960
|
+
function mainViewSessionId(list) {
|
|
2961
|
+
return Object.values(list.byId).find((session) => (session.retainedBy.mainView ?? 0) > 0)?.id;
|
|
3043
2962
|
}
|
|
2963
|
+
//#endregion
|
|
2964
|
+
//#region src/client/rows/ViewOptionsMenu.tsx
|
|
3044
2965
|
/**
|
|
3045
|
-
*
|
|
2966
|
+
* Grouping, ordering, and archived-filter menu for the section header.
|
|
3046
2967
|
*
|
|
3047
|
-
*
|
|
3048
|
-
*
|
|
2968
|
+
* Own open state so it resets with the wide chrome; the trigger button is the
|
|
2969
|
+
* menu's own anchor, so the header does not thread a callback down for it.
|
|
3049
2970
|
*/
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
})]
|
|
2971
|
+
/** Grouping, ordering, and archived-filter menu; own open state so it resets with the wide chrome. */
|
|
2972
|
+
function ViewOptionsMenu({ groupBy, orderBy, archivedFilter, onGroupPick, onOrderPick, onArchivedFilterPick, t }) {
|
|
2973
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
2974
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
|
|
2975
|
+
open,
|
|
2976
|
+
onClose: () => {
|
|
2977
|
+
setOpen(false);
|
|
2978
|
+
},
|
|
2979
|
+
items: [
|
|
2980
|
+
{
|
|
2981
|
+
type: "label",
|
|
2982
|
+
id: "group-by",
|
|
2983
|
+
text: t("groupBy.label")
|
|
2984
|
+
},
|
|
2985
|
+
{
|
|
2986
|
+
id: "workspace",
|
|
2987
|
+
label: t("groupBy.workspace"),
|
|
2988
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderCloseRegular, {})
|
|
2989
|
+
},
|
|
2990
|
+
{
|
|
2991
|
+
id: "workspace-tree",
|
|
2992
|
+
label: t("groupBy.workspaceTree"),
|
|
2993
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWorkspaceTreeOutlineRegular, {})
|
|
2994
|
+
},
|
|
2995
|
+
{
|
|
2996
|
+
id: "flat",
|
|
2997
|
+
label: t("groupBy.flat"),
|
|
2998
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFlatListOutlineRegular, {})
|
|
2999
|
+
},
|
|
3000
|
+
{
|
|
3001
|
+
type: "separator",
|
|
3002
|
+
id: "order-by-separator"
|
|
3003
|
+
},
|
|
3004
|
+
{
|
|
3005
|
+
type: "label",
|
|
3006
|
+
id: "order-by",
|
|
3007
|
+
text: t("orderBy.label")
|
|
3008
|
+
},
|
|
3009
|
+
{
|
|
3010
|
+
id: "manual",
|
|
3011
|
+
label: t("orderBy.manual"),
|
|
3012
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronsUpDownOutlineRegular, {})
|
|
3013
|
+
},
|
|
3014
|
+
{
|
|
3015
|
+
id: "updated",
|
|
3016
|
+
label: t("orderBy.updated"),
|
|
3017
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconClockOutlineRegular, {})
|
|
3018
|
+
},
|
|
3019
|
+
{
|
|
3020
|
+
type: "separator",
|
|
3021
|
+
id: "archived-filter-separator"
|
|
3022
|
+
},
|
|
3023
|
+
{
|
|
3024
|
+
type: "label",
|
|
3025
|
+
id: "filter-by",
|
|
3026
|
+
text: t("filterBy.label")
|
|
3027
|
+
},
|
|
3028
|
+
{
|
|
3029
|
+
id: "hide-archived",
|
|
3030
|
+
label: t("viewOptions.hideArchived"),
|
|
3031
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconArchiveOffOutlineRegular, {})
|
|
3032
|
+
},
|
|
3033
|
+
{
|
|
3034
|
+
id: "show-archived",
|
|
3035
|
+
label: t("viewOptions.showArchived"),
|
|
3036
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconQueueOutlineRegular, {})
|
|
3037
|
+
},
|
|
3038
|
+
{
|
|
3039
|
+
id: "only-archived",
|
|
3040
|
+
label: t("viewOptions.onlyArchived"),
|
|
3041
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconArchiveCheckOutlineRegular, {})
|
|
3042
|
+
}
|
|
3043
|
+
],
|
|
3044
|
+
selectedIds: [
|
|
3045
|
+
groupBy,
|
|
3046
|
+
orderBy,
|
|
3047
|
+
{
|
|
3048
|
+
default: "hide-archived",
|
|
3049
|
+
show: "show-archived",
|
|
3050
|
+
only: "only-archived"
|
|
3051
|
+
}[archivedFilter]
|
|
3052
|
+
],
|
|
3053
|
+
onSelect: (id) => {
|
|
3054
|
+
if (id === "workspace" || id === "workspace-tree" || id === "flat") onGroupPick(id);
|
|
3055
|
+
else if (id === "manual" || id === "updated") onOrderPick(id);
|
|
3056
|
+
else if (id === "hide-archived") onArchivedFilterPick("default");
|
|
3057
|
+
else if (id === "show-archived") onArchivedFilterPick("show");
|
|
3058
|
+
else if (id === "only-archived") onArchivedFilterPick("only");
|
|
3059
|
+
setOpen(false);
|
|
3060
|
+
},
|
|
3061
|
+
align: "end",
|
|
3062
|
+
dense: true,
|
|
3063
|
+
listClassName: WorkspaceBrowser_module_css_default.viewOptionsMenu,
|
|
3064
|
+
portal: true,
|
|
3065
|
+
anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
|
|
3066
|
+
label: t("viewOptions.label"),
|
|
3067
|
+
side: "bottom",
|
|
3068
|
+
delayMs: 500,
|
|
3069
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3070
|
+
type: "button",
|
|
3071
|
+
className: clsx(WorkspaceBrowser_module_css_default.iconButton, WorkspaceBrowser_module_css_default.wide),
|
|
3072
|
+
"aria-label": t("viewOptions.label"),
|
|
3073
|
+
onClick: () => {
|
|
3074
|
+
setOpen((v) => !v);
|
|
3075
|
+
},
|
|
3076
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSlidersTwoOutlineRegular, {})
|
|
3077
|
+
})
|
|
3078
|
+
})
|
|
3159
3079
|
});
|
|
3160
3080
|
}
|
|
3161
|
-
//#endregion
|
|
3162
|
-
//#region src/client/rows/Rows.tsx
|
|
3163
3081
|
/**
|
|
3164
|
-
*
|
|
3165
|
-
*
|
|
3166
|
-
*
|
|
3167
|
-
*
|
|
3168
|
-
*
|
|
3169
|
-
* its hover buttons are the `sidebar.workspaces.session.menu.item` and
|
|
3170
|
-
* `sidebar.workspaces.session.row.action` lists, rendered through the
|
|
3171
|
-
* browser's `renderSlot` with the menu's open state as the occurrence's hook
|
|
3172
|
-
* context; this package's own actions are entries like any plugin's. The
|
|
3173
|
-
* session and workspace hover cards are suppressed while a menu is open.
|
|
3082
|
+
* Zero-valued sentinel for {@link GroupLiveStatus}.
|
|
3083
|
+
*
|
|
3084
|
+
* Used when a group has nothing to report. Kept as a frozen constant so the
|
|
3085
|
+
* reference is stable across renders — `Object.is` comparisons in React selectors
|
|
3086
|
+
* never trigger spurious re-renders on it.
|
|
3174
3087
|
*/
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3088
|
+
const EMPTY_LIVE_STATUS = Object.freeze({
|
|
3089
|
+
running: 0,
|
|
3090
|
+
approval: 0,
|
|
3091
|
+
planReview: 0,
|
|
3092
|
+
question: 0,
|
|
3093
|
+
done: 0,
|
|
3094
|
+
subagents: 0,
|
|
3095
|
+
facets: /* @__PURE__ */ new Map()
|
|
3096
|
+
});
|
|
3181
3097
|
/**
|
|
3182
|
-
*
|
|
3183
|
-
*
|
|
3184
|
-
*
|
|
3185
|
-
* @param title - the row's clipping title element.
|
|
3186
|
-
* @param left - scroll offset in CSS pixels.
|
|
3187
|
-
* @param range - the title's maximum scroll offset in CSS pixels.
|
|
3098
|
+
* 一份**没有任何贡献**的折果:六个计数全 0 且没有外部来源。
|
|
3099
|
+
*
|
|
3100
|
+
* 它在合并里是加法单位元,所以「加它」不该产生新对象——见 {@link mergeLiveStatus}。
|
|
3188
3101
|
*/
|
|
3189
|
-
function
|
|
3190
|
-
|
|
3191
|
-
left,
|
|
3192
|
-
behavior: "instant"
|
|
3193
|
-
});
|
|
3194
|
-
else title.scrollLeft = left;
|
|
3195
|
-
if (left > 0) title.dataset.scrolled = "";
|
|
3196
|
-
else delete title.dataset.scrolled;
|
|
3197
|
-
if (left < range) title.dataset.clipped = "";
|
|
3198
|
-
else delete title.dataset.clipped;
|
|
3102
|
+
function isIdleStatus(status) {
|
|
3103
|
+
return status.running === 0 && status.approval === 0 && status.planReview === 0 && status.question === 0 && status.done === 0 && status.subagents === 0 && status.facets.size === 0;
|
|
3199
3104
|
}
|
|
3200
3105
|
/**
|
|
3201
|
-
*
|
|
3202
|
-
*
|
|
3203
|
-
*
|
|
3106
|
+
* 合并两份分组折果:本插件自己的计数相加,外部状态来源**按 facet 各自合并**。
|
|
3107
|
+
*
|
|
3108
|
+
* 用于多层聚合:折叠的父文件夹要覆盖整棵子树,而子树里每一层都已各自折过一遍。
|
|
3109
|
+
* `FacetFold.data` 怎么合归该 facet 自己决定,本模块从不解释。
|
|
3110
|
+
*
|
|
3111
|
+
* 加法单位元走**引用原样返回**:值不变时换一个新对象,会让"同一棵空闲子树"在两轮
|
|
3112
|
+
* 计算之间有不同的身份。`EMPTY_LIVE_STATUS` 留在这个模块里的理由就是身份稳定(见其
|
|
3113
|
+
* 注释),合并这一层不该每加一次零就把那个理由作废。返回的是入参本身(不改写、不
|
|
3114
|
+
* 拷贝),因此与逐字段相加的结果等值。
|
|
3204
3115
|
*/
|
|
3205
|
-
function
|
|
3206
|
-
if (
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3116
|
+
function mergeLiveStatus(a, b) {
|
|
3117
|
+
if (isIdleStatus(b)) return a;
|
|
3118
|
+
if (isIdleStatus(a)) return b;
|
|
3119
|
+
return {
|
|
3120
|
+
running: a.running + b.running,
|
|
3121
|
+
approval: a.approval + b.approval,
|
|
3122
|
+
planReview: a.planReview + b.planReview,
|
|
3123
|
+
question: a.question + b.question,
|
|
3124
|
+
done: a.done + b.done,
|
|
3125
|
+
subagents: a.subagents + b.subagents,
|
|
3126
|
+
facets: mergeFacets(a.facets, b.facets)
|
|
3127
|
+
};
|
|
3213
3128
|
}
|
|
3214
|
-
/**
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3129
|
+
/** 按 facet id 合并两侧折果;只有一侧有的来源原样带过。 */
|
|
3130
|
+
function mergeFacets(a, b) {
|
|
3131
|
+
if (a.size === 0) return b;
|
|
3132
|
+
if (b.size === 0) return a;
|
|
3133
|
+
const merged = new Map(a);
|
|
3134
|
+
for (const [id, entry] of b) {
|
|
3135
|
+
const own = merged.get(id);
|
|
3136
|
+
merged.set(id, own === void 0 ? entry : {
|
|
3137
|
+
facet: own.facet,
|
|
3138
|
+
fold: own.facet.merge?.(own.fold, entry.fold) ?? defaultMergeFold(own.fold, entry.fold)
|
|
3139
|
+
});
|
|
3140
|
+
}
|
|
3141
|
+
return merged;
|
|
3142
|
+
}
|
|
3143
|
+
/**
|
|
3144
|
+
* Fold every member row of a group into counts so a collapsed folder can hint at
|
|
3145
|
+
* hidden live sessions. Mirrors the session-row status priority (pending
|
|
3146
|
+
* outranks running outranks completed) at the counting level; labels resolve with
|
|
3147
|
+
* locale at render time.
|
|
3148
|
+
*
|
|
3149
|
+
* @param nodes - member rows of the group.
|
|
3150
|
+
* @param sessionIds - the same members' Session ids (facets address their own data by id).
|
|
3151
|
+
* @param facets - the registered status sources to fold over this group.
|
|
3152
|
+
* @param now - one clock reading for the whole pass, so sibling facets cannot disagree
|
|
3153
|
+
* about which deadlines are still ahead.
|
|
3225
3154
|
*/
|
|
3226
|
-
function
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
return;
|
|
3241
|
-
}
|
|
3242
|
-
cancelAnimationFrame(frame.current);
|
|
3243
|
-
let previous;
|
|
3244
|
-
let position = 0;
|
|
3245
|
-
const step = (now) => {
|
|
3246
|
-
position += previous === void 0 ? 0 : (now - previous) * TITLE_MARQUEE_PX_PER_MS;
|
|
3247
|
-
previous = now;
|
|
3248
|
-
placeTitle(element, Math.min(position, range), range);
|
|
3249
|
-
if (position < range) frame.current = requestAnimationFrame(step);
|
|
3250
|
-
};
|
|
3251
|
-
frame.current = requestAnimationFrame(step);
|
|
3252
|
-
},
|
|
3253
|
-
leave: () => {
|
|
3254
|
-
cancelAnimationFrame(frame.current);
|
|
3255
|
-
/* v8 ignore next -- defensive: the title span renders unconditionally. */
|
|
3256
|
-
if (title.current === null) return;
|
|
3257
|
-
restTitle(title.current);
|
|
3155
|
+
function summarizeGroupStatus(nodes, sessionIds, facets = [], now = Date.now()) {
|
|
3156
|
+
let running = 0;
|
|
3157
|
+
let approval = 0;
|
|
3158
|
+
let planReview = 0;
|
|
3159
|
+
let question = 0;
|
|
3160
|
+
let done = 0;
|
|
3161
|
+
let subagents = 0;
|
|
3162
|
+
for (const n of nodes) {
|
|
3163
|
+
if (n.blank || n.archived) continue;
|
|
3164
|
+
const hasSubagents = n.runningSubagentCount > 0;
|
|
3165
|
+
if (n.pendingInteraction === "approval") {
|
|
3166
|
+
approval += 1;
|
|
3167
|
+
subagents += n.runningSubagentCount;
|
|
3168
|
+
continue;
|
|
3258
3169
|
}
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3170
|
+
if (n.pendingInteraction === "plan-review") {
|
|
3171
|
+
planReview += 1;
|
|
3172
|
+
subagents += n.runningSubagentCount;
|
|
3173
|
+
continue;
|
|
3174
|
+
}
|
|
3175
|
+
if (n.pendingInteraction === "question") {
|
|
3176
|
+
question += 1;
|
|
3177
|
+
subagents += n.runningSubagentCount;
|
|
3178
|
+
continue;
|
|
3179
|
+
}
|
|
3180
|
+
if (n.running) {
|
|
3181
|
+
running += 1;
|
|
3182
|
+
subagents += n.runningSubagentCount;
|
|
3183
|
+
continue;
|
|
3184
|
+
}
|
|
3185
|
+
if (hasSubagents) {
|
|
3186
|
+
subagents += n.runningSubagentCount;
|
|
3187
|
+
continue;
|
|
3188
|
+
}
|
|
3189
|
+
if (n.completed) done += 1;
|
|
3190
|
+
}
|
|
3191
|
+
const folded = /* @__PURE__ */ new Map();
|
|
3192
|
+
for (const { facet } of facets) {
|
|
3193
|
+
let snapshot;
|
|
3194
|
+
try {
|
|
3195
|
+
snapshot = facet.getSnapshot();
|
|
3196
|
+
} catch {
|
|
3197
|
+
snapshot = void 0;
|
|
3198
|
+
}
|
|
3199
|
+
try {
|
|
3200
|
+
const result = facet.fold(snapshot, sessionIds, now);
|
|
3201
|
+
if (result !== void 0 && result.count > 0) folded.set(facet.id, {
|
|
3202
|
+
facet,
|
|
3203
|
+
fold: result
|
|
3204
|
+
});
|
|
3205
|
+
} catch {}
|
|
3206
|
+
}
|
|
3207
|
+
if (running === 0 && approval === 0 && planReview === 0 && question === 0 && done === 0 && subagents === 0 && folded.size === 0) return EMPTY_LIVE_STATUS;
|
|
3208
|
+
return {
|
|
3209
|
+
running,
|
|
3210
|
+
approval,
|
|
3211
|
+
planReview,
|
|
3212
|
+
question,
|
|
3213
|
+
done,
|
|
3214
|
+
subagents,
|
|
3215
|
+
facets: folded
|
|
3216
|
+
};
|
|
3270
3217
|
}
|
|
3271
3218
|
/**
|
|
3272
|
-
*
|
|
3273
|
-
*
|
|
3274
|
-
*
|
|
3219
|
+
* Measure each group's own members, as the input to {@link aggregateGroupStatuses}.
|
|
3220
|
+
*
|
|
3221
|
+
* Every group is measured, expanded ones included. A folder row shows no badge while
|
|
3222
|
+
* it is expanded, but its sessions still need attention and a collapsed ancestor must
|
|
3223
|
+
* keep counting them — so expansion is a rendering fact, never an input to the
|
|
3224
|
+
* aggregation. Short-circuiting expanded groups to `EMPTY_LIVE_STATUS` (the shape this
|
|
3225
|
+
* had before the tree-wide aggregator existed, harmless while nothing aggregated) makes
|
|
3226
|
+
* every collapsed parent undercount by the size of its expanded descendants.
|
|
3227
|
+
*
|
|
3228
|
+
* @param groups - derived groups, each with its member rows already populated.
|
|
3229
|
+
* @param facets - registered sources, already filtered by the visibility settings.
|
|
3230
|
+
* @param now - one clock reading shared by every group in the pass.
|
|
3275
3231
|
*/
|
|
3276
|
-
function
|
|
3277
|
-
const
|
|
3278
|
-
const
|
|
3279
|
-
return
|
|
3280
|
-
y: d.getFullYear(),
|
|
3281
|
-
m: d.getMonth() + 1,
|
|
3282
|
-
d: d.getDate()
|
|
3283
|
-
})} ${pad2(d.getHours())}:${pad2(d.getMinutes())}` });
|
|
3232
|
+
function directStatusesForGroups(groups, facets = [], now = Date.now()) {
|
|
3233
|
+
const map = /* @__PURE__ */ new Map();
|
|
3234
|
+
for (const group of groups) map.set(group.key, summarizeGroupStatus(group.sessions, group.sessions.map((s) => s.id), facets, now));
|
|
3235
|
+
return map;
|
|
3284
3236
|
}
|
|
3285
|
-
/**
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
}
|
|
3237
|
+
/**
|
|
3238
|
+
* Fold every group's DIRECT status up the workspace tree, so a collapsed folder
|
|
3239
|
+
* counts every descendant session. A group's own row shows no badge while it is
|
|
3240
|
+
* expanded, but its sessions still need attention and its collapsed ancestor must
|
|
3241
|
+
* keep counting them — so the walk never depends on a group's expanded state.
|
|
3242
|
+
*
|
|
3243
|
+
* @param direct - each group's own-member status, keyed by group key.
|
|
3244
|
+
* @param childrenByParent - the rendered tree: parent key (`undefined` = roots) → children.
|
|
3245
|
+
* @returns aggregated status per group key: own members plus the whole subtree.
|
|
3246
|
+
*/
|
|
3247
|
+
function aggregateGroupStatuses(direct, childrenByParent) {
|
|
3248
|
+
const aggregated = /* @__PURE__ */ new Map();
|
|
3249
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
3250
|
+
const fold = (key) => {
|
|
3251
|
+
const cached = aggregated.get(key);
|
|
3252
|
+
if (cached !== void 0) return cached;
|
|
3253
|
+
if (visiting.has(key)) return direct.get(key) ?? EMPTY_LIVE_STATUS;
|
|
3254
|
+
visiting.add(key);
|
|
3255
|
+
let total = direct.get(key) ?? EMPTY_LIVE_STATUS;
|
|
3256
|
+
for (const child of childrenByParent.get(key) ?? []) total = mergeLiveStatus(total, fold(child.key));
|
|
3257
|
+
visiting.delete(key);
|
|
3258
|
+
aggregated.set(key, total);
|
|
3259
|
+
return total;
|
|
3260
|
+
};
|
|
3261
|
+
for (const children of childrenByParent.values()) for (const child of children) fold(child.key);
|
|
3262
|
+
return aggregated;
|
|
3309
3263
|
}
|
|
3310
|
-
/**
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3264
|
+
/**
|
|
3265
|
+
* Attach each group's subtree-wide status, in derivation order. This is the producer
|
|
3266
|
+
* the sidebar renders from.
|
|
3267
|
+
*
|
|
3268
|
+
* Expanded groups carry their status too: their own row draws no badge (a decision
|
|
3269
|
+
* inside `groupLiveBadge`), but a collapsed ancestor still has to count them, so
|
|
3270
|
+
* expansion never suppresses the measurement.
|
|
3271
|
+
*
|
|
3272
|
+
* @param groups - derived rows, in the order they render.
|
|
3273
|
+
* @param direct - each group's own-member status, from {@link directStatusesForGroups}.
|
|
3274
|
+
* @param childrenByParent - the rendered tree: parent key (`undefined` = roots) → children.
|
|
3275
|
+
*/
|
|
3276
|
+
function withAggregatedStatus(groups, direct, childrenByParent) {
|
|
3277
|
+
const aggregated = aggregateGroupStatuses(direct, childrenByParent);
|
|
3278
|
+
return groups.map((group) => ({
|
|
3279
|
+
...group,
|
|
3280
|
+
liveStatus: aggregated.get(group.key) ?? EMPTY_LIVE_STATUS
|
|
3281
|
+
}));
|
|
3314
3282
|
}
|
|
3315
3283
|
/**
|
|
3316
|
-
*
|
|
3317
|
-
*
|
|
3318
|
-
*
|
|
3319
|
-
*
|
|
3320
|
-
* @param props.group - derived group node.
|
|
3321
|
-
* @param props.containsCurrentDescendant - highlight an ancestor even when its subtree is collapsed.
|
|
3322
|
-
* @param props.onToggle - expand/collapse the group.
|
|
3323
|
-
* @param props.onCreate - start a frontend Session inside this Workspace.
|
|
3324
|
-
* @param props.drag - optional workspace-row drag wiring.
|
|
3325
|
-
* @param props.home - host account home for POSIX hover-path abbreviation.
|
|
3326
|
-
* @param props.t - the browser root's locale seat.
|
|
3327
|
-
* @returns the row element.
|
|
3284
|
+
* The blank row plus the first `watermark` non-blank rows, in list order.
|
|
3285
|
+
* @param sessions - one group's rows, whose blank placeholder(s) lead the list
|
|
3286
|
+
* (`sectionMembers` puts every blank session ahead of the pinned/ordinary partitions).
|
|
3287
|
+
* @param watermark - highest non-blank row index that stays visible; `0` keeps only blank rows.
|
|
3328
3288
|
*/
|
|
3329
|
-
function
|
|
3330
|
-
const
|
|
3331
|
-
|
|
3332
|
-
const
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
className: clsx(Rows_module_css_default.projectRow, menuOpen && Rows_module_css_default.menuOpen),
|
|
3346
|
-
"data-row-key": `workspace:${group.key}`,
|
|
3347
|
-
role: "treeitem",
|
|
3348
|
-
"aria-expanded": row.expanded,
|
|
3349
|
-
onClick: onToggle,
|
|
3350
|
-
draggable: drag !== void 0,
|
|
3351
|
-
onDragStart: drag === void 0 ? void 0 : (e) => {
|
|
3352
|
-
e.dataTransfer.effectAllowed = "move";
|
|
3353
|
-
e.dataTransfer.setData("text/plain", row.key);
|
|
3354
|
-
drag.start();
|
|
3355
|
-
},
|
|
3356
|
-
onDragEnd: drag?.end,
|
|
3357
|
-
children: [
|
|
3358
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3359
|
-
className: clsx(Rows_module_css_default.slot, Rows_module_css_default.folder, active && Rows_module_css_default.folderActive),
|
|
3360
|
-
children: row.expanded ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderOpenRegular, {}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderCloseRegular, {})
|
|
3361
|
-
}),
|
|
3362
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3363
|
-
className: clsx(Rows_module_css_default.slot, Rows_module_css_default.chevron),
|
|
3364
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTriangleRightFillRegular, { className: clsx(Rows_module_css_default.arrow, row.expanded && Rows_module_css_default.arrowOpen) })
|
|
3365
|
-
}),
|
|
3366
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3367
|
-
className: Rows_module_css_default.projectText,
|
|
3368
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3369
|
-
className: Rows_module_css_default.title,
|
|
3370
|
-
children: label
|
|
3371
|
-
})
|
|
3372
|
-
}),
|
|
3373
|
-
groupLiveBadge(group, t, settings, now),
|
|
3374
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
3375
|
-
className: Rows_module_css_default.rowActions,
|
|
3376
|
-
children: [actions !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
|
|
3377
|
-
open: menuOpen,
|
|
3378
|
-
onClose: () => {
|
|
3379
|
-
setMenuOpen(false);
|
|
3380
|
-
},
|
|
3381
|
-
items: workspaceMenuItems,
|
|
3382
|
-
onSelect: (id) => {
|
|
3383
|
-
setMenuOpen(false);
|
|
3384
|
-
/* v8 ignore next -- Menu can emit only the rename and delete rows supplied above. */
|
|
3385
|
-
if (id !== "rename" && id !== "delete") return;
|
|
3386
|
-
if (id === "rename") actions.rename();
|
|
3387
|
-
else actions.delete();
|
|
3388
|
-
},
|
|
3389
|
-
portal: true,
|
|
3390
|
-
closeOnPointerLeave: true,
|
|
3391
|
-
anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3392
|
-
type: "button",
|
|
3393
|
-
className: Rows_module_css_default.iconButton,
|
|
3394
|
-
"aria-label": t("actions.workspace.aria", { name: label }),
|
|
3395
|
-
onClick: (e) => {
|
|
3396
|
-
e.stopPropagation();
|
|
3397
|
-
setMenuOpen((v) => !v);
|
|
3398
|
-
},
|
|
3399
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEllipsisOutlineRegular, {})
|
|
3400
|
-
})
|
|
3401
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
|
|
3402
|
-
label: t("actions.newSession"),
|
|
3403
|
-
shortcutKeys: newShortcut?.keys,
|
|
3404
|
-
side: "bottom",
|
|
3405
|
-
align: "end",
|
|
3406
|
-
delayMs: 500,
|
|
3407
|
-
portal: true,
|
|
3408
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3409
|
-
type: "button",
|
|
3410
|
-
className: Rows_module_css_default.iconButton,
|
|
3411
|
-
"aria-keyshortcuts": newShortcut?.aria,
|
|
3412
|
-
"aria-label": t("actions.newSession.aria", { name: label }),
|
|
3413
|
-
onClick: (e) => {
|
|
3414
|
-
e.stopPropagation();
|
|
3415
|
-
onCreate();
|
|
3416
|
-
},
|
|
3417
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconNewChatOutlineRegular, {})
|
|
3418
|
-
})
|
|
3419
|
-
})]
|
|
3420
|
-
})
|
|
3421
|
-
]
|
|
3422
|
-
});
|
|
3423
|
-
if (row.createdAt === void 0) return ownRow;
|
|
3424
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.HoverCard, {
|
|
3425
|
-
anchor: ownRow,
|
|
3426
|
-
content: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WorkspaceHoverContent, {
|
|
3427
|
-
label: row.label,
|
|
3428
|
-
cwd: row.cwd === void 0 ? void 0 : abbreviateHomePath(row.cwd, home),
|
|
3429
|
-
createdAt: row.createdAt,
|
|
3430
|
-
status: row.expanded ? void 0 : row.liveStatus,
|
|
3431
|
-
settings,
|
|
3432
|
-
now: now ?? Date.now(),
|
|
3433
|
-
t
|
|
3434
|
-
}),
|
|
3435
|
-
openDelayMs: 800,
|
|
3436
|
-
grouped: true,
|
|
3437
|
-
disabled: menuOpen,
|
|
3438
|
-
copyText: row.cwd,
|
|
3439
|
-
copyLabel: t("copy"),
|
|
3440
|
-
copiedLabel: t("hover.copied")
|
|
3441
|
-
});
|
|
3442
|
-
}
|
|
3443
|
-
/* v8 ignore next 3 -- closed-union backstop; only reached if the status is forged */
|
|
3444
|
-
function assertNever(value) {
|
|
3445
|
-
throw new Error(`unknown pending interaction: ${String(value)}`);
|
|
3289
|
+
function watermarkRows(sessions, watermark) {
|
|
3290
|
+
const rows = [];
|
|
3291
|
+
let count = 0;
|
|
3292
|
+
for (const session of sessions) {
|
|
3293
|
+
if (session.blank) {
|
|
3294
|
+
rows.push(session);
|
|
3295
|
+
continue;
|
|
3296
|
+
}
|
|
3297
|
+
if (count >= watermark) break;
|
|
3298
|
+
count += 1;
|
|
3299
|
+
rows.push(session);
|
|
3300
|
+
}
|
|
3301
|
+
return {
|
|
3302
|
+
rows,
|
|
3303
|
+
hiddenCount: sessions.length - rows.length
|
|
3304
|
+
};
|
|
3446
3305
|
}
|
|
3447
3306
|
/**
|
|
3448
|
-
*
|
|
3449
|
-
*
|
|
3307
|
+
* Smallest watermark that covers every non-blank row within the automatic maximum that is running, has a running
|
|
3308
|
+
* child, awaits the user, carries an unread completion, or is current, and the reveal target at any position.
|
|
3309
|
+
* @param sessions - one group's ordered Session rows.
|
|
3310
|
+
* @param current - Session retained by the main view, if the sidebar is the active surface.
|
|
3311
|
+
* @param revealSessionId - Session selected from search and required to be visible.
|
|
3312
|
+
* @returns the group's baseline non-blank row count.
|
|
3450
3313
|
*/
|
|
3451
|
-
function
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
case "approval":
|
|
3459
|
-
pending = {
|
|
3460
|
-
state: "warning",
|
|
3461
|
-
label: t("status.waitingApproval"),
|
|
3462
|
-
trailingLabel: t("status.compact.approval")
|
|
3463
|
-
};
|
|
3464
|
-
break;
|
|
3465
|
-
case "plan-review":
|
|
3466
|
-
pending = {
|
|
3467
|
-
state: "warning",
|
|
3468
|
-
label: t("status.planReview"),
|
|
3469
|
-
trailingLabel: t("status.compact.planReview")
|
|
3470
|
-
};
|
|
3471
|
-
break;
|
|
3472
|
-
case "question":
|
|
3473
|
-
pending = {
|
|
3474
|
-
state: "warning",
|
|
3475
|
-
label: t("status.waitingAnswer"),
|
|
3476
|
-
trailingLabel: t("status.compact.answer")
|
|
3477
|
-
};
|
|
3478
|
-
break;
|
|
3479
|
-
case void 0: break;
|
|
3480
|
-
/* v8 ignore next -- closed PendingInteractionStatus union */
|
|
3481
|
-
default: return assertNever(node.pendingInteraction);
|
|
3482
|
-
}
|
|
3483
|
-
if (pending !== void 0) return subagents === void 0 ? [pending] : [pending, subagents];
|
|
3484
|
-
if (node.running) {
|
|
3485
|
-
const primary = {
|
|
3486
|
-
state: "ongoing",
|
|
3487
|
-
label: t("status.running")
|
|
3488
|
-
};
|
|
3489
|
-
return subagents === void 0 ? [primary] : [primary, subagents];
|
|
3314
|
+
function requiredWatermark(sessions, current, revealSessionId) {
|
|
3315
|
+
let index = 0;
|
|
3316
|
+
let required = 0;
|
|
3317
|
+
for (const session of sessions) {
|
|
3318
|
+
if (session.blank) continue;
|
|
3319
|
+
index += 1;
|
|
3320
|
+
if (index <= 50 && (session.running || session.runningSubagentCount > 0 || session.pendingInteraction !== void 0 || session.completed || session.id === current) || session.id === revealSessionId) required = index;
|
|
3490
3321
|
}
|
|
3491
|
-
|
|
3492
|
-
if (node.completed) return [{
|
|
3493
|
-
state: "done",
|
|
3494
|
-
label: t("status.completed")
|
|
3495
|
-
}];
|
|
3496
|
-
return [{
|
|
3497
|
-
state: "idle",
|
|
3498
|
-
label: t("status.idle")
|
|
3499
|
-
}];
|
|
3500
|
-
}
|
|
3501
|
-
/** Primary status dot plus every status's screen-reader label, shared by the search and session rows. */
|
|
3502
|
-
function SessionStatusDots({ statuses }) {
|
|
3503
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: statuses[0].state }), statuses.map((status) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3504
|
-
className: Rows_module_css_default.visuallyHidden,
|
|
3505
|
-
children: status.label
|
|
3506
|
-
}, status.label))] });
|
|
3507
|
-
}
|
|
3508
|
-
/** Non-interactive pinned-row marker; the enclosing row remains the only action. */
|
|
3509
|
-
function PinnedIndicator({ t }) {
|
|
3510
|
-
const label = t("row.pinned");
|
|
3511
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3512
|
-
className: Rows_module_css_default.pinIndicator,
|
|
3513
|
-
role: "img",
|
|
3514
|
-
"aria-label": label,
|
|
3515
|
-
title: label,
|
|
3516
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPinFillRegular, { size: 14 })
|
|
3517
|
-
});
|
|
3322
|
+
return required;
|
|
3518
3323
|
}
|
|
3519
3324
|
/**
|
|
3520
|
-
*
|
|
3521
|
-
*
|
|
3522
|
-
*
|
|
3325
|
+
* Raise each listed group's watermark to its requirement; returns `marks` itself when nothing rises.
|
|
3326
|
+
* @param marks - watermarks stored by the current browser tree.
|
|
3327
|
+
* @param required - required baselines, in group order.
|
|
3328
|
+
* @returns either the unchanged input or a new object containing only raised groups.
|
|
3523
3329
|
*/
|
|
3524
|
-
function
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
}),
|
|
3533
|
-
!node.blank && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3534
|
-
className: Rows_module_css_default.hoverTime,
|
|
3535
|
-
children: hoverTimeLabel(node.updatedAt, now, t)
|
|
3536
|
-
}),
|
|
3537
|
-
renderSlot("sidebar.session.row.hover", { sessionId: node.id }),
|
|
3538
|
-
statuses.map((status) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3539
|
-
className: Rows_module_css_default.hoverStatus,
|
|
3540
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: status.state }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: status.label })]
|
|
3541
|
-
}, status.label)),
|
|
3542
|
-
node.archived && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3543
|
-
className: clsx(Rows_module_css_default.hoverStatus, Rows_module_css_default.hoverArchived),
|
|
3544
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconArchiveOutlineRegular, { size: 14 }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("row.archived") })]
|
|
3545
|
-
})
|
|
3546
|
-
]
|
|
3547
|
-
});
|
|
3330
|
+
function raiseWatermarks(marks, required) {
|
|
3331
|
+
let raised;
|
|
3332
|
+
for (const [key, watermark] of required) {
|
|
3333
|
+
if (watermark <= ((raised ?? marks)[key] ?? 5)) continue;
|
|
3334
|
+
raised ??= { ...marks };
|
|
3335
|
+
raised[key] = watermark;
|
|
3336
|
+
}
|
|
3337
|
+
return raised ?? marks;
|
|
3548
3338
|
}
|
|
3339
|
+
//#endregion
|
|
3340
|
+
//#region src/client/use-registered-facets.ts
|
|
3549
3341
|
/**
|
|
3550
|
-
*
|
|
3551
|
-
*
|
|
3552
|
-
*
|
|
3553
|
-
*
|
|
3554
|
-
*
|
|
3555
|
-
*
|
|
3556
|
-
*
|
|
3557
|
-
*
|
|
3558
|
-
*
|
|
3559
|
-
*
|
|
3342
|
+
* 把登记面读成「这一轮渲染该折哪些来源」的**唯一**一处接线。
|
|
3343
|
+
*
|
|
3344
|
+
* ## 为什么单独成文
|
|
3345
|
+
*
|
|
3346
|
+
* 这一步此前直接写在 `rows/WorkspaceBrowser.tsx` 里,两行:
|
|
3347
|
+
*
|
|
3348
|
+
* ```ts
|
|
3349
|
+
* useSyncExternalStore(facetsStore.subscribe, facetsStore.getVersion, …) // 返回值丢掉
|
|
3350
|
+
* const facets = useMemo(() => visibleFacets(facetsStore.list(), settings), [settings])
|
|
3351
|
+
* ```
|
|
3352
|
+
*
|
|
3353
|
+
* 看着没问题——订阅了、也 memo 了。实际是**订阅通知只重渲染、不重算**:
|
|
3354
|
+
* `useSyncExternalStore` 的返回值被丢掉,`useMemo` 的依赖里就没有任何东西随登记面变化,
|
|
3355
|
+
* 于是那次重渲染原样拿回**上一次**的登记面。提供方在自己的 `apply` 里注册,而宿主不保证
|
|
3356
|
+
* `apply` 与本组件首渲染的先后:注册**晚于**首渲染时,facet 明明登记上了,却永远进不了
|
|
3357
|
+
* 徽标——不报错、不红测试(能渲染这条路径的那份上游 spec 在本仓库是跳过的,见
|
|
3358
|
+
* `vitest.config.ts` 的 `MONOREPO_ONLY_SPECS`),只能靠刷新页面碰运气。
|
|
3359
|
+
*
|
|
3360
|
+
* 版本号必须**同时**当两件事用:① 触发重渲染的信号,② memo 的依赖。这里把它收进一个
|
|
3361
|
+
* hook,让「只用①、丢掉②」在结构上写不出来,并且让这条接缝能被 `@testing-library/react`
|
|
3362
|
+
* 直接覆盖(本文件零宿主 import,宿主依赖闭包进不来)。
|
|
3363
|
+
*
|
|
3364
|
+
* ## 为什么现在不再按设置筛
|
|
3365
|
+
*
|
|
3366
|
+
* 旧版本这里会过一道 `visibleFacets(list, settings)`,按 `facet.settingKey` 查本插件的
|
|
3367
|
+
* 布尔开关。那意味着**本插件的配置面里写着别人家的语义**(`showScheduledInBadge` /
|
|
3368
|
+
* `showWaitingReviewInBadge`,连图标形状都替对方写死),与"本插件不认识任何来源"直接
|
|
3369
|
+
* 矛盾。现在外部来源的显隐归**来源自己**:它想被关掉就不注册 facet,登记面即所见。
|
|
3370
|
+
* 本插件的开关只管自己那三档会话状态(见 `badge-settings.ts`)。
|
|
3371
|
+
*
|
|
3372
|
+
* @module dsh-group-status/client/use-registered-facets
|
|
3560
3373
|
*/
|
|
3561
|
-
function SearchResultItem({ result, currentId, onOpen, onUnarchive, t }) {
|
|
3562
|
-
const selected = result.id === currentId;
|
|
3563
|
-
const statuses = sessionStatuses(result, t);
|
|
3564
|
-
const primaryStatus = statuses[0];
|
|
3565
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3566
|
-
className: clsx(Rows_module_css_default.searchResultRow, selected && Rows_module_css_default.selected, result.archived && Rows_module_css_default.archived),
|
|
3567
|
-
role: "treeitem",
|
|
3568
|
-
"aria-selected": selected,
|
|
3569
|
-
"aria-description": result.archived ? t("toast.archivedNotOpenable") : void 0,
|
|
3570
|
-
onClick: () => {
|
|
3571
|
-
onOpen(result.id);
|
|
3572
|
-
},
|
|
3573
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
3574
|
-
className: Rows_module_css_default.searchResultHeading,
|
|
3575
|
-
children: [
|
|
3576
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3577
|
-
className: Rows_module_css_default.slot,
|
|
3578
|
-
children: !result.archived && primaryStatus.state !== "idle" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionStatusDots, { statuses })
|
|
3579
|
-
}),
|
|
3580
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3581
|
-
className: Rows_module_css_default.searchResultTitle,
|
|
3582
|
-
children: result.title || t("session.untitled")
|
|
3583
|
-
}),
|
|
3584
|
-
result.archived && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3585
|
-
className: Rows_module_css_default.rowActions,
|
|
3586
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
|
|
3587
|
-
label: t("actions.unarchive"),
|
|
3588
|
-
side: "bottom",
|
|
3589
|
-
align: "end",
|
|
3590
|
-
delayMs: 500,
|
|
3591
|
-
portal: true,
|
|
3592
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3593
|
-
type: "button",
|
|
3594
|
-
className: Rows_module_css_default.iconButton,
|
|
3595
|
-
"aria-label": t("menu.unarchiveSession"),
|
|
3596
|
-
onClick: (e) => {
|
|
3597
|
-
e.stopPropagation();
|
|
3598
|
-
onUnarchive(result.id);
|
|
3599
|
-
},
|
|
3600
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconUnarchiveOutlineRegular, { size: 14 })
|
|
3601
|
-
})
|
|
3602
|
-
})
|
|
3603
|
-
})
|
|
3604
|
-
]
|
|
3605
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
3606
|
-
className: Rows_module_css_default.searchResultMeta,
|
|
3607
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3608
|
-
className: Rows_module_css_default.searchResultWorkspace,
|
|
3609
|
-
children: result.workspace || t("group.ungrouped")
|
|
3610
|
-
}), result.snippet !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3611
|
-
className: Rows_module_css_default.searchResultSnippet,
|
|
3612
|
-
children: result.snippet
|
|
3613
|
-
})]
|
|
3614
|
-
})]
|
|
3615
|
-
});
|
|
3616
|
-
}
|
|
3617
3374
|
/**
|
|
3618
|
-
*
|
|
3619
|
-
*
|
|
3620
|
-
*
|
|
3621
|
-
*
|
|
3622
|
-
*
|
|
3623
|
-
* neither marker renders there, and its live status stays on the hover card.
|
|
3624
|
-
* @param props.node - derived session node.
|
|
3625
|
-
* @param props.currentId - selected session id (row highlight).
|
|
3626
|
-
* @param props.now - epoch ms for relative-time formatting.
|
|
3627
|
-
* @param props.onOpen - open a session by id.
|
|
3628
|
-
* @param props.onRenameRequest - open the rename dialog from a title double-click (id + current title).
|
|
3629
|
-
* @param props.renderSlot - child-seat renderer for the row's action lists
|
|
3630
|
-
* (`sidebar.workspaces.session.menu.item` / `sidebar.workspaces.session.row.action`),
|
|
3631
|
-
* its leading decoration, and its hover-card section.
|
|
3632
|
-
* @param props.onReveal - scroll this row into view after search navigation, then acknowledge it.
|
|
3633
|
-
* @param props.drag - optional row-drag target wiring; blank rows cannot start a drag.
|
|
3634
|
-
* @param props.t - the browser root's locale seat.
|
|
3635
|
-
* @returns the session row.
|
|
3375
|
+
* 当前登记的来源,按登记面顺序(已按 `order` 排好)。
|
|
3376
|
+
*
|
|
3377
|
+
* 引用在版本号不变时保持稳定:登记面是低频事件,重算成本 = O(facet × 组内会话)。
|
|
3378
|
+
*
|
|
3379
|
+
* @param store - facet 消费侧存储(`facetStore()` 单例)。
|
|
3636
3380
|
*/
|
|
3637
|
-
function
|
|
3638
|
-
const
|
|
3639
|
-
|
|
3640
|
-
const selected = node.id === currentId;
|
|
3641
|
-
const statuses = sessionStatuses(node, t);
|
|
3642
|
-
const primaryStatus = statuses[0];
|
|
3643
|
-
const showStatus = primaryStatus.state !== "idle";
|
|
3644
|
-
const draggable = drag !== void 0 && !row.blank && !row.archived;
|
|
3645
|
-
const [menuOpen, setMenuOpen] = (0, react.useState)(false);
|
|
3646
|
-
const menuOpenState = (0, react.useMemo)(() => [menuOpen, setMenuOpen], [menuOpen]);
|
|
3647
|
-
const rowRef = (0, react.useRef)(null);
|
|
3648
|
-
const titleRef = (0, react.useRef)(null);
|
|
3649
|
-
const marquee = useTitleMarquee(titleRef);
|
|
3650
|
-
(0, react.useEffect)(() => {
|
|
3651
|
-
if (onReveal === void 0) return;
|
|
3652
|
-
rowRef.current?.scrollIntoView({ block: "nearest" });
|
|
3653
|
-
onReveal();
|
|
3654
|
-
}, [onReveal]);
|
|
3655
|
-
const ownRow = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3656
|
-
ref: rowRef,
|
|
3657
|
-
"data-row-key": `session:${node.id}`,
|
|
3658
|
-
className: clsx(Rows_module_css_default.sessionRow, selected && Rows_module_css_default.selected, menuOpen && Rows_module_css_default.menuOpen, row.archived && Rows_module_css_default.archived, drag?.marker === "before" && Rows_module_css_default.dropBefore, drag?.marker === "after" && Rows_module_css_default.dropAfter),
|
|
3659
|
-
role: "treeitem",
|
|
3660
|
-
"aria-selected": selected,
|
|
3661
|
-
"aria-description": row.archived ? t("toast.archivedNotOpenable") : void 0,
|
|
3662
|
-
onClick: () => {
|
|
3663
|
-
onOpen(node.id);
|
|
3664
|
-
},
|
|
3665
|
-
onPointerEnter: marquee.enter,
|
|
3666
|
-
onPointerLeave: marquee.leave,
|
|
3667
|
-
draggable,
|
|
3668
|
-
onDragStart: !draggable ? void 0 : (e) => {
|
|
3669
|
-
e.dataTransfer.effectAllowed = "move";
|
|
3670
|
-
e.dataTransfer.setData("text/plain", node.id);
|
|
3671
|
-
drag.start();
|
|
3672
|
-
},
|
|
3673
|
-
onDragEnd: !draggable ? void 0 : drag.end,
|
|
3674
|
-
onDragOver: drag === void 0 ? void 0 : (e) => {
|
|
3675
|
-
if (!drag.active) return;
|
|
3676
|
-
e.preventDefault();
|
|
3677
|
-
e.dataTransfer.dropEffect = "move";
|
|
3678
|
-
drag.hover(rowHalf(e));
|
|
3679
|
-
},
|
|
3680
|
-
onDrop: drag === void 0 ? void 0 : (e) => {
|
|
3681
|
-
if (!drag.active) return;
|
|
3682
|
-
e.preventDefault();
|
|
3683
|
-
drag.drop(rowHalf(e));
|
|
3684
|
-
},
|
|
3685
|
-
children: [
|
|
3686
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3687
|
-
className: Rows_module_css_default.slot,
|
|
3688
|
-
children: !row.archived && !row.blank && (showStatus ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionStatusDots, { statuses }) : renderSlot("sidebar.session.row.leading", { sessionId: node.id }))
|
|
3689
|
-
}),
|
|
3690
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3691
|
-
ref: titleRef,
|
|
3692
|
-
className: Rows_module_css_default.title,
|
|
3693
|
-
onDoubleClick: row.blank ? void 0 : (e) => {
|
|
3694
|
-
e.stopPropagation();
|
|
3695
|
-
onRenameRequest(node.id, row.title);
|
|
3696
|
-
},
|
|
3697
|
-
children: title
|
|
3698
|
-
}),
|
|
3699
|
-
!row.blank && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3700
|
-
className: Rows_module_css_default.time,
|
|
3701
|
-
"aria-hidden": primaryStatus.trailingLabel === void 0 ? void 0 : true,
|
|
3702
|
-
children: primaryStatus.trailingLabel ?? timeLabel(row.updatedAt, now, t)
|
|
3703
|
-
}),
|
|
3704
|
-
row.pinned && !row.archived && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PinnedIndicator, { t }),
|
|
3705
|
-
!row.blank && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
3706
|
-
className: Rows_module_css_default.rowActions,
|
|
3707
|
-
onClick: (e) => {
|
|
3708
|
-
e.stopPropagation();
|
|
3709
|
-
},
|
|
3710
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
|
|
3711
|
-
open: menuOpen,
|
|
3712
|
-
onClose: () => {
|
|
3713
|
-
setMenuOpen(false);
|
|
3714
|
-
},
|
|
3715
|
-
portal: true,
|
|
3716
|
-
closeOnPointerLeave: true,
|
|
3717
|
-
anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3718
|
-
type: "button",
|
|
3719
|
-
className: Rows_module_css_default.iconButton,
|
|
3720
|
-
"aria-label": t("actions.session.aria", { name: title }),
|
|
3721
|
-
onClick: () => {
|
|
3722
|
-
setMenuOpen((v) => !v);
|
|
3723
|
-
},
|
|
3724
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEllipsisOutlineRegular, {})
|
|
3725
|
-
}),
|
|
3726
|
-
children: renderSlot("sidebar.workspaces.session.menu.item", {
|
|
3727
|
-
sessionId: node.id,
|
|
3728
|
-
displayTitle: row.title
|
|
3729
|
-
}, { hookContext: menuOpenState })
|
|
3730
|
-
}), renderSlot("sidebar.workspaces.session.row.action", {
|
|
3731
|
-
sessionId: node.id,
|
|
3732
|
-
displayTitle: row.title
|
|
3733
|
-
})]
|
|
3734
|
-
})
|
|
3735
|
-
]
|
|
3736
|
-
});
|
|
3737
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.HoverCard, {
|
|
3738
|
-
anchor: ownRow,
|
|
3739
|
-
content: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionHoverContent, {
|
|
3740
|
-
node,
|
|
3741
|
-
now,
|
|
3742
|
-
renderSlot,
|
|
3743
|
-
t
|
|
3744
|
-
}),
|
|
3745
|
-
openDelayMs: 800,
|
|
3746
|
-
grouped: true,
|
|
3747
|
-
disabled: menuOpen || drag?.active === true,
|
|
3748
|
-
copyText: row.blank || row.title === "" ? void 0 : row.title,
|
|
3749
|
-
copyLabel: t("copy"),
|
|
3750
|
-
copiedLabel: t("hover.copied")
|
|
3751
|
-
});
|
|
3381
|
+
function useRegisteredFacets(store) {
|
|
3382
|
+
const version = (0, react.useSyncExternalStore)(store.subscribe, store.getVersion, store.getServerVersion);
|
|
3383
|
+
return (0, react.useMemo)(() => [...store.list()], [version]);
|
|
3752
3384
|
}
|
|
3753
3385
|
//#endregion
|
|
3754
|
-
//#region
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3386
|
+
//#region src/client/badge-labels.ts
|
|
3387
|
+
/**
|
|
3388
|
+
* 把侧栏行的翻译位交给 facet。
|
|
3389
|
+
*
|
|
3390
|
+
* 这里有一次**故意的放宽**:协议的 `t` 收任意字符串键,而行位只收本插件 `workspace`
|
|
3391
|
+
* 命名空间的键。放宽是安全的——宿主 locale 服务对未注册的键一律**原样回显键名**(不是
|
|
3392
|
+
* 抛错),所以外来 facet 传了自己命名空间的键,最坏读出的是那串键名而不是崩渲染。
|
|
3393
|
+
* 要正确本地化,provider 应当闭包自己的翻译函数、忽略这里传入的 `t`。
|
|
3394
|
+
*/
|
|
3395
|
+
function asFacetTranslate(t) {
|
|
3396
|
+
return (key, options) => t(key, options);
|
|
3763
3397
|
}
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
const ROW_GLIDE_MS = 200;
|
|
3770
|
-
function sameRows(previous, next) {
|
|
3771
|
-
return previous.rowKeys.length === next.rowKeys.length && previous.rowKeys.every((key, index) => key === next.rowKeys[index]);
|
|
3398
|
+
/** 该 facet 贡献进徽标数字的条数。 */
|
|
3399
|
+
function facetCountTotal(s) {
|
|
3400
|
+
let total = 0;
|
|
3401
|
+
for (const entry of s.facets.values()) total += entry.fold.count;
|
|
3402
|
+
return total;
|
|
3772
3403
|
}
|
|
3773
|
-
|
|
3774
|
-
|
|
3404
|
+
/**
|
|
3405
|
+
* 每个外部状态来源的那句话,按登记顺序。
|
|
3406
|
+
*
|
|
3407
|
+
* facet 没实现 `label` 时**不能**退化成 `2 dsh-later.schedule` 这种读法,所以给一句
|
|
3408
|
+
* 通用措辞兜底:只说有几个来源在响,具体语义等该来源自己补文案。
|
|
3409
|
+
*/
|
|
3410
|
+
function facetLines(s, t, now) {
|
|
3411
|
+
const translate = asFacetTranslate(t);
|
|
3412
|
+
const lines = [];
|
|
3413
|
+
for (const entry of s.facets.values()) {
|
|
3414
|
+
const text = entry.facet.label?.(entry.fold, {
|
|
3415
|
+
now,
|
|
3416
|
+
t: translate
|
|
3417
|
+
});
|
|
3418
|
+
lines.push(text === void 0 || text === "" ? `${entry.fold.count} ${t("status.needsAttention")}` : text);
|
|
3419
|
+
}
|
|
3420
|
+
return lines;
|
|
3775
3421
|
}
|
|
3776
3422
|
/**
|
|
3777
|
-
*
|
|
3778
|
-
*
|
|
3779
|
-
*
|
|
3423
|
+
* Accessible label for a folded group's aggregated status, reusing the
|
|
3424
|
+
* session-row locale keys so both surfaces read identically.
|
|
3425
|
+
*
|
|
3426
|
+
* 按设置过滤:本插件那三档会话状态按设置逐项过滤;**外部来源不参与过滤** —— 登记了就
|
|
3427
|
+
* 一定出现在这里,没登记就不在折果里,所以 `aria-label` 与徽标视觉天然一致,不必在这里
|
|
3428
|
+
* 再判一次设置(本插件也没有替别人家存的开关)。
|
|
3780
3429
|
*/
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
getSnapshotBeforeUpdate(previous) {
|
|
3788
|
-
const list = this.list.current;
|
|
3789
|
-
if (!this.armed || sameRows(previous, this.props) || previous.resetKey !== this.props.resetKey || !previous.ready || !this.props.ready || list === null || typeof list.animate !== "function" || window.matchMedia("(prefers-reduced-motion: reduce)").matches) return null;
|
|
3790
|
-
const viewport = list.getBoundingClientRect();
|
|
3791
|
-
const positions = this.readPositions();
|
|
3792
|
-
const nextKeys = new Set(this.props.rowKeys);
|
|
3793
|
-
const removed = /* @__PURE__ */ new Map();
|
|
3794
|
-
for (const [key, row] of positions) {
|
|
3795
|
-
if (nextKeys.has(key) || !intersects(row.rect, viewport)) continue;
|
|
3796
|
-
const clone = row.element.cloneNode(true);
|
|
3797
|
-
clone.removeAttribute("data-row-key");
|
|
3798
|
-
clone.inert = true;
|
|
3799
|
-
clone.style.setProperty("--dsh-workspace-indent", getComputedStyle(row.element).getPropertyValue("--dsh-workspace-indent"));
|
|
3800
|
-
removed.set(key, {
|
|
3801
|
-
...row,
|
|
3802
|
-
element: clone
|
|
3803
|
-
});
|
|
3804
|
-
}
|
|
3805
|
-
return {
|
|
3806
|
-
positions,
|
|
3807
|
-
removed
|
|
3808
|
-
};
|
|
3809
|
-
}
|
|
3810
|
-
componentDidUpdate(previous, _state, snapshot) {
|
|
3811
|
-
if (snapshot === null) {
|
|
3812
|
-
if (!sameRows(previous, this.props) || previous.resetKey !== this.props.resetKey || previous.ready !== this.props.ready) this.clear();
|
|
3813
|
-
return;
|
|
3814
|
-
}
|
|
3815
|
-
this.cancelMovements();
|
|
3816
|
-
const list = this.list.current;
|
|
3817
|
-
const overlay = this.overlay.current;
|
|
3818
|
-
const viewport = list.getBoundingClientRect();
|
|
3819
|
-
const origin = overlay.getBoundingClientRect();
|
|
3820
|
-
const positions = this.readPositions();
|
|
3821
|
-
for (const [key, row] of positions) {
|
|
3822
|
-
this.removeExit(key);
|
|
3823
|
-
const previousRow = snapshot.positions.get(key);
|
|
3824
|
-
if (!intersects(row.rect, viewport) && (previousRow === void 0 || !intersects(previousRow.rect, viewport))) continue;
|
|
3825
|
-
if (previousRow === void 0) {
|
|
3826
|
-
this.move(row.element, [{ opacity: 0 }, { opacity: 1 }], ROW_FADE_MS);
|
|
3827
|
-
continue;
|
|
3828
|
-
}
|
|
3829
|
-
const dx = previousRow.rect.left - row.rect.left;
|
|
3830
|
-
const dy = previousRow.rect.top - row.rect.top;
|
|
3831
|
-
if (dx === 0 && dy === 0 && previousRow.opacity === 1) continue;
|
|
3832
|
-
this.move(row.element, [{
|
|
3833
|
-
transform: `translate(${String(dx)}px, ${String(dy)}px)`,
|
|
3834
|
-
opacity: previousRow.opacity
|
|
3835
|
-
}, {
|
|
3836
|
-
transform: "translate(0, 0)",
|
|
3837
|
-
opacity: 1
|
|
3838
|
-
}], ROW_GLIDE_MS);
|
|
3839
|
-
}
|
|
3840
|
-
for (const [key, row] of snapshot.removed) {
|
|
3841
|
-
const { element } = row;
|
|
3842
|
-
this.removeExit(key);
|
|
3843
|
-
Object.assign(element.style, {
|
|
3844
|
-
position: "absolute",
|
|
3845
|
-
margin: "0",
|
|
3846
|
-
transform: "none",
|
|
3847
|
-
boxSizing: "border-box",
|
|
3848
|
-
left: `${String(row.rect.left - origin.left)}px`,
|
|
3849
|
-
top: `${String(row.rect.top - origin.top)}px`,
|
|
3850
|
-
width: `${String(row.rect.width)}px`,
|
|
3851
|
-
height: `${String(row.rect.height)}px`
|
|
3852
|
-
});
|
|
3853
|
-
overlay.append(element);
|
|
3854
|
-
const animation = element.animate([{ opacity: row.opacity }, { opacity: 0 }], {
|
|
3855
|
-
duration: ROW_FADE_MS,
|
|
3856
|
-
easing: "ease-out",
|
|
3857
|
-
fill: "forwards"
|
|
3858
|
-
});
|
|
3859
|
-
this.exits.set(key, {
|
|
3860
|
-
element,
|
|
3861
|
-
animation
|
|
3862
|
-
});
|
|
3863
|
-
animation.onfinish = () => {
|
|
3864
|
-
this.removeExit(key);
|
|
3865
|
-
};
|
|
3866
|
-
}
|
|
3867
|
-
}
|
|
3868
|
-
componentWillUnmount() {
|
|
3869
|
-
this.clear();
|
|
3430
|
+
function groupLiveLabel(s, t, settings, now = Date.now()) {
|
|
3431
|
+
const parts = [];
|
|
3432
|
+
if (settings.showPendingInBadge) {
|
|
3433
|
+
if (s.approval > 0) parts.push(`${s.approval} ${t("status.waitingApproval")}`);
|
|
3434
|
+
if (s.planReview > 0) parts.push(`${s.planReview} ${t("status.planReview")}`);
|
|
3435
|
+
if (s.question > 0) parts.push(`${s.question} ${t("status.waitingAnswer")}`);
|
|
3870
3436
|
}
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
element,
|
|
3875
|
-
rect: element.getBoundingClientRect(),
|
|
3876
|
-
opacity: this.movements.has(element) ? Number(getComputedStyle(element).opacity) : 1
|
|
3877
|
-
}]));
|
|
3437
|
+
if (settings.showRunningInBadge) {
|
|
3438
|
+
if (s.running > 0) parts.push(`${s.running} ${t("status.running")}`);
|
|
3439
|
+
if (s.subagents > 0) parts.push(t(s.subagents === 1 ? "status.subagentsRunning.one" : "status.subagentsRunning.other", { n: s.subagents }));
|
|
3878
3440
|
}
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3441
|
+
parts.push(...facetLines(s, t, now));
|
|
3442
|
+
if (settings.showCompletedInBadge && s.done > 0) parts.push(`${s.done} ${t("status.completed")}`);
|
|
3443
|
+
return parts.join(" · ");
|
|
3444
|
+
}
|
|
3445
|
+
/**
|
|
3446
|
+
* 每个外部状态来源的明细行,**带它自己的图标**(#24)。
|
|
3447
|
+
*
|
|
3448
|
+
* 徽标侧画的是 provider 交出的、已含色档的元素(`rows/GroupBadge.tsx` 的 `facetGlyph`),
|
|
3449
|
+
* 明细侧此前只画一个统一的 `StateDot`,于是同一条状态、同一次悬停里,徽标是红色沙漏、
|
|
3450
|
+
* 明细是黄点——同一行的两个视图各说一套。宿主 `StateDot` 只有 warning / ongoing / done
|
|
3451
|
+
* 三档、没有 error 档,所以色档**只能**由来源自己的图标表达,这也是本函数存在的理由。
|
|
3452
|
+
*
|
|
3453
|
+
* provider 没交图标时退回通用点(与徽标侧同一套兜底),陌生来源仍然可见。
|
|
3454
|
+
*
|
|
3455
|
+
* @returns 明细行;`glyph` 为 `undefined` 表示该行用 `StateDot` 渲染。
|
|
3456
|
+
*/
|
|
3457
|
+
function facetDetailLines(s, t, now) {
|
|
3458
|
+
const translate = asFacetTranslate(t);
|
|
3459
|
+
const lines = [];
|
|
3460
|
+
for (const entry of s.facets.values()) {
|
|
3461
|
+
const raw = entry.facet.label?.(entry.fold, {
|
|
3462
|
+
now,
|
|
3463
|
+
t: translate
|
|
3464
|
+
});
|
|
3465
|
+
const text = raw === void 0 || raw === "" ? `${entry.fold.count} ${t("status.needsAttention")}` : raw;
|
|
3466
|
+
lines.push({
|
|
3467
|
+
state: "warning",
|
|
3468
|
+
text,
|
|
3469
|
+
glyph: facetGlyph(entry, t, now)
|
|
3883
3470
|
});
|
|
3884
|
-
this.movements.set(element, animation);
|
|
3885
|
-
animation.onfinish = () => {
|
|
3886
|
-
this.movements.delete(element);
|
|
3887
|
-
animation.cancel();
|
|
3888
|
-
};
|
|
3889
|
-
}
|
|
3890
|
-
cancelMovements() {
|
|
3891
|
-
for (const animation of this.movements.values()) {
|
|
3892
|
-
animation.onfinish = null;
|
|
3893
|
-
animation.cancel();
|
|
3894
|
-
}
|
|
3895
|
-
this.movements.clear();
|
|
3896
|
-
}
|
|
3897
|
-
removeExit(key) {
|
|
3898
|
-
const exit = this.exits.get(key);
|
|
3899
|
-
if (exit === void 0) return;
|
|
3900
|
-
exit.animation.onfinish = null;
|
|
3901
|
-
exit.animation.cancel();
|
|
3902
|
-
exit.element.remove();
|
|
3903
|
-
this.exits.delete(key);
|
|
3904
3471
|
}
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3472
|
+
return lines;
|
|
3473
|
+
}
|
|
3474
|
+
/**
|
|
3475
|
+
* Build the status detail lines for the workspace hover card.
|
|
3476
|
+
*
|
|
3477
|
+
* Extracted so `WorkspaceHoverContent` in `Rows.tsx` only needs to map the returned
|
|
3478
|
+
* array — the fork-specific status→line logic lives here.
|
|
3479
|
+
*
|
|
3480
|
+
* 按设置过滤:关掉的来源不出现在明细行里;`showHoverDetails=false` 时整段返回空。
|
|
3481
|
+
* 明细行的顺序与徽标**完全一致**(外部来源排在会话状态之后、已完成之前):两处顺序
|
|
3482
|
+
* 不一样会让人怀疑它们是两套事实。
|
|
3483
|
+
*/
|
|
3484
|
+
function hoverStatusLines(status, t, settings, now = Date.now()) {
|
|
3485
|
+
const lines = [];
|
|
3486
|
+
if (status === void 0 || !settings.showHoverDetails) return lines;
|
|
3487
|
+
if (settings.showPendingInBadge) {
|
|
3488
|
+
if (status.approval > 0) lines.push({
|
|
3489
|
+
state: "warning",
|
|
3490
|
+
text: `${status.approval} ${t("status.waitingApproval")}`
|
|
3491
|
+
});
|
|
3492
|
+
if (status.planReview > 0) lines.push({
|
|
3493
|
+
state: "warning",
|
|
3494
|
+
text: `${status.planReview} ${t("status.planReview")}`
|
|
3495
|
+
});
|
|
3496
|
+
if (status.question > 0) lines.push({
|
|
3497
|
+
state: "warning",
|
|
3498
|
+
text: `${status.question} ${t("status.waitingAnswer")}`
|
|
3499
|
+
});
|
|
3908
3500
|
}
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
onKeyDownCapture: () => {
|
|
3919
|
-
this.armed = true;
|
|
3920
|
-
},
|
|
3921
|
-
children: this.props.children
|
|
3922
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3923
|
-
ref: this.overlay,
|
|
3924
|
-
className: AnimatedRows_module_css_default.exits,
|
|
3925
|
-
"aria-hidden": "true"
|
|
3926
|
-
})] });
|
|
3501
|
+
if (settings.showRunningInBadge) {
|
|
3502
|
+
if (status.running > 0) lines.push({
|
|
3503
|
+
state: "ongoing",
|
|
3504
|
+
text: `${status.running} ${t("status.running")}`
|
|
3505
|
+
});
|
|
3506
|
+
if (status.subagents > 0) lines.push({
|
|
3507
|
+
state: "ongoing",
|
|
3508
|
+
text: t(status.subagents === 1 ? "status.subagentsRunning.one" : "status.subagentsRunning.other", { n: status.subagents })
|
|
3509
|
+
});
|
|
3927
3510
|
}
|
|
3928
|
-
|
|
3511
|
+
lines.push(...facetDetailLines(status, t, now));
|
|
3512
|
+
if (settings.showCompletedInBadge && status.done > 0) lines.push({
|
|
3513
|
+
state: "done",
|
|
3514
|
+
text: `${status.done} ${t("status.completed")}`
|
|
3515
|
+
});
|
|
3516
|
+
return lines;
|
|
3517
|
+
}
|
|
3518
|
+
/**
|
|
3519
|
+
* 某一条外部来源的元素该带哪只 class(#22)。
|
|
3520
|
+
*
|
|
3521
|
+
* 脉冲判据仍由各 facet 自己声明(`FacetFold.pulse`),但**作用对象必须下沉到声明它的那个
|
|
3522
|
+
* 元素**。此前 `groupBadgeIsUrgent()` 只返回一个整徽标的布尔值、class 加在容器上,于是同组
|
|
3523
|
+
* 里任何一家声明 `pulse: true` 都会让别家本该静止的图标跟着呼吸:`pulse: false` 是否生效,
|
|
3524
|
+
* 取决于同组里恰好有没有别人开了闪。判断交出去了,作用对象没交出去——这是修过一次的回归
|
|
3525
|
+
* (见下)。
|
|
3526
|
+
*
|
|
3527
|
+
* 保留这条历史的原因写在所有者那边:dsh-gitea-dispatch 把 `overdue` 画成**静止**(其
|
|
3528
|
+
* `lib/waiting-glyph.js`:「超时后停动画:静止即不再期待」),且它只代表 CI 窗口到期、
|
|
3529
|
+
* 不代表评审失败。同一条等待在行内静止、在父级闪动,就是同一事实的两种说法。
|
|
3530
|
+
*/
|
|
3531
|
+
function facetPulseClass(entry, urgentClass) {
|
|
3532
|
+
return entry.fold.pulse ? urgentClass : void 0;
|
|
3533
|
+
}
|
|
3534
|
+
/**
|
|
3535
|
+
* 一个来源的图标。
|
|
3536
|
+
*
|
|
3537
|
+
* 归 provider 画(`facet.renderIcon`):「这个状态长什么样」是它的知识。没实现时本插件
|
|
3538
|
+
* 按色档退回一个通用点(见 `rows/GroupBadge.tsx`),保证陌生来源仍然可见、只是朴素。
|
|
3539
|
+
*/
|
|
3540
|
+
function facetGlyph(entry, t, now) {
|
|
3541
|
+
return entry.facet.renderIcon?.(entry.fold, {
|
|
3542
|
+
now,
|
|
3543
|
+
t: asFacetTranslate(t)
|
|
3544
|
+
});
|
|
3545
|
+
}
|
|
3929
3546
|
//#endregion
|
|
3930
|
-
//#region \0dsh-css:src/client/
|
|
3931
|
-
const css$1 = ".
|
|
3932
|
-
const tagId$1 = "dsh-group-status/
|
|
3547
|
+
//#region \0dsh-css:src/client/rows/Rows.module.css.mjs
|
|
3548
|
+
const css$1 = "._1Jb0BW_projectRow,._1Jb0BW_sessionRow{border-radius:var(--dsw-radius-md);padding:0 8px;cursor:pointer;user-select:none;color:var(--dsw-alias-label-primary);align-items:center;gap:6px;padding-inline-start:calc(8px + var(--dsh-workspace-indent,0px));display:flex}._1Jb0BW_projectRow:hover,._1Jb0BW_sessionRow:hover,._1Jb0BW_sessionRow._1Jb0BW_selected{background:var(--dsw-alias-interactive-bg-hover)}._1Jb0BW_searchResultRow{box-sizing:border-box;border-radius:var(--dsw-radius-lg);cursor:pointer;text-align:left;width:100%;min-height:48px;color:var(--dsw-alias-label-primary);background:0 0;border:none;flex-direction:column;align-items:stretch;padding:4px 8px;display:flex}._1Jb0BW_searchResultRow:hover,._1Jb0BW_searchResultRow._1Jb0BW_selected{background:var(--dsw-alias-interactive-bg-hover)}._1Jb0BW_searchResultHeading{align-items:center;min-width:0;display:flex}._1Jb0BW_searchResultTitle{text-overflow:ellipsis;white-space:nowrap;flex:0 auto;min-width:0;margin-left:4px;font-size:14px;line-height:20px;overflow:hidden}._1Jb0BW_searchResultMeta{align-items:center;gap:6px;min-width:0;margin-left:20px;display:flex}._1Jb0BW_searchResultWorkspace,._1Jb0BW_searchResultSnippet{text-overflow:ellipsis;white-space:nowrap;font-size:12px;line-height:17px;overflow:hidden}._1Jb0BW_searchResultWorkspace{max-width:40%;color:var(--dsw-alias-label-tertiary);flex:none}._1Jb0BW_searchResultWorkspace:only-child{max-width:100%}._1Jb0BW_searchResultSnippet{min-width:0;color:var(--dsw-alias-label-secondary);flex:1}._1Jb0BW_projectRow{box-sizing:border-box;align-items:center;height:34px}._1Jb0BW_projectRow ._1Jb0BW_rowActions{height:20px}._1Jb0BW_sessionRow{gap:0;height:32px}._1Jb0BW_sessionRow ._1Jb0BW_title{margin:0 6px 0 4px}._1Jb0BW_slot{width:16px;height:20px;color:var(--dsw-alias-label-tertiary);flex:none;justify-content:center;align-items:center;display:inline-flex}._1Jb0BW_visuallyHidden{clip:rect(0 0 0 0);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}._1Jb0BW_folderActive{color:var(--dsw-alias-state-business-primary)}._1Jb0BW_projectRow ._1Jb0BW_chevron{display:none}._1Jb0BW_projectRow:hover ._1Jb0BW_chevron{display:inline-flex}._1Jb0BW_projectRow:hover ._1Jb0BW_folder{display:none}._1Jb0BW_arrow{transition:transform .15s var(--ds-ease-in-out)}._1Jb0BW_arrowOpen{transform:rotate(90deg)}._1Jb0BW_projectText{flex-direction:column;flex:1;gap:2px;min-width:0;display:flex}._1Jb0BW_title{text-overflow:ellipsis;white-space:nowrap;min-width:0;font-size:14px;line-height:20px;overflow:hidden}._1Jb0BW_renameInput{border:.5px solid var(--dsw-alias-border-l4);border-radius:var(--dsw-radius-sm);background:var(--dsw-alias-button-elevated-fill);min-width:0;color:inherit;outline:none;padding:0 2px;font-size:14px;line-height:20px}._1Jb0BW_sessionRow ._1Jb0BW_title{flex:1}._1Jb0BW_sessionRow ._1Jb0BW_title[data-scrolled]{mask-image:linear-gradient(90deg,#0000,#000 12px)}._1Jb0BW_sessionRow ._1Jb0BW_title[data-clipped]{mask-image:linear-gradient(270deg,#0000,#000 12px)}._1Jb0BW_sessionRow ._1Jb0BW_title[data-scrolled][data-clipped]{mask-image:linear-gradient(90deg,#0000,#000 12px calc(100% - 12px),#0000)}._1Jb0BW_meta{text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:20px;overflow:hidden}._1Jb0BW_time{color:var(--dsw-alias-label-tertiary);flex:none;font-size:10px;line-height:16px}._1Jb0BW_pinIndicator{width:16px;height:20px;color:var(--dsw-alias-label-caption);flex:none;justify-content:center;align-items:center;margin-left:6px;display:inline-flex}._1Jb0BW_sessionRow._1Jb0BW_archived ._1Jb0BW_title,._1Jb0BW_searchResultRow._1Jb0BW_archived ._1Jb0BW_searchResultTitle,._1Jb0BW_searchResultRow._1Jb0BW_archived ._1Jb0BW_searchResultWorkspace,._1Jb0BW_searchResultRow._1Jb0BW_archived ._1Jb0BW_searchResultSnippet{color:var(--dsw-alias-label-caption)}._1Jb0BW_dot{flex:none}._1Jb0BW_rowActions{flex:none;align-items:center;gap:10px;display:none}._1Jb0BW_projectRow:hover ._1Jb0BW_rowActions,._1Jb0BW_sessionRow:hover ._1Jb0BW_rowActions,._1Jb0BW_searchResultRow:hover ._1Jb0BW_rowActions,._1Jb0BW_projectRow._1Jb0BW_menuOpen ._1Jb0BW_rowActions,._1Jb0BW_sessionRow._1Jb0BW_menuOpen ._1Jb0BW_rowActions{display:inline-flex}._1Jb0BW_searchResultHeading ._1Jb0BW_rowActions{margin-left:auto}._1Jb0BW_sessionRow:hover ._1Jb0BW_time,._1Jb0BW_sessionRow._1Jb0BW_menuOpen ._1Jb0BW_time,._1Jb0BW_sessionRow:hover ._1Jb0BW_pinIndicator,._1Jb0BW_sessionRow._1Jb0BW_menuOpen ._1Jb0BW_pinIndicator{display:none}@media (hover:hover){._1Jb0BW_sessionRow:hover ._1Jb0BW_title,._1Jb0BW_sessionRow._1Jb0BW_menuOpen ._1Jb0BW_title{text-overflow:clip}}._1Jb0BW_projectRow._1Jb0BW_menuOpen,._1Jb0BW_sessionRow._1Jb0BW_menuOpen{background:var(--dsw-alias-interactive-bg-hover)}._1Jb0BW_sessionRow._1Jb0BW_dropBefore,._1Jb0BW_sessionRow._1Jb0BW_dropAfter{position:relative}._1Jb0BW_sessionRow._1Jb0BW_dropBefore:before,._1Jb0BW_sessionRow._1Jb0BW_dropAfter:after{content:\"\";z-index:1;background:linear-gradient(55deg, transparent calc(50% - 1px), var(--dsw-alias-state-business-primary) calc(50% - 1px) calc(50% + 1px), transparent calc(50% + 1px)) 0 0 / 5px 7px no-repeat, linear-gradient(125deg, transparent calc(50% - 1px), var(--dsw-alias-state-business-primary) calc(50% - 1px) calc(50% + 1px), transparent calc(50% + 1px)) 0 5px / 5px 7px no-repeat, linear-gradient(var(--dsw-alias-state-business-primary) 0 0) 4px 5px / calc(100% - 4px) 2px no-repeat;pointer-events:none;height:12px;position:absolute;left:0;right:4px}._1Jb0BW_sessionRow._1Jb0BW_dropBefore:before{top:-7px}._1Jb0BW_sessionRow._1Jb0BW_dropAfter:after{bottom:-7px}._1Jb0BW_hoverContent{flex-direction:column;gap:8px;display:flex}._1Jb0BW_hoverTitle{color:var(--dsw-alias-label-primary);overflow-wrap:break-word;font-size:14px;line-height:20px}._1Jb0BW_hoverPath{color:var(--dsw-alias-label-secondary);word-break:break-all;font-size:12px;line-height:16px}._1Jb0BW_hoverTime{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:16px}._1Jb0BW_hoverStatus{color:var(--dsw-alias-label-tertiary);align-items:center;gap:8px;font-size:12px;line-height:20px;display:flex}._1Jb0BW_groupBadge{flex:none;align-items:center;gap:3px;margin-inline-start:4px;display:inline-flex}._1Jb0BW_groupBadgeCount{color:var(--dsw-alias-label-tertiary);border:1px solid;border-radius:6px;padding:0 3px;font-size:9px;line-height:12px}@keyframes _1Jb0BW_groupBadgePulse{0%,to{opacity:1}50%{opacity:.6}}._1Jb0BW_groupBadgeUrgent{animation:2s ease-in-out infinite _1Jb0BW_groupBadgePulse}._1Jb0BW_hoverArchived svg{flex-shrink:0;margin:0 -4px}._1Jb0BW_iconButton{border-radius:var(--dsw-radius-xs);cursor:pointer;width:16px;height:16px;color:var(--dsw-alias-label-tertiary);background:0 0;border:none;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}._1Jb0BW_iconButton:hover{color:var(--dsw-alias-label-primary)}._1Jb0BW_chevron{color:var(--dsw-alias-label-caption)}@media (prefers-reduced-motion:reduce){._1Jb0BW_arrow{transition:none;animation:none}}";
|
|
3549
|
+
const tagId$1 = "dsh-group-status/Rows.module.css";
|
|
3933
3550
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
|
|
3934
3551
|
const tag = document.createElement("style");
|
|
3935
3552
|
tag.dataset.plugin = "dsh-group-status";
|
|
@@ -3937,416 +3554,871 @@ window.__ModuleLoader__.load({
|
|
|
3937
3554
|
tag.textContent = css$1;
|
|
3938
3555
|
document.head.appendChild(tag);
|
|
3939
3556
|
}
|
|
3940
|
-
var
|
|
3941
|
-
"
|
|
3942
|
-
"
|
|
3943
|
-
"
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3557
|
+
var Rows_module_css_default = {
|
|
3558
|
+
"archived": "_1Jb0BW_archived",
|
|
3559
|
+
"arrow": "_1Jb0BW_arrow",
|
|
3560
|
+
"arrowOpen": "_1Jb0BW_arrowOpen",
|
|
3561
|
+
"chevron": "_1Jb0BW_chevron",
|
|
3562
|
+
"dot": "_1Jb0BW_dot",
|
|
3563
|
+
"dropAfter": "_1Jb0BW_dropAfter",
|
|
3564
|
+
"dropBefore": "_1Jb0BW_dropBefore",
|
|
3565
|
+
"folder": "_1Jb0BW_folder",
|
|
3566
|
+
"folderActive": "_1Jb0BW_folderActive",
|
|
3567
|
+
"groupBadge": "_1Jb0BW_groupBadge",
|
|
3568
|
+
"groupBadgeCount": "_1Jb0BW_groupBadgeCount",
|
|
3569
|
+
"groupBadgePulse": "_1Jb0BW_groupBadgePulse",
|
|
3570
|
+
"groupBadgeUrgent": "_1Jb0BW_groupBadgeUrgent",
|
|
3571
|
+
"hoverArchived": "_1Jb0BW_hoverArchived",
|
|
3572
|
+
"hoverContent": "_1Jb0BW_hoverContent",
|
|
3573
|
+
"hoverPath": "_1Jb0BW_hoverPath",
|
|
3574
|
+
"hoverStatus": "_1Jb0BW_hoverStatus",
|
|
3575
|
+
"hoverTime": "_1Jb0BW_hoverTime",
|
|
3576
|
+
"hoverTitle": "_1Jb0BW_hoverTitle",
|
|
3577
|
+
"iconButton": "_1Jb0BW_iconButton",
|
|
3578
|
+
"menuOpen": "_1Jb0BW_menuOpen",
|
|
3579
|
+
"meta": "_1Jb0BW_meta",
|
|
3580
|
+
"pinIndicator": "_1Jb0BW_pinIndicator",
|
|
3581
|
+
"projectRow": "_1Jb0BW_projectRow",
|
|
3582
|
+
"projectText": "_1Jb0BW_projectText",
|
|
3583
|
+
"renameInput": "_1Jb0BW_renameInput",
|
|
3584
|
+
"rowActions": "_1Jb0BW_rowActions",
|
|
3585
|
+
"searchResultHeading": "_1Jb0BW_searchResultHeading",
|
|
3586
|
+
"searchResultMeta": "_1Jb0BW_searchResultMeta",
|
|
3587
|
+
"searchResultRow": "_1Jb0BW_searchResultRow",
|
|
3588
|
+
"searchResultSnippet": "_1Jb0BW_searchResultSnippet",
|
|
3589
|
+
"searchResultTitle": "_1Jb0BW_searchResultTitle",
|
|
3590
|
+
"searchResultWorkspace": "_1Jb0BW_searchResultWorkspace",
|
|
3591
|
+
"selected": "_1Jb0BW_selected",
|
|
3592
|
+
"sessionRow": "_1Jb0BW_sessionRow",
|
|
3593
|
+
"slot": "_1Jb0BW_slot",
|
|
3594
|
+
"time": "_1Jb0BW_time",
|
|
3595
|
+
"title": "_1Jb0BW_title",
|
|
3596
|
+
"visuallyHidden": "_1Jb0BW_visuallyHidden"
|
|
3597
|
+
};
|
|
3598
|
+
//#endregion
|
|
3599
|
+
//#region src/client/rows/GroupBadge.tsx
|
|
3600
|
+
/**
|
|
3601
|
+
* Aggregated live-status badge for a folded folder row. Dots reuse StateDot so
|
|
3602
|
+
* colors match session rows (warning = pending, ongoing = running, done = completed).
|
|
3603
|
+
* Renders nothing while expanded or when nothing is pending/active/completed or any
|
|
3604
|
+
* status source reports a contribution.
|
|
3605
|
+
*
|
|
3606
|
+
* Count matches the dots: host-side sessions plus every registered facet's own count.
|
|
3607
|
+
*
|
|
3608
|
+
* `settings` 只过滤**本插件自己的**三档会话状态:关掉的档位不画点、不计数、不进
|
|
3609
|
+
* aria-label、不参与「是否显示徽标」的判断。会话行自身的状态点不受影响:那是行级状态,
|
|
3610
|
+
* 不是分组提示。**外部来源在这里没有开关可关** —— 它们要么注册了(一定显示),要么没
|
|
3611
|
+
* 注册(根本不在登记面上)。要关某个来源,去那个插件自己的设置卡片:显隐是来源的决定。
|
|
3612
|
+
*
|
|
3613
|
+
* `now` 缺省取渲染时刻(与 `WorkspaceBrowser` 为相对时间读的时钟同一姿态):文案里的
|
|
3614
|
+
* 剩余时长以此计算,只在提供方 store 通知时随渲染更新。
|
|
3615
|
+
*/
|
|
3616
|
+
function groupLiveBadge(group, t, settings, now = Date.now()) {
|
|
3617
|
+
const s = group.liveStatus;
|
|
3618
|
+
if (group.expanded || s === void 0) return null;
|
|
3619
|
+
const pending = settings.showPendingInBadge ? s.approval + s.planReview + s.question : 0;
|
|
3620
|
+
const active = settings.showRunningInBadge ? s.running + s.subagents : 0;
|
|
3621
|
+
const completed = settings.showCompletedInBadge ? s.done : 0;
|
|
3622
|
+
const facetTotal = facetCountTotal(s);
|
|
3623
|
+
if (pending === 0 && active === 0 && completed === 0 && facetTotal === 0) return null;
|
|
3624
|
+
const label = groupLiveLabel(s, t, settings, now);
|
|
3625
|
+
const dots = [];
|
|
3626
|
+
if (pending > 0) dots.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: BADGE_FIELD_DOT_STATE.showPendingInBadge }, "warn"));
|
|
3627
|
+
if (active > 0) dots.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: BADGE_FIELD_DOT_STATE.showRunningInBadge }, "run"));
|
|
3628
|
+
const urgentClass = Rows_module_css_default.groupBadgeUrgent ?? "";
|
|
3629
|
+
for (const entry of s.facets.values()) {
|
|
3630
|
+
const glyph = facetGlyph(entry, t, now);
|
|
3631
|
+
const pulseClass = facetPulseClass(entry, urgentClass) || void 0;
|
|
3632
|
+
dots.push(glyph === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: entry.fold.tone === "info" ? "ongoing" : "warning" }, entry.facet.id) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3633
|
+
className: pulseClass,
|
|
3634
|
+
children: glyph
|
|
3635
|
+
}, entry.facet.id));
|
|
3636
|
+
}
|
|
3637
|
+
if (completed > 0) dots.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: BADGE_FIELD_DOT_STATE.showCompletedInBadge }, "done"));
|
|
3638
|
+
const count = pending + active + completed + facetTotal;
|
|
3639
|
+
const countLabel = count > 99 ? "99+" : count >= settings.countThreshold ? String(count) : "";
|
|
3640
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
3641
|
+
className: Rows_module_css_default.groupBadge,
|
|
3642
|
+
role: "img",
|
|
3643
|
+
"aria-label": label,
|
|
3644
|
+
title: label,
|
|
3645
|
+
children: [dots, countLabel && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3646
|
+
className: Rows_module_css_default.groupBadgeCount,
|
|
3647
|
+
children: countLabel
|
|
3648
|
+
})]
|
|
3995
3649
|
});
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
3650
|
+
}
|
|
3651
|
+
//#endregion
|
|
3652
|
+
//#region src/client/rows/Rows.tsx
|
|
3653
|
+
/**
|
|
3654
|
+
* Workspace browser tree row components (figma Cell set 14:3080): pure presentational —
|
|
3655
|
+
* all data and callbacks arrive via props. Hover swaps (folder->chevron,
|
|
3656
|
+
* time->ellipsis, action buttons) are CSS-only, and a session row's clipped
|
|
3657
|
+
* title marquees programmatically while the row is hovered. Workspace row
|
|
3658
|
+
* menus are visual-only except Rename/Delete. A Session row's "..." menu and
|
|
3659
|
+
* its hover buttons are the `sidebar.workspaces.session.menu.item` and
|
|
3660
|
+
* `sidebar.workspaces.session.row.action` lists, rendered through the
|
|
3661
|
+
* browser's `renderSlot` with the menu's open state as the occurrence's hook
|
|
3662
|
+
* context; this package's own actions are entries like any plugin's. The
|
|
3663
|
+
* session and workspace hover cards are suppressed while a menu is open.
|
|
3664
|
+
*/
|
|
3665
|
+
/** Row display title: blank rows show the localized New Session label. */
|
|
3666
|
+
function displayTitle(node, t) {
|
|
3667
|
+
return node.blank ? t("session.new") : node.title || t("session.untitled");
|
|
3668
|
+
}
|
|
3669
|
+
const MIN_TITLE_REVEAL_PX = 8;
|
|
3670
|
+
const TITLE_MARQUEE_PX_PER_MS = .03;
|
|
3671
|
+
/**
|
|
3672
|
+
* Place the title's scroll position and publish the stylesheet's fade-mask
|
|
3673
|
+
* hooks: `data-scrolled` while the title has left its start (left fade) and
|
|
3674
|
+
* `data-clipped` while text remains beyond the right edge (right fade).
|
|
3675
|
+
* @param title - the row's clipping title element.
|
|
3676
|
+
* @param left - scroll offset in CSS pixels.
|
|
3677
|
+
* @param range - the title's maximum scroll offset in CSS pixels.
|
|
3678
|
+
*/
|
|
3679
|
+
function placeTitle(title, left, range) {
|
|
3680
|
+
if (typeof title.scrollTo === "function") title.scrollTo({
|
|
3681
|
+
left,
|
|
3682
|
+
behavior: "instant"
|
|
3683
|
+
});
|
|
3684
|
+
else title.scrollLeft = left;
|
|
3685
|
+
if (left > 0) title.dataset.scrolled = "";
|
|
3686
|
+
else delete title.dataset.scrolled;
|
|
3687
|
+
if (left < range) title.dataset.clipped = "";
|
|
3688
|
+
else delete title.dataset.clipped;
|
|
3689
|
+
}
|
|
3690
|
+
/**
|
|
3691
|
+
* Return the title to its resting state: scrolled to the start with both fade
|
|
3692
|
+
* masks off, so the resting ellipsis renders at full strength.
|
|
3693
|
+
* @param title - the row's clipping title element.
|
|
3694
|
+
*/
|
|
3695
|
+
function restTitle(title) {
|
|
3696
|
+
if (typeof title.scrollTo === "function") title.scrollTo({
|
|
3697
|
+
left: 0,
|
|
3698
|
+
behavior: "instant"
|
|
3699
|
+
});
|
|
3700
|
+
else title.scrollLeft = 0;
|
|
3701
|
+
delete title.dataset.scrolled;
|
|
3702
|
+
delete title.dataset.clipped;
|
|
3703
|
+
}
|
|
3704
|
+
/**
|
|
3705
|
+
* Marquee a title wider than its one-line cell while its row is hovered: the
|
|
3706
|
+
* title clips its own text, so entering crawls it at a constant speed until the
|
|
3707
|
+
* far edge (a fork's incremented title, for example) is in view, then rests
|
|
3708
|
+
* there under the pointer. Overflow of at most {@link MIN_TITLE_REVEAL_PX}
|
|
3709
|
+
* stays put — a barely-clipped title moving a few pixels reads as jitter, not a
|
|
3710
|
+
* reveal. Leaving returns the title to the start in one step, because the
|
|
3711
|
+
* resting ellipsis and the narrowed cell would otherwise meet the text while it
|
|
3712
|
+
* travelled back. Reduced motion jumps to the far edge instead of crawling.
|
|
3713
|
+
* @param title - ref to the row's clipping title element.
|
|
3714
|
+
* @returns stable pointer enter/leave handlers for the row.
|
|
3715
|
+
*/
|
|
3716
|
+
function useTitleMarquee(title) {
|
|
3717
|
+
const frame = (0, react.useRef)(0);
|
|
3718
|
+
(0, react.useEffect)(() => () => {
|
|
3719
|
+
cancelAnimationFrame(frame.current);
|
|
3720
|
+
}, []);
|
|
3721
|
+
return (0, react.useMemo)(() => ({
|
|
3722
|
+
enter: () => {
|
|
3723
|
+
/* v8 ignore next -- defensive: the title span renders unconditionally. */
|
|
3724
|
+
if (title.current === null) return;
|
|
3725
|
+
const element = title.current;
|
|
3726
|
+
const range = element.scrollWidth - element.clientWidth;
|
|
3727
|
+
if (range <= MIN_TITLE_REVEAL_PX) return;
|
|
3728
|
+
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
|
|
3729
|
+
placeTitle(element, range, range);
|
|
3730
|
+
return;
|
|
3731
|
+
}
|
|
3732
|
+
cancelAnimationFrame(frame.current);
|
|
3733
|
+
let previous;
|
|
3734
|
+
let position = 0;
|
|
3735
|
+
const step = (now) => {
|
|
3736
|
+
position += previous === void 0 ? 0 : (now - previous) * TITLE_MARQUEE_PX_PER_MS;
|
|
3737
|
+
previous = now;
|
|
3738
|
+
placeTitle(element, Math.min(position, range), range);
|
|
3739
|
+
if (position < range) frame.current = requestAnimationFrame(step);
|
|
3740
|
+
};
|
|
3741
|
+
frame.current = requestAnimationFrame(step);
|
|
4024
3742
|
},
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
};
|
|
4031
|
-
const handleSelect = (id) => {
|
|
4032
|
-
if (id === ADD_WORKSPACE) {
|
|
4033
|
-
openDirectoryFlow();
|
|
4034
|
-
return;
|
|
3743
|
+
leave: () => {
|
|
3744
|
+
cancelAnimationFrame(frame.current);
|
|
3745
|
+
/* v8 ignore next -- defensive: the title span renders unconditionally. */
|
|
3746
|
+
if (title.current === null) return;
|
|
3747
|
+
restTitle(title.current);
|
|
4035
3748
|
}
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
className:
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
3749
|
+
}), [title]);
|
|
3750
|
+
}
|
|
3751
|
+
/** Localized compact relative time ("刚刚"/"5分钟" in zh, "now"/"5min" in en). */
|
|
3752
|
+
function timeLabel(updatedAt, now, t) {
|
|
3753
|
+
const { unit, n } = (0, _deepseek_ai_dsh_client_ui_primitives.relativeTime)(updatedAt, now);
|
|
3754
|
+
return unit === "now" ? t("time.now") : t(`time.${unit}`, { n });
|
|
3755
|
+
}
|
|
3756
|
+
/** Hover-card variant: distances wrap in the ago template; the now bucket stays bare (no "now ago"). */
|
|
3757
|
+
function hoverTimeLabel(updatedAt, now, t) {
|
|
3758
|
+
const { unit, n } = (0, _deepseek_ai_dsh_client_ui_primitives.relativeTime)(updatedAt, now);
|
|
3759
|
+
return unit === "now" ? t("time.now") : t("time.ago", { t: t(`time.${unit}`, { n }) });
|
|
3760
|
+
}
|
|
3761
|
+
/**
|
|
3762
|
+
* Absolute creation time through the dictionary's date template (the message
|
|
3763
|
+
* clock pattern): `toLocaleString` would follow the browser language, not the
|
|
3764
|
+
* app locale, and produce mixed-language text after a switch.
|
|
3765
|
+
*/
|
|
3766
|
+
function createdLabel(createdAt, t) {
|
|
3767
|
+
const d = new Date(createdAt);
|
|
3768
|
+
const pad2 = (v) => String(v).padStart(2, "0");
|
|
3769
|
+
return t("hover.created", { time: `${t("date.ymd", {
|
|
3770
|
+
y: d.getFullYear(),
|
|
3771
|
+
m: d.getMonth() + 1,
|
|
3772
|
+
d: d.getDate()
|
|
3773
|
+
})} ${pad2(d.getHours())}:${pad2(d.getMinutes())}` });
|
|
3774
|
+
}
|
|
3775
|
+
/** Hover-card body: workspace title, display directory path, absolute creation time. */
|
|
3776
|
+
function WorkspaceHoverContent({ label, cwd, createdAt, status, settings, now, t }) {
|
|
3777
|
+
const lines = hoverStatusLines(status, t, settings, now);
|
|
3778
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3779
|
+
className: Rows_module_css_default.hoverContent,
|
|
3780
|
+
children: [
|
|
3781
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3782
|
+
className: Rows_module_css_default.hoverTitle,
|
|
3783
|
+
children: label
|
|
3784
|
+
}),
|
|
3785
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3786
|
+
className: Rows_module_css_default.hoverPath,
|
|
3787
|
+
children: cwd
|
|
3788
|
+
}),
|
|
3789
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3790
|
+
className: Rows_module_css_default.hoverTime,
|
|
3791
|
+
children: createdLabel(createdAt, t)
|
|
3792
|
+
}),
|
|
3793
|
+
lines.map((line) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3794
|
+
className: Rows_module_css_default.hoverStatus,
|
|
3795
|
+
children: [line.glyph ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: line.state }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: line.text })]
|
|
3796
|
+
}, line.text))
|
|
3797
|
+
]
|
|
3798
|
+
});
|
|
3799
|
+
}
|
|
3800
|
+
/** Pointer-position half of a row (insert line above or below). */
|
|
3801
|
+
function rowHalf(e) {
|
|
3802
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
3803
|
+
return e.clientY < rect.top + rect.height / 2 ? "before" : "after";
|
|
3804
|
+
}
|
|
3805
|
+
/**
|
|
3806
|
+
* Project (workspace) header row: folder + title;
|
|
3807
|
+
* hover reveals the chevron and create button, and dwelling on a real
|
|
3808
|
+
* Workspace shows its hover card (the ungrouped bucket has none).
|
|
3809
|
+
* `containsCurrent` arrives on the node (derivation fact, no renderer scan).
|
|
3810
|
+
* @param props.group - derived group node.
|
|
3811
|
+
* @param props.containsCurrentDescendant - highlight an ancestor even when its subtree is collapsed.
|
|
3812
|
+
* @param props.onToggle - expand/collapse the group.
|
|
3813
|
+
* @param props.onCreate - start a frontend Session inside this Workspace.
|
|
3814
|
+
* @param props.drag - optional workspace-row drag wiring.
|
|
3815
|
+
* @param props.home - host account home for POSIX hover-path abbreviation.
|
|
3816
|
+
* @param props.t - the browser root's locale seat.
|
|
3817
|
+
* @returns the row element.
|
|
3818
|
+
*/
|
|
3819
|
+
function ProjectRowItem({ group, containsCurrentDescendant = false, onToggle, onCreate, actions, drag, home, newShortcut, settings, now, t }) {
|
|
3820
|
+
const row = group;
|
|
3821
|
+
const label = row.workspaceId === void 0 ? t("group.ungrouped") : row.label;
|
|
3822
|
+
const active = containsCurrentDescendant || group.expanded && group.containsCurrent;
|
|
3823
|
+
const [menuOpen, setMenuOpen] = (0, react.useState)(false);
|
|
3824
|
+
const workspaceMenuItems = [{
|
|
3825
|
+
id: "rename",
|
|
3826
|
+
label: t("rename"),
|
|
3827
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEditOutlineRegular, {})
|
|
3828
|
+
}, {
|
|
3829
|
+
id: "delete",
|
|
3830
|
+
label: t("delete.workspace"),
|
|
3831
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutlineRegular, {}),
|
|
3832
|
+
danger: true
|
|
3833
|
+
}];
|
|
3834
|
+
const ownRow = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3835
|
+
className: clsx(Rows_module_css_default.projectRow, menuOpen && Rows_module_css_default.menuOpen),
|
|
3836
|
+
"data-row-key": `workspace:${group.key}`,
|
|
3837
|
+
role: "treeitem",
|
|
3838
|
+
"aria-expanded": row.expanded,
|
|
3839
|
+
onClick: onToggle,
|
|
3840
|
+
draggable: drag !== void 0,
|
|
3841
|
+
onDragStart: drag === void 0 ? void 0 : (e) => {
|
|
3842
|
+
e.dataTransfer.effectAllowed = "move";
|
|
3843
|
+
e.dataTransfer.setData("text/plain", row.key);
|
|
3844
|
+
drag.start();
|
|
3845
|
+
},
|
|
3846
|
+
onDragEnd: drag?.end,
|
|
3847
|
+
children: [
|
|
3848
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3849
|
+
className: clsx(Rows_module_css_default.slot, Rows_module_css_default.folder, active && Rows_module_css_default.folderActive),
|
|
3850
|
+
children: row.expanded ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderOpenRegular, {}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderCloseRegular, {})
|
|
3851
|
+
}),
|
|
3852
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3853
|
+
className: clsx(Rows_module_css_default.slot, Rows_module_css_default.chevron),
|
|
3854
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTriangleRightFillRegular, { className: clsx(Rows_module_css_default.arrow, row.expanded && Rows_module_css_default.arrowOpen) })
|
|
3855
|
+
}),
|
|
3856
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3857
|
+
className: Rows_module_css_default.projectText,
|
|
3858
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3859
|
+
className: Rows_module_css_default.title,
|
|
3860
|
+
children: label
|
|
3861
|
+
})
|
|
3862
|
+
}),
|
|
3863
|
+
groupLiveBadge(group, t, settings, now),
|
|
3864
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
3865
|
+
className: Rows_module_css_default.rowActions,
|
|
3866
|
+
children: [actions !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
|
|
3867
|
+
open: menuOpen,
|
|
3868
|
+
onClose: () => {
|
|
3869
|
+
setMenuOpen(false);
|
|
3870
|
+
},
|
|
3871
|
+
items: workspaceMenuItems,
|
|
3872
|
+
onSelect: (id) => {
|
|
3873
|
+
setMenuOpen(false);
|
|
3874
|
+
/* v8 ignore next -- Menu can emit only the rename and delete rows supplied above. */
|
|
3875
|
+
if (id !== "rename" && id !== "delete") return;
|
|
3876
|
+
if (id === "rename") actions.rename();
|
|
3877
|
+
else actions.delete();
|
|
3878
|
+
},
|
|
3879
|
+
portal: true,
|
|
3880
|
+
closeOnPointerLeave: true,
|
|
3881
|
+
anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3882
|
+
type: "button",
|
|
3883
|
+
className: Rows_module_css_default.iconButton,
|
|
3884
|
+
"aria-label": t("actions.workspace.aria", { name: label }),
|
|
3885
|
+
onClick: (e) => {
|
|
3886
|
+
e.stopPropagation();
|
|
3887
|
+
setMenuOpen((v) => !v);
|
|
3888
|
+
},
|
|
3889
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEllipsisOutlineRegular, {})
|
|
3890
|
+
})
|
|
3891
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
|
|
3892
|
+
label: t("actions.newSession"),
|
|
3893
|
+
shortcutKeys: newShortcut?.keys,
|
|
3894
|
+
side: "bottom",
|
|
3895
|
+
align: "end",
|
|
3896
|
+
delayMs: 500,
|
|
3897
|
+
portal: true,
|
|
3898
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3899
|
+
type: "button",
|
|
3900
|
+
className: Rows_module_css_default.iconButton,
|
|
3901
|
+
"aria-keyshortcuts": newShortcut?.aria,
|
|
3902
|
+
"aria-label": t("actions.newSession.aria", { name: label }),
|
|
3903
|
+
onClick: (e) => {
|
|
3904
|
+
e.stopPropagation();
|
|
3905
|
+
onCreate();
|
|
3906
|
+
},
|
|
3907
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconNewChatOutlineRegular, {})
|
|
3908
|
+
})
|
|
3909
|
+
})]
|
|
4078
3910
|
})
|
|
4079
|
-
|
|
4080
|
-
|
|
3911
|
+
]
|
|
3912
|
+
});
|
|
3913
|
+
if (row.createdAt === void 0) return ownRow;
|
|
3914
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.HoverCard, {
|
|
3915
|
+
anchor: ownRow,
|
|
3916
|
+
content: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WorkspaceHoverContent, {
|
|
3917
|
+
label: row.label,
|
|
3918
|
+
cwd: row.cwd === void 0 ? void 0 : abbreviateHomePath(row.cwd, home),
|
|
3919
|
+
createdAt: row.createdAt,
|
|
3920
|
+
status: row.expanded ? void 0 : row.liveStatus,
|
|
3921
|
+
settings,
|
|
3922
|
+
now: now ?? Date.now(),
|
|
3923
|
+
t
|
|
3924
|
+
}),
|
|
3925
|
+
openDelayMs: 800,
|
|
3926
|
+
grouped: true,
|
|
3927
|
+
disabled: menuOpen,
|
|
3928
|
+
copyText: row.cwd,
|
|
3929
|
+
copyLabel: t("copy"),
|
|
3930
|
+
copiedLabel: t("hover.copied")
|
|
3931
|
+
});
|
|
3932
|
+
}
|
|
3933
|
+
/* v8 ignore next 3 -- closed-union backstop; only reached if the status is forged */
|
|
3934
|
+
function assertNever(value) {
|
|
3935
|
+
throw new Error(`unknown pending interaction: ${String(value)}`);
|
|
4081
3936
|
}
|
|
4082
3937
|
/**
|
|
4083
|
-
*
|
|
4084
|
-
*
|
|
4085
|
-
* @param props - empty-state slot props (owner share + injected creation callback).
|
|
4086
|
-
* @returns the flow element.
|
|
3938
|
+
* Session status presentation; pending interaction is primary and live activity
|
|
3939
|
+
* outranks completion reminders.
|
|
4087
3940
|
*/
|
|
4088
|
-
function
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
3941
|
+
function sessionStatuses(node, t) {
|
|
3942
|
+
const subagents = node.runningSubagentCount === 0 ? void 0 : {
|
|
3943
|
+
state: "ongoing",
|
|
3944
|
+
label: t(node.runningSubagentCount === 1 ? "status.subagentsRunning.one" : "status.subagentsRunning.other", { n: node.runningSubagentCount })
|
|
3945
|
+
};
|
|
3946
|
+
let pending;
|
|
3947
|
+
switch (node.pendingInteraction) {
|
|
3948
|
+
case "approval":
|
|
3949
|
+
pending = {
|
|
3950
|
+
state: "warning",
|
|
3951
|
+
label: t("status.waitingApproval"),
|
|
3952
|
+
trailingLabel: t("status.compact.approval")
|
|
3953
|
+
};
|
|
3954
|
+
break;
|
|
3955
|
+
case "plan-review":
|
|
3956
|
+
pending = {
|
|
3957
|
+
state: "warning",
|
|
3958
|
+
label: t("status.planReview"),
|
|
3959
|
+
trailingLabel: t("status.compact.planReview")
|
|
3960
|
+
};
|
|
3961
|
+
break;
|
|
3962
|
+
case "question":
|
|
3963
|
+
pending = {
|
|
3964
|
+
state: "warning",
|
|
3965
|
+
label: t("status.waitingAnswer"),
|
|
3966
|
+
trailingLabel: t("status.compact.answer")
|
|
3967
|
+
};
|
|
3968
|
+
break;
|
|
3969
|
+
case void 0: break;
|
|
3970
|
+
/* v8 ignore next -- closed PendingInteractionStatus union */
|
|
3971
|
+
default: return assertNever(node.pendingInteraction);
|
|
3972
|
+
}
|
|
3973
|
+
if (pending !== void 0) return subagents === void 0 ? [pending] : [pending, subagents];
|
|
3974
|
+
if (node.running) {
|
|
3975
|
+
const primary = {
|
|
3976
|
+
state: "ongoing",
|
|
3977
|
+
label: t("status.running")
|
|
3978
|
+
};
|
|
3979
|
+
return subagents === void 0 ? [primary] : [primary, subagents];
|
|
3980
|
+
}
|
|
3981
|
+
if (subagents !== void 0) return [subagents];
|
|
3982
|
+
if (node.completed) return [{
|
|
3983
|
+
state: "done",
|
|
3984
|
+
label: t("status.completed")
|
|
3985
|
+
}];
|
|
3986
|
+
return [{
|
|
3987
|
+
state: "idle",
|
|
3988
|
+
label: t("status.idle")
|
|
3989
|
+
}];
|
|
4101
3990
|
}
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
3991
|
+
/** Primary status dot plus every status's screen-reader label, shared by the search and session rows. */
|
|
3992
|
+
function SessionStatusDots({ statuses }) {
|
|
3993
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: statuses[0].state }), statuses.map((status) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3994
|
+
className: Rows_module_css_default.visuallyHidden,
|
|
3995
|
+
children: status.label
|
|
3996
|
+
}, status.label))] });
|
|
3997
|
+
}
|
|
3998
|
+
/** Non-interactive pinned-row marker; the enclosing row remains the only action. */
|
|
3999
|
+
function PinnedIndicator({ t }) {
|
|
4000
|
+
const label = t("row.pinned");
|
|
4001
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4002
|
+
className: Rows_module_css_default.pinIndicator,
|
|
4003
|
+
role: "img",
|
|
4004
|
+
"aria-label": label,
|
|
4005
|
+
title: label,
|
|
4006
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPinFillRegular, { size: 14 })
|
|
4007
|
+
});
|
|
4112
4008
|
}
|
|
4113
|
-
var WorkspaceBrowser_module_css_default = {
|
|
4114
|
-
"archiveActivity": "QAYkWG_archiveActivity",
|
|
4115
|
-
"clearButton": "QAYkWG_clearButton",
|
|
4116
|
-
"deleteAction": "QAYkWG_deleteAction",
|
|
4117
|
-
"deleteStatus": "QAYkWG_deleteStatus",
|
|
4118
|
-
"empty": "QAYkWG_empty",
|
|
4119
|
-
"emptyAction": "QAYkWG_emptyAction",
|
|
4120
|
-
"emptyState": "QAYkWG_emptyState",
|
|
4121
|
-
"fade": "QAYkWG_fade",
|
|
4122
|
-
"flatList": "QAYkWG_flatList",
|
|
4123
|
-
"groupSection": "QAYkWG_groupSection",
|
|
4124
|
-
"headerActions": "QAYkWG_headerActions",
|
|
4125
|
-
"headerActionsHidden": "QAYkWG_headerActionsHidden",
|
|
4126
|
-
"iconButton": "QAYkWG_iconButton",
|
|
4127
|
-
"list": "QAYkWG_list",
|
|
4128
|
-
"listArea": "QAYkWG_listArea",
|
|
4129
|
-
"listTopDropActive": "QAYkWG_listTopDropActive",
|
|
4130
|
-
"listTopDropIndicator": "QAYkWG_listTopDropIndicator",
|
|
4131
|
-
"rail": "QAYkWG_rail",
|
|
4132
|
-
"renameError": "QAYkWG_renameError",
|
|
4133
|
-
"renameInput": "QAYkWG_renameInput",
|
|
4134
|
-
"root": "QAYkWG_root",
|
|
4135
|
-
"search": "QAYkWG_search",
|
|
4136
|
-
"search-skeleton": "QAYkWG_search-skeleton",
|
|
4137
|
-
"searchButton": "QAYkWG_searchButton",
|
|
4138
|
-
"searchExpanded": "QAYkWG_searchExpanded",
|
|
4139
|
-
"searchInput": "QAYkWG_searchInput",
|
|
4140
|
-
"searchSlot": "QAYkWG_searchSlot",
|
|
4141
|
-
"searchSlotExpanded": "QAYkWG_searchSlotExpanded",
|
|
4142
|
-
"searchStatus": "QAYkWG_searchStatus",
|
|
4143
|
-
"searchTree": "QAYkWG_searchTree",
|
|
4144
|
-
"sectionHeader": "QAYkWG_sectionHeader",
|
|
4145
|
-
"sectionLabel": "QAYkWG_sectionLabel",
|
|
4146
|
-
"sectionLabelHidden": "QAYkWG_sectionLabelHidden",
|
|
4147
|
-
"sessionOverflowButton": "QAYkWG_sessionOverflowButton",
|
|
4148
|
-
"skeletonBar": "QAYkWG_skeletonBar",
|
|
4149
|
-
"skeletonBarWide": "QAYkWG_skeletonBarWide",
|
|
4150
|
-
"skeletonBars": "QAYkWG_skeletonBars",
|
|
4151
|
-
"skeletonDot": "QAYkWG_skeletonDot",
|
|
4152
|
-
"skeletonRow": "QAYkWG_skeletonRow",
|
|
4153
|
-
"treeBody": "QAYkWG_treeBody",
|
|
4154
|
-
"viewOptionsMenu": "QAYkWG_viewOptionsMenu",
|
|
4155
|
-
"wide": "QAYkWG_wide",
|
|
4156
|
-
"wide-in": "QAYkWG_wide-in",
|
|
4157
|
-
"workspaceDropAfter": "QAYkWG_workspaceDropAfter",
|
|
4158
|
-
"workspaceDropBefore": "QAYkWG_workspaceDropBefore"
|
|
4159
|
-
};
|
|
4160
|
-
//#endregion
|
|
4161
|
-
//#region src/client/rows/WorkspaceBrowser.tsx
|
|
4162
4009
|
/**
|
|
4163
|
-
*
|
|
4164
|
-
*
|
|
4165
|
-
*
|
|
4166
|
-
* dialogs. Wide state renders the full browser; rail state renders the two
|
|
4167
|
-
* region icons (search / add workspace) as 36px controls on the shell's shared
|
|
4168
|
-
* rail entry path, each requesting expansion through the owner share. Adding
|
|
4169
|
-
* is the header button's one action, so it raises the directory flow with no
|
|
4170
|
-
* menu in between; the flow and its error dialog live in WorkspacePicker
|
|
4171
|
-
* (same package — direct composition, no slot between them). A Session row's
|
|
4172
|
-
* "..." menu and hover buttons are the `sidebar.workspaces.session.menu.item`
|
|
4173
|
-
* and `sidebar.workspaces.session.row.action` lists rendered through this
|
|
4174
|
-
* entry's `renderSlot`; the actions in them, this package's own included,
|
|
4175
|
-
* are slot entries with their own behavior, so this component threads no
|
|
4176
|
-
* action callbacks and hosts no action surface.
|
|
4010
|
+
* Hover-card body: full title, relative time, the Session's own scheduled-task
|
|
4011
|
+
* section, and every relevant live status. The task section sits above the
|
|
4012
|
+
* status lines so they stay the card's trailing status line.
|
|
4177
4013
|
*/
|
|
4178
|
-
|
|
4014
|
+
function SessionHoverContent({ node, now, renderSlot, t }) {
|
|
4015
|
+
const statuses = sessionStatuses(node, t).filter((status) => !(node.archived && (status.state === "done" || status.state === "idle")));
|
|
4016
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4017
|
+
className: Rows_module_css_default.hoverContent,
|
|
4018
|
+
children: [
|
|
4019
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4020
|
+
className: Rows_module_css_default.hoverTitle,
|
|
4021
|
+
children: displayTitle(node, t)
|
|
4022
|
+
}),
|
|
4023
|
+
!node.blank && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4024
|
+
className: Rows_module_css_default.hoverTime,
|
|
4025
|
+
children: hoverTimeLabel(node.updatedAt, now, t)
|
|
4026
|
+
}),
|
|
4027
|
+
renderSlot("sidebar.session.row.hover", { sessionId: node.id }),
|
|
4028
|
+
statuses.map((status) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4029
|
+
className: Rows_module_css_default.hoverStatus,
|
|
4030
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: status.state }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: status.label })]
|
|
4031
|
+
}, status.label)),
|
|
4032
|
+
node.archived && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4033
|
+
className: clsx(Rows_module_css_default.hoverStatus, Rows_module_css_default.hoverArchived),
|
|
4034
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconArchiveOutlineRegular, { size: 14 }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("row.archived") })]
|
|
4035
|
+
})
|
|
4036
|
+
]
|
|
4037
|
+
});
|
|
4038
|
+
}
|
|
4179
4039
|
/**
|
|
4180
|
-
*
|
|
4181
|
-
*
|
|
4040
|
+
* One flat search result: title, Workspace context, and optional content
|
|
4041
|
+
* excerpt. Search navigation opens the session only; it does not address an
|
|
4042
|
+
* event inside the conversation. Archived rows carry a hover unarchive
|
|
4043
|
+
* button, because search is where the filter surfaces them for recovery.
|
|
4044
|
+
* @param props.result - merged local/content search row.
|
|
4045
|
+
* @param props.currentId - selected session id.
|
|
4046
|
+
* @param props.onOpen - open the selected session.
|
|
4047
|
+
* @param props.onUnarchive - unarchive an archived result row.
|
|
4048
|
+
* @param props.t - Workspace-browser translation seat.
|
|
4049
|
+
* @returns the result row.
|
|
4182
4050
|
*/
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4051
|
+
function SearchResultItem({ result, currentId, onOpen, onUnarchive, t }) {
|
|
4052
|
+
const selected = result.id === currentId;
|
|
4053
|
+
const statuses = sessionStatuses(result, t);
|
|
4054
|
+
const primaryStatus = statuses[0];
|
|
4055
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4056
|
+
className: clsx(Rows_module_css_default.searchResultRow, selected && Rows_module_css_default.selected, result.archived && Rows_module_css_default.archived),
|
|
4057
|
+
role: "treeitem",
|
|
4058
|
+
"aria-selected": selected,
|
|
4059
|
+
"aria-description": result.archived ? t("toast.archivedNotOpenable") : void 0,
|
|
4060
|
+
onClick: () => {
|
|
4061
|
+
onOpen(result.id);
|
|
4062
|
+
},
|
|
4063
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
4064
|
+
className: Rows_module_css_default.searchResultHeading,
|
|
4065
|
+
children: [
|
|
4066
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4067
|
+
className: Rows_module_css_default.slot,
|
|
4068
|
+
children: !result.archived && primaryStatus.state !== "idle" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionStatusDots, { statuses })
|
|
4069
|
+
}),
|
|
4070
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4071
|
+
className: Rows_module_css_default.searchResultTitle,
|
|
4072
|
+
children: result.title || t("session.untitled")
|
|
4073
|
+
}),
|
|
4074
|
+
result.archived && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4075
|
+
className: Rows_module_css_default.rowActions,
|
|
4076
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
|
|
4077
|
+
label: t("actions.unarchive"),
|
|
4078
|
+
side: "bottom",
|
|
4079
|
+
align: "end",
|
|
4080
|
+
delayMs: 500,
|
|
4081
|
+
portal: true,
|
|
4082
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4083
|
+
type: "button",
|
|
4084
|
+
className: Rows_module_css_default.iconButton,
|
|
4085
|
+
"aria-label": t("menu.unarchiveSession"),
|
|
4086
|
+
onClick: (e) => {
|
|
4087
|
+
e.stopPropagation();
|
|
4088
|
+
onUnarchive(result.id);
|
|
4089
|
+
},
|
|
4090
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconUnarchiveOutlineRegular, { size: 14 })
|
|
4091
|
+
})
|
|
4092
|
+
})
|
|
4093
|
+
})
|
|
4094
|
+
]
|
|
4095
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
4096
|
+
className: Rows_module_css_default.searchResultMeta,
|
|
4097
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4098
|
+
className: Rows_module_css_default.searchResultWorkspace,
|
|
4099
|
+
children: result.workspace || t("group.ungrouped")
|
|
4100
|
+
}), result.snippet !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4101
|
+
className: Rows_module_css_default.searchResultSnippet,
|
|
4102
|
+
children: result.snippet
|
|
4103
|
+
})]
|
|
4104
|
+
})]
|
|
4105
|
+
});
|
|
4197
4106
|
}
|
|
4198
4107
|
/**
|
|
4199
|
-
*
|
|
4200
|
-
*
|
|
4201
|
-
*
|
|
4108
|
+
* One top-level 32px session row: leading 16px cell (status dot, or the
|
|
4109
|
+
* leading seat while the row's primary state is idle), title, relative time or
|
|
4110
|
+
* compact pending label, and the row actions menu. A row that owns a state dot
|
|
4111
|
+
* keeps that cell and renders no seat, so an ambient automation mark never
|
|
4112
|
+
* appears beside the row's own state dot. An archived row keeps the cell blank:
|
|
4113
|
+
* neither marker renders there, and its live status stays on the hover card.
|
|
4114
|
+
* @param props.node - derived session node.
|
|
4115
|
+
* @param props.currentId - selected session id (row highlight).
|
|
4116
|
+
* @param props.now - epoch ms for relative-time formatting.
|
|
4117
|
+
* @param props.onOpen - open a session by id.
|
|
4118
|
+
* @param props.onRenameRequest - open the rename dialog from a title double-click (id + current title).
|
|
4119
|
+
* @param props.renderSlot - child-seat renderer for the row's action lists
|
|
4120
|
+
* (`sidebar.workspaces.session.menu.item` / `sidebar.workspaces.session.row.action`),
|
|
4121
|
+
* its leading decoration, and its hover-card section.
|
|
4122
|
+
* @param props.onReveal - scroll this row into view after search navigation, then acknowledge it.
|
|
4123
|
+
* @param props.drag - optional row-drag target wiring; blank rows cannot start a drag.
|
|
4124
|
+
* @param props.t - the browser root's locale seat.
|
|
4125
|
+
* @returns the session row.
|
|
4202
4126
|
*/
|
|
4203
|
-
function
|
|
4127
|
+
function SessionNodeItem({ node, currentId, now, onOpen, onRenameRequest, renderSlot, onReveal, drag, t }) {
|
|
4128
|
+
const row = node;
|
|
4129
|
+
const title = displayTitle(node, t);
|
|
4130
|
+
const selected = node.id === currentId;
|
|
4131
|
+
const statuses = sessionStatuses(node, t);
|
|
4132
|
+
const primaryStatus = statuses[0];
|
|
4133
|
+
const showStatus = primaryStatus.state !== "idle";
|
|
4134
|
+
const draggable = drag !== void 0 && !row.blank && !row.archived;
|
|
4135
|
+
const [menuOpen, setMenuOpen] = (0, react.useState)(false);
|
|
4136
|
+
const menuOpenState = (0, react.useMemo)(() => [menuOpen, setMenuOpen], [menuOpen]);
|
|
4137
|
+
const rowRef = (0, react.useRef)(null);
|
|
4138
|
+
const titleRef = (0, react.useRef)(null);
|
|
4139
|
+
const marquee = useTitleMarquee(titleRef);
|
|
4204
4140
|
(0, react.useEffect)(() => {
|
|
4205
|
-
if (
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
};
|
|
4219
|
-
}, [active]);
|
|
4220
|
-
}
|
|
4221
|
-
/** Grouping, ordering, and archived-filter menu; own open state so it resets with the wide chrome. */
|
|
4222
|
-
function ViewOptionsMenu({ groupBy, orderBy, archivedFilter, onGroupPick, onOrderPick, onArchivedFilterPick, t }) {
|
|
4223
|
-
const [open, setOpen] = (0, react.useState)(false);
|
|
4224
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
|
|
4225
|
-
open,
|
|
4226
|
-
onClose: () => {
|
|
4227
|
-
setOpen(false);
|
|
4141
|
+
if (onReveal === void 0) return;
|
|
4142
|
+
rowRef.current?.scrollIntoView({ block: "nearest" });
|
|
4143
|
+
onReveal();
|
|
4144
|
+
}, [onReveal]);
|
|
4145
|
+
const ownRow = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4146
|
+
ref: rowRef,
|
|
4147
|
+
"data-row-key": `session:${node.id}`,
|
|
4148
|
+
className: clsx(Rows_module_css_default.sessionRow, selected && Rows_module_css_default.selected, menuOpen && Rows_module_css_default.menuOpen, row.archived && Rows_module_css_default.archived, drag?.marker === "before" && Rows_module_css_default.dropBefore, drag?.marker === "after" && Rows_module_css_default.dropAfter),
|
|
4149
|
+
role: "treeitem",
|
|
4150
|
+
"aria-selected": selected,
|
|
4151
|
+
"aria-description": row.archived ? t("toast.archivedNotOpenable") : void 0,
|
|
4152
|
+
onClick: () => {
|
|
4153
|
+
onOpen(node.id);
|
|
4228
4154
|
},
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
id: "workspace",
|
|
4237
|
-
label: t("groupBy.workspace"),
|
|
4238
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderCloseRegular, {})
|
|
4239
|
-
},
|
|
4240
|
-
{
|
|
4241
|
-
id: "workspace-tree",
|
|
4242
|
-
label: t("groupBy.workspaceTree"),
|
|
4243
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWorkspaceTreeOutlineRegular, {})
|
|
4244
|
-
},
|
|
4245
|
-
{
|
|
4246
|
-
id: "flat",
|
|
4247
|
-
label: t("groupBy.flat"),
|
|
4248
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFlatListOutlineRegular, {})
|
|
4249
|
-
},
|
|
4250
|
-
{
|
|
4251
|
-
type: "separator",
|
|
4252
|
-
id: "order-by-separator"
|
|
4253
|
-
},
|
|
4254
|
-
{
|
|
4255
|
-
type: "label",
|
|
4256
|
-
id: "order-by",
|
|
4257
|
-
text: t("orderBy.label")
|
|
4258
|
-
},
|
|
4259
|
-
{
|
|
4260
|
-
id: "manual",
|
|
4261
|
-
label: t("orderBy.manual"),
|
|
4262
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronsUpDownOutlineRegular, {})
|
|
4263
|
-
},
|
|
4264
|
-
{
|
|
4265
|
-
id: "updated",
|
|
4266
|
-
label: t("orderBy.updated"),
|
|
4267
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconClockOutlineRegular, {})
|
|
4268
|
-
},
|
|
4269
|
-
{
|
|
4270
|
-
type: "separator",
|
|
4271
|
-
id: "archived-filter-separator"
|
|
4272
|
-
},
|
|
4273
|
-
{
|
|
4274
|
-
type: "label",
|
|
4275
|
-
id: "filter-by",
|
|
4276
|
-
text: t("filterBy.label")
|
|
4277
|
-
},
|
|
4278
|
-
{
|
|
4279
|
-
id: "hide-archived",
|
|
4280
|
-
label: t("viewOptions.hideArchived"),
|
|
4281
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconArchiveOffOutlineRegular, {})
|
|
4282
|
-
},
|
|
4283
|
-
{
|
|
4284
|
-
id: "show-archived",
|
|
4285
|
-
label: t("viewOptions.showArchived"),
|
|
4286
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconQueueOutlineRegular, {})
|
|
4287
|
-
},
|
|
4288
|
-
{
|
|
4289
|
-
id: "only-archived",
|
|
4290
|
-
label: t("viewOptions.onlyArchived"),
|
|
4291
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconArchiveCheckOutlineRegular, {})
|
|
4292
|
-
}
|
|
4293
|
-
],
|
|
4294
|
-
selectedIds: [
|
|
4295
|
-
groupBy,
|
|
4296
|
-
orderBy,
|
|
4297
|
-
{
|
|
4298
|
-
default: "hide-archived",
|
|
4299
|
-
show: "show-archived",
|
|
4300
|
-
only: "only-archived"
|
|
4301
|
-
}[archivedFilter]
|
|
4302
|
-
],
|
|
4303
|
-
onSelect: (id) => {
|
|
4304
|
-
if (id === "workspace" || id === "workspace-tree" || id === "flat") onGroupPick(id);
|
|
4305
|
-
else if (id === "manual" || id === "updated") onOrderPick(id);
|
|
4306
|
-
else if (id === "hide-archived") onArchivedFilterPick("default");
|
|
4307
|
-
else if (id === "show-archived") onArchivedFilterPick("show");
|
|
4308
|
-
else if (id === "only-archived") onArchivedFilterPick("only");
|
|
4309
|
-
setOpen(false);
|
|
4155
|
+
onPointerEnter: marquee.enter,
|
|
4156
|
+
onPointerLeave: marquee.leave,
|
|
4157
|
+
draggable,
|
|
4158
|
+
onDragStart: !draggable ? void 0 : (e) => {
|
|
4159
|
+
e.dataTransfer.effectAllowed = "move";
|
|
4160
|
+
e.dataTransfer.setData("text/plain", node.id);
|
|
4161
|
+
drag.start();
|
|
4310
4162
|
},
|
|
4311
|
-
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4163
|
+
onDragEnd: !draggable ? void 0 : drag.end,
|
|
4164
|
+
onDragOver: drag === void 0 ? void 0 : (e) => {
|
|
4165
|
+
if (!drag.active) return;
|
|
4166
|
+
e.preventDefault();
|
|
4167
|
+
e.dataTransfer.dropEffect = "move";
|
|
4168
|
+
drag.hover(rowHalf(e));
|
|
4169
|
+
},
|
|
4170
|
+
onDrop: drag === void 0 ? void 0 : (e) => {
|
|
4171
|
+
if (!drag.active) return;
|
|
4172
|
+
e.preventDefault();
|
|
4173
|
+
drag.drop(rowHalf(e));
|
|
4174
|
+
},
|
|
4175
|
+
children: [
|
|
4176
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4177
|
+
className: Rows_module_css_default.slot,
|
|
4178
|
+
children: !row.archived && !row.blank && (showStatus ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionStatusDots, { statuses }) : renderSlot("sidebar.session.row.leading", { sessionId: node.id }))
|
|
4179
|
+
}),
|
|
4180
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4181
|
+
ref: titleRef,
|
|
4182
|
+
className: Rows_module_css_default.title,
|
|
4183
|
+
onDoubleClick: row.blank ? void 0 : (e) => {
|
|
4184
|
+
e.stopPropagation();
|
|
4185
|
+
onRenameRequest(node.id, row.title);
|
|
4325
4186
|
},
|
|
4326
|
-
children:
|
|
4187
|
+
children: title
|
|
4188
|
+
}),
|
|
4189
|
+
!row.blank && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4190
|
+
className: Rows_module_css_default.time,
|
|
4191
|
+
"aria-hidden": primaryStatus.trailingLabel === void 0 ? void 0 : true,
|
|
4192
|
+
children: primaryStatus.trailingLabel ?? timeLabel(row.updatedAt, now, t)
|
|
4193
|
+
}),
|
|
4194
|
+
row.pinned && !row.archived && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PinnedIndicator, { t }),
|
|
4195
|
+
!row.blank && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
4196
|
+
className: Rows_module_css_default.rowActions,
|
|
4197
|
+
onClick: (e) => {
|
|
4198
|
+
e.stopPropagation();
|
|
4199
|
+
},
|
|
4200
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
|
|
4201
|
+
open: menuOpen,
|
|
4202
|
+
onClose: () => {
|
|
4203
|
+
setMenuOpen(false);
|
|
4204
|
+
},
|
|
4205
|
+
portal: true,
|
|
4206
|
+
closeOnPointerLeave: true,
|
|
4207
|
+
anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4208
|
+
type: "button",
|
|
4209
|
+
className: Rows_module_css_default.iconButton,
|
|
4210
|
+
"aria-label": t("actions.session.aria", { name: title }),
|
|
4211
|
+
onClick: () => {
|
|
4212
|
+
setMenuOpen((v) => !v);
|
|
4213
|
+
},
|
|
4214
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEllipsisOutlineRegular, {})
|
|
4215
|
+
}),
|
|
4216
|
+
children: renderSlot("sidebar.workspaces.session.menu.item", {
|
|
4217
|
+
sessionId: node.id,
|
|
4218
|
+
displayTitle: row.title
|
|
4219
|
+
}, { hookContext: menuOpenState })
|
|
4220
|
+
}), renderSlot("sidebar.workspaces.session.row.action", {
|
|
4221
|
+
sessionId: node.id,
|
|
4222
|
+
displayTitle: row.title
|
|
4223
|
+
})]
|
|
4327
4224
|
})
|
|
4328
|
-
|
|
4225
|
+
]
|
|
4226
|
+
});
|
|
4227
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.HoverCard, {
|
|
4228
|
+
anchor: ownRow,
|
|
4229
|
+
content: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionHoverContent, {
|
|
4230
|
+
node,
|
|
4231
|
+
now,
|
|
4232
|
+
renderSlot,
|
|
4233
|
+
t
|
|
4234
|
+
}),
|
|
4235
|
+
openDelayMs: 800,
|
|
4236
|
+
grouped: true,
|
|
4237
|
+
disabled: menuOpen || drag?.active === true,
|
|
4238
|
+
copyText: row.blank || row.title === "" ? void 0 : row.title,
|
|
4239
|
+
copyLabel: t("copy"),
|
|
4240
|
+
copiedLabel: t("hover.copied")
|
|
4329
4241
|
});
|
|
4330
4242
|
}
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
const
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
if (targetIndex === -1) return;
|
|
4342
|
-
next.splice(targetIndex + (over.half === "after" ? 1 : 0), 0, source.id);
|
|
4343
|
-
return pinCurrentBlank(next, rows.find((row) => row.blank)?.id);
|
|
4243
|
+
//#endregion
|
|
4244
|
+
//#region \0dsh-css:src/client/rows/AnimatedRows.module.css.mjs
|
|
4245
|
+
const css = "._8TnAsq_exits{contain:strict;pointer-events:none;position:absolute;inset:0;overflow:clip}";
|
|
4246
|
+
const tagId = "dsh-group-status/AnimatedRows.module.css";
|
|
4247
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
4248
|
+
const tag = document.createElement("style");
|
|
4249
|
+
tag.dataset.plugin = "dsh-group-status";
|
|
4250
|
+
tag.dataset.pluginCss = tagId;
|
|
4251
|
+
tag.textContent = css;
|
|
4252
|
+
document.head.appendChild(tag);
|
|
4344
4253
|
}
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4254
|
+
var AnimatedRows_module_css_default = { "exits": "_8TnAsq_exits" };
|
|
4255
|
+
//#endregion
|
|
4256
|
+
//#region src/client/rows/AnimatedRows.tsx
|
|
4257
|
+
/** React-commit-driven movement and entry/exit fades for the sidebar's keyed rows. */
|
|
4258
|
+
const ROW_FADE_MS = 100;
|
|
4259
|
+
const ROW_GLIDE_MS = 200;
|
|
4260
|
+
function sameRows(previous, next) {
|
|
4261
|
+
return previous.rowKeys.length === next.rowKeys.length && previous.rowKeys.every((key, index) => key === next.rowKeys[index]);
|
|
4349
4262
|
}
|
|
4263
|
+
function intersects(row, viewport) {
|
|
4264
|
+
return row.bottom > viewport.top && row.top < viewport.bottom && row.right > viewport.left && row.left < viewport.right;
|
|
4265
|
+
}
|
|
4266
|
+
/**
|
|
4267
|
+
* Animates keyed sidebar rows only when their rendered membership or order changes.
|
|
4268
|
+
* Motion starts after the first pointer or keyboard input inside the mounted list.
|
|
4269
|
+
* The parent supplies a positioned container for the inert exit overlay.
|
|
4270
|
+
*/
|
|
4271
|
+
var AnimatedRows = class extends react.Component {
|
|
4272
|
+
armed = false;
|
|
4273
|
+
list = (0, react.createRef)();
|
|
4274
|
+
overlay = (0, react.createRef)();
|
|
4275
|
+
movements = /* @__PURE__ */ new Map();
|
|
4276
|
+
exits = /* @__PURE__ */ new Map();
|
|
4277
|
+
getSnapshotBeforeUpdate(previous) {
|
|
4278
|
+
const list = this.list.current;
|
|
4279
|
+
if (!this.armed || sameRows(previous, this.props) || previous.resetKey !== this.props.resetKey || !previous.ready || !this.props.ready || list === null || typeof list.animate !== "function" || window.matchMedia("(prefers-reduced-motion: reduce)").matches) return null;
|
|
4280
|
+
const viewport = list.getBoundingClientRect();
|
|
4281
|
+
const positions = this.readPositions();
|
|
4282
|
+
const nextKeys = new Set(this.props.rowKeys);
|
|
4283
|
+
const removed = /* @__PURE__ */ new Map();
|
|
4284
|
+
for (const [key, row] of positions) {
|
|
4285
|
+
if (nextKeys.has(key) || !intersects(row.rect, viewport)) continue;
|
|
4286
|
+
const clone = row.element.cloneNode(true);
|
|
4287
|
+
clone.removeAttribute("data-row-key");
|
|
4288
|
+
clone.inert = true;
|
|
4289
|
+
clone.style.setProperty("--dsh-workspace-indent", getComputedStyle(row.element).getPropertyValue("--dsh-workspace-indent"));
|
|
4290
|
+
removed.set(key, {
|
|
4291
|
+
...row,
|
|
4292
|
+
element: clone
|
|
4293
|
+
});
|
|
4294
|
+
}
|
|
4295
|
+
return {
|
|
4296
|
+
positions,
|
|
4297
|
+
removed
|
|
4298
|
+
};
|
|
4299
|
+
}
|
|
4300
|
+
componentDidUpdate(previous, _state, snapshot) {
|
|
4301
|
+
if (snapshot === null) {
|
|
4302
|
+
if (!sameRows(previous, this.props) || previous.resetKey !== this.props.resetKey || previous.ready !== this.props.ready) this.clear();
|
|
4303
|
+
return;
|
|
4304
|
+
}
|
|
4305
|
+
this.cancelMovements();
|
|
4306
|
+
const list = this.list.current;
|
|
4307
|
+
const overlay = this.overlay.current;
|
|
4308
|
+
const viewport = list.getBoundingClientRect();
|
|
4309
|
+
const origin = overlay.getBoundingClientRect();
|
|
4310
|
+
const positions = this.readPositions();
|
|
4311
|
+
for (const [key, row] of positions) {
|
|
4312
|
+
this.removeExit(key);
|
|
4313
|
+
const previousRow = snapshot.positions.get(key);
|
|
4314
|
+
if (!intersects(row.rect, viewport) && (previousRow === void 0 || !intersects(previousRow.rect, viewport))) continue;
|
|
4315
|
+
if (previousRow === void 0) {
|
|
4316
|
+
this.move(row.element, [{ opacity: 0 }, { opacity: 1 }], ROW_FADE_MS);
|
|
4317
|
+
continue;
|
|
4318
|
+
}
|
|
4319
|
+
const dx = previousRow.rect.left - row.rect.left;
|
|
4320
|
+
const dy = previousRow.rect.top - row.rect.top;
|
|
4321
|
+
if (dx === 0 && dy === 0 && previousRow.opacity === 1) continue;
|
|
4322
|
+
this.move(row.element, [{
|
|
4323
|
+
transform: `translate(${String(dx)}px, ${String(dy)}px)`,
|
|
4324
|
+
opacity: previousRow.opacity
|
|
4325
|
+
}, {
|
|
4326
|
+
transform: "translate(0, 0)",
|
|
4327
|
+
opacity: 1
|
|
4328
|
+
}], ROW_GLIDE_MS);
|
|
4329
|
+
}
|
|
4330
|
+
for (const [key, row] of snapshot.removed) {
|
|
4331
|
+
const { element } = row;
|
|
4332
|
+
this.removeExit(key);
|
|
4333
|
+
Object.assign(element.style, {
|
|
4334
|
+
position: "absolute",
|
|
4335
|
+
margin: "0",
|
|
4336
|
+
transform: "none",
|
|
4337
|
+
boxSizing: "border-box",
|
|
4338
|
+
left: `${String(row.rect.left - origin.left)}px`,
|
|
4339
|
+
top: `${String(row.rect.top - origin.top)}px`,
|
|
4340
|
+
width: `${String(row.rect.width)}px`,
|
|
4341
|
+
height: `${String(row.rect.height)}px`
|
|
4342
|
+
});
|
|
4343
|
+
overlay.append(element);
|
|
4344
|
+
const animation = element.animate([{ opacity: row.opacity }, { opacity: 0 }], {
|
|
4345
|
+
duration: ROW_FADE_MS,
|
|
4346
|
+
easing: "ease-out",
|
|
4347
|
+
fill: "forwards"
|
|
4348
|
+
});
|
|
4349
|
+
this.exits.set(key, {
|
|
4350
|
+
element,
|
|
4351
|
+
animation
|
|
4352
|
+
});
|
|
4353
|
+
animation.onfinish = () => {
|
|
4354
|
+
this.removeExit(key);
|
|
4355
|
+
};
|
|
4356
|
+
}
|
|
4357
|
+
}
|
|
4358
|
+
componentWillUnmount() {
|
|
4359
|
+
this.clear();
|
|
4360
|
+
}
|
|
4361
|
+
readPositions() {
|
|
4362
|
+
const rows = this.list.current.querySelectorAll("[data-row-key]");
|
|
4363
|
+
return new Map(Array.from(rows, (element) => [element.dataset.rowKey, {
|
|
4364
|
+
element,
|
|
4365
|
+
rect: element.getBoundingClientRect(),
|
|
4366
|
+
opacity: this.movements.has(element) ? Number(getComputedStyle(element).opacity) : 1
|
|
4367
|
+
}]));
|
|
4368
|
+
}
|
|
4369
|
+
move(element, keyframes, duration) {
|
|
4370
|
+
const animation = element.animate(keyframes, {
|
|
4371
|
+
duration,
|
|
4372
|
+
easing: "ease-out"
|
|
4373
|
+
});
|
|
4374
|
+
this.movements.set(element, animation);
|
|
4375
|
+
animation.onfinish = () => {
|
|
4376
|
+
this.movements.delete(element);
|
|
4377
|
+
animation.cancel();
|
|
4378
|
+
};
|
|
4379
|
+
}
|
|
4380
|
+
cancelMovements() {
|
|
4381
|
+
for (const animation of this.movements.values()) {
|
|
4382
|
+
animation.onfinish = null;
|
|
4383
|
+
animation.cancel();
|
|
4384
|
+
}
|
|
4385
|
+
this.movements.clear();
|
|
4386
|
+
}
|
|
4387
|
+
removeExit(key) {
|
|
4388
|
+
const exit = this.exits.get(key);
|
|
4389
|
+
if (exit === void 0) return;
|
|
4390
|
+
exit.animation.onfinish = null;
|
|
4391
|
+
exit.animation.cancel();
|
|
4392
|
+
exit.element.remove();
|
|
4393
|
+
this.exits.delete(key);
|
|
4394
|
+
}
|
|
4395
|
+
clear() {
|
|
4396
|
+
this.cancelMovements();
|
|
4397
|
+
for (const key of this.exits.keys()) this.removeExit(key);
|
|
4398
|
+
}
|
|
4399
|
+
render() {
|
|
4400
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4401
|
+
ref: this.list,
|
|
4402
|
+
className: this.props.className,
|
|
4403
|
+
role: "tree",
|
|
4404
|
+
"aria-label": this.props.label,
|
|
4405
|
+
onPointerDownCapture: () => {
|
|
4406
|
+
this.armed = true;
|
|
4407
|
+
},
|
|
4408
|
+
onKeyDownCapture: () => {
|
|
4409
|
+
this.armed = true;
|
|
4410
|
+
},
|
|
4411
|
+
children: this.props.children
|
|
4412
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4413
|
+
ref: this.overlay,
|
|
4414
|
+
className: AnimatedRows_module_css_default.exits,
|
|
4415
|
+
"aria-hidden": "true"
|
|
4416
|
+
})] });
|
|
4417
|
+
}
|
|
4418
|
+
};
|
|
4419
|
+
//#endregion
|
|
4420
|
+
//#region src/client/rows/EmptySessions.tsx
|
|
4421
|
+
/** The list-empty placeholder shared by the tree, flat, and search list bodies. */
|
|
4350
4422
|
/** The list-empty placeholder — a glyph over the text; the archived-only view names its filter and offers the way back. */
|
|
4351
4423
|
function EmptySessions({ rowState, onLeaveArchivedOnly, t }) {
|
|
4352
4424
|
const archivedOnly = rowState.archivedFilter === "only";
|
|
@@ -4365,14 +4437,25 @@ window.__ModuleLoader__.load({
|
|
|
4365
4437
|
]
|
|
4366
4438
|
});
|
|
4367
4439
|
}
|
|
4440
|
+
//#endregion
|
|
4441
|
+
//#region src/client/rows/SessionTree.tsx
|
|
4442
|
+
/**
|
|
4443
|
+
* The scrolling session tree: Workspace groups, aggregated live-status badges,
|
|
4444
|
+
* watermarks, and row drag ordering.
|
|
4445
|
+
*
|
|
4446
|
+
* Split out of `WorkspaceBrowser.tsx` so the shell keeps only the header, search
|
|
4447
|
+
* box, and dialogs; this file owns the tree body and its local row limits, and
|
|
4448
|
+
* unmounting it drops both the sessions subscription and those limits.
|
|
4449
|
+
*/
|
|
4450
|
+
const facetsStore = facetStore();
|
|
4368
4451
|
/** The scrolling session tree; unmounting drops the sessions subscription and local row limits. */
|
|
4369
4452
|
function SessionTree({ list, useSessionStatus, startSession, open, workspaces, ungroupedSessionIds, rowState, onLeaveArchivedOnly, workspaceReady, animationResetKey, usePanelInfo, onRenameRequest, onDeleteRequest, onSessionRenameRequest, renderSlot, insertWorkspaceBefore, nestWorkspaces, groupExpansion, setGroupExpanded, setSessionOrder, home, t, revealSessionId, onSessionRevealed, shortcuts, useBadgeSettings }) {
|
|
4370
4453
|
const panelActive = usePanelInfo((info) => info.activePanelId !== null);
|
|
4371
4454
|
const badgeSettings = useBadgeSettings((settings) => settings);
|
|
4372
4455
|
const statuses = useSessionStatus((s) => s);
|
|
4373
|
-
|
|
4374
|
-
const
|
|
4375
|
-
const current = panelActive ? void 0 :
|
|
4456
|
+
const facets = useRegisteredFacets(facetsStore);
|
|
4457
|
+
const mainViewId = (0, react.useMemo)(() => mainViewSessionId(list), [list]);
|
|
4458
|
+
const current = panelActive ? void 0 : mainViewId;
|
|
4376
4459
|
const revealGroup = revealSessionId === void 0 || !workspaceReady ? void 0 : owningGroupKey(workspaces, revealSessionId);
|
|
4377
4460
|
const [storedWatermarks, setStoredWatermarks] = (0, react.useState)({});
|
|
4378
4461
|
const [watermarkMotionKey, setWatermarkMotionKey] = (0, react.useState)(0);
|
|
@@ -4719,6 +4802,9 @@ window.__ModuleLoader__.load({
|
|
|
4719
4802
|
]
|
|
4720
4803
|
});
|
|
4721
4804
|
}
|
|
4805
|
+
//#endregion
|
|
4806
|
+
//#region src/client/rows/ListView.tsx
|
|
4807
|
+
/** The two wide-only list bodies: the flat single-list view and the search results view. */
|
|
4722
4808
|
/** The flat "In one list" body: every session is one draggable top-level row. */
|
|
4723
4809
|
function FlatList({ list, sessionIds, rowState, onLeaveArchivedOnly, useSessionStatus, open, onSessionRenameRequest, usePanelInfo, setSessionOrder, workspaceReady, animationResetKey, revealSessionId, onSessionRevealed, renderSlot, t }) {
|
|
4724
4810
|
const panelActive = usePanelInfo((info) => info.activePanelId !== null);
|
|
@@ -4732,7 +4818,8 @@ window.__ModuleLoader__.load({
|
|
|
4732
4818
|
const [drag, setDrag] = (0, react.useState)(null);
|
|
4733
4819
|
const dropCommitted = (0, react.useRef)(false);
|
|
4734
4820
|
useNativeDragAcceptance(drag !== null);
|
|
4735
|
-
const
|
|
4821
|
+
const mainViewId = (0, react.useMemo)(() => mainViewSessionId(list), [list]);
|
|
4822
|
+
const currentId = panelActive ? void 0 : mainViewId;
|
|
4736
4823
|
const commitDrag = (activeDrag, over) => {
|
|
4737
4824
|
if (dropCommitted.current) return;
|
|
4738
4825
|
dropCommitted.current = true;
|
|
@@ -4827,7 +4914,8 @@ window.__ModuleLoader__.load({
|
|
|
4827
4914
|
resultLimit
|
|
4828
4915
|
]);
|
|
4829
4916
|
const pending = currentRemote.status === "loading";
|
|
4830
|
-
const
|
|
4917
|
+
const mainViewId = (0, react.useMemo)(() => mainViewSessionId(list), [list]);
|
|
4918
|
+
const currentId = panelActive ? void 0 : mainViewId;
|
|
4831
4919
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4832
4920
|
className: clsx(WorkspaceBrowser_module_css_default.treeBody, WorkspaceBrowser_module_css_default.wide),
|
|
4833
4921
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -4837,6 +4925,7 @@ window.__ModuleLoader__.load({
|
|
|
4837
4925
|
className: WorkspaceBrowser_module_css_default.searchTree,
|
|
4838
4926
|
role: "tree",
|
|
4839
4927
|
"aria-label": t("search.results.aria"),
|
|
4928
|
+
"aria-busy": pending,
|
|
4840
4929
|
children: results.items.map((result) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SearchResultItem, {
|
|
4841
4930
|
result,
|
|
4842
4931
|
currentId,
|
|
@@ -4859,20 +4948,35 @@ window.__ModuleLoader__.load({
|
|
|
4859
4948
|
}),
|
|
4860
4949
|
!pending && results.items.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4861
4950
|
className: WorkspaceBrowser_module_css_default.empty,
|
|
4951
|
+
role: "status",
|
|
4862
4952
|
children: t("search.noMatches")
|
|
4863
4953
|
}),
|
|
4864
4954
|
results.hasMore && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4865
4955
|
className: WorkspaceBrowser_module_css_default.searchStatus,
|
|
4956
|
+
role: "status",
|
|
4866
4957
|
children: t("search.hasMore", { n: resultLimit })
|
|
4867
4958
|
})
|
|
4868
4959
|
]
|
|
4869
4960
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: WorkspaceBrowser_module_css_default.fade })]
|
|
4870
4961
|
});
|
|
4871
4962
|
}
|
|
4963
|
+
//#endregion
|
|
4964
|
+
//#region src/client/rows/WorkspaceBrowser.tsx
|
|
4872
4965
|
/**
|
|
4873
|
-
*
|
|
4874
|
-
*
|
|
4875
|
-
*
|
|
4966
|
+
* The workspace/session browsing region filling the sidebar shell's
|
|
4967
|
+
* `sidebar.workspaces` hole: section header (title + view options + add
|
|
4968
|
+
* workspace), search, the grouped tree or flat list, and the workspace
|
|
4969
|
+
* dialogs. Wide state renders the full browser; rail state renders the two
|
|
4970
|
+
* region icons (search / add workspace) as 36px controls on the shell's shared
|
|
4971
|
+
* rail entry path, each requesting expansion through the owner share. Adding
|
|
4972
|
+
* is the header button's one action, so it raises the directory flow with no
|
|
4973
|
+
* menu in between; the flow and its error dialog live in WorkspacePicker
|
|
4974
|
+
* (same package — direct composition, no slot between them). A Session row's
|
|
4975
|
+
* "..." menu and hover buttons are the `sidebar.workspaces.session.menu.item`
|
|
4976
|
+
* and `sidebar.workspaces.session.row.action` lists rendered through this
|
|
4977
|
+
* entry's `renderSlot`; the actions in them, this package's own included,
|
|
4978
|
+
* are slot entries with their own behavior, so this component threads no
|
|
4979
|
+
* action callbacks and hosts no action surface.
|
|
4876
4980
|
*/
|
|
4877
4981
|
function WorkspaceBrowser({ wide, usePanelInfo, expandSidebar, useSessions, useSessionStatus, useWorkspaces, useStore, actions, startSession, open, requestSessionRename, notifyArchivedNotOpenable, renameWorkspace, deleteWorkspace, insertWorkspaceBefore, unarchiveSession, createWorkspace, searchSessions, searchResultLimit, useDirectoryFlow, useHostInfo, useShortcuts, useWorkspaceShortcuts, useBadgeSettings, requestSearch, requestAddWorkspace, closeAddWorkspace, setDirectoryBusy, renderSlot, t }) {
|
|
4878
4982
|
const home = useHostInfo((info) => info.home);
|
|
@@ -4908,7 +5012,7 @@ window.__ModuleLoader__.load({
|
|
|
4908
5012
|
actions.setArchivedFilter("default");
|
|
4909
5013
|
};
|
|
4910
5014
|
const workspaceReady = workspacePhase === "ready" && workspaceStreamState !== "loading";
|
|
4911
|
-
const mainSessionId =
|
|
5015
|
+
const mainSessionId = (0, react.useMemo)(() => mainViewSessionId(list), [list]);
|
|
4912
5016
|
const currentBlank = mainSessionId !== void 0 && list.byId[mainSessionId]?.blank === true ? mainSessionId : void 0;
|
|
4913
5017
|
const ungroupedMemberIds = (0, react.useMemo)(() => {
|
|
4914
5018
|
const accounted = new Set(workspaces.flatMap((workspace) => workspace.sessionIds));
|
|
@@ -5051,7 +5155,7 @@ window.__ModuleLoader__.load({
|
|
|
5051
5155
|
const timer = window.setTimeout(() => {
|
|
5052
5156
|
searchInput.current?.focus({ preventScroll: true });
|
|
5053
5157
|
setSearchOnExpand(false);
|
|
5054
|
-
},
|
|
5158
|
+
}, 300);
|
|
5055
5159
|
return () => {
|
|
5056
5160
|
window.clearTimeout(timer);
|
|
5057
5161
|
};
|
|
@@ -5127,7 +5231,7 @@ window.__ModuleLoader__.load({
|
|
|
5127
5231
|
hasMore: false
|
|
5128
5232
|
});
|
|
5129
5233
|
});
|
|
5130
|
-
},
|
|
5234
|
+
}, 250);
|
|
5131
5235
|
return () => {
|
|
5132
5236
|
window.clearTimeout(timer);
|
|
5133
5237
|
controller.abort();
|
|
@@ -5234,7 +5338,7 @@ window.__ModuleLoader__.load({
|
|
|
5234
5338
|
className: WorkspaceBrowser_module_css_default.searchInput,
|
|
5235
5339
|
type: "text",
|
|
5236
5340
|
placeholder: t("search.placeholder"),
|
|
5237
|
-
maxLength:
|
|
5341
|
+
maxLength: 500,
|
|
5238
5342
|
value: query,
|
|
5239
5343
|
tabIndex: searchExpanded ? 0 : -1,
|
|
5240
5344
|
onChange: (e) => {
|
|
@@ -5647,6 +5751,15 @@ window.__ModuleLoader__.load({
|
|
|
5647
5751
|
//#endregion
|
|
5648
5752
|
//#region src/client/session-actions/derived.ts
|
|
5649
5753
|
/**
|
|
5754
|
+
* "还没投影过"的标记。
|
|
5755
|
+
*
|
|
5756
|
+
* 不能用 `undefined` 兼任:投影**合法地**可能返回 `undefined`("当前没有待处理的
|
|
5757
|
+
* 请求"就是一次正常的空结果),把它当成"未计算"会让每次读都重跑一次 `project`。
|
|
5758
|
+
* 投影若新建 `Set` / 数组,消费者每次读都拿到新引用,选择器于是永远不收敛——这正是
|
|
5759
|
+
* 本函数要避免的那件事。Symbol 不与任何投影结果相等,且只在本模块内存在。
|
|
5760
|
+
*/
|
|
5761
|
+
const NOT_PROJECTED = Symbol("derive.notProjected");
|
|
5762
|
+
/**
|
|
5650
5763
|
* Project one observable into another, recomputing only when the source
|
|
5651
5764
|
* snapshot changes identity, so consumers that select from the projection
|
|
5652
5765
|
* (a Set lookup per row) never rebuild it per read.
|
|
@@ -5656,11 +5769,11 @@ window.__ModuleLoader__.load({
|
|
|
5656
5769
|
*/
|
|
5657
5770
|
function derive(source, project) {
|
|
5658
5771
|
let seen;
|
|
5659
|
-
let value;
|
|
5772
|
+
let value = NOT_PROJECTED;
|
|
5660
5773
|
return {
|
|
5661
5774
|
getSnapshot: () => {
|
|
5662
5775
|
const snapshot = source.getSnapshot();
|
|
5663
|
-
if (value ===
|
|
5776
|
+
if (value === NOT_PROJECTED || snapshot !== seen) {
|
|
5664
5777
|
seen = snapshot;
|
|
5665
5778
|
value = project(snapshot);
|
|
5666
5779
|
}
|
|
@@ -6042,16 +6155,14 @@ window.__ModuleLoader__.load({
|
|
|
6042
6155
|
"settings.badge.showPendingHint": "关闭后,等待审批 / 计划评审 / 等待回答的会话不再画黄点,也不再计入折叠徽标的数字与悬停明细。",
|
|
6043
6156
|
"settings.badge.showCompleted": "显示「已完成」绿点",
|
|
6044
6157
|
"settings.badge.showCompletedHint": "关闭后,「已完成」的会话不再画绿点,也不再计入折叠徽标的数字与悬停明细。会话行自身的绿点不受影响。",
|
|
6045
|
-
"settings.badge.showScheduled": "显示「定时任务」时钟",
|
|
6046
|
-
"settings.badge.showScheduledHint": "关闭后,带定时提醒的会话(来自 dsh-later)不再画时钟图标,也不再计入折叠徽标的数字与悬停明细。",
|
|
6047
|
-
"settings.badge.showWaitingReview": "显示「等待评审」沙漏",
|
|
6048
|
-
"settings.badge.showWaitingReviewHint": "关闭后,等待代码评审的会话(来自 dsh-gitea-dispatch)不再画沙漏图标,也不再计入折叠徽标的数字与悬停明细。",
|
|
6049
6158
|
"settings.hover.section": "悬停明细",
|
|
6050
6159
|
"settings.hover.showDetails": "显示状态明细行",
|
|
6051
6160
|
"settings.hover.showDetailsHint": "关闭后,悬停卡只显示工作区标题、路径和创建时间——不再显示聚合状态明细行。",
|
|
6052
6161
|
"settings.count.section": "数字显示",
|
|
6053
6162
|
"settings.count.threshold": "数字显示阈值",
|
|
6054
6163
|
"settings.count.thresholdHint": "数量 >= 此值时才在徽标上显示数字。默认 2(即 count=1 时省略,因为状态点已表达\"有东西\")。设为 1 则始终显示。",
|
|
6164
|
+
"settings.count.decrease": "减少",
|
|
6165
|
+
"settings.count.increase": "增加",
|
|
6055
6166
|
"settings.save": "保存",
|
|
6056
6167
|
"settings.saving": "保存中…",
|
|
6057
6168
|
"settings.saveFailed": "本部署没有接受这些值,已保留供你修改。",
|
|
@@ -6187,16 +6298,14 @@ window.__ModuleLoader__.load({
|
|
|
6187
6298
|
"settings.badge.showPendingHint": "When off, Sessions waiting for approval / plan review / answer stop drawing the yellow dot and drop out of the collapsed badge count and its hover details.",
|
|
6188
6299
|
"settings.badge.showCompleted": "Show the completed dot",
|
|
6189
6300
|
"settings.badge.showCompletedHint": "When off, completed Sessions stop drawing the dot and drop out of the collapsed badge count and its hover details. The Session row's own dot is unaffected.",
|
|
6190
|
-
"settings.badge.showScheduled": "Show the scheduled icon",
|
|
6191
|
-
"settings.badge.showScheduledHint": "When off, Sessions with scheduled reminders (from dsh-later) stop drawing the clock icon and drop out of the collapsed badge count and its hover details.",
|
|
6192
|
-
"settings.badge.showWaitingReview": "Show the waiting-for-review icon",
|
|
6193
|
-
"settings.badge.showWaitingReviewHint": "When off, Sessions waiting for code review (from dsh-gitea-dispatch) stop drawing the hourglass icon and drop out of the collapsed badge count and its hover details.",
|
|
6194
6301
|
"settings.hover.section": "Hover details",
|
|
6195
6302
|
"settings.hover.showDetails": "Show status detail lines",
|
|
6196
6303
|
"settings.hover.showDetailsHint": "When off, the hover card shows only the workspace title, path, and creation time — no aggregated status lines.",
|
|
6197
6304
|
"settings.count.section": "Count display",
|
|
6198
6305
|
"settings.count.threshold": "Count display threshold",
|
|
6199
6306
|
"settings.count.thresholdHint": "The count is shown on the badge only when the total is >= this value. Default 2 (omits the count when it equals 1 — the dots already convey \"something is happening\"). Set to 1 to always show.",
|
|
6307
|
+
"settings.count.decrease": "Decrease",
|
|
6308
|
+
"settings.count.increase": "Increase",
|
|
6200
6309
|
"settings.save": "Save",
|
|
6201
6310
|
"settings.saving": "Saving…",
|
|
6202
6311
|
"settings.saveFailed": "The deployment did not accept these values; they were left for you to correct.",
|