Optimized component rendering
This commit is contained in:
parent
2f0410bfea
commit
3c7bf65cd2
11 changed files with 132 additions and 128 deletions
53
src/components/applications/terminal/InputLine.jsx
Normal file
53
src/components/applications/terminal/InputLine.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
15
src/components/applications/terminal/OutputLine.jsx
Normal file
15
src/components/applications/terminal/OutputLine.jsx
Normal 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>
|
||||
)}
|
||||
</>);
|
||||
}
|
||||
|
|
@ -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("");
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>);
|
||||
}
|
||||
});
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
});
|
||||
36
src/components/task-bar/AppButton.jsx
Normal file
36
src/components/task-bar/AppButton.jsx
Normal 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>
|
||||
);
|
||||
});
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}, []);
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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>);
|
||||
}
|
||||
});
|
||||
|
|
@ -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;
|
||||
Loading…
Reference in a new issue