From 584bf734ac9b0223c736c7438e789c4a58b955e2 Mon Sep 17 00:00:00 2001 From: Prozilla Date: Sat, 12 Aug 2023 20:17:50 +0200 Subject: [PATCH] Added custom terminal cursor + fixed style issues --- .../applications/terminal/Terminal.jsx | 92 +++++++++++++++---- .../applications/terminal/Terminal.module.css | 48 +++++++++- src/components/windows/WindowView.jsx | 5 +- src/components/windows/WindowsView.jsx | 3 +- .../applications/terminal/commands.js | 23 +++-- src/styles/global.css | 7 +- 6 files changed, 143 insertions(+), 35 deletions(-) diff --git a/src/components/applications/terminal/Terminal.jsx b/src/components/applications/terminal/Terminal.jsx index 96171fa..b93a9f7 100644 --- a/src/components/applications/terminal/Terminal.jsx +++ b/src/components/applications/terminal/Terminal.jsx @@ -1,7 +1,8 @@ -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState } from "react"; 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"; const USERNAME = "user"; const HOSTNAME = "prozilla-os"; @@ -27,22 +28,45 @@ function OutputLine({ text }) { * @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 }) { +function InputLine({ value, prefix, onChange, onKeyUp, onKeyDown, inputRef }) { + const [cursorPosition, setCursorPosition] = useState(0); + + const checkCursorPosition = () => { + setCursorPosition(inputRef.current?.selectionStart); + }; + return ( {prefix &&

{prefix}

} -
); } @@ -53,6 +77,8 @@ export function Terminal({ setTitle }) { const [history, setHistory] = useState([]); const virtualRoot = useVirtualRoot(); const [currentDirectory, setCurrentDirectory] = useState(virtualRoot.navigate("~")); + const inputRef = useRef(null); + const [historyIndex, setHistoryIndex] = useState(0); useEffect(() => { setTitle(`${USERNAME}@${HOSTNAME}: ${currentDirectory.root ? "/" : currentDirectory.path}`); @@ -76,7 +102,8 @@ export function Terminal({ setTitle }) { const submitInput = (value) => { pushHistory({ text: prefix + value, - isInput: true + isInput: true, + value }); setInputValue(""); @@ -119,13 +146,38 @@ export function Terminal({ setTitle }) { } }; + const updateHistoryIndex = (delta) => { + const inputHistory = history.filter(({ isInput }) => isInput); + const index = clamp(historyIndex + delta, 0, inputHistory.length); + + if (index === historyIndex) { + if (delta < 0) { + setInputValue(""); + } + + return; + } + + if (index === 0) { + setInputValue(""); + } else { + setInputValue(inputHistory[inputHistory.length - index].value); + } + + setHistoryIndex(index); + }; + const onKeyDown = (event) => { const value = event.target.value; + const { key } = event; - // console.log(event); - if (event.key === "Enter") { + if (key === "Enter") { submitInput(value); setInputKey((previousKey) => previousKey + 1); + } else if (key === "ArrowUp") { + updateHistoryIndex(1); + } else if (key === "ArrowDown") { + updateHistoryIndex(-1); } }; @@ -169,6 +221,12 @@ export function Terminal({ setTitle }) { className={styles.Terminal} onMouseDown={onMouseDown} onContextMenu={onContextMenu} + onClick={(event) => { + if (window.getSelection().toString() === "") { + event.preventDefault(); + inputRef.current?.focus(); + } + }} > {displayHistory()} ); diff --git a/src/components/applications/terminal/Terminal.module.css b/src/components/applications/terminal/Terminal.module.css index 923fb1d..d984352 100644 --- a/src/components/applications/terminal/Terminal.module.css +++ b/src/components/applications/terminal/Terminal.module.css @@ -1,13 +1,18 @@ .Terminal { + --char-width: 0.626rem; + display: flex; flex-direction: column; + align-items: flex-start; padding: 0.5rem; - overflow-y: auto; + overflow: auto; height: 100%; + cursor: text; } .Terminal * { font-family: var(--mono-font-family); + letter-spacing: 0.01rem; } .Terminal p { @@ -27,21 +32,54 @@ line-height: 1.25rem; font-size: 1rem; text-align: start; - white-space: break-spaces; + white-space: pre; } .Input { height: 1.25rem; } -.Input input { - width: 100%; +.Input-container { + --cursor-width: var(--char-width); + --cursor-offset: 0; + + position: relative; height: 100%; + width: fit-content; + margin-left: var(--char-width); +} + +.Input-container::after { + content: ""; + position: absolute; + top: 0; + left: calc(var(--cursor-offset) * var(--char-width) * 0.9746124950079872); + width: var(--cursor-width); + height: 100%; + background-color: var(--foreground-color-a); + animation: blink 1000ms step-end infinite; +} + +.Input-container input { + opacity: 0; + position: absolute; + left: 0; + width: 100%; + padding: 0; background: none; border: none; outline: none; font-size: inherit; - margin-left: 0.45rem; + caret-color: transparent; +} + +@keyframes blink { + from, to { + background-color: transparent; + } + 50% { + background-color: var(--foreground-color-a); + } } .Input label { diff --git a/src/components/windows/WindowView.jsx b/src/components/windows/WindowView.jsx index 37a739c..88240b9 100644 --- a/src/components/windows/WindowView.jsx +++ b/src/components/windows/WindowView.jsx @@ -22,8 +22,9 @@ import { useContextMenu } from "../../hooks/modals/ContextMenu.js"; * @param {boolean} props.focused * @param {Function} props.onInteract * @param {object} props.options + * @param {boolean} props.active */ -export const WindowView = memo(function Window({ id, app, size, position, focused = false, onInteract, options }) { +export const WindowView = memo(function Window({ id, app, size, position, onInteract, options, active }) { const windowsManager = useWindowsManager(); const nodeRef = useRef(null); const [modalsManager, modals] = useModals(); @@ -161,7 +162,7 @@ export const WindowView = memo(function Window({ id, app, size, position, focuse
- +
diff --git a/src/components/windows/WindowsView.jsx b/src/components/windows/WindowsView.jsx index 70256ea..0cdbcf8 100644 --- a/src/components/windows/WindowsView.jsx +++ b/src/components/windows/WindowsView.jsx @@ -17,9 +17,10 @@ export function WindowsView() { // TO DO: prevent windows from being rerendered when order is changed return (
- {sortedWindows.map(({ id, app, size, position, options }) => + {sortedWindows.map(({ id, app, size, position, options }, index) => { windowsManager.focus(id); }} + active={index === 0} id={id} key={id} app={app} diff --git a/src/features/applications/terminal/commands.js b/src/features/applications/terminal/commands.js index c71a97d..f2e48a2 100644 --- a/src/features/applications/terminal/commands.js +++ b/src/features/applications/terminal/commands.js @@ -2,23 +2,22 @@ import { START_DATE } from "../../../index.js"; import { formatRelativeTime } from "../../utils/date.js"; import ApplicationsManager from "../applications.js"; -export const ASCII_LOGO = ` +export const ASCII_LOGO = ` :. -==. .=====: ---::..:=======-. :===+=----------::.. =+=---------------:.. - --------------=-----:. - .:-+=---=*#*#*==*#*##=---. - :==+---=#%+=+%#+##**+=---:. - .=---=#%+=+%*+*++*%+---:. - ==---=*###*==*###*=---. + --------------------:. + .:-+=----*###*--*####=---. + :==+----#%+-+%#-##%*+----:. + .=----#%+-+%#-*+-%#+---:. + ==----*###*--*###*----. ==+-------------------:. ...::---------------:. .::---------::.. - ....::... -`; + ....::... `; export class Command { /** @@ -149,9 +148,10 @@ export class Command { }), new Command("neofetch", (args, { username, hostname }) => { const leftColumn = ASCII_LOGO.split("\n"); + const rightColumnWidth = username.length + hostname.length + 1; const rightColumn = [ `${username}@${hostname}`, - "-".repeat(username.length + hostname.length + 1), + "-".repeat(rightColumnWidth), "OS: ProzillaOS", `UPTIME: ${formatRelativeTime(START_DATE, 2, false)}`, `RESOLUTION: ${window.innerWidth}x${window.innerHeight}`, @@ -161,11 +161,14 @@ export class Command { ]; const combined = []; - for (let i = 1; i < leftColumn.length - 1; i++) { + for (let i = 1; i < leftColumn.length; i++) { let line = `${leftColumn[i]} `; if (i <= rightColumn.length) { line += rightColumn[i - 1]; + } else { + // This fixes a weird display bug on Safari mobile + line += " ".repeat(rightColumnWidth); } combined.push(line); diff --git a/src/styles/global.css b/src/styles/global.css index af61bc5..bf8a3ff 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -105,7 +105,8 @@ code { } *::-webkit-scrollbar { - width: 20px; + width: 1.25rem; + height: 1.25rem; } *::-webkit-scrollbar-track { @@ -120,4 +121,8 @@ code { backdrop-filter: invert(100%); transition: 200ms ease-in-out; z-index: 1; +} + +*::-webkit-scrollbar-corner { + background-color: transparent; } \ No newline at end of file