Updated terminal app
This commit is contained in:
parent
23062bcd29
commit
c4868a040f
38 changed files with 688 additions and 184 deletions
|
|
@ -11,7 +11,7 @@ import { ModalsView } from "../../modals/ModalsView.jsx";
|
||||||
import { QuickAccessButton } from "./QuickAccessButton.jsx";
|
import { QuickAccessButton } from "./QuickAccessButton.jsx";
|
||||||
import { useDialogBox } from "../../../hooks/modals/dialogBox.js";
|
import { useDialogBox } from "../../../hooks/modals/dialogBox.js";
|
||||||
import Vector2 from "../../../features/math/vector2.js";
|
import Vector2 from "../../../features/math/vector2.js";
|
||||||
import { DIALOG_CONTENT_TYPES } from "../../modals/dialog-box/DialogBox.jsx";
|
import { DIALOG_CONTENT_TYPES } from "../../../constants/modals.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object} props
|
* @param {object} props
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,7 @@ import { SettingsManager } from "../../../features/settings/settingsManager.js";
|
||||||
import styles from "./Settings.module.css";
|
import styles from "./Settings.module.css";
|
||||||
import { useVirtualRoot } from "../../../hooks/virtual-drive/virtualRootContext.js";
|
import { useVirtualRoot } from "../../../hooks/virtual-drive/virtualRootContext.js";
|
||||||
import { useSettingsManager } from "../../../hooks/settings/settingsManagerContext.js";
|
import { useSettingsManager } from "../../../hooks/settings/settingsManagerContext.js";
|
||||||
|
import { WALLPAPERS_PATH } from "../../../constants/applications/settings.js";
|
||||||
const wallpapersPath = "~/Images/Wallpapers";
|
|
||||||
|
|
||||||
export function AppearanceSettings() {
|
export function AppearanceSettings() {
|
||||||
const virtualRoot = useVirtualRoot();
|
const virtualRoot = useVirtualRoot();
|
||||||
|
|
@ -25,7 +24,7 @@ export function AppearanceSettings() {
|
||||||
<div className={styles["Option"]}>
|
<div className={styles["Option"]}>
|
||||||
<p className={styles["Label"]}>Wallpaper</p>
|
<p className={styles["Label"]}>Wallpaper</p>
|
||||||
<div className={styles["Input"]}>
|
<div className={styles["Input"]}>
|
||||||
{virtualRoot.navigate(wallpapersPath)?.getFiles()?.toReversed().map(({ id, source }) =>
|
{virtualRoot.navigate(WALLPAPERS_PATH)?.getFiles()?.toReversed().map(({ id, source }) =>
|
||||||
<label className={styles["Image-select"]} key={id}>
|
<label className={styles["Image-select"]} key={id}>
|
||||||
<input
|
<input
|
||||||
type="radio"
|
type="radio"
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ export function OutputLine({ text }) {
|
||||||
|
|
||||||
return (<>
|
return (<>
|
||||||
{lines.map((line, index) =>
|
{lines.map((line, index) =>
|
||||||
<p key={index} className={styles.Output}>{line}</p>
|
<pre key={index} className={styles.Output}>{line === "" ? " " : line}</pre>
|
||||||
)}
|
)}
|
||||||
</>);
|
</>);
|
||||||
}
|
}
|
||||||
|
|
@ -1,13 +1,11 @@
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import styles from "./Terminal.module.css";
|
import styles from "./Terminal.module.css";
|
||||||
import { useVirtualRoot } from "../../../hooks/virtual-drive/virtualRootContext.js";
|
import { useVirtualRoot } from "../../../hooks/virtual-drive/virtualRootContext.js";
|
||||||
import { Command } from "../../../features/applications/terminal/commands.js";
|
|
||||||
import { clamp } from "../../../features/math/clamp.js";
|
import { clamp } from "../../../features/math/clamp.js";
|
||||||
import { OutputLine } from "./OutputLine.jsx";
|
import { OutputLine } from "./OutputLine.jsx";
|
||||||
import { InputLine } from "./InputLine.jsx";
|
import { InputLine } from "./InputLine.jsx";
|
||||||
|
import { HOSTNAME, USERNAME } from "../../../constants/applications/terminal.js";
|
||||||
const USERNAME = "user";
|
import CommandsManager from "../../../features/applications/terminal/commands.js";
|
||||||
const HOSTNAME = "prozilla-os";
|
|
||||||
|
|
||||||
export function Terminal({ setTitle }) {
|
export function Terminal({ setTitle }) {
|
||||||
const [inputKey, setInputKey] = useState(0);
|
const [inputKey, setInputKey] = useState(0);
|
||||||
|
|
@ -37,28 +35,29 @@ export function Terminal({ setTitle }) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const submitInput = (value) => {
|
const handleInput = (value) => {
|
||||||
pushHistory({
|
const rawInputValueStart = value.indexOf(" ") + 1;
|
||||||
text: prefix + value,
|
const rawInputValue = rawInputValueStart <= 0 ? "" : value.substr(rawInputValueStart);
|
||||||
isInput: true,
|
|
||||||
value
|
|
||||||
});
|
|
||||||
|
|
||||||
setInputValue("");
|
|
||||||
|
|
||||||
value = value.trim();
|
value = value.trim();
|
||||||
|
|
||||||
if (value === "")
|
if (value === "")
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const args = value.split(/ +/);
|
const args = value.split(/ +/);
|
||||||
|
|
||||||
|
if (args[0].toLowerCase() === "sudo" && args.length > 1) {
|
||||||
|
args.shift();
|
||||||
|
}
|
||||||
|
|
||||||
const commandName = args.shift().toLowerCase();
|
const commandName = args.shift().toLowerCase();
|
||||||
|
|
||||||
const command = Command.find(commandName);
|
const command = CommandsManager.find(commandName);
|
||||||
|
|
||||||
if (!command) {
|
if (!command)
|
||||||
return promptOutput(`${commandName}: Command not found`);
|
return `${commandName}: Command not found`;
|
||||||
}
|
|
||||||
|
if (command.requireArgs && args.length === 0)
|
||||||
|
return `${commandName}: Incorrect syntax: ${commandName} requires at least 1 argument`;
|
||||||
|
|
||||||
let response = null;
|
let response = null;
|
||||||
|
|
||||||
|
|
@ -71,19 +70,43 @@ export function Terminal({ setTitle }) {
|
||||||
setCurrentDirectory,
|
setCurrentDirectory,
|
||||||
username: USERNAME,
|
username: USERNAME,
|
||||||
hostname: HOSTNAME,
|
hostname: HOSTNAME,
|
||||||
|
rawInputValue
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response == null)
|
if (response == null)
|
||||||
return promptOutput(`${commandName}: Command failed`);
|
return `${commandName}: Command failed`;
|
||||||
|
|
||||||
if (!response.blank)
|
if (!response.blank)
|
||||||
promptOutput(response);
|
return response;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
promptOutput(`${commandName}: Command failed`);
|
return `${commandName}: Command failed`;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const submitInput = (value) => {
|
||||||
|
pushHistory({
|
||||||
|
text: prefix + value,
|
||||||
|
isInput: true,
|
||||||
|
value
|
||||||
|
});
|
||||||
|
|
||||||
|
setInputValue("");
|
||||||
|
setHistoryIndex(0);
|
||||||
|
|
||||||
|
// Piping is used to chain commands
|
||||||
|
const segments = value.split(" | ");
|
||||||
|
|
||||||
|
let output = null;
|
||||||
|
segments.forEach((segment) => {
|
||||||
|
// Output from the previous command gets added as an argument for the next command
|
||||||
|
output = handleInput(output ? `${segment} ${output}` : segment);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (output)
|
||||||
|
promptOutput(output);
|
||||||
|
};
|
||||||
|
|
||||||
const updateHistoryIndex = (delta) => {
|
const updateHistoryIndex = (delta) => {
|
||||||
const inputHistory = history.filter(({ isInput }) => isInput);
|
const inputHistory = history.filter(({ isInput }) => isInput);
|
||||||
const index = clamp(historyIndex + delta, 0, inputHistory.length);
|
const index = clamp(historyIndex + delta, 0, inputHistory.length);
|
||||||
|
|
@ -142,7 +165,7 @@ export function Terminal({ setTitle }) {
|
||||||
if (event.button === 2) {
|
if (event.button === 2) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
navigator.clipboard.readText().then((text) => {
|
navigator.clipboard.readText?.().then((text) => {
|
||||||
setInputValue(inputValue + text);
|
setInputValue(inputValue + text);
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,9 @@
|
||||||
letter-spacing: 0.01rem;
|
letter-spacing: 0.01rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Terminal p {
|
.Terminal p, .Terminal pre {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
min-height: 1.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Prefix {
|
.Prefix {
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,7 @@ import styles from "./TextEditor.module.css";
|
||||||
import { HeaderMenu } from "../.common/HeaderMenu.jsx";
|
import { HeaderMenu } from "../.common/HeaderMenu.jsx";
|
||||||
import Markdown from "markdown-to-jsx";
|
import Markdown from "markdown-to-jsx";
|
||||||
import Application from "../../../features/applications/application.js";
|
import Application from "../../../features/applications/application.js";
|
||||||
|
import { DEFAULT_ZOOM, ZOOM_FACTOR } from "../../../constants/applications/textEditor.js";
|
||||||
const DEFAULT_ZOOM = 16;
|
|
||||||
const ZOOM_FACTOR = 4;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object} props
|
* @param {object} props
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,7 @@ import { ReactSVG } from "react-svg";
|
||||||
import utilStyles from "../../../styles/utils.module.css";
|
import utilStyles from "../../../styles/utils.module.css";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { faXmark } from "@fortawesome/free-solid-svg-icons";
|
import { faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { DIALOG_CONTENT_TYPES } from "../../../constants/modals.js";
|
||||||
export const DIALOG_CONTENT_TYPES = {
|
|
||||||
CloseButton: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
export function DialogBox({ modal, params }) {
|
export function DialogBox({ modal, params }) {
|
||||||
const { app, title, children } = params;
|
const { app, title, children } = params;
|
||||||
|
|
|
||||||
1
src/constants/applications/settings.js
Normal file
1
src/constants/applications/settings.js
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export const WALLPAPERS_PATH = "~/Images/Wallpapers";
|
||||||
3
src/constants/applications/terminal.js
Normal file
3
src/constants/applications/terminal.js
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
export const USERNAME = "user";
|
||||||
|
export const HOSTNAME = "prozilla-os";
|
||||||
|
export const MAX_WIDTH = 50;
|
||||||
2
src/constants/applications/textEditor.js
Normal file
2
src/constants/applications/textEditor.js
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
export const DEFAULT_ZOOM = 16;
|
||||||
|
export const ZOOM_FACTOR = 4;
|
||||||
7
src/constants/modals.js
Normal file
7
src/constants/modals.js
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
import Vector2 from "../features/math/vector2.js";
|
||||||
|
|
||||||
|
export const DIALOG_CONTENT_TYPES = {
|
||||||
|
CloseButton: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DEFAULT_DIALOG_SIZE = new Vector2(400, 200);
|
||||||
2
src/constants/windows.js
Normal file
2
src/constants/windows.js
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
export const SCREEN_MARGIN = 32;
|
||||||
|
export const TASKBAR_HEIGHT = 48;
|
||||||
67
src/features/applications/terminal/command.js
Normal file
67
src/features/applications/terminal/command.js
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
import { VirtualFolder } from "../../virtual-drive/virtualFolder.js";
|
||||||
|
import { VirtualRoot } from "../../virtual-drive/virtualRoot.js";
|
||||||
|
|
||||||
|
export default class Command {
|
||||||
|
/** @type {string} */
|
||||||
|
name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string[]} args
|
||||||
|
* @param {object} options
|
||||||
|
* @param {Function} options.promptOutput
|
||||||
|
* @param {Function} options.pushHistory
|
||||||
|
* @param {VirtualRoot} options.virtualRoot
|
||||||
|
* @param {VirtualFolder} options.currentDirectory
|
||||||
|
* @param {Function} options.setCurrentDirectory
|
||||||
|
* @param {string} options.username
|
||||||
|
* @param {string} options.hostname
|
||||||
|
* @param {string} options.rawInputValue
|
||||||
|
* @returns {string|{ blank: boolean }}
|
||||||
|
*/
|
||||||
|
execute = (args, options) => {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} name
|
||||||
|
* @param {Function} execute
|
||||||
|
*/
|
||||||
|
constructor(name, execute) {
|
||||||
|
this.name = name;
|
||||||
|
this.execute = execute;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} name
|
||||||
|
* @returns {Command}
|
||||||
|
*/
|
||||||
|
setName(name) {
|
||||||
|
this.name = name;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Function} execute
|
||||||
|
* @returns {Command}
|
||||||
|
*/
|
||||||
|
setExecute(execute) {
|
||||||
|
this.execute = execute;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {boolean} value
|
||||||
|
* @returns {Command}
|
||||||
|
*/
|
||||||
|
setRequireArgs(value) {
|
||||||
|
this.requireArgs = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {{ purpose: string, usage: string, description: string}} name
|
||||||
|
* @returns {Command}
|
||||||
|
*/
|
||||||
|
setManual({ purpose, usage, description }) {
|
||||||
|
this.manual = { purpose, usage, description };
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,23 +1,30 @@
|
||||||
import { ASCII_LOGO } from "../../../constants/branding.js";
|
import { blow } from "./commands/blow.js";
|
||||||
import { START_DATE } from "../../../index.js";
|
import { cat } from "./commands/cat.js";
|
||||||
import { formatRelativeTime } from "../../utils/date.js";
|
import { cd } from "./commands/cd.js";
|
||||||
import ApplicationsManager from "../applications.js";
|
import { clear } from "./commands/clear.js";
|
||||||
|
import { cowsay } from "./commands/cowsay.js";
|
||||||
|
import { dir } from "./commands/dir.js";
|
||||||
|
import { echo } from "./commands/echo.js";
|
||||||
|
import { fortune } from "./commands/fortune.js";
|
||||||
|
import { hostname } from "./commands/hostname.js";
|
||||||
|
import { ls } from "./commands/ls.js";
|
||||||
|
import { make } from "./commands/make.js";
|
||||||
|
import { man } from "./commands/man.js";
|
||||||
|
import { mkdir } from "./commands/mkdir.js";
|
||||||
|
import { neofetch } from "./commands/neofetch.js";
|
||||||
|
import { nice } from "./commands/nice.js";
|
||||||
|
import { pwd } from "./commands/pwd.js";
|
||||||
|
import { reboot } from "./commands/reboot.js";
|
||||||
|
import { rm } from "./commands/rm.js";
|
||||||
|
import { rmdir } from "./commands/rmdir.js";
|
||||||
|
import { touch } from "./commands/touch.js";
|
||||||
|
import { world } from "./commands/world.js";
|
||||||
|
|
||||||
export class Command {
|
export default class CommandsManager {
|
||||||
/**
|
/**
|
||||||
* @param {string} name
|
* @param {string} name
|
||||||
* @param {Function} execute
|
* @returns {CommandsManager}
|
||||||
*/
|
*/
|
||||||
constructor(name, execute) {
|
|
||||||
this.name = name;
|
|
||||||
this.execute = execute;
|
|
||||||
this.aliases = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
addAlias(alias) {
|
|
||||||
this.aliases.push(alias);
|
|
||||||
}
|
|
||||||
|
|
||||||
static find(name) {
|
static find(name) {
|
||||||
let matchCommand = null;
|
let matchCommand = null;
|
||||||
|
|
||||||
|
|
@ -32,133 +39,26 @@ export class Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
static COMMANDS = [
|
static COMMANDS = [
|
||||||
new Command("echo", (args) => {
|
echo,
|
||||||
return args.join(" ");
|
clear,
|
||||||
}),
|
ls,
|
||||||
new Command("clear", (args, { pushHistory }) => {
|
cd,
|
||||||
pushHistory({
|
dir,
|
||||||
clear: true,
|
pwd,
|
||||||
isInput: false
|
touch,
|
||||||
});
|
mkdir,
|
||||||
|
rm,
|
||||||
return { blank: true };
|
rmdir,
|
||||||
}),
|
hostname,
|
||||||
new Command("ls", (args, { currentDirectory }) => {
|
neofetch,
|
||||||
let directory = currentDirectory;
|
fortune,
|
||||||
|
cowsay,
|
||||||
if (args.length > 0) {
|
world,
|
||||||
directory = currentDirectory.navigate(args[0]);
|
nice,
|
||||||
}
|
blow,
|
||||||
|
make,
|
||||||
if (!directory)
|
cat,
|
||||||
return `ls: Cannot access '${args[0]}': No such file or directory`;
|
man,
|
||||||
|
reboot,
|
||||||
const folderNames = directory.subFolders.map((folder) => folder.id);
|
|
||||||
const fileNames = directory.files.map((file) => file.id);
|
|
||||||
|
|
||||||
const contents = folderNames.concat(fileNames);
|
|
||||||
|
|
||||||
if (contents.length === 0)
|
|
||||||
return { blank: true };
|
|
||||||
|
|
||||||
return contents.sort((nameA, nameB) => nameA.localeCompare(nameB)).join(" ");
|
|
||||||
}),
|
|
||||||
new Command("cd", (args, { currentDirectory, setCurrentDirectory }) => {
|
|
||||||
const path = args[0] ?? "~";
|
|
||||||
const destination = currentDirectory.navigate(path);
|
|
||||||
|
|
||||||
if (!destination)
|
|
||||||
return `cd: ${args[0]}: No such file or directory`;
|
|
||||||
|
|
||||||
console.log(destination);
|
|
||||||
setCurrentDirectory(destination);
|
|
||||||
return { blank: true };
|
|
||||||
}),
|
|
||||||
new Command("dir", (args, { currentDirectory }) => {
|
|
||||||
const folderNames = currentDirectory.subFolders.map((folder) => folder.id);
|
|
||||||
|
|
||||||
if (folderNames.length === 0)
|
|
||||||
return { blank: true };
|
|
||||||
|
|
||||||
return folderNames.sort((nameA, nameB) => nameA.localeCompare(nameB)).join(" ");
|
|
||||||
}),
|
|
||||||
new Command("pwd", (args, { currentDirectory }) => {
|
|
||||||
if (currentDirectory.root) {
|
|
||||||
return "/";
|
|
||||||
} else {
|
|
||||||
return currentDirectory.absolutePath;
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
new Command("touch", (args, { currentDirectory }) => {
|
|
||||||
const [name, extension] = args[0].split(".");
|
|
||||||
|
|
||||||
if (currentDirectory.findFile(name, extension))
|
|
||||||
return { blank: true };
|
|
||||||
|
|
||||||
currentDirectory.createFile(name, extension);
|
|
||||||
return { blank: true };
|
|
||||||
}),
|
|
||||||
new Command("mkdir", (args, { currentDirectory }) => {
|
|
||||||
const name = args[0];
|
|
||||||
|
|
||||||
if (currentDirectory.findSubFolder(name))
|
|
||||||
return { blank: true };
|
|
||||||
|
|
||||||
currentDirectory.createFolder(name);
|
|
||||||
return { blank: true };
|
|
||||||
}),
|
|
||||||
new Command("rm", (args, { currentDirectory }) => {
|
|
||||||
const [name, extension] = args[0].split(".");
|
|
||||||
const file = currentDirectory.findFile(name, extension);
|
|
||||||
|
|
||||||
if (!file)
|
|
||||||
return `rm: ${args[0]}: No such file`;
|
|
||||||
|
|
||||||
file.delete();
|
|
||||||
return { blank: true };
|
|
||||||
}),
|
|
||||||
new Command("rmdir", (args, { currentDirectory }) => {
|
|
||||||
const name = args[0];
|
|
||||||
const folder = currentDirectory.findSubFolder(name);
|
|
||||||
|
|
||||||
if (!folder)
|
|
||||||
return `rm: ${args[0]}: No such directory`;
|
|
||||||
|
|
||||||
folder.delete();
|
|
||||||
return { blank: true };
|
|
||||||
}),
|
|
||||||
new Command("hostname", (args, { hostname }) => {
|
|
||||||
return hostname;
|
|
||||||
}),
|
|
||||||
new Command("neofetch", (args, { username, hostname }) => {
|
|
||||||
const leftColumn = ASCII_LOGO.split("\n");
|
|
||||||
const rightColumnWidth = username.length + hostname.length + 1;
|
|
||||||
const rightColumn = [
|
|
||||||
`${username}@${hostname}`,
|
|
||||||
"-".repeat(rightColumnWidth),
|
|
||||||
"OS: ProzillaOS",
|
|
||||||
`UPTIME: ${formatRelativeTime(START_DATE, 2, false)}`,
|
|
||||||
`RESOLUTION: ${window.innerWidth}x${window.innerHeight}`,
|
|
||||||
"THEME: default",
|
|
||||||
"ICONS: Font Awesome",
|
|
||||||
`TERMINAL: ${ApplicationsManager.getApplication("terminal")?.name ?? "Unknown"}`,
|
|
||||||
];
|
|
||||||
|
|
||||||
const combined = [];
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
return combined.join("\n");
|
|
||||||
}),
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
5
src/features/applications/terminal/commands/blow.js
Normal file
5
src/features/applications/terminal/commands/blow.js
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
import Command from "../command.js";
|
||||||
|
|
||||||
|
export const blow = new Command("%blow", () => {
|
||||||
|
return "fg: %blow: No such job";
|
||||||
|
});
|
||||||
24
src/features/applications/terminal/commands/cat.js
Normal file
24
src/features/applications/terminal/commands/cat.js
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import Command from "../command.js";
|
||||||
|
|
||||||
|
export const cat = new Command("cat")
|
||||||
|
.setRequireArgs(true)
|
||||||
|
.setManual({
|
||||||
|
purpose: "Concetenate files and print on the standard output",
|
||||||
|
usage: "cat [OPTION]... [FILE]...",
|
||||||
|
description: "Concetenate FILE(s) to standard output."
|
||||||
|
})
|
||||||
|
.setExecute((args, { currentDirectory }) => {
|
||||||
|
const [name, extension] = args[0].split(".");
|
||||||
|
const file = currentDirectory.findFile(name, extension);
|
||||||
|
|
||||||
|
if (!file)
|
||||||
|
return `rm: ${args[0]}: No such file`;
|
||||||
|
|
||||||
|
if (file.content) {
|
||||||
|
return file.content;
|
||||||
|
} else if (file.source) {
|
||||||
|
return `Src: ${file.source}`;
|
||||||
|
} else {
|
||||||
|
return { blank: true };
|
||||||
|
}
|
||||||
|
});
|
||||||
13
src/features/applications/terminal/commands/cd.js
Normal file
13
src/features/applications/terminal/commands/cd.js
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
import Command from "../command.js";
|
||||||
|
|
||||||
|
export const cd = new Command("cd", (args, { currentDirectory, setCurrentDirectory }) => {
|
||||||
|
const path = args[0] ?? "~";
|
||||||
|
const destination = currentDirectory.navigate(path);
|
||||||
|
|
||||||
|
if (!destination)
|
||||||
|
return `cd: ${args[0]}: No such file or directory`;
|
||||||
|
|
||||||
|
console.log(destination);
|
||||||
|
setCurrentDirectory(destination);
|
||||||
|
return { blank: true };
|
||||||
|
}).setRequireArgs(true);
|
||||||
10
src/features/applications/terminal/commands/clear.js
Normal file
10
src/features/applications/terminal/commands/clear.js
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import Command from "../command.js";
|
||||||
|
|
||||||
|
export const clear = new Command("clear", (args, { pushHistory }) => {
|
||||||
|
pushHistory({
|
||||||
|
clear: true,
|
||||||
|
isInput: false
|
||||||
|
});
|
||||||
|
|
||||||
|
return { blank: true };
|
||||||
|
});
|
||||||
99
src/features/applications/terminal/commands/cowsay.js
Normal file
99
src/features/applications/terminal/commands/cowsay.js
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
import { MAX_WIDTH } from "../../../../constants/applications/terminal.js";
|
||||||
|
import Command from "../command.js";
|
||||||
|
|
||||||
|
const COW = `
|
||||||
|
\\ ^__^
|
||||||
|
\\ (oo)\\_______
|
||||||
|
(__)\\ )\\/\\
|
||||||
|
||----w |
|
||||||
|
|| ||`;
|
||||||
|
|
||||||
|
export const cowsay = new Command("cowsay", (args, { rawInputValue }) => {
|
||||||
|
// Separate input value into lines
|
||||||
|
const segments = rawInputValue.split(" ");
|
||||||
|
const lines = [];
|
||||||
|
let currentLine = "";
|
||||||
|
let maxLineWidth = 0;
|
||||||
|
|
||||||
|
const addLine = (line) => {
|
||||||
|
line = line.trimEnd();
|
||||||
|
lines.push(line);
|
||||||
|
if (line.length > maxLineWidth)
|
||||||
|
maxLineWidth = line.length;
|
||||||
|
};
|
||||||
|
|
||||||
|
const nextLine = (word) => {
|
||||||
|
addLine(currentLine);
|
||||||
|
|
||||||
|
if (word) {
|
||||||
|
currentLine = word + " ";
|
||||||
|
} else {
|
||||||
|
currentLine = "";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
segments.forEach((segment) => {
|
||||||
|
// Add empty spaces preceding lines
|
||||||
|
if (segment === "") {
|
||||||
|
currentLine += " ";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const words = segment.split("\n");
|
||||||
|
for (let i = 0; i < words.length; i++) {
|
||||||
|
const word = words[i];
|
||||||
|
|
||||||
|
// Handle next lines
|
||||||
|
if (i > 0)
|
||||||
|
nextLine();
|
||||||
|
|
||||||
|
// Fit word on current line
|
||||||
|
if ((currentLine + word).length <= MAX_WIDTH) {
|
||||||
|
currentLine += word + " ";
|
||||||
|
} else if (word.length > MAX_WIDTH) {
|
||||||
|
const remainingSpaces = MAX_WIDTH - currentLine.length;
|
||||||
|
|
||||||
|
if (remainingSpaces >= 2) {
|
||||||
|
addLine(currentLine + word.substring(0, remainingSpaces - 1) + "-");
|
||||||
|
currentLine = word.substring(remainingSpaces - 1) + " ";
|
||||||
|
} else {
|
||||||
|
nextLine(word);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
nextLine(word);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (currentLine.length > 0)
|
||||||
|
addLine(currentLine);
|
||||||
|
|
||||||
|
// Turn lines into speech bubble
|
||||||
|
const speechBubble = [` ${"_".repeat(maxLineWidth + 2)} `];
|
||||||
|
|
||||||
|
for (let i = 0; i < lines.length; i++) {
|
||||||
|
let line = lines[i];
|
||||||
|
const missingSpaces = maxLineWidth - line.length;
|
||||||
|
|
||||||
|
if (missingSpaces > 0)
|
||||||
|
line += " ".repeat(missingSpaces);
|
||||||
|
|
||||||
|
if (lines.length > 1) {
|
||||||
|
if (i === 0) {
|
||||||
|
line = `/ ${line} \\`;
|
||||||
|
} else if (i === lines.length - 1) {
|
||||||
|
line = `\\ ${line} /`;
|
||||||
|
} else {
|
||||||
|
line = `| ${line} |`;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
line = `< ${line} >`;
|
||||||
|
}
|
||||||
|
|
||||||
|
speechBubble.push(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
speechBubble.push(` ${"-".repeat(maxLineWidth + 2)} `);
|
||||||
|
|
||||||
|
return speechBubble.join("\n") + COW;
|
||||||
|
}).setRequireArgs(true);
|
||||||
10
src/features/applications/terminal/commands/dir.js
Normal file
10
src/features/applications/terminal/commands/dir.js
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import Command from "../command.js";
|
||||||
|
|
||||||
|
export const dir = new Command("dir", (args, { currentDirectory }) => {
|
||||||
|
const folderNames = currentDirectory.subFolders.map((folder) => folder.id);
|
||||||
|
|
||||||
|
if (folderNames.length === 0)
|
||||||
|
return { blank: true };
|
||||||
|
|
||||||
|
return folderNames.sort((nameA, nameB) => nameA.localeCompare(nameB)).join(" ");
|
||||||
|
});
|
||||||
5
src/features/applications/terminal/commands/echo.js
Normal file
5
src/features/applications/terminal/commands/echo.js
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
import Command from "../command.js";
|
||||||
|
|
||||||
|
export const echo = new Command("echo", (args, { rawInputValue }) => {
|
||||||
|
return rawInputValue;
|
||||||
|
});
|
||||||
99
src/features/applications/terminal/commands/fortune.js
Normal file
99
src/features/applications/terminal/commands/fortune.js
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
import { randomFromArray } from "../../../utils/array.js";
|
||||||
|
import Command from "../command.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Source: https://github.com/shlomif/fortune-mod/blob/master/fortune-mod/datfiles/fortunes
|
||||||
|
*/
|
||||||
|
const FORTUNES = [
|
||||||
|
"A day for firm decisions!!!!! Or is it?",
|
||||||
|
"Just to have it is enough.",
|
||||||
|
"Keep emotionally active. Cater to your favorite neurosis.",
|
||||||
|
"Keep it short for pithy sake.",
|
||||||
|
"Lady Luck brings added income today. Lady friend takes it away tonight.",
|
||||||
|
"Learn to pause -- or nothing worthwhile can catch up to you.",
|
||||||
|
"Let me put it this way: today is going to be a learning experience.",
|
||||||
|
"Life is to you a dashing and bold adventure.",
|
||||||
|
"\"Life, loathe it or ignore it, you can't like it.\"\n\n -- Marvin, \"Hitchhiker's Guide to the Galaxy\"",
|
||||||
|
"Live in a world of your own, but always welcome visitors.",
|
||||||
|
"Living your life is a task so difficult, it has never been attempted before.",
|
||||||
|
"Long life is in store for you.",
|
||||||
|
"Look afar and see the end from the beginning.",
|
||||||
|
"Love is in the offing. Be affectionate to one who adores you.",
|
||||||
|
"Make a wish, it might come true.",
|
||||||
|
"Many changes of mind and mood; do not hesitate too long.",
|
||||||
|
"Never be led astray onto the path of virtue.",
|
||||||
|
"Never commit yourself! Let someone else commit you.",
|
||||||
|
"Never give an inch!",
|
||||||
|
"Never look up when dragons fly overhead.",
|
||||||
|
"Never reveal your best argument.",
|
||||||
|
"Next Friday will not be your lucky day. As a matter of fact, you don't have a lucky day this year.",
|
||||||
|
"Of course you have a purpose -- to find a purpose.",
|
||||||
|
"People are beginning to notice you. Try dressing before you leave the house.",
|
||||||
|
"Perfect day for scrubbing the floor and other exciting things.",
|
||||||
|
"Questionable day.\n\nAsk somebody something.",
|
||||||
|
"Reply hazy, ask again later.",
|
||||||
|
"Save energy: be apathetic.",
|
||||||
|
"Ships are safe in harbor, but they were never meant to stay there.",
|
||||||
|
"Slow day. Practice crawling.",
|
||||||
|
"Snow Day -- stay home.",
|
||||||
|
"So this is it. We're going to die.",
|
||||||
|
"So you're back... about time...",
|
||||||
|
"Someone is speaking well of you.",
|
||||||
|
"Someone is speaking well of you.\n\nHow unusual!",
|
||||||
|
"Someone whom you reject today, will reject you tomorrow.",
|
||||||
|
"Stay away from flying saucers today.",
|
||||||
|
"Stay away from hurricanes for a while.",
|
||||||
|
"Stay the curse.",
|
||||||
|
"That secret you've been guarding, isn't.",
|
||||||
|
"The time is right to make new friends.",
|
||||||
|
"The whole world is a tuxedo and you are a pair of brown shoes.\n\n -- George Gobel",
|
||||||
|
"There is a 20% chance of tomorrow.",
|
||||||
|
"There is a fly on your nose.",
|
||||||
|
"There was a phone call for you.",
|
||||||
|
"You're not my type. For that matter, you're not even my species!!!",
|
||||||
|
"You're ugly and your mother dresses you funny.",
|
||||||
|
"You're working under a slight handicap. You happen to be human.",
|
||||||
|
"You've been leading a dog's life. Stay off the furniture.",
|
||||||
|
"Your aim is high and to the right.",
|
||||||
|
"Your aims are high, and you are capable of much.",
|
||||||
|
"Your analyst has you mixed up with another patient. Don't believe a thing he tells you.",
|
||||||
|
"Your best consolation is the hope that the things you failed to get weren't really worth having.",
|
||||||
|
"Your boss climbed the corporate ladder, wrong by wrong.",
|
||||||
|
"Your boss is a few sandwiches short of a picnic.",
|
||||||
|
"Your boyfriend takes chocolate from strangers.",
|
||||||
|
"Your business will assume vast proportions.",
|
||||||
|
"Your business will go through a period of considerable expansion.",
|
||||||
|
"Your depth of comprehension may tend to make you lax in worldly ways.",
|
||||||
|
"Your domestic life may be harmonious.",
|
||||||
|
"Your fly might be open (but don't check it just now).",
|
||||||
|
"Your goose is cooked. (Your current chick is burned up too!)",
|
||||||
|
"Your heart is pure, and your mind clear, and your soul devout.",
|
||||||
|
"Your ignorance cramps my conversation.",
|
||||||
|
"Your life would be very empty if you had nothing to regret.",
|
||||||
|
"Your love life will be happy and harmonious.",
|
||||||
|
"Your love life will be... interesting.",
|
||||||
|
"Your lover will never wish to leave you.",
|
||||||
|
"Your lucky color has faded.",
|
||||||
|
"Your lucky number has been disconnected.",
|
||||||
|
"Your lucky number is 3552664958674928. Watch for it everywhere.",
|
||||||
|
"Your mode of life will be changed for the better because of good news soon.",
|
||||||
|
"Your mode of life will be changed for the better because of new developments.",
|
||||||
|
"Your motives for doing whatever good deed you may have in mind will be misinterpreted by somebody.",
|
||||||
|
"Your nature demands love and your happiness depends on it.",
|
||||||
|
"Your object is to save the world, while still leading a pleasant life.",
|
||||||
|
"Your own qualities will help prevent your advancement in the world.",
|
||||||
|
"Your present plans will be successful.",
|
||||||
|
"Your reasoning is excellent -- it's only your basic assumptions that are wrong.",
|
||||||
|
"Your reasoning powers are good, and you are a fairly good planner.",
|
||||||
|
"Your sister swims out to meet troop ships.",
|
||||||
|
"Your society will be sought by people of taste and refinement.",
|
||||||
|
"Your step will soil many countries.",
|
||||||
|
"Your supervisor is thinking about you.",
|
||||||
|
"Your talents will be recognized and suitably rewarded.",
|
||||||
|
"Your temporary financial embarrassment will be relieved in a surprising manner.",
|
||||||
|
"Your true value depends entirely on what you are compared with.",
|
||||||
|
];
|
||||||
|
|
||||||
|
export const fortune = new Command("fortune", () => {
|
||||||
|
return randomFromArray(FORTUNES);
|
||||||
|
});
|
||||||
5
src/features/applications/terminal/commands/hostname.js
Normal file
5
src/features/applications/terminal/commands/hostname.js
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
import Command from "../command.js";
|
||||||
|
|
||||||
|
export const hostname = new Command("hostname", (args, { hostname }) => {
|
||||||
|
return hostname;
|
||||||
|
});
|
||||||
29
src/features/applications/terminal/commands/ls.js
Normal file
29
src/features/applications/terminal/commands/ls.js
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
import Command from "../command.js";
|
||||||
|
|
||||||
|
export const ls = new Command("ls")
|
||||||
|
.setManual({
|
||||||
|
purpose: "list directory contents",
|
||||||
|
usage: "ls [OPTION]... [FILE]...",
|
||||||
|
description: "List information about the FILEs (the current directory by default).\n"
|
||||||
|
+ "Sort entries alphabetically if none of -cftuvSUX nor --sort is specified."
|
||||||
|
})
|
||||||
|
.setExecute((args, { currentDirectory }) => {
|
||||||
|
let directory = currentDirectory;
|
||||||
|
|
||||||
|
if (args.length > 0) {
|
||||||
|
directory = currentDirectory.navigate(args[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!directory)
|
||||||
|
return `ls: Cannot access '${args[0]}': No such file or directory`;
|
||||||
|
|
||||||
|
const folderNames = directory.subFolders.map((folder) => folder.id);
|
||||||
|
const fileNames = directory.files.map((file) => file.id);
|
||||||
|
|
||||||
|
const contents = folderNames.concat(fileNames);
|
||||||
|
|
||||||
|
if (contents.length === 0)
|
||||||
|
return { blank: true };
|
||||||
|
|
||||||
|
return contents.sort((nameA, nameB) => nameA.localeCompare(nameB)).join(" ");
|
||||||
|
});
|
||||||
6
src/features/applications/terminal/commands/make.js
Normal file
6
src/features/applications/terminal/commands/make.js
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
import Command from "../command.js";
|
||||||
|
|
||||||
|
export const make = new Command("make", (args) => {
|
||||||
|
if (args[0] === "love")
|
||||||
|
return "make: *** No rule to make target `love'. Stop.";
|
||||||
|
});
|
||||||
54
src/features/applications/terminal/commands/man.js
Normal file
54
src/features/applications/terminal/commands/man.js
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
import Command from "../command.js";
|
||||||
|
import CommandsManager from "../commands.js";
|
||||||
|
|
||||||
|
const MARGIN = 5;
|
||||||
|
|
||||||
|
export const man = new Command("man")
|
||||||
|
.setRequireArgs(true)
|
||||||
|
.setManual({
|
||||||
|
purpose: "show system reference manuals",
|
||||||
|
usage: "man [OPTION]... page",
|
||||||
|
description: "Each page arguments given to man is normally the name of a command.\n"
|
||||||
|
+ "The manual page associated with this command is then found and displayed."
|
||||||
|
})
|
||||||
|
.setExecute((args) => {
|
||||||
|
const commandName = args[0].toLowerCase();
|
||||||
|
const command = CommandsManager.find(commandName);
|
||||||
|
|
||||||
|
if (!command)
|
||||||
|
return `man: ${commandName}: Command not found`;
|
||||||
|
|
||||||
|
const manual = command.manual;
|
||||||
|
|
||||||
|
if (!manual)
|
||||||
|
return `man: ${commandName}: No manual found`;
|
||||||
|
|
||||||
|
const formatText = (text) => {
|
||||||
|
const lines = text.split("\n").map((line) => " ".repeat(MARGIN) + line);
|
||||||
|
return lines.join("\n");
|
||||||
|
};
|
||||||
|
|
||||||
|
const sections = [["NAME"]];
|
||||||
|
|
||||||
|
if (manual.purpose) {
|
||||||
|
sections[0].push(formatText(`${commandName} - ${command.manual.purpose}`));
|
||||||
|
} else {
|
||||||
|
sections[0].push(formatText(commandName));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (manual.usage) {
|
||||||
|
sections.push([
|
||||||
|
"SYNOPSIS",
|
||||||
|
formatText(manual.usage)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (manual.description) {
|
||||||
|
sections.push([
|
||||||
|
"DESCRIPTION",
|
||||||
|
formatText(manual.description)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sections.map((section) => section.join("\n")).join("\n\n");
|
||||||
|
});
|
||||||
11
src/features/applications/terminal/commands/mkdir.js
Normal file
11
src/features/applications/terminal/commands/mkdir.js
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
import Command from "../command.js";
|
||||||
|
|
||||||
|
export const mkdir = new Command("mkdir", (args, { currentDirectory }) => {
|
||||||
|
const name = args[0];
|
||||||
|
|
||||||
|
if (currentDirectory.findSubFolder(name))
|
||||||
|
return { blank: true };
|
||||||
|
|
||||||
|
currentDirectory.createFolder(name);
|
||||||
|
return { blank: true };
|
||||||
|
}).setRequireArgs(true);
|
||||||
56
src/features/applications/terminal/commands/neofetch.js
Normal file
56
src/features/applications/terminal/commands/neofetch.js
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
import { ASCII_LOGO, NAME } from "../../../../constants/branding.js";
|
||||||
|
import { START_DATE } from "../../../../index.js";
|
||||||
|
import { formatRelativeTime } from "../../../utils/date.js";
|
||||||
|
import ApplicationsManager from "../../applications.js";
|
||||||
|
import Command from "../command.js";
|
||||||
|
|
||||||
|
export const neofetch = new Command("neofetch", (args, { username, hostname }) => {
|
||||||
|
const leftColumn = ASCII_LOGO.split("\n");
|
||||||
|
const rightColumnWidth = username.length + hostname.length + 1;
|
||||||
|
|
||||||
|
const userAgent = navigator.userAgent;
|
||||||
|
|
||||||
|
// Check for the browser name using regular expressions
|
||||||
|
let browserName;
|
||||||
|
if (userAgent.match(/Firefox\//)) {
|
||||||
|
browserName = "Mozilla Firefox";
|
||||||
|
} else if (userAgent.match(/Edg\//)) {
|
||||||
|
browserName = "Microsoft Edge";
|
||||||
|
} else if (userAgent.match(/Chrome\//)) {
|
||||||
|
browserName = "Google Chrome";
|
||||||
|
} else if (userAgent.match(/Safari\//)) {
|
||||||
|
browserName = "Apple Safari";
|
||||||
|
} else {
|
||||||
|
browserName = "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
const rightColumn = [
|
||||||
|
`${username}@${hostname}`,
|
||||||
|
"-".repeat(rightColumnWidth),
|
||||||
|
`OS: ${NAME}`,
|
||||||
|
`UPTIME: ${formatRelativeTime(START_DATE, 2, false)}`,
|
||||||
|
`RESOLUTION: ${window.innerWidth}x${window.innerHeight}`,
|
||||||
|
"THEME: default",
|
||||||
|
"ICONS: Font Awesome",
|
||||||
|
`TERMINAL: ${ApplicationsManager.getApplication("terminal")?.name ?? "Unknown"}`,
|
||||||
|
`BROWSER: ${browserName}`,
|
||||||
|
`PLATFORM: ${navigator.platform}`,
|
||||||
|
`LANGUAGE: ${navigator.language}`,
|
||||||
|
];
|
||||||
|
|
||||||
|
const combined = [];
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
return combined.join("\n");
|
||||||
|
});
|
||||||
6
src/features/applications/terminal/commands/nice.js
Normal file
6
src/features/applications/terminal/commands/nice.js
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
import Command from "../command.js";
|
||||||
|
|
||||||
|
export const nice = new Command("nice", (args) => {
|
||||||
|
if (args[0] === "man" && args[1] === "woman")
|
||||||
|
return "nice: No manual entry for woman";
|
||||||
|
});
|
||||||
9
src/features/applications/terminal/commands/pwd.js
Normal file
9
src/features/applications/terminal/commands/pwd.js
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
import Command from "../command.js";
|
||||||
|
|
||||||
|
export const pwd = new Command("pwd", (args, { currentDirectory }) => {
|
||||||
|
if (currentDirectory.root) {
|
||||||
|
return "/";
|
||||||
|
} else {
|
||||||
|
return currentDirectory.absolutePath;
|
||||||
|
}
|
||||||
|
});
|
||||||
7
src/features/applications/terminal/commands/reboot.js
Normal file
7
src/features/applications/terminal/commands/reboot.js
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { reloadViewport } from "../../../utils/browser.js";
|
||||||
|
import Command from "../command.js";
|
||||||
|
|
||||||
|
export const reboot = new Command("reboot", () => {
|
||||||
|
reloadViewport();
|
||||||
|
return { blank: true };
|
||||||
|
});
|
||||||
12
src/features/applications/terminal/commands/rm.js
Normal file
12
src/features/applications/terminal/commands/rm.js
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import Command from "../command.js";
|
||||||
|
|
||||||
|
export const rm = new Command("rm", (args, { currentDirectory }) => {
|
||||||
|
const [name, extension] = args[0].split(".");
|
||||||
|
const file = currentDirectory.findFile(name, extension);
|
||||||
|
|
||||||
|
if (!file)
|
||||||
|
return `rm: ${args[0]}: No such file`;
|
||||||
|
|
||||||
|
file.delete();
|
||||||
|
return { blank: true };
|
||||||
|
}).setRequireArgs(true);
|
||||||
12
src/features/applications/terminal/commands/rmdir.js
Normal file
12
src/features/applications/terminal/commands/rmdir.js
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import Command from "../command.js";
|
||||||
|
|
||||||
|
export const rmdir = new Command("rmdir", (args, { currentDirectory }) => {
|
||||||
|
const name = args[0];
|
||||||
|
const folder = currentDirectory.findSubFolder(name);
|
||||||
|
|
||||||
|
if (!folder)
|
||||||
|
return `rmdir: ${args[0]}: No such directory`;
|
||||||
|
|
||||||
|
folder.delete();
|
||||||
|
return { blank: true };
|
||||||
|
}).setRequireArgs(true);
|
||||||
22
src/features/applications/terminal/commands/touch.js
Normal file
22
src/features/applications/terminal/commands/touch.js
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
import Command from "../command.js";
|
||||||
|
|
||||||
|
export const touch = new Command("touch")
|
||||||
|
.setRequireArgs(true)
|
||||||
|
.setManual({
|
||||||
|
purpose: "change file timestamps",
|
||||||
|
usage: "touch [OPTION]... FILE...",
|
||||||
|
description: "Update the access and modification times of each FILE to the current time.\n\n"
|
||||||
|
+ "A FILE argument that does not exist is created empty."
|
||||||
|
})
|
||||||
|
.setExecute((args, { currentDirectory }) => {
|
||||||
|
if (args[0] === "girls\\" && args[1] === "boo**")
|
||||||
|
return "touch: Cannot touch 'girls boo**': Permission denied";
|
||||||
|
|
||||||
|
const [name, extension] = args[0].split(".");
|
||||||
|
|
||||||
|
if (currentDirectory.findFile(name, extension))
|
||||||
|
return { blank: true };
|
||||||
|
|
||||||
|
currentDirectory.createFile(name, extension);
|
||||||
|
return { blank: true };
|
||||||
|
});
|
||||||
5
src/features/applications/terminal/commands/world.js
Normal file
5
src/features/applications/terminal/commands/world.js
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
import Command from "../command.js";
|
||||||
|
|
||||||
|
export const world = new Command("world", () => {
|
||||||
|
return "world: Not found";
|
||||||
|
});
|
||||||
|
|
@ -8,3 +8,11 @@ export function removeFromArray(item, array) {
|
||||||
array.splice(index, 1);
|
array.splice(index, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {*[]} array
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
export function randomFromArray(array) {
|
||||||
|
return array[Math.floor(Math.random() * array.length)];
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
|
import { SCREEN_MARGIN, TASKBAR_HEIGHT } from "../../constants/windows.js";
|
||||||
import ApplicationsManager from "../applications/applications.js";
|
import ApplicationsManager from "../applications/applications.js";
|
||||||
import { randomRange } from "../math/random.js";
|
import { randomRange } from "../math/random.js";
|
||||||
import Vector2 from "../math/vector2.js";
|
import Vector2 from "../math/vector2.js";
|
||||||
import { VirtualFile } from "../virtual-drive/virtualFile.js";
|
import { VirtualFile } from "../virtual-drive/virtualFile.js";
|
||||||
|
|
||||||
export const SCREEN_MARGIN = 32;
|
|
||||||
export const TASKBAR_HEIGHT = 48;
|
|
||||||
|
|
||||||
export default class WindowsManager {
|
export default class WindowsManager {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.windows = {};
|
this.windows = {};
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,11 @@ import { useCallback } from "react";
|
||||||
import Modal from "../../features/modals/modal.js";
|
import Modal from "../../features/modals/modal.js";
|
||||||
import { DialogBox } from "../../components/modals/dialog-box/DialogBox.jsx";
|
import { DialogBox } from "../../components/modals/dialog-box/DialogBox.jsx";
|
||||||
import Vector2 from "../../features/math/vector2.js";
|
import Vector2 from "../../features/math/vector2.js";
|
||||||
|
import { DEFAULT_DIALOG_SIZE } from "../../constants/modals.js";
|
||||||
const DEFAULT_SIZE = new Vector2(400, 200);
|
|
||||||
|
|
||||||
export function useDialogBox({ modalsManager }) {
|
export function useDialogBox({ modalsManager }) {
|
||||||
const onDialogBox = useCallback((event, params = {}) => {
|
const onDialogBox = useCallback((event, params = {}) => {
|
||||||
const size = params.size ?? DEFAULT_SIZE;
|
const size = params.size ?? DEFAULT_DIALOG_SIZE;
|
||||||
let positionX = (window.innerWidth - size.x) / 4;
|
let positionX = (window.innerWidth - size.x) / 4;
|
||||||
let positionY = (window.innerHeight - size.y) / 4;
|
let positionY = (window.innerHeight - size.y) / 4;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue