Added ansi support for terminal app
This commit is contained in:
parent
efced37fc6
commit
ceb115c0c7
14 changed files with 331 additions and 30 deletions
12
package-lock.json
generated
12
package-lock.json
generated
|
|
@ -15,7 +15,9 @@
|
|||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"anser": "^2.1.1",
|
||||
"core-js": "^3.31.1",
|
||||
"escape-carriage": "^1.3.1",
|
||||
"markdown-to-jsx": "^7.2.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
|
|
@ -5108,6 +5110,11 @@
|
|||
"ajv": "^6.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/anser": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/anser/-/anser-2.1.1.tgz",
|
||||
"integrity": "sha512-nqLm4HxOTpeLOxcmB3QWmV5TcDFhW9y/fyQ+hivtDFcK4OQ+pQ5fzPnXHM1Mfcm0VkLtvVi1TCPr++Qy0Q/3EQ=="
|
||||
},
|
||||
"node_modules/ansi-escapes": {
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
|
||||
|
|
@ -7328,6 +7335,11 @@
|
|||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/escape-carriage": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/escape-carriage/-/escape-carriage-1.3.1.tgz",
|
||||
"integrity": "sha512-GwBr6yViW3ttx1kb7/Oh+gKQ1/TrhYwxKqVmg5gS+BK+Qe2KrOa/Vh7w3HPBvgGf0LfcDGoY9I6NHKoA5Hozhw=="
|
||||
},
|
||||
"node_modules/escape-html": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||
|
|
|
|||
14
package.json
14
package.json
|
|
@ -21,7 +21,9 @@
|
|||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"anser": "^2.1.1",
|
||||
"core-js": "^3.31.1",
|
||||
"escape-carriage": "^1.3.1",
|
||||
"markdown-to-jsx": "^7.2.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
|
|
@ -60,11 +62,19 @@
|
|||
"error",
|
||||
"double"
|
||||
],
|
||||
"object-curly-spacing": ["warn", "always"],
|
||||
"object-curly-spacing": [
|
||||
"warn",
|
||||
"always"
|
||||
],
|
||||
"default-case": "off",
|
||||
"arrow-parens": "error",
|
||||
"space-infix-ops": "warn",
|
||||
"react/no-multi-comp": ["error", { "ignoreStateless": true }],
|
||||
"react/no-multi-comp": [
|
||||
"error",
|
||||
{
|
||||
"ignoreStateless": true
|
||||
}
|
||||
],
|
||||
"jsdoc/no-undefined-types": "warn",
|
||||
"jsdoc/require-param": "warn",
|
||||
"jsdoc/check-tag-names": "warn",
|
||||
|
|
|
|||
|
|
@ -80,6 +80,8 @@
|
|||
|
||||
.Context-menu .Icon {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 0.875rem;
|
||||
height: 0.875rem;
|
||||
}
|
||||
|
|
@ -90,6 +92,25 @@
|
|||
object-fit: contain;
|
||||
}
|
||||
|
||||
.Context-menu .Image-icon {
|
||||
position: absolute;
|
||||
width: 1rem;
|
||||
height: auto;
|
||||
aspect-ratio: 1;
|
||||
}
|
||||
|
||||
.Context-menu .Image-icon div {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.Context-menu .Image-icon div > svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.Context-menu .Shortcut {
|
||||
color: var(--foreground-color-b);
|
||||
}
|
||||
|
|
|
|||
184
src/components/applications/terminal/Ansi.jsx
Normal file
184
src/components/applications/terminal/Ansi.jsx
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
import Anser, { AnserJsonEntry } from "anser";
|
||||
import { escapeCarriageReturn } from "escape-carriage";
|
||||
import * as React from "react";
|
||||
import styles from "./Terminal.module.css";
|
||||
|
||||
/**
|
||||
* This was copied from
|
||||
* https://github.com/nteract/ansi-to-react/blob/master/src/index.ts
|
||||
*/
|
||||
|
||||
/**
|
||||
* Converts ANSI strings into JSON output.
|
||||
* @name ansiToJSON
|
||||
* @function
|
||||
* @param {string} input - The input string.
|
||||
* @param {boolean=} use_classes - If `true`, HTML classes will be appended
|
||||
* to the HTML output.
|
||||
* @returns {AnserJsonEntry[]} The parsed input.
|
||||
*/
|
||||
function ansiToJSON(input, use_classes) {
|
||||
input = escapeCarriageReturn(fixBackspace(input));
|
||||
return Anser.ansiToJson(input, {
|
||||
json: true,
|
||||
remove_empty: true,
|
||||
use_classes,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a class string.
|
||||
* @param {AnserJsonEntry} bundle
|
||||
* @returns {string} class name(s)
|
||||
*/
|
||||
function createClass(bundle) {
|
||||
const classNames = [];
|
||||
|
||||
if (bundle.bg) {
|
||||
classNames.push(styles[`${bundle.bg}-bg`]);
|
||||
}
|
||||
if (bundle.fg) {
|
||||
classNames.push(styles[`${bundle.fg}-fg`]);
|
||||
}
|
||||
if (bundle.decoration) {
|
||||
classNames.push(styles[`ansi-${bundle.decoration}`]);
|
||||
}
|
||||
|
||||
if (classNames.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return classNames.join(" ");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the style attribute.
|
||||
* @param {AnserJsonEntry} bundle
|
||||
* @returns {object} returns the style object
|
||||
*/
|
||||
function createStyle(bundle) {
|
||||
const style = {};
|
||||
if (bundle.bg) {
|
||||
style.backgroundColor = `rgb(${bundle.bg})`;
|
||||
}
|
||||
if (bundle.fg) {
|
||||
style.color = `rgb(${bundle.fg})`;
|
||||
}
|
||||
switch (bundle.decoration) {
|
||||
case "bold":
|
||||
style.fontWeight = "bold";
|
||||
break;
|
||||
case "dim":
|
||||
style.opacity = "0.5";
|
||||
break;
|
||||
case "italic":
|
||||
style.fontStyle = "italic";
|
||||
break;
|
||||
case "hidden":
|
||||
style.visibility = "hidden";
|
||||
break;
|
||||
case "strikethrough":
|
||||
style.textDecoration = "line-through";
|
||||
break;
|
||||
case "underline":
|
||||
style.textDecoration = "underline";
|
||||
break;
|
||||
case "blink":
|
||||
style.textDecoration = "blink";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return style;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an Anser bundle into a React Node.
|
||||
* @param {boolean} linkify - whether links should be converting into clickable anchor tags.
|
||||
* @param {boolean} useClasses - should render the span with a class instead of style.
|
||||
* @param {AnserJsonEntry} bundle - Anser output.
|
||||
* @param {number} key
|
||||
*/
|
||||
function convertBundleIntoReact(linkify, useClasses, bundle, key) {
|
||||
const style = useClasses ? null : createStyle(bundle);
|
||||
const className = useClasses ? createClass(bundle) : null;
|
||||
|
||||
if (!linkify) {
|
||||
return React.createElement(
|
||||
"pre",
|
||||
{ style, key, className },
|
||||
bundle.content
|
||||
);
|
||||
}
|
||||
|
||||
const content = [];
|
||||
const linkRegex = /(\s|^)(https?:\/\/(?:www\.|(?!www))[^\s.]+\.[^\s]{2,}|www\.[^\s]+\.[^\s]{2,})/g;
|
||||
|
||||
let index = 0;
|
||||
let match;
|
||||
while ((match = linkRegex.exec(bundle.content)) !== null) {
|
||||
const [, pre, url] = match;
|
||||
|
||||
const startIndex = match.index + pre.length;
|
||||
if (startIndex > index) {
|
||||
content.push(bundle.content.substring(index, startIndex));
|
||||
}
|
||||
|
||||
// Make sure the href we generate from the link is fully qualified. We assume http
|
||||
// if it starts with a www because many sites don't support https
|
||||
const href = url.startsWith("www.") ? `http://${url}` : url;
|
||||
content.push(
|
||||
React.createElement(
|
||||
"a",
|
||||
{
|
||||
key: index,
|
||||
href,
|
||||
target: "_blank",
|
||||
},
|
||||
`${url}`
|
||||
)
|
||||
);
|
||||
|
||||
index = linkRegex.lastIndex;
|
||||
}
|
||||
|
||||
if (index < bundle.content.length) {
|
||||
content.push(bundle.content.substring(index));
|
||||
}
|
||||
|
||||
return React.createElement("span", { style, key, className }, content);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} props
|
||||
* @param {string=} props.children
|
||||
* @param {boolean=} props.linkify
|
||||
* @param {string=} props.className
|
||||
* @param {boolean=} props.useClasses
|
||||
*/
|
||||
export default function Ansi(props) {
|
||||
const { className, useClasses, children, linkify } = props;
|
||||
return React.createElement(
|
||||
"code",
|
||||
{ className },
|
||||
ansiToJSON(children ?? "", useClasses ?? false).map(
|
||||
convertBundleIntoReact.bind(null, linkify ?? false, useClasses ?? false)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// This is copied from the Jupyter Classic source code
|
||||
// notebook/static/base/js/utils.js to handle \b in a way
|
||||
// that is **compatible with Jupyter classic**. One can
|
||||
// argue that this behavior is questionable:
|
||||
// https://stackoverflow.com/questions/55440152/multiple-b-doesnt-work-as-expected-in-jupyter#
|
||||
function fixBackspace(txt) {
|
||||
let tmp = txt;
|
||||
do {
|
||||
txt = tmp;
|
||||
// Cancel out anything-but-newline followed by backspace
|
||||
// eslint-disable-next-line no-control-regex
|
||||
tmp = txt.replace(/[^\n]\x08/gm, "");
|
||||
} while (tmp.length < txt.length);
|
||||
return txt;
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import { useState } from "react";
|
||||
import styles from "./Terminal.module.css";
|
||||
import Ansi from "./Ansi.jsx";
|
||||
|
||||
/**
|
||||
* @param {object} props
|
||||
|
|
@ -19,7 +20,7 @@ export function InputLine({ value, prefix, onChange, onKeyUp, onKeyDown, inputRe
|
|||
|
||||
return (
|
||||
<span className={styles.Input}>
|
||||
{prefix && <p className={[styles.Prefix]}>{prefix}</p>}
|
||||
{prefix && <Ansi className={[styles.Prefix]} useClasses>{prefix}</Ansi>}
|
||||
<span className={styles["Input-container"]} style={{ "--cursor-offset": cursorPosition }}>
|
||||
<span aria-hidden="true">{value}</span>
|
||||
<input
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import Ansi from "./Ansi.jsx";
|
||||
import styles from "./Terminal.module.css";
|
||||
|
||||
/**
|
||||
|
|
@ -9,7 +10,7 @@ export function OutputLine({ text }) {
|
|||
|
||||
return (<>
|
||||
{lines.map((line, index) =>
|
||||
<pre key={index} className={styles.Output}>{line === "" ? " " : line}</pre>
|
||||
<Ansi key={index} className={styles.Output} useClasses>{line === "" ? " " : line}</Ansi>
|
||||
)}
|
||||
</>);
|
||||
}
|
||||
|
|
@ -4,19 +4,19 @@ import { useVirtualRoot } from "../../../hooks/virtual-drive/virtualRootContext.
|
|||
import { clamp } from "../../../features/math/clamp.js";
|
||||
import { OutputLine } from "./OutputLine.jsx";
|
||||
import { InputLine } from "./InputLine.jsx";
|
||||
import { HOSTNAME, USERNAME } from "../../../constants/applications/terminal.js";
|
||||
import { ANSI, HOSTNAME, USERNAME } from "../../../constants/applications/terminal.js";
|
||||
import CommandsManager from "../../../features/applications/terminal/commands.js";
|
||||
import { removeFromArray } from "../../../features/utils/array.js";
|
||||
|
||||
/**
|
||||
* @param {import("../../windows/WindowView.jsx").windowProps} props
|
||||
*/
|
||||
export function Terminal({ setTitle, close: exit }) {
|
||||
export function Terminal({ startPath, setTitle, close: exit }) {
|
||||
const [inputKey, setInputKey] = useState(0);
|
||||
const [inputValue, setInputValue] = useState("");
|
||||
const [history, setHistory] = useState([]);
|
||||
const virtualRoot = useVirtualRoot();
|
||||
const [currentDirectory, setCurrentDirectory] = useState(virtualRoot.navigate("~"));
|
||||
const [currentDirectory, setCurrentDirectory] = useState(virtualRoot.navigate(startPath ?? "~"));
|
||||
const inputRef = useRef(null);
|
||||
const [historyIndex, setHistoryIndex] = useState(0);
|
||||
|
||||
|
|
@ -24,7 +24,8 @@ export function Terminal({ setTitle, close: exit }) {
|
|||
setTitle(`${USERNAME}@${HOSTNAME}: ${currentDirectory.root ? "/" : currentDirectory.path}`);
|
||||
}, [currentDirectory.path, currentDirectory.root, setTitle]);
|
||||
|
||||
const prefix = `${USERNAME}@${HOSTNAME}:${currentDirectory.root ? "/" : currentDirectory.path}$ `;
|
||||
const prefix = `${ANSI.fg.cyan + USERNAME}@${HOSTNAME + ANSI.reset}:`
|
||||
+ `${ANSI.fg.blue + (currentDirectory.root ? "/" : currentDirectory.path) + ANSI.reset}$ `;
|
||||
|
||||
const updatedHistory = history;
|
||||
const pushHistory = (entry) => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
.Terminal {
|
||||
--char-width: 0.626rem;
|
||||
--char-width: 0.585rem;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
.Terminal * {
|
||||
font-family: var(--mono-font-family);
|
||||
letter-spacing: 0.01rem;
|
||||
letter-spacing: -0.03em;
|
||||
}
|
||||
|
||||
.Terminal p, .Terminal pre {
|
||||
|
|
@ -21,6 +21,7 @@
|
|||
}
|
||||
|
||||
.Prefix {
|
||||
display: flex;
|
||||
width: max-content;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
|
@ -47,7 +48,7 @@
|
|||
position: relative;
|
||||
height: 100%;
|
||||
width: fit-content;
|
||||
margin-left: var(--char-width);
|
||||
/* margin-left: var(--char-width); */
|
||||
}
|
||||
|
||||
.Input-container::after {
|
||||
|
|
@ -90,4 +91,31 @@
|
|||
top: 0;
|
||||
left: 0;
|
||||
cursor: text;
|
||||
}
|
||||
}
|
||||
|
||||
.ansi-black-fg { color: var(--dark-grey-e); }
|
||||
.ansi-red-fg { color: var(--red-b); }
|
||||
.ansi-green-fg { color: var(--green-b); }
|
||||
.ansi-yellow-fg { color: var(--yellow-b); }
|
||||
.ansi-blue-fg { color: var(--blue-b); }
|
||||
.ansi-magenta-fg { color: var(--purple-b); }
|
||||
.ansi-cyan-fg { color: var(--cyan-b); }
|
||||
.ansi-white-fg { color: var(--grey-a); }
|
||||
|
||||
.ansi-bright-black-fg { color: var(--dark-grey-d); }
|
||||
.ansi-bright-red-fg { color: var(--red-a); }
|
||||
.ansi-bright-green-fg { color: var(--green-a); }
|
||||
.ansi-bright-yellow-fg { color: var(--yellow-a); }
|
||||
.ansi-bright-blue-fg { color: var(--blue-a); }
|
||||
.ansi-bright-magenta-fg { color: var(--purple-a); }
|
||||
.ansi-bright-cyan-fg { color: var(--cyan-a); }
|
||||
.ansi-bright-white-fg { color: var(--white-a); }
|
||||
|
||||
.ansi-black-bg { background-color: var(--dark-grey-d); }
|
||||
.ansi-red-bg { background-color: var(--red-a); }
|
||||
.ansi-green-bg { background-color: var(--green-a); }
|
||||
.ansi-yellow-bg { background-color: var(--yellow-a); }
|
||||
.ansi-blue-bg { background-color: var(--blue-a); }
|
||||
.ansi-magenta-bg { background-color: var(--purple-a); }
|
||||
.ansi-cyan-bg { background-color: var(--cyan-a); }
|
||||
.ansi-white-bg { background-color: var(--white-a); }
|
||||
|
|
|
|||
|
|
@ -1,3 +1,20 @@
|
|||
export const USERNAME = "user";
|
||||
export const HOSTNAME = "prozilla-os";
|
||||
export const MAX_WIDTH = 50;
|
||||
export const MAX_WIDTH = 50;
|
||||
|
||||
export const ANSI = {
|
||||
fg: {
|
||||
black: "\u001b[30m",
|
||||
red: "\u001b[31m",
|
||||
green: "\u001b[32m",
|
||||
yellow: "\u001b[33m",
|
||||
blue: "\u001b[34m",
|
||||
magenta: "\u001b[35m",
|
||||
cyan: "\u001b[36m",
|
||||
white: "\u001b[37m",
|
||||
},
|
||||
bg: {
|
||||
|
||||
},
|
||||
reset: "\u001b[0m",
|
||||
};
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
import { ANSI } from "./applications/terminal.js";
|
||||
|
||||
export const NAME = "ProzillaOS";
|
||||
|
||||
export const ASCII_LOGO = `
|
||||
|
|
@ -13,6 +15,24 @@ export const ASCII_LOGO = `
|
|||
.=----#%+-+%#-*+-%#+---:.
|
||||
==----*###*--*###*----.
|
||||
==+-------------------:.
|
||||
...::---------------:.
|
||||
.::---------::..
|
||||
....::... `;
|
||||
|
||||
export const ANSI_LOGO_COLOR = ANSI.fg.green;
|
||||
export const ANSI_ASCII_LOGO = `
|
||||
:.
|
||||
-==.
|
||||
.=====:
|
||||
---::..:=======-.
|
||||
:===+=----------::..
|
||||
=+=---------------:..
|
||||
--------------------:.
|
||||
.:-+=----${ANSI.fg.white}*###*${ANSI_LOGO_COLOR}--${ANSI.fg.white}*####=${ANSI_LOGO_COLOR}---.
|
||||
:==+----${ANSI.fg.white}#%+${ANSI_LOGO_COLOR}-${ANSI.fg.white}+%#${ANSI_LOGO_COLOR}-${ANSI.fg.white}##%*+${ANSI_LOGO_COLOR}----:.
|
||||
.=----${ANSI.fg.white}#%+${ANSI_LOGO_COLOR}-${ANSI.fg.white}+%#${ANSI_LOGO_COLOR}-${ANSI.fg.white}*+${ANSI_LOGO_COLOR}-${ANSI.fg.white}%#+${ANSI_LOGO_COLOR}---:.
|
||||
==----${ANSI.fg.white}*###*${ANSI_LOGO_COLOR}--${ANSI.fg.white}*###*${ANSI_LOGO_COLOR}----.
|
||||
==+-------------------:.
|
||||
...::---------------:.
|
||||
.::---------::..
|
||||
....::... `;
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
import Command from "../command.js";
|
||||
|
||||
export const cd = new Command("cd")
|
||||
.setRequireArgs(true)
|
||||
.setManual({
|
||||
purpose: "Change the current directory",
|
||||
usage: "cd path",
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { ANSI } from "../../../../constants/applications/terminal.js";
|
||||
import Command from "../command.js";
|
||||
|
||||
export const ls = new Command("ls")
|
||||
|
|
@ -16,7 +17,7 @@ export const ls = new Command("ls")
|
|||
if (!directory)
|
||||
return `${this.name}: Cannot access '${args[0]}': No such file or directory`;
|
||||
|
||||
const folderNames = directory.subFolders.map((folder) => folder.id);
|
||||
const folderNames = directory.subFolders.map((folder) => `${ANSI.fg.blue}${folder.id}${ANSI.reset}`);
|
||||
const fileNames = directory.files.map((file) => file.id);
|
||||
|
||||
const contents = folderNames.concat(fileNames);
|
||||
|
|
@ -24,5 +25,5 @@ export const ls = new Command("ls")
|
|||
if (contents.length === 0)
|
||||
return { blank: true };
|
||||
|
||||
return contents.sort((nameA, nameB) => nameA.localeCompare(nameB)).join(" ");
|
||||
return contents.sort().join(" ");
|
||||
});
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import { APPS } from "../../../../constants/applications.js";
|
||||
import { ASCII_LOGO, NAME } from "../../../../constants/branding.js";
|
||||
import { ANSI } from "../../../../constants/applications/terminal.js";
|
||||
import { ANSI_ASCII_LOGO, ANSI_LOGO_COLOR, NAME } from "../../../../constants/branding.js";
|
||||
import { START_DATE } from "../../../../index.js";
|
||||
import { formatRelativeTime } from "../../../utils/date.js";
|
||||
import AppsManager from "../../applications.js";
|
||||
|
|
@ -10,7 +11,7 @@ export const neofetch = new Command("neofetch")
|
|||
purpose: "Fetch system information"
|
||||
})
|
||||
.setExecute((args, { username, hostname }) => {
|
||||
const leftColumn = ASCII_LOGO.split("\n");
|
||||
const leftColumn = ANSI_ASCII_LOGO.split("\n");
|
||||
const rightColumnWidth = username.length + hostname.length + 1;
|
||||
|
||||
const userAgent = navigator.userAgent;
|
||||
|
|
@ -29,23 +30,27 @@ export const neofetch = new Command("neofetch")
|
|||
browserName = "Unknown";
|
||||
}
|
||||
|
||||
const formatLine = (label, text) => ANSI.fg.green + label.toUpperCase() + ANSI.reset + ": " + text;
|
||||
|
||||
const rightColumn = [
|
||||
`${username}@${hostname}`,
|
||||
`${ANSI.fg.green + username + ANSI.reset}@${ANSI.fg.green + hostname + ANSI.reset}`,
|
||||
"-".repeat(rightColumnWidth),
|
||||
`OS: ${NAME}`,
|
||||
`UPTIME: ${formatRelativeTime(START_DATE, 2, false)}`,
|
||||
`RESOLUTION: ${window.innerWidth}x${window.innerHeight}`,
|
||||
"THEME: default",
|
||||
"ICONS: Font Awesome",
|
||||
`TERMINAL: ${AppsManager.getApp(APPS.TERMINAL)?.name ?? "Unknown"}`,
|
||||
`BROWSER: ${browserName}`,
|
||||
`PLATFORM: ${navigator.platform}`,
|
||||
`LANGUAGE: ${navigator.language}`,
|
||||
formatLine("os", NAME),
|
||||
formatLine("uptime", formatRelativeTime(START_DATE, 2, false)),
|
||||
formatLine("resolution", window.innerWidth + "x" + window.innerHeight),
|
||||
formatLine("theme", "default"),
|
||||
formatLine("icons", "Font Awesome"),
|
||||
formatLine("terminal", AppsManager.getApp(APPS.TERMINAL)?.name ?? "Unknown"),
|
||||
formatLine("browser", browserName),
|
||||
formatLine("platform", navigator.platform),
|
||||
formatLine("language", navigator.language),
|
||||
"",
|
||||
Object.values(ANSI.fg).map((colorCode) => colorCode + "███").join("") + ANSI.reset,
|
||||
];
|
||||
|
||||
const combined = [];
|
||||
for (let i = 1; i < leftColumn.length; i++) {
|
||||
let line = `${leftColumn[i]} `;
|
||||
let line = `${ANSI_LOGO_COLOR + leftColumn[i] + ANSI.reset} `;
|
||||
|
||||
if (i <= rightColumn.length) {
|
||||
line += rightColumn[i - 1];
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
:root {
|
||||
--white-a: #fff;
|
||||
--pink-a: #ff9ff3;
|
||||
--pink-b: #f368e0;
|
||||
--yellow-a: #feca57;
|
||||
|
|
@ -24,7 +25,7 @@
|
|||
--dark-grey-d: hsl(212, 14%, 10%);
|
||||
--dark-grey-e: hsl(212, 12%, 8%);
|
||||
|
||||
--foreground-color-a: #fff;
|
||||
--foreground-color-a: var(--white-a);
|
||||
--foreground-color-b: var(--grey-a);
|
||||
--foreground-color-c: var(--grey-b);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue