2.0.0 - Overhaul
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m13s
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m13s
This commit is contained in:
@@ -10,20 +10,20 @@ const { title, description: blurb, pubDate } = post.data;
|
||||
const { slug } = post;
|
||||
---
|
||||
|
||||
<div class="card bg-accent w-full max-w-sm shrink shadow-md">
|
||||
<div class="card bg-accent text-accent-content w-full max-w-sm shrink shadow-md">
|
||||
<div class="card-body break-words">
|
||||
<h2
|
||||
class="card-title text-xl md:text-2xl font-bold justify-center text-center break-words text-base-100"
|
||||
class="card-title text-xl md:text-2xl font-bold justify-center text-center break-words"
|
||||
>
|
||||
{title}
|
||||
</h2>
|
||||
|
||||
<p class="text-center break-words my-4 text-base-100">
|
||||
<p class="text-center break-words my-4">
|
||||
{blurb || "No description available."}
|
||||
</p>
|
||||
|
||||
<div
|
||||
class="flex flex-wrap items-center justify-center text-base-100 opacity-75 gap-2 text-sm mb-4"
|
||||
class="flex flex-wrap items-center justify-center opacity-75 gap-2 text-sm mb-4"
|
||||
>
|
||||
<Icon name="mdi:clock" class="text-xl" />
|
||||
<span>
|
||||
@@ -41,7 +41,7 @@ const { slug } = post;
|
||||
post.data.tags && post.data.tags.length > 0 && (
|
||||
<div class="flex gap-2 flex-wrap mb-4 justify-center">
|
||||
{post.data.tags.map((tag: string) => (
|
||||
<div class="badge badge-primary font-bold text-base-100">
|
||||
<div class="badge badge-primary font-bold">
|
||||
<Icon name="mdi:tag" class="text-lg" />
|
||||
{tag}
|
||||
</div>
|
||||
@@ -53,7 +53,7 @@ const { slug } = post;
|
||||
<div class="card-actions justify-end">
|
||||
<a
|
||||
href={`/post/${slug}`}
|
||||
class="btn btn-circle text-accent"
|
||||
class="btn btn-circle"
|
||||
aria-label={`Read more about ${title}`}
|
||||
>
|
||||
<Icon name="mdi:arrow-right" class="text-lg" />
|
||||
|
||||
@@ -9,15 +9,15 @@ interface Props {
|
||||
const { project } = Astro.props;
|
||||
---
|
||||
|
||||
<div class="card bg-accent w-full max-w-sm shrink shadow-md">
|
||||
<div class="card bg-accent text-accent-content w-full max-w-sm shrink shadow-md">
|
||||
<div class="card-body break-words">
|
||||
<h2
|
||||
class="card-title text-xl md:text-2xl font-bold justify-center text-center break-words text-base-100"
|
||||
class="card-title text-xl md:text-2xl font-bold justify-center text-center break-words"
|
||||
>
|
||||
{project.name}
|
||||
</h2>
|
||||
|
||||
<p class="text-center break-words text-base-100">
|
||||
<p class="text-center break-words">
|
||||
{project.description}
|
||||
</p>
|
||||
|
||||
@@ -26,7 +26,7 @@ const { project } = Astro.props;
|
||||
href={project.link}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="btn btn-circle text-accent"
|
||||
class="btn btn-circle"
|
||||
aria-label={`Visit ${project.name}`}
|
||||
>
|
||||
<Icon name="mdi:link" class="text-lg" />
|
||||
|
||||
@@ -52,7 +52,7 @@ export default function ResumeDownloadButton({
|
||||
class={`btn bg-primary font-bold rounded-full inline-flex items-center gap-2 text-sm sm:text-base ${
|
||||
isLoading
|
||||
? "text-primary border-2 border-primary"
|
||||
: "text-base-100"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
{isLoading ? (
|
||||
|
||||
@@ -162,7 +162,7 @@ export default function ResumeSettingsModal({
|
||||
{/* Floating Settings Button */}
|
||||
<button
|
||||
onClick={openModal}
|
||||
class={`fixed top-4 right-4 z-20 bg-secondary text-base-100 hover:bg-primary btn btn-circle ${className}`}
|
||||
class={`fixed top-4 right-4 z-20 bg-secondary hover:bg-primary btn btn-circle ${className}`}
|
||||
aria-label="Resume Settings"
|
||||
>
|
||||
<Settings class="text-lg" />
|
||||
@@ -175,7 +175,7 @@ export default function ResumeSettingsModal({
|
||||
<h3 class="font-bold text-lg">Resume Generator</h3>
|
||||
<button
|
||||
onClick={closeModal}
|
||||
class="btn btn-circle bg-secondary hover:bg-primary text-base-100"
|
||||
class="btn btn-circle bg-secondary hover:bg-primary"
|
||||
>
|
||||
<X className="text-lg" />
|
||||
</button>
|
||||
@@ -189,10 +189,10 @@ export default function ResumeSettingsModal({
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div class="flex flex-wrap gap-2 mb-6">
|
||||
<button onClick={downloadTemplate} class="btn bg-primary btn-sm font-bold text-base-100">
|
||||
<button onClick={downloadTemplate} class="btn bg-primary btn-sm font-bold">
|
||||
Download Template
|
||||
</button>
|
||||
<button onClick={loadTemplate} class="btn bg-secondary btn-sm font-bold text-base-100">
|
||||
<button onClick={loadTemplate} class="btn bg-secondary btn-sm font-bold">
|
||||
Load Template in Editor
|
||||
</button>
|
||||
</div>
|
||||
@@ -207,7 +207,7 @@ export default function ResumeSettingsModal({
|
||||
role="tab"
|
||||
class={`px-4 py-2 rounded-full text-sm transition-all duration-200 font-bold ${
|
||||
activeTab === "upload"
|
||||
? "bg-primary shadow-sm text-base-100"
|
||||
? "bg-primary shadow-sm"
|
||||
: "text-base-content/70 hover:text-base-content hover:bg-base-200"
|
||||
}`}
|
||||
onClick={() => setActiveTab("upload")}
|
||||
@@ -218,7 +218,7 @@ export default function ResumeSettingsModal({
|
||||
role="tab"
|
||||
class={`px-4 py-2 rounded-full text-sm font-bold transition-all duration-200 ${
|
||||
activeTab === "edit"
|
||||
? "bg-primary font-bold text-base-100 shadow-sm"
|
||||
? "bg-primary font-bold shadow-sm"
|
||||
: "text-base-content/70 hover:text-base-content font-bold hover:bg-base-200"
|
||||
}`}
|
||||
onClick={() => setActiveTab("edit")}
|
||||
|
||||
@@ -29,7 +29,7 @@ export default function ScrollUpButton() {
|
||||
return (
|
||||
<button
|
||||
onClick={scrollToTop}
|
||||
class={`fixed bottom-4 right-4 z-20 bg-secondary hover:bg-primary text-base-100
|
||||
class={`fixed bottom-4 right-4 z-20 bg-secondary hover:bg-primary
|
||||
btn btn-circle transition-all duration-300
|
||||
${
|
||||
isVisible.value
|
||||
|
||||
@@ -10,16 +10,16 @@ const { talk } = Astro.props;
|
||||
---
|
||||
|
||||
<div
|
||||
class="card bg-accent shadow-lg w-full sm:w-[calc(50%-1rem)] md:w-96 min-w-[280px] max-w-sm shrink"
|
||||
class="card bg-accent text-accent-content shadow-lg w-full sm:w-[calc(50%-1rem)] md:w-96 min-w-[280px] max-w-sm shrink"
|
||||
>
|
||||
<div class="card-body p-6 break-words">
|
||||
<h2
|
||||
class="card-title text-xl md:text-2xl font-bold justify-center text-center break-words text-base-100"
|
||||
class="card-title text-xl md:text-2xl font-bold justify-center text-center break-words"
|
||||
>
|
||||
{talk.name}
|
||||
</h2>
|
||||
|
||||
<p class="text-center break-words my-4 text-base-100">
|
||||
<p class="text-center break-words my-4">
|
||||
{talk.description}
|
||||
</p>
|
||||
|
||||
@@ -39,7 +39,7 @@ const { talk } = Astro.props;
|
||||
href={talk.link}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="btn btn-circle text-accent"
|
||||
class="btn btn-circle"
|
||||
aria-label={`Visit ${talk.name}`}
|
||||
>
|
||||
<Icon name="mdi:link" class="text-lg" />
|
||||
|
||||
@@ -1,234 +0,0 @@
|
||||
import { useState, useEffect, useRef } from "preact/hooks";
|
||||
import type { JSX } from "preact";
|
||||
import type { Command } from "../utils/terminal/types";
|
||||
import { buildFileSystem } from "../utils/terminal/fs";
|
||||
import {
|
||||
executeCommand,
|
||||
type CommandContext,
|
||||
} from "../utils/terminal/commands";
|
||||
import {
|
||||
getCompletions,
|
||||
formatOutput,
|
||||
saveCommandToHistory,
|
||||
loadCommandHistory,
|
||||
} from "../utils/terminal/utils";
|
||||
|
||||
const Terminal = () => {
|
||||
const [currentPath, setCurrentPath] = useState("/");
|
||||
const [commandHistory, setCommandHistory] = useState<Command[]>([
|
||||
{
|
||||
input: "",
|
||||
output:
|
||||
'Welcome to Atridad\'s Shell!\nType "help" to see available commands.\n',
|
||||
timestamp: new Date(),
|
||||
path: "/",
|
||||
},
|
||||
]);
|
||||
const [currentInput, setCurrentInput] = useState("");
|
||||
const [historyIndex, setHistoryIndex] = useState(-1);
|
||||
const [fileSystem, setFileSystem] = useState<{ [key: string]: any }>({});
|
||||
const [isTrainRunning, setIsTrainRunning] = useState(false);
|
||||
const [trainPosition, setTrainPosition] = useState(100);
|
||||
const [persistentHistory, setPersistentHistory] = useState<string[]>([]);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const terminalRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (terminalRef.current && !isTrainRunning) {
|
||||
terminalRef.current.scrollTop = terminalRef.current.scrollHeight;
|
||||
}
|
||||
}, [commandHistory, isTrainRunning]);
|
||||
|
||||
// Load command history from localStorage
|
||||
useEffect(() => {
|
||||
const history = loadCommandHistory();
|
||||
setPersistentHistory(history);
|
||||
}, []);
|
||||
|
||||
// Initialize file system
|
||||
useEffect(() => {
|
||||
buildFileSystem().then(setFileSystem);
|
||||
}, []);
|
||||
|
||||
const handleSubmit = (e: JSX.TargetedEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
|
||||
const commandContext: CommandContext = {
|
||||
currentPath,
|
||||
fileSystem,
|
||||
setCurrentPath,
|
||||
setIsTrainRunning,
|
||||
setTrainPosition,
|
||||
};
|
||||
|
||||
const output = executeCommand(currentInput, commandContext);
|
||||
const newCommand: Command = {
|
||||
input: currentInput,
|
||||
output,
|
||||
timestamp: new Date(),
|
||||
path: currentPath,
|
||||
};
|
||||
|
||||
// Save command to persistent history
|
||||
const updatedHistory = saveCommandToHistory(
|
||||
currentInput,
|
||||
persistentHistory,
|
||||
);
|
||||
setPersistentHistory(updatedHistory);
|
||||
|
||||
if (currentInput.trim().toLowerCase() === "clear") {
|
||||
setCommandHistory([]);
|
||||
} else {
|
||||
setCommandHistory((prev: Command[]) => [...prev, newCommand]);
|
||||
}
|
||||
|
||||
setCurrentInput("");
|
||||
setHistoryIndex(-1);
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: JSX.TargetedKeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === "Tab") {
|
||||
e.preventDefault();
|
||||
|
||||
const { completion, replaceFrom } = getCompletions(
|
||||
currentInput,
|
||||
currentPath,
|
||||
fileSystem,
|
||||
);
|
||||
|
||||
if (completion) {
|
||||
const beforeReplacement = currentInput.substring(0, replaceFrom);
|
||||
const newInput = beforeReplacement + completion;
|
||||
setCurrentInput(newInput + (completion.endsWith("/") ? "" : " "));
|
||||
}
|
||||
} else if (e.key === "ArrowUp") {
|
||||
e.preventDefault();
|
||||
if (persistentHistory.length > 0) {
|
||||
const newIndex =
|
||||
historyIndex === -1
|
||||
? persistentHistory.length - 1
|
||||
: Math.max(0, historyIndex - 1);
|
||||
setHistoryIndex(newIndex);
|
||||
setCurrentInput(persistentHistory[newIndex]);
|
||||
}
|
||||
} else if (e.key === "ArrowDown") {
|
||||
e.preventDefault();
|
||||
if (historyIndex !== -1) {
|
||||
const newIndex = Math.min(
|
||||
persistentHistory.length - 1,
|
||||
historyIndex + 1,
|
||||
);
|
||||
if (
|
||||
newIndex === persistentHistory.length - 1 &&
|
||||
historyIndex === newIndex
|
||||
) {
|
||||
setHistoryIndex(-1);
|
||||
setCurrentInput("");
|
||||
} else {
|
||||
setHistoryIndex(newIndex);
|
||||
setCurrentInput(persistentHistory[newIndex]);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-base-100 text-base-content font-mono text-sm h-full flex flex-col rounded-lg border-2 border-primary shadow-2xl relative">
|
||||
<div className="bg-base-200 px-4 py-2 rounded-t-lg border-b border-base-300">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="w-3 h-3 bg-error rounded-full"></div>
|
||||
<div className="w-3 h-3 bg-warning rounded-full"></div>
|
||||
<div className="w-3 h-3 bg-success rounded-full"></div>
|
||||
<span className="ml-4 text-base-content/70 text-xs">
|
||||
guest@atri.dad: {currentPath}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
ref={terminalRef}
|
||||
className={`flex-1 p-4 overflow-y-auto scrollbar-thin scrollbar-thumb-base-300 scrollbar-track-base-100 relative ${
|
||||
isTrainRunning ? "opacity-0" : "opacity-100"
|
||||
}`}
|
||||
onClick={() => !isTrainRunning && inputRef.current?.focus()}
|
||||
>
|
||||
<div className="min-h-full">
|
||||
{commandHistory.map((command: Command, index: number) => (
|
||||
<div key={index} className="mb-2">
|
||||
{command.input && (
|
||||
<div className="flex items-center">
|
||||
<span className="text-primary font-semibold">
|
||||
guest@atri.dad
|
||||
</span>
|
||||
<span className="text-base-content">:</span>
|
||||
<span className="text-secondary font-semibold">
|
||||
{command.path}
|
||||
</span>
|
||||
<span className="text-base-content">$ </span>
|
||||
<span className="text-accent">{command.input}</span>
|
||||
</div>
|
||||
)}
|
||||
{command.output && (
|
||||
<div
|
||||
className="whitespace-pre-wrap text-base-content/80 mt-1"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: formatOutput(command.output),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
|
||||
{!isTrainRunning && (
|
||||
<form onSubmit={handleSubmit} className="flex items-center">
|
||||
<span className="text-primary font-semibold">guest@atri.dad</span>
|
||||
<span className="text-base-content">:</span>
|
||||
<span className="text-secondary font-semibold">
|
||||
{currentPath}
|
||||
</span>
|
||||
<span className="text-base-content">$ </span>
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
value={currentInput}
|
||||
onInput={(e) =>
|
||||
setCurrentInput((e.target as HTMLInputElement).value)
|
||||
}
|
||||
onKeyDown={handleKeyDown}
|
||||
className="flex-1 bg-transparent border-none outline-none text-accent ml-1"
|
||||
spellcheck={false}
|
||||
/>
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Train animation overlay - positioned over the content area but outside the opacity div */}
|
||||
{isTrainRunning && (
|
||||
<div className="absolute inset-x-0 top-16 bottom-0 flex items-center justify-center overflow-hidden pointer-events-none">
|
||||
<div
|
||||
className="text-white font-mono text-xs whitespace-nowrap"
|
||||
style={{
|
||||
transform: `translateX(${trainPosition}%)`,
|
||||
transition: "none",
|
||||
}}
|
||||
>
|
||||
<pre className="leading-none">{`
|
||||
==== ________ ___________
|
||||
_D _| |_______/ \\__I_I_____===__|_________|
|
||||
|(_)--- | H\\________/ | | =|___ ___| _________________
|
||||
/ | | H | | | | ||_| |_|| _| \\_____A
|
||||
| | | H |__--------------------| [___] | =| |
|
||||
| ________|___H__/__|_____/[][]~\\_______| | -| |
|
||||
|/ | |-----------I_____I [][] [] D |=======|____|________________________|_
|
||||
__/ =| o |=-O=====O=====O=====O \\ ____Y___________|__|__________________________|_
|
||||
|/-=|___|= || || || |_____/~\\___/ |_D__D__D_| |_D__D__D_|
|
||||
\\_/ \\__/ \\__/ \\__/ \\__/ \\_/ \\_/ \\_/ \\_/ \\_/`}</pre>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Terminal;
|
||||
Reference in New Issue
Block a user