forked from seth/ipv4.army
move index to src, add .env add types, fix > to actual bash like input and history
This commit is contained in:
parent
a65ee6fe67
commit
17467258fc
11 changed files with 339 additions and 180 deletions
|
@ -1,27 +1,145 @@
|
|||
import Hyperate from "./components/Hyperate";
|
||||
import Lanyard from "./components/Lanyard";
|
||||
|
||||
let latestLanyard: LanyardData | null = null;
|
||||
|
||||
window.addEventListener("lanyard-update", (e) => {
|
||||
latestLanyard = (e as CustomEvent<LanyardData>).detail;
|
||||
});
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<div class="app">
|
||||
<p>seth> cat ./about.txt</p>
|
||||
<p>
|
||||
A Dedicated Backend Developer,
|
||||
<br />
|
||||
with a passion for high-fidelity audio,
|
||||
<br />
|
||||
gaming, and web development.
|
||||
</p>
|
||||
const container = document.createElement("div");
|
||||
container.className = "app terminal";
|
||||
|
||||
<p>seth> curl /tmp/discord-ipc</p>
|
||||
<p>
|
||||
<Lanyard />
|
||||
</p>
|
||||
const renderElement = (content: string | Node) => {
|
||||
const p = document.createElement("p");
|
||||
if (typeof content === "string") {
|
||||
p.textContent = content;
|
||||
} else {
|
||||
p.appendChild(content);
|
||||
}
|
||||
return p;
|
||||
};
|
||||
|
||||
<p>seth> cat /tmp/heartrate</p>
|
||||
<p>
|
||||
<Hyperate />
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
const prompt = "[seth@ipv4 ~]$";
|
||||
|
||||
const staticLines: (string | (() => Node))[] = [
|
||||
`${prompt} cat ./about.txt`,
|
||||
() =>
|
||||
document
|
||||
.createRange()
|
||||
.createContextualFragment(
|
||||
"A Dedicated Backend Developer,<br />with a passion for high-fidelity audio,<br />gaming, and web development.",
|
||||
),
|
||||
`${prompt} cat /tmp/discord-ipc`,
|
||||
() => Lanyard(),
|
||||
`${prompt} cat /tmp/heartrate`,
|
||||
() => Hyperate(),
|
||||
];
|
||||
|
||||
const renderStatic = () => {
|
||||
for (const line of staticLines) {
|
||||
const content = typeof line === "function" ? line() : line;
|
||||
container.appendChild(renderElement(content));
|
||||
}
|
||||
};
|
||||
|
||||
renderStatic();
|
||||
|
||||
const lanyardInstance = Lanyard();
|
||||
const files: Record<string, () => Node> = {
|
||||
"./about.txt": () =>
|
||||
document
|
||||
.createRange()
|
||||
.createContextualFragment(
|
||||
"A Dedicated Backend Developer,<br />with a passion for high-fidelity audio,<br />gaming, and web development.",
|
||||
),
|
||||
"/tmp/discord-ipc": () => lanyardInstance,
|
||||
"/tmp/heartrate": () => Hyperate(),
|
||||
};
|
||||
|
||||
const history: string[] = [];
|
||||
let historyIndex = -1;
|
||||
|
||||
const inputBox = document.createElement("input");
|
||||
inputBox.className = "terminal-input";
|
||||
inputBox.autofocus = true;
|
||||
|
||||
const inputLine = document.createElement("div");
|
||||
inputLine.className = "terminal-line";
|
||||
|
||||
const promptSpan = document.createElement("span");
|
||||
promptSpan.textContent = `${prompt} `;
|
||||
|
||||
inputLine.appendChild(promptSpan);
|
||||
inputLine.appendChild(inputBox);
|
||||
container.appendChild(inputLine);
|
||||
|
||||
const appendLine = (line: string | Node) => {
|
||||
container.insertBefore(renderElement(line), inputLine);
|
||||
};
|
||||
|
||||
inputBox.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Enter") {
|
||||
const cmd = inputBox.value.trim();
|
||||
if (!cmd) return;
|
||||
|
||||
history.push(cmd);
|
||||
historyIndex = history.length;
|
||||
|
||||
appendLine(`${prompt} ${cmd}`);
|
||||
|
||||
let out: string | Node;
|
||||
|
||||
if (cmd.startsWith("cat ")) {
|
||||
const file = cmd.slice(4).trim();
|
||||
out = files[file]?.() ?? `cat: ${file}: No such file`;
|
||||
} else if (cmd === "ls") {
|
||||
out = Object.keys(files)
|
||||
.filter((f) => f.startsWith("./"))
|
||||
.map((f) => f.slice(2))
|
||||
.join("\n");
|
||||
} else if (cmd.startsWith("ls ")) {
|
||||
const dir = cmd.slice(3).trim();
|
||||
if (dir === "/tmp") {
|
||||
out = Object.keys(files)
|
||||
.filter((f) => f.startsWith("/tmp/"))
|
||||
.map((f) => f.slice("/tmp/".length))
|
||||
.join("\n");
|
||||
} else {
|
||||
out = `ls: cannot access '${dir}': No such file or directory`;
|
||||
}
|
||||
} else if (cmd === "help") {
|
||||
out = [
|
||||
"Available commands:",
|
||||
" cat [file] View contents of a file",
|
||||
" ls List files in current directory",
|
||||
" ls /tmp List files in /tmp directory",
|
||||
" help Show this message",
|
||||
].join("\n");
|
||||
} else {
|
||||
out = `bash: ${cmd}: command not found`;
|
||||
}
|
||||
|
||||
appendLine(out);
|
||||
inputBox.value = "";
|
||||
} else if (e.key === "ArrowUp") {
|
||||
if (historyIndex > 0) {
|
||||
historyIndex--;
|
||||
inputBox.value = history[historyIndex] || "";
|
||||
}
|
||||
e.preventDefault();
|
||||
} else if (e.key === "ArrowDown") {
|
||||
if (historyIndex < history.length - 1) {
|
||||
historyIndex++;
|
||||
inputBox.value = history[historyIndex] || "";
|
||||
} else {
|
||||
historyIndex = history.length;
|
||||
inputBox.value = "";
|
||||
}
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
return container;
|
||||
};
|
||||
|
|
|
@ -39,8 +39,11 @@ class Socket extends EventTarget {
|
|||
}, 30 * 1000);
|
||||
}
|
||||
|
||||
emitLanyard(lanyard: object) {
|
||||
emitLanyard(lanyard: LanyardData) {
|
||||
this.dispatchEvent(new CustomEvent("lanyard", { detail: lanyard }));
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("lanyard-update", { detail: lanyard }),
|
||||
);
|
||||
}
|
||||
emitHyperate(heartRate: number) {
|
||||
this.dispatchEvent(new CustomEvent("hyperate", { detail: heartRate }));
|
||||
|
|
|
@ -1,22 +1,22 @@
|
|||
import { highlightAll } from "@speed-highlight/core";
|
||||
import { highlightElement } from "@speed-highlight/core";
|
||||
import { createRef } from "tsx-dom";
|
||||
|
||||
import socket from "../../Socket";
|
||||
|
||||
const statusTypes: { [key: string]: string } = {
|
||||
const statusTypes = {
|
||||
online: "rgb(0, 150, 0)",
|
||||
idle: "rgb(150, 150, 0)",
|
||||
dnd: "rgb(150, 0, 0)",
|
||||
offline: "rgb(150, 150, 150)",
|
||||
};
|
||||
|
||||
const gradientTypes: { [key: string]: string } = {
|
||||
const gradientTypes = {
|
||||
online: "rgba(0, 150, 0, 0.1)",
|
||||
idle: "rgba(150, 150, 0, 0.1)",
|
||||
dnd: "rgba(150, 0, 0, 0.1)",
|
||||
offline: "rgba(150, 150, 150, 0.1)",
|
||||
};
|
||||
const activityTypes: { [key: number]: string } = {
|
||||
|
||||
const activityTypes: Record<number, string> = {
|
||||
0: "Playing",
|
||||
1: "Streaming",
|
||||
2: "Listening to",
|
||||
|
@ -25,43 +25,37 @@ const activityTypes: { [key: number]: string } = {
|
|||
5: "Competing in",
|
||||
};
|
||||
|
||||
const stringify = (data: { [key: string]: string }) => {
|
||||
return JSON.stringify(data, null, 2);
|
||||
};
|
||||
|
||||
export default () => {
|
||||
const code = createRef<HTMLDivElement>();
|
||||
const container = createRef<HTMLDivElement>();
|
||||
|
||||
socket.addEventListener("lanyard", (event: Event) => {
|
||||
const lanyard = (event as CustomEvent).detail;
|
||||
const lanyard = (event as CustomEvent<LanyardData>).detail;
|
||||
|
||||
document.body.style = `--status-color: ${statusTypes[lanyard.discord_status]}; --gradient-color: ${gradientTypes[lanyard.discord_status]};`;
|
||||
if (code.current) {
|
||||
code.current.innerHTML = stringify({
|
||||
status: lanyard.discord_status,
|
||||
activities: lanyard.activities.map(
|
||||
(act: {
|
||||
type: number;
|
||||
name: string;
|
||||
details: string;
|
||||
state: string;
|
||||
}) => {
|
||||
return [
|
||||
...new Set([
|
||||
activityTypes[act.type],
|
||||
act.name,
|
||||
act.details,
|
||||
act.state,
|
||||
]),
|
||||
].filter((n) => n);
|
||||
},
|
||||
),
|
||||
});
|
||||
|
||||
if (container.current) {
|
||||
container.current.className = "shj-lang-json";
|
||||
container.current.textContent = JSON.stringify(
|
||||
{
|
||||
status: lanyard.discord_status,
|
||||
activities: lanyard.activities.map((act) => {
|
||||
const type = activityTypes[act.type];
|
||||
const parts = [type];
|
||||
if (act.name !== type) parts.push(act.name);
|
||||
if (act.details) parts.push(act.details);
|
||||
if (act.state) parts.push(act.state);
|
||||
return parts;
|
||||
}),
|
||||
},
|
||||
null,
|
||||
2
|
||||
);
|
||||
highlightElement(container.current);
|
||||
}
|
||||
highlightAll();
|
||||
});
|
||||
|
||||
return (
|
||||
<div class="shj-lang-json" ref={code}>
|
||||
<div class="shj-lang-json" ref={container}>
|
||||
{"{}"}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
@import "../../node_modules/@speed-highlight/core/dist/themes/dark.css";
|
||||
|
||||
@import "./App.css";
|
||||
|
||||
html,
|
||||
|
@ -21,4 +20,47 @@ body {
|
|||
rgba(0, 0, 0, 1) 100%
|
||||
);
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
line-height: 1.4em;
|
||||
}
|
||||
|
||||
.terminal {
|
||||
white-space: pre-wrap;
|
||||
font-family: monospace;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
|
||||
gap: 0.4em;
|
||||
}
|
||||
|
||||
.terminal-input {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
outline: none;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.terminal-line {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.terminal-line > span {
|
||||
white-space: pre;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue