Enhance FilesView with breadcrumbs, drafts, and editor UX (#363)
* feat: enable search panel in CodeMirrorEditor Add enableSearch and searchOpen props to toggle search UI Wire CodeMirror search extension and keybindings into the editor Automatically open or close the search panel based on searchOpen * feat(files): add breadcrumb navigation and inline comments Add breadcrumb trail derived from file path and root to navigate directories Introduce inline comment UI with draft storage for per-file discussions Enable long-press gestures on items to reveal quick actions * feat: add useLongPress hook for long-press gestures Provide a reusable long-press hook with onLongPress and onTap callbacks. Support pointer and touch events with movement threshold to cancel. Optionally vibrate on long press for haptic feedback. * feat: add expandedPaths state and expand actions for file tabs Introduce expandedPaths per root to remember expanded folders Add actions to toggle, expand a path, and expand multiple paths Initialize expandedPaths for new roots and clamp state to valid roots * feat: add onSearchOpenChange prop to CodeMirrorEditor Expose onSearchOpenChange callback prop for search state Notify parent when search panel visibility toggles
This commit is contained in:
@@ -6,6 +6,7 @@ import { getSafeStorage } from './utils/safeStorage';
|
||||
type RootTabsState = {
|
||||
openPaths: string[];
|
||||
selectedPath: string | null;
|
||||
expandedPaths: string[];
|
||||
touchedAt: number;
|
||||
};
|
||||
|
||||
@@ -19,6 +20,9 @@ type FilesViewTabsActions = {
|
||||
removeOpenPathsByPrefix: (root: string, prefixPath: string) => void;
|
||||
setSelectedPath: (root: string, path: string | null) => void;
|
||||
ensureSelectedPath: (root: string) => void;
|
||||
toggleExpandedPath: (root: string, path: string) => void;
|
||||
expandPath: (root: string, path: string) => void;
|
||||
expandPaths: (root: string, paths: string[]) => void;
|
||||
};
|
||||
|
||||
export type FilesViewTabsStore = FilesViewTabsState & FilesViewTabsActions;
|
||||
@@ -43,7 +47,7 @@ const touchRoot = (prev: RootTabsState | undefined): RootTabsState => {
|
||||
if (prev) {
|
||||
return { ...prev, touchedAt: Date.now() };
|
||||
}
|
||||
return { openPaths: [], selectedPath: null, touchedAt: Date.now() };
|
||||
return { openPaths: [], selectedPath: null, expandedPaths: [], touchedAt: Date.now() };
|
||||
};
|
||||
|
||||
export const useFilesViewTabsStore = create<FilesViewTabsStore>()(
|
||||
@@ -199,6 +203,92 @@ export const useFilesViewTabsStore = create<FilesViewTabsStore>()(
|
||||
|
||||
get().setSelectedPath(normalizedRoot, first);
|
||||
},
|
||||
|
||||
toggleExpandedPath: (root, path) => {
|
||||
const normalizedRoot = normalizePath((root || '').trim());
|
||||
const normalizedPath = normalizePath((path || '').trim());
|
||||
if (!normalizedRoot || !normalizedPath) {
|
||||
return;
|
||||
}
|
||||
|
||||
set((state) => {
|
||||
const prev = state.byRoot[normalizedRoot];
|
||||
const current = touchRoot(prev);
|
||||
const isExpanded = current.expandedPaths.includes(normalizedPath);
|
||||
const nextExpandedPaths = isExpanded
|
||||
? current.expandedPaths.filter((p) => p !== normalizedPath)
|
||||
: [...current.expandedPaths, normalizedPath];
|
||||
|
||||
if (prev && prev.expandedPaths === nextExpandedPaths && prev.selectedPath === current.selectedPath && prev.openPaths === current.openPaths) {
|
||||
return state;
|
||||
}
|
||||
|
||||
const byRoot = {
|
||||
...state.byRoot,
|
||||
[normalizedRoot]: {
|
||||
...current,
|
||||
expandedPaths: nextExpandedPaths,
|
||||
},
|
||||
};
|
||||
return { byRoot: clampRoots(byRoot, 20) };
|
||||
});
|
||||
},
|
||||
|
||||
expandPath: (root, path) => {
|
||||
const normalizedRoot = normalizePath((root || '').trim());
|
||||
const normalizedPath = normalizePath((path || '').trim());
|
||||
if (!normalizedRoot || !normalizedPath) {
|
||||
return;
|
||||
}
|
||||
|
||||
set((state) => {
|
||||
const prev = state.byRoot[normalizedRoot];
|
||||
const current = touchRoot(prev);
|
||||
const isExpanded = current.expandedPaths.includes(normalizedPath);
|
||||
|
||||
if (isExpanded && prev) {
|
||||
return state;
|
||||
}
|
||||
|
||||
const byRoot = {
|
||||
...state.byRoot,
|
||||
[normalizedRoot]: {
|
||||
...current,
|
||||
expandedPaths: [...current.expandedPaths, normalizedPath],
|
||||
},
|
||||
};
|
||||
return { byRoot: clampRoots(byRoot, 20) };
|
||||
});
|
||||
},
|
||||
|
||||
expandPaths: (root, paths) => {
|
||||
const normalizedRoot = normalizePath((root || '').trim());
|
||||
if (!normalizedRoot || !paths || paths.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const normalizedPaths = paths.map((p) => normalizePath((p || '').trim())).filter(Boolean);
|
||||
|
||||
set((state) => {
|
||||
const prev = state.byRoot[normalizedRoot];
|
||||
const current = touchRoot(prev);
|
||||
const existingPaths = new Set(current.expandedPaths);
|
||||
const newPaths = normalizedPaths.filter((p) => !existingPaths.has(p));
|
||||
|
||||
if (newPaths.length === 0) {
|
||||
return state;
|
||||
}
|
||||
|
||||
const byRoot = {
|
||||
...state.byRoot,
|
||||
[normalizedRoot]: {
|
||||
...current,
|
||||
expandedPaths: [...current.expandedPaths, ...newPaths],
|
||||
},
|
||||
};
|
||||
return { byRoot: clampRoots(byRoot, 20) };
|
||||
});
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: 'files-view-tabs-store',
|
||||
|
||||
Reference in New Issue
Block a user