diff --git a/components/system/Dialogs/Run/index.tsx b/components/system/Dialogs/Run/index.tsx index 3d2acb91..19662d98 100644 --- a/components/system/Dialogs/Run/index.tsx +++ b/components/system/Dialogs/Run/index.tsx @@ -58,10 +58,12 @@ const Run: FC = ({ 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; diff --git a/components/system/Files/FileEntry/useFileContextMenu.ts b/components/system/Files/FileEntry/useFileContextMenu.ts index c2d7cba9..0b7479c2 100644 --- a/components/system/Files/FileEntry/useFileContextMenu.ts +++ b/components/system/Files/FileEntry/useFileContextMenu.ts @@ -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, diff --git a/components/system/Files/FileEntry/useFileInfo.ts b/components/system/Files/FileEntry/useFileInfo.ts index c6bfba9c..8a5d271a 100644 --- a/components/system/Files/FileEntry/useFileInfo.ts +++ b/components/system/Files/FileEntry/useFileInfo.ts @@ -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>] => { const [info, setInfo] = useState(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]; }; diff --git a/components/system/Files/FileManager/useDraggableEntries.ts b/components/system/Files/FileManager/useDraggableEntries.ts index e56b34f9..d7c25bdd 100644 --- a/components/system/Files/FileManager/useDraggableEntries.ts +++ b/components/system/Files/FileManager/useDraggableEntries.ts @@ -48,127 +48,154 @@ const useDraggableEntries = ( const dragPositionRef = useRef( 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 = [ diff --git a/components/system/Files/FileManager/useFocusableEntries.ts b/components/system/Files/FileManager/useFocusableEntries.ts index 38c7446f..5c0f23a7 100644 --- a/components/system/Files/FileManager/useFocusableEntries.ts +++ b/components/system/Files/FileManager/useFocusableEntries.ts @@ -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 }; }; diff --git a/components/system/Menu/MenuItemEntry.tsx b/components/system/Menu/MenuItemEntry.tsx index 1b77cf0c..e7b3ed47 100644 --- a/components/system/Menu/MenuItemEntry.tsx +++ b/components/system/Menu/MenuItemEntry.tsx @@ -66,31 +66,38 @@ const MenuItemEntry: FC = ({ 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( (event) => { haltEvent(event); diff --git a/components/system/StartMenu/Sidebar/index.tsx b/components/system/StartMenu/Sidebar/index.tsx index daea0821..fb83ae73 100644 --- a/components/system/StartMenu/Sidebar/index.tsx +++ b/components/system/StartMenu/Sidebar/index.tsx @@ -44,80 +44,86 @@ const Sidebar: FC = ({ height }) => { const clearTimer = (): void => { if (expandTimer.current) clearTimeout(expandTimer.current); }; - const topButtons: SidebarButtons = [ - { - heading: true, - icon: , - name: "START", - ...(collapsed && { tooltip: "Expand" }), - }, - { - active: true, - icon: , - name: "All apps", - ...(collapsed && { tooltip: "All apps" }), - }, - ]; + const topButtons: SidebarButtons = useMemo( + () => [ + { + heading: true, + icon: , + name: "START", + ...(collapsed && { tooltip: "Expand" }), + }, + { + active: true, + icon: , + 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: , + name: "Documents", + ...(collapsed && { tooltip: "Documents" }), + } + : undefined, + buttonAreaCount > 4 + ? { + action: () => + open( + "FileExplorer", + { url: `${HOME}/Pictures` }, + "/System/Icons/pictures.webp" + ), + icon: , + name: "Pictures", + ...(collapsed && { tooltip: "Pictures" }), + } + : undefined, + buttonAreaCount > 5 + ? { + action: () => + open( + "FileExplorer", + { url: `${HOME}/Videos` }, + "/System/Icons/videos.webp" + ), + icon: , + name: "Videos", + ...(collapsed && { tooltip: "Videos" }), + } + : undefined, + { + action: () => { + setHaltSession(true); - const bottomButtons = [ - buttonAreaCount > 3 - ? { - action: () => - open( - "FileExplorer", - { url: `${HOME}/Documents` }, - "/System/Icons/documents.webp" - ), - icon: , - name: "Documents", - ...(collapsed && { tooltip: "Documents" }), - } - : undefined, - buttonAreaCount > 4 - ? { - action: () => - open( - "FileExplorer", - { url: `${HOME}/Pictures` }, - "/System/Icons/pictures.webp" - ), - icon: , - name: "Pictures", - ...(collapsed && { tooltip: "Pictures" }), - } - : undefined, - buttonAreaCount > 5 - ? { - action: () => - open( - "FileExplorer", - { url: `${HOME}/Videos` }, - "/System/Icons/videos.webp" - ), - icon: , - name: "Videos", - ...(collapsed && { tooltip: "Videos" }), - } - : undefined, - { - action: () => { - setHaltSession(true); - - import("contexts/fileSystem/functions").then(({ resetStorage }) => - resetStorage(rootFs).finally(() => window.location.reload()) - ); - }, - icon: , - 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: , + name: "Power", + tooltip: "Clears session data and reloads the page.", + }, + ].filter(Boolean) as SidebarButtons, + [buttonAreaCount, collapsed, open, rootFs, setHaltSession] + ); useEffect(() => clearTimer, []); diff --git a/components/system/Taskbar/Calendar/index.tsx b/components/system/Taskbar/Calendar/index.tsx index 0a0ab16d..2ccc0b28 100644 --- a/components/system/Taskbar/Calendar/index.tsx +++ b/components/system/Taskbar/Calendar/index.tsx @@ -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 = ({ 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(null); const { sizes: { @@ -55,7 +58,7 @@ const Calendar: FC = ({ toggleCalendar }) => { }, } = useTheme(); const calendarTransition = useTaskbarItemTransition(maxHeight, false); - const finePointer = hasFinePointer(); + const finePointer = useMemo(() => hasFinePointer(), []); useEffect(() => { calendarRef.current?.addEventListener("blur", ({ relatedTarget }) => { diff --git a/components/system/Taskbar/Search/Details.tsx b/components/system/Taskbar/Search/Details.tsx index 23ecdc92..372a6bbb 100644 --- a/components/system/Taskbar/Search/Details.tsx +++ b/components/system/Taskbar/Search/Details.tsx @@ -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({ 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(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( diff --git a/components/system/Taskbar/Search/ResultEntry.tsx b/components/system/Taskbar/Search/ResultEntry.tsx index a91bdc42..224018a9 100644 --- a/components/system/Taskbar/Search/ResultEntry.tsx +++ b/components/system/Taskbar/Search/ResultEntry.tsx @@ -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 = ({ const { updateRecentFiles } = useSession(); const [stats, setStats] = useState(); const [info, setInfo] = useState(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 = ({ 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 = ({ const [hovered, setHovered] = useState(false); const elementRef = useRef(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(undefined); diff --git a/components/system/Taskbar/TaskbarEntry/Peek/PeekWindow.tsx b/components/system/Taskbar/TaskbarEntry/Peek/PeekWindow.tsx index 647098d1..1f7a6d88 100644 --- a/components/system/Taskbar/TaskbarEntry/Peek/PeekWindow.tsx +++ b/components/system/Taskbar/TaskbarEntry/Peek/PeekWindow.tsx @@ -1,5 +1,6 @@ import { memo, + useCallback, useEffect, useLayoutEffect, useMemo, @@ -49,11 +50,11 @@ const PeekWindow: FC = ({ id }) => { ); const peekTransition = usePeekTransition(showControls); const peekRef = useRef(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); diff --git a/components/system/Taskbar/TaskbarEntry/index.tsx b/components/system/Taskbar/TaskbarEntry/index.tsx index a0074ab1..a0326dec 100644 --- a/components/system/Taskbar/TaskbarEntry/index.tsx +++ b/components/system/Taskbar/TaskbarEntry/index.tsx @@ -39,13 +39,13 @@ const TaskbarEntry: FC = ({ 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 ( diff --git a/components/system/Taskbar/useTaskbarItemTransition.ts b/components/system/Taskbar/useTaskbarItemTransition.ts index fc844654..4384a203 100644 --- a/components/system/Taskbar/useTaskbarItemTransition.ts +++ b/components/system/Taskbar/useTaskbarItemTransition.ts @@ -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", diff --git a/components/system/Window/useNextFocusable.ts b/components/system/Window/useNextFocusable.ts index 795ee0c1..92d4f12d 100644 --- a/components/system/Window/useNextFocusable.ts +++ b/components/system/Window/useNextFocusable.ts @@ -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 || ""; diff --git a/components/system/Window/useTitle.ts b/components/system/Window/useTitle.ts index 438db122..14b1697f 100644 --- a/components/system/Window/useTitle.ts +++ b/components/system/Window/useTitle.ts @@ -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) => { diff --git a/contexts/viewport/useViewportContextState.ts b/contexts/viewport/useViewportContextState.ts index f07d897d..f706f6fe 100644 --- a/contexts/viewport/useViewportContextState.ts +++ b/contexts/viewport/useViewportContextState.ts @@ -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 => { - 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 => { + 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 => { diff --git a/pages/_app.tsx b/pages/_app.tsx index f2e9084c..84b743d3 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -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 => ( @@ -17,7 +17,7 @@ const App = ({ Component, pageProps }: AppProps): React.ReactElement => ( - +