Split up the commands into individual files, made it smoother.
This commit is contained in:
parent
0749795395
commit
39b5d22512
5 changed files with 220 additions and 220 deletions
16
src/app/commands/help.tsx
Normal file
16
src/app/commands/help.tsx
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
const helpCommand = () => [
|
||||||
|
<div key="help">
|
||||||
|
<strong className="text-yellow-400">📖 Help Menu:</strong>
|
||||||
|
<ul className="mt-2 space-y-1">
|
||||||
|
<li>🆘 <strong className="text-green-400">help</strong>: Display this help menu.</li>
|
||||||
|
<li>📡 <strong className="text-green-400">socials</strong>: View my social links.</li>
|
||||||
|
<li>🌐 <strong className="text-green-400">selfhosted</strong>: View what I self host in my homelab.</li>
|
||||||
|
<li>❌ <strong className="text-green-400">exit</strong>: Close the terminal (browser tab).</li>
|
||||||
|
</ul>
|
||||||
|
<p className="mt-4 text-gray-400">
|
||||||
|
💡 Pro Tip: Use <span className="text-green-400">↑</span> and <span className="text-green-400">↓</span> to navigate through your command history.
|
||||||
|
</p>
|
||||||
|
</div>,
|
||||||
|
];
|
||||||
|
|
||||||
|
export default helpCommand;
|
39
src/app/commands/selfhosted.tsx
Normal file
39
src/app/commands/selfhosted.tsx
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import dynamic from "next/dynamic";
|
||||||
|
|
||||||
|
// Self-hosted Services
|
||||||
|
const SiDocker = dynamic(() => import("react-icons/si").then((mod) => mod.SiDocker), { ssr: false });
|
||||||
|
const SiPlex = dynamic(() => import("react-icons/si").then((mod) => mod.SiPlex), { ssr: false });
|
||||||
|
const SiProxmox = dynamic(() => import("react-icons/si").then((mod) => mod.SiProxmox), { ssr: false });
|
||||||
|
const SiHomeassistant = dynamic(() => import("react-icons/si").then((mod) => mod.SiHomeassistant), { ssr: false });
|
||||||
|
const SiPaperlessngx = dynamic(() => import("react-icons/si").then((mod) => mod.SiPaperlessngx), { ssr: false });
|
||||||
|
|
||||||
|
const selfhostedCommand= (showHeader = true) => [
|
||||||
|
<div key="selfHosted">
|
||||||
|
{showHeader && (
|
||||||
|
<strong className="text-yellow-400">⚙️ SELF-HOSTED TOOLS</strong>
|
||||||
|
)}
|
||||||
|
<div className="text-white flex items-center mt-2">
|
||||||
|
<SiProxmox className="text-orange-500 mr-2"/>
|
||||||
|
Proxmox: Virtual machines and containers, all in one place.
|
||||||
|
</div>
|
||||||
|
<div className="text-white flex items-center mt-2">
|
||||||
|
<SiDocker className="text-blue-500 mr-2"/>
|
||||||
|
Portainer: Orchestrating Docker containers effortlessly.
|
||||||
|
</div>
|
||||||
|
<div className="text-white flex items-center mt-2">
|
||||||
|
<SiPlex className="text-blue-400 mr-2"/>
|
||||||
|
Plex: Streaming my curated media library.
|
||||||
|
</div>
|
||||||
|
<div className="text-white flex items-center mt-2">
|
||||||
|
<SiHomeassistant className="text-teal-400 mr-2"/>
|
||||||
|
Home Assistant: Automating everything smart at home.
|
||||||
|
</div>
|
||||||
|
<div className="text-white flex items-center mt-2">
|
||||||
|
<SiPaperlessngx className="text-green-500 mr-2"/>
|
||||||
|
Paperless-ngx: Turning paper piles into searchable archives.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>,
|
||||||
|
];
|
||||||
|
|
||||||
|
export default selfhostedCommand;
|
70
src/app/commands/socials.tsx
Normal file
70
src/app/commands/socials.tsx
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
import dynamic from "next/dynamic";
|
||||||
|
|
||||||
|
// Dynamically import React Icons with client-side rendering
|
||||||
|
const FaYoutube = dynamic(() => import("react-icons/fa6").then((mod) => mod.FaYoutube), { ssr: false });
|
||||||
|
const FaBluesky = dynamic(() => import("react-icons/fa6").then((mod) => mod.FaBluesky), { ssr: false });
|
||||||
|
const FaDiscord = dynamic(() => import("react-icons/fa6").then((mod) => mod.FaDiscord), { ssr: false });
|
||||||
|
const FaGithub = dynamic(() => import("react-icons/fa6").then((mod) => mod.FaGithub), { ssr: false });
|
||||||
|
const FaReddit = dynamic(() => import("react-icons/fa6").then((mod) => mod.FaReddit), { ssr: false });
|
||||||
|
|
||||||
|
const socialsCommand = (showHeader = true) => [
|
||||||
|
<div key="socials">
|
||||||
|
{showHeader && (
|
||||||
|
<strong className="text-yellow-400">📡 Social Links:</strong>
|
||||||
|
)}
|
||||||
|
<div className="text-white flex items-center mt-2">
|
||||||
|
<FaGithub className="text-gray-400 mr-2" />
|
||||||
|
GitHub:{" "}
|
||||||
|
<a
|
||||||
|
href="https://github.com/Ronniie"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="underline"
|
||||||
|
>
|
||||||
|
Ronniie
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className="text-white flex items-center mt-2">
|
||||||
|
<FaBluesky className="text-blue-400 mr-2" />
|
||||||
|
BlueSky:{" "}
|
||||||
|
<a
|
||||||
|
href="https://bsky.app/profile/ronniie.dev"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="underline"
|
||||||
|
>
|
||||||
|
@ronniie.dev
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className="text-white flex items-center mt-2">
|
||||||
|
<FaDiscord className="text-blue-500 mr-2" />
|
||||||
|
Discord: ronniie.
|
||||||
|
</div>
|
||||||
|
<div className="text-white flex items-center mt-2">
|
||||||
|
<FaYoutube className="text-red-500 mr-2" />
|
||||||
|
YouTube:{" "}
|
||||||
|
<a
|
||||||
|
href="https://www.youtube.com/@Zotechz"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="underline"
|
||||||
|
>
|
||||||
|
@Zotechz
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className="text-white flex items-center mt-2">
|
||||||
|
<FaReddit className="text-orange-500 mr-2" />
|
||||||
|
Reddit:{" "}
|
||||||
|
<a
|
||||||
|
href="https://www.reddit.com/user/Zotechz/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="underline"
|
||||||
|
>
|
||||||
|
u/Zotechz
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>,
|
||||||
|
];
|
||||||
|
|
||||||
|
export default socialsCommand;
|
10
src/app/commands/unknown.tsx
Normal file
10
src/app/commands/unknown.tsx
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
const unknownCommand = (input: string) => () => [
|
||||||
|
<div key="unknown-command" className="text-red-500">
|
||||||
|
Unknown command: <span className="font-bold">{input}</span>
|
||||||
|
</div>,
|
||||||
|
<div key="suggestion" className="text-green-400">
|
||||||
|
Type <span className="font-bold">help</span> to see available commands.
|
||||||
|
</div>,
|
||||||
|
];
|
||||||
|
|
||||||
|
export default unknownCommand;
|
|
@ -1,22 +1,12 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useRef, useEffect } from "react";
|
import { useState, useRef, useEffect } from "react";
|
||||||
import dynamic from "next/dynamic";
|
import helpCommand from "../app/commands/help";
|
||||||
|
import socialsCommand from "../app/commands/socials";
|
||||||
|
import selfhostedCommand from "../app/commands/selfhosted"
|
||||||
|
import exitCommand from "../app/commands/exit";
|
||||||
|
import unknownCommand from "../app/commands/unknown";
|
||||||
// Dynamically import React Icons with client-side rendering
|
// Dynamically import React Icons with client-side rendering
|
||||||
const FaYoutube = dynamic(() => import("react-icons/fa6").then((mod) => mod.FaYoutube), { ssr: false });
|
|
||||||
const FaBluesky = dynamic(() => import("react-icons/fa6").then((mod) => mod.FaBluesky), { ssr: false });
|
|
||||||
const FaDiscord = dynamic(() => import("react-icons/fa6").then((mod) => mod.FaDiscord), { ssr: false });
|
|
||||||
const FaGithub = dynamic(() => import("react-icons/fa6").then((mod) => mod.FaGithub), { ssr: false });
|
|
||||||
const FaReddit = dynamic(() => import("react-icons/fa6").then((mod) => mod.FaReddit), { ssr: false });
|
|
||||||
|
|
||||||
// Self-hosted Services
|
|
||||||
const SiDocker = dynamic(() => import("react-icons/si").then((mod) => mod.SiDocker), { ssr: false });
|
|
||||||
const SiPlex = dynamic(() => import("react-icons/si").then((mod) => mod.SiPlex), { ssr: false });
|
|
||||||
const SiProxmox = dynamic(() => import("react-icons/si").then((mod) => mod.SiProxmox), { ssr: false });
|
|
||||||
const SiHomeassistant = dynamic(() => import("react-icons/si").then((mod) => mod.SiHomeassistant), { ssr: false });
|
|
||||||
const SiPaperlessngx = dynamic(() => import("react-icons/si").then((mod) => mod.SiPaperlessngx), { ssr: false });
|
|
||||||
|
|
||||||
|
|
||||||
const Terminal: React.FC = () => {
|
const Terminal: React.FC = () => {
|
||||||
const [input, setInput] = useState("");
|
const [input, setInput] = useState("");
|
||||||
|
@ -30,96 +20,38 @@ const Terminal: React.FC = () => {
|
||||||
const handleInput = (e: React.FormEvent<HTMLFormElement>) => {
|
const handleInput = (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (input.trim() === "") return;
|
// If the input is empty, add a blank line and return
|
||||||
|
if (input.trim() === "") {
|
||||||
|
setOutput((prev) => [
|
||||||
|
...prev,
|
||||||
|
<div key={`blank-${prev.length}`}> </div>, // Blank line
|
||||||
|
]);
|
||||||
|
setInput(""); // Clear the input field
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const commands: { [key: string]: JSX.Element[] } = {
|
const normalizedInput = input.toLowerCase();
|
||||||
help: [
|
const commands: { [key: string]: () => JSX.Element[] } = {
|
||||||
<div key="help">
|
help: helpCommand,
|
||||||
<strong className="text-yellow-400">📖 Help Menu:</strong>
|
socials: socialsCommand,
|
||||||
<ul className="mt-2 space-y-1">
|
exit: exitCommand,
|
||||||
<li>🆘 <strong className="text-green-400">help</strong>: Display this help menu.</li>
|
selfhosted: selfhostedCommand,
|
||||||
<li>📡 <strong className="text-green-400">socials</strong>: View my social links.</li>
|
|
||||||
<li>❌ <strong className="text-green-400">exit</strong>: Exit this terminal.</li>
|
|
||||||
</ul>
|
|
||||||
<p className="mt-4 text-gray-400">
|
|
||||||
💡 Pro Tip: Use <span className="text-green-400">↑</span> and <span className="text-green-400">↓</span> to navigate through your command history.
|
|
||||||
</p>
|
|
||||||
</div>,
|
|
||||||
],
|
|
||||||
socials: [
|
|
||||||
<div key="socials">
|
|
||||||
<strong className="text-yellow-400">📡 Social Links:</strong>
|
|
||||||
<div className="text-white flex items-center mt-2">
|
|
||||||
<FaGithub className="text-gray-400 mr-2"/>
|
|
||||||
GitHub:{" "}
|
|
||||||
<a
|
|
||||||
href="https://github.com/Ronniie"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="underline"
|
|
||||||
>
|
|
||||||
Ronniie
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="text-white flex items-center mt-2">
|
|
||||||
<FaBluesky className="text-blue-400 mr-2"/>
|
|
||||||
BlueSky:{" "}
|
|
||||||
<a
|
|
||||||
href="https://bsky.app/profile/ronniie.dev"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="underline"
|
|
||||||
>
|
|
||||||
@ronniie.dev
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div className="text-white flex items-center mt-2">
|
|
||||||
<FaDiscord className="text-blue-500 mr-2"/>
|
|
||||||
Discord:{" "}
|
|
||||||
ronniie.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="text-white flex items-center mt-2">
|
|
||||||
<FaYoutube className="text-red-500 mr-2"/>
|
|
||||||
YouTube:{" "}
|
|
||||||
<a
|
|
||||||
href="https://www.youtube.com/@Zotechz"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="underline"
|
|
||||||
>
|
|
||||||
@Zotechz
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div className="text-white flex items-center mt-2">
|
|
||||||
<FaReddit className="text-orange-500 mr-2"/>
|
|
||||||
Reddit:{" "}
|
|
||||||
<a
|
|
||||||
href="https://www.reddit.com/user/Zotechz/"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="underline"
|
|
||||||
>
|
|
||||||
u/Zotechz
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>,
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const commandOutput = commands[input] || [
|
const commandOutput =
|
||||||
<div key="unknown" className="text-red-400">
|
commands[normalizedInput] || unknownCommand(normalizedInput);
|
||||||
Unknown command: {input}
|
|
||||||
</div>,
|
|
||||||
];
|
|
||||||
|
|
||||||
setOutput((prev) => [...prev, <div key={`cmd-${input}`}>$ {input}</div>, ...commandOutput]);
|
setOutput((prev) => [
|
||||||
|
...prev,
|
||||||
|
<div key={`cmd-${input}`}>$ {input}</div>,
|
||||||
|
...commandOutput(),
|
||||||
|
]);
|
||||||
setHistory((prev) => [...prev, input]);
|
setHistory((prev) => [...prev, input]);
|
||||||
setHistoryIndex(null); // Reset the history index
|
setHistoryIndex(null);
|
||||||
setInput("");
|
setInput("");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||||
if (history.length === 0) return;
|
if (history.length === 0) return;
|
||||||
|
|
||||||
|
@ -152,14 +84,6 @@ const Terminal: React.FC = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClick = () => {
|
|
||||||
const selection = window.getSelection();
|
|
||||||
if (selection && selection.toString()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
inputRef.current?.focus();
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
terminalEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
terminalEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
||||||
}, [output]);
|
}, [output]);
|
||||||
|
@ -167,7 +91,7 @@ const Terminal: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="bg-black text-white font-mono w-full h-screen overflow-y-auto p-4"
|
className="bg-black text-white font-mono w-full h-screen overflow-y-auto p-4"
|
||||||
onClick={handleClick}
|
onClick={() => inputRef.current?.focus()}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
{output.map((line, index) => (
|
{output.map((line, index) => (
|
||||||
|
@ -176,7 +100,6 @@ const Terminal: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form onSubmit={handleInput} className="flex items-center mt-2">
|
<form onSubmit={handleInput} className="flex items-center mt-2">
|
||||||
<span className="text-green-500">$</span>
|
<span className="text-green-500">$</span>
|
||||||
<input
|
<input
|
||||||
|
@ -195,7 +118,12 @@ const Terminal: React.FC = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const MOTD = () => (
|
const MOTD = () => {
|
||||||
|
const socialsOutput = socialsCommand(false);
|
||||||
|
const selfHostedOutput = selfhostedCommand(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
|
||||||
<>
|
<>
|
||||||
<div className="text-green-400">
|
<div className="text-green-400">
|
||||||
{` _____ _ _ _ `}
|
{` _____ _ _ _ `}
|
||||||
|
@ -224,83 +152,18 @@ const MOTD = () => (
|
||||||
|
|
||||||
<div className="border-t border-gray-700 my-4"/>
|
<div className="border-t border-gray-700 my-4"/>
|
||||||
<strong className="text-yellow-400">📡 SOCIALS</strong>
|
<strong className="text-yellow-400">📡 SOCIALS</strong>
|
||||||
<div className="text-white flex items-center mt-2">
|
<div>
|
||||||
<FaGithub className="text-gray-400 mr-2"/>
|
{socialsOutput.map((line, index) => (
|
||||||
GitHub:{" "}
|
<div key={index}>{line}</div>
|
||||||
<a
|
))}
|
||||||
href="https://github.com/Ronniie"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="underline"
|
|
||||||
>
|
|
||||||
Ronniie
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="text-white flex items-center mt-2">
|
|
||||||
<FaBluesky className="text-blue-400 mr-2"/>
|
|
||||||
BlueSky:{" "}
|
|
||||||
<a
|
|
||||||
href="https://bsky.app/profile/ronniie.dev"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="underline"
|
|
||||||
>
|
|
||||||
@ronniie.dev
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div className="text-white flex items-center mt-2">
|
|
||||||
<FaDiscord className="text-blue-500 mr-2"/>
|
|
||||||
Discord:{" "}
|
|
||||||
ronniie.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="text-white flex items-center mt-2">
|
|
||||||
<FaYoutube className="text-red-500 mr-2"/>
|
|
||||||
YouTube:{" "}
|
|
||||||
<a
|
|
||||||
href="https://www.youtube.com/@Zotechz"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="underline"
|
|
||||||
>
|
|
||||||
@Zotechz
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div className="text-white flex items-center mt-2">
|
|
||||||
<FaReddit className="text-orange-500 mr-2"/>
|
|
||||||
Reddit:{" "}
|
|
||||||
<a
|
|
||||||
href="https://www.reddit.com/user/Zotechz/"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="underline"
|
|
||||||
>
|
|
||||||
u/Zotechz
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="border-t border-gray-700 my-4"/>
|
<div className="border-t border-gray-700 my-4"/>
|
||||||
<strong className="text-yellow-400">⚙️ SELF-HOSTED TOOLS</strong>
|
<strong className="text-yellow-400">⚙️ SELF-HOSTED TOOLS</strong>
|
||||||
<div className="text-white flex items-center mt-2">
|
<div>
|
||||||
<SiProxmox className="text-orange-500 mr-2"/>
|
{selfHostedOutput.map((line, index) => (
|
||||||
Proxmox: Virtual machines and containers, all in one place.
|
<div key={index}>{line}</div>
|
||||||
</div>
|
))}
|
||||||
<div className="text-white flex items-center mt-2">
|
|
||||||
<SiDocker className="text-blue-500 mr-2"/>
|
|
||||||
Portainer: Orchestrating Docker containers effortlessly.
|
|
||||||
</div>
|
|
||||||
<div className="text-white flex items-center mt-2">
|
|
||||||
<SiPlex className="text-blue-400 mr-2"/>
|
|
||||||
Plex: Streaming my curated media library.
|
|
||||||
</div>
|
|
||||||
<div className="text-white flex items-center mt-2">
|
|
||||||
<SiHomeassistant className="text-teal-400 mr-2"/>
|
|
||||||
Home Assistant: Automating everything smart at home.
|
|
||||||
</div>
|
|
||||||
<div className="text-white flex items-center mt-2">
|
|
||||||
<SiPaperlessngx className="text-green-500 mr-2"/>
|
|
||||||
Paperless-ngx: Turning paper piles into searchable archives.
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="border-t border-gray-700 my-4"/>
|
<div className="border-t border-gray-700 my-4"/>
|
||||||
|
@ -312,9 +175,11 @@ const MOTD = () => (
|
||||||
|
|
||||||
<div className="border-t border-gray-700 my-4"/>
|
<div className="border-t border-gray-700 my-4"/>
|
||||||
<div className="text-gray-400 mt-2">
|
<div className="text-gray-400 mt-2">
|
||||||
Type <span className="text-green-400">'help'</span> to see what you can do. Let’s explore together!
|
Type <span className="text-green-400">'help'</span> to see what you can do. Let’s explore
|
||||||
|
together!
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default Terminal;
|
export default Terminal;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue