momoi-explorer 0.8.0
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/README.md +355 -0
- package/dist/index.cjs +690 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +501 -0
- package/dist/index.d.ts +501 -0
- package/dist/index.js +652 -0
- package/dist/index.js.map +1 -0
- package/dist/react.cjs +850 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +499 -0
- package/dist/react.d.ts +499 -0
- package/dist/react.js +816 -0
- package/dist/react.js.map +1 -0
- package/dist/ui/style.css +332 -0
- package/dist/ui.cjs +1379 -0
- package/dist/ui.cjs.map +1 -0
- package/dist/ui.d.cts +554 -0
- package/dist/ui.d.ts +554 -0
- package/dist/ui.js +1347 -0
- package/dist/ui.js.map +1 -0
- package/package.json +95 -0
package/dist/ui.js
ADDED
|
@@ -0,0 +1,1347 @@
|
|
|
1
|
+
// src/ui/FileExplorer.tsx
|
|
2
|
+
import { useCallback as useCallback5, useEffect as useEffect5, useMemo as useMemo3 } from "react";
|
|
3
|
+
import { Virtuoso } from "react-virtuoso";
|
|
4
|
+
|
|
5
|
+
// src/react/TreeProvider.tsx
|
|
6
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
7
|
+
|
|
8
|
+
// src/core/event-processor.ts
|
|
9
|
+
var DEFAULT_DEBOUNCE_MS = 75;
|
|
10
|
+
var DEFAULT_THROTTLE_CHUNK_SIZE = 500;
|
|
11
|
+
var DEFAULT_THROTTLE_DELAY_MS = 200;
|
|
12
|
+
function coalesceEvents(raw) {
|
|
13
|
+
const expanded = [];
|
|
14
|
+
for (const event of raw) {
|
|
15
|
+
if (event.type === "rename" && event.newPath) {
|
|
16
|
+
expanded.push({ type: "delete", path: event.path, isDirectory: event.isDirectory });
|
|
17
|
+
expanded.push({ type: "create", path: event.newPath, isDirectory: event.isDirectory });
|
|
18
|
+
} else {
|
|
19
|
+
expanded.push({ type: event.type, path: event.path, isDirectory: event.isDirectory });
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
23
|
+
for (const event of expanded) {
|
|
24
|
+
const existing = byPath.get(event.path);
|
|
25
|
+
if (!existing) {
|
|
26
|
+
byPath.set(event.path, event);
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (existing.type === "delete" && event.type === "create") {
|
|
30
|
+
byPath.set(event.path, { type: "modify", path: event.path, isDirectory: event.isDirectory });
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (existing.type === "create" && event.type === "modify") {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (existing.type === "create" && event.type === "delete") {
|
|
37
|
+
byPath.delete(event.path);
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
byPath.set(event.path, event);
|
|
41
|
+
}
|
|
42
|
+
const result = Array.from(byPath.values());
|
|
43
|
+
const deletedDirs = /* @__PURE__ */ new Set();
|
|
44
|
+
for (const event of result) {
|
|
45
|
+
if (event.type === "delete" && event.isDirectory) {
|
|
46
|
+
deletedDirs.add(event.path);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (deletedDirs.size === 0) return result;
|
|
50
|
+
return result.filter((event) => {
|
|
51
|
+
if (event.type !== "delete") return true;
|
|
52
|
+
for (const dir of deletedDirs) {
|
|
53
|
+
if (event.path !== dir && event.path.startsWith(dir + "/")) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return true;
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
function createEventProcessor(callback, options = {}) {
|
|
61
|
+
const debounceMs = options.debounceMs ?? DEFAULT_DEBOUNCE_MS;
|
|
62
|
+
const shouldCoalesce = options.coalesce ?? true;
|
|
63
|
+
const throttleChunkSize = options.throttle?.maxChunkSize ?? DEFAULT_THROTTLE_CHUNK_SIZE;
|
|
64
|
+
const throttleDelayMs = options.throttle?.delayMs ?? DEFAULT_THROTTLE_DELAY_MS;
|
|
65
|
+
let buffer = [];
|
|
66
|
+
let debounceTimer = null;
|
|
67
|
+
let throttleTimer = null;
|
|
68
|
+
let destroyed = false;
|
|
69
|
+
function processBuffer() {
|
|
70
|
+
if (destroyed || buffer.length === 0) return;
|
|
71
|
+
const raw = buffer;
|
|
72
|
+
buffer = [];
|
|
73
|
+
const events = shouldCoalesce ? coalesceEvents(raw) : raw.map((e) => ({
|
|
74
|
+
type: e.type === "rename" ? "modify" : e.type,
|
|
75
|
+
path: e.type === "rename" && e.newPath ? e.newPath : e.path,
|
|
76
|
+
isDirectory: e.isDirectory
|
|
77
|
+
}));
|
|
78
|
+
if (events.length === 0) return;
|
|
79
|
+
if (events.length <= throttleChunkSize) {
|
|
80
|
+
callback(events);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
let offset = 0;
|
|
84
|
+
function emitChunk() {
|
|
85
|
+
if (destroyed || offset >= events.length) return;
|
|
86
|
+
const chunk = events.slice(offset, offset + throttleChunkSize);
|
|
87
|
+
offset += throttleChunkSize;
|
|
88
|
+
callback(chunk);
|
|
89
|
+
if (offset < events.length) {
|
|
90
|
+
throttleTimer = setTimeout(emitChunk, throttleDelayMs);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
emitChunk();
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
push(events) {
|
|
97
|
+
if (destroyed) return;
|
|
98
|
+
buffer.push(...events);
|
|
99
|
+
if (debounceTimer !== null) {
|
|
100
|
+
clearTimeout(debounceTimer);
|
|
101
|
+
}
|
|
102
|
+
debounceTimer = setTimeout(processBuffer, debounceMs);
|
|
103
|
+
},
|
|
104
|
+
flush() {
|
|
105
|
+
if (debounceTimer !== null) {
|
|
106
|
+
clearTimeout(debounceTimer);
|
|
107
|
+
debounceTimer = null;
|
|
108
|
+
}
|
|
109
|
+
processBuffer();
|
|
110
|
+
},
|
|
111
|
+
destroy() {
|
|
112
|
+
destroyed = true;
|
|
113
|
+
if (debounceTimer !== null) clearTimeout(debounceTimer);
|
|
114
|
+
if (throttleTimer !== null) clearTimeout(throttleTimer);
|
|
115
|
+
buffer = [];
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// src/core/flatten.ts
|
|
121
|
+
function flattenTree(nodes, expandedPaths, matchingPaths) {
|
|
122
|
+
const result = [];
|
|
123
|
+
function walk(children, depth) {
|
|
124
|
+
for (const node of children) {
|
|
125
|
+
if (matchingPaths && !matchingPaths.has(node.path)) continue;
|
|
126
|
+
result.push({ node, depth });
|
|
127
|
+
if (node.isDirectory && node.children) {
|
|
128
|
+
if (matchingPaths || expandedPaths.has(node.path)) {
|
|
129
|
+
walk(node.children, depth + 1);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
walk(nodes, 0);
|
|
135
|
+
return result;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// src/core/selection.ts
|
|
139
|
+
function computeSelection(currentSelected, anchorPath, targetPath, mode, flatList) {
|
|
140
|
+
switch (mode) {
|
|
141
|
+
case "replace":
|
|
142
|
+
return {
|
|
143
|
+
selectedPaths: /* @__PURE__ */ new Set([targetPath]),
|
|
144
|
+
anchorPath: targetPath
|
|
145
|
+
};
|
|
146
|
+
case "toggle": {
|
|
147
|
+
const next = new Set(currentSelected);
|
|
148
|
+
if (next.has(targetPath)) {
|
|
149
|
+
next.delete(targetPath);
|
|
150
|
+
} else {
|
|
151
|
+
next.add(targetPath);
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
selectedPaths: next,
|
|
155
|
+
anchorPath: targetPath
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
case "range": {
|
|
159
|
+
if (!anchorPath) {
|
|
160
|
+
return {
|
|
161
|
+
selectedPaths: /* @__PURE__ */ new Set([targetPath]),
|
|
162
|
+
anchorPath: targetPath
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
const paths = flatList.map((f) => f.node.path);
|
|
166
|
+
const anchorIdx = paths.indexOf(anchorPath);
|
|
167
|
+
const targetIdx = paths.indexOf(targetPath);
|
|
168
|
+
if (anchorIdx === -1 || targetIdx === -1) {
|
|
169
|
+
return {
|
|
170
|
+
selectedPaths: /* @__PURE__ */ new Set([targetPath]),
|
|
171
|
+
anchorPath: targetPath
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
const start = Math.min(anchorIdx, targetIdx);
|
|
175
|
+
const end = Math.max(anchorIdx, targetIdx);
|
|
176
|
+
const rangePaths = new Set(paths.slice(start, end + 1));
|
|
177
|
+
return {
|
|
178
|
+
selectedPaths: rangePaths,
|
|
179
|
+
anchorPath
|
|
180
|
+
// range選択ではanchorは変えない
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// src/core/sort.ts
|
|
187
|
+
function defaultSort(a, b) {
|
|
188
|
+
if (a.isDirectory !== b.isDirectory) {
|
|
189
|
+
return a.isDirectory ? -1 : 1;
|
|
190
|
+
}
|
|
191
|
+
return a.name.localeCompare(b.name, void 0, { sensitivity: "base" });
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// src/core/filter.ts
|
|
195
|
+
function defaultFilter(_entry) {
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// src/core/search.ts
|
|
200
|
+
function fuzzyMatch(query, target) {
|
|
201
|
+
const q = query.toLowerCase();
|
|
202
|
+
const t = target.toLowerCase();
|
|
203
|
+
if (q.length === 0) return { match: true, score: 0 };
|
|
204
|
+
if (q.length > t.length) return { match: false, score: 0 };
|
|
205
|
+
if (t === q) return { match: true, score: 100 };
|
|
206
|
+
if (t.startsWith(q)) return { match: true, score: 90 };
|
|
207
|
+
if (t.includes(q)) return { match: true, score: 80 };
|
|
208
|
+
let qi = 0;
|
|
209
|
+
let score = 0;
|
|
210
|
+
let lastMatchIndex = -2;
|
|
211
|
+
for (let ti = 0; ti < t.length && qi < q.length; ti++) {
|
|
212
|
+
if (t[ti] === q[qi]) {
|
|
213
|
+
qi++;
|
|
214
|
+
if (ti === lastMatchIndex + 1) {
|
|
215
|
+
score += 5;
|
|
216
|
+
}
|
|
217
|
+
if (ti === 0 || "/\\-_.".includes(t[ti - 1])) {
|
|
218
|
+
score += 10;
|
|
219
|
+
}
|
|
220
|
+
score += 1;
|
|
221
|
+
lastMatchIndex = ti;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
if (qi < q.length) return { match: false, score: 0 };
|
|
225
|
+
return { match: true, score };
|
|
226
|
+
}
|
|
227
|
+
function findMatchingPaths(nodes, query) {
|
|
228
|
+
const matching = /* @__PURE__ */ new Set();
|
|
229
|
+
function walk(node, ancestors) {
|
|
230
|
+
const nameMatch = fuzzyMatch(query, node.name).match;
|
|
231
|
+
let childMatch = false;
|
|
232
|
+
if (node.children) {
|
|
233
|
+
for (const child of node.children) {
|
|
234
|
+
if (walk(child, [...ancestors, node.path])) {
|
|
235
|
+
childMatch = true;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
if (nameMatch || childMatch) {
|
|
240
|
+
matching.add(node.path);
|
|
241
|
+
for (const a of ancestors) {
|
|
242
|
+
matching.add(a);
|
|
243
|
+
}
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
for (const node of nodes) {
|
|
249
|
+
walk(node, []);
|
|
250
|
+
}
|
|
251
|
+
return matching;
|
|
252
|
+
}
|
|
253
|
+
function fuzzyFind(files, query, maxResults = 50) {
|
|
254
|
+
if (!query) return [];
|
|
255
|
+
const scored = [];
|
|
256
|
+
for (const entry of files) {
|
|
257
|
+
const nameResult = fuzzyMatch(query, entry.name);
|
|
258
|
+
const pathResult = fuzzyMatch(query, entry.path);
|
|
259
|
+
const bestScore = Math.max(nameResult.score, pathResult.score * 0.5);
|
|
260
|
+
if (nameResult.match || pathResult.match) {
|
|
261
|
+
scored.push({ entry, score: bestScore });
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
scored.sort((a, b) => b.score - a.score);
|
|
265
|
+
return scored.slice(0, maxResults).map((s) => s.entry);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// src/core/tree.ts
|
|
269
|
+
function toTreeNode(entry, depth) {
|
|
270
|
+
return {
|
|
271
|
+
...entry,
|
|
272
|
+
depth,
|
|
273
|
+
children: entry.isDirectory ? void 0 : void 0,
|
|
274
|
+
childrenLoaded: false
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
function findNode(nodes, path) {
|
|
278
|
+
for (const node of nodes) {
|
|
279
|
+
if (node.path === path) return node;
|
|
280
|
+
if (node.children) {
|
|
281
|
+
const found = findNode(node.children, path);
|
|
282
|
+
if (found) return found;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return void 0;
|
|
286
|
+
}
|
|
287
|
+
function dirname(path) {
|
|
288
|
+
const sep = path.includes("\\") ? "\\" : "/";
|
|
289
|
+
const idx = path.lastIndexOf(sep);
|
|
290
|
+
return idx === -1 ? "" : path.slice(0, idx);
|
|
291
|
+
}
|
|
292
|
+
function createFileTree(options) {
|
|
293
|
+
const { adapter, rootPath, onEvent } = options;
|
|
294
|
+
let sortFn = options.sort ?? defaultSort;
|
|
295
|
+
let filterFn = options.filter ?? defaultFilter;
|
|
296
|
+
let state = {
|
|
297
|
+
rootPath,
|
|
298
|
+
rootNodes: [],
|
|
299
|
+
expandedPaths: /* @__PURE__ */ new Set(),
|
|
300
|
+
selectedPaths: /* @__PURE__ */ new Set(),
|
|
301
|
+
anchorPath: null,
|
|
302
|
+
renamingPath: null,
|
|
303
|
+
creatingState: null,
|
|
304
|
+
searchQuery: null,
|
|
305
|
+
flatList: []
|
|
306
|
+
};
|
|
307
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
308
|
+
let expandingPaths = /* @__PURE__ */ new Set();
|
|
309
|
+
function emit(event) {
|
|
310
|
+
onEvent?.(event);
|
|
311
|
+
}
|
|
312
|
+
function notify() {
|
|
313
|
+
const matchingPaths = state.searchQuery ? findMatchingPaths(state.rootNodes, state.searchQuery) : null;
|
|
314
|
+
state = { ...state, flatList: flattenTree(state.rootNodes, state.expandedPaths, matchingPaths) };
|
|
315
|
+
for (const listener of listeners) {
|
|
316
|
+
listener(state);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
async function loadChildren(node) {
|
|
320
|
+
const entries = await adapter.readDir(node.path);
|
|
321
|
+
const filtered = entries.filter(filterFn);
|
|
322
|
+
filtered.sort(sortFn);
|
|
323
|
+
const oldChildMap = node.children ? new Map(node.children.map((c) => [c.path, c])) : /* @__PURE__ */ new Map();
|
|
324
|
+
node.children = filtered.map((e) => {
|
|
325
|
+
const existing = oldChildMap.get(e.path);
|
|
326
|
+
if (existing && existing.childrenLoaded) {
|
|
327
|
+
return { ...toTreeNode(e, node.depth + 1), children: existing.children, childrenLoaded: true };
|
|
328
|
+
}
|
|
329
|
+
return toTreeNode(e, node.depth + 1);
|
|
330
|
+
});
|
|
331
|
+
node.childrenLoaded = true;
|
|
332
|
+
}
|
|
333
|
+
async function refreshParent(parentPath) {
|
|
334
|
+
const parentNode = findNode(state.rootNodes, parentPath);
|
|
335
|
+
if (parentNode) {
|
|
336
|
+
await loadChildren(parentNode);
|
|
337
|
+
state.expandedPaths = new Set(state.expandedPaths);
|
|
338
|
+
state.expandedPaths.add(parentPath);
|
|
339
|
+
} else if (parentPath === rootPath) {
|
|
340
|
+
const entries = await adapter.readDir(rootPath);
|
|
341
|
+
const filtered = entries.filter(filterFn);
|
|
342
|
+
filtered.sort(sortFn);
|
|
343
|
+
const oldNodeMap = new Map(state.rootNodes.map((n) => [n.path, n]));
|
|
344
|
+
state.rootNodes = filtered.map((e) => {
|
|
345
|
+
const existing = oldNodeMap.get(e.path);
|
|
346
|
+
if (existing && existing.childrenLoaded) {
|
|
347
|
+
return { ...toTreeNode(e, 0), children: existing.children, childrenLoaded: true };
|
|
348
|
+
}
|
|
349
|
+
return toTreeNode(e, 0);
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
function sortNodes(nodes) {
|
|
354
|
+
nodes.sort(sortFn);
|
|
355
|
+
for (const node of nodes) {
|
|
356
|
+
if (node.children) {
|
|
357
|
+
sortNodes(node.children);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
function filterNodes(nodes) {
|
|
362
|
+
return nodes.filter((node) => {
|
|
363
|
+
if (!filterFn(node)) return false;
|
|
364
|
+
if (node.children) {
|
|
365
|
+
node.children = filterNodes(node.children);
|
|
366
|
+
}
|
|
367
|
+
return true;
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
let unwatchFn = null;
|
|
371
|
+
let eventProcessor = null;
|
|
372
|
+
function handleWatchEvents(events) {
|
|
373
|
+
emit({ type: "external-change", changes: events });
|
|
374
|
+
const dirsToRefresh = /* @__PURE__ */ new Set();
|
|
375
|
+
for (const event of events) {
|
|
376
|
+
const parent = dirname(event.path);
|
|
377
|
+
if (parent === rootPath || state.expandedPaths.has(parent)) {
|
|
378
|
+
dirsToRefresh.add(parent);
|
|
379
|
+
}
|
|
380
|
+
if (event.isDirectory && state.expandedPaths.has(event.path)) {
|
|
381
|
+
dirsToRefresh.add(event.path);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
for (const dir of dirsToRefresh) {
|
|
385
|
+
refreshParent(dir).then(() => notify()).catch(() => {
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
function startWatching() {
|
|
390
|
+
if (!adapter.watch) return;
|
|
391
|
+
eventProcessor = createEventProcessor(handleWatchEvents, options.watchOptions);
|
|
392
|
+
unwatchFn = adapter.watch(rootPath, (events) => {
|
|
393
|
+
eventProcessor.push(events);
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
function stopWatching() {
|
|
397
|
+
if (unwatchFn) {
|
|
398
|
+
unwatchFn();
|
|
399
|
+
unwatchFn = null;
|
|
400
|
+
}
|
|
401
|
+
if (eventProcessor) {
|
|
402
|
+
eventProcessor.destroy();
|
|
403
|
+
eventProcessor = null;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
const controller = {
|
|
407
|
+
getState() {
|
|
408
|
+
return state;
|
|
409
|
+
},
|
|
410
|
+
subscribe(listener) {
|
|
411
|
+
listeners.add(listener);
|
|
412
|
+
return () => listeners.delete(listener);
|
|
413
|
+
},
|
|
414
|
+
async loadRoot() {
|
|
415
|
+
const entries = await adapter.readDir(rootPath);
|
|
416
|
+
const filtered = entries.filter(filterFn);
|
|
417
|
+
filtered.sort(sortFn);
|
|
418
|
+
state.rootNodes = filtered.map((e) => toTreeNode(e, 0));
|
|
419
|
+
notify();
|
|
420
|
+
startWatching();
|
|
421
|
+
},
|
|
422
|
+
async expand(path) {
|
|
423
|
+
if (expandingPaths.has(path)) return;
|
|
424
|
+
expandingPaths.add(path);
|
|
425
|
+
try {
|
|
426
|
+
const node = findNode(state.rootNodes, path);
|
|
427
|
+
if (!node || !node.isDirectory) return;
|
|
428
|
+
if (!node.childrenLoaded) {
|
|
429
|
+
await loadChildren(node);
|
|
430
|
+
}
|
|
431
|
+
state.expandedPaths = new Set(state.expandedPaths);
|
|
432
|
+
state.expandedPaths.add(path);
|
|
433
|
+
notify();
|
|
434
|
+
emit({ type: "expand", path });
|
|
435
|
+
} finally {
|
|
436
|
+
expandingPaths.delete(path);
|
|
437
|
+
}
|
|
438
|
+
},
|
|
439
|
+
collapse(path) {
|
|
440
|
+
const sep = path.includes("\\") ? "\\" : "/";
|
|
441
|
+
const prefix = path + sep;
|
|
442
|
+
state.expandedPaths = new Set(state.expandedPaths);
|
|
443
|
+
state.expandedPaths.delete(path);
|
|
444
|
+
for (const p of state.expandedPaths) {
|
|
445
|
+
if (p.startsWith(prefix)) {
|
|
446
|
+
state.expandedPaths.delete(p);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
notify();
|
|
450
|
+
emit({ type: "collapse", path });
|
|
451
|
+
},
|
|
452
|
+
async toggleExpand(path) {
|
|
453
|
+
if (expandingPaths.has(path)) return;
|
|
454
|
+
if (state.expandedPaths.has(path)) {
|
|
455
|
+
controller.collapse(path);
|
|
456
|
+
} else {
|
|
457
|
+
await controller.expand(path);
|
|
458
|
+
}
|
|
459
|
+
},
|
|
460
|
+
async expandTo(path) {
|
|
461
|
+
const parts = [];
|
|
462
|
+
let current = path;
|
|
463
|
+
while (current !== rootPath && current !== "") {
|
|
464
|
+
const parent = dirname(current);
|
|
465
|
+
if (parent === current) break;
|
|
466
|
+
parts.unshift(parent);
|
|
467
|
+
current = parent;
|
|
468
|
+
}
|
|
469
|
+
for (const ancestorPath of parts) {
|
|
470
|
+
if (ancestorPath === rootPath) continue;
|
|
471
|
+
if (!state.expandedPaths.has(ancestorPath)) {
|
|
472
|
+
await controller.expand(ancestorPath);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
},
|
|
476
|
+
select(path, mode = "replace") {
|
|
477
|
+
const result = computeSelection(
|
|
478
|
+
state.selectedPaths,
|
|
479
|
+
state.anchorPath,
|
|
480
|
+
path,
|
|
481
|
+
mode,
|
|
482
|
+
state.flatList
|
|
483
|
+
);
|
|
484
|
+
state.selectedPaths = result.selectedPaths;
|
|
485
|
+
state.anchorPath = result.anchorPath;
|
|
486
|
+
notify();
|
|
487
|
+
emit({ type: "select", paths: Array.from(result.selectedPaths) });
|
|
488
|
+
},
|
|
489
|
+
selectAll() {
|
|
490
|
+
state.selectedPaths = new Set(state.flatList.map((f) => f.node.path));
|
|
491
|
+
notify();
|
|
492
|
+
emit({ type: "select", paths: Array.from(state.selectedPaths) });
|
|
493
|
+
},
|
|
494
|
+
clearSelection() {
|
|
495
|
+
state.selectedPaths = /* @__PURE__ */ new Set();
|
|
496
|
+
state.anchorPath = null;
|
|
497
|
+
notify();
|
|
498
|
+
emit({ type: "select", paths: [] });
|
|
499
|
+
},
|
|
500
|
+
startRename(path) {
|
|
501
|
+
state.renamingPath = path;
|
|
502
|
+
notify();
|
|
503
|
+
},
|
|
504
|
+
async commitRename(newName) {
|
|
505
|
+
if (!state.renamingPath || !adapter.rename) return;
|
|
506
|
+
const oldPath = state.renamingPath;
|
|
507
|
+
const parent = dirname(oldPath);
|
|
508
|
+
const sep = oldPath.includes("\\") ? "\\" : "/";
|
|
509
|
+
const newPath = parent + sep + newName;
|
|
510
|
+
await adapter.rename(oldPath, newPath);
|
|
511
|
+
state.renamingPath = null;
|
|
512
|
+
if (parent === rootPath) {
|
|
513
|
+
await controller.loadRoot();
|
|
514
|
+
} else {
|
|
515
|
+
const parentNode = findNode(state.rootNodes, parent);
|
|
516
|
+
if (parentNode) {
|
|
517
|
+
await loadChildren(parentNode);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
notify();
|
|
521
|
+
emit({ type: "rename", oldPath, newPath });
|
|
522
|
+
},
|
|
523
|
+
cancelRename() {
|
|
524
|
+
state.renamingPath = null;
|
|
525
|
+
notify();
|
|
526
|
+
},
|
|
527
|
+
async startCreate(parentPath, isDirectory, insertAfterPath) {
|
|
528
|
+
if (parentPath !== rootPath && !state.expandedPaths.has(parentPath)) {
|
|
529
|
+
await controller.expand(parentPath);
|
|
530
|
+
}
|
|
531
|
+
state.creatingState = { parentPath, isDirectory, insertAfterPath };
|
|
532
|
+
notify();
|
|
533
|
+
},
|
|
534
|
+
async commitCreate(name) {
|
|
535
|
+
if (!state.creatingState) return;
|
|
536
|
+
const { parentPath, isDirectory } = state.creatingState;
|
|
537
|
+
state.creatingState = null;
|
|
538
|
+
if (isDirectory) {
|
|
539
|
+
await controller.createDir(parentPath, name);
|
|
540
|
+
} else {
|
|
541
|
+
await controller.createFile(parentPath, name);
|
|
542
|
+
}
|
|
543
|
+
},
|
|
544
|
+
cancelCreate() {
|
|
545
|
+
state.creatingState = null;
|
|
546
|
+
notify();
|
|
547
|
+
},
|
|
548
|
+
async createFile(parentPath, name) {
|
|
549
|
+
if (!adapter.createFile) return;
|
|
550
|
+
await adapter.createFile(parentPath, name);
|
|
551
|
+
await refreshParent(parentPath);
|
|
552
|
+
notify();
|
|
553
|
+
emit({ type: "create", parentPath, name, isDirectory: false });
|
|
554
|
+
},
|
|
555
|
+
async createDir(parentPath, name) {
|
|
556
|
+
if (!adapter.createDir) return;
|
|
557
|
+
await adapter.createDir(parentPath, name);
|
|
558
|
+
await refreshParent(parentPath);
|
|
559
|
+
notify();
|
|
560
|
+
emit({ type: "create", parentPath, name, isDirectory: true });
|
|
561
|
+
},
|
|
562
|
+
async deleteSelected() {
|
|
563
|
+
if (!adapter.delete || state.selectedPaths.size === 0) return;
|
|
564
|
+
const paths = Array.from(state.selectedPaths);
|
|
565
|
+
await adapter.delete(paths);
|
|
566
|
+
state.selectedPaths = /* @__PURE__ */ new Set();
|
|
567
|
+
state.anchorPath = null;
|
|
568
|
+
const parentDirs = new Set(paths.map(dirname));
|
|
569
|
+
for (const dir of parentDirs) {
|
|
570
|
+
await refreshParent(dir);
|
|
571
|
+
}
|
|
572
|
+
notify();
|
|
573
|
+
emit({ type: "delete", paths });
|
|
574
|
+
},
|
|
575
|
+
async refresh(path) {
|
|
576
|
+
if (!path || path === rootPath) {
|
|
577
|
+
await refreshParent(rootPath);
|
|
578
|
+
} else {
|
|
579
|
+
const node = findNode(state.rootNodes, path);
|
|
580
|
+
if (node && node.isDirectory) {
|
|
581
|
+
await loadChildren(node);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
notify();
|
|
585
|
+
emit({ type: "refresh", path });
|
|
586
|
+
},
|
|
587
|
+
setSearchQuery(query) {
|
|
588
|
+
state.searchQuery = query && query.trim() ? query.trim() : null;
|
|
589
|
+
notify();
|
|
590
|
+
},
|
|
591
|
+
async collectAllFiles() {
|
|
592
|
+
const result = [];
|
|
593
|
+
async function walk(dirPath) {
|
|
594
|
+
const entries = await adapter.readDir(dirPath);
|
|
595
|
+
for (const entry of entries) {
|
|
596
|
+
if (!filterFn(entry)) continue;
|
|
597
|
+
result.push(entry);
|
|
598
|
+
if (entry.isDirectory) {
|
|
599
|
+
await walk(entry.path);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
await walk(rootPath);
|
|
604
|
+
return result;
|
|
605
|
+
},
|
|
606
|
+
setFilter(fn) {
|
|
607
|
+
filterFn = fn ?? defaultFilter;
|
|
608
|
+
state.rootNodes = filterNodes(state.rootNodes);
|
|
609
|
+
notify();
|
|
610
|
+
},
|
|
611
|
+
setSort(fn) {
|
|
612
|
+
sortFn = fn ?? defaultSort;
|
|
613
|
+
sortNodes(state.rootNodes);
|
|
614
|
+
notify();
|
|
615
|
+
},
|
|
616
|
+
destroy() {
|
|
617
|
+
stopWatching();
|
|
618
|
+
listeners.clear();
|
|
619
|
+
}
|
|
620
|
+
};
|
|
621
|
+
return controller;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// src/react/context.ts
|
|
625
|
+
import { createContext, useContext } from "react";
|
|
626
|
+
var TreeContext = createContext(null);
|
|
627
|
+
function useTreeContext() {
|
|
628
|
+
const ctx = useContext(TreeContext);
|
|
629
|
+
if (!ctx) {
|
|
630
|
+
throw new Error("useTreeContext must be used within a <TreeProvider>");
|
|
631
|
+
}
|
|
632
|
+
return ctx;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// src/react/TreeProvider.tsx
|
|
636
|
+
import { jsx } from "react/jsx-runtime";
|
|
637
|
+
function TreeProvider({
|
|
638
|
+
adapter,
|
|
639
|
+
rootPath,
|
|
640
|
+
sort,
|
|
641
|
+
filter,
|
|
642
|
+
watchOptions,
|
|
643
|
+
onEvent,
|
|
644
|
+
children
|
|
645
|
+
}) {
|
|
646
|
+
const onEventRef = useRef(onEvent);
|
|
647
|
+
onEventRef.current = onEvent;
|
|
648
|
+
const controller = useMemo(() => {
|
|
649
|
+
return createFileTree({
|
|
650
|
+
adapter,
|
|
651
|
+
rootPath,
|
|
652
|
+
sort,
|
|
653
|
+
filter,
|
|
654
|
+
watchOptions,
|
|
655
|
+
onEvent: (event) => onEventRef.current?.(event)
|
|
656
|
+
});
|
|
657
|
+
}, [adapter, rootPath]);
|
|
658
|
+
const [state, setState] = useState(() => controller.getState());
|
|
659
|
+
useEffect(() => {
|
|
660
|
+
const unsub = controller.subscribe(setState);
|
|
661
|
+
controller.loadRoot();
|
|
662
|
+
return () => {
|
|
663
|
+
unsub();
|
|
664
|
+
controller.destroy();
|
|
665
|
+
};
|
|
666
|
+
}, [controller]);
|
|
667
|
+
const value = useMemo(() => ({ controller, state }), [controller, state]);
|
|
668
|
+
return /* @__PURE__ */ jsx(TreeContext.Provider, { value, children });
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
// src/react/useFileTree.ts
|
|
672
|
+
function useFileTree() {
|
|
673
|
+
const { controller, state } = useTreeContext();
|
|
674
|
+
return { ...state, controller };
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// src/react/useContextMenu.ts
|
|
678
|
+
import { useCallback, useState as useState2 } from "react";
|
|
679
|
+
function useContextMenu() {
|
|
680
|
+
const [menuState, setMenuState] = useState2({
|
|
681
|
+
isVisible: false,
|
|
682
|
+
x: 0,
|
|
683
|
+
y: 0,
|
|
684
|
+
targetPath: null
|
|
685
|
+
});
|
|
686
|
+
const show = useCallback((e, targetPath) => {
|
|
687
|
+
e.preventDefault();
|
|
688
|
+
e.stopPropagation();
|
|
689
|
+
setMenuState({
|
|
690
|
+
isVisible: true,
|
|
691
|
+
x: e.clientX,
|
|
692
|
+
y: e.clientY,
|
|
693
|
+
targetPath
|
|
694
|
+
});
|
|
695
|
+
}, []);
|
|
696
|
+
const hide = useCallback(() => {
|
|
697
|
+
setMenuState((prev) => ({ ...prev, isVisible: false, targetPath: null }));
|
|
698
|
+
}, []);
|
|
699
|
+
return { ...menuState, show, hide };
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// src/react/useExplorerKeybindings.ts
|
|
703
|
+
import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2 } from "react";
|
|
704
|
+
|
|
705
|
+
// src/core/keybindings.ts
|
|
706
|
+
var ExplorerCommands = {
|
|
707
|
+
DELETE: "explorer.delete",
|
|
708
|
+
RENAME: "explorer.rename",
|
|
709
|
+
NEW_FILE: "explorer.newFile",
|
|
710
|
+
NEW_FOLDER: "explorer.newFolder",
|
|
711
|
+
REFRESH: "explorer.refresh",
|
|
712
|
+
COLLAPSE_ALL: "explorer.collapseAll",
|
|
713
|
+
SELECT_ALL: "explorer.selectAll",
|
|
714
|
+
COPY_PATH: "explorer.copyPath"
|
|
715
|
+
};
|
|
716
|
+
var defaultExplorerKeybindings = [
|
|
717
|
+
{ key: "Delete", command: ExplorerCommands.DELETE, when: "explorerFocus" },
|
|
718
|
+
{ key: "F2", command: ExplorerCommands.RENAME, when: "explorerFocus" },
|
|
719
|
+
{ key: "Ctrl+N", command: ExplorerCommands.NEW_FILE, when: "explorerFocus" },
|
|
720
|
+
{ key: "Ctrl+Shift+N", command: ExplorerCommands.NEW_FOLDER, when: "explorerFocus" },
|
|
721
|
+
{ key: "Ctrl+R", command: ExplorerCommands.REFRESH, when: "explorerFocus" },
|
|
722
|
+
{ key: "Ctrl+Shift+E", command: ExplorerCommands.COLLAPSE_ALL, when: "explorerFocus" },
|
|
723
|
+
{ key: "Ctrl+A", command: ExplorerCommands.SELECT_ALL, when: "explorerFocus" },
|
|
724
|
+
{ key: "Ctrl+Shift+C", command: ExplorerCommands.COPY_PATH, when: "explorerFocus" }
|
|
725
|
+
];
|
|
726
|
+
|
|
727
|
+
// src/react/useExplorerKeybindings.ts
|
|
728
|
+
function useExplorerKeybindings(inputService, options) {
|
|
729
|
+
const { controller, state } = useTreeContext();
|
|
730
|
+
const stateRef = useRef2(state);
|
|
731
|
+
stateRef.current = state;
|
|
732
|
+
const optionsRef = useRef2(options);
|
|
733
|
+
optionsRef.current = options;
|
|
734
|
+
const getCreateTarget = useCallback2(() => {
|
|
735
|
+
const s = stateRef.current;
|
|
736
|
+
if (s.selectedPaths.size === 0) return { parentPath: s.rootPath };
|
|
737
|
+
const firstSelected = s.flatList.find((f) => s.selectedPaths.has(f.node.path));
|
|
738
|
+
if (!firstSelected) return { parentPath: s.rootPath };
|
|
739
|
+
if (firstSelected.node.isDirectory) return { parentPath: firstSelected.node.path };
|
|
740
|
+
const sep = firstSelected.node.path.includes("\\") ? "\\" : "/";
|
|
741
|
+
const idx = firstSelected.node.path.lastIndexOf(sep);
|
|
742
|
+
const parentPath = idx === -1 ? s.rootPath : firstSelected.node.path.slice(0, idx);
|
|
743
|
+
return { parentPath, insertAfterPath: firstSelected.node.path };
|
|
744
|
+
}, []);
|
|
745
|
+
useEffect2(() => {
|
|
746
|
+
if (!inputService) return;
|
|
747
|
+
const disposers = [];
|
|
748
|
+
const handlers = {
|
|
749
|
+
[ExplorerCommands.DELETE]: () => controller.deleteSelected(),
|
|
750
|
+
[ExplorerCommands.RENAME]: () => {
|
|
751
|
+
const s = stateRef.current;
|
|
752
|
+
if (s.selectedPaths.size === 1) {
|
|
753
|
+
controller.startRename(Array.from(s.selectedPaths)[0]);
|
|
754
|
+
}
|
|
755
|
+
},
|
|
756
|
+
[ExplorerCommands.NEW_FILE]: () => {
|
|
757
|
+
const t = getCreateTarget();
|
|
758
|
+
controller.startCreate(t.parentPath, false, t.insertAfterPath);
|
|
759
|
+
},
|
|
760
|
+
[ExplorerCommands.NEW_FOLDER]: () => {
|
|
761
|
+
const t = getCreateTarget();
|
|
762
|
+
controller.startCreate(t.parentPath, true, t.insertAfterPath);
|
|
763
|
+
},
|
|
764
|
+
[ExplorerCommands.REFRESH]: () => controller.refresh(),
|
|
765
|
+
[ExplorerCommands.COLLAPSE_ALL]: () => {
|
|
766
|
+
const s = stateRef.current;
|
|
767
|
+
for (const path of s.expandedPaths) {
|
|
768
|
+
controller.collapse(path);
|
|
769
|
+
}
|
|
770
|
+
},
|
|
771
|
+
[ExplorerCommands.SELECT_ALL]: () => controller.selectAll(),
|
|
772
|
+
[ExplorerCommands.COPY_PATH]: () => {
|
|
773
|
+
const s = stateRef.current;
|
|
774
|
+
const paths = Array.from(s.selectedPaths);
|
|
775
|
+
optionsRef.current?.onCopyPath?.(paths);
|
|
776
|
+
}
|
|
777
|
+
};
|
|
778
|
+
for (const [command, handler] of Object.entries(handlers)) {
|
|
779
|
+
disposers.push(inputService.registerCommand(command, handler));
|
|
780
|
+
}
|
|
781
|
+
return () => {
|
|
782
|
+
for (const dispose of disposers) {
|
|
783
|
+
dispose();
|
|
784
|
+
}
|
|
785
|
+
};
|
|
786
|
+
}, [inputService, controller, getCreateTarget]);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
// src/react/useExplorerFocus.ts
|
|
790
|
+
import { useCallback as useCallback3, useRef as useRef3 } from "react";
|
|
791
|
+
function useExplorerFocus(inputService, contextKey = "explorerFocus") {
|
|
792
|
+
const focused = useRef3(false);
|
|
793
|
+
const onFocus = useCallback3(() => {
|
|
794
|
+
if (!focused.current) {
|
|
795
|
+
focused.current = true;
|
|
796
|
+
inputService?.setContext(contextKey, true);
|
|
797
|
+
}
|
|
798
|
+
}, [inputService, contextKey]);
|
|
799
|
+
const onBlur = useCallback3(() => {
|
|
800
|
+
if (focused.current) {
|
|
801
|
+
focused.current = false;
|
|
802
|
+
inputService?.deleteContext(contextKey);
|
|
803
|
+
}
|
|
804
|
+
}, [inputService, contextKey]);
|
|
805
|
+
return { onFocus, onBlur, tabIndex: 0 };
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
// src/ui/TreeNodeRow.tsx
|
|
809
|
+
import { memo, useMemo as useMemo2 } from "react";
|
|
810
|
+
import { getIcon } from "material-file-icons";
|
|
811
|
+
|
|
812
|
+
// src/ui/InlineRename.tsx
|
|
813
|
+
import { useEffect as useEffect3, useRef as useRef4, useState as useState3 } from "react";
|
|
814
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
815
|
+
function InlineRename({ currentName, onCommit, onCancel }) {
|
|
816
|
+
const inputRef = useRef4(null);
|
|
817
|
+
const [value, setValue] = useState3(currentName);
|
|
818
|
+
useEffect3(() => {
|
|
819
|
+
const input = inputRef.current;
|
|
820
|
+
if (!input) return;
|
|
821
|
+
input.focus();
|
|
822
|
+
const dotIndex = currentName.lastIndexOf(".");
|
|
823
|
+
if (dotIndex > 0) {
|
|
824
|
+
input.setSelectionRange(0, dotIndex);
|
|
825
|
+
} else {
|
|
826
|
+
input.select();
|
|
827
|
+
}
|
|
828
|
+
}, [currentName]);
|
|
829
|
+
function handleKeyDown(e) {
|
|
830
|
+
e.stopPropagation();
|
|
831
|
+
if (e.key === "Enter") {
|
|
832
|
+
const trimmed = value.trim();
|
|
833
|
+
if (trimmed && trimmed !== currentName) {
|
|
834
|
+
onCommit(trimmed);
|
|
835
|
+
} else {
|
|
836
|
+
onCancel();
|
|
837
|
+
}
|
|
838
|
+
} else if (e.key === "Escape") {
|
|
839
|
+
onCancel();
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
function handleBlur() {
|
|
843
|
+
const trimmed = value.trim();
|
|
844
|
+
if (trimmed && trimmed !== currentName) {
|
|
845
|
+
onCommit(trimmed);
|
|
846
|
+
} else {
|
|
847
|
+
onCancel();
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
return /* @__PURE__ */ jsx2(
|
|
851
|
+
"input",
|
|
852
|
+
{
|
|
853
|
+
ref: inputRef,
|
|
854
|
+
className: "momoi-explorer-rename-input",
|
|
855
|
+
value,
|
|
856
|
+
onChange: (e) => setValue(e.target.value),
|
|
857
|
+
onKeyDown: handleKeyDown,
|
|
858
|
+
onBlur: handleBlur
|
|
859
|
+
}
|
|
860
|
+
);
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
// src/ui/TreeNodeRow.tsx
|
|
864
|
+
import { jsx as jsx3, jsxs } from "react/jsx-runtime";
|
|
865
|
+
function ChevronIcon() {
|
|
866
|
+
return /* @__PURE__ */ jsx3("svg", { viewBox: "0 0 16 16", xmlns: "http://www.w3.org/2000/svg", children: /* @__PURE__ */ jsx3("path", { d: "M6 4l4 4-4 4", stroke: "currentColor", strokeWidth: "1.5", fill: "none" }) });
|
|
867
|
+
}
|
|
868
|
+
function FolderIcon({ isExpanded }) {
|
|
869
|
+
return /* @__PURE__ */ jsx3("svg", { viewBox: "0 0 16 16", xmlns: "http://www.w3.org/2000/svg", children: isExpanded ? /* @__PURE__ */ jsx3("path", { d: "M1.5 3h5l1 1.5H14.5v9h-13z", fill: "#dcb67a", opacity: "0.9" }) : /* @__PURE__ */ jsx3("path", { d: "M1.5 2.5h5l1 1.5H14.5v10h-13z", fill: "#dcb67a" }) });
|
|
870
|
+
}
|
|
871
|
+
function MaterialFileIcon({ filename }) {
|
|
872
|
+
const svg = useMemo2(() => getIcon(filename).svg, [filename]);
|
|
873
|
+
return /* @__PURE__ */ jsx3("span", { className: "momoi-explorer-icon-inner", dangerouslySetInnerHTML: { __html: svg } });
|
|
874
|
+
}
|
|
875
|
+
var TreeNodeRow = memo(function TreeNodeRow2({
|
|
876
|
+
node,
|
|
877
|
+
depth,
|
|
878
|
+
isExpanded,
|
|
879
|
+
isSelected,
|
|
880
|
+
isRenaming,
|
|
881
|
+
onClick,
|
|
882
|
+
onDoubleClick,
|
|
883
|
+
onContextMenu,
|
|
884
|
+
onToggleExpand,
|
|
885
|
+
onCommitRename,
|
|
886
|
+
onCancelRename,
|
|
887
|
+
renderIcon,
|
|
888
|
+
renderBadge
|
|
889
|
+
}) {
|
|
890
|
+
return /* @__PURE__ */ jsxs(
|
|
891
|
+
"div",
|
|
892
|
+
{
|
|
893
|
+
className: "momoi-explorer-row",
|
|
894
|
+
"data-selected": isSelected,
|
|
895
|
+
"data-path": node.path,
|
|
896
|
+
onClick,
|
|
897
|
+
onDoubleClick,
|
|
898
|
+
onContextMenu,
|
|
899
|
+
children: [
|
|
900
|
+
/* @__PURE__ */ jsx3("span", { className: "momoi-explorer-indent", children: Array.from({ length: depth }, (_, i) => /* @__PURE__ */ jsx3("span", { className: "momoi-explorer-indent-guide" }, i)) }),
|
|
901
|
+
/* @__PURE__ */ jsx3(
|
|
902
|
+
"span",
|
|
903
|
+
{
|
|
904
|
+
className: "momoi-explorer-chevron",
|
|
905
|
+
"data-expanded": isExpanded,
|
|
906
|
+
"data-is-dir": node.isDirectory,
|
|
907
|
+
onClick: (e) => {
|
|
908
|
+
e.stopPropagation();
|
|
909
|
+
onToggleExpand();
|
|
910
|
+
},
|
|
911
|
+
children: /* @__PURE__ */ jsx3(ChevronIcon, {})
|
|
912
|
+
}
|
|
913
|
+
),
|
|
914
|
+
/* @__PURE__ */ jsx3("span", { className: "momoi-explorer-icon", children: renderIcon ? renderIcon(node, isExpanded) : node.isDirectory ? /* @__PURE__ */ jsx3(FolderIcon, { isExpanded }) : /* @__PURE__ */ jsx3(MaterialFileIcon, { filename: node.name }) }),
|
|
915
|
+
isRenaming ? /* @__PURE__ */ jsx3(
|
|
916
|
+
InlineRename,
|
|
917
|
+
{
|
|
918
|
+
currentName: node.name,
|
|
919
|
+
onCommit: onCommitRename,
|
|
920
|
+
onCancel: onCancelRename
|
|
921
|
+
}
|
|
922
|
+
) : /* @__PURE__ */ jsx3("span", { className: "momoi-explorer-name", children: node.name }),
|
|
923
|
+
renderBadge && /* @__PURE__ */ jsx3("span", { className: "momoi-explorer-badge", children: renderBadge(node) })
|
|
924
|
+
]
|
|
925
|
+
}
|
|
926
|
+
);
|
|
927
|
+
});
|
|
928
|
+
|
|
929
|
+
// src/ui/ContextMenu.tsx
|
|
930
|
+
import { useEffect as useEffect4, useRef as useRef5 } from "react";
|
|
931
|
+
import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
932
|
+
function ContextMenu({ items, x, y, onClose }) {
|
|
933
|
+
const menuRef = useRef5(null);
|
|
934
|
+
useEffect4(() => {
|
|
935
|
+
function handleClick(e) {
|
|
936
|
+
if (menuRef.current && !menuRef.current.contains(e.target)) {
|
|
937
|
+
onClose();
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
function handleKey(e) {
|
|
941
|
+
if (e.key === "Escape") onClose();
|
|
942
|
+
}
|
|
943
|
+
document.addEventListener("mousedown", handleClick);
|
|
944
|
+
document.addEventListener("keydown", handleKey);
|
|
945
|
+
return () => {
|
|
946
|
+
document.removeEventListener("mousedown", handleClick);
|
|
947
|
+
document.removeEventListener("keydown", handleKey);
|
|
948
|
+
};
|
|
949
|
+
}, [onClose]);
|
|
950
|
+
useEffect4(() => {
|
|
951
|
+
const menu = menuRef.current;
|
|
952
|
+
if (!menu) return;
|
|
953
|
+
const rect = menu.getBoundingClientRect();
|
|
954
|
+
if (rect.right > window.innerWidth) {
|
|
955
|
+
menu.style.left = `${window.innerWidth - rect.width - 4}px`;
|
|
956
|
+
}
|
|
957
|
+
if (rect.bottom > window.innerHeight) {
|
|
958
|
+
menu.style.top = `${window.innerHeight - rect.height - 4}px`;
|
|
959
|
+
}
|
|
960
|
+
}, [x, y]);
|
|
961
|
+
return /* @__PURE__ */ jsx4(
|
|
962
|
+
"div",
|
|
963
|
+
{
|
|
964
|
+
ref: menuRef,
|
|
965
|
+
className: "momoi-explorer-context-menu",
|
|
966
|
+
style: { left: x, top: y },
|
|
967
|
+
children: items.map((item) => {
|
|
968
|
+
if (item.separator) {
|
|
969
|
+
return /* @__PURE__ */ jsx4("div", { className: "momoi-explorer-context-menu-separator" }, item.id);
|
|
970
|
+
}
|
|
971
|
+
return /* @__PURE__ */ jsxs2(
|
|
972
|
+
"div",
|
|
973
|
+
{
|
|
974
|
+
className: "momoi-explorer-context-menu-item",
|
|
975
|
+
"data-disabled": item.disabled ?? false,
|
|
976
|
+
onClick: () => {
|
|
977
|
+
if (!item.disabled) {
|
|
978
|
+
item.action([]);
|
|
979
|
+
onClose();
|
|
980
|
+
}
|
|
981
|
+
},
|
|
982
|
+
children: [
|
|
983
|
+
/* @__PURE__ */ jsx4("span", { children: item.label }),
|
|
984
|
+
item.shortcut && /* @__PURE__ */ jsx4("span", { className: "momoi-explorer-context-menu-shortcut", children: item.shortcut })
|
|
985
|
+
]
|
|
986
|
+
},
|
|
987
|
+
item.id
|
|
988
|
+
);
|
|
989
|
+
})
|
|
990
|
+
}
|
|
991
|
+
);
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
// src/ui/TreeFilterBar.tsx
|
|
995
|
+
import { useCallback as useCallback4, useRef as useRef6 } from "react";
|
|
996
|
+
import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
997
|
+
function TreeFilterBar({
|
|
998
|
+
placeholder = "Filter files..."
|
|
999
|
+
}) {
|
|
1000
|
+
const { controller, state } = useTreeContext();
|
|
1001
|
+
const inputRef = useRef6(null);
|
|
1002
|
+
const handleChange = useCallback4((e) => {
|
|
1003
|
+
controller.setSearchQuery(e.target.value || null);
|
|
1004
|
+
}, [controller]);
|
|
1005
|
+
const handleKeyDown = useCallback4((e) => {
|
|
1006
|
+
if (e.key === "Escape") {
|
|
1007
|
+
controller.setSearchQuery(null);
|
|
1008
|
+
if (inputRef.current) inputRef.current.value = "";
|
|
1009
|
+
}
|
|
1010
|
+
}, [controller]);
|
|
1011
|
+
return /* @__PURE__ */ jsxs3("div", { className: "momoi-explorer-filter-bar", children: [
|
|
1012
|
+
/* @__PURE__ */ jsx5(
|
|
1013
|
+
"input",
|
|
1014
|
+
{
|
|
1015
|
+
ref: inputRef,
|
|
1016
|
+
className: "momoi-explorer-filter-input",
|
|
1017
|
+
type: "text",
|
|
1018
|
+
placeholder,
|
|
1019
|
+
defaultValue: state.searchQuery ?? "",
|
|
1020
|
+
onChange: handleChange,
|
|
1021
|
+
onKeyDown: handleKeyDown
|
|
1022
|
+
}
|
|
1023
|
+
),
|
|
1024
|
+
state.searchQuery && /* @__PURE__ */ jsx5(
|
|
1025
|
+
"span",
|
|
1026
|
+
{
|
|
1027
|
+
className: "momoi-explorer-filter-clear",
|
|
1028
|
+
onClick: () => {
|
|
1029
|
+
controller.setSearchQuery(null);
|
|
1030
|
+
if (inputRef.current) inputRef.current.value = "";
|
|
1031
|
+
},
|
|
1032
|
+
children: "\xD7"
|
|
1033
|
+
}
|
|
1034
|
+
)
|
|
1035
|
+
] });
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
// src/ui/FileExplorer.tsx
|
|
1039
|
+
import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1040
|
+
function FileExplorerInner({
|
|
1041
|
+
onOpen,
|
|
1042
|
+
renderIcon,
|
|
1043
|
+
renderBadge,
|
|
1044
|
+
contextMenuItems,
|
|
1045
|
+
showFilterBar,
|
|
1046
|
+
onControllerReady,
|
|
1047
|
+
inputService,
|
|
1048
|
+
onKeyDown
|
|
1049
|
+
}) {
|
|
1050
|
+
const { flatList, expandedPaths, selectedPaths, renamingPath, creatingState, rootPath, controller } = useFileTree();
|
|
1051
|
+
useEffect5(() => {
|
|
1052
|
+
onControllerReady?.(controller);
|
|
1053
|
+
}, [controller, onControllerReady]);
|
|
1054
|
+
useExplorerKeybindings(inputService ?? null);
|
|
1055
|
+
const focusProps = useExplorerFocus(inputService ?? null);
|
|
1056
|
+
const ctxMenu = useContextMenu();
|
|
1057
|
+
const rowItems = useMemo3(() => {
|
|
1058
|
+
const items = flatList.map((f) => ({
|
|
1059
|
+
type: "node",
|
|
1060
|
+
node: f.node,
|
|
1061
|
+
depth: f.depth
|
|
1062
|
+
}));
|
|
1063
|
+
if (!creatingState) return items;
|
|
1064
|
+
const { parentPath, isDirectory, insertAfterPath } = creatingState;
|
|
1065
|
+
if (insertAfterPath) {
|
|
1066
|
+
const idx = items.findIndex((item) => item.type === "node" && item.node.path === insertAfterPath);
|
|
1067
|
+
if (idx !== -1) {
|
|
1068
|
+
items.splice(idx + 1, 0, { type: "create", parentPath, isDirectory, depth: items[idx].depth });
|
|
1069
|
+
}
|
|
1070
|
+
} else if (parentPath === rootPath) {
|
|
1071
|
+
items.push({ type: "create", parentPath, isDirectory, depth: 0 });
|
|
1072
|
+
} else {
|
|
1073
|
+
const parentIdx = items.findIndex((item) => item.type === "node" && item.node.path === parentPath);
|
|
1074
|
+
if (parentIdx !== -1) {
|
|
1075
|
+
const parentDepth = items[parentIdx].depth;
|
|
1076
|
+
items.splice(parentIdx + 1, 0, { type: "create", parentPath, isDirectory, depth: parentDepth + 1 });
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
return items;
|
|
1080
|
+
}, [flatList, creatingState, rootPath]);
|
|
1081
|
+
const handleClick = useCallback5((path, isDirectory, e) => {
|
|
1082
|
+
if (e.shiftKey) {
|
|
1083
|
+
controller.select(path, "range");
|
|
1084
|
+
} else if (e.ctrlKey || e.metaKey) {
|
|
1085
|
+
controller.select(path, "toggle");
|
|
1086
|
+
} else {
|
|
1087
|
+
controller.select(path, "replace");
|
|
1088
|
+
if (isDirectory) {
|
|
1089
|
+
controller.toggleExpand(path);
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
}, [controller]);
|
|
1093
|
+
const handleDoubleClick = useCallback5((path, isDirectory) => {
|
|
1094
|
+
if (!isDirectory) {
|
|
1095
|
+
onOpen?.(path);
|
|
1096
|
+
}
|
|
1097
|
+
}, [onOpen]);
|
|
1098
|
+
const handleContextMenu = useCallback5((e, path) => {
|
|
1099
|
+
if (!selectedPaths.has(path)) {
|
|
1100
|
+
controller.select(path, "replace");
|
|
1101
|
+
}
|
|
1102
|
+
ctxMenu.show(e, path);
|
|
1103
|
+
}, [controller, selectedPaths, ctxMenu]);
|
|
1104
|
+
const handleBackgroundContextMenu = useCallback5((e) => {
|
|
1105
|
+
e.preventDefault();
|
|
1106
|
+
controller.clearSelection();
|
|
1107
|
+
ctxMenu.show(e, "");
|
|
1108
|
+
}, [controller, ctxMenu]);
|
|
1109
|
+
const menuItems = useMemo3(() => {
|
|
1110
|
+
if (!ctxMenu.isVisible || !contextMenuItems) return [];
|
|
1111
|
+
if (ctxMenu.targetPath === "") return contextMenuItems([]);
|
|
1112
|
+
const targetNodes = flatList.filter((f) => selectedPaths.has(f.node.path)).map((f) => f.node);
|
|
1113
|
+
return contextMenuItems(targetNodes);
|
|
1114
|
+
}, [ctxMenu.isVisible, ctxMenu.targetPath, contextMenuItems, flatList, selectedPaths]);
|
|
1115
|
+
return /* @__PURE__ */ jsxs4(
|
|
1116
|
+
"div",
|
|
1117
|
+
{
|
|
1118
|
+
style: { height: "100%", outline: "none" },
|
|
1119
|
+
onContextMenu: handleBackgroundContextMenu,
|
|
1120
|
+
onKeyDown,
|
|
1121
|
+
onFocus: focusProps.onFocus,
|
|
1122
|
+
onBlur: focusProps.onBlur,
|
|
1123
|
+
tabIndex: focusProps.tabIndex,
|
|
1124
|
+
children: [
|
|
1125
|
+
showFilterBar && /* @__PURE__ */ jsx6(TreeFilterBar, {}),
|
|
1126
|
+
/* @__PURE__ */ jsx6(
|
|
1127
|
+
Virtuoso,
|
|
1128
|
+
{
|
|
1129
|
+
totalCount: rowItems.length,
|
|
1130
|
+
fixedItemHeight: 22,
|
|
1131
|
+
increaseViewportBy: 200,
|
|
1132
|
+
computeItemKey: (index) => {
|
|
1133
|
+
const item = rowItems[index];
|
|
1134
|
+
return item.type === "create" ? `__create__${item.parentPath}` : item.node.path;
|
|
1135
|
+
},
|
|
1136
|
+
itemContent: (index) => {
|
|
1137
|
+
const item = rowItems[index];
|
|
1138
|
+
if (item.type === "create") {
|
|
1139
|
+
return /* @__PURE__ */ jsxs4("div", { className: "momoi-explorer-row", children: [
|
|
1140
|
+
/* @__PURE__ */ jsx6("span", { className: "momoi-explorer-indent", children: Array.from({ length: item.depth }, (_, i) => /* @__PURE__ */ jsx6("span", { className: "momoi-explorer-indent-guide" }, i)) }),
|
|
1141
|
+
/* @__PURE__ */ jsx6("span", { className: "momoi-explorer-chevron", "data-is-dir": "false", children: /* @__PURE__ */ jsx6("svg", { viewBox: "0 0 16 16", children: /* @__PURE__ */ jsx6("path", { d: "M6 4l4 4-4 4", stroke: "currentColor", strokeWidth: "1.5", fill: "none" }) }) }),
|
|
1142
|
+
/* @__PURE__ */ jsx6("span", { className: "momoi-explorer-icon", children: item.isDirectory ? "\u{1F4C1}" : "\u{1F4C4}" }),
|
|
1143
|
+
/* @__PURE__ */ jsx6(
|
|
1144
|
+
InlineRename,
|
|
1145
|
+
{
|
|
1146
|
+
currentName: "",
|
|
1147
|
+
onCommit: (name) => controller.commitCreate(name),
|
|
1148
|
+
onCancel: () => controller.cancelCreate()
|
|
1149
|
+
}
|
|
1150
|
+
)
|
|
1151
|
+
] });
|
|
1152
|
+
}
|
|
1153
|
+
const { node, depth } = item;
|
|
1154
|
+
return /* @__PURE__ */ jsx6(
|
|
1155
|
+
TreeNodeRow,
|
|
1156
|
+
{
|
|
1157
|
+
node,
|
|
1158
|
+
depth,
|
|
1159
|
+
isExpanded: expandedPaths.has(node.path),
|
|
1160
|
+
isSelected: selectedPaths.has(node.path),
|
|
1161
|
+
isRenaming: renamingPath === node.path,
|
|
1162
|
+
onClick: (e) => handleClick(node.path, node.isDirectory, e),
|
|
1163
|
+
onDoubleClick: () => handleDoubleClick(node.path, node.isDirectory),
|
|
1164
|
+
onContextMenu: (e) => handleContextMenu(e, node.path),
|
|
1165
|
+
onToggleExpand: () => controller.toggleExpand(node.path),
|
|
1166
|
+
onCommitRename: (name) => controller.commitRename(name),
|
|
1167
|
+
onCancelRename: () => controller.cancelRename(),
|
|
1168
|
+
renderIcon,
|
|
1169
|
+
renderBadge
|
|
1170
|
+
}
|
|
1171
|
+
);
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
),
|
|
1175
|
+
ctxMenu.isVisible && menuItems.length > 0 && /* @__PURE__ */ jsx6(
|
|
1176
|
+
ContextMenu,
|
|
1177
|
+
{
|
|
1178
|
+
items: menuItems,
|
|
1179
|
+
x: ctxMenu.x,
|
|
1180
|
+
y: ctxMenu.y,
|
|
1181
|
+
onClose: ctxMenu.hide
|
|
1182
|
+
}
|
|
1183
|
+
)
|
|
1184
|
+
]
|
|
1185
|
+
}
|
|
1186
|
+
);
|
|
1187
|
+
}
|
|
1188
|
+
function FileExplorer({
|
|
1189
|
+
adapter,
|
|
1190
|
+
rootPath,
|
|
1191
|
+
sort,
|
|
1192
|
+
filter,
|
|
1193
|
+
watchOptions,
|
|
1194
|
+
onEvent,
|
|
1195
|
+
onOpen,
|
|
1196
|
+
renderIcon,
|
|
1197
|
+
renderBadge,
|
|
1198
|
+
contextMenuItems,
|
|
1199
|
+
showFilterBar,
|
|
1200
|
+
onControllerReady,
|
|
1201
|
+
inputService,
|
|
1202
|
+
onKeyDown,
|
|
1203
|
+
className,
|
|
1204
|
+
style
|
|
1205
|
+
}) {
|
|
1206
|
+
return /* @__PURE__ */ jsx6("div", { className: `momoi-explorer ${className ?? ""}`, style, children: /* @__PURE__ */ jsx6(
|
|
1207
|
+
TreeProvider,
|
|
1208
|
+
{
|
|
1209
|
+
adapter,
|
|
1210
|
+
rootPath,
|
|
1211
|
+
sort,
|
|
1212
|
+
filter,
|
|
1213
|
+
watchOptions,
|
|
1214
|
+
onEvent,
|
|
1215
|
+
children: /* @__PURE__ */ jsx6(
|
|
1216
|
+
FileExplorerInner,
|
|
1217
|
+
{
|
|
1218
|
+
onOpen,
|
|
1219
|
+
renderIcon,
|
|
1220
|
+
renderBadge,
|
|
1221
|
+
contextMenuItems,
|
|
1222
|
+
showFilterBar,
|
|
1223
|
+
onControllerReady,
|
|
1224
|
+
inputService,
|
|
1225
|
+
onKeyDown
|
|
1226
|
+
}
|
|
1227
|
+
)
|
|
1228
|
+
}
|
|
1229
|
+
) });
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
// src/ui/QuickOpen.tsx
|
|
1233
|
+
import { useCallback as useCallback6, useEffect as useEffect6, useMemo as useMemo4, useRef as useRef7, useState as useState4 } from "react";
|
|
1234
|
+
import { getIcon as getIcon2 } from "material-file-icons";
|
|
1235
|
+
import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1236
|
+
function QuickOpenIcon({ filename }) {
|
|
1237
|
+
const svg = useMemo4(() => getIcon2(filename).svg, [filename]);
|
|
1238
|
+
return /* @__PURE__ */ jsx7("span", { className: "momoi-explorer-quickopen-icon", dangerouslySetInnerHTML: { __html: svg } });
|
|
1239
|
+
}
|
|
1240
|
+
function QuickOpen({
|
|
1241
|
+
controller,
|
|
1242
|
+
isOpen,
|
|
1243
|
+
onClose,
|
|
1244
|
+
onSelect,
|
|
1245
|
+
placeholder = "Search files by name...",
|
|
1246
|
+
maxResults = 50
|
|
1247
|
+
}) {
|
|
1248
|
+
const inputRef = useRef7(null);
|
|
1249
|
+
const [query, setQuery] = useState4("");
|
|
1250
|
+
const [allFiles, setAllFiles] = useState4([]);
|
|
1251
|
+
const [results, setResults] = useState4([]);
|
|
1252
|
+
const [selectedIndex, setSelectedIndex] = useState4(0);
|
|
1253
|
+
useEffect6(() => {
|
|
1254
|
+
if (!isOpen) return;
|
|
1255
|
+
controller.collectAllFiles().then(setAllFiles);
|
|
1256
|
+
}, [isOpen, controller]);
|
|
1257
|
+
useEffect6(() => {
|
|
1258
|
+
if (isOpen) {
|
|
1259
|
+
setQuery("");
|
|
1260
|
+
setResults([]);
|
|
1261
|
+
setSelectedIndex(0);
|
|
1262
|
+
setTimeout(() => inputRef.current?.focus(), 0);
|
|
1263
|
+
}
|
|
1264
|
+
}, [isOpen]);
|
|
1265
|
+
useEffect6(() => {
|
|
1266
|
+
if (!query) {
|
|
1267
|
+
setResults([]);
|
|
1268
|
+
setSelectedIndex(0);
|
|
1269
|
+
return;
|
|
1270
|
+
}
|
|
1271
|
+
const found = fuzzyFind(allFiles, query, maxResults);
|
|
1272
|
+
setResults(found);
|
|
1273
|
+
setSelectedIndex(0);
|
|
1274
|
+
}, [query, allFiles, maxResults]);
|
|
1275
|
+
const handleKeyDown = useCallback6((e) => {
|
|
1276
|
+
switch (e.key) {
|
|
1277
|
+
case "Escape":
|
|
1278
|
+
onClose();
|
|
1279
|
+
break;
|
|
1280
|
+
case "ArrowDown":
|
|
1281
|
+
e.preventDefault();
|
|
1282
|
+
setSelectedIndex((i) => Math.min(i + 1, results.length - 1));
|
|
1283
|
+
break;
|
|
1284
|
+
case "ArrowUp":
|
|
1285
|
+
e.preventDefault();
|
|
1286
|
+
setSelectedIndex((i) => Math.max(i - 1, 0));
|
|
1287
|
+
break;
|
|
1288
|
+
case "Enter":
|
|
1289
|
+
e.preventDefault();
|
|
1290
|
+
if (results[selectedIndex]) {
|
|
1291
|
+
onSelect(results[selectedIndex]);
|
|
1292
|
+
onClose();
|
|
1293
|
+
}
|
|
1294
|
+
break;
|
|
1295
|
+
}
|
|
1296
|
+
}, [onClose, onSelect, results, selectedIndex]);
|
|
1297
|
+
if (!isOpen) return null;
|
|
1298
|
+
return /* @__PURE__ */ jsx7("div", { className: "momoi-explorer-quickopen-overlay", onClick: onClose, children: /* @__PURE__ */ jsxs5(
|
|
1299
|
+
"div",
|
|
1300
|
+
{
|
|
1301
|
+
className: "momoi-explorer-quickopen",
|
|
1302
|
+
onClick: (e) => e.stopPropagation(),
|
|
1303
|
+
children: [
|
|
1304
|
+
/* @__PURE__ */ jsx7(
|
|
1305
|
+
"input",
|
|
1306
|
+
{
|
|
1307
|
+
ref: inputRef,
|
|
1308
|
+
className: "momoi-explorer-quickopen-input",
|
|
1309
|
+
type: "text",
|
|
1310
|
+
placeholder,
|
|
1311
|
+
value: query,
|
|
1312
|
+
onChange: (e) => setQuery(e.target.value),
|
|
1313
|
+
onKeyDown: handleKeyDown
|
|
1314
|
+
}
|
|
1315
|
+
),
|
|
1316
|
+
results.length > 0 && /* @__PURE__ */ jsx7("div", { className: "momoi-explorer-quickopen-results", children: results.map((entry, i) => /* @__PURE__ */ jsxs5(
|
|
1317
|
+
"div",
|
|
1318
|
+
{
|
|
1319
|
+
className: "momoi-explorer-quickopen-item",
|
|
1320
|
+
"data-selected": i === selectedIndex,
|
|
1321
|
+
onMouseEnter: () => setSelectedIndex(i),
|
|
1322
|
+
onClick: () => {
|
|
1323
|
+
onSelect(entry);
|
|
1324
|
+
onClose();
|
|
1325
|
+
},
|
|
1326
|
+
children: [
|
|
1327
|
+
/* @__PURE__ */ jsx7(QuickOpenIcon, { filename: entry.name }),
|
|
1328
|
+
/* @__PURE__ */ jsx7("span", { className: "momoi-explorer-quickopen-name", children: entry.name }),
|
|
1329
|
+
/* @__PURE__ */ jsx7("span", { className: "momoi-explorer-quickopen-path", children: entry.path })
|
|
1330
|
+
]
|
|
1331
|
+
},
|
|
1332
|
+
entry.path
|
|
1333
|
+
)) }),
|
|
1334
|
+
query && results.length === 0 && /* @__PURE__ */ jsx7("div", { className: "momoi-explorer-quickopen-empty", children: "No matching files" })
|
|
1335
|
+
]
|
|
1336
|
+
}
|
|
1337
|
+
) });
|
|
1338
|
+
}
|
|
1339
|
+
export {
|
|
1340
|
+
ContextMenu,
|
|
1341
|
+
FileExplorer,
|
|
1342
|
+
InlineRename,
|
|
1343
|
+
QuickOpen,
|
|
1344
|
+
TreeFilterBar,
|
|
1345
|
+
TreeNodeRow
|
|
1346
|
+
};
|
|
1347
|
+
//# sourceMappingURL=ui.js.map
|