Skip to content

Commit

Permalink
bringing bacc the terminal
Browse files Browse the repository at this point in the history
  • Loading branch information
soohoonc committed Aug 5, 2024
1 parent 01bd514 commit 9156556
Show file tree
Hide file tree
Showing 8 changed files with 275 additions and 13 deletions.
Binary file modified bun.lockb
Binary file not shown.
21 changes: 11 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,19 @@
"@mdx-js/loader": "^3.0.1",
"@mdx-js/react": "^3.0.1",
"@next/mdx": "^14.2.5",
"@radix-ui/react-collapsible": "^1.0.3",
"@radix-ui/react-collapsible": "^1.1.0",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-navigation-menu": "^1.1.3",
"@radix-ui/react-scroll-area": "^1.0.4",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-navigation-menu": "^1.2.0",
"@radix-ui/react-scroll-area": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0",
"@types/mdx": "^2.0.13",
"caniuse-lite": "^1.0.30001649",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"lucide-react": "^0.263.0",
"next": "^14.2.3",
"clsx": "^2.1.1",
"lucide-react": "^0.263.1",
"next": "^14.2.5",
"next-themes": "^0.2.1",
"prettier": "^3.0.3",
"prettier": "^3.3.3",
"react": "18.2.0",
"react-dom": "18.2.0",
"remark": "^15.0.1",
Expand All @@ -41,11 +42,11 @@
"bun-types": "latest",
"eslint": "8.45.0",
"eslint-config-next": "13.4.10",
"gh-pages": "^6.0.0",
"gh-pages": "^6.1.1",
"postcss": "8.4.31",
"tailwind-merge": "^1.14.0",
"tailwindcss": "3.3.3",
"tailwindcss-animate": "^1.0.6",
"tailwindcss-animate": "^1.0.7",
"typescript": "5.1.6"
},
"module": "index.ts"
Expand Down
8 changes: 6 additions & 2 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
import '@/styles/globals.css';
import type { Metadata } from 'next';
import { ThemeProvider } from './providers';
import { ThemeProvider } from '@/providers/theme';
import { ShellProvider } from '@/providers/shell';

export const metadata: Metadata = {
title: 'soohoonchoi',
description: 'soohoonchoi',
};


export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang='en' suppressHydrationWarning>
<body>
<main>
<ThemeProvider attribute='class' defaultTheme='system' enableSystem>
{children}
<ShellProvider>
{children}
</ShellProvider>
</ThemeProvider>
</main>
</body>
Expand Down
6 changes: 5 additions & 1 deletion src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
// import { Shell } from "@/components/shell"

const Page = () => {
return (
<div>
<h1>Soohoon Choi</h1>
<h3 className="mb-2">about me</h3>
<p className="my-2">
Cofounder at <a href="https://greptile.com">Greptile</a>.<br/>
Cofounder at <a href="https://greptile.com" target="_blank">Greptile</a>.<br/>
Georgia Tech computer science and mathematics class of 2023.
</p>
Interested in (but not limited to):
Expand All @@ -24,7 +26,9 @@ const Page = () => {
<div><a href="https://linkedin.com/in/soohoonchoi">LinkedIn</a></div>
<div><a href="https://x.com/soohoonchoi">Twitter</a></div>
</div>
{/* <Shell/> */}
</div>

)
}

Expand Down
115 changes: 115 additions & 0 deletions src/components/shell.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
'use client';

import React from 'react';
import { useShell } from '@/providers/shell';

export const Shell = () => {
const shellInputRef = React.useRef<HTMLSpanElement>(null);

const focusInput = () => {
shellInputRef.current?.focus();
};

return (
<div
className='font-mono text-xs md:text-base h-screen flex flex-col overflow-y-scroll'
onClick={focusInput}
>
<ShellHistory />
<ShellInput ref={shellInputRef} />
</div>
);
};

const ShellInput = React.forwardRef<HTMLSpanElement | null>((_, ref) => {
const [input, setInput] = React.useState<string>('');
const inputRef = React.useRef<HTMLSpanElement>(null);
const { history, execute, user, hostname } = useShell();

// React.useImperativeHandle(ref, () => ({
// focus: () => {
// inputRef.current?.focus();
// },
// }));

const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
const output = execute(input);
// Add the input and output to history
history.add({ role: 'user', content: `${user.get()}@${hostname.get()} $ ${input}` });
history.add({ role: 'assistant', content: output });
setInput('');
}
};

const handleChange = (e: React.FormEvent<HTMLSpanElement>) => {
setInput(e.currentTarget.textContent || '');
};

return (
<div className='bg-transparent outline-none resize-none break-all'>
<span>{`${user.get()}@${hostname.get()} $ `}</span>
<span
contentEditable
ref={inputRef}
onInput={handleChange}
onKeyDown={handleKeyDown}
className='bg-transparent outline-none'
autoFocus
/>
</div>
);
});

export const ShellHistory = () => {
const { history } = useShell();
const [currentTime, setCurrentTime] = React.useState(getTime());

React.useEffect(() => {
const timer = setInterval(() => {
setCurrentTime(getTime());
}, 1000);

return () => {
clearInterval(timer);
};
}, []);

return (
<>
<p suppressHydrationWarning>
<a href='https://soohoonchoi.com' className='link'>
soohoonchoi
</a>{' '}
<React.Suspense fallback={null}>
{currentTime}
</React.Suspense>
<br />
Type &quot;help&quot;, &quot;credits&quot; or &quot;license&quot; for more.
</p>
{history.get().map((message, index) => (
<React.Fragment key={index}>
<span>{message.content}</span>
<br />
</React.Fragment>
))}
</>
);
};

function getTime() {
const date = new Date();
const formattedDate = date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric',
});
const formattedTime = date.toLocaleTimeString('en-US', {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false,
});
return `${formattedDate}, ${formattedTime}`;
}
114 changes: 114 additions & 0 deletions src/providers/shell.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@


'use client';

import * as React from 'react';

const initialShellState: ShellState = {
history: [] as Message[],
user: 'guest',
hostname: 'soohoonchoi.com',
shell: {} as Shell
}

const ShellStateReducer = (state: ShellState, action: ShellStateAction): ShellState => {
switch (action.type) {
case 'addHistory':
return {
...state,
history: [...state.history, action.payload]
};
case 'clearHistory':
return {
...state,
history: []
};
case 'setUser':
return {
...state,
user: action.payload
};
case 'setHostname':
return {
...state,
hostname: action.payload
};
case 'setShell':
return {
...state,
shell: action.payload
};
default:
return state;
}
}

interface ShellContext {
history: {
get: () => Message[];
add: (message: Message) => void;
clear: () => void;
},
user: {
get: () => string;
set: (user: string) => void;
},
hostname: {
get: () => string;
set: (hostname: string) => void;
},
execute: (command: string) => string;
}

const ShellContext = React.createContext<ShellContext | undefined>(undefined);


export function useShell() {
const context = React.useContext(ShellContext);
if (!context) {
throw new Error('useShell must be used within a ShellProvider');
}
return context;
}

export function ShellProvider({ children }: { children: React.ReactNode }) {
const [state, dispatch] = React.useReducer(ShellStateReducer, initialShellState);
const shellContext = {
history: {
get: () => state.history,
add: (message: Message) => dispatch({ type: 'addHistory', payload: message }),
clear: () => dispatch({ type: 'clearHistory' })
},
user: {
get: () => state.user,
set: (user: string) => dispatch({ type: 'setUser', payload: user })
},
hostname: {
get: () => state.hostname,
set: (hostname: string) => dispatch({ type: 'setHostname', payload: hostname })
},
execute: (command: string) => {
return state.shell.run(command);
}
}
React.useEffect(() => {
async function init() {
const shell = {
run: (command: string) => {
return command;
}
} as Shell;
// const shell = await getShell();
dispatch({ type: 'setShell', payload: shell });
}
init();
}, []);

return (
<ShellContext.Provider
value={shellContext}
>
{children}
</ShellContext.Provider>
);
}
File renamed without changes.
24 changes: 24 additions & 0 deletions src/types/shell.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@

type Message = {
role: 'user' | 'assistant' | 'system';
content: string;
}

type Shell = {
run: (command: string) => string;
}

type ShellStateAction =
| { type: 'addHistory'; payload: Message }
| { type: 'clearHistory' }
| { type: 'setUser'; payload: string }
| { type: 'setHostname'; payload: string }
| { type: 'setShell'; payload: Shell };

type ShellState = {
history: Message [];
user: string;
hostname: string;
shell: Shell
};

0 comments on commit 9156556

Please sign in to comment.