btop and reactive theme in neofetch
This commit is contained in:
247
src/components/Btop.tsx
Normal file
247
src/components/Btop.tsx
Normal file
@@ -0,0 +1,247 @@
|
|||||||
|
import { createSignal, onMount, onCleanup, Show } from "solid-js";
|
||||||
|
|
||||||
|
interface BtopProps {
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Btop(props: BtopProps) {
|
||||||
|
const [cpuUsage, setCpuUsage] = createSignal(64);
|
||||||
|
const [memUsage, setMemUsage] = createSignal(80);
|
||||||
|
const [netDown, setNetDown] = createSignal(404);
|
||||||
|
const [netUp, setNetUp] = createSignal(0);
|
||||||
|
const [processes, setProcesses] = createSignal([
|
||||||
|
{
|
||||||
|
pid: 404,
|
||||||
|
name: "error-handler",
|
||||||
|
cpu: 32.0,
|
||||||
|
mem: 15.2,
|
||||||
|
status: "Running"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pid: 128,
|
||||||
|
name: "glitch-generator",
|
||||||
|
cpu: 18.4,
|
||||||
|
mem: 8.7,
|
||||||
|
status: "Running"
|
||||||
|
},
|
||||||
|
{ pid: 256, name: "terminal-shell", cpu: 8.2, mem: 4.1, status: "Running" },
|
||||||
|
{
|
||||||
|
pid: 512,
|
||||||
|
name: "random-process",
|
||||||
|
cpu: 5.4,
|
||||||
|
mem: 2.3,
|
||||||
|
status: "Sleeping"
|
||||||
|
},
|
||||||
|
{ pid: 1024, name: "mystery-daemon", cpu: 0.0, mem: 0.0, status: "???" }
|
||||||
|
]);
|
||||||
|
const [isMobile, setIsMobile] = createSignal(false);
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
// Check if mobile
|
||||||
|
if (typeof window !== "undefined") {
|
||||||
|
setIsMobile(window.innerWidth < 768);
|
||||||
|
|
||||||
|
const handleResize = () => {
|
||||||
|
setIsMobile(window.innerWidth < 768);
|
||||||
|
};
|
||||||
|
window.addEventListener("resize", handleResize);
|
||||||
|
onCleanup(() => window.removeEventListener("resize", handleResize));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Animate CPU usage
|
||||||
|
const cpuInterval = setInterval(() => {
|
||||||
|
setCpuUsage((prev) => {
|
||||||
|
const change = (Math.random() - 0.5) * 10;
|
||||||
|
const newVal = Math.max(30, Math.min(95, prev + change));
|
||||||
|
return Math.round(newVal);
|
||||||
|
});
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
// Animate memory usage
|
||||||
|
const memInterval = setInterval(() => {
|
||||||
|
setMemUsage((prev) => {
|
||||||
|
const change = (Math.random() - 0.5) * 5;
|
||||||
|
const newVal = Math.max(60, Math.min(90, prev + change));
|
||||||
|
return Math.round(newVal);
|
||||||
|
});
|
||||||
|
}, 1500);
|
||||||
|
|
||||||
|
// Animate network
|
||||||
|
const netInterval = setInterval(() => {
|
||||||
|
setNetDown(Math.floor(Math.random() * 1000));
|
||||||
|
setNetUp(Math.floor(Math.random() * 100));
|
||||||
|
}, 800);
|
||||||
|
|
||||||
|
// Animate processes
|
||||||
|
const procInterval = setInterval(() => {
|
||||||
|
setProcesses((prev) =>
|
||||||
|
prev.map((proc) => ({
|
||||||
|
...proc,
|
||||||
|
cpu:
|
||||||
|
proc.name === "mystery-daemon"
|
||||||
|
? 0.0
|
||||||
|
: Math.max(0, proc.cpu + (Math.random() - 0.5) * 5),
|
||||||
|
mem:
|
||||||
|
proc.name === "mystery-daemon"
|
||||||
|
? 0.0
|
||||||
|
: Math.max(0, proc.mem + (Math.random() - 0.5) * 2)
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}, 2000);
|
||||||
|
|
||||||
|
// Keyboard handler for :q
|
||||||
|
const handleKeyPress = (e: KeyboardEvent) => {
|
||||||
|
if (!isMobile() && e.key === "q" && e.shiftKey && e.key === ":") {
|
||||||
|
props.onClose();
|
||||||
|
}
|
||||||
|
// Simple 'q' press to quit
|
||||||
|
if (!isMobile() && e.key === "q") {
|
||||||
|
props.onClose();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("keydown", handleKeyPress);
|
||||||
|
|
||||||
|
onCleanup(() => {
|
||||||
|
clearInterval(cpuInterval);
|
||||||
|
clearInterval(memInterval);
|
||||||
|
clearInterval(netInterval);
|
||||||
|
clearInterval(procInterval);
|
||||||
|
window.removeEventListener("keydown", handleKeyPress);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const createBar = (percentage: number, width: number = 30) => {
|
||||||
|
const filled = Math.round((percentage / 100) * width);
|
||||||
|
const empty = width - filled;
|
||||||
|
return "█".repeat(filled) + "░".repeat(empty);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class="bg-crust fixed inset-0 z-[10000] flex items-center justify-center p-4">
|
||||||
|
{/* Main btop container */}
|
||||||
|
<div class="bg-mantle border-surface0 text-text relative h-full w-full max-w-6xl overflow-hidden rounded-lg border-2 font-mono text-sm shadow-2xl md:h-auto md:max-h-[90vh]">
|
||||||
|
{/* Header */}
|
||||||
|
<div class="border-surface0 bg-surface0 border-b px-4 py-2">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span class="text-blue">
|
||||||
|
btop <span class="text-subtext0">v1.404.0</span> - ErrorOS System
|
||||||
|
Monitor
|
||||||
|
</span>
|
||||||
|
<Show
|
||||||
|
when={!isMobile()}
|
||||||
|
fallback={
|
||||||
|
<button
|
||||||
|
onClick={props.onClose}
|
||||||
|
class="bg-red hover:bg-red/80 rounded px-3 py-1 text-base transition-colors"
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<span class="text-subtext1 text-xs">Press 'q' to quit</span>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Content */}
|
||||||
|
<div class="space-y-4 p-4">
|
||||||
|
{/* System Stats */}
|
||||||
|
<div class="border-surface0 bg-base rounded border p-3">
|
||||||
|
<div class="text-green mb-2 font-bold">System Resources</div>
|
||||||
|
<div class="space-y-2">
|
||||||
|
{/* CPU */}
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="text-subtext1 w-12">CPU</span>
|
||||||
|
<span class="text-blue">[{createBar(cpuUsage())}]</span>
|
||||||
|
<span class="text-text w-16 text-right">{cpuUsage()}%</span>
|
||||||
|
<span class="text-subtext0">2.4 GHz</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Memory */}
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="text-subtext1 w-12">MEM</span>
|
||||||
|
<span class="text-blue">[{createBar(memUsage())}]</span>
|
||||||
|
<span class="text-text w-16 text-right">{memUsage()}%</span>
|
||||||
|
<span class="text-subtext0">
|
||||||
|
{((memUsage() / 100) * 16).toFixed(1)}/16 GB
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Network */}
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="text-subtext1 w-12">NET</span>
|
||||||
|
<span class="text-green">↓ {netDown()} KB/s</span>
|
||||||
|
<span class="text-red">↑ {netUp()} KB/s</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Process List */}
|
||||||
|
<div class="border-surface0 bg-base rounded border">
|
||||||
|
<div class="border-surface0 border-b px-3 py-2">
|
||||||
|
<span class="text-green font-bold">Processes</span>
|
||||||
|
</div>
|
||||||
|
<div class="overflow-x-auto">
|
||||||
|
<table class="w-full">
|
||||||
|
<thead class="bg-surface0 text-subtext1 sticky top-0">
|
||||||
|
<tr>
|
||||||
|
<th class="px-3 py-2 text-left">PID</th>
|
||||||
|
<th class="px-3 py-2 text-left">Name</th>
|
||||||
|
<th class="px-3 py-2 text-right">CPU%</th>
|
||||||
|
<th class="px-3 py-2 text-right">MEM%</th>
|
||||||
|
<th class="px-3 py-2 text-left">Status</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{processes().map((proc) => (
|
||||||
|
<tr class="hover:bg-surface0 border-surface0 border-t transition-colors">
|
||||||
|
<td class="text-yellow px-3 py-2">{proc.pid}</td>
|
||||||
|
<td class="text-text px-3 py-2 font-medium">
|
||||||
|
{proc.name}
|
||||||
|
</td>
|
||||||
|
<td class="text-blue px-3 py-2 text-right">
|
||||||
|
{proc.cpu.toFixed(1)}
|
||||||
|
</td>
|
||||||
|
<td class="text-peach px-3 py-2 text-right">
|
||||||
|
{proc.mem.toFixed(1)}
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="px-3 py-2"
|
||||||
|
classList={{
|
||||||
|
"text-green": proc.status === "Running",
|
||||||
|
"text-subtext1": proc.status === "Sleeping",
|
||||||
|
"text-mauve": proc.status === "???"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{proc.status}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Footer info */}
|
||||||
|
<div class="text-subtext1 text-center text-xs">
|
||||||
|
<Show
|
||||||
|
when={!isMobile()}
|
||||||
|
fallback={<span>Tap the Close button above to exit</span>}
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
Type <span class="text-green">q</span> to quit btop
|
||||||
|
</span>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Overlay background */}
|
||||||
|
<div
|
||||||
|
class="absolute inset-0 -z-10 bg-black/80 backdrop-blur-sm"
|
||||||
|
onClick={props.onClose}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -20,13 +20,14 @@ export default function ErrorBoundaryFallback(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to get dark mode, fallback to true (dark) if context unavailable
|
// Try to get dark mode, fallback to a function returning true (dark) if context unavailable
|
||||||
let isDark = true;
|
let isDark: () => boolean;
|
||||||
try {
|
try {
|
||||||
const darkMode = useDarkMode();
|
const darkMode = useDarkMode();
|
||||||
isDark = darkMode.isDark();
|
isDark = darkMode.isDark;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Context not available, use default
|
// Context not available, use default
|
||||||
|
isDark = () => true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [glitchText, setGlitchText] = createSignal("ERROR");
|
const [glitchText, setGlitchText] = createSignal("ERROR");
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
executeTerminalCommand,
|
executeTerminalCommand,
|
||||||
CommandContext
|
CommandContext
|
||||||
} from "~/lib/terminal-commands";
|
} from "~/lib/terminal-commands";
|
||||||
|
import { Btop } from "~/components/Btop";
|
||||||
|
|
||||||
interface TerminalErrorPageProps {
|
interface TerminalErrorPageProps {
|
||||||
glitchText: string;
|
glitchText: string;
|
||||||
@@ -34,6 +35,7 @@ export function TerminalErrorPage(props: TerminalErrorPageProps) {
|
|||||||
const [command, setCommand] = createSignal("");
|
const [command, setCommand] = createSignal("");
|
||||||
const [history, setHistory] = createSignal<CommandHistoryItem[]>([]);
|
const [history, setHistory] = createSignal<CommandHistoryItem[]>([]);
|
||||||
const [historyIndex, setHistoryIndex] = createSignal(-1);
|
const [historyIndex, setHistoryIndex] = createSignal(-1);
|
||||||
|
const [btopOpen, setBtopOpen] = createSignal(false);
|
||||||
let inputRef: HTMLInputElement | undefined;
|
let inputRef: HTMLInputElement | undefined;
|
||||||
let footerRef: HTMLDivElement | undefined;
|
let footerRef: HTMLDivElement | undefined;
|
||||||
|
|
||||||
@@ -62,6 +64,7 @@ export function TerminalErrorPage(props: TerminalErrorPageProps) {
|
|||||||
navigate: props.navigate,
|
navigate: props.navigate,
|
||||||
location: props.location,
|
location: props.location,
|
||||||
addToHistory,
|
addToHistory,
|
||||||
|
openBtop: () => setBtopOpen(true),
|
||||||
...props.commandContext
|
...props.commandContext
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -159,7 +162,7 @@ export function TerminalErrorPage(props: TerminalErrorPageProps) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Main content */}
|
{/* Main content */}
|
||||||
<div class="relative z-10 flex min-h-screen flex-col items-start justify-start py-16 lg:px-16">
|
<div class="relative z-10 flex min-h-screen flex-col items-start justify-start px-4 py-16 lg:px-16">
|
||||||
{/* Terminal header */}
|
{/* Terminal header */}
|
||||||
<div class="mb-8 w-full max-w-4xl">
|
<div class="mb-8 w-full max-w-4xl">
|
||||||
<div class="border-surface0 text-subtext0 flex items-center gap-2 border-b pb-2 font-mono text-sm">
|
<div class="border-surface0 text-subtext0 flex items-center gap-2 border-b pb-2 font-mono text-sm">
|
||||||
@@ -220,6 +223,7 @@ export function TerminalErrorPage(props: TerminalErrorPageProps) {
|
|||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
class="text-text caret-text ml-1 flex-1 border-none bg-transparent outline-none"
|
class="text-text caret-text ml-1 flex-1 border-none bg-transparent outline-none"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
|
autocapitalize="off"
|
||||||
spellcheck={false}
|
spellcheck={false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -234,6 +238,11 @@ export function TerminalErrorPage(props: TerminalErrorPageProps) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Btop overlay */}
|
||||||
|
<Show when={btopOpen()}>
|
||||||
|
<Btop onClose={() => setBtopOpen(false)} />
|
||||||
|
</Show>
|
||||||
|
|
||||||
{/* Custom styles */}
|
{/* Custom styles */}
|
||||||
<style>{`
|
<style>{`
|
||||||
@keyframes scanline {
|
@keyframes scanline {
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ export interface CommandContext {
|
|||||||
type: "success" | "error" | "info"
|
type: "success" | "error" | "info"
|
||||||
) => void;
|
) => void;
|
||||||
triggerCrash?: () => void;
|
triggerCrash?: () => void;
|
||||||
isDark?: boolean;
|
isDark?: () => boolean;
|
||||||
|
openBtop?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createTerminalCommands = (context: CommandContext) => {
|
export const createTerminalCommands = (context: CommandContext) => {
|
||||||
@@ -164,7 +165,7 @@ export const createTerminalCommands = (context: CommandContext) => {
|
|||||||
},
|
},
|
||||||
neofetch: {
|
neofetch: {
|
||||||
action: () => {
|
action: () => {
|
||||||
const theme = context.isDark ? "Catppuccin-mocha" : "Gruvbox-light";
|
const theme = context.isDark?.() ? "Catppuccin-mocha" : "Gruvbox-light";
|
||||||
context.addToHistory(
|
context.addToHistory(
|
||||||
"neofetch",
|
"neofetch",
|
||||||
` _,met$$$$$gg. guest@freno.dev\n ,g$$$$$$$$$$$$$$$P. ----------------\n ,g$$P\"\" \"\"\"Y$$.\" OS: 404 Not Found\n ,$$P' \`$$$. Shell: terminal-shell\n',$$P ,ggs. \`$$b: Resolution: Lost\n\`d$$' ,$P\"' . $$$ Theme: ${theme}\n $$P d$' , $$P Terminal: web-terminal\n $$: $$. - ,d$$' CPU: Confusion (404)\n $$; Y$b._ _,d$P' Memory: ???\n Y$$. \`.\`\"Y$$$$P\"' \n \`$$b \"-.__ \n \`Y$$ \n \`Y$$. \n \`$$b. \n \`Y$$b. \n \`\"Y$b._ \n \`\"\"\"\" `,
|
` _,met$$$$$gg. guest@freno.dev\n ,g$$$$$$$$$$$$$$$P. ----------------\n ,g$$P\"\" \"\"\"Y$$.\" OS: 404 Not Found\n ,$$P' \`$$$. Shell: terminal-shell\n',$$P ,ggs. \`$$b: Resolution: Lost\n\`d$$' ,$P\"' . $$$ Theme: ${theme}\n $$P d$' , $$P Terminal: web-terminal\n $$: $$. - ,d$$' CPU: Confusion (404)\n $$; Y$b._ _,d$P' Memory: ???\n Y$$. \`.\`\"Y$$$$P\"' \n \`$$b \"-.__ \n \`Y$$ \n \`Y$$. \n \`$$b. \n \`Y$$b. \n \`\"Y$b._ \n \`\"\"\"\" `,
|
||||||
@@ -211,6 +212,20 @@ export const createTerminalCommands = (context: CommandContext) => {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
description: "Print system information"
|
description: "Print system information"
|
||||||
|
},
|
||||||
|
btop: {
|
||||||
|
action: () => {
|
||||||
|
if (context.openBtop) {
|
||||||
|
context.openBtop();
|
||||||
|
} else {
|
||||||
|
context.addToHistory(
|
||||||
|
"btop",
|
||||||
|
"btop: command not available in this context",
|
||||||
|
"error"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
description: "System resource monitor"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ export default function NotFound() {
|
|||||||
onGlitchTextChange={setGlitchText}
|
onGlitchTextChange={setGlitchText}
|
||||||
commandContext={{
|
commandContext={{
|
||||||
triggerCrash: () => setShouldCrash(true),
|
triggerCrash: () => setShouldCrash(true),
|
||||||
isDark: isDark()
|
isDark: isDark
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
|||||||
Reference in New Issue
Block a user