mirror of
https://github.com/wavetermdev/waveterm.git
synced 2025-01-02 18:39:05 +01:00
Directory Search (#82)
This adds a frontend directory search by filtering out files that don't match. It also allows navigation of the directory using the arrow keys while maintaining focus on the search box.
This commit is contained in:
parent
f9236fc18b
commit
5da3257031
@ -50,11 +50,15 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
|
|
||||||
|
&.focused {
|
||||||
|
background-color: var(--accent-color);
|
||||||
|
}
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
background-color: var(--accent-color);
|
background-color: var(--accent-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover:not(:focus) {
|
&:hover:not(:focus):not(.focused) {
|
||||||
background-color: var(--highlight-bg-color);
|
background-color: var(--highlight-bg-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,3 +82,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dir-table-search-line {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.7rem;
|
||||||
|
|
||||||
|
.dir-table-search-box {
|
||||||
|
background-color: var(--panel-bg-color);
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
border: none;
|
||||||
|
width: 15rem;
|
||||||
|
color: var(--main-text-color);
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline-color: var(--accent-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -22,6 +22,9 @@ import "./directorypreview.less";
|
|||||||
interface DirectoryTableProps {
|
interface DirectoryTableProps {
|
||||||
data: FileInfo[];
|
data: FileInfo[];
|
||||||
cwd: string;
|
cwd: string;
|
||||||
|
focusIndex: number;
|
||||||
|
enter: boolean;
|
||||||
|
setFocusIndex: (_: number) => void;
|
||||||
setFileName: (_: string) => void;
|
setFileName: (_: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,7 +154,7 @@ function cleanMimetype(input: string): string {
|
|||||||
return truncated.trim();
|
return truncated.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
function DirectoryTable({ data, cwd, setFileName }: DirectoryTableProps) {
|
function DirectoryTable({ data, cwd, focusIndex, enter, setFocusIndex, setFileName }: DirectoryTableProps) {
|
||||||
let settings = jotai.useAtomValue(atoms.settingsConfigAtom);
|
let settings = jotai.useAtomValue(atoms.settingsConfigAtom);
|
||||||
const getIconFromMimeType = React.useCallback(
|
const getIconFromMimeType = React.useCallback(
|
||||||
(mimeType: string): string => {
|
(mimeType: string): string => {
|
||||||
@ -279,9 +282,23 @@ function DirectoryTable({ data, cwd, setFileName }: DirectoryTableProps) {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
{table.getState().columnSizingInfo.isResizingColumn ? (
|
{table.getState().columnSizingInfo.isResizingColumn ? (
|
||||||
<MemoizedTableBody table={table} cwd={cwd} setFileName={setFileName} />
|
<MemoizedTableBody
|
||||||
|
table={table}
|
||||||
|
cwd={cwd}
|
||||||
|
focusIndex={focusIndex}
|
||||||
|
enter={enter}
|
||||||
|
setFileName={setFileName}
|
||||||
|
setFocusIndex={setFocusIndex}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<TableBody table={table} cwd={cwd} setFileName={setFileName} />
|
<TableBody
|
||||||
|
table={table}
|
||||||
|
cwd={cwd}
|
||||||
|
focusIndex={focusIndex}
|
||||||
|
enter={enter}
|
||||||
|
setFileName={setFileName}
|
||||||
|
setFocusIndex={setFocusIndex}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -290,24 +307,37 @@ function DirectoryTable({ data, cwd, setFileName }: DirectoryTableProps) {
|
|||||||
interface TableBodyProps {
|
interface TableBodyProps {
|
||||||
table: Table<FileInfo>;
|
table: Table<FileInfo>;
|
||||||
cwd: string;
|
cwd: string;
|
||||||
|
focusIndex: number;
|
||||||
|
enter: boolean;
|
||||||
|
setFocusIndex: (_: number) => void;
|
||||||
setFileName: (_: string) => void;
|
setFileName: (_: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function TableBody({ table, cwd, setFileName }: TableBodyProps) {
|
function TableBody({ table, cwd, focusIndex, enter, setFocusIndex, setFileName }: TableBodyProps) {
|
||||||
let [refresh, setRefresh] = React.useState(false);
|
let [refresh, setRefresh] = React.useState(false);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const selected = (table.getSortedRowModel()?.flatRows[focusIndex]?.getValue("path") as string) ?? null;
|
||||||
|
if (selected != null) {
|
||||||
|
console.log("yipee");
|
||||||
|
const fullPath = cwd.concat("/", selected);
|
||||||
|
setFileName(fullPath);
|
||||||
|
}
|
||||||
|
}, [enter]);
|
||||||
|
|
||||||
|
table.getRow;
|
||||||
return (
|
return (
|
||||||
<div className="dir-table-body">
|
<div className="dir-table-body">
|
||||||
{table.getRowModel().rows.map((row) => (
|
{table.getRowModel().rows.map((row, idx) => (
|
||||||
<div
|
<div
|
||||||
className="dir-table-body-row"
|
className={clsx("dir-table-body-row", { focused: focusIndex === idx })}
|
||||||
key={row.id}
|
key={row.id}
|
||||||
tabIndex={0}
|
|
||||||
onDoubleClick={() => {
|
onDoubleClick={() => {
|
||||||
const newFileName = row.getValue("path") as string;
|
const newFileName = row.getValue("path") as string;
|
||||||
const fullPath = cwd.concat("/", newFileName);
|
const fullPath = cwd.concat("/", newFileName);
|
||||||
setFileName(fullPath);
|
setFileName(fullPath);
|
||||||
}}
|
}}
|
||||||
|
onClick={() => setFocusIndex(idx)}
|
||||||
onContextMenu={(e) => handleFileContextMenu(e, cwd.concat("/", row.getValue("path") as string))}
|
onContextMenu={(e) => handleFileContextMenu(e, cwd.concat("/", row.getValue("path") as string))}
|
||||||
>
|
>
|
||||||
{row.getVisibleCells().map((cell) => {
|
{row.getVisibleCells().map((cell) => {
|
||||||
@ -333,15 +363,98 @@ const MemoizedTableBody = React.memo(
|
|||||||
) as typeof TableBody;
|
) as typeof TableBody;
|
||||||
|
|
||||||
interface DirectoryPreviewProps {
|
interface DirectoryPreviewProps {
|
||||||
contentAtom: jotai.Atom<Promise<string>>;
|
|
||||||
fileNameAtom: jotai.WritableAtom<string, [string], void>;
|
fileNameAtom: jotai.WritableAtom<string, [string], void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function DirectoryPreview({ contentAtom, fileNameAtom }: DirectoryPreviewProps) {
|
function DirectoryPreview({ fileNameAtom }: DirectoryPreviewProps) {
|
||||||
const contentText = jotai.useAtomValue(contentAtom);
|
const [searchText, setSearchText] = React.useState("");
|
||||||
let content: FileInfo[] = JSON.parse(contentText);
|
let [focusIndex, setFocusIndex] = React.useState(0);
|
||||||
|
const [content, setContent] = React.useState<FileInfo[]>([]);
|
||||||
let [fileName, setFileName] = jotai.useAtom(fileNameAtom);
|
let [fileName, setFileName] = jotai.useAtom(fileNameAtom);
|
||||||
return <DirectoryTable data={content} cwd={fileName} setFileName={setFileName} />;
|
const [enter, setEnter] = React.useState(false);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const getContent = async () => {
|
||||||
|
const file = await services.FileService.ReadFile(fileName);
|
||||||
|
const serializedContent = util.base64ToString(file?.data64);
|
||||||
|
let content: FileInfo[] = JSON.parse(serializedContent);
|
||||||
|
let filtered = content.filter((fileInfo) => {
|
||||||
|
return fileInfo.path.toLowerCase().includes(searchText);
|
||||||
|
});
|
||||||
|
setContent(filtered);
|
||||||
|
};
|
||||||
|
getContent();
|
||||||
|
}, [fileName, searchText]);
|
||||||
|
|
||||||
|
const handleKeyDown = React.useCallback(
|
||||||
|
(e) => {
|
||||||
|
switch (e.key) {
|
||||||
|
case "Escape":
|
||||||
|
//todo: escape block focus
|
||||||
|
break;
|
||||||
|
case "ArrowUp":
|
||||||
|
e.preventDefault();
|
||||||
|
setFocusIndex((idx) => Math.max(idx - 1, 0));
|
||||||
|
break;
|
||||||
|
case "ArrowDown":
|
||||||
|
e.preventDefault();
|
||||||
|
setFocusIndex((idx) => Math.min(idx + 1, content.length - 1));
|
||||||
|
break;
|
||||||
|
case "Enter":
|
||||||
|
e.preventDefault();
|
||||||
|
console.log("enter thinks focus Index is ", focusIndex);
|
||||||
|
let newFileName = content[focusIndex].path;
|
||||||
|
console.log(
|
||||||
|
"enter thinks contents are",
|
||||||
|
content.slice(0, focusIndex + 1).map((fi) => fi.path)
|
||||||
|
);
|
||||||
|
setEnter((current) => !current);
|
||||||
|
/*
|
||||||
|
const fullPath = fileName.concat("/", newFileName);
|
||||||
|
setFileName(fullPath);
|
||||||
|
*/
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[content, focusIndex, setEnter]
|
||||||
|
);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
console.log(focusIndex);
|
||||||
|
}, [focusIndex]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
document.addEventListener("keydown", handleKeyDown);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener("keydown", handleKeyDown);
|
||||||
|
};
|
||||||
|
}, [handleKeyDown]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="dir-table-search-line">
|
||||||
|
<label>Search:</label>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="dir-table-search-box"
|
||||||
|
onChange={(e) => setSearchText(e.target.value.toLowerCase())}
|
||||||
|
maxLength={400}
|
||||||
|
autoFocus={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<DirectoryTable
|
||||||
|
data={content}
|
||||||
|
cwd={fileName}
|
||||||
|
focusIndex={focusIndex}
|
||||||
|
enter={enter}
|
||||||
|
setFileName={setFileName}
|
||||||
|
setFocusIndex={setFocusIndex}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { DirectoryPreview };
|
export { DirectoryPreview };
|
||||||
|
@ -251,7 +251,7 @@ function PreviewView({ blockId }: { blockId: string }) {
|
|||||||
) {
|
) {
|
||||||
specializedView = <CodeEditPreview readonly={true} contentAtom={fileContentAtom} filename={fileName} />;
|
specializedView = <CodeEditPreview readonly={true} contentAtom={fileContentAtom} filename={fileName} />;
|
||||||
} else if (mimeType === "directory") {
|
} else if (mimeType === "directory") {
|
||||||
specializedView = <DirectoryPreview contentAtom={fileContentAtom} fileNameAtom={fileNameAtom} />;
|
specializedView = <DirectoryPreview fileNameAtom={fileNameAtom} />;
|
||||||
} else {
|
} else {
|
||||||
specializedView = (
|
specializedView = (
|
||||||
<div className="view-preview">
|
<div className="view-preview">
|
||||||
|
Loading…
Reference in New Issue
Block a user