diff --git a/src/components/applications/terminal/InputLine.jsx b/src/components/applications/terminal/InputLine.jsx new file mode 100644 index 0000000..e3f300f --- /dev/null +++ b/src/components/applications/terminal/InputLine.jsx @@ -0,0 +1,53 @@ +import { useState } from "react"; +import styles from "./Terminal.module.css"; + +/** + * @param {object} props + * @param {string} props.value + * @param {string} props.prefix + * @param {Function} props.onChange + * @param {Function} props.onKeyUp + * @param {Function} props.onKeyDown + * @param {import("react").MutableRefObject} props.inputRef + */ +export function InputLine({ value, prefix, onChange, onKeyUp, onKeyDown, inputRef }) { + const [cursorPosition, setCursorPosition] = useState(0); + + const checkCursorPosition = () => { + setCursorPosition(inputRef.current?.selectionStart); + }; + + return ( + + {prefix &&

{prefix}

} + + + { + onChange(event); + checkCursorPosition(); + }} + ref={inputRef} + onKeyUp={onKeyUp} + onKeyDown={(event) => { + onKeyDown(event); + checkCursorPosition(); + }} + onClick={checkCursorPosition} + onTouchEnd={checkCursorPosition} + onSelect={checkCursorPosition} + onCut={checkCursorPosition} + onCopy={checkCursorPosition} + onPaste={checkCursorPosition} + spellCheck={false} + autoComplete="off" + autoFocus + size="" + /> + +
+ ); +} \ No newline at end of file diff --git a/src/components/applications/terminal/OutputLine.jsx b/src/components/applications/terminal/OutputLine.jsx new file mode 100644 index 0000000..08d78fe --- /dev/null +++ b/src/components/applications/terminal/OutputLine.jsx @@ -0,0 +1,15 @@ +import styles from "./Terminal.module.css"; + +/** + * @param {object} props + * @param {string} props.text + */ +export function OutputLine({ text }) { + const lines = text?.split("\n"); + + return (<> + {lines.map((line, index) => +

{line}

+ )} + ); +} \ No newline at end of file diff --git a/src/components/applications/terminal/Terminal.jsx b/src/components/applications/terminal/Terminal.jsx index fac2cb9..5d4c443 100644 --- a/src/components/applications/terminal/Terminal.jsx +++ b/src/components/applications/terminal/Terminal.jsx @@ -3,75 +3,12 @@ import styles from "./Terminal.module.css"; import { useVirtualRoot } from "../../../hooks/virtual-drive/virtualRootContext.js"; import { Command } from "../../../features/applications/terminal/commands.js"; import { clamp } from "../../../features/math/clamp.js"; +import { OutputLine } from "./OutputLine.jsx"; +import { InputLine } from "./InputLine.jsx"; const USERNAME = "user"; const HOSTNAME = "prozilla-os"; -/** - * @param {object} props - * @param {string} props.text - */ -function OutputLine({ text }) { - const lines = text?.split("\n"); - - return (<> - {lines.map((line, index) => -

{line}

- )} - ); -} - -/** - * @param {object} props - * @param {string} props.value - * @param {string} props.prefix - * @param {Function} props.onChange - * @param {Function} props.onKeyUp - * @param {Function} props.onKeyDown - * @param {import("react").MutableRefObject} props.inputRef - */ -function InputLine({ value, prefix, onChange, onKeyUp, onKeyDown, inputRef }) { - const [cursorPosition, setCursorPosition] = useState(0); - - const checkCursorPosition = () => { - setCursorPosition(inputRef.current?.selectionStart); - }; - - return ( - - {prefix &&

{prefix}

} - - - { - onChange(event); - checkCursorPosition(); - }} - ref={inputRef} - onKeyUp={onKeyUp} - onKeyDown={(event) => { - onKeyDown(event); - checkCursorPosition(); - }} - onClick={checkCursorPosition} - onTouchEnd={checkCursorPosition} - onSelect={checkCursorPosition} - onCut={checkCursorPosition} - onCopy={checkCursorPosition} - onPaste={checkCursorPosition} - spellCheck={false} - autoComplete="off" - autoFocus - size="" - /> - -
- ); -} - export function Terminal({ setTitle }) { const [inputKey, setInputKey] = useState(0); const [inputValue, setInputValue] = useState(""); diff --git a/src/components/desktop/Desktop.jsx b/src/components/desktop/Desktop.jsx index 1d65d46..3971169 100644 --- a/src/components/desktop/Desktop.jsx +++ b/src/components/desktop/Desktop.jsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { memo, useState } from "react"; import { SettingsManager } from "../../features/settings/settingsManager.js"; import { useSettingsManager } from "../../hooks/settings/settingsManagerContext.js"; import styles from "./Desktop.module.css"; @@ -8,7 +8,7 @@ import { ModalsView } from "../modals/ModalsView.jsx"; import { useWindowsManager } from "../../hooks/windows/windowsManagerContext.js"; import { useContextMenu } from "../../hooks/modals/contextMenu.js"; -export function Desktop() { +export const Desktop = memo(() => { const settingsManager = useSettingsManager(); const [wallpaper, setWallpaper] = useState(null); const [modalsManager, modals] = useModals(); @@ -37,4 +37,4 @@ export function Desktop() { ); -} \ No newline at end of file +}); \ No newline at end of file diff --git a/src/components/modals/ModalView.jsx b/src/components/modals/ModalView.jsx index db21615..a427fea 100644 --- a/src/components/modals/ModalView.jsx +++ b/src/components/modals/ModalView.jsx @@ -1,3 +1,4 @@ +import { memo } from "react"; import { Modal as ModalType } from "../../features/modals/modal.js"; import OutsideClickListener from "../../hooks/utils/outsideClick.js"; import styles from "./ModalView.module.css"; @@ -6,7 +7,7 @@ import styles from "./ModalView.module.css"; * @param {object} root * @param {ModalType} root.modal */ -export function ModalView({ modal }) { +export const ModalView = memo(({ modal }) => { return ( { modal.close(); }}>
@@ -14,4 +15,4 @@ export function ModalView({ modal }) {
); -} \ No newline at end of file +}); \ No newline at end of file diff --git a/src/components/modals/ModalsView.jsx b/src/components/modals/ModalsView.jsx index 5285dc9..88117cf 100644 --- a/src/components/modals/ModalsView.jsx +++ b/src/components/modals/ModalsView.jsx @@ -1,3 +1,4 @@ +import { memo } from "react"; import { Modal } from "../../features/modals/modal.js"; import ModalsManager from "../../features/modals/modals.js"; import { ModalView } from "./ModalView.jsx"; @@ -10,7 +11,7 @@ import styles from "./ModalsView.module.css"; * @param {import("react").CSSProperties} root.style * @param {import("react").className} root.className */ -export function ModalsView({ modalsManager, modals, style, className }) { +export const ModalsView = memo(({ modalsManager, modals, style, className }) => { return (
{modals?.map((modal) => @@ -18,4 +19,4 @@ export function ModalsView({ modalsManager, modals, style, className }) { )}
); -} \ No newline at end of file +}); \ No newline at end of file diff --git a/src/components/task-bar/AppButton.jsx b/src/components/task-bar/AppButton.jsx new file mode 100644 index 0000000..6d1da2b --- /dev/null +++ b/src/components/task-bar/AppButton.jsx @@ -0,0 +1,36 @@ +import { memo, useEffect, useState } from "react"; +import Application from "../../features/applications/application.js"; +import { useWindows } from "../../hooks/windows/windowsContext.js"; +import { useWindowsManager } from "../../hooks/windows/windowsManagerContext.js"; +import styles from "./TaskBar.module.css"; +import { ReactSVG } from "react-svg"; + +/** + * @param {object} props + * @param {Application} props.app + */ +export const AppButton = memo(({ app }) => { + const [active, setActive] = useState(false); + const windows = useWindows(); + const windowsManager = useWindowsManager(); + + useEffect(() => { + setActive(windowsManager.isAppActive(app.id)); + }, [app.id, windows, windowsManager]); + + const classNames = [styles["App-icon"]]; + if (active) + classNames.push(styles.Active); + + return ( + + ); +}); \ No newline at end of file diff --git a/src/components/task-bar/TaskBar.jsx b/src/components/task-bar/TaskBar.jsx index 3555d83..b625a01 100644 --- a/src/components/task-bar/TaskBar.jsx +++ b/src/components/task-bar/TaskBar.jsx @@ -1,12 +1,9 @@ -import { useEffect, useRef, useState } from "react"; +import { memo, useRef, useState } from "react"; import styles from "./TaskBar.module.css"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faSearch } from "@fortawesome/free-solid-svg-icons"; import ApplicationsManager from "../../features/applications/applications.js"; -import { useWindows } from "../../hooks/windows/windowsContext.js"; -import { useWindowsManager } from "../../hooks/windows/windowsManagerContext.js"; import { ReactSVG } from "react-svg"; -import Application from "../../features/applications/application.js"; import { HomeMenu } from "./menus/HomeMenu.jsx"; import OutsideClickListener from "../../hooks/utils/outsideClick.js"; import { Battery } from "./indicators/Battery.jsx"; @@ -15,38 +12,9 @@ import { Volume } from "./indicators/Volume.jsx"; import { SearchMenu } from "./menus/SearchMenu.jsx"; import { Calendar } from "./indicators/Calendar.jsx"; import { useScrollWithShadow } from "../../hooks/utils/scrollWithShadows.js"; +import { AppButton } from "./AppButton.jsx"; -/** - * @param {object} props - * @param {Application} props.app - */ -function AppButton({ app }) { - const [active, setActive] = useState(false); - const windows = useWindows(); - const windowsManager = useWindowsManager(); - - useEffect(() => { - setActive(windowsManager.isAppActive(app.id)); - }, [app.id, windows, windowsManager]); - - const classNames = [styles["App-icon"]]; - if (active) - classNames.push(styles.Active); - - return ( - - ); -} - -export function Taskbar() { +export const Taskbar = memo(() => { const [showHome, setShowHome] = useState(false); const [showSearch, setShowSearch] = useState(false); const [searchQuery, setSearchQuery] = useState(""); @@ -151,4 +119,4 @@ export function Taskbar() { ); -} \ No newline at end of file +}, []); \ No newline at end of file diff --git a/src/components/windows/WindowView.jsx b/src/components/windows/WindowView.jsx index a61d8a0..27e7626 100644 --- a/src/components/windows/WindowView.jsx +++ b/src/components/windows/WindowView.jsx @@ -24,7 +24,7 @@ import { useContextMenu } from "../../hooks/modals/contextMenu.js"; * @param {object} props.options * @param {boolean} props.active */ -export const WindowView = memo(function Window({ id, app, size, position, onInteract, options, active }) { +export const WindowView = memo(({ id, app, size, position, onInteract, options, active }) => { const windowsManager = useWindowsManager(); const nodeRef = useRef(null); const [modalsManager, modals] = useModals(); diff --git a/src/components/windows/WindowsView.jsx b/src/components/windows/WindowsView.jsx index 2858e27..3acd689 100644 --- a/src/components/windows/WindowsView.jsx +++ b/src/components/windows/WindowsView.jsx @@ -1,9 +1,9 @@ import { useWindows } from "../../hooks/windows/windowsContext.js"; import { useWindowsManager } from "../../hooks/windows/windowsManagerContext.js"; -import { useEffect, useState } from "react"; +import { memo, useEffect, useState } from "react"; import { WindowView } from "./WindowView.jsx"; -export function WindowsView() { +export const WindowsView = memo(() => { const windows = useWindows(); const windowsManager = useWindowsManager(); const [sortedWindows, setSortedWindows] = useState([]); @@ -30,4 +30,4 @@ export function WindowsView() { /> )} ); -} \ No newline at end of file +}); \ No newline at end of file diff --git a/src/hooks/utils/outsideClick.js b/src/hooks/utils/outsideClick.js index 942b2ae..68a61cd 100644 --- a/src/hooks/utils/outsideClick.js +++ b/src/hooks/utils/outsideClick.js @@ -2,28 +2,19 @@ * https://stackoverflow.com/a/42234988 */ -import { useRef, useEffect } from "react"; +import { useRef, useEffect, memo } from "react"; -/** - * Hook that alerts clicks outside of the passed ref - * @param {import("react").ElementRef} ref - * @param {Function} callback - */ function useOutsideClickListener(ref, callback) { useEffect(() => { - /** - * Alert if clicked on outside of element - * @param event - */ - function handleClickOutside(event) { + const handleClickOutside = (event) => { if (ref.current && !ref.current.contains(event.target)) { callback(event); } - } - // Bind the event listener + }; + document.addEventListener("mousedown", handleClickOutside); + return () => { - // Unbind the event listener on clean up document.removeEventListener("mousedown", handleClickOutside); }; }, [ref, callback]); @@ -34,9 +25,11 @@ function useOutsideClickListener(ref, callback) { * @param {Function} props.onOutsideClick * @param {import("react").ElementType} props.children */ -export default function OutsideClickListener({ onOutsideClick, children }) { +const OutsideClickListener = memo(({ onOutsideClick, children }) => { const wrapperRef = useRef(null); useOutsideClickListener(wrapperRef, onOutsideClick); return
{children}
; -} +}); + +export default OutsideClickListener; \ No newline at end of file