Added file explorer history navigation
This commit is contained in:
parent
7334b6ceb5
commit
e19d284a91
3 changed files with 97 additions and 17 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
import { useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { useVirtualRoot } from "../../../hooks/virtual-drive/virtualRootContext.js";
|
import { useVirtualRoot } from "../../../hooks/virtual-drive/virtualRootContext.js";
|
||||||
import styles from "./FileExplorer.module.css";
|
import styles from "./FileExplorer.module.css";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
@ -18,6 +18,7 @@ import AppsManager from "../../../features/applications/applications.js";
|
||||||
import { APPS } from "../../../constants/applications.js";
|
import { APPS } from "../../../constants/applications.js";
|
||||||
import { TITLE_SEPARATOR } from "../../../constants/windows.js";
|
import { TITLE_SEPARATOR } from "../../../constants/windows.js";
|
||||||
import { FileProperties } from "../../modals/file-properties/FileProperties.jsx";
|
import { FileProperties } from "../../modals/file-properties/FileProperties.jsx";
|
||||||
|
import { useHistory } from "../../../hooks/utils/history.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import("../../windows/WindowView.jsx").windowProps} props
|
* @param {import("../../windows/WindowView.jsx").windowProps} props
|
||||||
|
|
@ -28,6 +29,7 @@ export function FileExplorer({ startPath, app, modalsManager }) {
|
||||||
const [path, setPath] = useState(currentDirectory?.path ?? "");
|
const [path, setPath] = useState(currentDirectory?.path ?? "");
|
||||||
const windowsManager = useWindowsManager();
|
const windowsManager = useWindowsManager();
|
||||||
const [showHidden] = useState(true);
|
const [showHidden] = useState(true);
|
||||||
|
const { history, stateIndex, pushState, undo, redo, undoAvailable, redoAvailable } = useHistory(currentDirectory.path);
|
||||||
|
|
||||||
const { openWindowedModal } = useWindowedModal({ modalsManager });
|
const { openWindowedModal } = useWindowedModal({ modalsManager });
|
||||||
const { onContextMenu: onContextMenuFile } = useContextMenu({ modalsManager, Actions: (props) =>
|
const { onContextMenu: onContextMenuFile } = useContextMenu({ modalsManager, Actions: (props) =>
|
||||||
|
|
@ -46,15 +48,6 @@ export function FileExplorer({ startPath, app, modalsManager }) {
|
||||||
Modal: (props) =>
|
Modal: (props) =>
|
||||||
<FileProperties file={file} {...props}/>
|
<FileProperties file={file} {...props}/>
|
||||||
});
|
});
|
||||||
// modalsManager.open(new Modal(FileProperties)
|
|
||||||
// .setPosition(new Vector2(positionX, positionY))
|
|
||||||
// .setProps({
|
|
||||||
// triggerParams: params,
|
|
||||||
// className: STYLES.CONTEXT_MENU,
|
|
||||||
// onAnyTrigger: () => {
|
|
||||||
// newModal.close();
|
|
||||||
// }
|
|
||||||
// }));
|
|
||||||
}}/>
|
}}/>
|
||||||
</Actions>
|
</Actions>
|
||||||
});
|
});
|
||||||
|
|
@ -76,7 +69,10 @@ export function FileExplorer({ startPath, app, modalsManager }) {
|
||||||
// }
|
// }
|
||||||
// });
|
// });
|
||||||
|
|
||||||
const changeDirectory = (path, absolute = false) => {
|
const changeDirectory = useCallback((path, absolute = false) => {
|
||||||
|
if (path == null)
|
||||||
|
return;
|
||||||
|
|
||||||
if (currentDirectory == null)
|
if (currentDirectory == null)
|
||||||
absolute = true;
|
absolute = true;
|
||||||
|
|
||||||
|
|
@ -84,14 +80,27 @@ export function FileExplorer({ startPath, app, modalsManager }) {
|
||||||
|
|
||||||
console.debug(directory);
|
console.debug(directory);
|
||||||
|
|
||||||
if (directory) {
|
if (directory != null) {
|
||||||
|
setCurrentDirectory(directory);
|
||||||
|
setPath(directory.root ? "/" : directory.path);
|
||||||
|
pushState(directory.path);
|
||||||
|
}
|
||||||
|
}, [currentDirectory, pushState, virtualRoot]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (history.length === 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const path = history[stateIndex];
|
||||||
|
const directory = virtualRoot.navigate(path);
|
||||||
|
if (directory != null) {
|
||||||
setCurrentDirectory(directory);
|
setCurrentDirectory(directory);
|
||||||
setPath(directory.root ? "/" : directory.path);
|
setPath(directory.root ? "/" : directory.path);
|
||||||
}
|
}
|
||||||
};
|
}, [history, stateIndex, virtualRoot]);
|
||||||
|
|
||||||
const onPathChange = (event) => {
|
const onPathChange = (event) => {
|
||||||
return setPath(event.target.value);
|
setPath(event.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onKeyDown = (event) => {
|
const onKeyDown = (event) => {
|
||||||
|
|
@ -113,10 +122,22 @@ export function FileExplorer({ startPath, app, modalsManager }) {
|
||||||
return (
|
return (
|
||||||
<div className={styles.Container}>
|
<div className={styles.Container}>
|
||||||
<div className={styles.Header}>
|
<div className={styles.Header}>
|
||||||
<button title="Back" tabIndex={0} className={styles["Icon-button"]}>
|
<button
|
||||||
|
title="Back"
|
||||||
|
tabIndex={0}
|
||||||
|
className={styles["Icon-button"]}
|
||||||
|
onClick={undo}
|
||||||
|
disabled={!undoAvailable}
|
||||||
|
>
|
||||||
<FontAwesomeIcon icon={faCaretLeft}/>
|
<FontAwesomeIcon icon={faCaretLeft}/>
|
||||||
</button>
|
</button>
|
||||||
<button title="Forward" tabIndex={0} className={styles["Icon-button"]}>
|
<button
|
||||||
|
title="Forward"
|
||||||
|
tabIndex={0}
|
||||||
|
className={styles["Icon-button"]}
|
||||||
|
onClick={redo}
|
||||||
|
disabled={!redoAvailable}
|
||||||
|
>
|
||||||
<FontAwesomeIcon icon={faCaretRight}/>
|
<FontAwesomeIcon icon={faCaretRight}/>
|
||||||
</button>
|
</button>
|
||||||
<button title="Up" tabIndex={0} className={styles["Icon-button"]} onClick={() => { changeDirectory(".."); }}>
|
<button title="Up" tabIndex={0} className={styles["Icon-button"]} onClick={() => { changeDirectory(".."); }}>
|
||||||
|
|
|
||||||
|
|
@ -20,11 +20,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.Icon-button {
|
.Icon-button {
|
||||||
|
--color: var(--foreground-color-a);
|
||||||
|
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 1.25rem;
|
height: 1.25rem;
|
||||||
width: auto;
|
width: auto;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
color: var(--foreground-a);
|
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
|
@ -51,10 +52,19 @@
|
||||||
transform: scale(150%);
|
transform: scale(150%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.Icon-button:disabled {
|
||||||
|
--color: var(--foreground-color-c);
|
||||||
|
}
|
||||||
|
|
||||||
.Icon-button svg {
|
.Icon-button svg {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.Icon-button svg path {
|
||||||
|
fill: var(--color);
|
||||||
|
transition: fill 100ms ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
.Path-input {
|
.Path-input {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 0.25rem 0.5rem;
|
padding: 0.25rem 0.5rem;
|
||||||
|
|
|
||||||
49
src/hooks/utils/history.js
Normal file
49
src/hooks/utils/history.js
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
import { useState } from "react";
|
||||||
|
import { clamp } from "../../features/math/clamp.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {*} initialState
|
||||||
|
* @returns {{
|
||||||
|
* history: *[],
|
||||||
|
* stateIndex: number,
|
||||||
|
* pushState: Function,
|
||||||
|
* undo: Function,
|
||||||
|
* redo: Function,
|
||||||
|
* undoAvailable: boolean,
|
||||||
|
* redoAvailable: boolean
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
export function useHistory(initialState) {
|
||||||
|
const [history, setHistory] = useState(initialState ? [initialState] : []);
|
||||||
|
const [stateIndex, setStateIndex] = useState(0);
|
||||||
|
|
||||||
|
const pushState = (state) => {
|
||||||
|
if (state === history[0])
|
||||||
|
return;
|
||||||
|
|
||||||
|
const newHistory = [
|
||||||
|
state,
|
||||||
|
...history.slice(stateIndex, history.length)
|
||||||
|
];
|
||||||
|
|
||||||
|
setHistory(newHistory);
|
||||||
|
setStateIndex(0);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateStateIndex = (delta) => {
|
||||||
|
const index = clamp(stateIndex + delta, 0, history.length - 1);
|
||||||
|
|
||||||
|
if (index === stateIndex)
|
||||||
|
return;
|
||||||
|
|
||||||
|
setStateIndex(index);
|
||||||
|
};
|
||||||
|
|
||||||
|
const undo = () => updateStateIndex(1);
|
||||||
|
const redo = () => updateStateIndex(-1);
|
||||||
|
|
||||||
|
const undoAvailable = (stateIndex < history.length - 1);
|
||||||
|
const redoAvailable = (stateIndex >= 1);
|
||||||
|
|
||||||
|
return { history, stateIndex, pushState, undo, redo, undoAvailable, redoAvailable };
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue