diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..9eb3ff6 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "editor.rulers": [ + 120 + ], + "workbench.colorCustomizations": { + "editorRuler.foreground": "#1F1F1F" + } +} \ No newline at end of file diff --git a/src/components/Window.css b/src/components/Window.css index db7561c..c45b3bd 100644 --- a/src/components/Window.css +++ b/src/components/Window.css @@ -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; } \ No newline at end of file diff --git a/src/components/Window.js b/src/components/Window.js index 1a53039..60faa66 100644 --- a/src/components/Window.js +++ b/src/components/Window.js @@ -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 (

{app.name}

-
- + {app.windowContent}
diff --git a/src/components/applications/terminal/Terminal.js b/src/components/applications/terminal/Terminal.js new file mode 100644 index 0000000..69d2a63 --- /dev/null +++ b/src/components/applications/terminal/Terminal.js @@ -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 ( +

{text}

+ ); +} + +function InputLine({ value, prefix, onChange, onKeyUp, onKeyDown }) { + return ( + + {prefix &&

{prefix}

} + +
+ ); +} + +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 + }); + } + + return ( +
+ {displayHistory()} + +
+ ) +} \ No newline at end of file diff --git a/src/components/applications/terminal/Terminal.module.css b/src/components/applications/terminal/Terminal.module.css new file mode 100644 index 0000000..8b3852a --- /dev/null +++ b/src/components/applications/terminal/Terminal.module.css @@ -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; +} \ No newline at end of file diff --git a/src/components/applications/terminal/commands.js b/src/components/applications/terminal/commands.js new file mode 100644 index 0000000..44df198 --- /dev/null +++ b/src/components/applications/terminal/commands.js @@ -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 }; + }), + ] +} \ No newline at end of file diff --git a/src/modules/applications/applications.js b/src/modules/applications/applications.js index 0f85435..8df43cf 100644 --- a/src/modules/applications/applications.js +++ b/src/modules/applications/applications.js @@ -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", ), + // 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) { diff --git a/src/modules/math/random.js b/src/modules/math/random.js new file mode 100644 index 0000000..9df23a3 --- /dev/null +++ b/src/modules/math/random.js @@ -0,0 +1,3 @@ +export function randomRange(min, max) { + return Math.random() * (max - min) + min; +} \ No newline at end of file diff --git a/src/modules/windows/windows.js b/src/modules/windows/windows.js index 6655789..6bc07fb 100644 --- a/src/modules/windows/windows.js +++ b/src/modules/windows/windows.js @@ -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); }