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);
}