Added commands to terminal
This commit is contained in:
parent
f39e663cd7
commit
f8efae6f22
9 changed files with 244 additions and 9 deletions
8
.vscode/settings.json
vendored
Normal file
8
.vscode/settings.json
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"editor.rulers": [
|
||||
120
|
||||
],
|
||||
"workbench.colorCustomizations": {
|
||||
"editorRuler.foreground": "#1F1F1F"
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
127
src/components/applications/terminal/Terminal.js
Normal file
127
src/components/applications/terminal/Terminal.js
Normal 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>
|
||||
)
|
||||
}
|
||||
34
src/components/applications/terminal/Terminal.module.css
Normal file
34
src/components/applications/terminal/Terminal.module.css
Normal 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;
|
||||
}
|
||||
42
src/components/applications/terminal/commands.js
Normal file
42
src/components/applications/terminal/commands.js
Normal 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 };
|
||||
}),
|
||||
]
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
3
src/modules/math/random.js
Normal file
3
src/modules/math/random.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export function randomRange(min, max) {
|
||||
return Math.random() * (max - min) + min;
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue