Added commands to terminal

This commit is contained in:
Prozilla 2023-07-18 23:14:02 +02:00
parent f39e663cd7
commit f8efae6f22
No known key found for this signature in database
GPG key ID: 5858DFE71CAF31EE
9 changed files with 244 additions and 9 deletions

8
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,8 @@
{
"editor.rulers": [
120
],
"workbench.colorCustomizations": {
"editorRuler.foreground": "#1F1F1F"
}
}

View file

@ -4,6 +4,9 @@
}
.Window-container {
position: absolute;
display: flex;
flex-direction: column;
background-color: var(--background-color-c);
}
@ -29,9 +32,15 @@
}
.Window-icon {
height: 1rem;
margin-right: 0.25rem;
}
.Window-icon > div {
display: flex;
align-items: center;
}
.Header > p {
user-select: none;
margin: 0;
@ -59,5 +68,5 @@
}
.Window-content {
overflow: hidden;
}

View file

@ -7,10 +7,11 @@ import { useWindowsManager } from "../hooks/WindowsManagerContext.js";
import Draggable from "react-draggable";
import { useEffect, useRef, useState } from "react";
export function Window({ id, app, size, position, focused = false, minimized = false }) {
export function Window({ id, app, size, position, focused = false }) {
const windowsManager = useWindowsManager();
const nodeRef = useRef(null);
const [maximized, setMaximized] = useState(false);
const [minimized, setMinimized] = useState(false);
const [screenWidth, setScreenWidth] = useState(100);
const [screenHeight, setScreenHeight] = useState(100);
@ -24,6 +25,12 @@ export function Window({ id, app, size, position, focused = false, minimized = f
resizeObserver.observe(document.getElementById("root"));
});
const classNames = ["Window-container"];
if (maximized)
classNames.push("Maximized");
if (minimized)
classNames.push("Minimized");
return (
<Draggable
axis="both"
@ -42,7 +49,7 @@ export function Window({ id, app, size, position, focused = false, minimized = f
disabled={maximized}
>
<div
className={`Window-container ${maximized ? "Maximized" : ""}`}
className={classNames.join(" ")}
ref={nodeRef}
style={{
width: maximized ? screenWidth : size.x,
@ -52,7 +59,7 @@ export function Window({ id, app, size, position, focused = false, minimized = f
<div className="Header">
<ReactSVG className="Window-icon" src={process.env.PUBLIC_URL + `/media/applications/icons/${app.id}.svg`}/>
<p>{app.name}</p>
<button>
<button onClick={() => setMinimized(!minimized)}>
<FontAwesomeIcon icon={faMinus}/>
</button>
<button onClick={() => setMaximized(!maximized)}>
@ -63,7 +70,7 @@ export function Window({ id, app, size, position, focused = false, minimized = f
</button>
</div>
<div className="Window-content">
{app.windowContent}
</div>
</div>
</Draggable>

View file

@ -0,0 +1,127 @@
import { useEffect, useState } from "react";
import styles from "./Terminal.module.css";
import { Command } from "./commands.js";
const PREFIX = "$ ";
function OutputLine({ text }) {
return (
<p className={styles.Output}>{text}</p>
);
}
function InputLine({ value, prefix, onChange, onKeyUp, onKeyDown }) {
return (
<span className={styles.Input}>
{prefix && <p className={[styles.Prefix]}>{prefix}</p>}
<input
value={value}
onChange={onChange}
onKeyUp={onKeyUp}
onKeyDown={onKeyDown}
spellCheck={false}
autoFocus
/>
</span>
);
}
export function Terminal() {
const [inputValue, setInputValue] = useState("");
const [history, setHistory] = useState([]);
const updatedHistory = history;
const pushHistory = (entry) => {
updatedHistory.push(entry);
setHistory(updatedHistory);
};
const promptOutput = (text) => {
pushHistory({
text,
isInput: false
});
};
const submitInput = (value) => {
// To do: make empty submit go to new line
if (value.trim() === "")
return;
pushHistory({
text: PREFIX + value,
isInput: true
});
setInputValue("");
value = value.trim();
const args = value.split(/ +/);
const commandName = args.shift().toLowerCase();
const command = Command.find(commandName);
if (!command) {
return promptOutput("Command not found: " + commandName);
}
let response = null;
try {
response = command.execute(args, {
promptOutput,
pushHistory
});
if (response == null)
return promptOutput("Command failed.");
if (!response.blank)
promptOutput(response);
} catch (error) {
promptOutput("Command failed.");
console.error(error);
}
};
const onKeyDown = (event) => {
const value = event.target.value;
// console.log(event);
if (event.key === "Enter") {
submitInput(value);
}
};
const onChange = (event) => {
const value = event.target.value;
return setInputValue(value);
};
const displayHistory = () => {
const visibleHistory = history.slice(-16);
let startIndex = 0;
visibleHistory.forEach((entry, index) => {
if (entry.clear)
startIndex = index + 1;
});
return visibleHistory.slice(startIndex).map(({ text }, index) => {
return <OutputLine text={text} key={index}/>
});
}
return (
<div className={styles.Terminal}>
{displayHistory()}
<InputLine
value={inputValue}
prefix={PREFIX}
onKeyDown={onKeyDown}
onChange={onChange}
/>
</div>
)
}

View file

@ -0,0 +1,34 @@
.Terminal {
display: flex;
flex-direction: column;
padding: 0.5rem;
overflow-y: auto;
height: 100%;
}
.Terminal p {
margin: 0;
}
.Prefix {
width: max-content;
}
.Input, .Output {
display: flex;
flex-direction: row;
align-items: center;
width: 100%;
height: 1.25rem;
font-size: 1rem;
}
.Input input {
width: 100%;
height: 100%;
background: none;
border: none;
outline: none;
font-size: inherit;
margin-left: 0.15rem;
}

View file

@ -0,0 +1,42 @@
export class Command {
/**
* @param {String} name
* @param {Function} execute
*/
constructor(name, execute) {
this.name = name;
this.execute = execute;
this.aliases = [];
}
addAlias(alias) {
this.aliases.push(alias);
}
static find(name) {
let matchCommand = null;
this.COMMANDS.forEach((command) => {
if (command.name === name) {
matchCommand = command;
return;
}
});
return matchCommand;
}
static COMMANDS = [
new Command("echo", (args) => {
return args.join(" ");
}),
new Command("clear", (args, { pushHistory }) => {
pushHistory({
clear: true,
isInput: false
});
return { blank: true };
}),
]
}

View file

@ -1,9 +1,14 @@
/* eslint-disable eqeqeq */
import { Terminal } from "../../components/applications/terminal/Terminal.js";
import Application from "./application.js";
export default class ApplicationsManager {
static APPLICATIONS = [
new Application("Terminal", "terminal"),
new Application("Terminal", "terminal", <Terminal/>),
// new Application("Browser", "browser"),
new Application("Code Editor", "code-editor"),
new Application("File Explorer", "file-explorer"),
new Application("Media Viewer", "media-viewer"),
]
static getApplication(id) {

View file

@ -0,0 +1,3 @@
export function randomRange(min, max) {
return Math.random() * (max - min) + min;
}

View file

@ -1,4 +1,5 @@
import ApplicationsManager from "../applications/applications.js";
import { randomRange } from "../math/random.js";
import Vector2 from "../math/vector2.js";
export default class WindowsManager {
@ -10,8 +11,8 @@ export default class WindowsManager {
open(appId) {
const app = ApplicationsManager.getApplication(appId);
const size = new Vector2(800, 400);
const position = new Vector2(300, 200);
const size = new Vector2(700, 400);
const position = new Vector2(randomRange(50, 600), randomRange(50, 450));
let id = 0;
while (this.windowIds.includes(id.toString())) {
@ -28,7 +29,6 @@ export default class WindowsManager {
};
this.updateWindows(this.windows);
// console.log(this);
}