Skip to content

Commit

Permalink
new commands & code refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
n0rrman committed May 13, 2024
1 parent 2e88c57 commit fb94381
Show file tree
Hide file tree
Showing 13 changed files with 228 additions and 138 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"next": "14.0.4",
"react": "^18",
"react-dom": "^18",
"react-icons": "^5.2.1",
"zod": "^3.23.4"
},
"devDependencies": {
Expand Down
8 changes: 8 additions & 0 deletions src/actions/action-codes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export enum ActionCode {
NOOP = "noop",
CLEAR = "clear",
REDIRECT = "redirect",
ERROR = "error",
EXIT = "exit",
HELP = "help",
}
32 changes: 32 additions & 0 deletions src/actions/commands.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
[
{
"cmd": "whoami",
"value": "Hello, World! 👋 I'm \n \n █████ █████ ███ █████ \n░░███ ░░███ ░░░ ░░███ \n ░███ ░███ ██████ ████████ ████████ ████ ░███ █████ \n ░███████████ ███░░███░░███░░███ ░░███░░███░░███ ░███░░███ \n ░███░░░░░███ ░███████ ░███ ░███ ░███ ░░░ ░███ ░██████░ \n ░███ ░███ ░███░░░ ░███ ░███ ░███ ░███ ░███░░███ \n █████ █████░░██████ ████ █████ █████ █████ ████ █████ \n░░░░░ ░░░░░ ░░░░░░ ░░░░ ░░░░░ ░░░░░ ░░░░░ ░░░░ ░░░░░ \n \n ██████ █████ \n░░██████ ░░███ \n ░███░███ ░███ ██████ ████████ ████████ █████████████ ██████ ████████ \n ░███░░███░███ ███░░███░░███░░███░░███░░███░░███░░███░░███ ░░░░░███ ░░███░░███ \n ░███ ░░██████ ░███ ░███ ░███ ░░░ ░███ ░░░ ░███ ░███ ░███ ███████ ░███ ░███ \n ░███ ░░█████ ░███ ░███ ░███ ░███ ░███ ░███ ░███ ███░░███ ░███ ░███ \n █████ ░░█████░░██████ █████ █████ █████░███ █████░░████████ ████ █████\n░░░░░ ░░░░░ ░░░░░░ ░░░░░ ░░░░░ ░░░░░ ░░░ ░░░░░ ░░░░░░░░ ░░░░ ░░░░░ \n \na software engineer based in Stockholm, Sweden 🇸🇪 \n",
"actionCode": "noop"
},
{
"cmd": "clear",
"value": "Clearning terminal...",
"actionCode": "clear"
},
{
"cmd": "github",
"value": "https://github.com/n0rrman",
"actionCode": "redirect"
},
{
"cmd": "source code",
"value": "https://github.com/n0rrman/henriknorrman.com",
"actionCode": "redirect"
},
{
"cmd": "exit",
"value": "\nSaving session...completed.",
"actionCode": "exit"
},
{
"cmd": "help",
"value": "These shell commands are defined internally. Type `help' to see this list.\n\twhoami\t\t\tdisplay effective user\n\tclear\t\t\tclear the terminal screen\n\tgithub\t\t\topen my github\n\tsource code\t\topen the source code repository\n\thelp\t\t\tlist available commands\n\texit\t\t\texit the terminal",
"actionCode": "help"
}
]
32 changes: 0 additions & 32 deletions src/actions/commands.ts

This file was deleted.

74 changes: 57 additions & 17 deletions src/actions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,81 @@

import { z } from "zod";
import { randomUUID } from 'crypto'
import callCommand from "./commands";
import { promises as fs } from 'fs';

import { ActionCode } from "./action-codes";

const terminalSchema = z.object({
cmd: z.string().min(1).max(128),
});

interface Command {
cmd: string;
value: string;
description: string;
actionCode: ActionCode;
}

export interface CommandAction {
id: string;
input: string;
output: string;
actionCode: ActionCode;
}

interface TerminalState {
payload?: {
id: string;
input: string;
output: string;
payload?: CommandAction
}

async function callCommand(id: string, input: string, errorState: TerminalState) {
// Load command file to JSON
// success -> continue
// error -> return error state
let data: [Command];
try {
const file = await fs.readFile(process.cwd() + '/src/actions/commands.json', 'utf8');
data = (JSON.parse(file) as [Command]);
} catch (err: unknown) {
return ({payload: {...errorState.payload, output: "Error: internal error"}} as TerminalState);
}

// Get command
const command = data.find(({cmd}: Command) => cmd === input);

// -> Command not found
if (!command) {
return {
payload: {id, input, output: `${input}: command not found. Type 'help' for list of commands.`, actionCode: ActionCode.NOOP}
}
}

// -> Command found
return {
payload: {
id, input, output: command!.value, actionCode: command!.actionCode
}
}
}

export async function terminalState(formState: TerminalState, formData: FormData): Promise<TerminalState> {

const result = terminalSchema.safeParse({
cmd: formData.get("cmd"),
});

const input = formData.get("cmd")!.toString();
const id = randomUUID();

if(!result.success) {
return {
payload: {
id, input, output: ""
}

// Default error state
const errorState = {
payload: {
id, input, output: "", actionCode: ActionCode.ERROR
}
}

return {
payload: {
id, input, output: callCommand(input)
}
// Input error -> return blank output
if(!result.success) {
return {payload: {...errorState.payload, output: ""}};
}

return callCommand(id, input, errorState);
}

4 changes: 2 additions & 2 deletions src/app/fonts.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Montserrat, Source_Code_Pro } from "next/font/google";
import localFont from "next/font/local";

export const courier = localFont({ src: "./courier.woff2" });
export const courier = localFont({ src: "courier.woff2" });

export const code = Source_Code_Pro({
subsets: ["latin"],
Expand All @@ -10,5 +10,5 @@ export const code = Source_Code_Pro({

export const main = Montserrat({
subsets: ["latin"],
weight: ["400"],
weight: ["400", "700"],
});
8 changes: 1 addition & 7 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,14 @@ export const metadata: Metadata = {
},
};

export const viewport: Viewport = {
width: "device-width",
initialScale: 1,
maximumScale: 1,
};

export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body className="bg-blue-100">{children}</body>
<body className="bg-slate-300">{children}</body>
</html>
);
}
16 changes: 4 additions & 12 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,15 @@
import Image from "next/image";

import background from "/public/background.svg";
import logo from "/public/logo.svg";
import Terminal from "@/components/terminal";

export default function LandingPage() {
return (
<div className="flex flex-col gap-5 justify-start items-center bg-blue-100">
<div className="relative w-52 h-52">
<Image src={logo.src} fill alt="" priority loading="eager" />
</div>
<div className="absolute inset-0 w-full z-0">
{/* <div className="h-[200vh] w-full relative opacity-75">
<Image src={background} fill alt="" />
</div> */}
</div>
<div className="z-50">
<Terminal />
<div className="flex flex-col items-center justify-start bg-gradient-to-b from-blue-100 to-slate-300 w-full min-h-[70vh]">
<div className="relative w-48 h-48 select-none pointer-events-none">
<Image src={logo} fill alt="" priority loading="eager" />
</div>
<Terminal />
</div>
);
}
69 changes: 69 additions & 0 deletions src/components/terminal-screen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import Image from "next/image";

import screenImg from "/public/screen.svg";
import { courier } from "../app/fonts";
import { CommandAction } from "@/actions";

interface TerminalScreenProps {
prompt: React.ReactElement;
outputs: CommandAction[];
inputRef: React.RefObject<HTMLInputElement>;
formRef: React.RefObject<HTMLFormElement>;
submitHandler: (event: React.FormEvent<HTMLFormElement>) => void;
}

export default function TerminalScreen({
prompt,
outputs,
inputRef,
formRef,
submitHandler,
}: TerminalScreenProps) {
const renderedOutputs = outputs.map(
({ id, input, output }: CommandAction) => {
return (
<div
className={`flex flex-col gap-y-0.5 font-thin ${courier.className}`}
key={id}
>
<p>
{prompt}
{input}
</p>
{output !== "" && <p className="">{output}</p>}
</div>
);
}
);

return (
<div
className="flex justify-center items-center text-[1.77vw] sm:text-xs lg:text-sm w-full py-0 sm:py-7"
onClick={() => {
inputRef!.current!.focus();
}}
>
<div className="flex z-50 justify-center items-center absolute overflow-hidden pointer-events-none">
<div className="hidden sm:block relative w-[93.5ch] aspect-[1.25] pointer-events-none select-none">
<Image alt="" src={screenImg} fill></Image>
</div>
</div>
<div
className={`${courier.className} z-40 px-[1.5ch] py-[0.5ch] mb-[13ch] bg-slate-800 text-slate-200 flex items-end w-full sm:w-[90ch] leading-snug tracking-wide overflow-hidden h-[57ch]`}
>
<form ref={formRef} onSubmit={submitHandler} className="select-text">
<pre className="space-y-[0.25ch]">{renderedOutputs}</pre>
<div className="flex flex-row items-end justify-start">
{prompt}
<input
ref={inputRef}
className="bg-transparent w-[67ch] outline-none mt-[0.25ch]"
name="cmd"
type="text"
/>
</div>
</form>
</div>
</div>
);
}
Loading

0 comments on commit fb94381

Please sign in to comment.