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,127 +48,154 @@ const useDraggableEntries = (
const dragPositionRef = useRef<DragPosition>(
Object.create(null) as DragPosition
);
const onDragging = ({ clientX: x, clientY: y }: DragEvent): void => {
dragPositionRef.current = { ...dragPositionRef.current, x, y };
};
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);
(event) => {
haltEvent(event);
if (allowMoving && focusedEntries.length > 0) {
updateIconPositions(
entryUrl,
fileManagerRef.current,
iconPositions,
sortOrders,
dragPositionRef.current,
focusedEntries,
setIconPositions,
exists
);
fileManagerRef.current?.removeEventListener("dragover", onDragging);
} else if (dropIndex !== -1) {
setSortOrder(entryUrl, (currentSortOrders) => {
const sortedEntries = currentSortOrders.filter(
(entry) => !focusedEntries.includes(entry)
if (allowMoving && focusedEntries.length > 0) {
updateIconPositions(
entryUrl,
fileManagerRef.current,
iconPositions,
sortOrders,
dragPositionRef.current,
focusedEntries,
setIconPositions,
exists
);
fileManagerRef.current?.removeEventListener("dragover", onDragging);
} else if (dropIndex !== -1) {
setSortOrder(entryUrl, (currentSortOrders) => {
const sortedEntries = currentSortOrders.filter(
(entry) => !focusedEntries.includes(entry)
);
sortedEntries.splice(dropIndex, 0, ...focusedEntries);
sortedEntries.splice(dropIndex, 0, ...focusedEntries);
return sortedEntries;
});
}
};
const onDragOver =
return sortedEntries;
});
}
},
[
allowMoving,
dropIndex,
exists,
fileManagerRef,
focusedEntries,
iconPositions,
onDragging,
setIconPositions,
setSortOrder,
sortOrders,
]
);
const onDragOver = useCallback(
(file: string): React.DragEventHandler =>
({ target }) => {
if (!allowMoving && target instanceof HTMLLIElement) {
const { children = [] } = target.parentElement || {};
const dragOverFocused = focusedEntries.includes(file);
({ target }) => {
if (!allowMoving && target instanceof HTMLLIElement) {
const { children = [] } = target.parentElement || {};
const dragOverFocused = focusedEntries.includes(file);
setDropIndex(dragOverFocused ? -1 : [...children].indexOf(target));
}
};
const onDragStart =
setDropIndex(dragOverFocused ? -1 : [...children].indexOf(target));
}
},
[allowMoving, focusedEntries]
);
const onDragStart = useCallback(
(
entryUrl: string,
file: string,
renaming: boolean
): React.DragEventHandler =>
(event) => {
if (renaming || "ontouchstart" in window) {
haltEvent(event);
return;
}
focusEntry(file);
const singleFile = focusedEntries.length <= 1;
event.nativeEvent.dataTransfer?.setData(
"application/json",
JSON.stringify(
singleFile
? [join(entryUrl, file)]
: focusedEntries.map((entryFile) => join(entryUrl, entryFile))
)
);
if (singleFile) {
event.nativeEvent.dataTransfer?.setData(
"DownloadURL",
`${getMimeType(file) || "application/octet-stream"}:${file}:${
window.location.href
}${join(entryUrl, file)}`
);
}
if (!singleFile && dragImageRef.current) {
if (!adjustedCaptureOffsetRef.current) {
adjustedCaptureOffsetRef.current = true;
const hasCapturedImageOffset =
capturedImageOffset.current.x || capturedImageOffset.current.y;
capturedImageOffset.current = {
x: hasCapturedImageOffset
? event.nativeEvent.clientX - capturedImageOffset.current.x
: event.nativeEvent.offsetX,
y: hasCapturedImageOffset
? event.nativeEvent.clientY - capturedImageOffset.current.y
: event.nativeEvent.offsetY + FILE_MANAGER_TOP_PADDING,
};
(event) => {
if (renaming || "ontouchstart" in window) {
haltEvent(event);
return;
}
event.nativeEvent.dataTransfer?.setDragImage(
dragImageRef.current,
isMainContainer
? capturedImageOffset.current.x
: event.nativeEvent.offsetX,
isMainContainer
? capturedImageOffset.current.y
: event.nativeEvent.offsetY
focusEntry(file);
const singleFile = focusedEntries.length <= 1;
event.nativeEvent.dataTransfer?.setData(
"application/json",
JSON.stringify(
singleFile
? [join(entryUrl, file)]
: focusedEntries.map((entryFile) => join(entryUrl, entryFile))
)
);
}
Object.assign(event.dataTransfer, { effectAllowed: "move" });
if (singleFile) {
event.nativeEvent.dataTransfer?.setData(
"DownloadURL",
`${getMimeType(file) || "application/octet-stream"}:${file}:${
window.location.href
}${join(entryUrl, file)}`
);
}
if (allowMoving) {
dragPositionRef.current =
focusedEntries.length > 1
? {
offsetX: event.nativeEvent.offsetX,
offsetY: event.nativeEvent.offsetY,
}
: (Object.create(null) as DragPosition);
fileManagerRef.current?.addEventListener("dragover", onDragging, {
passive: true,
});
}
};
if (!singleFile && dragImageRef.current) {
if (!adjustedCaptureOffsetRef.current) {
adjustedCaptureOffsetRef.current = true;
const hasCapturedImageOffset =
capturedImageOffset.current.x || capturedImageOffset.current.y;
capturedImageOffset.current = {
x: hasCapturedImageOffset
? event.nativeEvent.clientX - capturedImageOffset.current.x
: event.nativeEvent.offsetX,
y: hasCapturedImageOffset
? event.nativeEvent.clientY - capturedImageOffset.current.y
: event.nativeEvent.offsetY + FILE_MANAGER_TOP_PADDING,
};
}
event.nativeEvent.dataTransfer?.setDragImage(
dragImageRef.current,
isMainContainer
? capturedImageOffset.current.x
: event.nativeEvent.offsetX,
isMainContainer
? capturedImageOffset.current.y
: event.nativeEvent.offsetY
);
}
Object.assign(event.dataTransfer, { effectAllowed: "move" });
if (allowMoving) {
dragPositionRef.current =
focusedEntries.length > 1
? {
offsetX: event.nativeEvent.offsetX,
offsetY: event.nativeEvent.offsetY,
}
: (Object.create(null) as DragPosition);
fileManagerRef.current?.addEventListener("dragover", onDragging, {
passive: true,
});
}
},
[
allowMoving,
fileManagerRef,
focusEntry,
focusedEntries,
isMainContainer,
onDragging,
]
);
const updateDragImage = useCallback(async () => {
if (fileManagerRef.current) {
const focusedElements = [

View File

@ -73,62 +73,65 @@ const useFocusableEntries = (
});
}, []);
const mouseDownPositionRef = useRef({ x: 0, y: 0 });
const focusableEntry = (file: string): FocusedEntryProps => {
const isFocused = focusedEntries.includes(file);
const isOnlyFocusedEntry =
focusedEntries.length === 1 && focusedEntries[0] === file;
const className = clsx({
"focus-within": isFocused,
"only-focused": isOnlyFocusedEntry,
});
const onMouseDown: React.MouseEventHandler = ({
ctrlKey,
pageX,
pageY,
}) => {
mouseDownPositionRef.current = { x: pageX, y: pageY };
const focusableEntry = useCallback(
(file: string): FocusedEntryProps => {
const isFocused = focusedEntries.includes(file);
const isOnlyFocusedEntry =
focusedEntries.length === 1 && focusedEntries[0] === file;
const className = clsx({
"focus-within": isFocused,
"only-focused": isOnlyFocusedEntry,
});
const onMouseDown: React.MouseEventHandler = ({
ctrlKey,
pageX,
pageY,
}) => {
mouseDownPositionRef.current = { x: pageX, y: pageY };
if (ctrlKey) {
if (isFocused) {
blurEntry(file);
} else {
if (ctrlKey) {
if (isFocused) {
blurEntry(file);
} else {
focusEntry(file);
}
} else if (!isFocused) {
blurEntry();
focusEntry(file);
}
} else if (!isFocused) {
blurEntry();
focusEntry(file);
}
};
const onMouseUp: React.MouseEventHandler = ({
ctrlKey,
pageX,
pageY,
button,
}) => {
const { x, y } = mouseDownPositionRef.current;
};
const onMouseUp: React.MouseEventHandler = ({
ctrlKey,
pageX,
pageY,
button,
}) => {
const { x, y } = mouseDownPositionRef.current;
if (
!ctrlKey &&
!isOnlyFocusedEntry &&
button === 0 &&
x === pageX &&
y === pageY
) {
blurEntry();
focusEntry(file);
}
if (
!ctrlKey &&
!isOnlyFocusedEntry &&
button === 0 &&
x === pageX &&
y === pageY
) {
blurEntry();
focusEntry(file);
}
mouseDownPositionRef.current = { x: 0, y: 0 };
};
mouseDownPositionRef.current = { x: 0, y: 0 };
};
return {
className,
onBlurCapture,
onFocusCapture,
onMouseDown,
onMouseUp,
};
};
return {
className,
onBlurCapture,
onFocusCapture,
onMouseDown,
onMouseUp,
};
},
[blurEntry, focusEntry, focusedEntries, onBlurCapture, onFocusCapture]
);
return { blurEntry, focusEntry, focusableEntry, focusedEntries };
};

View File

@ -66,31 +66,38 @@ 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 }) => {
if (
!(relatedTarget instanceof HTMLElement) ||
!entryRef.current?.contains(relatedTarget)
) {
setMouseOver(false);
}, [menu, setDelayedShowSubMenu]);
const onMouseLeave: React.MouseEventHandler = useCallback(
({ relatedTarget, type }) => {
if (
!(relatedTarget instanceof HTMLElement) ||
!entryRef.current?.contains(relatedTarget)
) {
setMouseOver(false);
if (type === "mouseleave") {
setDelayedShowSubMenu(false);
} else {
setShowSubMenu(false);
if (type === "mouseleave") {
setDelayedShowSubMenu(false);
} else {
setShowSubMenu(false);
}
}
}
};
const subMenuEvents = menu
? {
onBlur: onMouseLeave as unknown as React.FocusEventHandler,
onMouseEnter,
onMouseLeave,
}
: {};
},
[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,80 +44,86 @@ const Sidebar: FC<SidebarProps> = ({ height }) => {
const clearTimer = (): void => {
if (expandTimer.current) clearTimeout(expandTimer.current);
};
const topButtons: SidebarButtons = [
{
heading: true,
icon: <SideMenu />,
name: "START",
...(collapsed && { tooltip: "Expand" }),
},
{
active: true,
icon: <AllApps />,
name: "All apps",
...(collapsed && { tooltip: "All apps" }),
},
];
const topButtons: SidebarButtons = useMemo(
() => [
{
heading: true,
icon: <SideMenu />,
name: "START",
...(collapsed && { tooltip: "Expand" }),
},
{
active: true,
icon: <AllApps />,
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 = useMemo(
() =>
[
buttonAreaCount > 3
? {
action: () =>
open(
"FileExplorer",
{ url: `${HOME}/Documents` },
"/System/Icons/documents.webp"
),
icon: <Documents />,
name: "Documents",
...(collapsed && { tooltip: "Documents" }),
}
: undefined,
buttonAreaCount > 4
? {
action: () =>
open(
"FileExplorer",
{ url: `${HOME}/Pictures` },
"/System/Icons/pictures.webp"
),
icon: <Pictures />,
name: "Pictures",
...(collapsed && { tooltip: "Pictures" }),
}
: undefined,
buttonAreaCount > 5
? {
action: () =>
open(
"FileExplorer",
{ url: `${HOME}/Videos` },
"/System/Icons/videos.webp"
),
icon: <Videos />,
name: "Videos",
...(collapsed && { tooltip: "Videos" }),
}
: undefined,
{
action: () => {
setHaltSession(true);
const bottomButtons = [
buttonAreaCount > 3
? {
action: () =>
open(
"FileExplorer",
{ url: `${HOME}/Documents` },
"/System/Icons/documents.webp"
),
icon: <Documents />,
name: "Documents",
...(collapsed && { tooltip: "Documents" }),
}
: undefined,
buttonAreaCount > 4
? {
action: () =>
open(
"FileExplorer",
{ url: `${HOME}/Pictures` },
"/System/Icons/pictures.webp"
),
icon: <Pictures />,
name: "Pictures",
...(collapsed && { tooltip: "Pictures" }),
}
: undefined,
buttonAreaCount > 5
? {
action: () =>
open(
"FileExplorer",
{ url: `${HOME}/Videos` },
"/System/Icons/videos.webp"
),
icon: <Videos />,
name: "Videos",
...(collapsed && { tooltip: "Videos" }),
}
: undefined,
{
action: () => {
setHaltSession(true);
import("contexts/fileSystem/functions").then(({ resetStorage }) =>
resetStorage(rootFs).finally(() => window.location.reload())
);
},
icon: <Power />,
name: "Power",
tooltip: "Clears session data and reloads the page.",
},
].filter(Boolean) as SidebarButtons;
import("contexts/fileSystem/functions").then(({ resetStorage }) =>
resetStorage(rootFs).finally(() => window.location.reload())
);
},
icon: <Power />,
name: "Power",
tooltip: "Clears session data and reloads the page.",
},
].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,22 +32,25 @@ const Calendar: FC<CalendarProps> = ({ toggleCalendar }) => {
date.getFullYear() === today.getFullYear(),
[date, today]
);
const changeMonth = (direction: number): void => {
const newDate = new Date(date);
const newMonth = newDate.getMonth() + direction;
const changeMonth = useCallback(
(direction: number): void => {
const newDate = new Date(date);
const newMonth = newDate.getMonth() + direction;
newDate.setDate(1);
newDate.setMonth(newMonth);
newDate.setDate(1);
newDate.setMonth(newMonth);
const isCurrentMonth =
(newMonth === 12 ? 0 : newMonth === -1 ? 11 : newMonth) ===
today.getMonth();
const isCurrentMonth =
(newMonth === 12 ? 0 : newMonth === -1 ? 11 : newMonth) ===
today.getMonth();
if (isCurrentMonth) newDate.setDate(today.getDate());
if (isCurrentMonth) newDate.setDate(today.getDate());
setDate(newDate);
setCalendar(createCalendar(newDate));
};
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 =
baseUrl === "/"
? ROOT_NAME
: baseUrl
? basename(baseUrl, SHORTCUT_EXTENSION)
: "";
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(
(stackId) => stackId !== id && !processes?.[stackId]?.minimized
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,23 +63,26 @@ const useViewportContextState = (): ViewportContextState => {
// eslint-disable-next-line unicorn/no-null
null
);
const toggleFullscreen = async (
element?: HTMLElement | null,
navigationUI?: FullscreenNavigationUI
): Promise<void> => {
if (fullscreenElement && (!element || element === fullscreenElement)) {
await exitFullscreen();
} else {
// Only Chrome switches full screen elements without exiting
if (fullscreenElement && (isFirefox() || isSafari())) {
const toggleFullscreen = useCallback(
async (
element?: HTMLElement | null,
navigationUI?: FullscreenNavigationUI
): Promise<void> => {
if (fullscreenElement && (!element || element === fullscreenElement)) {
await exitFullscreen();
}
} else {
// Only Chrome switches full screen elements without exiting
if (fullscreenElement && (isFirefox() || isSafari())) {
await exitFullscreen();
}
await enterFullscreen(element || document.documentElement, {
navigationUI: navigationUI || "hide",
});
}
};
await enterFullscreen(element || document.documentElement, {
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>