Optimized component rendering

This commit is contained in:
Prozilla 2023-08-16 21:19:50 +02:00
parent 2f0410bfea
commit 3c7bf65cd2
No known key found for this signature in database
GPG key ID: 5858DFE71CAF31EE
11 changed files with 132 additions and 128 deletions

View file

@ -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 (
<span className={styles.Input}>
{prefix && <p className={[styles.Prefix]}>{prefix}</p>}
<span className={styles["Input-container"]} style={{ "--cursor-offset": cursorPosition }}>
<span aria-hidden="true">{value}</span>
<input
id="input"
value={value}
aria-label="Command input"
onChange={(event) => {
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=""
/>
</span>
</span>
);
}

View file

@ -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) =>
<p key={index} className={styles.Output}>{line}</p>
)}
</>);
}

View file

@ -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) =>
<p key={index} className={styles.Output}>{line}</p>
)}
</>);
}
/**
* @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 (
<span className={styles.Input}>
{prefix && <p className={[styles.Prefix]}>{prefix}</p>}
<span className={styles["Input-container"]} style={{ "--cursor-offset": cursorPosition }}>
<span aria-hidden="true">{value}</span>
<input
id="input"
value={value}
aria-label="Command input"
onChange={(event) => {
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=""
/>
</span>
</span>
);
}
export function Terminal({ setTitle }) {
const [inputKey, setInputKey] = useState(0);
const [inputValue, setInputValue] = useState("");

View file

@ -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() {
<ModalsView modalsManager={modalsManager} modals={modals}/>
</div>
</>);
}
});

View file

@ -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 (
<OutsideClickListener onOutsideClick={() => { modal.close(); }}>
<div className={styles.Container} style={{ "--position-x": modal.position.x, "--position-y": modal.position.y }}>
@ -14,4 +15,4 @@ export function ModalView({ modal }) {
</div>
</OutsideClickListener>
);
}
});

View file

@ -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 (
<div style={style} className={`${styles.Container} ${className}`}>
{modals?.map((modal) =>
@ -18,4 +19,4 @@ export function ModalsView({ modalsManager, modals, style, className }) {
)}
</div>
);
}
});

View file

@ -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 (
<button
key={app.id}
tabIndex={0}
className={classNames.join(" ")}
onClick={() => { windowsManager.open(app.id); }}
title={app.name}
>
<ReactSVG src={`${process.env.PUBLIC_URL}/media/applications/icons/${app.id}.svg`}/>
</button>
);
});

View file

@ -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 (
<button
key={app.id}
tabIndex={0}
className={classNames.join(" ")}
onClick={() => { windowsManager.open(app.id); }}
title={app.name}
>
<ReactSVG src={`${process.env.PUBLIC_URL}/media/applications/icons/${app.id}.svg`}/>
</button>
);
}
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() {
</div>
</div>
);
}
}, []);

View file

@ -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();

View file

@ -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() {
/>
)}
</div>);
}
});

View file

@ -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 <div ref={wrapperRef}>{children}</div>;
}
});
export default OutsideClickListener;