@tutti-os/workspace-file-reference 0.0.13 → 0.0.15
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/dist/chunk-HRC2MZ5H.js +393 -0
- package/dist/chunk-HRC2MZ5H.js.map +1 -0
- package/dist/{chunk-LS7P3J6Z.js → chunk-KSK6PILG.js} +661 -14
- package/dist/chunk-KSK6PILG.js.map +1 -0
- package/dist/contracts/index.d.ts +167 -1
- package/dist/core/index.d.ts +152 -2
- package/dist/core/index.js +37 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +37 -1
- package/dist/react/index.d.ts +134 -2
- package/dist/react/index.js +6 -2
- package/dist/referenceSourceAggregator-B8ATnJOZ.d.ts +47 -0
- package/dist/ui/index.d.ts +42 -3
- package/dist/ui/index.js +1507 -94
- package/dist/ui/index.js.map +1 -1
- package/package.json +6 -5
- package/dist/chunk-LS7P3J6Z.js.map +0 -1
- package/dist/chunk-PAR2R2G5.js +0 -23
- package/dist/chunk-PAR2R2G5.js.map +0 -1
package/dist/ui/index.js
CHANGED
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
import {
|
|
2
|
+
ROOT_CHILDREN_KEY,
|
|
3
|
+
createReferenceSourcePickerController,
|
|
2
4
|
normalizeDirectoryPath,
|
|
3
5
|
useWorkspaceFileReferencePickerView
|
|
4
|
-
} from "../chunk-
|
|
5
|
-
import
|
|
6
|
+
} from "../chunk-KSK6PILG.js";
|
|
7
|
+
import {
|
|
8
|
+
REFERENCE_FILTER_CATEGORIES,
|
|
9
|
+
WORKSPACE_ROOT_GROUP_NODE_ID,
|
|
10
|
+
nodeRefKey,
|
|
11
|
+
selectedReferenceToWorkspaceFileReference
|
|
12
|
+
} from "../chunk-HRC2MZ5H.js";
|
|
6
13
|
|
|
7
14
|
// src/ui/internal/reference/WorkspaceFileReferencePicker.tsx
|
|
8
|
-
import { useId } from "react";
|
|
9
|
-
import { createPortal } from "react-dom";
|
|
10
15
|
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
CardTitle,
|
|
16
|
-
CloseIcon,
|
|
16
|
+
Dialog,
|
|
17
|
+
DialogContent,
|
|
18
|
+
DialogHeader,
|
|
19
|
+
DialogTitle,
|
|
17
20
|
cn as cn2
|
|
18
21
|
} from "@tutti-os/ui-system";
|
|
19
22
|
|
|
@@ -45,7 +48,7 @@ import {
|
|
|
45
48
|
} from "@tutti-os/ui-system";
|
|
46
49
|
import { AddIcon as AddLinedIcon } from "@tutti-os/ui-system/icons";
|
|
47
50
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
48
|
-
var workspaceFileReferenceTreeIndent =
|
|
51
|
+
var workspaceFileReferenceTreeIndent = 28;
|
|
49
52
|
var workspaceFileReferenceTreeCollapseDurationMs = 200;
|
|
50
53
|
function WorkspaceFileReferencePickerBrowserPane({
|
|
51
54
|
browseRootEntries,
|
|
@@ -154,6 +157,12 @@ function WorkspaceFileReferencePickerTreeEntry({
|
|
|
154
157
|
}, workspaceFileReferenceTreeCollapseDurationMs);
|
|
155
158
|
return () => window.clearTimeout(timeoutId);
|
|
156
159
|
}, [expanded]);
|
|
160
|
+
const handleRowSelect = () => {
|
|
161
|
+
onFocusPath(entry.path);
|
|
162
|
+
if (isFolder) {
|
|
163
|
+
onToggleFolder(entry);
|
|
164
|
+
}
|
|
165
|
+
};
|
|
157
166
|
const shouldBuildChildContent = expanded || shouldRenderChildContent;
|
|
158
167
|
const childContent = shouldBuildChildContent ? childState?.loading ? /* @__PURE__ */ jsxs(
|
|
159
168
|
"div",
|
|
@@ -196,14 +205,24 @@ function WorkspaceFileReferencePickerTreeEntry({
|
|
|
196
205
|
/* @__PURE__ */ jsxs(
|
|
197
206
|
"div",
|
|
198
207
|
{
|
|
208
|
+
"aria-current": focused ? "true" : void 0,
|
|
199
209
|
className: cn(
|
|
200
|
-
"
|
|
210
|
+
"flex cursor-pointer items-center gap-2 rounded-[6px] py-1.5 pr-1 transition-colors select-none",
|
|
201
211
|
"nodrag [-webkit-app-region:no-drag]",
|
|
202
212
|
focused || selected ? "bg-transparency-block" : "hover:bg-transparency-block"
|
|
203
213
|
),
|
|
214
|
+
role: "button",
|
|
215
|
+
tabIndex: 0,
|
|
204
216
|
style: {
|
|
205
217
|
paddingLeft: `${(childDepth - 1) * workspaceFileReferenceTreeIndent + 8}px`
|
|
206
218
|
},
|
|
219
|
+
onClick: handleRowSelect,
|
|
220
|
+
onKeyDown: (event) => {
|
|
221
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
222
|
+
event.preventDefault();
|
|
223
|
+
handleRowSelect();
|
|
224
|
+
}
|
|
225
|
+
},
|
|
207
226
|
children: [
|
|
208
227
|
isFolder ? /* @__PURE__ */ jsx(
|
|
209
228
|
"button",
|
|
@@ -225,25 +244,11 @@ function WorkspaceFileReferencePickerTreeEntry({
|
|
|
225
244
|
}
|
|
226
245
|
)
|
|
227
246
|
}
|
|
228
|
-
) :
|
|
229
|
-
/* @__PURE__ */ jsxs(
|
|
230
|
-
"
|
|
231
|
-
{
|
|
232
|
-
|
|
233
|
-
type: "button",
|
|
234
|
-
onClick: (event) => {
|
|
235
|
-
event.stopPropagation();
|
|
236
|
-
onFocusPath(entry.path);
|
|
237
|
-
if (entry.kind === "folder") {
|
|
238
|
-
onToggleFolder(entry);
|
|
239
|
-
}
|
|
240
|
-
},
|
|
241
|
-
children: [
|
|
242
|
-
isFolder ? /* @__PURE__ */ jsx(FolderFilledIcon, { className: "size-4 shrink-0 text-[var(--rich-text-folder)]" }) : /* @__PURE__ */ jsx(FileIcon, { className: "size-4 shrink-0 text-[var(--text-tertiary)]" }),
|
|
243
|
-
/* @__PURE__ */ jsx("span", { className: "truncate text-[13px] text-[var(--text-primary)]", children: resolveWorkspaceFileReferenceLabel(entry) })
|
|
244
|
-
]
|
|
245
|
-
}
|
|
246
|
-
),
|
|
247
|
+
) : null,
|
|
248
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-2 text-left", children: [
|
|
249
|
+
isFolder ? /* @__PURE__ */ jsx(FolderFilledIcon, { className: "size-4 shrink-0 text-[var(--rich-text-folder)]" }) : /* @__PURE__ */ jsx(FileIcon, { className: "size-4 shrink-0 text-[var(--text-tertiary)]" }),
|
|
250
|
+
/* @__PURE__ */ jsx("span", { className: "truncate text-[13px] text-[var(--text-primary)]", children: resolveWorkspaceFileReferenceLabel(entry) })
|
|
251
|
+
] }),
|
|
247
252
|
/* @__PURE__ */ jsx(
|
|
248
253
|
Button,
|
|
249
254
|
{
|
|
@@ -252,7 +257,7 @@ function WorkspaceFileReferencePickerTreeEntry({
|
|
|
252
257
|
size: "icon-sm",
|
|
253
258
|
type: "button",
|
|
254
259
|
variant: "ghost",
|
|
255
|
-
className: "nodrag [-webkit-app-region:no-drag]",
|
|
260
|
+
className: "nodrag shrink-0 [-webkit-app-region:no-drag]",
|
|
256
261
|
onClick: (event) => {
|
|
257
262
|
event.stopPropagation();
|
|
258
263
|
onFocusPath(entry.path);
|
|
@@ -299,30 +304,29 @@ function WorkspaceFileReferencePickerSearchEntry({
|
|
|
299
304
|
return /* @__PURE__ */ jsxs(
|
|
300
305
|
"div",
|
|
301
306
|
{
|
|
307
|
+
"aria-current": focused ? "true" : void 0,
|
|
302
308
|
className: cn(
|
|
303
|
-
"grid grid-cols-[minmax(0,1fr)_auto] items-center gap-3 rounded-[6px] border py-2.5 pr-1 pl-3 transition-colors",
|
|
309
|
+
"grid cursor-pointer grid-cols-[minmax(0,1fr)_auto] items-center gap-3 rounded-[6px] border py-2.5 pr-1 pl-3 transition-colors select-none",
|
|
304
310
|
"nodrag [-webkit-app-region:no-drag]",
|
|
305
311
|
focused || selected ? "border-border bg-transparency-block" : "border-transparent bg-transparent hover:border-border/70 hover:bg-transparency-block"
|
|
306
312
|
),
|
|
313
|
+
role: "button",
|
|
314
|
+
tabIndex: 0,
|
|
315
|
+
onClick: () => onFocusPath(entry.path),
|
|
316
|
+
onKeyDown: (event) => {
|
|
317
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
318
|
+
event.preventDefault();
|
|
319
|
+
onFocusPath(entry.path);
|
|
320
|
+
}
|
|
321
|
+
},
|
|
307
322
|
children: [
|
|
308
|
-
/* @__PURE__ */ jsxs(
|
|
309
|
-
"
|
|
310
|
-
{
|
|
311
|
-
className: "
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
onFocusPath(entry.path);
|
|
316
|
-
},
|
|
317
|
-
children: [
|
|
318
|
-
/* @__PURE__ */ jsx("span", { className: "grid size-9 shrink-0 place-items-center rounded-lg bg-[var(--transparency-block)] text-[var(--text-tertiary)]", children: isFolder ? /* @__PURE__ */ jsx(FolderFilledIcon, { className: "size-4 text-[var(--rich-text-folder)]" }) : /* @__PURE__ */ jsx(FileIcon, { className: "size-4 text-[var(--text-tertiary)]" }) }),
|
|
319
|
-
/* @__PURE__ */ jsxs("span", { className: "min-w-0", children: [
|
|
320
|
-
/* @__PURE__ */ jsx("span", { className: "block truncate text-[13px] font-medium text-[var(--text-primary)]", children: resolveWorkspaceFileReferenceLabel(entry) }),
|
|
321
|
-
/* @__PURE__ */ jsx("span", { className: "block truncate text-[11px] text-[var(--text-secondary)]", children: entry.path })
|
|
322
|
-
] })
|
|
323
|
-
]
|
|
324
|
-
}
|
|
325
|
-
),
|
|
323
|
+
/* @__PURE__ */ jsxs("div", { className: "nodrag flex min-w-0 items-center gap-3 text-left [-webkit-app-region:no-drag]", children: [
|
|
324
|
+
/* @__PURE__ */ jsx("span", { className: "grid size-9 shrink-0 place-items-center rounded-lg bg-[var(--transparency-block)] text-[var(--text-tertiary)]", children: isFolder ? /* @__PURE__ */ jsx(FolderFilledIcon, { className: "size-4 text-[var(--rich-text-folder)]" }) : /* @__PURE__ */ jsx(FileIcon, { className: "size-4 text-[var(--text-tertiary)]" }) }),
|
|
325
|
+
/* @__PURE__ */ jsxs("span", { className: "min-w-0", children: [
|
|
326
|
+
/* @__PURE__ */ jsx("span", { className: "block truncate text-[13px] font-medium text-[var(--text-primary)]", children: resolveWorkspaceFileReferenceLabel(entry) }),
|
|
327
|
+
/* @__PURE__ */ jsx("span", { className: "block truncate text-[11px] text-[var(--text-secondary)]", children: entry.path })
|
|
328
|
+
] })
|
|
329
|
+
] }),
|
|
326
330
|
/* @__PURE__ */ jsx(
|
|
327
331
|
Button,
|
|
328
332
|
{
|
|
@@ -540,8 +544,6 @@ function WorkspaceFileReferencePickerFeedback2({
|
|
|
540
544
|
|
|
541
545
|
// src/ui/internal/reference/WorkspaceFileReferencePicker.tsx
|
|
542
546
|
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
543
|
-
var workspaceFileReferencePickerBackdropMotionClassName = "motion-safe:animate-in motion-safe:fade-in-0 motion-safe:duration-[180ms] motion-safe:ease-[cubic-bezier(0.22,1,0.36,1)] motion-reduce:animate-none";
|
|
544
|
-
var workspaceFileReferencePickerPanelMotionClassName = "motion-safe:animate-in motion-safe:fade-in-0 motion-safe:zoom-in-[0.96] motion-safe:duration-[250ms] motion-safe:ease-[cubic-bezier(0.22,1,0.36,1)] motion-reduce:animate-none";
|
|
545
547
|
function WorkspaceFileReferencePicker({
|
|
546
548
|
copy,
|
|
547
549
|
fileAdapter,
|
|
@@ -549,9 +551,9 @@ function WorkspaceFileReferencePicker({
|
|
|
549
551
|
onClose,
|
|
550
552
|
onConfirm,
|
|
551
553
|
open,
|
|
554
|
+
scoped = false,
|
|
552
555
|
workspaceId
|
|
553
556
|
}) {
|
|
554
|
-
const titleId = useId();
|
|
555
557
|
const {
|
|
556
558
|
browseRootEntries,
|
|
557
559
|
directoryStateByPath,
|
|
@@ -576,52 +578,27 @@ function WorkspaceFileReferencePicker({
|
|
|
576
578
|
open,
|
|
577
579
|
workspaceId
|
|
578
580
|
});
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
}
|
|
582
|
-
const dialog = /* @__PURE__ */ jsx3(
|
|
583
|
-
"div",
|
|
581
|
+
return /* @__PURE__ */ jsx3(
|
|
582
|
+
Dialog,
|
|
584
583
|
{
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
584
|
+
open,
|
|
585
|
+
onOpenChange: (nextOpen) => {
|
|
586
|
+
if (!nextOpen) {
|
|
587
|
+
onClose();
|
|
588
|
+
}
|
|
589
|
+
},
|
|
591
590
|
children: /* @__PURE__ */ jsxs3(
|
|
592
|
-
|
|
591
|
+
DialogContent,
|
|
593
592
|
{
|
|
594
|
-
"aria-
|
|
595
|
-
"aria-modal": "true",
|
|
593
|
+
"aria-describedby": void 0,
|
|
596
594
|
className: cn2(
|
|
597
|
-
"nodrag flex h-[min(88vh,44rem)] w-full max-w-5xl
|
|
598
|
-
workspaceFileReferencePickerPanelMotionClassName
|
|
595
|
+
"nodrag flex h-[min(88vh,44rem)] w-full max-w-5xl flex-col gap-0 overflow-hidden border-[var(--line-1)] bg-[var(--background-fronted)] p-0 text-[var(--text-primary)] shadow-panel [-webkit-app-region:no-drag] sm:h-[min(82vh,44rem)] sm:max-w-5xl"
|
|
599
596
|
),
|
|
600
|
-
|
|
601
|
-
|
|
597
|
+
overlayClassName: cn2("nodrag", scoped && "!absolute"),
|
|
598
|
+
portaled: !scoped,
|
|
602
599
|
children: [
|
|
603
|
-
/* @__PURE__ */ jsx3(
|
|
604
|
-
|
|
605
|
-
{
|
|
606
|
-
className: "border-b px-4 py-4 sm:px-6 sm:py-5",
|
|
607
|
-
style: { borderBottomColor: "var(--line-1)" },
|
|
608
|
-
children: /* @__PURE__ */ jsxs3("div", { className: "flex items-start justify-between gap-4", children: [
|
|
609
|
-
/* @__PURE__ */ jsx3("div", { className: "min-w-0", children: /* @__PURE__ */ jsx3(CardTitle, { id: titleId, children: copy.t("referencePicker.title") }) }),
|
|
610
|
-
/* @__PURE__ */ jsx3(
|
|
611
|
-
Button3,
|
|
612
|
-
{
|
|
613
|
-
"aria-label": copy.t("actions.cancel"),
|
|
614
|
-
size: "icon-sm",
|
|
615
|
-
type: "button",
|
|
616
|
-
variant: "ghost",
|
|
617
|
-
onClick: onClose,
|
|
618
|
-
children: /* @__PURE__ */ jsx3(CloseIcon, { size: 16 })
|
|
619
|
-
}
|
|
620
|
-
)
|
|
621
|
-
] })
|
|
622
|
-
}
|
|
623
|
-
),
|
|
624
|
-
/* @__PURE__ */ jsxs3(CardContent, { className: "grid min-h-0 flex-1 grid-rows-[minmax(0,1fr)_auto] gap-0 overflow-hidden p-0 lg:grid-cols-[minmax(0,1.15fr)_minmax(20rem,0.85fr)] lg:grid-rows-1", children: [
|
|
600
|
+
/* @__PURE__ */ jsx3(DialogHeader, { className: "flex-none border-b border-[var(--line-1)] px-4 py-4 sm:px-6 sm:py-5", children: /* @__PURE__ */ jsx3(DialogTitle, { children: copy.t("referencePicker.title") }) }),
|
|
601
|
+
/* @__PURE__ */ jsxs3("div", { className: "grid min-h-0 flex-1 grid-rows-[minmax(0,1fr)_auto] gap-0 overflow-hidden p-0 lg:grid-cols-[minmax(0,1.15fr)_minmax(20rem,0.85fr)] lg:grid-rows-1", children: [
|
|
625
602
|
/* @__PURE__ */ jsx3(
|
|
626
603
|
WorkspaceFileReferencePickerBrowserPane,
|
|
627
604
|
{
|
|
@@ -665,12 +642,1448 @@ function WorkspaceFileReferencePicker({
|
|
|
665
642
|
)
|
|
666
643
|
}
|
|
667
644
|
);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// src/ui/internal/reference/ReferenceSourcePicker.tsx
|
|
648
|
+
import {
|
|
649
|
+
useEffect as useEffect3,
|
|
650
|
+
useId,
|
|
651
|
+
useRef as useRef2,
|
|
652
|
+
useState as useState3
|
|
653
|
+
} from "react";
|
|
654
|
+
import { createPortal } from "react-dom";
|
|
655
|
+
import {
|
|
656
|
+
ArrowRightIcon as ArrowRightIcon2,
|
|
657
|
+
Badge as Badge2,
|
|
658
|
+
Button as Button3,
|
|
659
|
+
Card,
|
|
660
|
+
CardContent,
|
|
661
|
+
CardHeader,
|
|
662
|
+
CardTitle,
|
|
663
|
+
CheckIcon as CheckIcon2,
|
|
664
|
+
ChevronDownIcon,
|
|
665
|
+
CloseIcon,
|
|
666
|
+
FileIcon as FileIcon3,
|
|
667
|
+
FolderFilledIcon as FolderFilledIcon3,
|
|
668
|
+
Input as Input2,
|
|
669
|
+
IssueIcon,
|
|
670
|
+
ResizableHandle,
|
|
671
|
+
ResizablePanel,
|
|
672
|
+
ResizablePanelGroup,
|
|
673
|
+
ScrollArea as ScrollArea2,
|
|
674
|
+
SearchIcon as SearchIcon2,
|
|
675
|
+
Spinner as Spinner2,
|
|
676
|
+
cn as cn3
|
|
677
|
+
} from "@tutti-os/ui-system";
|
|
678
|
+
import { AddIcon } from "@tutti-os/ui-system/icons";
|
|
679
|
+
import {
|
|
680
|
+
WorkspaceFilePreviewSurface
|
|
681
|
+
} from "@tutti-os/workspace-file-preview/react";
|
|
682
|
+
|
|
683
|
+
// src/react/internal/reference/useReferenceSourcePickerView.ts
|
|
684
|
+
import { useCallback, useEffect as useEffect2, useMemo, useRef, useState as useState2 } from "react";
|
|
685
|
+
import { useSnapshot } from "valtio";
|
|
686
|
+
import {
|
|
687
|
+
sortWorkspaceFileEntriesForArrangeMode
|
|
688
|
+
} from "@tutti-os/workspace-file-manager/services";
|
|
689
|
+
import {
|
|
690
|
+
createWorkspaceFilePreviewLoadedState
|
|
691
|
+
} from "@tutti-os/workspace-file-preview";
|
|
692
|
+
function useReferenceSourcePickerView({
|
|
693
|
+
aggregator,
|
|
694
|
+
workspaceId,
|
|
695
|
+
open,
|
|
696
|
+
workspaceRootGroupLabel,
|
|
697
|
+
initialTarget = null,
|
|
698
|
+
onClose,
|
|
699
|
+
onConfirm,
|
|
700
|
+
onConfirmBundles
|
|
701
|
+
}) {
|
|
702
|
+
const readSnapshot = useSnapshot;
|
|
703
|
+
const scope = useMemo(() => ({ workspaceId }), [workspaceId]);
|
|
704
|
+
const controller = useMemo(
|
|
705
|
+
() => createReferenceSourcePickerController({ aggregator, scope }),
|
|
706
|
+
[aggregator, scope]
|
|
707
|
+
);
|
|
708
|
+
const snapshot = readSnapshot(controller.store);
|
|
709
|
+
const [breadcrumbBySource, setBreadcrumbBySource] = useState2({});
|
|
710
|
+
const [focusedNode, setFocusedNode] = useState2(null);
|
|
711
|
+
const [arrangeMode, setArrangeMode] = useState2("none");
|
|
712
|
+
const sortNodes = useCallback(
|
|
713
|
+
(nodes) => {
|
|
714
|
+
if (arrangeMode === "none") {
|
|
715
|
+
return [...nodes];
|
|
716
|
+
}
|
|
717
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
718
|
+
const fileEntries = nodes.map((node) => {
|
|
719
|
+
const key = nodeRefKey(node.ref);
|
|
720
|
+
byKey.set(key, node);
|
|
721
|
+
return {
|
|
722
|
+
hasChildren: node.kind === "folder",
|
|
723
|
+
kind: node.kind === "folder" ? "directory" : "file",
|
|
724
|
+
mtimeMs: node.mtimeMs ?? null,
|
|
725
|
+
name: node.displayName,
|
|
726
|
+
path: key,
|
|
727
|
+
sizeBytes: node.sizeBytes ?? null
|
|
728
|
+
};
|
|
729
|
+
});
|
|
730
|
+
return sortWorkspaceFileEntriesForArrangeMode(fileEntries, arrangeMode).map((entry) => byKey.get(entry.path)).filter((node) => node !== void 0);
|
|
731
|
+
},
|
|
732
|
+
[arrangeMode]
|
|
733
|
+
);
|
|
734
|
+
const autoEnteredSourcesRef = useRef(/* @__PURE__ */ new Set());
|
|
735
|
+
const appliedInitialTargetRef = useRef(null);
|
|
736
|
+
useEffect2(() => {
|
|
737
|
+
if (!open) {
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
controller.reset();
|
|
741
|
+
controller.open();
|
|
742
|
+
setBreadcrumbBySource({});
|
|
743
|
+
setFocusedNode(null);
|
|
744
|
+
autoEnteredSourcesRef.current = /* @__PURE__ */ new Set();
|
|
745
|
+
appliedInitialTargetRef.current = null;
|
|
746
|
+
return () => {
|
|
747
|
+
controller.close();
|
|
748
|
+
};
|
|
749
|
+
}, [controller, open]);
|
|
750
|
+
useEffect2(() => {
|
|
751
|
+
const target = initialTarget;
|
|
752
|
+
if (!open || !target || appliedInitialTargetRef.current === target) {
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
appliedInitialTargetRef.current = target;
|
|
756
|
+
autoEnteredSourcesRef.current.add(target.sourceId);
|
|
757
|
+
let canceled = false;
|
|
758
|
+
void controller.locatePath(target).then((path) => {
|
|
759
|
+
if (canceled) {
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
const group = path[0];
|
|
763
|
+
if (!group) {
|
|
764
|
+
autoEnteredSourcesRef.current.delete(target.sourceId);
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
controller.setActiveSource(target.sourceId);
|
|
768
|
+
setBreadcrumbBySource((current) => ({
|
|
769
|
+
...current,
|
|
770
|
+
[group.ref.sourceId]: [group]
|
|
771
|
+
}));
|
|
772
|
+
controller.ensureChildren(group);
|
|
773
|
+
const deepest = path[path.length - 1];
|
|
774
|
+
setFocusedNode(path.length > 1 && deepest ? deepest : null);
|
|
775
|
+
}).catch(() => {
|
|
776
|
+
autoEnteredSourcesRef.current.delete(target.sourceId);
|
|
777
|
+
});
|
|
778
|
+
return () => {
|
|
779
|
+
canceled = true;
|
|
780
|
+
};
|
|
781
|
+
}, [open, initialTarget, controller]);
|
|
782
|
+
const activeSourceId = snapshot.activeSourceId;
|
|
783
|
+
const activeTab = useMemo(
|
|
784
|
+
() => snapshot.tabs.find((tab) => tab.sourceId === activeSourceId) ?? null,
|
|
785
|
+
[activeSourceId, snapshot.tabs]
|
|
786
|
+
);
|
|
787
|
+
const capabilities = activeTab?.capabilities ?? null;
|
|
788
|
+
const breadcrumb = activeSourceId ? breadcrumbBySource[activeSourceId] ?? [] : [];
|
|
789
|
+
const currentNode = breadcrumb.at(-1) ?? null;
|
|
790
|
+
const currentKey = currentNode ? nodeRefKey(currentNode.ref) : ROOT_CHILDREN_KEY;
|
|
791
|
+
const activeTabState = activeSourceId ? snapshot.bySource[activeSourceId] : void 0;
|
|
792
|
+
const activeFilters = activeTabState?.searchFilters ?? [];
|
|
793
|
+
const isQuery = activeTabState?.mode === "search" && (activeTabState.searchQuery.trim() !== "" || activeFilters.length > 0);
|
|
794
|
+
const currentChildren = activeTabState?.childrenByKey[currentKey];
|
|
795
|
+
const currentEntries = useMemo(
|
|
796
|
+
() => sortNodes(currentChildren?.entries ?? []),
|
|
797
|
+
[currentChildren?.entries, sortNodes]
|
|
798
|
+
);
|
|
799
|
+
const searchResults = useMemo(
|
|
800
|
+
() => sortNodes(activeTabState?.searchEntries ?? []),
|
|
801
|
+
[activeTabState?.searchEntries, sortNodes]
|
|
802
|
+
);
|
|
803
|
+
const sidebarGroupsBySource = useMemo(() => {
|
|
804
|
+
const result = {};
|
|
805
|
+
for (const tab of snapshot.tabs) {
|
|
806
|
+
const sourceId = tab.sourceId;
|
|
807
|
+
const provided = aggregator.getLoadedSource(sourceId)?.listSidebarGroups?.(scope);
|
|
808
|
+
if (provided && provided.length > 0) {
|
|
809
|
+
result[sourceId] = provided;
|
|
810
|
+
continue;
|
|
811
|
+
}
|
|
812
|
+
const root = snapshot.bySource[sourceId]?.childrenByKey[ROOT_CHILDREN_KEY];
|
|
813
|
+
const folders = (root?.entries ?? []).filter(
|
|
814
|
+
(node) => node.kind === "folder"
|
|
815
|
+
);
|
|
816
|
+
if (tab.capabilities.navigable) {
|
|
817
|
+
result[sourceId] = folders;
|
|
818
|
+
} else {
|
|
819
|
+
const workspaceRoot = {
|
|
820
|
+
ref: { sourceId, nodeId: WORKSPACE_ROOT_GROUP_NODE_ID },
|
|
821
|
+
kind: "folder",
|
|
822
|
+
displayName: workspaceRootGroupLabel
|
|
823
|
+
};
|
|
824
|
+
result[sourceId] = [workspaceRoot, ...folders];
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
return result;
|
|
828
|
+
}, [
|
|
829
|
+
snapshot.tabs,
|
|
830
|
+
snapshot.bySource,
|
|
831
|
+
aggregator,
|
|
832
|
+
scope,
|
|
833
|
+
workspaceRootGroupLabel
|
|
834
|
+
]);
|
|
835
|
+
const sidebarHasMoreBySource = useMemo(() => {
|
|
836
|
+
const result = {};
|
|
837
|
+
for (const tab of snapshot.tabs) {
|
|
838
|
+
const sourceId = tab.sourceId;
|
|
839
|
+
const provided = aggregator.getLoadedSource(sourceId)?.listSidebarGroups?.(scope);
|
|
840
|
+
if (provided && provided.length > 0) {
|
|
841
|
+
result[sourceId] = false;
|
|
842
|
+
continue;
|
|
843
|
+
}
|
|
844
|
+
const root = snapshot.bySource[sourceId]?.childrenByKey[ROOT_CHILDREN_KEY];
|
|
845
|
+
result[sourceId] = Boolean(root?.nextCursor);
|
|
846
|
+
}
|
|
847
|
+
return result;
|
|
848
|
+
}, [snapshot.tabs, snapshot.bySource, aggregator, scope]);
|
|
849
|
+
const sidebarLoadingMoreBySource = useMemo(() => {
|
|
850
|
+
const result = {};
|
|
851
|
+
for (const tab of snapshot.tabs) {
|
|
852
|
+
const root = snapshot.bySource[tab.sourceId]?.childrenByKey[ROOT_CHILDREN_KEY];
|
|
853
|
+
result[tab.sourceId] = Boolean(root?.loaded && root.loading);
|
|
854
|
+
}
|
|
855
|
+
return result;
|
|
856
|
+
}, [snapshot.tabs, snapshot.bySource]);
|
|
857
|
+
const sidebarGroups = activeSourceId ? sidebarGroupsBySource[activeSourceId] ?? [] : [];
|
|
858
|
+
const rootGroupNode = breadcrumb[0] ?? null;
|
|
859
|
+
const searchScopeNodeId = rootGroupNode ? rootGroupNode.ref.nodeId : null;
|
|
860
|
+
const selectedGroupKey = rootGroupNode ? nodeRefKey(rootGroupNode.ref) : activeSourceId && !capabilities?.navigable ? nodeRefKey({
|
|
861
|
+
sourceId: activeSourceId,
|
|
862
|
+
nodeId: WORKSPACE_ROOT_GROUP_NODE_ID
|
|
863
|
+
}) : null;
|
|
864
|
+
useEffect2(() => {
|
|
865
|
+
controller.setSearchScope(searchScopeNodeId);
|
|
866
|
+
}, [controller, searchScopeNodeId]);
|
|
867
|
+
const setActiveSource = useCallback(
|
|
868
|
+
(sourceId) => {
|
|
869
|
+
controller.setActiveSource(sourceId);
|
|
870
|
+
setFocusedNode(null);
|
|
871
|
+
},
|
|
872
|
+
[controller]
|
|
873
|
+
);
|
|
874
|
+
const enterFolder = useCallback(
|
|
875
|
+
(node) => {
|
|
876
|
+
const sourceId = node.ref.sourceId;
|
|
877
|
+
if (node.kind !== "folder" || !sourceId || node.ref.nodeId === WORKSPACE_ROOT_GROUP_NODE_ID) {
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
controller.ensureChildren(node);
|
|
881
|
+
setBreadcrumbBySource((current) => {
|
|
882
|
+
const stack = current[sourceId] ?? [];
|
|
883
|
+
const index = stack.findIndex(
|
|
884
|
+
(item) => nodeRefKey(item.ref) === nodeRefKey(node.ref)
|
|
885
|
+
);
|
|
886
|
+
const nextStack = index >= 0 ? stack.slice(0, index + 1) : [...stack, node];
|
|
887
|
+
return { ...current, [sourceId]: nextStack };
|
|
888
|
+
});
|
|
889
|
+
setFocusedNode(null);
|
|
890
|
+
},
|
|
891
|
+
[controller]
|
|
892
|
+
);
|
|
893
|
+
useEffect2(() => {
|
|
894
|
+
if (!open || !activeSourceId) {
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
if (autoEnteredSourcesRef.current.has(activeSourceId)) {
|
|
898
|
+
return;
|
|
899
|
+
}
|
|
900
|
+
const stack = breadcrumbBySource[activeSourceId] ?? [];
|
|
901
|
+
if (stack.length > 0) {
|
|
902
|
+
autoEnteredSourcesRef.current.add(activeSourceId);
|
|
903
|
+
return;
|
|
904
|
+
}
|
|
905
|
+
const firstGroup = sidebarGroups[0];
|
|
906
|
+
if (!firstGroup) {
|
|
907
|
+
return;
|
|
908
|
+
}
|
|
909
|
+
autoEnteredSourcesRef.current.add(activeSourceId);
|
|
910
|
+
enterFolder(firstGroup);
|
|
911
|
+
}, [open, activeSourceId, sidebarGroups, breadcrumbBySource, enterFolder]);
|
|
912
|
+
useEffect2(() => {
|
|
913
|
+
if (!open) {
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
for (const tab of snapshot.tabs) {
|
|
917
|
+
controller.ensureSourceRoot(tab.sourceId);
|
|
918
|
+
}
|
|
919
|
+
}, [open, snapshot.tabs, controller]);
|
|
920
|
+
const navigateToBreadcrumb = useCallback(
|
|
921
|
+
(index) => {
|
|
922
|
+
if (!activeSourceId) {
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
925
|
+
setBreadcrumbBySource((current) => {
|
|
926
|
+
const stack = current[activeSourceId] ?? [];
|
|
927
|
+
return { ...current, [activeSourceId]: stack.slice(0, index + 1) };
|
|
928
|
+
});
|
|
929
|
+
const target = (breadcrumbBySource[activeSourceId] ?? [])[index] ?? null;
|
|
930
|
+
controller.ensureChildren(target);
|
|
931
|
+
setFocusedNode(null);
|
|
932
|
+
},
|
|
933
|
+
[activeSourceId, breadcrumbBySource, controller]
|
|
934
|
+
);
|
|
935
|
+
const navigateToRoot = useCallback(
|
|
936
|
+
(sourceId) => {
|
|
937
|
+
const sid = sourceId ?? activeSourceId;
|
|
938
|
+
if (!sid) {
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
setBreadcrumbBySource((current) => ({ ...current, [sid]: [] }));
|
|
942
|
+
controller.ensureSourceRoot(sid);
|
|
943
|
+
setFocusedNode(null);
|
|
944
|
+
},
|
|
945
|
+
[activeSourceId, controller]
|
|
946
|
+
);
|
|
947
|
+
const selectGroup = useCallback(
|
|
948
|
+
(node) => {
|
|
949
|
+
const sourceId = node.ref.sourceId;
|
|
950
|
+
if (!sourceId) {
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
953
|
+
if (sourceId !== snapshot.activeSourceId) {
|
|
954
|
+
controller.setActiveSource(sourceId);
|
|
955
|
+
}
|
|
956
|
+
if (node.ref.nodeId === WORKSPACE_ROOT_GROUP_NODE_ID) {
|
|
957
|
+
navigateToRoot(sourceId);
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
960
|
+
controller.ensureChildren(node);
|
|
961
|
+
setBreadcrumbBySource((current) => ({ ...current, [sourceId]: [node] }));
|
|
962
|
+
setFocusedNode(null);
|
|
963
|
+
},
|
|
964
|
+
[controller, snapshot.activeSourceId, navigateToRoot]
|
|
965
|
+
);
|
|
966
|
+
const isSelected = useCallback(
|
|
967
|
+
(node) => snapshot.selection.some(
|
|
968
|
+
(item) => nodeRefKey(item.ref) === nodeRefKey(node.ref)
|
|
969
|
+
),
|
|
970
|
+
[snapshot.selection]
|
|
971
|
+
);
|
|
972
|
+
const [isConfirming, setIsConfirming] = useState2(false);
|
|
973
|
+
const confirm = useCallback(async () => {
|
|
974
|
+
if (isConfirming) {
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
977
|
+
setIsConfirming(true);
|
|
978
|
+
try {
|
|
979
|
+
if (onConfirmBundles) {
|
|
980
|
+
const grouped = await controller.confirmGrouped();
|
|
981
|
+
onConfirmBundles({
|
|
982
|
+
files: grouped.files.map(selectedReferenceToWorkspaceFileReference),
|
|
983
|
+
bundles: grouped.bundles.map((bundle) => ({
|
|
984
|
+
sourceId: bundle.root.ref.sourceId,
|
|
985
|
+
nodeId: bundle.root.ref.nodeId,
|
|
986
|
+
displayName: bundle.root.displayName,
|
|
987
|
+
iconUrl: bundle.root.iconUrl ?? null,
|
|
988
|
+
files: bundle.files.map(selectedReferenceToWorkspaceFileReference)
|
|
989
|
+
}))
|
|
990
|
+
});
|
|
991
|
+
} else {
|
|
992
|
+
const selected = await controller.confirm();
|
|
993
|
+
onConfirm(selected.map(selectedReferenceToWorkspaceFileReference));
|
|
994
|
+
}
|
|
995
|
+
onClose();
|
|
996
|
+
} finally {
|
|
997
|
+
setIsConfirming(false);
|
|
998
|
+
}
|
|
999
|
+
}, [controller, isConfirming, onClose, onConfirm, onConfirmBundles]);
|
|
1000
|
+
const [previewState, setPreviewState] = useState2({
|
|
1001
|
+
status: "empty"
|
|
1002
|
+
});
|
|
1003
|
+
const previewObjectUrlRef = useRef(null);
|
|
1004
|
+
const revokePreviewObjectUrl = useCallback(() => {
|
|
1005
|
+
if (previewObjectUrlRef.current) {
|
|
1006
|
+
URL.revokeObjectURL(previewObjectUrlRef.current);
|
|
1007
|
+
previewObjectUrlRef.current = null;
|
|
1008
|
+
}
|
|
1009
|
+
}, []);
|
|
1010
|
+
useEffect2(() => {
|
|
1011
|
+
const node = focusedNode;
|
|
1012
|
+
if (!node) {
|
|
1013
|
+
revokePreviewObjectUrl();
|
|
1014
|
+
setPreviewState({ status: "empty" });
|
|
1015
|
+
return;
|
|
1016
|
+
}
|
|
1017
|
+
if (node.kind === "folder") {
|
|
1018
|
+
revokePreviewObjectUrl();
|
|
1019
|
+
setPreviewState({ node, status: "directory" });
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1022
|
+
const previewable = aggregator.getLoadedSource(node.ref.sourceId)?.capabilities.previewable ?? false;
|
|
1023
|
+
if (!previewable) {
|
|
1024
|
+
revokePreviewObjectUrl();
|
|
1025
|
+
setPreviewState({ node, status: "unsupported" });
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
let cancelled = false;
|
|
1029
|
+
revokePreviewObjectUrl();
|
|
1030
|
+
setPreviewState({ node, status: "loading" });
|
|
1031
|
+
void (async () => {
|
|
1032
|
+
try {
|
|
1033
|
+
const preview = await aggregator.readPreview(scope, node);
|
|
1034
|
+
if (cancelled) {
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
1037
|
+
if (!preview) {
|
|
1038
|
+
setPreviewState({ node, status: "unsupported" });
|
|
1039
|
+
return;
|
|
1040
|
+
}
|
|
1041
|
+
const loaded = createWorkspaceFilePreviewLoadedState({
|
|
1042
|
+
bytes: preview.bytes,
|
|
1043
|
+
contentType: preview.contentType,
|
|
1044
|
+
entry: {
|
|
1045
|
+
kind: node.kind,
|
|
1046
|
+
name: node.displayName,
|
|
1047
|
+
path: node.ref.nodeId,
|
|
1048
|
+
mtimeMs: node.mtimeMs ?? null,
|
|
1049
|
+
sizeBytes: node.sizeBytes ?? null
|
|
1050
|
+
},
|
|
1051
|
+
target: {
|
|
1052
|
+
fileKind: preview.kind,
|
|
1053
|
+
name: node.displayName,
|
|
1054
|
+
path: node.ref.nodeId,
|
|
1055
|
+
mtimeMs: node.mtimeMs ?? null,
|
|
1056
|
+
sizeBytes: node.sizeBytes ?? null
|
|
1057
|
+
}
|
|
1058
|
+
});
|
|
1059
|
+
if (cancelled) {
|
|
1060
|
+
return;
|
|
1061
|
+
}
|
|
1062
|
+
if (loaded.status === "image") {
|
|
1063
|
+
const objectUrl = URL.createObjectURL(
|
|
1064
|
+
new Blob([loaded.bytes], { type: loaded.contentType })
|
|
1065
|
+
);
|
|
1066
|
+
previewObjectUrlRef.current = objectUrl;
|
|
1067
|
+
setPreviewState({ node, objectUrl, status: "image" });
|
|
1068
|
+
return;
|
|
1069
|
+
}
|
|
1070
|
+
if (loaded.status === "text") {
|
|
1071
|
+
setPreviewState({ content: loaded.content, node, status: "text" });
|
|
1072
|
+
return;
|
|
1073
|
+
}
|
|
1074
|
+
setPreviewState({
|
|
1075
|
+
node,
|
|
1076
|
+
reason: loaded.reason,
|
|
1077
|
+
...loaded.maxSizeBytes == null ? {} : { maxSizeBytes: loaded.maxSizeBytes },
|
|
1078
|
+
status: "readonly"
|
|
1079
|
+
});
|
|
1080
|
+
} catch {
|
|
1081
|
+
if (!cancelled) {
|
|
1082
|
+
setPreviewState({ node, status: "error" });
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
})();
|
|
1086
|
+
return () => {
|
|
1087
|
+
cancelled = true;
|
|
1088
|
+
};
|
|
1089
|
+
}, [aggregator, focusedNode, revokePreviewObjectUrl, scope]);
|
|
1090
|
+
useEffect2(() => revokePreviewObjectUrl, [revokePreviewObjectUrl]);
|
|
1091
|
+
return {
|
|
1092
|
+
tabs: snapshot.tabs,
|
|
1093
|
+
activeSourceId,
|
|
1094
|
+
previewState,
|
|
1095
|
+
activeTabLabel: activeTab?.label ?? "",
|
|
1096
|
+
capabilities,
|
|
1097
|
+
// 内容区递归就地树:当前选中二级节点的子条目(本地根时为源根子条目)。
|
|
1098
|
+
currentEntries,
|
|
1099
|
+
// 搜索态:扁平搜索结果。
|
|
1100
|
+
searchResults,
|
|
1101
|
+
expandedKeys: activeTabState?.expandedKeys ?? {},
|
|
1102
|
+
childrenByKey: activeTabState?.childrenByKey ?? {},
|
|
1103
|
+
toggleNode: (node) => controller.toggleNode(node),
|
|
1104
|
+
sortNodes,
|
|
1105
|
+
isLoadingTabs: snapshot.isLoadingTabs,
|
|
1106
|
+
breadcrumb,
|
|
1107
|
+
currentNode,
|
|
1108
|
+
sidebarGroups,
|
|
1109
|
+
sidebarGroupsBySource,
|
|
1110
|
+
sidebarHasMoreBySource,
|
|
1111
|
+
sidebarLoadingMoreBySource,
|
|
1112
|
+
loadMoreSidebarGroups: (sourceId) => controller.loadMoreSourceRoot(sourceId),
|
|
1113
|
+
selectedGroupKey,
|
|
1114
|
+
arrangeMode,
|
|
1115
|
+
setArrangeMode,
|
|
1116
|
+
// 查询态(关键词或筛选任一非空)→ 平铺结果;否则浏览树。
|
|
1117
|
+
isQuery,
|
|
1118
|
+
searchQuery: activeTabState?.searchQuery ?? "",
|
|
1119
|
+
// 当前源支持的文件类型筛选分类(不支持则空数组,picker 据此决定是否展示筛选下拉)。
|
|
1120
|
+
filterCategories: capabilities?.filterable ? REFERENCE_FILTER_CATEGORIES : [],
|
|
1121
|
+
activeFilters,
|
|
1122
|
+
// 搜索态:仅在「还没有任何结果」时显示 spinner;细化关键词(已有结果)时
|
|
1123
|
+
// 保留旧结果直到新结果就绪,避免内容区在 spinner/结果间反复切换造成闪烁。
|
|
1124
|
+
isLoading: isQuery ? (activeTabState?.isSearchLoading ?? false) && (activeTabState?.searchEntries.length ?? 0) === 0 : currentChildren?.loading ?? false,
|
|
1125
|
+
// 查询态:增长式分页是否还有更多;浏览态:cursor 是否有下一页。
|
|
1126
|
+
hasMore: isQuery ? activeTabState?.searchHasMore ?? false : Boolean(currentChildren?.nextCursor),
|
|
1127
|
+
// 底部「加载更多」在途(查询态 = 增长重查;浏览态 = cursor append)。
|
|
1128
|
+
isLoadingMore: isQuery ? activeTabState?.isSearchLoadingMore ?? false : currentChildren?.loading ?? false,
|
|
1129
|
+
focusedNode,
|
|
1130
|
+
selection: snapshot.selection,
|
|
1131
|
+
selectionCount: snapshot.selection.length,
|
|
1132
|
+
setActiveSource,
|
|
1133
|
+
enterFolder,
|
|
1134
|
+
selectGroup,
|
|
1135
|
+
navigateToBreadcrumb,
|
|
1136
|
+
navigateToRoot,
|
|
1137
|
+
setFocusedNode,
|
|
1138
|
+
setSearchQuery: (query) => controller.setSearchQuery(query, searchScopeNodeId),
|
|
1139
|
+
setFilters: (filters) => controller.setSearchFilters(filters, searchScopeNodeId),
|
|
1140
|
+
toggleSelection: (node) => controller.toggleSelection(node),
|
|
1141
|
+
loadMore: () => isQuery ? controller.loadMoreSearch() : controller.loadMore(currentNode),
|
|
1142
|
+
isSelected,
|
|
1143
|
+
confirm,
|
|
1144
|
+
isConfirming
|
|
1145
|
+
};
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
// src/ui/internal/reference/ReferenceSourcePicker.tsx
|
|
1149
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1150
|
+
var SIDEBAR_GROUP_PAGE_SIZE = 20;
|
|
1151
|
+
function autoFitPanelWidth(groupEl, contentEl, panel, trailingPx) {
|
|
1152
|
+
if (!groupEl || !contentEl || !panel) {
|
|
1153
|
+
return;
|
|
1154
|
+
}
|
|
1155
|
+
const groupWidth = groupEl.clientWidth;
|
|
1156
|
+
if (groupWidth <= 0) {
|
|
1157
|
+
return;
|
|
1158
|
+
}
|
|
1159
|
+
const contentLeft = contentEl.getBoundingClientRect().left;
|
|
1160
|
+
const labels = contentEl.querySelectorAll(
|
|
1161
|
+
"[data-autofit-label]"
|
|
1162
|
+
);
|
|
1163
|
+
let maxRight = 0;
|
|
1164
|
+
labels.forEach((label) => {
|
|
1165
|
+
const right = label.getBoundingClientRect().left - contentLeft + label.scrollWidth;
|
|
1166
|
+
if (right > maxRight) {
|
|
1167
|
+
maxRight = right;
|
|
1168
|
+
}
|
|
1169
|
+
});
|
|
1170
|
+
if (maxRight <= 0) {
|
|
1171
|
+
return;
|
|
1172
|
+
}
|
|
1173
|
+
const naturalWidth = maxRight + trailingPx;
|
|
1174
|
+
panel.resize(Math.min(80, naturalWidth / groupWidth * 100));
|
|
1175
|
+
}
|
|
1176
|
+
function ReferenceSourcePicker({
|
|
1177
|
+
aggregator,
|
|
1178
|
+
copy,
|
|
1179
|
+
initialTarget,
|
|
1180
|
+
onClose,
|
|
1181
|
+
onConfirm,
|
|
1182
|
+
onConfirmBundles,
|
|
1183
|
+
open,
|
|
1184
|
+
workspaceId
|
|
1185
|
+
}) {
|
|
1186
|
+
const titleId = useId();
|
|
1187
|
+
const view = useReferenceSourcePickerView({
|
|
1188
|
+
aggregator,
|
|
1189
|
+
workspaceId,
|
|
1190
|
+
open,
|
|
1191
|
+
workspaceRootGroupLabel: copy.t("referencePicker.workspaceRootGroup"),
|
|
1192
|
+
initialTarget,
|
|
1193
|
+
onClose,
|
|
1194
|
+
onConfirm,
|
|
1195
|
+
onConfirmBundles
|
|
1196
|
+
});
|
|
1197
|
+
const activeFilterSet = new Set(view.activeFilters);
|
|
1198
|
+
const toggleFilter = (id) => {
|
|
1199
|
+
const next = new Set(activeFilterSet);
|
|
1200
|
+
if (next.has(id)) {
|
|
1201
|
+
next.delete(id);
|
|
1202
|
+
} else {
|
|
1203
|
+
next.add(id);
|
|
1204
|
+
}
|
|
1205
|
+
view.setFilters([...next]);
|
|
1206
|
+
};
|
|
1207
|
+
const clearFilters = () => view.setFilters([]);
|
|
1208
|
+
const layoutRef = useRef2(null);
|
|
1209
|
+
const sidebarContentRef = useRef2(null);
|
|
1210
|
+
const middleContentRef = useRef2(null);
|
|
1211
|
+
const sidebarPanelRef = useRef2(null);
|
|
1212
|
+
const middlePanelRef = useRef2(null);
|
|
1213
|
+
if (!open) {
|
|
1214
|
+
return null;
|
|
1215
|
+
}
|
|
1216
|
+
const hasSelectedGroup = view.selectedGroupKey != null;
|
|
1217
|
+
const fitSidebar = () => autoFitPanelWidth(
|
|
1218
|
+
layoutRef.current,
|
|
1219
|
+
sidebarContentRef.current,
|
|
1220
|
+
sidebarPanelRef.current,
|
|
1221
|
+
36
|
|
1222
|
+
);
|
|
1223
|
+
const fitMiddle = () => autoFitPanelWidth(
|
|
1224
|
+
layoutRef.current,
|
|
1225
|
+
middleContentRef.current,
|
|
1226
|
+
middlePanelRef.current,
|
|
1227
|
+
56
|
|
1228
|
+
);
|
|
1229
|
+
const dialog = /* @__PURE__ */ jsx4(
|
|
1230
|
+
"div",
|
|
1231
|
+
{
|
|
1232
|
+
className: "nodrag fixed inset-0 grid place-items-center bg-[var(--backdrop)] px-3 py-4 backdrop-blur-md [-webkit-app-region:no-drag] sm:px-6 sm:py-8",
|
|
1233
|
+
style: { zIndex: "var(--z-panel)" },
|
|
1234
|
+
onClick: onClose,
|
|
1235
|
+
children: /* @__PURE__ */ jsxs4(
|
|
1236
|
+
Card,
|
|
1237
|
+
{
|
|
1238
|
+
"aria-labelledby": titleId,
|
|
1239
|
+
"aria-modal": "true",
|
|
1240
|
+
className: "nodrag flex h-[min(88vh,46rem)] w-full max-w-5xl flex-col gap-0 overflow-hidden border-[var(--line-1)] bg-[var(--background-fronted)] py-0 text-[var(--text-primary)] shadow-panel [-webkit-app-region:no-drag]",
|
|
1241
|
+
role: "dialog",
|
|
1242
|
+
onClick: (event) => event.stopPropagation(),
|
|
1243
|
+
children: [
|
|
1244
|
+
/* @__PURE__ */ jsx4(CardHeader, { className: "gap-3 px-4 pt-4 pb-4 sm:px-6", children: /* @__PURE__ */ jsxs4("div", { className: "flex items-start justify-between gap-4", children: [
|
|
1245
|
+
/* @__PURE__ */ jsx4(CardTitle, { id: titleId, children: copy.t("referencePicker.title") }),
|
|
1246
|
+
/* @__PURE__ */ jsx4(
|
|
1247
|
+
Button3,
|
|
1248
|
+
{
|
|
1249
|
+
"aria-label": copy.t("actions.cancel"),
|
|
1250
|
+
size: "icon-sm",
|
|
1251
|
+
type: "button",
|
|
1252
|
+
variant: "ghost",
|
|
1253
|
+
onClick: onClose,
|
|
1254
|
+
children: /* @__PURE__ */ jsx4(CloseIcon, { size: 16 })
|
|
1255
|
+
}
|
|
1256
|
+
)
|
|
1257
|
+
] }) }),
|
|
1258
|
+
/* @__PURE__ */ jsx4(CardContent, { className: "flex min-h-0 flex-1 overflow-hidden border-t border-[var(--line-1)] p-0", children: /* @__PURE__ */ jsx4("div", { ref: layoutRef, className: "flex min-h-0 min-w-0 flex-1", children: /* @__PURE__ */ jsxs4(
|
|
1259
|
+
ResizablePanelGroup,
|
|
1260
|
+
{
|
|
1261
|
+
className: "min-h-0 min-w-0 flex-1",
|
|
1262
|
+
orientation: "horizontal",
|
|
1263
|
+
defaultLayout: { sidebar: 2.5, middle: 4.5, preview: 3 },
|
|
1264
|
+
children: [
|
|
1265
|
+
/* @__PURE__ */ jsx4(
|
|
1266
|
+
ResizablePanel,
|
|
1267
|
+
{
|
|
1268
|
+
id: "sidebar",
|
|
1269
|
+
className: "min-h-0 min-w-0",
|
|
1270
|
+
defaultSize: 15,
|
|
1271
|
+
minSize: "150px",
|
|
1272
|
+
panelRef: (handle) => {
|
|
1273
|
+
sidebarPanelRef.current = handle;
|
|
1274
|
+
},
|
|
1275
|
+
children: /* @__PURE__ */ jsx4(
|
|
1276
|
+
SourceSidebar,
|
|
1277
|
+
{
|
|
1278
|
+
contentRef: sidebarContentRef,
|
|
1279
|
+
copy,
|
|
1280
|
+
view
|
|
1281
|
+
}
|
|
1282
|
+
)
|
|
1283
|
+
}
|
|
1284
|
+
),
|
|
1285
|
+
/* @__PURE__ */ jsx4(
|
|
1286
|
+
ResizableHandle,
|
|
1287
|
+
{
|
|
1288
|
+
disableDoubleClick: true,
|
|
1289
|
+
withHandle: true,
|
|
1290
|
+
className: "after:bg-[var(--line-1)]",
|
|
1291
|
+
onDoubleClick: fitSidebar
|
|
1292
|
+
}
|
|
1293
|
+
),
|
|
1294
|
+
/* @__PURE__ */ jsx4(
|
|
1295
|
+
ResizablePanel,
|
|
1296
|
+
{
|
|
1297
|
+
id: "middle",
|
|
1298
|
+
className: "min-h-0 min-w-0",
|
|
1299
|
+
defaultSize: 50,
|
|
1300
|
+
minSize: "260px",
|
|
1301
|
+
panelRef: (handle) => {
|
|
1302
|
+
middlePanelRef.current = handle;
|
|
1303
|
+
},
|
|
1304
|
+
children: /* @__PURE__ */ jsxs4("div", { className: "flex h-full min-h-0 flex-col", children: [
|
|
1305
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2 border-b border-[var(--line-1)] p-3", children: [
|
|
1306
|
+
/* @__PURE__ */ jsxs4("div", { className: "relative flex-1", children: [
|
|
1307
|
+
/* @__PURE__ */ jsx4(SearchIcon2, { className: "pointer-events-none absolute top-1/2 left-3 size-4 -translate-y-1/2 text-[var(--text-tertiary)]" }),
|
|
1308
|
+
/* @__PURE__ */ jsx4(
|
|
1309
|
+
Input2,
|
|
1310
|
+
{
|
|
1311
|
+
className: "pl-9",
|
|
1312
|
+
placeholder: copy.t(
|
|
1313
|
+
"referencePicker.searchPlaceholder"
|
|
1314
|
+
),
|
|
1315
|
+
value: view.searchQuery,
|
|
1316
|
+
onChange: (event) => view.setSearchQuery(event.target.value)
|
|
1317
|
+
}
|
|
1318
|
+
)
|
|
1319
|
+
] }),
|
|
1320
|
+
view.capabilities?.filterable && view.filterCategories.length > 0 ? /* @__PURE__ */ jsx4(
|
|
1321
|
+
FilterCategoryFilter,
|
|
1322
|
+
{
|
|
1323
|
+
categories: view.filterCategories,
|
|
1324
|
+
copy,
|
|
1325
|
+
selected: activeFilterSet,
|
|
1326
|
+
onClear: clearFilters,
|
|
1327
|
+
onToggle: toggleFilter
|
|
1328
|
+
}
|
|
1329
|
+
) : null
|
|
1330
|
+
] }),
|
|
1331
|
+
/* @__PURE__ */ jsx4(
|
|
1332
|
+
ScrollArea2,
|
|
1333
|
+
{
|
|
1334
|
+
className: "min-h-0 flex-1",
|
|
1335
|
+
viewportProps: {
|
|
1336
|
+
// 拉到底部(距底 <120px)自动加载更多 —— 查询态走增长式分页,
|
|
1337
|
+
// 浏览态走 cursor 续页。已在加载/无更多时由 loadMore 内部 no-op。
|
|
1338
|
+
onScroll: (event) => {
|
|
1339
|
+
const el = event.currentTarget;
|
|
1340
|
+
if (view.hasMore && !view.isLoading && !view.isLoadingMore && el.scrollHeight - el.scrollTop - el.clientHeight < 120) {
|
|
1341
|
+
view.loadMore();
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
},
|
|
1345
|
+
children: /* @__PURE__ */ jsxs4(
|
|
1346
|
+
"div",
|
|
1347
|
+
{
|
|
1348
|
+
ref: middleContentRef,
|
|
1349
|
+
className: "flex flex-col gap-[2px] p-3",
|
|
1350
|
+
children: [
|
|
1351
|
+
view.isLoading ? /* @__PURE__ */ jsx4(Feedback, { children: /* @__PURE__ */ jsx4(Spinner2, { size: 16 }) }) : view.isQuery ? (
|
|
1352
|
+
// 查询态(关键词或筛选):扁平结果
|
|
1353
|
+
view.searchResults.length === 0 ? /* @__PURE__ */ jsx4(Feedback, { children: copy.t("referencePicker.emptySearch") }) : view.searchResults.map((node) => /* @__PURE__ */ jsx4(
|
|
1354
|
+
SearchResultRow,
|
|
1355
|
+
{
|
|
1356
|
+
focused: isFocused(view.focusedNode, node),
|
|
1357
|
+
node,
|
|
1358
|
+
selected: view.isSelected(node),
|
|
1359
|
+
onFocus: view.setFocusedNode,
|
|
1360
|
+
onToggle: view.toggleSelection
|
|
1361
|
+
},
|
|
1362
|
+
nodeRefKey(node.ref)
|
|
1363
|
+
))
|
|
1364
|
+
) : !hasSelectedGroup ? /* @__PURE__ */ jsx4(Feedback, { children: copy.t("referencePicker.selectGroupHint") }) : view.currentEntries.length === 0 ? /* @__PURE__ */ jsx4(Feedback, { children: copy.t("referencePicker.emptyDirectory") }) : (
|
|
1365
|
+
// 浏览:就地递归展开树(复刻 agent 引用面板文件树交互)
|
|
1366
|
+
view.currentEntries.map((node) => /* @__PURE__ */ jsx4(
|
|
1367
|
+
TreeNodeRow,
|
|
1368
|
+
{
|
|
1369
|
+
copy,
|
|
1370
|
+
depth: 0,
|
|
1371
|
+
node,
|
|
1372
|
+
view
|
|
1373
|
+
},
|
|
1374
|
+
nodeRefKey(node.ref)
|
|
1375
|
+
))
|
|
1376
|
+
),
|
|
1377
|
+
view.hasMore && (view.isQuery || hasSelectedGroup) ? /* @__PURE__ */ jsxs4(
|
|
1378
|
+
Button3,
|
|
1379
|
+
{
|
|
1380
|
+
className: "mt-1 w-full",
|
|
1381
|
+
disabled: view.isLoadingMore,
|
|
1382
|
+
size: "sm",
|
|
1383
|
+
type: "button",
|
|
1384
|
+
variant: "ghost",
|
|
1385
|
+
onClick: view.loadMore,
|
|
1386
|
+
children: [
|
|
1387
|
+
view.isLoadingMore ? /* @__PURE__ */ jsx4(Spinner2, { className: "text-current", size: 14 }) : null,
|
|
1388
|
+
copy.t("referencePicker.loadMore")
|
|
1389
|
+
]
|
|
1390
|
+
}
|
|
1391
|
+
) : null
|
|
1392
|
+
]
|
|
1393
|
+
}
|
|
1394
|
+
)
|
|
1395
|
+
}
|
|
1396
|
+
)
|
|
1397
|
+
] })
|
|
1398
|
+
}
|
|
1399
|
+
),
|
|
1400
|
+
/* @__PURE__ */ jsx4(
|
|
1401
|
+
ResizableHandle,
|
|
1402
|
+
{
|
|
1403
|
+
disableDoubleClick: true,
|
|
1404
|
+
withHandle: true,
|
|
1405
|
+
className: "after:bg-[var(--line-1)]",
|
|
1406
|
+
onDoubleClick: fitMiddle
|
|
1407
|
+
}
|
|
1408
|
+
),
|
|
1409
|
+
/* @__PURE__ */ jsx4(
|
|
1410
|
+
ResizablePanel,
|
|
1411
|
+
{
|
|
1412
|
+
id: "preview",
|
|
1413
|
+
className: "min-h-0 min-w-0",
|
|
1414
|
+
defaultSize: 30,
|
|
1415
|
+
minSize: "200px",
|
|
1416
|
+
children: /* @__PURE__ */ jsx4(
|
|
1417
|
+
PreviewInfoPane,
|
|
1418
|
+
{
|
|
1419
|
+
copy,
|
|
1420
|
+
node: view.focusedNode,
|
|
1421
|
+
previewState: view.previewState,
|
|
1422
|
+
sourceLabel: view.activeTabLabel,
|
|
1423
|
+
hierarchy: resolveHierarchyPath(view)
|
|
1424
|
+
}
|
|
1425
|
+
)
|
|
1426
|
+
}
|
|
1427
|
+
)
|
|
1428
|
+
]
|
|
1429
|
+
}
|
|
1430
|
+
) }) }),
|
|
1431
|
+
/* @__PURE__ */ jsx4(
|
|
1432
|
+
Footer,
|
|
1433
|
+
{
|
|
1434
|
+
cancelLabel: copy.t("actions.cancel"),
|
|
1435
|
+
confirmLabel: copy.t("referencePicker.confirm"),
|
|
1436
|
+
countLabel: copy.t("referencePicker.selectedCount", {
|
|
1437
|
+
count: view.selectionCount
|
|
1438
|
+
}),
|
|
1439
|
+
disabled: view.selectionCount === 0,
|
|
1440
|
+
loading: view.isConfirming,
|
|
1441
|
+
selection: view.selection,
|
|
1442
|
+
onClose,
|
|
1443
|
+
onConfirm: () => void view.confirm()
|
|
1444
|
+
}
|
|
1445
|
+
)
|
|
1446
|
+
]
|
|
1447
|
+
}
|
|
1448
|
+
)
|
|
1449
|
+
}
|
|
1450
|
+
);
|
|
668
1451
|
if (typeof document === "undefined") {
|
|
669
1452
|
return dialog;
|
|
670
1453
|
}
|
|
671
1454
|
return createPortal(dialog, document.body);
|
|
672
1455
|
}
|
|
1456
|
+
function GroupFallbackIcon({
|
|
1457
|
+
icon,
|
|
1458
|
+
className
|
|
1459
|
+
}) {
|
|
1460
|
+
if (icon === "issue") {
|
|
1461
|
+
return /* @__PURE__ */ jsx4(IssueIcon, { className });
|
|
1462
|
+
}
|
|
1463
|
+
return /* @__PURE__ */ jsx4(FolderFilledIcon3, { className });
|
|
1464
|
+
}
|
|
1465
|
+
function SourceSidebar({
|
|
1466
|
+
copy,
|
|
1467
|
+
view,
|
|
1468
|
+
contentRef
|
|
1469
|
+
}) {
|
|
1470
|
+
const selectedGroupRef = useRef2(null);
|
|
1471
|
+
useEffect3(() => {
|
|
1472
|
+
selectedGroupRef.current?.scrollIntoView({ block: "nearest" });
|
|
1473
|
+
}, [view.selectedGroupKey]);
|
|
1474
|
+
const [shownBySource, setShownBySource] = useState3(
|
|
1475
|
+
{}
|
|
1476
|
+
);
|
|
1477
|
+
const loadMoreGroups = (sourceId) => {
|
|
1478
|
+
const groups = view.sidebarGroupsBySource[sourceId] ?? [];
|
|
1479
|
+
const limit = shownBySource[sourceId] ?? SIDEBAR_GROUP_PAGE_SIZE;
|
|
1480
|
+
const selectedIndex = groups.findIndex(
|
|
1481
|
+
(group) => nodeRefKey(group.ref) === view.selectedGroupKey
|
|
1482
|
+
);
|
|
1483
|
+
const visibleCount = selectedIndex >= limit ? selectedIndex + 1 : limit;
|
|
1484
|
+
const next = visibleCount + SIDEBAR_GROUP_PAGE_SIZE;
|
|
1485
|
+
setShownBySource((prev) => ({ ...prev, [sourceId]: next }));
|
|
1486
|
+
if (next > groups.length && (view.sidebarHasMoreBySource[sourceId] ?? false)) {
|
|
1487
|
+
view.loadMoreSidebarGroups(sourceId);
|
|
1488
|
+
}
|
|
1489
|
+
};
|
|
1490
|
+
return /* @__PURE__ */ jsx4(ScrollArea2, { className: "h-full min-h-0 w-full", children: /* @__PURE__ */ jsx4("div", { ref: contentRef, className: "flex flex-col gap-1 p-2", children: view.tabs.map((tab) => {
|
|
1491
|
+
const groups = view.sidebarGroupsBySource[tab.sourceId] ?? [];
|
|
1492
|
+
const limit = shownBySource[tab.sourceId] ?? SIDEBAR_GROUP_PAGE_SIZE;
|
|
1493
|
+
const selectedIndex = groups.findIndex(
|
|
1494
|
+
(group) => nodeRefKey(group.ref) === view.selectedGroupKey
|
|
1495
|
+
);
|
|
1496
|
+
const effectiveLimit = selectedIndex >= limit ? selectedIndex + 1 : limit;
|
|
1497
|
+
const visibleGroups = groups.slice(0, effectiveLimit);
|
|
1498
|
+
const loadingMore = view.sidebarLoadingMoreBySource[tab.sourceId] ?? false;
|
|
1499
|
+
const hasMore = groups.length > effectiveLimit || (view.sidebarHasMoreBySource[tab.sourceId] ?? false);
|
|
1500
|
+
return /* @__PURE__ */ jsxs4("div", { className: "flex flex-col gap-0.5", children: [
|
|
1501
|
+
/* @__PURE__ */ jsx4(
|
|
1502
|
+
"p",
|
|
1503
|
+
{
|
|
1504
|
+
className: "px-2 pt-1.5 pb-0.5 text-[11px] font-semibold text-[var(--text-tertiary)]",
|
|
1505
|
+
"data-autofit-label": true,
|
|
1506
|
+
children: tab.label
|
|
1507
|
+
}
|
|
1508
|
+
),
|
|
1509
|
+
groups.length === 0 ? view.isLoadingTabs ? /* @__PURE__ */ jsx4("p", { className: "px-2 py-1 text-[12px] text-[var(--text-tertiary)]", children: "\u2026" }) : null : visibleGroups.map((group) => {
|
|
1510
|
+
const key = nodeRefKey(group.ref);
|
|
1511
|
+
const selected = key === view.selectedGroupKey;
|
|
1512
|
+
return /* @__PURE__ */ jsxs4(
|
|
1513
|
+
"button",
|
|
1514
|
+
{
|
|
1515
|
+
ref: selected ? selectedGroupRef : void 0,
|
|
1516
|
+
"aria-current": selected ? "true" : void 0,
|
|
1517
|
+
className: cn3(
|
|
1518
|
+
"flex items-center gap-2 rounded-[6px] px-2 py-1.5 text-left text-[13px] transition-colors",
|
|
1519
|
+
selected ? "bg-primary/10 font-medium text-[var(--text-primary)] hover:bg-primary/15" : "text-[var(--text-secondary)] hover:bg-[var(--transparency-hover)] hover:text-[var(--text-primary)]"
|
|
1520
|
+
),
|
|
1521
|
+
type: "button",
|
|
1522
|
+
onClick: () => view.selectGroup(group),
|
|
1523
|
+
children: [
|
|
1524
|
+
group.iconUrl ? /* @__PURE__ */ jsx4(
|
|
1525
|
+
"img",
|
|
1526
|
+
{
|
|
1527
|
+
alt: "",
|
|
1528
|
+
className: "size-4 shrink-0 rounded-[3px] object-cover",
|
|
1529
|
+
src: group.iconUrl
|
|
1530
|
+
}
|
|
1531
|
+
) : /* @__PURE__ */ jsx4(
|
|
1532
|
+
GroupFallbackIcon,
|
|
1533
|
+
{
|
|
1534
|
+
className: "size-4 shrink-0 text-[var(--rich-text-folder)]",
|
|
1535
|
+
icon: tab.icon
|
|
1536
|
+
}
|
|
1537
|
+
),
|
|
1538
|
+
/* @__PURE__ */ jsx4(
|
|
1539
|
+
"span",
|
|
1540
|
+
{
|
|
1541
|
+
className: "min-w-0 flex-1 truncate",
|
|
1542
|
+
"data-autofit-label": true,
|
|
1543
|
+
children: group.displayName
|
|
1544
|
+
}
|
|
1545
|
+
),
|
|
1546
|
+
group.childCount != null ? /* @__PURE__ */ jsx4("span", { className: "shrink-0 text-[11px] text-[var(--text-tertiary)]", children: group.childCount }) : null
|
|
1547
|
+
]
|
|
1548
|
+
},
|
|
1549
|
+
key
|
|
1550
|
+
);
|
|
1551
|
+
}),
|
|
1552
|
+
hasMore ? /* @__PURE__ */ jsxs4(
|
|
1553
|
+
"button",
|
|
1554
|
+
{
|
|
1555
|
+
className: "flex items-center gap-1.5 rounded-[6px] px-2 py-1.5 text-left text-[12px] text-[var(--text-secondary)] transition-colors hover:bg-[var(--transparency-hover)] hover:text-[var(--text-primary)] disabled:opacity-60",
|
|
1556
|
+
disabled: loadingMore,
|
|
1557
|
+
type: "button",
|
|
1558
|
+
onClick: () => loadMoreGroups(tab.sourceId),
|
|
1559
|
+
children: [
|
|
1560
|
+
loadingMore ? /* @__PURE__ */ jsx4(
|
|
1561
|
+
Spinner2,
|
|
1562
|
+
{
|
|
1563
|
+
className: "text-[var(--text-secondary)]",
|
|
1564
|
+
size: 12
|
|
1565
|
+
}
|
|
1566
|
+
) : null,
|
|
1567
|
+
/* @__PURE__ */ jsx4("span", { children: copy.t("referencePicker.loadMoreGroups") })
|
|
1568
|
+
]
|
|
1569
|
+
}
|
|
1570
|
+
) : null
|
|
1571
|
+
] }, tab.sourceId);
|
|
1572
|
+
}) }) });
|
|
1573
|
+
}
|
|
1574
|
+
function SearchResultRow({
|
|
1575
|
+
node,
|
|
1576
|
+
focused,
|
|
1577
|
+
selected,
|
|
1578
|
+
onFocus,
|
|
1579
|
+
onToggle
|
|
1580
|
+
}) {
|
|
1581
|
+
const isFolder = node.kind === "folder";
|
|
1582
|
+
return /* @__PURE__ */ jsxs4(
|
|
1583
|
+
"div",
|
|
1584
|
+
{
|
|
1585
|
+
className: cn3(
|
|
1586
|
+
"grid cursor-pointer grid-cols-[minmax(0,1fr)_auto] items-center gap-3 rounded-[6px] border py-2.5 pr-1 pl-3 transition-colors",
|
|
1587
|
+
focused || selected ? "border-border bg-transparency-block" : "border-transparent bg-transparent hover:border-border/70 hover:bg-transparency-block"
|
|
1588
|
+
),
|
|
1589
|
+
onClick: () => onFocus(node),
|
|
1590
|
+
children: [
|
|
1591
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex min-w-0 items-center gap-3 text-left", children: [
|
|
1592
|
+
/* @__PURE__ */ jsx4("span", { className: "grid size-9 shrink-0 place-items-center rounded-lg bg-[var(--transparency-block)] text-[var(--text-tertiary)]", children: isFolder ? /* @__PURE__ */ jsx4(FolderFilledIcon3, { className: "size-4 text-[var(--rich-text-folder)]" }) : /* @__PURE__ */ jsx4(FileIcon3, { className: "size-4 text-[var(--text-tertiary)]" }) }),
|
|
1593
|
+
/* @__PURE__ */ jsxs4("span", { className: "min-w-0", children: [
|
|
1594
|
+
/* @__PURE__ */ jsx4("span", { className: "block truncate text-[13px] font-medium text-[var(--text-primary)]", children: node.displayName }),
|
|
1595
|
+
/* @__PURE__ */ jsx4("span", { className: "block truncate text-[11px] text-[var(--text-secondary)]", children: node.contextLabel ?? node.ref.nodeId })
|
|
1596
|
+
] })
|
|
1597
|
+
] }),
|
|
1598
|
+
/* @__PURE__ */ jsx4(
|
|
1599
|
+
Button3,
|
|
1600
|
+
{
|
|
1601
|
+
"aria-label": node.displayName,
|
|
1602
|
+
"aria-pressed": selected,
|
|
1603
|
+
size: "icon-sm",
|
|
1604
|
+
type: "button",
|
|
1605
|
+
variant: "ghost",
|
|
1606
|
+
onClick: (event) => {
|
|
1607
|
+
event.stopPropagation();
|
|
1608
|
+
onFocus(node);
|
|
1609
|
+
onToggle(node);
|
|
1610
|
+
},
|
|
1611
|
+
children: selected ? /* @__PURE__ */ jsx4(CheckIcon2, { size: 14 }) : /* @__PURE__ */ jsx4(AddIcon, { className: "text-[var(--text-secondary)]", size: 16 })
|
|
1612
|
+
}
|
|
1613
|
+
)
|
|
1614
|
+
]
|
|
1615
|
+
}
|
|
1616
|
+
);
|
|
1617
|
+
}
|
|
1618
|
+
function toPreviewSurfaceState(node, previewState, copy) {
|
|
1619
|
+
if (!("node" in previewState) || nodeRefKey(previewState.node.ref) !== nodeRefKey(node.ref)) {
|
|
1620
|
+
return node.kind === "folder" ? { entry: node, status: "directory" } : { entry: node, status: "loading" };
|
|
1621
|
+
}
|
|
1622
|
+
switch (previewState.status) {
|
|
1623
|
+
case "directory":
|
|
1624
|
+
return { entry: node, status: "directory" };
|
|
1625
|
+
case "loading":
|
|
1626
|
+
return { entry: node, status: "loading" };
|
|
1627
|
+
case "image":
|
|
1628
|
+
return {
|
|
1629
|
+
entry: node,
|
|
1630
|
+
objectUrl: previewState.objectUrl,
|
|
1631
|
+
status: "image"
|
|
1632
|
+
};
|
|
1633
|
+
case "text":
|
|
1634
|
+
return { content: previewState.content, entry: node, status: "text" };
|
|
1635
|
+
case "readonly":
|
|
1636
|
+
return {
|
|
1637
|
+
entry: node,
|
|
1638
|
+
message: previewState.reason === "binary" ? copy.t("referencePicker.previewBinary") : previewState.reason === "file_too_large" || previewState.reason === "text_too_large" ? copy.t("referencePicker.previewTooLarge") : copy.t("referencePicker.previewUnsupported"),
|
|
1639
|
+
status: "readonly"
|
|
1640
|
+
};
|
|
1641
|
+
case "error":
|
|
1642
|
+
return {
|
|
1643
|
+
entry: node,
|
|
1644
|
+
message: copy.t("referencePicker.previewError"),
|
|
1645
|
+
status: "error"
|
|
1646
|
+
};
|
|
1647
|
+
case "unsupported":
|
|
1648
|
+
return {
|
|
1649
|
+
entry: node,
|
|
1650
|
+
message: copy.t("referencePicker.previewUnsupported"),
|
|
1651
|
+
status: "unsupported"
|
|
1652
|
+
};
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
function sourceBadgeClassName(sourceId) {
|
|
1656
|
+
return SOURCE_BADGE_CLASSES[sourceId];
|
|
1657
|
+
}
|
|
1658
|
+
var SOURCE_BADGE_CLASSES = {
|
|
1659
|
+
"workspace-file": "bg-[color-mix(in_srgb,var(--rich-text-mention-file)_12%,transparent)] text-[var(--rich-text-mention-file)]",
|
|
1660
|
+
"app-artifact": "bg-[color-mix(in_srgb,var(--rich-text-mention-app)_12%,transparent)] text-[var(--rich-text-mention-app)]",
|
|
1661
|
+
"issue-file": "bg-[color-mix(in_srgb,var(--rich-text-mention-issue)_12%,transparent)] text-[var(--rich-text-mention-issue)]"
|
|
1662
|
+
};
|
|
1663
|
+
function PreviewInfoPane({
|
|
1664
|
+
copy,
|
|
1665
|
+
node,
|
|
1666
|
+
previewState,
|
|
1667
|
+
sourceLabel,
|
|
1668
|
+
hierarchy
|
|
1669
|
+
}) {
|
|
1670
|
+
return /* @__PURE__ */ jsx4("aside", { className: "flex h-full min-h-0 w-full flex-col bg-[var(--background-fronted)]", children: node ? /* @__PURE__ */ jsxs4("div", { className: "flex min-h-0 flex-1 flex-col gap-4 p-4", children: [
|
|
1671
|
+
/* @__PURE__ */ jsx4(
|
|
1672
|
+
WorkspaceFilePreviewSurface,
|
|
1673
|
+
{
|
|
1674
|
+
directoryMessage: copy.t("referencePicker.previewFolder"),
|
|
1675
|
+
emptyMessage: copy.t("referencePicker.emptyPreview"),
|
|
1676
|
+
frameClassName: "flex aspect-[3/2] w-full flex-col items-center justify-center overflow-hidden rounded-[8px] border border-[var(--line-2,var(--border-2))] bg-[var(--transparency-block)] p-0 text-center",
|
|
1677
|
+
imageAlt: (entry) => entry.displayName,
|
|
1678
|
+
imageFrameClassName: "p-3",
|
|
1679
|
+
loadingIndicator: /* @__PURE__ */ jsx4(Spinner2, { size: 16 }),
|
|
1680
|
+
loadingMessage: copy.t("referencePicker.previewLoading"),
|
|
1681
|
+
messageClassName: "mx-auto max-w-[24ch] text-[13px] leading-5 text-[var(--text-secondary)] [overflow-wrap:anywhere]",
|
|
1682
|
+
renderIcon: (entry) => entry.kind === "folder" ? /* @__PURE__ */ jsx4(FolderFilledIcon3, { className: "size-9 text-[var(--rich-text-folder)]" }) : /* @__PURE__ */ jsx4(FileIcon3, { className: "size-9 text-[var(--text-tertiary)]" }),
|
|
1683
|
+
state: toPreviewSurfaceState(node, previewState, copy),
|
|
1684
|
+
textClassName: "h-full w-full overflow-auto p-3 text-left text-[11px] leading-5 whitespace-pre-wrap break-words text-[var(--text-primary)]",
|
|
1685
|
+
textFrameClassName: "items-stretch justify-stretch"
|
|
1686
|
+
}
|
|
1687
|
+
),
|
|
1688
|
+
/* @__PURE__ */ jsx4("p", { className: "truncate text-[15px] font-semibold", children: node.displayName }),
|
|
1689
|
+
/* @__PURE__ */ jsxs4("dl", { className: "space-y-2 text-[13px]", children: [
|
|
1690
|
+
/* @__PURE__ */ jsx4(InfoRow, { label: copy.t("referencePicker.previewSource"), children: /* @__PURE__ */ jsx4(
|
|
1691
|
+
Badge2,
|
|
1692
|
+
{
|
|
1693
|
+
variant: "secondary",
|
|
1694
|
+
className: sourceBadgeClassName(node.ref.sourceId),
|
|
1695
|
+
children: sourceLabel
|
|
1696
|
+
}
|
|
1697
|
+
) }),
|
|
1698
|
+
node.mtimeMs != null ? /* @__PURE__ */ jsx4(InfoRow, { label: copy.t("referencePicker.previewModified"), children: formatDateTime(node.mtimeMs) }) : null,
|
|
1699
|
+
node.sizeBytes != null ? /* @__PURE__ */ jsx4(InfoRow, { label: copy.t("referencePicker.previewSize"), children: formatBytes(node.sizeBytes) }) : null
|
|
1700
|
+
] }),
|
|
1701
|
+
hierarchy.length > 0 ? /* @__PURE__ */ jsxs4("div", { className: "space-y-1.5", children: [
|
|
1702
|
+
/* @__PURE__ */ jsx4("p", { className: "text-[11px] font-semibold text-[var(--text-tertiary)]", children: copy.t("referencePicker.previewHierarchy") }),
|
|
1703
|
+
/* @__PURE__ */ jsx4("div", { className: "flex flex-wrap items-center gap-y-1 text-[12px] leading-5", children: hierarchy.map((crumb, index) => /* @__PURE__ */ jsxs4(
|
|
1704
|
+
"span",
|
|
1705
|
+
{
|
|
1706
|
+
className: "flex min-w-0 items-center",
|
|
1707
|
+
children: [
|
|
1708
|
+
index > 0 ? /* @__PURE__ */ jsx4("span", { className: "mx-1 shrink-0 text-[var(--text-tertiary)]", children: "/" }) : null,
|
|
1709
|
+
/* @__PURE__ */ jsx4(
|
|
1710
|
+
"span",
|
|
1711
|
+
{
|
|
1712
|
+
className: "max-w-[14ch] truncate text-[var(--text-secondary)]",
|
|
1713
|
+
title: crumb.displayName,
|
|
1714
|
+
children: crumb.displayName
|
|
1715
|
+
}
|
|
1716
|
+
)
|
|
1717
|
+
]
|
|
1718
|
+
},
|
|
1719
|
+
nodeRefKey(crumb.ref)
|
|
1720
|
+
)) })
|
|
1721
|
+
] }) : null
|
|
1722
|
+
] }) : /* @__PURE__ */ jsx4(Feedback, { children: copy.t("referencePicker.emptyPreview") }) });
|
|
1723
|
+
}
|
|
1724
|
+
function InfoRow({
|
|
1725
|
+
label,
|
|
1726
|
+
children
|
|
1727
|
+
}) {
|
|
1728
|
+
return /* @__PURE__ */ jsxs4("div", { className: "flex items-center justify-between gap-3", children: [
|
|
1729
|
+
/* @__PURE__ */ jsx4("dt", { className: "text-[var(--text-secondary)]", children: label }),
|
|
1730
|
+
/* @__PURE__ */ jsx4("dd", { className: "min-w-0 truncate text-right text-[var(--text-primary)]", children })
|
|
1731
|
+
] });
|
|
1732
|
+
}
|
|
1733
|
+
function Footer({
|
|
1734
|
+
cancelLabel,
|
|
1735
|
+
confirmLabel,
|
|
1736
|
+
countLabel,
|
|
1737
|
+
disabled,
|
|
1738
|
+
loading = false,
|
|
1739
|
+
selection,
|
|
1740
|
+
onClose,
|
|
1741
|
+
onConfirm
|
|
1742
|
+
}) {
|
|
1743
|
+
return /* @__PURE__ */ jsxs4("div", { className: "flex items-center justify-between gap-3 border-t border-[var(--line-1)] px-4 py-3 sm:px-6", children: [
|
|
1744
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex min-w-0 items-center gap-2", children: [
|
|
1745
|
+
/* @__PURE__ */ jsx4("span", { className: "shrink-0 text-[13px] text-[var(--text-secondary)]", children: countLabel }),
|
|
1746
|
+
selection.slice(0, 2).map((node) => /* @__PURE__ */ jsx4(
|
|
1747
|
+
Badge2,
|
|
1748
|
+
{
|
|
1749
|
+
className: "min-w-0 max-w-[12rem]",
|
|
1750
|
+
variant: "secondary",
|
|
1751
|
+
children: /* @__PURE__ */ jsx4("span", { className: "truncate", children: node.displayName })
|
|
1752
|
+
},
|
|
1753
|
+
nodeRefKey(node.ref)
|
|
1754
|
+
)),
|
|
1755
|
+
selection.length > 2 ? /* @__PURE__ */ jsxs4(Badge2, { className: "shrink-0", variant: "secondary", children: [
|
|
1756
|
+
"+",
|
|
1757
|
+
selection.length - 2
|
|
1758
|
+
] }) : null
|
|
1759
|
+
] }),
|
|
1760
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2", children: [
|
|
1761
|
+
/* @__PURE__ */ jsx4(Button3, { type: "button", variant: "secondary", onClick: onClose, children: cancelLabel }),
|
|
1762
|
+
/* @__PURE__ */ jsxs4(
|
|
1763
|
+
Button3,
|
|
1764
|
+
{
|
|
1765
|
+
disabled: disabled || loading,
|
|
1766
|
+
type: "button",
|
|
1767
|
+
onClick: onConfirm,
|
|
1768
|
+
children: [
|
|
1769
|
+
loading ? /* @__PURE__ */ jsx4(Spinner2, { className: "text-current", size: 14 }) : null,
|
|
1770
|
+
confirmLabel
|
|
1771
|
+
]
|
|
1772
|
+
}
|
|
1773
|
+
)
|
|
1774
|
+
] })
|
|
1775
|
+
] });
|
|
1776
|
+
}
|
|
1777
|
+
function Feedback({ children }) {
|
|
1778
|
+
return /* @__PURE__ */ jsx4("div", { className: "grid min-h-0 flex-1 place-items-center px-4 py-8 text-center text-[13px] text-[var(--text-secondary)]", children });
|
|
1779
|
+
}
|
|
1780
|
+
function FilterCategoryFilter({
|
|
1781
|
+
categories,
|
|
1782
|
+
copy,
|
|
1783
|
+
selected,
|
|
1784
|
+
onClear,
|
|
1785
|
+
onToggle
|
|
1786
|
+
}) {
|
|
1787
|
+
const [open, setOpen] = useState3(false);
|
|
1788
|
+
const containerRef = useRef2(null);
|
|
1789
|
+
useEffect3(() => {
|
|
1790
|
+
if (!open) {
|
|
1791
|
+
return;
|
|
1792
|
+
}
|
|
1793
|
+
const onPointerDown = (event) => {
|
|
1794
|
+
if (containerRef.current && !containerRef.current.contains(event.target)) {
|
|
1795
|
+
setOpen(false);
|
|
1796
|
+
}
|
|
1797
|
+
};
|
|
1798
|
+
const onKeyDown = (event) => {
|
|
1799
|
+
if (event.key === "Escape") {
|
|
1800
|
+
setOpen(false);
|
|
1801
|
+
}
|
|
1802
|
+
};
|
|
1803
|
+
document.addEventListener("pointerdown", onPointerDown, true);
|
|
1804
|
+
document.addEventListener("keydown", onKeyDown);
|
|
1805
|
+
return () => {
|
|
1806
|
+
document.removeEventListener("pointerdown", onPointerDown, true);
|
|
1807
|
+
document.removeEventListener("keydown", onKeyDown);
|
|
1808
|
+
};
|
|
1809
|
+
}, [open]);
|
|
1810
|
+
const selectedLabels = categories.filter((category) => selected.has(category.id)).map((category) => copy.t(category.labelKey));
|
|
1811
|
+
const count = selectedLabels.length;
|
|
1812
|
+
const labelText = count > 0 ? selectedLabels.join(copy.t("referencePicker.fileTypeSeparator")) : copy.t("referencePicker.fileTypeAll");
|
|
1813
|
+
return (
|
|
1814
|
+
// shrink-0:不被搜索框挤压;固定宽度让控件不随选中文案变宽。
|
|
1815
|
+
/* @__PURE__ */ jsxs4("div", { ref: containerRef, className: "relative w-[124px] shrink-0", children: [
|
|
1816
|
+
/* @__PURE__ */ jsxs4(
|
|
1817
|
+
Button3,
|
|
1818
|
+
{
|
|
1819
|
+
"aria-expanded": open,
|
|
1820
|
+
"aria-haspopup": "menu",
|
|
1821
|
+
className: "h-8 w-full justify-between gap-1.5 border-0 px-2.5",
|
|
1822
|
+
size: "default",
|
|
1823
|
+
type: "button",
|
|
1824
|
+
variant: "secondary",
|
|
1825
|
+
onClick: () => setOpen((current) => !current),
|
|
1826
|
+
children: [
|
|
1827
|
+
/* @__PURE__ */ jsx4("span", { className: "min-w-0 flex-1 truncate text-left", title: labelText, children: labelText }),
|
|
1828
|
+
count > 1 ? /* @__PURE__ */ jsx4(Badge2, { className: "shrink-0 px-1.5", variant: "secondary", children: count }) : null,
|
|
1829
|
+
count > 0 ? (
|
|
1830
|
+
// 有选中时,右侧箭头位让位给「清除」:role=button 的 span(避免 button 嵌
|
|
1831
|
+
// button 的非法结构),stopPropagation 让点击只清空筛选、不触发触发器的开合。
|
|
1832
|
+
/* @__PURE__ */ jsx4(
|
|
1833
|
+
"span",
|
|
1834
|
+
{
|
|
1835
|
+
"aria-label": copy.t("referencePicker.clearFilter"),
|
|
1836
|
+
className: "grid size-4 shrink-0 cursor-pointer place-items-center rounded-full text-[var(--text-tertiary)] transition-colors hover:bg-[var(--transparency-hover)] hover:text-[var(--text-primary)]",
|
|
1837
|
+
role: "button",
|
|
1838
|
+
tabIndex: 0,
|
|
1839
|
+
onClick: (event) => {
|
|
1840
|
+
event.stopPropagation();
|
|
1841
|
+
onClear();
|
|
1842
|
+
},
|
|
1843
|
+
onKeyDown: (event) => {
|
|
1844
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
1845
|
+
event.preventDefault();
|
|
1846
|
+
event.stopPropagation();
|
|
1847
|
+
onClear();
|
|
1848
|
+
}
|
|
1849
|
+
},
|
|
1850
|
+
children: /* @__PURE__ */ jsx4(CloseIcon, { size: 12 })
|
|
1851
|
+
}
|
|
1852
|
+
)
|
|
1853
|
+
) : /* @__PURE__ */ jsx4(ChevronDownIcon, { className: "size-3.5 shrink-0 text-[var(--text-tertiary)]" })
|
|
1854
|
+
]
|
|
1855
|
+
}
|
|
1856
|
+
),
|
|
1857
|
+
open ? /* @__PURE__ */ jsx4(
|
|
1858
|
+
"div",
|
|
1859
|
+
{
|
|
1860
|
+
className: "absolute top-[calc(100%+4px)] right-0 min-w-40 overflow-hidden rounded-[8px] border border-[var(--line-1)] bg-[var(--background-fronted)] p-1 shadow-panel",
|
|
1861
|
+
role: "menu",
|
|
1862
|
+
style: { zIndex: "var(--z-panel-popover)" },
|
|
1863
|
+
children: categories.map((category) => {
|
|
1864
|
+
const checked = selected.has(category.id);
|
|
1865
|
+
return /* @__PURE__ */ jsxs4(
|
|
1866
|
+
"button",
|
|
1867
|
+
{
|
|
1868
|
+
"aria-checked": checked,
|
|
1869
|
+
className: "flex w-full items-center gap-2 rounded-[6px] px-2 py-1.5 text-left text-[13px] text-[var(--text-primary)] transition-colors hover:bg-[var(--transparency-hover)]",
|
|
1870
|
+
role: "menuitemcheckbox",
|
|
1871
|
+
type: "button",
|
|
1872
|
+
onClick: () => onToggle(category.id),
|
|
1873
|
+
children: [
|
|
1874
|
+
/* @__PURE__ */ jsx4("span", { className: "grid size-4 shrink-0 place-items-center", children: checked ? /* @__PURE__ */ jsx4(CheckIcon2, { className: "size-3.5 text-[var(--tutti-purple)]" }) : null }),
|
|
1875
|
+
/* @__PURE__ */ jsx4("span", { className: "flex-1", children: copy.t(category.labelKey) })
|
|
1876
|
+
]
|
|
1877
|
+
},
|
|
1878
|
+
category.id
|
|
1879
|
+
);
|
|
1880
|
+
})
|
|
1881
|
+
}
|
|
1882
|
+
) : null
|
|
1883
|
+
] })
|
|
1884
|
+
);
|
|
1885
|
+
}
|
|
1886
|
+
var TREE_INDENT = 28;
|
|
1887
|
+
var TREE_COLLAPSE_DURATION_MS = 200;
|
|
1888
|
+
function isFocused(focused, node) {
|
|
1889
|
+
return focused ? nodeRefKey(focused.ref) === nodeRefKey(node.ref) : false;
|
|
1890
|
+
}
|
|
1891
|
+
function resolveHierarchyPath(view) {
|
|
1892
|
+
const focused = view.focusedNode;
|
|
1893
|
+
if (!focused) {
|
|
1894
|
+
return view.breadcrumb;
|
|
1895
|
+
}
|
|
1896
|
+
const trail = findFocusedTrail(
|
|
1897
|
+
view.currentEntries,
|
|
1898
|
+
view.childrenByKey,
|
|
1899
|
+
nodeRefKey(focused.ref),
|
|
1900
|
+
[]
|
|
1901
|
+
);
|
|
1902
|
+
return trail ? [...view.breadcrumb, ...trail] : view.breadcrumb;
|
|
1903
|
+
}
|
|
1904
|
+
function findFocusedTrail(roots, childrenByKey, targetKey, trail) {
|
|
1905
|
+
for (const node of roots) {
|
|
1906
|
+
if (nodeRefKey(node.ref) === targetKey) {
|
|
1907
|
+
return [...trail];
|
|
1908
|
+
}
|
|
1909
|
+
if (node.kind === "folder") {
|
|
1910
|
+
const children = childrenByKey[nodeRefKey(node.ref)]?.entries ?? [];
|
|
1911
|
+
if (children.length > 0) {
|
|
1912
|
+
const found = findFocusedTrail(children, childrenByKey, targetKey, [
|
|
1913
|
+
...trail,
|
|
1914
|
+
node
|
|
1915
|
+
]);
|
|
1916
|
+
if (found) {
|
|
1917
|
+
return found;
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
}
|
|
1922
|
+
return null;
|
|
1923
|
+
}
|
|
1924
|
+
function TreeNodeRow({
|
|
1925
|
+
node,
|
|
1926
|
+
depth,
|
|
1927
|
+
view,
|
|
1928
|
+
copy
|
|
1929
|
+
}) {
|
|
1930
|
+
const key = nodeRefKey(node.ref);
|
|
1931
|
+
const isFolder = node.kind === "folder";
|
|
1932
|
+
const expanded = view.expandedKeys[key] ?? false;
|
|
1933
|
+
const childState = view.childrenByKey[key];
|
|
1934
|
+
const childEntries = view.sortNodes(childState?.entries ?? []);
|
|
1935
|
+
const selected = view.isSelected(node);
|
|
1936
|
+
const focused = isFocused(view.focusedNode, node);
|
|
1937
|
+
const [shouldRenderChildContent, setShouldRenderChildContent] = useState3(expanded);
|
|
1938
|
+
useEffect3(() => {
|
|
1939
|
+
if (expanded) {
|
|
1940
|
+
setShouldRenderChildContent(true);
|
|
1941
|
+
return;
|
|
1942
|
+
}
|
|
1943
|
+
const timeoutId = window.setTimeout(() => {
|
|
1944
|
+
setShouldRenderChildContent(false);
|
|
1945
|
+
}, TREE_COLLAPSE_DURATION_MS);
|
|
1946
|
+
return () => window.clearTimeout(timeoutId);
|
|
1947
|
+
}, [expanded]);
|
|
1948
|
+
const shouldBuildChildContent = expanded || shouldRenderChildContent;
|
|
1949
|
+
const childContent = shouldBuildChildContent ? childState?.loading ? /* @__PURE__ */ jsxs4(
|
|
1950
|
+
"div",
|
|
1951
|
+
{
|
|
1952
|
+
className: "flex items-center gap-2 px-2 py-2 text-[11px] text-[var(--text-secondary)]",
|
|
1953
|
+
style: { paddingLeft: `${(depth + 1) * TREE_INDENT + 12}px` },
|
|
1954
|
+
children: [
|
|
1955
|
+
/* @__PURE__ */ jsx4(Spinner2, { className: "text-[var(--text-secondary)]", size: 14 }),
|
|
1956
|
+
/* @__PURE__ */ jsx4("span", { children: copy.t("referencePicker.loading") })
|
|
1957
|
+
]
|
|
1958
|
+
}
|
|
1959
|
+
) : childEntries.length > 0 ? /* @__PURE__ */ jsx4("div", { className: "space-y-0.5", children: childEntries.map((child) => /* @__PURE__ */ jsx4(
|
|
1960
|
+
TreeNodeRow,
|
|
1961
|
+
{
|
|
1962
|
+
copy,
|
|
1963
|
+
depth: depth + 1,
|
|
1964
|
+
node: child,
|
|
1965
|
+
view
|
|
1966
|
+
},
|
|
1967
|
+
nodeRefKey(child.ref)
|
|
1968
|
+
)) }) : childState?.loaded ? /* @__PURE__ */ jsx4(
|
|
1969
|
+
"div",
|
|
1970
|
+
{
|
|
1971
|
+
className: "px-2 py-2 text-[11px] text-[var(--text-secondary)]",
|
|
1972
|
+
style: { paddingLeft: `${(depth + 1) * TREE_INDENT + 12}px` },
|
|
1973
|
+
children: copy.t("referencePicker.emptyDirectory")
|
|
1974
|
+
}
|
|
1975
|
+
) : null : null;
|
|
1976
|
+
return /* @__PURE__ */ jsxs4("div", { children: [
|
|
1977
|
+
/* @__PURE__ */ jsxs4(
|
|
1978
|
+
"div",
|
|
1979
|
+
{
|
|
1980
|
+
className: cn3(
|
|
1981
|
+
"flex cursor-pointer items-center gap-2 rounded-[6px] py-1.5 pr-1 transition-colors",
|
|
1982
|
+
focused || selected ? "bg-transparency-block" : "hover:bg-transparency-block"
|
|
1983
|
+
),
|
|
1984
|
+
style: { paddingLeft: `${depth * TREE_INDENT + 8}px` },
|
|
1985
|
+
onClick: () => {
|
|
1986
|
+
view.setFocusedNode(node);
|
|
1987
|
+
if (isFolder) {
|
|
1988
|
+
view.toggleNode(node);
|
|
1989
|
+
}
|
|
1990
|
+
},
|
|
1991
|
+
children: [
|
|
1992
|
+
isFolder ? /* @__PURE__ */ jsx4(
|
|
1993
|
+
"button",
|
|
1994
|
+
{
|
|
1995
|
+
"aria-label": node.displayName,
|
|
1996
|
+
className: "grid size-5 shrink-0 place-items-center rounded-sm text-[var(--text-secondary)] hover:bg-[var(--transparency-hover)]",
|
|
1997
|
+
type: "button",
|
|
1998
|
+
onClick: (event) => {
|
|
1999
|
+
event.stopPropagation();
|
|
2000
|
+
view.setFocusedNode(node);
|
|
2001
|
+
view.toggleNode(node);
|
|
2002
|
+
},
|
|
2003
|
+
children: /* @__PURE__ */ jsx4(
|
|
2004
|
+
ArrowRightIcon2,
|
|
2005
|
+
{
|
|
2006
|
+
className: cn3(
|
|
2007
|
+
"size-3.5 transition-transform",
|
|
2008
|
+
expanded && "rotate-90"
|
|
2009
|
+
)
|
|
2010
|
+
}
|
|
2011
|
+
)
|
|
2012
|
+
}
|
|
2013
|
+
) : null,
|
|
2014
|
+
isFolder ? /* @__PURE__ */ jsx4(FolderFilledIcon3, { className: "size-4 shrink-0 text-[var(--rich-text-folder)]" }) : /* @__PURE__ */ jsx4(FileIcon3, { className: "size-4 shrink-0 text-[var(--text-tertiary)]" }),
|
|
2015
|
+
/* @__PURE__ */ jsx4(
|
|
2016
|
+
"span",
|
|
2017
|
+
{
|
|
2018
|
+
className: "min-w-0 flex-1 truncate text-[13px] text-[var(--text-primary)]",
|
|
2019
|
+
"data-autofit-label": true,
|
|
2020
|
+
children: node.displayName
|
|
2021
|
+
}
|
|
2022
|
+
),
|
|
2023
|
+
/* @__PURE__ */ jsx4(
|
|
2024
|
+
Button3,
|
|
2025
|
+
{
|
|
2026
|
+
"aria-label": node.displayName,
|
|
2027
|
+
"aria-pressed": selected,
|
|
2028
|
+
className: "shrink-0",
|
|
2029
|
+
size: "icon-sm",
|
|
2030
|
+
type: "button",
|
|
2031
|
+
variant: "ghost",
|
|
2032
|
+
onClick: (event) => {
|
|
2033
|
+
event.stopPropagation();
|
|
2034
|
+
view.setFocusedNode(node);
|
|
2035
|
+
view.toggleSelection(node);
|
|
2036
|
+
},
|
|
2037
|
+
children: selected ? /* @__PURE__ */ jsx4(CheckIcon2, { size: 14 }) : /* @__PURE__ */ jsx4(AddIcon, { className: "text-[var(--text-secondary)]", size: 16 })
|
|
2038
|
+
}
|
|
2039
|
+
)
|
|
2040
|
+
]
|
|
2041
|
+
}
|
|
2042
|
+
),
|
|
2043
|
+
isFolder ? /* @__PURE__ */ jsx4(
|
|
2044
|
+
"div",
|
|
2045
|
+
{
|
|
2046
|
+
className: cn3(
|
|
2047
|
+
"grid transition-[grid-template-rows] duration-200 ease-out motion-reduce:transition-none",
|
|
2048
|
+
expanded ? "grid-rows-[1fr]" : "grid-rows-[0fr]",
|
|
2049
|
+
childContent && "mt-[2px]"
|
|
2050
|
+
),
|
|
2051
|
+
children: /* @__PURE__ */ jsx4(
|
|
2052
|
+
"div",
|
|
2053
|
+
{
|
|
2054
|
+
"aria-hidden": expanded ? void 0 : "true",
|
|
2055
|
+
className: cn3(
|
|
2056
|
+
"min-h-0 overflow-hidden transition-[opacity,transform] duration-200 ease-out motion-reduce:transition-none",
|
|
2057
|
+
expanded ? "translate-y-0 opacity-100" : "-translate-y-1 opacity-0"
|
|
2058
|
+
),
|
|
2059
|
+
inert: expanded ? void 0 : true,
|
|
2060
|
+
children: childContent
|
|
2061
|
+
}
|
|
2062
|
+
)
|
|
2063
|
+
}
|
|
2064
|
+
) : null
|
|
2065
|
+
] });
|
|
2066
|
+
}
|
|
2067
|
+
function formatDateTime(ms) {
|
|
2068
|
+
const date = new Date(ms);
|
|
2069
|
+
const pad = (value) => String(value).padStart(2, "0");
|
|
2070
|
+
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
|
2071
|
+
}
|
|
2072
|
+
function formatBytes(bytes) {
|
|
2073
|
+
if (bytes < 1024) {
|
|
2074
|
+
return `${bytes} B`;
|
|
2075
|
+
}
|
|
2076
|
+
const units = ["KB", "MB", "GB"];
|
|
2077
|
+
let value = bytes / 1024;
|
|
2078
|
+
let unitIndex = 0;
|
|
2079
|
+
while (value >= 1024 && unitIndex < units.length - 1) {
|
|
2080
|
+
value /= 1024;
|
|
2081
|
+
unitIndex += 1;
|
|
2082
|
+
}
|
|
2083
|
+
return `${value.toFixed(1)} ${units[unitIndex]}`;
|
|
2084
|
+
}
|
|
673
2085
|
export {
|
|
2086
|
+
ReferenceSourcePicker,
|
|
674
2087
|
WorkspaceFileReferencePicker
|
|
675
2088
|
};
|
|
676
2089
|
//# sourceMappingURL=index.js.map
|