dsh-group-status 0.5.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 +71 -0
- package/README.en.md +4 -2
- package/README.md +4 -2
- package/lib/client.js +1192 -1157
- package/lib/client.js.map +1 -1
- package/lib/index.js +31 -4
- 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.js +3 -3
- package/lib/types/client/BadgeSettingsCard.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 -0
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/locales.js +4 -0
- 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/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 +7 -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/host-contract.d.ts +15 -1
- package/lib/types/host-contract.d.ts.map +1 -1
- package/lib/types/host-contract.js +47 -4
- package/lib/types/host-contract.js.map +1 -1
- package/lib/types/index.d.ts +6 -3
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +10 -4
- package/lib/types/index.js.map +1 -1
- package/package.json +15 -2
package/lib/client.js
CHANGED
|
@@ -857,6 +857,8 @@ window.__ModuleLoader__.load({
|
|
|
857
857
|
text: fieldText(BADGE_FIELDS.countThreshold),
|
|
858
858
|
label: t("settings.count.threshold"),
|
|
859
859
|
hint: t("settings.count.thresholdHint"),
|
|
860
|
+
decreaseLabel: t("settings.count.decrease"),
|
|
861
|
+
increaseLabel: t("settings.count.increase"),
|
|
860
862
|
state,
|
|
861
863
|
actions
|
|
862
864
|
})]
|
|
@@ -891,7 +893,7 @@ window.__ModuleLoader__.load({
|
|
|
891
893
|
});
|
|
892
894
|
}
|
|
893
895
|
/** 单行数字输入:label + 带加减按钮的数字输入 + hint。 */
|
|
894
|
-
function NumberRow({ field, text, label, hint, state, actions }) {
|
|
896
|
+
function NumberRow({ field, text, label, hint, decreaseLabel, increaseLabel, state, actions }) {
|
|
895
897
|
const disabled = !state.writable || state.saving;
|
|
896
898
|
const currentValue = Number(text) || 0;
|
|
897
899
|
const decrement = () => {
|
|
@@ -915,7 +917,7 @@ window.__ModuleLoader__.load({
|
|
|
915
917
|
className: BadgeSettingsCard_module_css_default.badgeNumberButton,
|
|
916
918
|
disabled: disabled || currentValue <= 0,
|
|
917
919
|
onClick: decrement,
|
|
918
|
-
"aria-label":
|
|
920
|
+
"aria-label": decreaseLabel,
|
|
919
921
|
children: "−"
|
|
920
922
|
}),
|
|
921
923
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
@@ -927,7 +929,7 @@ window.__ModuleLoader__.load({
|
|
|
927
929
|
className: BadgeSettingsCard_module_css_default.badgeNumberButton,
|
|
928
930
|
disabled,
|
|
929
931
|
onClick: increment,
|
|
930
|
-
"aria-label":
|
|
932
|
+
"aria-label": increaseLabel,
|
|
931
933
|
children: "+"
|
|
932
934
|
})
|
|
933
935
|
]
|
|
@@ -987,11 +989,34 @@ window.__ModuleLoader__.load({
|
|
|
987
989
|
}
|
|
988
990
|
//#endregion
|
|
989
991
|
//#region src/client/toast/register.tsx
|
|
990
|
-
/**
|
|
992
|
+
/**
|
|
993
|
+
* Wrap a business display mapping without making the slot component generic.
|
|
994
|
+
* @param display - 该键的纯文案映射;`notice` 的类型由键决定,写错字段即编译失败。
|
|
995
|
+
*/
|
|
991
996
|
function plainToast(display) {
|
|
992
997
|
return () => ({ display });
|
|
993
998
|
}
|
|
994
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
|
+
/**
|
|
995
1020
|
* Register notice presentations with their business-owned display fields and actions.
|
|
996
1021
|
* @param ctx - workspace plugin context.
|
|
997
1022
|
* @param viewStore - viewing store, read only by archive notices.
|
|
@@ -1008,130 +1033,72 @@ window.__ModuleLoader__.load({
|
|
|
1008
1033
|
actions.showArchived();
|
|
1009
1034
|
}
|
|
1010
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
|
+
];
|
|
1011
1083
|
ctx.slots.registerFactory({
|
|
1012
1084
|
name: "workspace.toast.content",
|
|
1013
1085
|
scope: "root"
|
|
1014
1086
|
}, WorkspaceToast);
|
|
1015
1087
|
ctx.slots.inject("workspace.toast", function* () {
|
|
1016
|
-
yield ctx.slots.register({
|
|
1017
|
-
name: "workspace.toast",
|
|
1018
|
-
key: "sessionIdCopied",
|
|
1019
|
-
locale: "workspace",
|
|
1020
|
-
inject: plainToast((_notice, t) => ({
|
|
1021
|
-
text: t("toast.sessionIdCopied"),
|
|
1022
|
-
tone: "success"
|
|
1023
|
-
}))
|
|
1024
|
-
}, toastEntry);
|
|
1025
|
-
yield ctx.slots.register({
|
|
1026
|
-
name: "workspace.toast",
|
|
1027
|
-
key: "sessionIdCopyFailed",
|
|
1028
|
-
locale: "workspace",
|
|
1029
|
-
inject: plainToast((_notice, t) => ({
|
|
1030
|
-
text: t("toast.sessionIdCopyFailed"),
|
|
1031
|
-
tone: "warning"
|
|
1032
|
-
}))
|
|
1033
|
-
}, toastEntry);
|
|
1034
|
-
yield ctx.slots.register({
|
|
1035
|
-
name: "workspace.toast",
|
|
1036
|
-
key: "pinFailed",
|
|
1037
|
-
locale: "workspace",
|
|
1038
|
-
inject: plainToast((_notice, t) => ({
|
|
1039
|
-
text: t("toast.pinFailed"),
|
|
1040
|
-
tone: "warning"
|
|
1041
|
-
}))
|
|
1042
|
-
}, toastEntry);
|
|
1043
|
-
yield ctx.slots.register({
|
|
1044
|
-
name: "workspace.toast",
|
|
1045
|
-
key: "unpinFailed",
|
|
1046
|
-
locale: "workspace",
|
|
1047
|
-
inject: plainToast((_notice, t) => ({
|
|
1048
|
-
text: t("toast.unpinFailed"),
|
|
1049
|
-
tone: "warning"
|
|
1050
|
-
}))
|
|
1051
|
-
}, toastEntry);
|
|
1052
|
-
yield ctx.slots.register({
|
|
1053
|
-
name: "workspace.toast",
|
|
1054
|
-
key: "archivedNotOpenable",
|
|
1055
|
-
locale: "workspace",
|
|
1056
|
-
inject: plainToast((_notice, t) => ({
|
|
1057
|
-
text: t("toast.archivedNotOpenable"),
|
|
1058
|
-
tone: "warning"
|
|
1059
|
-
}))
|
|
1060
|
-
}, toastEntry);
|
|
1061
|
-
yield ctx.slots.register({
|
|
1062
|
-
name: "workspace.toast",
|
|
1063
|
-
key: "defaultWorkspaceFailed",
|
|
1064
|
-
locale: "workspace",
|
|
1065
|
-
inject: plainToast((_notice, t) => ({
|
|
1066
|
-
text: t("defaultWorkspace.failed"),
|
|
1067
|
-
tone: "warning"
|
|
1068
|
-
}))
|
|
1069
|
-
}, toastEntry);
|
|
1070
|
-
yield ctx.slots.register({
|
|
1071
|
-
name: "workspace.toast",
|
|
1072
|
-
key: "shortcutForkFailed",
|
|
1073
|
-
locale: "workspace",
|
|
1074
|
-
inject: plainToast((notice, t) => ({
|
|
1075
|
-
text: t(notice.reason === "unavailable" ? "shortcut.noCompletedTurn" : "shortcut.forkFailed"),
|
|
1076
|
-
tone: "plain"
|
|
1077
|
-
}))
|
|
1078
|
-
}, toastEntry);
|
|
1079
|
-
yield ctx.slots.register({
|
|
1080
|
-
name: "workspace.toast",
|
|
1081
|
-
key: "createFailed",
|
|
1082
|
-
locale: "workspace",
|
|
1083
|
-
inject: plainToast((notice, t) => ({
|
|
1084
|
-
text: t("toast.createFailed", { message: notice.message }),
|
|
1085
|
-
tone: "warning",
|
|
1086
|
-
holdMs: 6e3
|
|
1087
|
-
}))
|
|
1088
|
-
}, toastEntry);
|
|
1089
|
-
yield ctx.slots.register({
|
|
1088
|
+
for (const { key, inject } of plainToasts) yield ctx.slots.register({
|
|
1090
1089
|
name: "workspace.toast",
|
|
1091
|
-
key
|
|
1090
|
+
key,
|
|
1092
1091
|
locale: "workspace",
|
|
1093
|
-
inject
|
|
1094
|
-
text: t("toast.forkRequiresOpen"),
|
|
1095
|
-
tone: "warning",
|
|
1096
|
-
holdMs: 6e3,
|
|
1097
|
-
actions: [{
|
|
1098
|
-
label: t("toast.openForkSource"),
|
|
1099
|
-
onClick: () => {
|
|
1100
|
-
dismiss();
|
|
1101
|
-
actions.openForkSource(notice.sessionId);
|
|
1102
|
-
}
|
|
1103
|
-
}]
|
|
1104
|
-
}))
|
|
1092
|
+
inject
|
|
1105
1093
|
}, toastEntry);
|
|
1106
|
-
yield ctx.slots.register({
|
|
1107
|
-
name: "workspace.toast",
|
|
1108
|
-
key: "archived",
|
|
1109
|
-
locale: "workspace",
|
|
1110
|
-
store: viewStore,
|
|
1111
|
-
inject: () => ({
|
|
1112
|
-
display: (notice, t, dismiss) => ({
|
|
1113
|
-
text: t("toast.archived"),
|
|
1114
|
-
tone: "success",
|
|
1115
|
-
holdMs: 6e3,
|
|
1116
|
-
actions: [{
|
|
1117
|
-
label: t("toast.archivedUndo"),
|
|
1118
|
-
onClick: () => {
|
|
1119
|
-
dismiss();
|
|
1120
|
-
actions.undoArchive(notice.sessionId);
|
|
1121
|
-
}
|
|
1122
|
-
}]
|
|
1123
|
-
}),
|
|
1124
|
-
filterAction: archiveFilterAction
|
|
1125
|
-
})
|
|
1126
|
-
}, archiveToastEntry);
|
|
1127
|
-
yield ctx.slots.register({
|
|
1094
|
+
for (const { key, text } of ARCHIVE_TOASTS) yield ctx.slots.register({
|
|
1128
1095
|
name: "workspace.toast",
|
|
1129
|
-
key
|
|
1096
|
+
key,
|
|
1130
1097
|
locale: "workspace",
|
|
1131
1098
|
store: viewStore,
|
|
1132
1099
|
inject: () => ({
|
|
1133
1100
|
display: (notice, t, dismiss) => ({
|
|
1134
|
-
text: t(
|
|
1101
|
+
text: t(text),
|
|
1135
1102
|
tone: "success",
|
|
1136
1103
|
holdMs: 6e3,
|
|
1137
1104
|
actions: [{
|
|
@@ -2698,462 +2665,902 @@ window.__ModuleLoader__.load({
|
|
|
2698
2665
|
function workspaceDisplayTitle(title, localizedDefault) {
|
|
2699
2666
|
return title === "default-workspace" ? localizedDefault : title;
|
|
2700
2667
|
}
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
planReview: 0,
|
|
2712
|
-
question: 0,
|
|
2713
|
-
done: 0,
|
|
2714
|
-
subagents: 0,
|
|
2715
|
-
facets: /* @__PURE__ */ new Map()
|
|
2716
|
-
});
|
|
2717
|
-
/**
|
|
2718
|
-
* 合并两份分组折果:本插件自己的计数相加,外部状态来源**按 facet 各自合并**。
|
|
2719
|
-
*
|
|
2720
|
-
* 用于多层聚合:折叠的父文件夹要覆盖整棵子树,而子树里每一层都已各自折过一遍。
|
|
2721
|
-
* `FacetFold.data` 怎么合归该 facet 自己决定,本模块从不解释。
|
|
2722
|
-
*/
|
|
2723
|
-
function mergeLiveStatus(a, b) {
|
|
2724
|
-
return {
|
|
2725
|
-
running: a.running + b.running,
|
|
2726
|
-
approval: a.approval + b.approval,
|
|
2727
|
-
planReview: a.planReview + b.planReview,
|
|
2728
|
-
question: a.question + b.question,
|
|
2729
|
-
done: a.done + b.done,
|
|
2730
|
-
subagents: a.subagents + b.subagents,
|
|
2731
|
-
facets: mergeFacets(a.facets, b.facets)
|
|
2732
|
-
};
|
|
2733
|
-
}
|
|
2734
|
-
/** 按 facet id 合并两侧折果;只有一侧有的来源原样带过。 */
|
|
2735
|
-
function mergeFacets(a, b) {
|
|
2736
|
-
if (a.size === 0) return b;
|
|
2737
|
-
if (b.size === 0) return a;
|
|
2738
|
-
const merged = new Map(a);
|
|
2739
|
-
for (const [id, entry] of b) {
|
|
2740
|
-
const own = merged.get(id);
|
|
2741
|
-
merged.set(id, own === void 0 ? entry : {
|
|
2742
|
-
facet: own.facet,
|
|
2743
|
-
fold: own.facet.merge?.(own.fold, entry.fold) ?? defaultMergeFold(own.fold, entry.fold)
|
|
2744
|
-
});
|
|
2745
|
-
}
|
|
2746
|
-
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);
|
|
2747
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";
|
|
2748
2687
|
/**
|
|
2749
|
-
*
|
|
2750
|
-
*
|
|
2751
|
-
*
|
|
2752
|
-
* locale at render time.
|
|
2753
|
-
*
|
|
2754
|
-
* @param nodes - member rows of the group.
|
|
2755
|
-
* @param sessionIds - the same members' Session ids (facets address their own data by id).
|
|
2756
|
-
* @param facets - the registered status sources to fold over this group.
|
|
2757
|
-
* @param now - one clock reading for the whole pass, so sibling facets cannot disagree
|
|
2758
|
-
* 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.
|
|
2759
2691
|
*/
|
|
2760
|
-
function
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
}
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
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);
|
|
2725
|
+
};
|
|
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);
|
|
2734
|
+
});
|
|
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);
|
|
2789
2768
|
}
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2769
|
+
};
|
|
2770
|
+
const handleSelect = (id) => {
|
|
2771
|
+
if (id === ADD_WORKSPACE) {
|
|
2772
|
+
openDirectoryFlow();
|
|
2773
|
+
return;
|
|
2793
2774
|
}
|
|
2794
|
-
|
|
2795
|
-
}
|
|
2796
|
-
const folded = /* @__PURE__ */ new Map();
|
|
2797
|
-
for (const { facet } of facets) {
|
|
2798
|
-
let snapshot;
|
|
2799
|
-
try {
|
|
2800
|
-
snapshot = facet.getSnapshot();
|
|
2801
|
-
} catch {
|
|
2802
|
-
snapshot = void 0;
|
|
2803
|
-
}
|
|
2804
|
-
try {
|
|
2805
|
-
const result = facet.fold(snapshot, sessionIds, now);
|
|
2806
|
-
if (result !== void 0 && result.count > 0) folded.set(facet.id, {
|
|
2807
|
-
facet,
|
|
2808
|
-
fold: result
|
|
2809
|
-
});
|
|
2810
|
-
} catch {}
|
|
2811
|
-
}
|
|
2812
|
-
return {
|
|
2813
|
-
running,
|
|
2814
|
-
approval,
|
|
2815
|
-
planReview,
|
|
2816
|
-
question,
|
|
2817
|
-
done,
|
|
2818
|
-
subagents,
|
|
2819
|
-
facets: folded
|
|
2775
|
+
onPick(id);
|
|
2820
2776
|
};
|
|
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
|
+
] });
|
|
2821
2820
|
}
|
|
2822
2821
|
/**
|
|
2823
|
-
*
|
|
2824
|
-
*
|
|
2825
|
-
*
|
|
2826
|
-
*
|
|
2827
|
-
* keep counting them — so expansion is a rendering fact, never an input to the
|
|
2828
|
-
* aggregation. Short-circuiting expanded groups to `EMPTY_LIVE_STATUS` (the shape this
|
|
2829
|
-
* had before the tree-wide aggregator existed, harmless while nothing aggregated) makes
|
|
2830
|
-
* every collapsed parent undercount by the size of its expanded descendants.
|
|
2831
|
-
*
|
|
2832
|
-
* @param groups - derived groups, each with its member rows already populated.
|
|
2833
|
-
* @param facets - registered sources, already filtered by the visibility settings.
|
|
2834
|
-
* @param now - one clock reading shared by every group in the pass.
|
|
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.
|
|
2835
2826
|
*/
|
|
2836
|
-
function
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
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
|
+
});
|
|
2840
|
+
}
|
|
2841
|
+
//#endregion
|
|
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);
|
|
2851
|
+
}
|
|
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);
|
|
2840
2908
|
}
|
|
2841
2909
|
/**
|
|
2842
|
-
*
|
|
2843
|
-
*
|
|
2844
|
-
*
|
|
2845
|
-
* keep counting them — so the walk never depends on a group's expanded state.
|
|
2846
|
-
*
|
|
2847
|
-
* @param direct - each group's own-member status, keyed by group key.
|
|
2848
|
-
* @param childrenByParent - the rendered tree: parent key (`undefined` = roots) → children.
|
|
2849
|
-
* @returns aggregated status per group key: own members plus the whole subtree.
|
|
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.
|
|
2850
2913
|
*/
|
|
2851
|
-
function
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
return
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
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]);
|
|
2931
|
+
}
|
|
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);
|
|
2945
|
+
}
|
|
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";
|
|
2867
2950
|
}
|
|
2868
2951
|
/**
|
|
2869
|
-
*
|
|
2870
|
-
* the sidebar renders from.
|
|
2952
|
+
* Session the main view currently retains, if any.
|
|
2871
2953
|
*
|
|
2872
|
-
*
|
|
2873
|
-
*
|
|
2874
|
-
*
|
|
2875
|
-
*
|
|
2876
|
-
* @
|
|
2877
|
-
* @param direct - each group's own-member status, from {@link directStatusesForGroups}.
|
|
2878
|
-
* @param childrenByParent - the rendered tree: parent key (`undefined` = roots) → children.
|
|
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.
|
|
2879
2959
|
*/
|
|
2880
|
-
function
|
|
2881
|
-
|
|
2882
|
-
return groups.map((group) => ({
|
|
2883
|
-
...group,
|
|
2884
|
-
liveStatus: aggregated.get(group.key) ?? EMPTY_LIVE_STATUS
|
|
2885
|
-
}));
|
|
2886
|
-
}
|
|
2887
|
-
/** The blank row plus the first `watermark` non-blank rows, in list order. */
|
|
2888
|
-
function watermarkRows(sessions, watermark) {
|
|
2889
|
-
let count = 0;
|
|
2890
|
-
const rows = sessions.filter((session) => {
|
|
2891
|
-
if (session.blank) return true;
|
|
2892
|
-
count += 1;
|
|
2893
|
-
return count <= watermark;
|
|
2894
|
-
});
|
|
2895
|
-
return {
|
|
2896
|
-
rows,
|
|
2897
|
-
hiddenCount: sessions.length - rows.length
|
|
2898
|
-
};
|
|
2960
|
+
function mainViewSessionId(list) {
|
|
2961
|
+
return Object.values(list.byId).find((session) => (session.retainedBy.mainView ?? 0) > 0)?.id;
|
|
2899
2962
|
}
|
|
2963
|
+
//#endregion
|
|
2964
|
+
//#region src/client/rows/ViewOptionsMenu.tsx
|
|
2900
2965
|
/**
|
|
2901
|
-
*
|
|
2902
|
-
*
|
|
2903
|
-
*
|
|
2904
|
-
*
|
|
2905
|
-
* @param revealSessionId - Session selected from search and required to be visible.
|
|
2906
|
-
* @returns the group's baseline non-blank row count.
|
|
2907
|
-
*/
|
|
2908
|
-
function requiredWatermark(sessions, current, revealSessionId) {
|
|
2909
|
-
let index = 0;
|
|
2910
|
-
let required = 0;
|
|
2911
|
-
for (const session of sessions) {
|
|
2912
|
-
if (session.blank) continue;
|
|
2913
|
-
index += 1;
|
|
2914
|
-
if (index <= 50 && (session.running || session.runningSubagentCount > 0 || session.pendingInteraction !== void 0 || session.completed || session.id === current) || session.id === revealSessionId) required = index;
|
|
2915
|
-
}
|
|
2916
|
-
return required;
|
|
2917
|
-
}
|
|
2918
|
-
/**
|
|
2919
|
-
* Raise each listed group's watermark to its requirement; returns `marks` itself when nothing rises.
|
|
2920
|
-
* @param marks - watermarks stored by the current browser tree.
|
|
2921
|
-
* @param required - required baselines, in group order.
|
|
2922
|
-
* @returns either the unchanged input or a new object containing only raised groups.
|
|
2966
|
+
* Grouping, ordering, and archived-filter menu for the section header.
|
|
2967
|
+
*
|
|
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.
|
|
2923
2970
|
*/
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
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
|
+
})
|
|
3079
|
+
});
|
|
2931
3080
|
}
|
|
2932
|
-
//#endregion
|
|
2933
|
-
//#region src/client/use-registered-facets.ts
|
|
2934
3081
|
/**
|
|
2935
|
-
*
|
|
2936
|
-
*
|
|
2937
|
-
* ## 为什么单独成文
|
|
2938
|
-
*
|
|
2939
|
-
* 这一步此前直接写在 `rows/WorkspaceBrowser.tsx` 里,两行:
|
|
2940
|
-
*
|
|
2941
|
-
* ```ts
|
|
2942
|
-
* useSyncExternalStore(facetsStore.subscribe, facetsStore.getVersion, …) // 返回值丢掉
|
|
2943
|
-
* const facets = useMemo(() => visibleFacets(facetsStore.list(), settings), [settings])
|
|
2944
|
-
* ```
|
|
2945
|
-
*
|
|
2946
|
-
* 看着没问题——订阅了、也 memo 了。实际是**订阅通知只重渲染、不重算**:
|
|
2947
|
-
* `useSyncExternalStore` 的返回值被丢掉,`useMemo` 的依赖里就没有任何东西随登记面变化,
|
|
2948
|
-
* 于是那次重渲染原样拿回**上一次**的登记面。提供方在自己的 `apply` 里注册,而宿主不保证
|
|
2949
|
-
* `apply` 与本组件首渲染的先后:注册**晚于**首渲染时,facet 明明登记上了,却永远进不了
|
|
2950
|
-
* 徽标——不报错、不红测试(能渲染这条路径的那份上游 spec 在本仓库是跳过的,见
|
|
2951
|
-
* `vitest.config.ts` 的 `MONOREPO_ONLY_SPECS`),只能靠刷新页面碰运气。
|
|
2952
|
-
*
|
|
2953
|
-
* 版本号必须**同时**当两件事用:① 触发重渲染的信号,② memo 的依赖。这里把它收进一个
|
|
2954
|
-
* hook,让「只用①、丢掉②」在结构上写不出来,并且让这条接缝能被 `@testing-library/react`
|
|
2955
|
-
* 直接覆盖(本文件零宿主 import,宿主依赖闭包进不来)。
|
|
2956
|
-
*
|
|
2957
|
-
* ## 为什么现在不再按设置筛
|
|
2958
|
-
*
|
|
2959
|
-
* 旧版本这里会过一道 `visibleFacets(list, settings)`,按 `facet.settingKey` 查本插件的
|
|
2960
|
-
* 布尔开关。那意味着**本插件的配置面里写着别人家的语义**(`showScheduledInBadge` /
|
|
2961
|
-
* `showWaitingReviewInBadge`,连图标形状都替对方写死),与"本插件不认识任何来源"直接
|
|
2962
|
-
* 矛盾。现在外部来源的显隐归**来源自己**:它想被关掉就不注册 facet,登记面即所见。
|
|
2963
|
-
* 本插件的开关只管自己那三档会话状态(见 `badge-settings.ts`)。
|
|
3082
|
+
* Zero-valued sentinel for {@link GroupLiveStatus}.
|
|
2964
3083
|
*
|
|
2965
|
-
*
|
|
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.
|
|
2966
3087
|
*/
|
|
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
|
+
});
|
|
2967
3097
|
/**
|
|
2968
|
-
*
|
|
3098
|
+
* 一份**没有任何贡献**的折果:六个计数全 0 且没有外部来源。
|
|
2969
3099
|
*
|
|
2970
|
-
*
|
|
2971
|
-
*
|
|
2972
|
-
* @param store - facet 消费侧存储(`facetStore()` 单例)。
|
|
3100
|
+
* 它在合并里是加法单位元,所以「加它」不该产生新对象——见 {@link mergeLiveStatus}。
|
|
2973
3101
|
*/
|
|
2974
|
-
function
|
|
2975
|
-
|
|
2976
|
-
return (0, react.useMemo)(() => [...store.list()], [version]);
|
|
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;
|
|
2977
3104
|
}
|
|
2978
|
-
//#endregion
|
|
2979
|
-
//#region src/client/badge-labels.ts
|
|
2980
3105
|
/**
|
|
2981
|
-
*
|
|
3106
|
+
* 合并两份分组折果:本插件自己的计数相加,外部状态来源**按 facet 各自合并**。
|
|
2982
3107
|
*
|
|
2983
|
-
*
|
|
2984
|
-
*
|
|
2985
|
-
* 抛错),所以外来 facet 传了自己命名空间的键,最坏读出的是那串键名而不是崩渲染。
|
|
2986
|
-
* 要正确本地化,provider 应当闭包自己的翻译函数、忽略这里传入的 `t`。
|
|
2987
|
-
*/
|
|
2988
|
-
function asFacetTranslate(t) {
|
|
2989
|
-
return (key, options) => t(key, options);
|
|
2990
|
-
}
|
|
2991
|
-
/** 该 facet 贡献进徽标数字的条数。 */
|
|
2992
|
-
function facetCountTotal(s) {
|
|
2993
|
-
let total = 0;
|
|
2994
|
-
for (const entry of s.facets.values()) total += entry.fold.count;
|
|
2995
|
-
return total;
|
|
2996
|
-
}
|
|
2997
|
-
/**
|
|
2998
|
-
* 每个外部状态来源的那句话,按登记顺序。
|
|
3108
|
+
* 用于多层聚合:折叠的父文件夹要覆盖整棵子树,而子树里每一层都已各自折过一遍。
|
|
3109
|
+
* `FacetFold.data` 怎么合归该 facet 自己决定,本模块从不解释。
|
|
2999
3110
|
*
|
|
3000
|
-
*
|
|
3001
|
-
*
|
|
3111
|
+
* 加法单位元走**引用原样返回**:值不变时换一个新对象,会让"同一棵空闲子树"在两轮
|
|
3112
|
+
* 计算之间有不同的身份。`EMPTY_LIVE_STATUS` 留在这个模块里的理由就是身份稳定(见其
|
|
3113
|
+
* 注释),合并这一层不该每加一次零就把那个理由作废。返回的是入参本身(不改写、不
|
|
3114
|
+
* 拷贝),因此与逐字段相加的结果等值。
|
|
3002
3115
|
*/
|
|
3003
|
-
function
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
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
|
+
};
|
|
3128
|
+
}
|
|
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)
|
|
3010
3139
|
});
|
|
3011
|
-
lines.push(text === void 0 || text === "" ? `${entry.fold.count} ${t("status.needsAttention")}` : text);
|
|
3012
3140
|
}
|
|
3013
|
-
return
|
|
3141
|
+
return merged;
|
|
3014
3142
|
}
|
|
3015
3143
|
/**
|
|
3016
|
-
*
|
|
3017
|
-
*
|
|
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.
|
|
3018
3148
|
*
|
|
3019
|
-
*
|
|
3020
|
-
*
|
|
3021
|
-
*
|
|
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.
|
|
3022
3154
|
*/
|
|
3023
|
-
function
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
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;
|
|
3169
|
+
}
|
|
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;
|
|
3029
3190
|
}
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
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 {}
|
|
3033
3206
|
}
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
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
|
+
};
|
|
3037
3217
|
}
|
|
3038
3218
|
/**
|
|
3039
|
-
*
|
|
3219
|
+
* Measure each group's own members, as the input to {@link aggregateGroupStatuses}.
|
|
3040
3220
|
*
|
|
3041
|
-
*
|
|
3042
|
-
*
|
|
3043
|
-
*
|
|
3044
|
-
*
|
|
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.
|
|
3045
3227
|
*
|
|
3046
|
-
*
|
|
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.
|
|
3231
|
+
*/
|
|
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;
|
|
3236
|
+
}
|
|
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.
|
|
3047
3242
|
*
|
|
3048
|
-
* @
|
|
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.
|
|
3049
3246
|
*/
|
|
3050
|
-
function
|
|
3051
|
-
const
|
|
3052
|
-
const
|
|
3053
|
-
|
|
3054
|
-
const
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
return
|
|
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;
|
|
3066
3263
|
}
|
|
3067
3264
|
/**
|
|
3068
|
-
*
|
|
3265
|
+
* Attach each group's subtree-wide status, in derivation order. This is the producer
|
|
3266
|
+
* the sidebar renders from.
|
|
3069
3267
|
*
|
|
3070
|
-
*
|
|
3071
|
-
*
|
|
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.
|
|
3072
3271
|
*
|
|
3073
|
-
*
|
|
3074
|
-
*
|
|
3075
|
-
*
|
|
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.
|
|
3076
3275
|
*/
|
|
3077
|
-
function
|
|
3078
|
-
const
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
text: t(status.subagents === 1 ? "status.subagentsRunning.one" : "status.subagentsRunning.other", { n: status.subagents })
|
|
3102
|
-
});
|
|
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
|
+
}));
|
|
3282
|
+
}
|
|
3283
|
+
/**
|
|
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.
|
|
3288
|
+
*/
|
|
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);
|
|
3103
3300
|
}
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
});
|
|
3109
|
-
return lines;
|
|
3301
|
+
return {
|
|
3302
|
+
rows,
|
|
3303
|
+
hiddenCount: sessions.length - rows.length
|
|
3304
|
+
};
|
|
3110
3305
|
}
|
|
3111
3306
|
/**
|
|
3112
|
-
*
|
|
3113
|
-
*
|
|
3114
|
-
*
|
|
3115
|
-
*
|
|
3116
|
-
*
|
|
3117
|
-
*
|
|
3118
|
-
* (见下)。
|
|
3119
|
-
*
|
|
3120
|
-
* 保留这条历史的原因写在所有者那边:dsh-gitea-dispatch 把 `overdue` 画成**静止**(其
|
|
3121
|
-
* `lib/waiting-glyph.js`:「超时后停动画:静止即不再期待」),且它只代表 CI 窗口到期、
|
|
3122
|
-
* 不代表评审失败。同一条等待在行内静止、在父级闪动,就是同一事实的两种说法。
|
|
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.
|
|
3123
3313
|
*/
|
|
3124
|
-
function
|
|
3125
|
-
|
|
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;
|
|
3321
|
+
}
|
|
3322
|
+
return required;
|
|
3126
3323
|
}
|
|
3127
3324
|
/**
|
|
3128
|
-
*
|
|
3129
|
-
*
|
|
3130
|
-
*
|
|
3131
|
-
*
|
|
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.
|
|
3132
3329
|
*/
|
|
3133
|
-
function
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
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;
|
|
3138
3338
|
}
|
|
3139
3339
|
//#endregion
|
|
3140
|
-
//#region
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3340
|
+
//#region src/client/use-registered-facets.ts
|
|
3341
|
+
/**
|
|
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
|
|
3373
|
+
*/
|
|
3374
|
+
/**
|
|
3375
|
+
* 当前登记的来源,按登记面顺序(已按 `order` 排好)。
|
|
3376
|
+
*
|
|
3377
|
+
* 引用在版本号不变时保持稳定:登记面是低频事件,重算成本 = O(facet × 组内会话)。
|
|
3378
|
+
*
|
|
3379
|
+
* @param store - facet 消费侧存储(`facetStore()` 单例)。
|
|
3380
|
+
*/
|
|
3381
|
+
function useRegisteredFacets(store) {
|
|
3382
|
+
const version = (0, react.useSyncExternalStore)(store.subscribe, store.getVersion, store.getServerVersion);
|
|
3383
|
+
return (0, react.useMemo)(() => [...store.list()], [version]);
|
|
3384
|
+
}
|
|
3385
|
+
//#endregion
|
|
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);
|
|
3397
|
+
}
|
|
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;
|
|
3403
|
+
}
|
|
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;
|
|
3421
|
+
}
|
|
3422
|
+
/**
|
|
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
|
+
* 再判一次设置(本插件也没有替别人家存的开关)。
|
|
3429
|
+
*/
|
|
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")}`);
|
|
3436
|
+
}
|
|
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 }));
|
|
3440
|
+
}
|
|
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)
|
|
3470
|
+
});
|
|
3471
|
+
}
|
|
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
|
+
});
|
|
3500
|
+
}
|
|
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
|
+
});
|
|
3510
|
+
}
|
|
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
|
+
}
|
|
3546
|
+
//#endregion
|
|
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";
|
|
3550
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
|
|
3551
|
+
const tag = document.createElement("style");
|
|
3552
|
+
tag.dataset.plugin = "dsh-group-status";
|
|
3553
|
+
tag.dataset.pluginCss = tagId$1;
|
|
3554
|
+
tag.textContent = css$1;
|
|
3555
|
+
document.head.appendChild(tag);
|
|
3556
|
+
}
|
|
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",
|
|
3157
3564
|
"dropBefore": "_1Jb0BW_dropBefore",
|
|
3158
3565
|
"folder": "_1Jb0BW_folder",
|
|
3159
3566
|
"folderActive": "_1Jb0BW_folderActive",
|
|
@@ -3792,644 +4199,226 @@ window.__ModuleLoader__.load({
|
|
|
3792
4199
|
},
|
|
3793
4200
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
|
|
3794
4201
|
open: menuOpen,
|
|
3795
|
-
onClose: () => {
|
|
3796
|
-
setMenuOpen(false);
|
|
3797
|
-
},
|
|
3798
|
-
portal: true,
|
|
3799
|
-
closeOnPointerLeave: true,
|
|
3800
|
-
anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3801
|
-
type: "button",
|
|
3802
|
-
className: Rows_module_css_default.iconButton,
|
|
3803
|
-
"aria-label": t("actions.session.aria", { name: title }),
|
|
3804
|
-
onClick: () => {
|
|
3805
|
-
setMenuOpen((v) => !v);
|
|
3806
|
-
},
|
|
3807
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEllipsisOutlineRegular, {})
|
|
3808
|
-
}),
|
|
3809
|
-
children: renderSlot("sidebar.workspaces.session.menu.item", {
|
|
3810
|
-
sessionId: node.id,
|
|
3811
|
-
displayTitle: row.title
|
|
3812
|
-
}, { hookContext: menuOpenState })
|
|
3813
|
-
}), renderSlot("sidebar.workspaces.session.row.action", {
|
|
3814
|
-
sessionId: node.id,
|
|
3815
|
-
displayTitle: row.title
|
|
3816
|
-
})]
|
|
3817
|
-
})
|
|
3818
|
-
]
|
|
3819
|
-
});
|
|
3820
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.HoverCard, {
|
|
3821
|
-
anchor: ownRow,
|
|
3822
|
-
content: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionHoverContent, {
|
|
3823
|
-
node,
|
|
3824
|
-
now,
|
|
3825
|
-
renderSlot,
|
|
3826
|
-
t
|
|
3827
|
-
}),
|
|
3828
|
-
openDelayMs: 800,
|
|
3829
|
-
grouped: true,
|
|
3830
|
-
disabled: menuOpen || drag?.active === true,
|
|
3831
|
-
copyText: row.blank || row.title === "" ? void 0 : row.title,
|
|
3832
|
-
copyLabel: t("copy"),
|
|
3833
|
-
copiedLabel: t("hover.copied")
|
|
3834
|
-
});
|
|
3835
|
-
}
|
|
3836
|
-
//#endregion
|
|
3837
|
-
//#region \0dsh-css:src/client/rows/AnimatedRows.module.css.mjs
|
|
3838
|
-
const css$2 = "._8TnAsq_exits{contain:strict;pointer-events:none;position:absolute;inset:0;overflow:clip}";
|
|
3839
|
-
const tagId$2 = "dsh-group-status/AnimatedRows.module.css";
|
|
3840
|
-
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
|
|
3841
|
-
const tag = document.createElement("style");
|
|
3842
|
-
tag.dataset.plugin = "dsh-group-status";
|
|
3843
|
-
tag.dataset.pluginCss = tagId$2;
|
|
3844
|
-
tag.textContent = css$2;
|
|
3845
|
-
document.head.appendChild(tag);
|
|
3846
|
-
}
|
|
3847
|
-
var AnimatedRows_module_css_default = { "exits": "_8TnAsq_exits" };
|
|
3848
|
-
//#endregion
|
|
3849
|
-
//#region src/client/rows/AnimatedRows.tsx
|
|
3850
|
-
/** React-commit-driven movement and entry/exit fades for the sidebar's keyed rows. */
|
|
3851
|
-
const ROW_FADE_MS = 100;
|
|
3852
|
-
const ROW_GLIDE_MS = 200;
|
|
3853
|
-
function sameRows(previous, next) {
|
|
3854
|
-
return previous.rowKeys.length === next.rowKeys.length && previous.rowKeys.every((key, index) => key === next.rowKeys[index]);
|
|
3855
|
-
}
|
|
3856
|
-
function intersects(row, viewport) {
|
|
3857
|
-
return row.bottom > viewport.top && row.top < viewport.bottom && row.right > viewport.left && row.left < viewport.right;
|
|
3858
|
-
}
|
|
3859
|
-
/**
|
|
3860
|
-
* Animates keyed sidebar rows only when their rendered membership or order changes.
|
|
3861
|
-
* Motion starts after the first pointer or keyboard input inside the mounted list.
|
|
3862
|
-
* The parent supplies a positioned container for the inert exit overlay.
|
|
3863
|
-
*/
|
|
3864
|
-
var AnimatedRows = class extends react.Component {
|
|
3865
|
-
armed = false;
|
|
3866
|
-
list = (0, react.createRef)();
|
|
3867
|
-
overlay = (0, react.createRef)();
|
|
3868
|
-
movements = /* @__PURE__ */ new Map();
|
|
3869
|
-
exits = /* @__PURE__ */ new Map();
|
|
3870
|
-
getSnapshotBeforeUpdate(previous) {
|
|
3871
|
-
const list = this.list.current;
|
|
3872
|
-
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;
|
|
3873
|
-
const viewport = list.getBoundingClientRect();
|
|
3874
|
-
const positions = this.readPositions();
|
|
3875
|
-
const nextKeys = new Set(this.props.rowKeys);
|
|
3876
|
-
const removed = /* @__PURE__ */ new Map();
|
|
3877
|
-
for (const [key, row] of positions) {
|
|
3878
|
-
if (nextKeys.has(key) || !intersects(row.rect, viewport)) continue;
|
|
3879
|
-
const clone = row.element.cloneNode(true);
|
|
3880
|
-
clone.removeAttribute("data-row-key");
|
|
3881
|
-
clone.inert = true;
|
|
3882
|
-
clone.style.setProperty("--dsh-workspace-indent", getComputedStyle(row.element).getPropertyValue("--dsh-workspace-indent"));
|
|
3883
|
-
removed.set(key, {
|
|
3884
|
-
...row,
|
|
3885
|
-
element: clone
|
|
3886
|
-
});
|
|
3887
|
-
}
|
|
3888
|
-
return {
|
|
3889
|
-
positions,
|
|
3890
|
-
removed
|
|
3891
|
-
};
|
|
3892
|
-
}
|
|
3893
|
-
componentDidUpdate(previous, _state, snapshot) {
|
|
3894
|
-
if (snapshot === null) {
|
|
3895
|
-
if (!sameRows(previous, this.props) || previous.resetKey !== this.props.resetKey || previous.ready !== this.props.ready) this.clear();
|
|
3896
|
-
return;
|
|
3897
|
-
}
|
|
3898
|
-
this.cancelMovements();
|
|
3899
|
-
const list = this.list.current;
|
|
3900
|
-
const overlay = this.overlay.current;
|
|
3901
|
-
const viewport = list.getBoundingClientRect();
|
|
3902
|
-
const origin = overlay.getBoundingClientRect();
|
|
3903
|
-
const positions = this.readPositions();
|
|
3904
|
-
for (const [key, row] of positions) {
|
|
3905
|
-
this.removeExit(key);
|
|
3906
|
-
const previousRow = snapshot.positions.get(key);
|
|
3907
|
-
if (!intersects(row.rect, viewport) && (previousRow === void 0 || !intersects(previousRow.rect, viewport))) continue;
|
|
3908
|
-
if (previousRow === void 0) {
|
|
3909
|
-
this.move(row.element, [{ opacity: 0 }, { opacity: 1 }], ROW_FADE_MS);
|
|
3910
|
-
continue;
|
|
3911
|
-
}
|
|
3912
|
-
const dx = previousRow.rect.left - row.rect.left;
|
|
3913
|
-
const dy = previousRow.rect.top - row.rect.top;
|
|
3914
|
-
if (dx === 0 && dy === 0 && previousRow.opacity === 1) continue;
|
|
3915
|
-
this.move(row.element, [{
|
|
3916
|
-
transform: `translate(${String(dx)}px, ${String(dy)}px)`,
|
|
3917
|
-
opacity: previousRow.opacity
|
|
3918
|
-
}, {
|
|
3919
|
-
transform: "translate(0, 0)",
|
|
3920
|
-
opacity: 1
|
|
3921
|
-
}], ROW_GLIDE_MS);
|
|
3922
|
-
}
|
|
3923
|
-
for (const [key, row] of snapshot.removed) {
|
|
3924
|
-
const { element } = row;
|
|
3925
|
-
this.removeExit(key);
|
|
3926
|
-
Object.assign(element.style, {
|
|
3927
|
-
position: "absolute",
|
|
3928
|
-
margin: "0",
|
|
3929
|
-
transform: "none",
|
|
3930
|
-
boxSizing: "border-box",
|
|
3931
|
-
left: `${String(row.rect.left - origin.left)}px`,
|
|
3932
|
-
top: `${String(row.rect.top - origin.top)}px`,
|
|
3933
|
-
width: `${String(row.rect.width)}px`,
|
|
3934
|
-
height: `${String(row.rect.height)}px`
|
|
3935
|
-
});
|
|
3936
|
-
overlay.append(element);
|
|
3937
|
-
const animation = element.animate([{ opacity: row.opacity }, { opacity: 0 }], {
|
|
3938
|
-
duration: ROW_FADE_MS,
|
|
3939
|
-
easing: "ease-out",
|
|
3940
|
-
fill: "forwards"
|
|
3941
|
-
});
|
|
3942
|
-
this.exits.set(key, {
|
|
3943
|
-
element,
|
|
3944
|
-
animation
|
|
3945
|
-
});
|
|
3946
|
-
animation.onfinish = () => {
|
|
3947
|
-
this.removeExit(key);
|
|
3948
|
-
};
|
|
3949
|
-
}
|
|
3950
|
-
}
|
|
3951
|
-
componentWillUnmount() {
|
|
3952
|
-
this.clear();
|
|
3953
|
-
}
|
|
3954
|
-
readPositions() {
|
|
3955
|
-
const rows = this.list.current.querySelectorAll("[data-row-key]");
|
|
3956
|
-
return new Map(Array.from(rows, (element) => [element.dataset.rowKey, {
|
|
3957
|
-
element,
|
|
3958
|
-
rect: element.getBoundingClientRect(),
|
|
3959
|
-
opacity: this.movements.has(element) ? Number(getComputedStyle(element).opacity) : 1
|
|
3960
|
-
}]));
|
|
3961
|
-
}
|
|
3962
|
-
move(element, keyframes, duration) {
|
|
3963
|
-
const animation = element.animate(keyframes, {
|
|
3964
|
-
duration,
|
|
3965
|
-
easing: "ease-out"
|
|
3966
|
-
});
|
|
3967
|
-
this.movements.set(element, animation);
|
|
3968
|
-
animation.onfinish = () => {
|
|
3969
|
-
this.movements.delete(element);
|
|
3970
|
-
animation.cancel();
|
|
3971
|
-
};
|
|
3972
|
-
}
|
|
3973
|
-
cancelMovements() {
|
|
3974
|
-
for (const animation of this.movements.values()) {
|
|
3975
|
-
animation.onfinish = null;
|
|
3976
|
-
animation.cancel();
|
|
3977
|
-
}
|
|
3978
|
-
this.movements.clear();
|
|
3979
|
-
}
|
|
3980
|
-
removeExit(key) {
|
|
3981
|
-
const exit = this.exits.get(key);
|
|
3982
|
-
if (exit === void 0) return;
|
|
3983
|
-
exit.animation.onfinish = null;
|
|
3984
|
-
exit.animation.cancel();
|
|
3985
|
-
exit.element.remove();
|
|
3986
|
-
this.exits.delete(key);
|
|
3987
|
-
}
|
|
3988
|
-
clear() {
|
|
3989
|
-
this.cancelMovements();
|
|
3990
|
-
for (const key of this.exits.keys()) this.removeExit(key);
|
|
3991
|
-
}
|
|
3992
|
-
render() {
|
|
3993
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3994
|
-
ref: this.list,
|
|
3995
|
-
className: this.props.className,
|
|
3996
|
-
role: "tree",
|
|
3997
|
-
"aria-label": this.props.label,
|
|
3998
|
-
onPointerDownCapture: () => {
|
|
3999
|
-
this.armed = true;
|
|
4000
|
-
},
|
|
4001
|
-
onKeyDownCapture: () => {
|
|
4002
|
-
this.armed = true;
|
|
4003
|
-
},
|
|
4004
|
-
children: this.props.children
|
|
4005
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4006
|
-
ref: this.overlay,
|
|
4007
|
-
className: AnimatedRows_module_css_default.exits,
|
|
4008
|
-
"aria-hidden": "true"
|
|
4009
|
-
})] });
|
|
4010
|
-
}
|
|
4011
|
-
};
|
|
4012
|
-
//#endregion
|
|
4013
|
-
//#region \0dsh-css:src/client/WorkspacePicker.module.css.mjs
|
|
4014
|
-
const css$1 = ".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)}";
|
|
4015
|
-
const tagId$1 = "dsh-group-status/WorkspacePicker.module.css";
|
|
4016
|
-
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
|
|
4017
|
-
const tag = document.createElement("style");
|
|
4018
|
-
tag.dataset.plugin = "dsh-group-status";
|
|
4019
|
-
tag.dataset.pluginCss = tagId$1;
|
|
4020
|
-
tag.textContent = css$1;
|
|
4021
|
-
document.head.appendChild(tag);
|
|
4022
|
-
}
|
|
4023
|
-
var WorkspacePicker_module_css_default = {
|
|
4024
|
-
"menuStatus": "S_JYgq_menuStatus",
|
|
4025
|
-
"modalAction": "S_JYgq_modalAction",
|
|
4026
|
-
"modalError": "S_JYgq_modalError"
|
|
4027
|
-
};
|
|
4028
|
-
//#endregion
|
|
4029
|
-
//#region src/client/WorkspacePicker.tsx
|
|
4030
|
-
const ADD_WORKSPACE = "::add-workspace";
|
|
4031
|
-
/**
|
|
4032
|
-
* Render the pick menu plus the adoption error dialog.
|
|
4033
|
-
* @param props - owner-controlled flow props.
|
|
4034
|
-
* @returns menu + dialog elements.
|
|
4035
|
-
*/
|
|
4036
|
-
function WorkspacePickFlow({ t, open, anchorRef, useWorkspaces, createWorkspace, useDirectoryFlow, renderDirectoryFlow, onPick, onClose, addOnly = false, onBusyChange, side = "bottom", selectedId }) {
|
|
4037
|
-
const workspaceSnapshot = useWorkspaces((state) => state);
|
|
4038
|
-
const workspaces = workspaceSnapshot.items;
|
|
4039
|
-
const getAnchorRect = (0, react.useCallback)(() => anchorRef?.current?.getBoundingClientRect() ?? null, [anchorRef]);
|
|
4040
|
-
const [errorOpen, setErrorOpen] = (0, react.useState)(false);
|
|
4041
|
-
const [modalError, setModalError] = (0, react.useState)(null);
|
|
4042
|
-
const [flowOpen, setFlowOpen] = (0, react.useState)(false);
|
|
4043
|
-
const [pickingFolder, setPickingFolder] = (0, react.useState)(false);
|
|
4044
|
-
const flowBusy = flowOpen || pickingFolder;
|
|
4045
|
-
(0, react.useEffect)(() => {
|
|
4046
|
-
onBusyChange?.(flowBusy);
|
|
4047
|
-
}, [flowBusy, onBusyChange]);
|
|
4048
|
-
const flowAvailable = useDirectoryFlow((occupied) => occupied);
|
|
4049
|
-
(0, react.useEffect)(() => {
|
|
4050
|
-
if (flowOpen && !flowAvailable) setFlowOpen(false);
|
|
4051
|
-
}, [flowOpen, flowAvailable]);
|
|
4052
|
-
const addEntries = flowAvailable ? [{
|
|
4053
|
-
id: ADD_WORKSPACE,
|
|
4054
|
-
label: t("menu.addWorkspace"),
|
|
4055
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutlineRegular, { size: 16 }),
|
|
4056
|
-
disabled: flowBusy
|
|
4057
|
-
}] : [];
|
|
4058
|
-
const pinAdd = !addOnly && workspaces.length > 0;
|
|
4059
|
-
const items = pinAdd ? workspaces.map((workspace) => ({
|
|
4060
|
-
id: workspace.workspaceId,
|
|
4061
|
-
label: workspaceDisplayTitle(workspace.title, t("workspace.defaultName")),
|
|
4062
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderCloseRegular, { size: 16 }),
|
|
4063
|
-
disabled: flowBusy
|
|
4064
|
-
})) : addEntries;
|
|
4065
|
-
const menuIsEmpty = items.length === 0;
|
|
4066
|
-
const closeModal = () => {
|
|
4067
|
-
setErrorOpen(false);
|
|
4068
|
-
setModalError(null);
|
|
4069
|
-
};
|
|
4070
|
-
/** Adopt a picked directory; failures land in the folder-error dialog (Choose again reopens the flow). */
|
|
4071
|
-
const adoptDirectory = (path) => createWorkspace({ path }).then((workspace) => {
|
|
4072
|
-
setFlowOpen(false);
|
|
4073
|
-
onPick(workspace.workspaceId);
|
|
4074
|
-
}).catch((reason) => {
|
|
4075
|
-
setModalError(reason instanceof Error ? reason.message : String(reason));
|
|
4076
|
-
setFlowOpen(false);
|
|
4077
|
-
setErrorOpen(true);
|
|
4078
|
-
});
|
|
4079
|
-
const openDirectoryFlow = (0, react.useCallback)(() => {
|
|
4080
|
-
onClose();
|
|
4081
|
-
setErrorOpen(false);
|
|
4082
|
-
setModalError(null);
|
|
4083
|
-
setFlowOpen(true);
|
|
4084
|
-
}, [onClose]);
|
|
4085
|
-
const listSettled = addOnly || workspaceSnapshot.phase === "ready";
|
|
4086
|
-
const addIsTheOnlyEntry = !pinAdd && listSettled && addEntries.length === 1;
|
|
4087
|
-
(0, react.useEffect)(() => {
|
|
4088
|
-
if (open && addIsTheOnlyEntry && !flowBusy) openDirectoryFlow();
|
|
4089
|
-
}, [
|
|
4090
|
-
open,
|
|
4091
|
-
addIsTheOnlyEntry,
|
|
4092
|
-
flowBusy,
|
|
4093
|
-
openDirectoryFlow
|
|
4094
|
-
]);
|
|
4095
|
-
/** Owner side of the flow conversation: adopt keeps the flow open (busy) until the Host answers. */
|
|
4096
|
-
const flowOwner = {
|
|
4097
|
-
open: flowOpen,
|
|
4098
|
-
busy: pickingFolder,
|
|
4099
|
-
onPicked: (path) => {
|
|
4100
|
-
setPickingFolder(true);
|
|
4101
|
-
adoptDirectory(path).finally(() => {
|
|
4102
|
-
setPickingFolder(false);
|
|
4103
|
-
});
|
|
4104
|
-
},
|
|
4105
|
-
onCancel: () => {
|
|
4106
|
-
setFlowOpen(false);
|
|
4107
|
-
},
|
|
4108
|
-
onError: (message) => {
|
|
4109
|
-
setFlowOpen(false);
|
|
4110
|
-
setModalError(message);
|
|
4111
|
-
setErrorOpen(true);
|
|
4112
|
-
}
|
|
4113
|
-
};
|
|
4114
|
-
const handleSelect = (id) => {
|
|
4115
|
-
if (id === ADD_WORKSPACE) {
|
|
4116
|
-
openDirectoryFlow();
|
|
4117
|
-
return;
|
|
4118
|
-
}
|
|
4119
|
-
onPick(id);
|
|
4120
|
-
};
|
|
4121
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
4122
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
|
|
4123
|
-
open: open && !addIsTheOnlyEntry && !menuIsEmpty,
|
|
4124
|
-
anchor: null,
|
|
4125
|
-
items,
|
|
4126
|
-
...pinAdd ? { footer: addEntries } : {},
|
|
4127
|
-
selectedId,
|
|
4128
|
-
onSelect: handleSelect,
|
|
4129
|
-
onClose,
|
|
4130
|
-
side,
|
|
4131
|
-
portal: true,
|
|
4132
|
-
getAnchorRect
|
|
4133
|
-
}),
|
|
4134
|
-
open && !addIsTheOnlyEntry && !menuIsEmpty && workspaceSnapshot.phase === "pending" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4135
|
-
className: WorkspacePicker_module_css_default.menuStatus,
|
|
4136
|
-
role: "status",
|
|
4137
|
-
children: t("picker.loading")
|
|
4138
|
-
}),
|
|
4139
|
-
renderDirectoryFlow(flowOwner),
|
|
4140
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
4141
|
-
open: errorOpen,
|
|
4142
|
-
onClose: closeModal,
|
|
4143
|
-
closeLabel: t("close"),
|
|
4144
|
-
title: t("folderError.title"),
|
|
4145
|
-
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, {
|
|
4146
|
-
variant: "outline",
|
|
4147
|
-
className: WorkspacePicker_module_css_default.modalAction,
|
|
4148
|
-
onClick: closeModal,
|
|
4149
|
-
children: t("cancel")
|
|
4150
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
4151
|
-
variant: "primary",
|
|
4152
|
-
className: WorkspacePicker_module_css_default.modalAction,
|
|
4153
|
-
disabled: !flowAvailable,
|
|
4154
|
-
onClick: openDirectoryFlow,
|
|
4155
|
-
children: t("folderError.retry")
|
|
4156
|
-
})] }),
|
|
4157
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4158
|
-
className: WorkspacePicker_module_css_default.modalError,
|
|
4159
|
-
role: "alert",
|
|
4160
|
-
children: modalError
|
|
4161
|
-
})
|
|
4162
|
-
})
|
|
4163
|
-
] });
|
|
4164
|
-
}
|
|
4165
|
-
/**
|
|
4166
|
-
* The conversation empty-state registration: adapts the owner share to the
|
|
4167
|
-
* core flow (all state and semantics live in the flow / the owner).
|
|
4168
|
-
* @param props - empty-state slot props (owner share + injected creation callback).
|
|
4169
|
-
* @returns the flow element.
|
|
4170
|
-
*/
|
|
4171
|
-
function WorkspacePicker({ open, anchorRef, useWorkspaces, selectedId, onPick, onClose, createWorkspace, useDirectoryFlow, renderSlot, t }) {
|
|
4172
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WorkspacePickFlow, {
|
|
4173
|
-
t,
|
|
4174
|
-
open,
|
|
4175
|
-
anchorRef,
|
|
4176
|
-
useWorkspaces,
|
|
4177
|
-
createWorkspace,
|
|
4178
|
-
useDirectoryFlow,
|
|
4179
|
-
renderDirectoryFlow: (owner) => renderSlot("conversation.hero.workspace.directoryFlow", owner),
|
|
4180
|
-
selectedId,
|
|
4181
|
-
onPick,
|
|
4182
|
-
onClose
|
|
4183
|
-
});
|
|
4184
|
-
}
|
|
4185
|
-
//#endregion
|
|
4186
|
-
//#region \0dsh-css:src/client/rows/WorkspaceBrowser.module.css.mjs
|
|
4187
|
-
const css = ".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}}";
|
|
4188
|
-
const tagId = "dsh-group-status/WorkspaceBrowser.module.css";
|
|
4189
|
-
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
4190
|
-
const tag = document.createElement("style");
|
|
4191
|
-
tag.dataset.plugin = "dsh-group-status";
|
|
4192
|
-
tag.dataset.pluginCss = tagId;
|
|
4193
|
-
tag.textContent = css;
|
|
4194
|
-
document.head.appendChild(tag);
|
|
4195
|
-
}
|
|
4196
|
-
var WorkspaceBrowser_module_css_default = {
|
|
4197
|
-
"archiveActivity": "QAYkWG_archiveActivity",
|
|
4198
|
-
"clearButton": "QAYkWG_clearButton",
|
|
4199
|
-
"deleteAction": "QAYkWG_deleteAction",
|
|
4200
|
-
"deleteStatus": "QAYkWG_deleteStatus",
|
|
4201
|
-
"empty": "QAYkWG_empty",
|
|
4202
|
-
"emptyAction": "QAYkWG_emptyAction",
|
|
4203
|
-
"emptyState": "QAYkWG_emptyState",
|
|
4204
|
-
"fade": "QAYkWG_fade",
|
|
4205
|
-
"flatList": "QAYkWG_flatList",
|
|
4206
|
-
"groupSection": "QAYkWG_groupSection",
|
|
4207
|
-
"headerActions": "QAYkWG_headerActions",
|
|
4208
|
-
"headerActionsHidden": "QAYkWG_headerActionsHidden",
|
|
4209
|
-
"iconButton": "QAYkWG_iconButton",
|
|
4210
|
-
"list": "QAYkWG_list",
|
|
4211
|
-
"listArea": "QAYkWG_listArea",
|
|
4212
|
-
"listTopDropActive": "QAYkWG_listTopDropActive",
|
|
4213
|
-
"listTopDropIndicator": "QAYkWG_listTopDropIndicator",
|
|
4214
|
-
"rail": "QAYkWG_rail",
|
|
4215
|
-
"renameError": "QAYkWG_renameError",
|
|
4216
|
-
"renameInput": "QAYkWG_renameInput",
|
|
4217
|
-
"root": "QAYkWG_root",
|
|
4218
|
-
"search": "QAYkWG_search",
|
|
4219
|
-
"search-skeleton": "QAYkWG_search-skeleton",
|
|
4220
|
-
"searchButton": "QAYkWG_searchButton",
|
|
4221
|
-
"searchExpanded": "QAYkWG_searchExpanded",
|
|
4222
|
-
"searchInput": "QAYkWG_searchInput",
|
|
4223
|
-
"searchSlot": "QAYkWG_searchSlot",
|
|
4224
|
-
"searchSlotExpanded": "QAYkWG_searchSlotExpanded",
|
|
4225
|
-
"searchStatus": "QAYkWG_searchStatus",
|
|
4226
|
-
"searchTree": "QAYkWG_searchTree",
|
|
4227
|
-
"sectionHeader": "QAYkWG_sectionHeader",
|
|
4228
|
-
"sectionLabel": "QAYkWG_sectionLabel",
|
|
4229
|
-
"sectionLabelHidden": "QAYkWG_sectionLabelHidden",
|
|
4230
|
-
"sessionOverflowButton": "QAYkWG_sessionOverflowButton",
|
|
4231
|
-
"skeletonBar": "QAYkWG_skeletonBar",
|
|
4232
|
-
"skeletonBarWide": "QAYkWG_skeletonBarWide",
|
|
4233
|
-
"skeletonBars": "QAYkWG_skeletonBars",
|
|
4234
|
-
"skeletonDot": "QAYkWG_skeletonDot",
|
|
4235
|
-
"skeletonRow": "QAYkWG_skeletonRow",
|
|
4236
|
-
"treeBody": "QAYkWG_treeBody",
|
|
4237
|
-
"viewOptionsMenu": "QAYkWG_viewOptionsMenu",
|
|
4238
|
-
"wide": "QAYkWG_wide",
|
|
4239
|
-
"wide-in": "QAYkWG_wide-in",
|
|
4240
|
-
"workspaceDropAfter": "QAYkWG_workspaceDropAfter",
|
|
4241
|
-
"workspaceDropBefore": "QAYkWG_workspaceDropBefore"
|
|
4242
|
-
};
|
|
4243
|
-
//#endregion
|
|
4244
|
-
//#region src/client/rows/WorkspaceBrowser.tsx
|
|
4245
|
-
/**
|
|
4246
|
-
* The workspace/session browsing region filling the sidebar shell's
|
|
4247
|
-
* `sidebar.workspaces` hole: section header (title + view options + add
|
|
4248
|
-
* workspace), search, the grouped tree or flat list, and the workspace
|
|
4249
|
-
* dialogs. Wide state renders the full browser; rail state renders the two
|
|
4250
|
-
* region icons (search / add workspace) as 36px controls on the shell's shared
|
|
4251
|
-
* rail entry path, each requesting expansion through the owner share. Adding
|
|
4252
|
-
* is the header button's one action, so it raises the directory flow with no
|
|
4253
|
-
* menu in between; the flow and its error dialog live in WorkspacePicker
|
|
4254
|
-
* (same package — direct composition, no slot between them). A Session row's
|
|
4255
|
-
* "..." menu and hover buttons are the `sidebar.workspaces.session.menu.item`
|
|
4256
|
-
* and `sidebar.workspaces.session.row.action` lists rendered through this
|
|
4257
|
-
* entry's `renderSlot`; the actions in them, this package's own included,
|
|
4258
|
-
* are slot entries with their own behavior, so this component threads no
|
|
4259
|
-
* action callbacks and hosts no action surface.
|
|
4260
|
-
*/
|
|
4261
|
-
const facetsStore = facetStore();
|
|
4262
|
-
/**
|
|
4263
|
-
* Column slide length (--ds-transition-duration-slow): rail-search focus waits it out —
|
|
4264
|
-
* focus() forces a synchronous layout and would jank the slide.
|
|
4265
|
-
*/
|
|
4266
|
-
const EXPAND_SLIDE_MS = 300;
|
|
4267
|
-
/** Pause between the latest keystroke and a Host content-search request. */
|
|
4268
|
-
const SEARCH_DEBOUNCE_MS = 250;
|
|
4269
|
-
/** `session.search` wire bound, measured in JavaScript UTF-16 code units. */
|
|
4270
|
-
const SEARCH_QUERY_MAX_CODE_UNITS = 500;
|
|
4271
|
-
/** Keep controlled input and RPC payload inside the session.search wire contract. */
|
|
4272
|
-
function sanitizeSearchQuery(value) {
|
|
4273
|
-
const withoutNul = value.replaceAll("\0", "");
|
|
4274
|
-
if (withoutNul.length <= SEARCH_QUERY_MAX_CODE_UNITS) return withoutNul;
|
|
4275
|
-
let end = SEARCH_QUERY_MAX_CODE_UNITS;
|
|
4276
|
-
const last = withoutNul.charCodeAt(end - 1);
|
|
4277
|
-
const next = withoutNul.charCodeAt(end);
|
|
4278
|
-
if (last >= 55296 && last <= 56319 && next >= 56320 && next <= 57343) end--;
|
|
4279
|
-
return withoutNul.slice(0, end);
|
|
4280
|
-
}
|
|
4281
|
-
/**
|
|
4282
|
-
* Accept the native drag at document level while a row drag is active: row
|
|
4283
|
-
* hover still owns the insertion marker, and releasing outside the list must
|
|
4284
|
-
* not be rendered as a rejected drop before dragend commits that last marker.
|
|
4285
|
-
*/
|
|
4286
|
-
function useNativeDragAcceptance(active) {
|
|
4287
|
-
(0, react.useEffect)(() => {
|
|
4288
|
-
if (!active) return;
|
|
4289
|
-
const acceptDrag = (event) => {
|
|
4290
|
-
event.preventDefault();
|
|
4291
|
-
if (event.dataTransfer !== null) event.dataTransfer.dropEffect = "move";
|
|
4292
|
-
};
|
|
4293
|
-
const acceptDrop = (event) => {
|
|
4294
|
-
event.preventDefault();
|
|
4295
|
-
};
|
|
4296
|
-
document.addEventListener("dragover", acceptDrag);
|
|
4297
|
-
document.addEventListener("drop", acceptDrop);
|
|
4298
|
-
return () => {
|
|
4299
|
-
document.removeEventListener("dragover", acceptDrag);
|
|
4300
|
-
document.removeEventListener("drop", acceptDrop);
|
|
4301
|
-
};
|
|
4302
|
-
}, [active]);
|
|
4303
|
-
}
|
|
4304
|
-
/** Grouping, ordering, and archived-filter menu; own open state so it resets with the wide chrome. */
|
|
4305
|
-
function ViewOptionsMenu({ groupBy, orderBy, archivedFilter, onGroupPick, onOrderPick, onArchivedFilterPick, t }) {
|
|
4306
|
-
const [open, setOpen] = (0, react.useState)(false);
|
|
4307
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
|
|
4308
|
-
open,
|
|
4309
|
-
onClose: () => {
|
|
4310
|
-
setOpen(false);
|
|
4311
|
-
},
|
|
4312
|
-
items: [
|
|
4313
|
-
{
|
|
4314
|
-
type: "label",
|
|
4315
|
-
id: "group-by",
|
|
4316
|
-
text: t("groupBy.label")
|
|
4317
|
-
},
|
|
4318
|
-
{
|
|
4319
|
-
id: "workspace",
|
|
4320
|
-
label: t("groupBy.workspace"),
|
|
4321
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderCloseRegular, {})
|
|
4322
|
-
},
|
|
4323
|
-
{
|
|
4324
|
-
id: "workspace-tree",
|
|
4325
|
-
label: t("groupBy.workspaceTree"),
|
|
4326
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWorkspaceTreeOutlineRegular, {})
|
|
4327
|
-
},
|
|
4328
|
-
{
|
|
4329
|
-
id: "flat",
|
|
4330
|
-
label: t("groupBy.flat"),
|
|
4331
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFlatListOutlineRegular, {})
|
|
4332
|
-
},
|
|
4333
|
-
{
|
|
4334
|
-
type: "separator",
|
|
4335
|
-
id: "order-by-separator"
|
|
4336
|
-
},
|
|
4337
|
-
{
|
|
4338
|
-
type: "label",
|
|
4339
|
-
id: "order-by",
|
|
4340
|
-
text: t("orderBy.label")
|
|
4341
|
-
},
|
|
4342
|
-
{
|
|
4343
|
-
id: "manual",
|
|
4344
|
-
label: t("orderBy.manual"),
|
|
4345
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronsUpDownOutlineRegular, {})
|
|
4346
|
-
},
|
|
4347
|
-
{
|
|
4348
|
-
id: "updated",
|
|
4349
|
-
label: t("orderBy.updated"),
|
|
4350
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconClockOutlineRegular, {})
|
|
4351
|
-
},
|
|
4352
|
-
{
|
|
4353
|
-
type: "separator",
|
|
4354
|
-
id: "archived-filter-separator"
|
|
4355
|
-
},
|
|
4356
|
-
{
|
|
4357
|
-
type: "label",
|
|
4358
|
-
id: "filter-by",
|
|
4359
|
-
text: t("filterBy.label")
|
|
4360
|
-
},
|
|
4361
|
-
{
|
|
4362
|
-
id: "hide-archived",
|
|
4363
|
-
label: t("viewOptions.hideArchived"),
|
|
4364
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconArchiveOffOutlineRegular, {})
|
|
4365
|
-
},
|
|
4366
|
-
{
|
|
4367
|
-
id: "show-archived",
|
|
4368
|
-
label: t("viewOptions.showArchived"),
|
|
4369
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconQueueOutlineRegular, {})
|
|
4370
|
-
},
|
|
4371
|
-
{
|
|
4372
|
-
id: "only-archived",
|
|
4373
|
-
label: t("viewOptions.onlyArchived"),
|
|
4374
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconArchiveCheckOutlineRegular, {})
|
|
4375
|
-
}
|
|
4376
|
-
],
|
|
4377
|
-
selectedIds: [
|
|
4378
|
-
groupBy,
|
|
4379
|
-
orderBy,
|
|
4380
|
-
{
|
|
4381
|
-
default: "hide-archived",
|
|
4382
|
-
show: "show-archived",
|
|
4383
|
-
only: "only-archived"
|
|
4384
|
-
}[archivedFilter]
|
|
4385
|
-
],
|
|
4386
|
-
onSelect: (id) => {
|
|
4387
|
-
if (id === "workspace" || id === "workspace-tree" || id === "flat") onGroupPick(id);
|
|
4388
|
-
else if (id === "manual" || id === "updated") onOrderPick(id);
|
|
4389
|
-
else if (id === "hide-archived") onArchivedFilterPick("default");
|
|
4390
|
-
else if (id === "show-archived") onArchivedFilterPick("show");
|
|
4391
|
-
else if (id === "only-archived") onArchivedFilterPick("only");
|
|
4392
|
-
setOpen(false);
|
|
4393
|
-
},
|
|
4394
|
-
align: "end",
|
|
4395
|
-
dense: true,
|
|
4396
|
-
listClassName: WorkspaceBrowser_module_css_default.viewOptionsMenu,
|
|
4397
|
-
portal: true,
|
|
4398
|
-
anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
|
|
4399
|
-
label: t("viewOptions.label"),
|
|
4400
|
-
side: "bottom",
|
|
4401
|
-
delayMs: 500,
|
|
4402
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4403
|
-
type: "button",
|
|
4404
|
-
className: clsx(WorkspaceBrowser_module_css_default.iconButton, WorkspaceBrowser_module_css_default.wide),
|
|
4405
|
-
"aria-label": t("viewOptions.label"),
|
|
4406
|
-
onClick: () => {
|
|
4407
|
-
setOpen((v) => !v);
|
|
4408
|
-
},
|
|
4409
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSlidersTwoOutlineRegular, {})
|
|
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
|
+
})]
|
|
4410
4224
|
})
|
|
4411
|
-
|
|
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")
|
|
4412
4241
|
});
|
|
4413
4242
|
}
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
const
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
if (targetIndex === -1) return;
|
|
4425
|
-
next.splice(targetIndex + (over.half === "after" ? 1 : 0), 0, source.id);
|
|
4426
|
-
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);
|
|
4427
4253
|
}
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
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]);
|
|
4432
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. */
|
|
4433
4422
|
/** The list-empty placeholder — a glyph over the text; the archived-only view names its filter and offers the way back. */
|
|
4434
4423
|
function EmptySessions({ rowState, onLeaveArchivedOnly, t }) {
|
|
4435
4424
|
const archivedOnly = rowState.archivedFilter === "only";
|
|
@@ -4448,13 +4437,25 @@ window.__ModuleLoader__.load({
|
|
|
4448
4437
|
]
|
|
4449
4438
|
});
|
|
4450
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();
|
|
4451
4451
|
/** The scrolling session tree; unmounting drops the sessions subscription and local row limits. */
|
|
4452
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 }) {
|
|
4453
4453
|
const panelActive = usePanelInfo((info) => info.activePanelId !== null);
|
|
4454
4454
|
const badgeSettings = useBadgeSettings((settings) => settings);
|
|
4455
4455
|
const statuses = useSessionStatus((s) => s);
|
|
4456
4456
|
const facets = useRegisteredFacets(facetsStore);
|
|
4457
|
-
const
|
|
4457
|
+
const mainViewId = (0, react.useMemo)(() => mainViewSessionId(list), [list]);
|
|
4458
|
+
const current = panelActive ? void 0 : mainViewId;
|
|
4458
4459
|
const revealGroup = revealSessionId === void 0 || !workspaceReady ? void 0 : owningGroupKey(workspaces, revealSessionId);
|
|
4459
4460
|
const [storedWatermarks, setStoredWatermarks] = (0, react.useState)({});
|
|
4460
4461
|
const [watermarkMotionKey, setWatermarkMotionKey] = (0, react.useState)(0);
|
|
@@ -4801,6 +4802,9 @@ window.__ModuleLoader__.load({
|
|
|
4801
4802
|
]
|
|
4802
4803
|
});
|
|
4803
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. */
|
|
4804
4808
|
/** The flat "In one list" body: every session is one draggable top-level row. */
|
|
4805
4809
|
function FlatList({ list, sessionIds, rowState, onLeaveArchivedOnly, useSessionStatus, open, onSessionRenameRequest, usePanelInfo, setSessionOrder, workspaceReady, animationResetKey, revealSessionId, onSessionRevealed, renderSlot, t }) {
|
|
4806
4810
|
const panelActive = usePanelInfo((info) => info.activePanelId !== null);
|
|
@@ -4814,7 +4818,8 @@ window.__ModuleLoader__.load({
|
|
|
4814
4818
|
const [drag, setDrag] = (0, react.useState)(null);
|
|
4815
4819
|
const dropCommitted = (0, react.useRef)(false);
|
|
4816
4820
|
useNativeDragAcceptance(drag !== null);
|
|
4817
|
-
const
|
|
4821
|
+
const mainViewId = (0, react.useMemo)(() => mainViewSessionId(list), [list]);
|
|
4822
|
+
const currentId = panelActive ? void 0 : mainViewId;
|
|
4818
4823
|
const commitDrag = (activeDrag, over) => {
|
|
4819
4824
|
if (dropCommitted.current) return;
|
|
4820
4825
|
dropCommitted.current = true;
|
|
@@ -4909,7 +4914,8 @@ window.__ModuleLoader__.load({
|
|
|
4909
4914
|
resultLimit
|
|
4910
4915
|
]);
|
|
4911
4916
|
const pending = currentRemote.status === "loading";
|
|
4912
|
-
const
|
|
4917
|
+
const mainViewId = (0, react.useMemo)(() => mainViewSessionId(list), [list]);
|
|
4918
|
+
const currentId = panelActive ? void 0 : mainViewId;
|
|
4913
4919
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4914
4920
|
className: clsx(WorkspaceBrowser_module_css_default.treeBody, WorkspaceBrowser_module_css_default.wide),
|
|
4915
4921
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -4919,6 +4925,7 @@ window.__ModuleLoader__.load({
|
|
|
4919
4925
|
className: WorkspaceBrowser_module_css_default.searchTree,
|
|
4920
4926
|
role: "tree",
|
|
4921
4927
|
"aria-label": t("search.results.aria"),
|
|
4928
|
+
"aria-busy": pending,
|
|
4922
4929
|
children: results.items.map((result) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SearchResultItem, {
|
|
4923
4930
|
result,
|
|
4924
4931
|
currentId,
|
|
@@ -4941,20 +4948,35 @@ window.__ModuleLoader__.load({
|
|
|
4941
4948
|
}),
|
|
4942
4949
|
!pending && results.items.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4943
4950
|
className: WorkspaceBrowser_module_css_default.empty,
|
|
4951
|
+
role: "status",
|
|
4944
4952
|
children: t("search.noMatches")
|
|
4945
4953
|
}),
|
|
4946
4954
|
results.hasMore && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4947
4955
|
className: WorkspaceBrowser_module_css_default.searchStatus,
|
|
4956
|
+
role: "status",
|
|
4948
4957
|
children: t("search.hasMore", { n: resultLimit })
|
|
4949
4958
|
})
|
|
4950
4959
|
]
|
|
4951
4960
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: WorkspaceBrowser_module_css_default.fade })]
|
|
4952
4961
|
});
|
|
4953
4962
|
}
|
|
4963
|
+
//#endregion
|
|
4964
|
+
//#region src/client/rows/WorkspaceBrowser.tsx
|
|
4954
4965
|
/**
|
|
4955
|
-
*
|
|
4956
|
-
*
|
|
4957
|
-
*
|
|
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.
|
|
4958
4980
|
*/
|
|
4959
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 }) {
|
|
4960
4982
|
const home = useHostInfo((info) => info.home);
|
|
@@ -4990,7 +5012,7 @@ window.__ModuleLoader__.load({
|
|
|
4990
5012
|
actions.setArchivedFilter("default");
|
|
4991
5013
|
};
|
|
4992
5014
|
const workspaceReady = workspacePhase === "ready" && workspaceStreamState !== "loading";
|
|
4993
|
-
const mainSessionId =
|
|
5015
|
+
const mainSessionId = (0, react.useMemo)(() => mainViewSessionId(list), [list]);
|
|
4994
5016
|
const currentBlank = mainSessionId !== void 0 && list.byId[mainSessionId]?.blank === true ? mainSessionId : void 0;
|
|
4995
5017
|
const ungroupedMemberIds = (0, react.useMemo)(() => {
|
|
4996
5018
|
const accounted = new Set(workspaces.flatMap((workspace) => workspace.sessionIds));
|
|
@@ -5133,7 +5155,7 @@ window.__ModuleLoader__.load({
|
|
|
5133
5155
|
const timer = window.setTimeout(() => {
|
|
5134
5156
|
searchInput.current?.focus({ preventScroll: true });
|
|
5135
5157
|
setSearchOnExpand(false);
|
|
5136
|
-
},
|
|
5158
|
+
}, 300);
|
|
5137
5159
|
return () => {
|
|
5138
5160
|
window.clearTimeout(timer);
|
|
5139
5161
|
};
|
|
@@ -5209,7 +5231,7 @@ window.__ModuleLoader__.load({
|
|
|
5209
5231
|
hasMore: false
|
|
5210
5232
|
});
|
|
5211
5233
|
});
|
|
5212
|
-
},
|
|
5234
|
+
}, 250);
|
|
5213
5235
|
return () => {
|
|
5214
5236
|
window.clearTimeout(timer);
|
|
5215
5237
|
controller.abort();
|
|
@@ -5316,7 +5338,7 @@ window.__ModuleLoader__.load({
|
|
|
5316
5338
|
className: WorkspaceBrowser_module_css_default.searchInput,
|
|
5317
5339
|
type: "text",
|
|
5318
5340
|
placeholder: t("search.placeholder"),
|
|
5319
|
-
maxLength:
|
|
5341
|
+
maxLength: 500,
|
|
5320
5342
|
value: query,
|
|
5321
5343
|
tabIndex: searchExpanded ? 0 : -1,
|
|
5322
5344
|
onChange: (e) => {
|
|
@@ -5729,6 +5751,15 @@ window.__ModuleLoader__.load({
|
|
|
5729
5751
|
//#endregion
|
|
5730
5752
|
//#region src/client/session-actions/derived.ts
|
|
5731
5753
|
/**
|
|
5754
|
+
* "还没投影过"的标记。
|
|
5755
|
+
*
|
|
5756
|
+
* 不能用 `undefined` 兼任:投影**合法地**可能返回 `undefined`("当前没有待处理的
|
|
5757
|
+
* 请求"就是一次正常的空结果),把它当成"未计算"会让每次读都重跑一次 `project`。
|
|
5758
|
+
* 投影若新建 `Set` / 数组,消费者每次读都拿到新引用,选择器于是永远不收敛——这正是
|
|
5759
|
+
* 本函数要避免的那件事。Symbol 不与任何投影结果相等,且只在本模块内存在。
|
|
5760
|
+
*/
|
|
5761
|
+
const NOT_PROJECTED = Symbol("derive.notProjected");
|
|
5762
|
+
/**
|
|
5732
5763
|
* Project one observable into another, recomputing only when the source
|
|
5733
5764
|
* snapshot changes identity, so consumers that select from the projection
|
|
5734
5765
|
* (a Set lookup per row) never rebuild it per read.
|
|
@@ -5738,11 +5769,11 @@ window.__ModuleLoader__.load({
|
|
|
5738
5769
|
*/
|
|
5739
5770
|
function derive(source, project) {
|
|
5740
5771
|
let seen;
|
|
5741
|
-
let value;
|
|
5772
|
+
let value = NOT_PROJECTED;
|
|
5742
5773
|
return {
|
|
5743
5774
|
getSnapshot: () => {
|
|
5744
5775
|
const snapshot = source.getSnapshot();
|
|
5745
|
-
if (value ===
|
|
5776
|
+
if (value === NOT_PROJECTED || snapshot !== seen) {
|
|
5746
5777
|
seen = snapshot;
|
|
5747
5778
|
value = project(snapshot);
|
|
5748
5779
|
}
|
|
@@ -6130,6 +6161,8 @@ window.__ModuleLoader__.load({
|
|
|
6130
6161
|
"settings.count.section": "数字显示",
|
|
6131
6162
|
"settings.count.threshold": "数字显示阈值",
|
|
6132
6163
|
"settings.count.thresholdHint": "数量 >= 此值时才在徽标上显示数字。默认 2(即 count=1 时省略,因为状态点已表达\"有东西\")。设为 1 则始终显示。",
|
|
6164
|
+
"settings.count.decrease": "减少",
|
|
6165
|
+
"settings.count.increase": "增加",
|
|
6133
6166
|
"settings.save": "保存",
|
|
6134
6167
|
"settings.saving": "保存中…",
|
|
6135
6168
|
"settings.saveFailed": "本部署没有接受这些值,已保留供你修改。",
|
|
@@ -6271,6 +6304,8 @@ window.__ModuleLoader__.load({
|
|
|
6271
6304
|
"settings.count.section": "Count display",
|
|
6272
6305
|
"settings.count.threshold": "Count display threshold",
|
|
6273
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",
|
|
6274
6309
|
"settings.save": "Save",
|
|
6275
6310
|
"settings.saving": "Saving…",
|
|
6276
6311
|
"settings.saveFailed": "The deployment did not accept these values; they were left for you to correct.",
|