Memoize more stuff

This commit is contained in:
Dustin Brett
2025-02-09 14:04:58 -08:00
parent 5a2edfd7c7
commit 14731ccda6
17 changed files with 425 additions and 318 deletions

View File

@@ -58,10 +58,12 @@ const Run: FC<ComponentProcessProps> = ({ id }) => {
const [isInputFocused, setIsInputFocused] = useState(true);
const [isEmptyInput, setIsEmptyInput] = useState(!runHistory[0]);
const [running, setRunning] = useState(false);
const checkIsEmpty: React.KeyboardEventHandler | React.ChangeEventHandler = ({
target,
}: React.KeyboardEvent | React.ChangeEvent): void =>
setIsEmptyInput(!(target as HTMLInputElement)?.value);
const checkIsEmpty: React.KeyboardEventHandler | React.ChangeEventHandler =
useCallback(
({ target }: React.KeyboardEvent | React.ChangeEvent): void =>
setIsEmptyInput(!(target as HTMLInputElement)?.value),
[]
);
const runResource = useCallback(
async (resource?: string) => {
if (!resource) return;

View File

@@ -93,7 +93,10 @@ const useFileContextMenu = (
updateRecentFiles,
} = useSession();
const baseName = basename(path);
const isFocusedEntry = focusedEntries.includes(baseName);
const isFocusedEntry = useMemo(
() => focusedEntries.includes(baseName),
[baseName, focusedEntries]
);
const openFile = useFile(url, path);
const {
copyEntries,

View File

@@ -1,4 +1,4 @@
import { useEffect, useRef, useState } from "react";
import { useCallback, useEffect, useRef, useState } from "react";
import {
getInfoWithExtension,
getInfoWithoutExtension,
@@ -32,10 +32,10 @@ const useFileInfo = (
): [FileInfo, React.Dispatch<React.SetStateAction<FileInfo>>] => {
const [info, setInfo] = useState<FileInfo>(INITIAL_FILE_INFO);
const updatingInfo = useRef(false);
const updateInfo = (newInfo: FileInfo): void => {
const updateInfo = useCallback((newInfo: FileInfo): void => {
setInfo(newInfo);
updatingInfo.current = false;
};
}, []);
const { fs, rootFs } = useFileSystem();
useEffect(() => {
@@ -68,7 +68,16 @@ const useFileInfo = (
getInfoWithExtension(fs, path, extension, updateInfo);
}
}
}, [fs, hasNewFolderIcon, info, isDirectory, isVisible, path, rootFs]);
}, [
fs,
hasNewFolderIcon,
info,
isDirectory,
isVisible,
path,
rootFs,
updateInfo,
]);
return [info, setInfo];
};

View File

@@ -48,12 +48,15 @@ const useDraggableEntries = (
const dragPositionRef = useRef<DragPosition>(
Object.create(null) as DragPosition
);
const onDragging = ({ clientX: x, clientY: y }: DragEvent): void => {
const onDragging = useCallback(
({ clientX: x, clientY: y }: DragEvent): void => {
dragPositionRef.current = { ...dragPositionRef.current, x, y };
};
},
[]
);
const isMainContainer =
fileManagerRef.current?.parentElement?.tagName === "MAIN";
const onDragEnd =
const onDragEnd = useCallback(
(entryUrl: string): React.DragEventHandler =>
(event) => {
haltEvent(event);
@@ -81,8 +84,21 @@ const useDraggableEntries = (
return sortedEntries;
});
}
};
const onDragOver =
},
[
allowMoving,
dropIndex,
exists,
fileManagerRef,
focusedEntries,
iconPositions,
onDragging,
setIconPositions,
setSortOrder,
sortOrders,
]
);
const onDragOver = useCallback(
(file: string): React.DragEventHandler =>
({ target }) => {
if (!allowMoving && target instanceof HTMLLIElement) {
@@ -91,8 +107,10 @@ const useDraggableEntries = (
setDropIndex(dragOverFocused ? -1 : [...children].indexOf(target));
}
};
const onDragStart =
},
[allowMoving, focusedEntries]
);
const onDragStart = useCallback(
(
entryUrl: string,
file: string,
@@ -168,7 +186,16 @@ const useDraggableEntries = (
passive: true,
});
}
};
},
[
allowMoving,
fileManagerRef,
focusEntry,
focusedEntries,
isMainContainer,
onDragging,
]
);
const updateDragImage = useCallback(async () => {
if (fileManagerRef.current) {
const focusedElements = [

View File

@@ -73,7 +73,8 @@ const useFocusableEntries = (
});
}, []);
const mouseDownPositionRef = useRef({ x: 0, y: 0 });
const focusableEntry = (file: string): FocusedEntryProps => {
const focusableEntry = useCallback(
(file: string): FocusedEntryProps => {
const isFocused = focusedEntries.includes(file);
const isOnlyFocusedEntry =
focusedEntries.length === 1 && focusedEntries[0] === file;
@@ -128,7 +129,9 @@ const useFocusableEntries = (
onMouseDown,
onMouseUp,
};
};
},
[blurEntry, focusEntry, focusedEntries, onBlurCapture, onFocusCapture]
);
return { blurEntry, focusEntry, focusableEntry, focusedEntries };
};

View File

@@ -66,11 +66,12 @@ const MenuItemEntry: FC<MenuItemEntryProps> = ({
TRANSITIONS_IN_MILLISECONDS.MOUSE_IN_OUT
);
}, []);
const onMouseEnter: React.MouseEventHandler = () => {
const onMouseEnter: React.MouseEventHandler = useCallback(() => {
setMouseOver(true);
if (menu) setDelayedShowSubMenu(true);
};
const onMouseLeave: React.MouseEventHandler = ({ relatedTarget, type }) => {
}, [menu, setDelayedShowSubMenu]);
const onMouseLeave: React.MouseEventHandler = useCallback(
({ relatedTarget, type }) => {
if (
!(relatedTarget instanceof HTMLElement) ||
!entryRef.current?.contains(relatedTarget)
@@ -83,14 +84,20 @@ const MenuItemEntry: FC<MenuItemEntryProps> = ({
setShowSubMenu(false);
}
}
};
const subMenuEvents = menu
},
[setDelayedShowSubMenu]
);
const subMenuEvents = useMemo(
() =>
menu
? {
onBlur: onMouseLeave as unknown as React.FocusEventHandler,
onMouseEnter,
onMouseLeave,
}
: {};
: {},
[menu, onMouseEnter, onMouseLeave]
);
const triggerAction = useCallback<React.MouseEventHandler>(
(event) => {
haltEvent(event);

View File

@@ -44,7 +44,8 @@ const Sidebar: FC<SidebarProps> = ({ height }) => {
const clearTimer = (): void => {
if (expandTimer.current) clearTimeout(expandTimer.current);
};
const topButtons: SidebarButtons = [
const topButtons: SidebarButtons = useMemo(
() => [
{
heading: true,
icon: <SideMenu />,
@@ -57,15 +58,18 @@ const Sidebar: FC<SidebarProps> = ({ height }) => {
name: "All apps",
...(collapsed && { tooltip: "All apps" }),
},
];
],
[collapsed]
);
const { sizes } = useTheme();
const vh = viewHeight();
const buttonAreaCount = useMemo(
() => Math.floor((vh - TASKBAR_HEIGHT) / sizes.startMenu.sideBar.width),
[sizes.startMenu.sideBar.width, vh]
);
const bottomButtons = [
const bottomButtons = useMemo(
() =>
[
buttonAreaCount > 3
? {
action: () =>
@@ -117,7 +121,9 @@ const Sidebar: FC<SidebarProps> = ({ height }) => {
name: "Power",
tooltip: "Clears session data and reloads the page.",
},
].filter(Boolean) as SidebarButtons;
].filter(Boolean) as SidebarButtons,
[buttonAreaCount, collapsed, open, rootFs, setHaltSession]
);
useEffect(() => clearTimer, []);

View File

@@ -1,5 +1,5 @@
import { useTheme } from "styled-components";
import { memo, useEffect, useMemo, useRef, useState } from "react";
import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { Down, Up } from "components/system/Taskbar/Calendar/Icons";
import StyledCalendar from "components/system/Taskbar/Calendar/StyledCalendar";
import {
@@ -32,7 +32,8 @@ const Calendar: FC<CalendarProps> = ({ toggleCalendar }) => {
date.getFullYear() === today.getFullYear(),
[date, today]
);
const changeMonth = (direction: number): void => {
const changeMonth = useCallback(
(direction: number): void => {
const newDate = new Date(date);
const newMonth = newDate.getMonth() + direction;
@@ -47,7 +48,9 @@ const Calendar: FC<CalendarProps> = ({ toggleCalendar }) => {
setDate(newDate);
setCalendar(createCalendar(newDate));
};
},
[date, today]
);
const calendarRef = useRef<HTMLTableElement>(null);
const {
sizes: {
@@ -55,7 +58,7 @@ const Calendar: FC<CalendarProps> = ({ toggleCalendar }) => {
},
} = useTheme();
const calendarTransition = useTaskbarItemTransition(maxHeight, false);
const finePointer = hasFinePointer();
const finePointer = useMemo(() => hasFinePointer(), []);
useEffect(() => {
calendarRef.current?.addEventListener("blur", ({ relatedTarget }) => {

View File

@@ -1,5 +1,5 @@
import { basename, dirname, extname } from "path";
import { useCallback, useEffect, useRef, useState } from "react";
import { basename, dirname } from "path";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import type Stats from "browserfs/dist/node/core/node_fs_stats";
import { getModifiedTime } from "components/system/Files/FileEntry/functions";
import { UNKNOWN_ICON } from "components/system/Files/FileManager/icons";
@@ -20,7 +20,7 @@ import { useSession } from "contexts/session";
import Button from "styles/common/Button";
import Icon from "styles/common/Icon";
import { DEFAULT_LOCALE, ROOT_NAME, SHORTCUT_EXTENSION } from "utils/constants";
import { isYouTubeUrl } from "utils/functions";
import { getExtension, isYouTubeUrl } from "utils/functions";
import SubIcons from "components/system/Files/FileEntry/SubIcons";
const Details: FC<{
@@ -34,28 +34,46 @@ const Details: FC<{
const [info, setInfo] = useState<ResultInfo>({
icon: UNKNOWN_ICON,
} as ResultInfo);
const extension = extname(info?.url || url);
const extension = useMemo(
() => getExtension(info?.url || url),
[info?.url, url]
);
const { updateRecentFiles } = useSession();
const openFile = useCallback(() => {
openApp(info?.pid, { url: info?.url });
if (info?.url && info?.pid) updateRecentFiles(info?.url, info?.pid);
}, [info?.pid, info?.url, openApp, updateRecentFiles]);
const elementRef = useRef<HTMLDivElement>(null);
const isYTUrl = info?.url ? isYouTubeUrl(info.url) : false;
const isNostrUrl = info?.url ? info.url.startsWith("nostr:") : false;
const isAppShortcut = info?.pid
? url === info.url && extname(url) === SHORTCUT_EXTENSION
: false;
const isDirectory =
stats?.isDirectory() || (!extension && !isYTUrl && !isNostrUrl);
const isYTUrl = useMemo(
() => (info?.url ? isYouTubeUrl(info.url) : false),
[info?.url]
);
const isNostrUrl = useMemo(
() => (info?.url ? info.url.startsWith("nostr:") : false),
[info?.url]
);
const isAppShortcut = useMemo(
() =>
info?.pid
? url === info.url && getExtension(url) === SHORTCUT_EXTENSION
: false,
[info?.pid, info?.url, url]
);
const isDirectory = useMemo(
() => stats?.isDirectory() || (!extension && !isYTUrl && !isNostrUrl),
[extension, isNostrUrl, isYTUrl, stats]
);
const baseUrl = isYTUrl || isNostrUrl ? url : info?.url;
const currentUrlRef = useRef(url);
const name =
const name = useMemo(
() =>
baseUrl === "/"
? ROOT_NAME
: baseUrl
? basename(baseUrl, SHORTCUT_EXTENSION)
: "";
: "",
[baseUrl]
);
useEffect(() => {
stat(url).then(

View File

@@ -18,7 +18,7 @@ import { type ProcessArguments } from "contexts/process/types";
import { useSession } from "contexts/session";
import Icon from "styles/common/Icon";
import { DEFAULT_LOCALE, SHORTCUT_EXTENSION } from "utils/constants";
import { isYouTubeUrl } from "utils/functions";
import { getExtension, isYouTubeUrl } from "utils/functions";
import { useIsVisible } from "hooks/useIsVisible";
import SubIcons from "components/system/Files/FileEntry/SubIcons";
@@ -47,8 +47,11 @@ const ResultEntry: FC<ResultEntryProps> = ({
const { updateRecentFiles } = useSession();
const [stats, setStats] = useState<Stats>();
const [info, setInfo] = useState<ResultInfo>(INITIAL_INFO);
const extension = extname(info?.url || url);
const baseName = basename(url, SHORTCUT_EXTENSION);
const extension = useMemo(
() => getExtension(info?.url || url),
[info?.url, url]
);
const baseName = useMemo(() => basename(url, SHORTCUT_EXTENSION), [url]);
const name = useMemo(() => {
let text = baseName;
@@ -63,7 +66,10 @@ const ResultEntry: FC<ResultEntryProps> = ({
return text;
}, [baseName, searchTerm]);
const isYTUrl = info?.url ? isYouTubeUrl(info.url) : false;
const isYTUrl = useMemo(
() => (info?.url ? isYouTubeUrl(info.url) : false),
[info?.url]
);
const baseUrl = isYTUrl ? url : url || info?.url;
const lastModified = useMemo(
() =>
@@ -80,11 +86,21 @@ const ResultEntry: FC<ResultEntryProps> = ({
const [hovered, setHovered] = useState(false);
const elementRef = useRef<HTMLLIElement | null>(null);
const isVisible = useIsVisible(elementRef, ".list");
const isAppShortcut = info?.pid
? url === info.url && extname(url) === SHORTCUT_EXTENSION
: false;
const isDirectory = stats?.isDirectory() || (!extension && !isYTUrl);
const isNostrUrl = info?.url ? info.url.startsWith("nostr:") : false;
const isAppShortcut = useMemo(
() =>
info?.pid
? url === info.url && getExtension(url) === SHORTCUT_EXTENSION
: false,
[info?.pid, info?.url, url]
);
const isDirectory = useMemo(
() => stats?.isDirectory() || (!extension && !isYTUrl),
[extension, isYTUrl, stats]
);
const isNostrUrl = useMemo(
() => (info?.url ? info.url.startsWith("nostr:") : false),
[info?.url]
);
const { onContextMenuCapture } = useResultsContextMenu(info?.url);
const abortController = useRef<AbortController>(undefined);

View File

@@ -1,5 +1,6 @@
import {
memo,
useCallback,
useEffect,
useLayoutEffect,
useMemo,
@@ -49,11 +50,11 @@ const PeekWindow: FC<PeekWindowProps> = ({ id }) => {
);
const peekTransition = usePeekTransition(showControls);
const peekRef = useRef<HTMLDivElement | null>(null);
const onClick = (): void => {
const onClick = useCallback((): void => {
if (minimized) minimize(id);
setForegroundId(id);
};
}, [id, minimize, minimized, setForegroundId]);
const [isPaused, setIsPaused] = useState(false);
const monitoringPaused = useRef(false);

View File

@@ -39,13 +39,13 @@ const TaskbarEntry: FC<TaskbarEntryProps> = ({ icon, id, title }) => {
[id, linkElement]
);
const [isPeekVisible, setIsPeekVisible] = useState(false);
const hidePeek = (): void => setIsPeekVisible(false);
const showPeek = (): void => setIsPeekVisible(true);
const onClick = (): void => {
const hidePeek = useCallback((): void => setIsPeekVisible(false), []);
const showPeek = useCallback((): void => setIsPeekVisible(true), []);
const onClick = useCallback((): void => {
if (minimized || isForeground) minimize(id);
setForegroundId(isForeground ? nextFocusableId : id);
};
}, [id, isForeground, minimize, minimized, nextFocusableId, setForegroundId]);
const focusable = useMemo(() => (isSafari() ? DIV_BUTTON_PROPS : {}), []);
return (

View File

@@ -1,4 +1,5 @@
import { type MotionProps } from "motion/react";
import { useMemo } from "react";
import { TASKBAR_HEIGHT, TRANSITIONS_IN_SECONDS } from "utils/constants";
import { viewHeight } from "utils/functions";
@@ -8,7 +9,10 @@ const useTaskbarItemTransition = (
paddingOffset = 0.5,
heightOffset = 0.75
): MotionProps => {
const height = Math.min(maxHeight, viewHeight() - TASKBAR_HEIGHT);
const height = useMemo(
() => Math.min(maxHeight, viewHeight() - TASKBAR_HEIGHT),
[maxHeight]
);
return {
animate: "active",

View File

@@ -1,11 +1,16 @@
import { useMemo } from "react";
import { useProcesses } from "contexts/process";
import { useSession } from "contexts/session";
const useNextFocusable = (id: string): string => {
const { stackOrder = [] } = useSession();
const { processes } = useProcesses();
const nextFocusableId = stackOrder.find(
const nextFocusableId = useMemo(
() =>
stackOrder.find(
(stackId) => stackId !== id && !processes?.[stackId]?.minimized
),
[id, processes, stackOrder]
);
return nextFocusableId || "";

View File

@@ -1,4 +1,4 @@
import { useCallback } from "react";
import { useCallback, useMemo } from "react";
import { useProcesses } from "contexts/process";
import processDirectory from "contexts/process/directory";
import { PROCESS_DELIMITER, SAVE_TITLE_CHAR } from "utils/constants";
@@ -14,7 +14,7 @@ type Title = {
const useTitle = (id: string): Title => {
const { title } = useProcesses();
const [pid] = id.split(PROCESS_DELIMITER);
const [pid] = useMemo(() => id.split(PROCESS_DELIMITER), [id]);
const { title: originalTitle } = processDirectory[pid] || {};
const appendFileToTitle = useCallback(
(url: string, unSaved?: boolean) => {

View File

@@ -1,4 +1,4 @@
import { useEffect, useState } from "react";
import { useCallback, useEffect, useState } from "react";
import {
type FullscreenDocument,
type FullscreenElement,
@@ -63,7 +63,8 @@ const useViewportContextState = (): ViewportContextState => {
// eslint-disable-next-line unicorn/no-null
null
);
const toggleFullscreen = async (
const toggleFullscreen = useCallback(
async (
element?: HTMLElement | null,
navigationUI?: FullscreenNavigationUI
): Promise<void> => {
@@ -79,7 +80,9 @@ const useViewportContextState = (): ViewportContextState => {
navigationUI: navigationUI || "hide",
});
}
};
},
[fullscreenElement]
);
useEffect(() => {
const onFullscreenChange = (): void => {

View File

@@ -8,7 +8,7 @@ import { ProcessProvider } from "contexts/process";
import { SessionProvider } from "contexts/session";
import { ViewportProvider } from "contexts/viewport";
const App = ({ Component, pageProps }: AppProps): React.ReactElement => (
const App = ({ Component: Index, pageProps }: AppProps): React.ReactElement => (
<ViewportProvider>
<ProcessProvider>
<FileSystemProvider>
@@ -17,7 +17,7 @@ const App = ({ Component, pageProps }: AppProps): React.ReactElement => (
<Metadata />
<StyledApp>
<MenuProvider>
<Component {...pageProps} />
<Index {...pageProps} />
</MenuProvider>
</StyledApp>
</ErrorBoundary>