@tutti-os/workspace-file-reference 0.0.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/LICENSE +202 -0
- package/README.md +12 -0
- package/dist/chunk-LS7P3J6Z.js +901 -0
- package/dist/chunk-LS7P3J6Z.js.map +1 -0
- package/dist/chunk-PAR2R2G5.js +23 -0
- package/dist/chunk-PAR2R2G5.js.map +1 -0
- package/dist/contracts/index.d.ts +71 -0
- package/dist/contracts/index.js +1 -0
- package/dist/contracts/index.js.map +1 -0
- package/dist/core/index.d.ts +5 -0
- package/dist/core/index.js +7 -0
- package/dist/core/index.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/react/index.d.ts +92 -0
- package/dist/react/index.js +22 -0
- package/dist/react/index.js.map +1 -0
- package/dist/ui/index.d.ts +15 -0
- package/dist/ui/index.js +676 -0
- package/dist/ui/index.js.map +1 -0
- package/package.json +71 -0
|
@@ -0,0 +1,901 @@
|
|
|
1
|
+
import {
|
|
2
|
+
uniqueWorkspaceFileReferences
|
|
3
|
+
} from "./chunk-PAR2R2G5.js";
|
|
4
|
+
|
|
5
|
+
// src/react/internal/reference/WorkspaceFileReferencePickerState.ts
|
|
6
|
+
var workspaceFileReferenceDefaultExpandedDepth = 4;
|
|
7
|
+
function normalizeDirectoryPath(path) {
|
|
8
|
+
if (!path || path === "/") {
|
|
9
|
+
return "/";
|
|
10
|
+
}
|
|
11
|
+
return path.replace(/\/+$/, "") || "/";
|
|
12
|
+
}
|
|
13
|
+
function collectVisibleTreeEntries(entries, directoryStateByPath, expandedFolderPaths) {
|
|
14
|
+
const collected = [];
|
|
15
|
+
for (const entry of entries) {
|
|
16
|
+
collected.push(entry);
|
|
17
|
+
if (entry.kind !== "folder") {
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
const folderKey = normalizeDirectoryPath(entry.path);
|
|
21
|
+
if (!expandedFolderPaths[folderKey]) {
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
const childEntries = directoryStateByPath[folderKey]?.entries ?? [];
|
|
25
|
+
if (childEntries.length === 0) {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
collected.push(
|
|
29
|
+
...collectVisibleTreeEntries(
|
|
30
|
+
childEntries,
|
|
31
|
+
directoryStateByPath,
|
|
32
|
+
expandedFolderPaths
|
|
33
|
+
)
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
return collected;
|
|
37
|
+
}
|
|
38
|
+
function mergeExpandedFolderPaths(current, prefetched) {
|
|
39
|
+
return {
|
|
40
|
+
...prefetched,
|
|
41
|
+
...current
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function mergePrefetchedDirectoryState(current, prefetched) {
|
|
45
|
+
return {
|
|
46
|
+
...current,
|
|
47
|
+
...prefetched
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function createWorkspaceFileReferenceDirectoryStateFromSnapshot(snapshot) {
|
|
51
|
+
const stateByPath = {};
|
|
52
|
+
addReferenceDirectoryStateFromSnapshot(stateByPath, snapshot.directory);
|
|
53
|
+
return stateByPath;
|
|
54
|
+
}
|
|
55
|
+
function addReferenceDirectoryStateFromSnapshot(stateByPath, directory) {
|
|
56
|
+
const normalizedPath = normalizeDirectoryPath(directory.directoryPath);
|
|
57
|
+
stateByPath[normalizedPath] = {
|
|
58
|
+
displayPath: directory.directoryPath,
|
|
59
|
+
entries: directory.entries.map((entry) => ({
|
|
60
|
+
displayName: entry.displayName,
|
|
61
|
+
kind: entry.kind,
|
|
62
|
+
path: entry.path
|
|
63
|
+
})),
|
|
64
|
+
loaded: true,
|
|
65
|
+
loading: false,
|
|
66
|
+
prefetchReason: directory.prefetchReason,
|
|
67
|
+
prefetchState: directory.prefetchState
|
|
68
|
+
};
|
|
69
|
+
for (const entry of directory.entries) {
|
|
70
|
+
if (entry.kind !== "folder") {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const folderKey = normalizeDirectoryPath(entry.path);
|
|
74
|
+
if (entry.prefetchedDirectory) {
|
|
75
|
+
addReferenceDirectoryStateFromSnapshot(
|
|
76
|
+
stateByPath,
|
|
77
|
+
entry.prefetchedDirectory
|
|
78
|
+
);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (entry.prefetchState) {
|
|
82
|
+
stateByPath[folderKey] = {
|
|
83
|
+
displayPath: entry.path,
|
|
84
|
+
entries: [],
|
|
85
|
+
loaded: false,
|
|
86
|
+
loading: false,
|
|
87
|
+
prefetchReason: entry.prefetchReason,
|
|
88
|
+
prefetchState: entry.prefetchState
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
async function prefetchReferenceTree(input) {
|
|
94
|
+
const { depth = 1, listDirectory, maxDepth, path } = input;
|
|
95
|
+
const listing = await listDirectory(path);
|
|
96
|
+
if (!listing) {
|
|
97
|
+
return {
|
|
98
|
+
directoryStateByPath: {},
|
|
99
|
+
expandedFolderPaths: {}
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
const directoryStateByPath = {
|
|
103
|
+
[listing.normalizedPath]: {
|
|
104
|
+
displayPath: listing.displayPath,
|
|
105
|
+
entries: listing.entries,
|
|
106
|
+
loaded: true,
|
|
107
|
+
loading: false
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
const expandedFolderPaths = {
|
|
111
|
+
[listing.normalizedPath]: true
|
|
112
|
+
};
|
|
113
|
+
if (depth >= maxDepth) {
|
|
114
|
+
return {
|
|
115
|
+
directoryStateByPath,
|
|
116
|
+
expandedFolderPaths
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
for (const entry of listing.entries) {
|
|
120
|
+
if (entry.kind !== "folder") {
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
try {
|
|
124
|
+
const childTree = await prefetchReferenceTree({
|
|
125
|
+
depth: depth + 1,
|
|
126
|
+
listDirectory,
|
|
127
|
+
maxDepth,
|
|
128
|
+
path: entry.path
|
|
129
|
+
});
|
|
130
|
+
Object.assign(directoryStateByPath, childTree.directoryStateByPath);
|
|
131
|
+
Object.assign(expandedFolderPaths, childTree.expandedFolderPaths);
|
|
132
|
+
} catch {
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
directoryStateByPath,
|
|
137
|
+
expandedFolderPaths
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// src/react/internal/reference/useWorkspaceFileReferencePickerView.ts
|
|
142
|
+
import {
|
|
143
|
+
useCallback,
|
|
144
|
+
useEffect,
|
|
145
|
+
useEffectEvent,
|
|
146
|
+
useMemo,
|
|
147
|
+
useState
|
|
148
|
+
} from "react";
|
|
149
|
+
import { useSnapshot } from "valtio";
|
|
150
|
+
|
|
151
|
+
// src/react/internal/reference/WorkspaceFileReferencePickerController.ts
|
|
152
|
+
import { proxy } from "valtio/vanilla";
|
|
153
|
+
import {
|
|
154
|
+
createWorkspaceFilePreviewLoadedState,
|
|
155
|
+
resolveWorkspaceFilePreviewReadiness
|
|
156
|
+
} from "@tutti-os/workspace-file-preview";
|
|
157
|
+
var defaultDirectoryPath = "/";
|
|
158
|
+
var defaultSearchDebounceMs = 180;
|
|
159
|
+
function createWorkspaceFileReferencePickerController(input) {
|
|
160
|
+
const searchDebounceMs = input.searchDebounceMs ?? defaultSearchDebounceMs;
|
|
161
|
+
let browseSequence = 0;
|
|
162
|
+
let previewObjectUrl = null;
|
|
163
|
+
let previewSequence = 0;
|
|
164
|
+
let retained = false;
|
|
165
|
+
let searchAbortController = null;
|
|
166
|
+
let searchSequence = 0;
|
|
167
|
+
let searchTimer = null;
|
|
168
|
+
let snapshot = {
|
|
169
|
+
browseError: null,
|
|
170
|
+
browseRootPath: null,
|
|
171
|
+
directoryStateByPath: {},
|
|
172
|
+
expandedFolderPaths: {},
|
|
173
|
+
initialPathRevealed: false,
|
|
174
|
+
isBrowseLoading: false,
|
|
175
|
+
isSearchLoading: false,
|
|
176
|
+
mode: "browse",
|
|
177
|
+
previewState: { status: "empty" },
|
|
178
|
+
searchEntries: [],
|
|
179
|
+
searchError: null,
|
|
180
|
+
searchQuery: ""
|
|
181
|
+
};
|
|
182
|
+
const store = proxy(snapshot);
|
|
183
|
+
const setSnapshot = (update) => {
|
|
184
|
+
const next = typeof update === "function" ? update(snapshot) : { ...snapshot, ...update };
|
|
185
|
+
if (next === snapshot) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
snapshot = next;
|
|
189
|
+
Object.assign(store, next);
|
|
190
|
+
};
|
|
191
|
+
const loadDirectoryListing = async (path) => {
|
|
192
|
+
if (!input.fileAdapter?.listDirectory) {
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
const listing = await input.fileAdapter.listDirectory({
|
|
196
|
+
path: path ?? void 0,
|
|
197
|
+
workspaceId: input.workspaceId
|
|
198
|
+
});
|
|
199
|
+
const displayPath = listing.directoryPath || listing.rootPath || path || defaultDirectoryPath;
|
|
200
|
+
const normalizedPath = normalizeDirectoryPath(displayPath);
|
|
201
|
+
return {
|
|
202
|
+
displayPath,
|
|
203
|
+
entries: uniqueWorkspaceFileReferences(listing.entries),
|
|
204
|
+
normalizedPath
|
|
205
|
+
};
|
|
206
|
+
};
|
|
207
|
+
const resolveMode = (query) => query.trim().length > 0 && input.fileAdapter?.searchReferences ? "search" : "browse";
|
|
208
|
+
const clearSearchTimer = () => {
|
|
209
|
+
if (searchTimer === null) {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
clearTimeout(searchTimer);
|
|
213
|
+
searchTimer = null;
|
|
214
|
+
};
|
|
215
|
+
const cancelCurrentSearch = () => {
|
|
216
|
+
clearSearchTimer();
|
|
217
|
+
searchSequence += 1;
|
|
218
|
+
searchAbortController?.abort();
|
|
219
|
+
searchAbortController = null;
|
|
220
|
+
};
|
|
221
|
+
const cancelCurrentBrowse = () => {
|
|
222
|
+
browseSequence += 1;
|
|
223
|
+
};
|
|
224
|
+
const cancelCurrentPreview = () => {
|
|
225
|
+
previewSequence += 1;
|
|
226
|
+
if (!previewObjectUrl) {
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
URL.revokeObjectURL(previewObjectUrl);
|
|
230
|
+
previewObjectUrl = null;
|
|
231
|
+
};
|
|
232
|
+
const clearSearchResults = () => {
|
|
233
|
+
cancelCurrentSearch();
|
|
234
|
+
setSnapshot({
|
|
235
|
+
isSearchLoading: false,
|
|
236
|
+
searchEntries: [],
|
|
237
|
+
searchError: null
|
|
238
|
+
});
|
|
239
|
+
};
|
|
240
|
+
const runSearch = async (query) => {
|
|
241
|
+
if (!retained || !input.fileAdapter?.searchReferences) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
const sequence = ++searchSequence;
|
|
245
|
+
searchAbortController?.abort();
|
|
246
|
+
const abortController = new AbortController();
|
|
247
|
+
searchAbortController = abortController;
|
|
248
|
+
setSnapshot({
|
|
249
|
+
isSearchLoading: true,
|
|
250
|
+
searchError: null
|
|
251
|
+
});
|
|
252
|
+
try {
|
|
253
|
+
const refs = await input.fileAdapter.searchReferences({
|
|
254
|
+
query,
|
|
255
|
+
signal: abortController.signal,
|
|
256
|
+
workspaceId: input.workspaceId
|
|
257
|
+
});
|
|
258
|
+
if (!retained || sequence !== searchSequence) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
setSnapshot({
|
|
262
|
+
isSearchLoading: false,
|
|
263
|
+
searchEntries: uniqueWorkspaceFileReferences(refs),
|
|
264
|
+
searchError: null
|
|
265
|
+
});
|
|
266
|
+
} catch (error) {
|
|
267
|
+
if (isAbortError(error) || sequence !== searchSequence || !retained) {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
setSnapshot({
|
|
271
|
+
isSearchLoading: false,
|
|
272
|
+
searchEntries: [],
|
|
273
|
+
searchError: normalizeControllerError(
|
|
274
|
+
error,
|
|
275
|
+
"Workspace file reference search failed"
|
|
276
|
+
)
|
|
277
|
+
});
|
|
278
|
+
} finally {
|
|
279
|
+
if (sequence === searchSequence) {
|
|
280
|
+
searchAbortController = null;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
const scheduleSearch = () => {
|
|
285
|
+
clearSearchTimer();
|
|
286
|
+
const query = snapshot.searchQuery.trim();
|
|
287
|
+
if (!retained || !input.fileAdapter?.searchReferences || !query) {
|
|
288
|
+
clearSearchResults();
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
if (searchDebounceMs <= 0) {
|
|
292
|
+
void runSearch(query);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
searchTimer = setTimeout(() => {
|
|
296
|
+
searchTimer = null;
|
|
297
|
+
void runSearch(query);
|
|
298
|
+
}, searchDebounceMs);
|
|
299
|
+
};
|
|
300
|
+
const loadBrowseRoot = async () => {
|
|
301
|
+
if (!retained || snapshot.mode !== "browse" || !(input.fileAdapter?.listDirectory || input.fileAdapter?.loadReferenceTree)) {
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
const activeBrowseRootPath = snapshot.browseRootPath;
|
|
305
|
+
const normalizedRoot = activeBrowseRootPath ? normalizeDirectoryPath(activeBrowseRootPath) : null;
|
|
306
|
+
if (normalizedRoot && snapshot.directoryStateByPath[normalizedRoot]?.loaded) {
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
const sequence = ++browseSequence;
|
|
310
|
+
setSnapshot({
|
|
311
|
+
browseError: null,
|
|
312
|
+
isBrowseLoading: true
|
|
313
|
+
});
|
|
314
|
+
try {
|
|
315
|
+
if (input.fileAdapter.loadReferenceTree) {
|
|
316
|
+
const treeSnapshot = await input.fileAdapter.loadReferenceTree({
|
|
317
|
+
path: activeBrowseRootPath ?? void 0,
|
|
318
|
+
prefetchBudgetMs: 500,
|
|
319
|
+
prefetchDepth: 4,
|
|
320
|
+
workspaceId: input.workspaceId
|
|
321
|
+
});
|
|
322
|
+
if (!retained || sequence !== browseSequence) {
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
setSnapshot({
|
|
326
|
+
browseRootPath: normalizeDirectoryPath(
|
|
327
|
+
treeSnapshot.directory.directoryPath
|
|
328
|
+
),
|
|
329
|
+
directoryStateByPath: createWorkspaceFileReferenceDirectoryStateFromSnapshot(
|
|
330
|
+
treeSnapshot
|
|
331
|
+
),
|
|
332
|
+
isBrowseLoading: false
|
|
333
|
+
});
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
const listing = await loadDirectoryListing(activeBrowseRootPath);
|
|
337
|
+
if (!retained || sequence !== browseSequence || !listing) {
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
setSnapshot((current) => ({
|
|
341
|
+
...current,
|
|
342
|
+
browseRootPath: listing.normalizedPath,
|
|
343
|
+
directoryStateByPath: {
|
|
344
|
+
...current.directoryStateByPath,
|
|
345
|
+
[listing.normalizedPath]: {
|
|
346
|
+
displayPath: listing.displayPath,
|
|
347
|
+
entries: listing.entries,
|
|
348
|
+
loaded: true,
|
|
349
|
+
loading: false
|
|
350
|
+
}
|
|
351
|
+
},
|
|
352
|
+
isBrowseLoading: false
|
|
353
|
+
}));
|
|
354
|
+
} catch (error) {
|
|
355
|
+
if (!retained || sequence !== browseSequence) {
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
setSnapshot({
|
|
359
|
+
browseError: normalizeControllerError(
|
|
360
|
+
error,
|
|
361
|
+
"Workspace file reference browse failed"
|
|
362
|
+
),
|
|
363
|
+
isBrowseLoading: false
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
const loadFolderChildren = async (folder) => {
|
|
368
|
+
const folderKey = normalizeDirectoryPath(folder.path);
|
|
369
|
+
if (!retained || snapshot.directoryStateByPath[folderKey]?.loaded || snapshot.directoryStateByPath[folderKey]?.loading) {
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
const sequence = ++browseSequence;
|
|
373
|
+
setSnapshot((current) => ({
|
|
374
|
+
...current,
|
|
375
|
+
directoryStateByPath: {
|
|
376
|
+
...current.directoryStateByPath,
|
|
377
|
+
[folderKey]: {
|
|
378
|
+
displayPath: folder.path,
|
|
379
|
+
entries: current.directoryStateByPath[folderKey]?.entries ?? [],
|
|
380
|
+
loaded: current.directoryStateByPath[folderKey]?.loaded ?? false,
|
|
381
|
+
loading: true
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}));
|
|
385
|
+
try {
|
|
386
|
+
const listing = await loadDirectoryListing(folderKey);
|
|
387
|
+
if (!retained || sequence !== browseSequence || !listing) {
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
setSnapshot((current) => ({
|
|
391
|
+
...current,
|
|
392
|
+
directoryStateByPath: {
|
|
393
|
+
...current.directoryStateByPath,
|
|
394
|
+
[folderKey]: {
|
|
395
|
+
displayPath: listing.displayPath,
|
|
396
|
+
entries: listing.entries,
|
|
397
|
+
loaded: true,
|
|
398
|
+
loading: false
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}));
|
|
402
|
+
} catch {
|
|
403
|
+
if (!retained || sequence !== browseSequence) {
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
setSnapshot((current) => ({
|
|
407
|
+
...current,
|
|
408
|
+
directoryStateByPath: {
|
|
409
|
+
...current.directoryStateByPath,
|
|
410
|
+
[folderKey]: {
|
|
411
|
+
displayPath: current.directoryStateByPath[folderKey]?.displayPath ?? folder.path,
|
|
412
|
+
entries: current.directoryStateByPath[folderKey]?.entries ?? [],
|
|
413
|
+
loaded: current.directoryStateByPath[folderKey]?.loaded ?? false,
|
|
414
|
+
loading: false
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}));
|
|
418
|
+
}
|
|
419
|
+
};
|
|
420
|
+
const loadReferencePreview = async (reference, target, sequence) => {
|
|
421
|
+
try {
|
|
422
|
+
const preview = await input.fileAdapter?.readReferencePreview?.({
|
|
423
|
+
reference,
|
|
424
|
+
workspaceId: input.workspaceId
|
|
425
|
+
});
|
|
426
|
+
if (!retained || sequence !== previewSequence) {
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
if (!preview) {
|
|
430
|
+
setSnapshot({
|
|
431
|
+
previewState: { reference, status: "unsupported" }
|
|
432
|
+
});
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
const nextState = createReferencePreviewState(reference, target, preview);
|
|
436
|
+
if (!retained || sequence !== previewSequence) {
|
|
437
|
+
if (nextState.status === "image") {
|
|
438
|
+
URL.revokeObjectURL(nextState.objectUrl);
|
|
439
|
+
}
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
if (nextState.status === "image") {
|
|
443
|
+
previewObjectUrl = nextState.objectUrl;
|
|
444
|
+
}
|
|
445
|
+
setSnapshot({
|
|
446
|
+
previewState: nextState
|
|
447
|
+
});
|
|
448
|
+
} catch {
|
|
449
|
+
if (!retained || sequence !== previewSequence) {
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
setSnapshot({
|
|
453
|
+
previewState: { reference, status: "error" }
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
const setPreviewReference = (reference) => {
|
|
458
|
+
cancelCurrentPreview();
|
|
459
|
+
if (!retained || !reference) {
|
|
460
|
+
setSnapshot({
|
|
461
|
+
previewState: { status: "empty" }
|
|
462
|
+
});
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
const readiness = resolveWorkspaceFilePreviewReadiness(reference);
|
|
466
|
+
if (readiness.status === "directory") {
|
|
467
|
+
setSnapshot({
|
|
468
|
+
previewState: { reference, status: "directory" }
|
|
469
|
+
});
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
if (readiness.status === "unsupported") {
|
|
473
|
+
setSnapshot({
|
|
474
|
+
previewState: { reference, status: "unsupported" }
|
|
475
|
+
});
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
if (readiness.status === "readonly") {
|
|
479
|
+
setSnapshot({
|
|
480
|
+
previewState: {
|
|
481
|
+
maxSizeBytes: readiness.maxSizeBytes,
|
|
482
|
+
reason: readiness.reason,
|
|
483
|
+
reference,
|
|
484
|
+
status: "readonly"
|
|
485
|
+
}
|
|
486
|
+
});
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
if (!input.fileAdapter?.readReferencePreview) {
|
|
490
|
+
setSnapshot({
|
|
491
|
+
previewState: { reference, status: "unavailable" }
|
|
492
|
+
});
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
const sequence = ++previewSequence;
|
|
496
|
+
setSnapshot({
|
|
497
|
+
previewState: { reference, status: "loading" }
|
|
498
|
+
});
|
|
499
|
+
void loadReferencePreview(reference, readiness.target, sequence);
|
|
500
|
+
};
|
|
501
|
+
return {
|
|
502
|
+
close() {
|
|
503
|
+
retained = false;
|
|
504
|
+
cancelCurrentBrowse();
|
|
505
|
+
cancelCurrentPreview();
|
|
506
|
+
cancelCurrentSearch();
|
|
507
|
+
setSnapshot({
|
|
508
|
+
isBrowseLoading: false,
|
|
509
|
+
isSearchLoading: false,
|
|
510
|
+
previewState: { status: "empty" }
|
|
511
|
+
});
|
|
512
|
+
},
|
|
513
|
+
getSnapshot() {
|
|
514
|
+
return snapshot;
|
|
515
|
+
},
|
|
516
|
+
loadBrowseRoot() {
|
|
517
|
+
void loadBrowseRoot();
|
|
518
|
+
},
|
|
519
|
+
open() {
|
|
520
|
+
if (retained) {
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
retained = true;
|
|
524
|
+
if (snapshot.mode === "search") {
|
|
525
|
+
scheduleSearch();
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
void loadBrowseRoot();
|
|
529
|
+
},
|
|
530
|
+
async revealInitialPath(initialDirectoryPath) {
|
|
531
|
+
if (!retained || snapshot.mode !== "browse" || snapshot.initialPathRevealed || !snapshot.browseRootPath) {
|
|
532
|
+
return null;
|
|
533
|
+
}
|
|
534
|
+
const rootPath = normalizeDirectoryPath(snapshot.browseRootPath);
|
|
535
|
+
if (!isPathInsideOrEqual(initialDirectoryPath, rootPath)) {
|
|
536
|
+
setSnapshot({
|
|
537
|
+
initialPathRevealed: true
|
|
538
|
+
});
|
|
539
|
+
return null;
|
|
540
|
+
}
|
|
541
|
+
const sequence = ++browseSequence;
|
|
542
|
+
const loadedDirectories = {};
|
|
543
|
+
const expandedDirectories = {};
|
|
544
|
+
const directoryState = (path) => loadedDirectories[path] ?? snapshot.directoryStateByPath[path];
|
|
545
|
+
const directoryPaths = directoryChainBetween(
|
|
546
|
+
rootPath,
|
|
547
|
+
initialDirectoryPath
|
|
548
|
+
);
|
|
549
|
+
for (const directoryPath of directoryPaths) {
|
|
550
|
+
expandedDirectories[directoryPath] = true;
|
|
551
|
+
}
|
|
552
|
+
const listings = await Promise.all(
|
|
553
|
+
directoryPaths.filter((directoryPath) => !directoryState(directoryPath)?.loaded).map(async (directoryPath) => {
|
|
554
|
+
try {
|
|
555
|
+
return await loadDirectoryListing(directoryPath);
|
|
556
|
+
} catch {
|
|
557
|
+
return null;
|
|
558
|
+
}
|
|
559
|
+
})
|
|
560
|
+
);
|
|
561
|
+
for (const listing of listings) {
|
|
562
|
+
if (!listing) {
|
|
563
|
+
continue;
|
|
564
|
+
}
|
|
565
|
+
loadedDirectories[listing.normalizedPath] = {
|
|
566
|
+
displayPath: listing.displayPath,
|
|
567
|
+
entries: listing.entries,
|
|
568
|
+
loaded: true,
|
|
569
|
+
loading: false
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
if (!retained || sequence !== browseSequence) {
|
|
573
|
+
return null;
|
|
574
|
+
}
|
|
575
|
+
setSnapshot((current) => ({
|
|
576
|
+
...current,
|
|
577
|
+
directoryStateByPath: {
|
|
578
|
+
...current.directoryStateByPath,
|
|
579
|
+
...loadedDirectories
|
|
580
|
+
},
|
|
581
|
+
expandedFolderPaths: {
|
|
582
|
+
...current.expandedFolderPaths,
|
|
583
|
+
...expandedDirectories
|
|
584
|
+
},
|
|
585
|
+
initialPathRevealed: true
|
|
586
|
+
}));
|
|
587
|
+
return initialDirectoryPath;
|
|
588
|
+
},
|
|
589
|
+
reset() {
|
|
590
|
+
cancelCurrentBrowse();
|
|
591
|
+
cancelCurrentPreview();
|
|
592
|
+
cancelCurrentSearch();
|
|
593
|
+
setSnapshot({
|
|
594
|
+
browseError: null,
|
|
595
|
+
browseRootPath: null,
|
|
596
|
+
directoryStateByPath: {},
|
|
597
|
+
expandedFolderPaths: {},
|
|
598
|
+
initialPathRevealed: false,
|
|
599
|
+
isBrowseLoading: false,
|
|
600
|
+
isSearchLoading: false,
|
|
601
|
+
mode: "browse",
|
|
602
|
+
previewState: { status: "empty" },
|
|
603
|
+
searchEntries: [],
|
|
604
|
+
searchError: null,
|
|
605
|
+
searchQuery: ""
|
|
606
|
+
});
|
|
607
|
+
},
|
|
608
|
+
setPreviewReference(reference) {
|
|
609
|
+
setPreviewReference(reference);
|
|
610
|
+
},
|
|
611
|
+
setSearchQuery(query) {
|
|
612
|
+
if (query === snapshot.searchQuery) {
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
const nextMode = resolveMode(query);
|
|
616
|
+
setSnapshot({
|
|
617
|
+
mode: nextMode,
|
|
618
|
+
searchQuery: query,
|
|
619
|
+
...nextMode === "browse" ? { isSearchLoading: false } : {}
|
|
620
|
+
});
|
|
621
|
+
if (nextMode === "search") {
|
|
622
|
+
cancelCurrentBrowse();
|
|
623
|
+
scheduleSearch();
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
clearSearchResults();
|
|
627
|
+
void loadBrowseRoot();
|
|
628
|
+
},
|
|
629
|
+
get store() {
|
|
630
|
+
return store;
|
|
631
|
+
},
|
|
632
|
+
toggleFolder(entry) {
|
|
633
|
+
const folderKey = normalizeDirectoryPath(entry.path);
|
|
634
|
+
const childState = snapshot.directoryStateByPath[folderKey];
|
|
635
|
+
const nextExpanded = !(snapshot.expandedFolderPaths[folderKey] ?? false);
|
|
636
|
+
setSnapshot((current) => ({
|
|
637
|
+
...current,
|
|
638
|
+
expandedFolderPaths: {
|
|
639
|
+
...current.expandedFolderPaths,
|
|
640
|
+
[folderKey]: nextExpanded
|
|
641
|
+
}
|
|
642
|
+
}));
|
|
643
|
+
if (nextExpanded && !childState?.loaded && !childState?.loading) {
|
|
644
|
+
void loadFolderChildren(entry);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
function isAbortError(error) {
|
|
650
|
+
return error instanceof Error && error.name === "AbortError";
|
|
651
|
+
}
|
|
652
|
+
function normalizeControllerError(error, fallbackMessage) {
|
|
653
|
+
return error instanceof Error ? error : new Error(fallbackMessage);
|
|
654
|
+
}
|
|
655
|
+
function createReferencePreviewState(reference, target, preview) {
|
|
656
|
+
const loadedState = createWorkspaceFilePreviewLoadedState({
|
|
657
|
+
bytes: preview.bytes,
|
|
658
|
+
contentType: preview.contentType,
|
|
659
|
+
entry: reference,
|
|
660
|
+
target: {
|
|
661
|
+
...target,
|
|
662
|
+
fileKind: preview.kind
|
|
663
|
+
}
|
|
664
|
+
});
|
|
665
|
+
if (loadedState.status === "image") {
|
|
666
|
+
return {
|
|
667
|
+
objectUrl: URL.createObjectURL(
|
|
668
|
+
new Blob([loadedState.bytes], {
|
|
669
|
+
type: loadedState.contentType
|
|
670
|
+
})
|
|
671
|
+
),
|
|
672
|
+
reference,
|
|
673
|
+
status: "image"
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
if (loadedState.status === "text") {
|
|
677
|
+
return {
|
|
678
|
+
content: loadedState.content,
|
|
679
|
+
reference,
|
|
680
|
+
status: "text"
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
return {
|
|
684
|
+
maxSizeBytes: loadedState.maxSizeBytes,
|
|
685
|
+
reason: loadedState.reason,
|
|
686
|
+
reference,
|
|
687
|
+
status: "readonly"
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
function isPathInsideOrEqual(path, root) {
|
|
691
|
+
const normalizedPath = normalizeDirectoryPath(path);
|
|
692
|
+
const normalizedRoot = normalizeDirectoryPath(root);
|
|
693
|
+
if (normalizedRoot === "/") {
|
|
694
|
+
return normalizedPath.startsWith("/");
|
|
695
|
+
}
|
|
696
|
+
return normalizedPath === normalizedRoot || normalizedPath.startsWith(`${normalizedRoot}/`);
|
|
697
|
+
}
|
|
698
|
+
function directoryChainBetween(root, target) {
|
|
699
|
+
const normalizedRoot = normalizeDirectoryPath(root);
|
|
700
|
+
let current = normalizeDirectoryPath(target);
|
|
701
|
+
const chain = [];
|
|
702
|
+
while (current && current !== normalizedRoot) {
|
|
703
|
+
chain.unshift(current);
|
|
704
|
+
const parent = dirnameDirectoryPath(current);
|
|
705
|
+
if (parent === current) {
|
|
706
|
+
break;
|
|
707
|
+
}
|
|
708
|
+
current = parent;
|
|
709
|
+
}
|
|
710
|
+
return chain;
|
|
711
|
+
}
|
|
712
|
+
function dirnameDirectoryPath(path) {
|
|
713
|
+
const normalized = normalizeDirectoryPath(path);
|
|
714
|
+
if (normalized === "/") {
|
|
715
|
+
return "/";
|
|
716
|
+
}
|
|
717
|
+
const index = normalized.lastIndexOf("/");
|
|
718
|
+
if (index <= 0) {
|
|
719
|
+
return "/";
|
|
720
|
+
}
|
|
721
|
+
return normalized.slice(0, index);
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// src/react/internal/reference/useWorkspaceFileReferencePickerView.ts
|
|
725
|
+
function useWorkspaceFileReferencePickerView({
|
|
726
|
+
fileAdapter,
|
|
727
|
+
initialPath,
|
|
728
|
+
onClose,
|
|
729
|
+
onConfirm,
|
|
730
|
+
open,
|
|
731
|
+
workspaceId
|
|
732
|
+
}) {
|
|
733
|
+
const readPickerSnapshot = useSnapshot;
|
|
734
|
+
const trimmedInitialPath = initialPath?.trim() ?? "";
|
|
735
|
+
const initialDirectoryPath = trimmedInitialPath ? normalizeDirectoryPath(trimmedInitialPath) : null;
|
|
736
|
+
const [focusedPath, setFocusedPath] = useState(null);
|
|
737
|
+
const [selectedRefs, setSelectedRefs] = useState(
|
|
738
|
+
[]
|
|
739
|
+
);
|
|
740
|
+
const pickerController = useMemo(
|
|
741
|
+
() => createWorkspaceFileReferencePickerController({
|
|
742
|
+
fileAdapter,
|
|
743
|
+
workspaceId
|
|
744
|
+
}),
|
|
745
|
+
[fileAdapter, workspaceId]
|
|
746
|
+
);
|
|
747
|
+
const pickerSnapshot = readPickerSnapshot(pickerController.store);
|
|
748
|
+
const {
|
|
749
|
+
browseRootPath,
|
|
750
|
+
directoryStateByPath,
|
|
751
|
+
expandedFolderPaths,
|
|
752
|
+
isBrowseLoading,
|
|
753
|
+
isSearchLoading,
|
|
754
|
+
mode,
|
|
755
|
+
previewState,
|
|
756
|
+
searchEntries,
|
|
757
|
+
searchQuery
|
|
758
|
+
} = pickerSnapshot;
|
|
759
|
+
const isLoading = mode === "search" ? isSearchLoading : isBrowseLoading;
|
|
760
|
+
const finalizeRequestedReferences = useEffectEvent(
|
|
761
|
+
(refs) => {
|
|
762
|
+
onConfirm(uniqueWorkspaceFileReferences(refs));
|
|
763
|
+
onClose();
|
|
764
|
+
}
|
|
765
|
+
);
|
|
766
|
+
const setSearchQuery = useCallback(
|
|
767
|
+
(query) => {
|
|
768
|
+
pickerController.setSearchQuery(query);
|
|
769
|
+
},
|
|
770
|
+
[pickerController]
|
|
771
|
+
);
|
|
772
|
+
useEffect(() => {
|
|
773
|
+
if (!open) {
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
pickerController.reset();
|
|
777
|
+
pickerController.open();
|
|
778
|
+
setFocusedPath(null);
|
|
779
|
+
setSelectedRefs([]);
|
|
780
|
+
return () => {
|
|
781
|
+
pickerController.close();
|
|
782
|
+
};
|
|
783
|
+
}, [initialDirectoryPath, open, pickerController]);
|
|
784
|
+
useEffect(() => {
|
|
785
|
+
if (!open || mode !== "browse") {
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
pickerController.loadBrowseRoot();
|
|
789
|
+
}, [mode, open, pickerController]);
|
|
790
|
+
useEffect(() => {
|
|
791
|
+
if (!open || !fileAdapter || mode !== "browse") {
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
if (fileAdapter.listDirectory || fileAdapter.loadReferenceTree || !fileAdapter.requestReferences) {
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
let canceled = false;
|
|
798
|
+
void fileAdapter.requestReferences({ workspaceId }).then((refs) => {
|
|
799
|
+
if (!canceled) {
|
|
800
|
+
finalizeRequestedReferences(refs);
|
|
801
|
+
}
|
|
802
|
+
});
|
|
803
|
+
return () => {
|
|
804
|
+
canceled = true;
|
|
805
|
+
};
|
|
806
|
+
}, [fileAdapter, finalizeRequestedReferences, mode, open, workspaceId]);
|
|
807
|
+
useEffect(() => {
|
|
808
|
+
if (!open || mode !== "browse" || pickerSnapshot.initialPathRevealed || !initialDirectoryPath || !browseRootPath) {
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
let canceled = false;
|
|
812
|
+
const reveal = async () => {
|
|
813
|
+
const focusedInitialPath = await pickerController.revealInitialPath(initialDirectoryPath);
|
|
814
|
+
if (!canceled && focusedInitialPath) {
|
|
815
|
+
setFocusedPath(focusedInitialPath);
|
|
816
|
+
}
|
|
817
|
+
};
|
|
818
|
+
void reveal();
|
|
819
|
+
return () => {
|
|
820
|
+
canceled = true;
|
|
821
|
+
};
|
|
822
|
+
}, [
|
|
823
|
+
browseRootPath,
|
|
824
|
+
initialDirectoryPath,
|
|
825
|
+
mode,
|
|
826
|
+
open,
|
|
827
|
+
pickerController,
|
|
828
|
+
pickerSnapshot.initialPathRevealed
|
|
829
|
+
]);
|
|
830
|
+
const browseRootEntries = useMemo(
|
|
831
|
+
() => browseRootPath ? directoryStateByPath[normalizeDirectoryPath(browseRootPath)]?.entries ?? [] : [],
|
|
832
|
+
[browseRootPath, directoryStateByPath]
|
|
833
|
+
);
|
|
834
|
+
const visibleEntries = useMemo(
|
|
835
|
+
() => mode === "search" ? searchEntries : collectVisibleTreeEntries(
|
|
836
|
+
browseRootEntries,
|
|
837
|
+
directoryStateByPath,
|
|
838
|
+
expandedFolderPaths
|
|
839
|
+
),
|
|
840
|
+
[
|
|
841
|
+
browseRootEntries,
|
|
842
|
+
directoryStateByPath,
|
|
843
|
+
expandedFolderPaths,
|
|
844
|
+
mode,
|
|
845
|
+
searchEntries
|
|
846
|
+
]
|
|
847
|
+
);
|
|
848
|
+
useEffect(() => {
|
|
849
|
+
if (!open) {
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
setFocusedPath((current) => {
|
|
853
|
+
if (current && visibleEntries.some((entry) => entry.path === current)) {
|
|
854
|
+
return current;
|
|
855
|
+
}
|
|
856
|
+
return visibleEntries[0]?.path ?? null;
|
|
857
|
+
});
|
|
858
|
+
}, [open, visibleEntries]);
|
|
859
|
+
const focusedEntry = visibleEntries.find((entry) => entry.path === focusedPath) ?? selectedRefs.find((entry) => entry.path === focusedPath) ?? null;
|
|
860
|
+
useEffect(() => {
|
|
861
|
+
pickerController.setPreviewReference(open ? focusedEntry : null);
|
|
862
|
+
}, [focusedEntry, open, pickerController]);
|
|
863
|
+
const toggleRef = (ref) => {
|
|
864
|
+
setSelectedRefs((current) => {
|
|
865
|
+
const existing = current.some((item) => item.path === ref.path);
|
|
866
|
+
return existing ? current.filter((item) => item.path !== ref.path) : uniqueWorkspaceFileReferences([...current, ref]);
|
|
867
|
+
});
|
|
868
|
+
};
|
|
869
|
+
const toggleFolder = (entry) => {
|
|
870
|
+
pickerController.toggleFolder(entry);
|
|
871
|
+
};
|
|
872
|
+
return {
|
|
873
|
+
browseRootEntries,
|
|
874
|
+
directoryStateByPath,
|
|
875
|
+
expandedFolderPaths,
|
|
876
|
+
focusedEntry,
|
|
877
|
+
focusedPath,
|
|
878
|
+
isLoading,
|
|
879
|
+
mode,
|
|
880
|
+
previewState,
|
|
881
|
+
searchQuery,
|
|
882
|
+
selectedRefs,
|
|
883
|
+
visibleEntries,
|
|
884
|
+
setFocusedPath,
|
|
885
|
+
setSearchQuery,
|
|
886
|
+
toggleFolder,
|
|
887
|
+
toggleRef
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
export {
|
|
892
|
+
workspaceFileReferenceDefaultExpandedDepth,
|
|
893
|
+
normalizeDirectoryPath,
|
|
894
|
+
collectVisibleTreeEntries,
|
|
895
|
+
mergeExpandedFolderPaths,
|
|
896
|
+
mergePrefetchedDirectoryState,
|
|
897
|
+
createWorkspaceFileReferenceDirectoryStateFromSnapshot,
|
|
898
|
+
prefetchReferenceTree,
|
|
899
|
+
useWorkspaceFileReferencePickerView
|
|
900
|
+
};
|
|
901
|
+
//# sourceMappingURL=chunk-LS7P3J6Z.js.map
|