fix(logs): fix terminal colors and payload rendering
This commit is contained in:
@@ -44,11 +44,12 @@ export default function LogsPage() {
|
||||
}),
|
||||
});
|
||||
const d = await r.json();
|
||||
setLogs(
|
||||
typeof d.result === "string"
|
||||
? d.result
|
||||
: JSON.stringify(d.result ?? d.error, null, 2),
|
||||
);
|
||||
let parsed = "";
|
||||
try {
|
||||
parsed = JSON.parse(d.result).logs;
|
||||
} catch {}
|
||||
|
||||
setLogs(parsed || d.result || "No logs returned.");
|
||||
} catch {
|
||||
setLogs("Failed to load logs. Is the container running?");
|
||||
} finally {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"use client"
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Children,
|
||||
@@ -10,29 +10,29 @@ import {
|
||||
useState,
|
||||
type ComponentType,
|
||||
type RefAttributes,
|
||||
} from "react"
|
||||
} from "react";
|
||||
import {
|
||||
motion,
|
||||
useInView,
|
||||
type DOMMotionComponents,
|
||||
type HTMLMotionProps,
|
||||
type MotionProps,
|
||||
} from "motion/react"
|
||||
} from "motion/react";
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface SequenceContextValue {
|
||||
completeItem: (index: number) => void
|
||||
activeIndex: number
|
||||
sequenceStarted: boolean
|
||||
completeItem: (index: number) => void;
|
||||
activeIndex: number;
|
||||
sequenceStarted: boolean;
|
||||
}
|
||||
|
||||
const SequenceContext = createContext<SequenceContextValue | null>(null)
|
||||
const SequenceContext = createContext<SequenceContextValue | null>(null);
|
||||
|
||||
const useSequence = () => useContext(SequenceContext)
|
||||
const useSequence = () => useContext(SequenceContext);
|
||||
|
||||
const ItemIndexContext = createContext<number | null>(null)
|
||||
const useItemIndex = () => useContext(ItemIndexContext)
|
||||
const ItemIndexContext = createContext<number | null>(null);
|
||||
const useItemIndex = () => useContext(ItemIndexContext);
|
||||
|
||||
const motionElements = {
|
||||
article: motion.article,
|
||||
@@ -47,21 +47,21 @@ const motionElements = {
|
||||
p: motion.p,
|
||||
section: motion.section,
|
||||
span: motion.span,
|
||||
} as const
|
||||
} as const;
|
||||
|
||||
type MotionElementType = Extract<
|
||||
keyof DOMMotionComponents,
|
||||
keyof typeof motionElements
|
||||
>
|
||||
>;
|
||||
type TerminalTypingMotionComponent = ComponentType<
|
||||
Omit<HTMLMotionProps<"span">, "ref"> & RefAttributes<HTMLElement>
|
||||
>
|
||||
>;
|
||||
|
||||
interface AnimatedSpanProps extends MotionProps {
|
||||
children: React.ReactNode
|
||||
delay?: number
|
||||
className?: string
|
||||
startOnView?: boolean
|
||||
children: React.ReactNode;
|
||||
delay?: number;
|
||||
className?: string;
|
||||
startOnView?: boolean;
|
||||
}
|
||||
|
||||
export const AnimatedSpan = ({
|
||||
@@ -71,25 +71,25 @@ export const AnimatedSpan = ({
|
||||
startOnView = false,
|
||||
...props
|
||||
}: AnimatedSpanProps) => {
|
||||
const elementRef = useRef<HTMLDivElement | null>(null)
|
||||
const elementRef = useRef<HTMLDivElement | null>(null);
|
||||
const isInView = useInView(elementRef as React.RefObject<Element>, {
|
||||
amount: 0.3,
|
||||
once: true,
|
||||
})
|
||||
});
|
||||
|
||||
const sequence = useSequence()
|
||||
const itemIndex = useItemIndex()
|
||||
const [hasStarted, setHasStarted] = useState(false)
|
||||
const sequence = useSequence();
|
||||
const itemIndex = useItemIndex();
|
||||
const [hasStarted, setHasStarted] = useState(false);
|
||||
useEffect(() => {
|
||||
if (!sequence || itemIndex === null) return
|
||||
if (!sequence.sequenceStarted) return
|
||||
if (hasStarted) return
|
||||
if (!sequence || itemIndex === null) return;
|
||||
if (!sequence.sequenceStarted) return;
|
||||
if (hasStarted) return;
|
||||
if (sequence.activeIndex === itemIndex) {
|
||||
setHasStarted(true)
|
||||
setHasStarted(true);
|
||||
}
|
||||
}, [sequence, hasStarted, itemIndex])
|
||||
}, [sequence, hasStarted, itemIndex]);
|
||||
|
||||
const shouldAnimate = sequence ? hasStarted : startOnView ? isInView : true
|
||||
const shouldAnimate = sequence ? hasStarted : startOnView ? isInView : true;
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
@@ -99,24 +99,24 @@ export const AnimatedSpan = ({
|
||||
transition={{ duration: 0.3, delay: sequence ? 0 : delay / 1000 }}
|
||||
className={cn("grid text-sm font-normal tracking-tight", className)}
|
||||
onAnimationComplete={() => {
|
||||
if (!sequence) return
|
||||
if (itemIndex === null) return
|
||||
sequence.completeItem(itemIndex)
|
||||
if (!sequence) return;
|
||||
if (itemIndex === null) return;
|
||||
sequence.completeItem(itemIndex);
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</motion.div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
interface TypingAnimationProps extends Omit<MotionProps, "children"> {
|
||||
children: string
|
||||
className?: string
|
||||
duration?: number
|
||||
delay?: number
|
||||
as?: MotionElementType
|
||||
startOnView?: boolean
|
||||
children: string;
|
||||
className?: string;
|
||||
duration?: number;
|
||||
delay?: number;
|
||||
as?: MotionElementType;
|
||||
startOnView?: boolean;
|
||||
}
|
||||
|
||||
export const TypingAnimation = ({
|
||||
@@ -129,52 +129,52 @@ export const TypingAnimation = ({
|
||||
...props
|
||||
}: TypingAnimationProps) => {
|
||||
if (typeof children !== "string") {
|
||||
throw new Error("TypingAnimation: children must be a string. Received:")
|
||||
throw new Error("TypingAnimation: children must be a string. Received:");
|
||||
}
|
||||
|
||||
const MotionComponent = motionElements[
|
||||
Component
|
||||
] as TerminalTypingMotionComponent
|
||||
] as TerminalTypingMotionComponent;
|
||||
|
||||
const [displayedText, setDisplayedText] = useState<string>("")
|
||||
const [started, setStarted] = useState(false)
|
||||
const elementRef = useRef<HTMLElement | null>(null)
|
||||
const [displayedText, setDisplayedText] = useState<string>("");
|
||||
const [started, setStarted] = useState(false);
|
||||
const elementRef = useRef<HTMLElement | null>(null);
|
||||
const isInView = useInView(elementRef as React.RefObject<Element>, {
|
||||
amount: 0.3,
|
||||
once: true,
|
||||
})
|
||||
});
|
||||
|
||||
const sequence = useSequence()
|
||||
const itemIndex = useItemIndex()
|
||||
const hasSequence = sequence !== null
|
||||
const sequenceStarted = sequence?.sequenceStarted ?? false
|
||||
const sequenceActiveIndex = sequence?.activeIndex ?? null
|
||||
const sequence = useSequence();
|
||||
const itemIndex = useItemIndex();
|
||||
const hasSequence = sequence !== null;
|
||||
const sequenceStarted = sequence?.sequenceStarted ?? false;
|
||||
const sequenceActiveIndex = sequence?.activeIndex ?? null;
|
||||
const sequenceCompleteItemRef = useRef<
|
||||
SequenceContextValue["completeItem"] | null
|
||||
>(null)
|
||||
const sequenceItemIndexRef = useRef<number | null>(null)
|
||||
>(null);
|
||||
const sequenceItemIndexRef = useRef<number | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
sequenceCompleteItemRef.current = sequence?.completeItem ?? null
|
||||
sequenceItemIndexRef.current = itemIndex
|
||||
}, [sequence?.completeItem, itemIndex])
|
||||
sequenceCompleteItemRef.current = sequence?.completeItem ?? null;
|
||||
sequenceItemIndexRef.current = itemIndex;
|
||||
}, [sequence?.completeItem, itemIndex]);
|
||||
|
||||
useEffect(() => {
|
||||
let startTimeout: ReturnType<typeof setTimeout> | null = null
|
||||
let startTimeout: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
if (hasSequence && itemIndex !== null) {
|
||||
if (sequenceStarted && !started && sequenceActiveIndex === itemIndex) {
|
||||
setStarted(true)
|
||||
setStarted(true);
|
||||
}
|
||||
} else if (!startOnView || isInView) {
|
||||
startTimeout = setTimeout(() => setStarted(true), delay)
|
||||
startTimeout = setTimeout(() => setStarted(true), delay);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (startTimeout !== null) {
|
||||
clearTimeout(startTimeout)
|
||||
}
|
||||
clearTimeout(startTimeout);
|
||||
}
|
||||
};
|
||||
}, [
|
||||
delay,
|
||||
startOnView,
|
||||
@@ -184,36 +184,36 @@ export const TypingAnimation = ({
|
||||
sequenceActiveIndex,
|
||||
sequenceStarted,
|
||||
itemIndex,
|
||||
])
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
let typingEffect: ReturnType<typeof setInterval> | null = null
|
||||
let typingEffect: ReturnType<typeof setInterval> | null = null;
|
||||
|
||||
if (started) {
|
||||
let i = 0
|
||||
let i = 0;
|
||||
typingEffect = setInterval(() => {
|
||||
if (i < children.length) {
|
||||
setDisplayedText(children.substring(0, i + 1))
|
||||
i++
|
||||
setDisplayedText(children.substring(0, i + 1));
|
||||
i++;
|
||||
} else {
|
||||
if (typingEffect !== null) {
|
||||
clearInterval(typingEffect)
|
||||
clearInterval(typingEffect);
|
||||
}
|
||||
const completeItem = sequenceCompleteItemRef.current
|
||||
const currentItemIndex = sequenceItemIndexRef.current
|
||||
const completeItem = sequenceCompleteItemRef.current;
|
||||
const currentItemIndex = sequenceItemIndexRef.current;
|
||||
if (completeItem && currentItemIndex !== null) {
|
||||
completeItem(currentItemIndex)
|
||||
completeItem(currentItemIndex);
|
||||
}
|
||||
}
|
||||
}, duration)
|
||||
}, duration);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (typingEffect !== null) {
|
||||
clearInterval(typingEffect)
|
||||
clearInterval(typingEffect);
|
||||
}
|
||||
}
|
||||
}, [children, duration, started])
|
||||
};
|
||||
}, [children, duration, started]);
|
||||
|
||||
return (
|
||||
<MotionComponent
|
||||
@@ -223,14 +223,14 @@ export const TypingAnimation = ({
|
||||
>
|
||||
{displayedText}
|
||||
</MotionComponent>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
interface TerminalProps {
|
||||
children: React.ReactNode
|
||||
className?: string
|
||||
sequence?: boolean
|
||||
startOnView?: boolean
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
sequence?: boolean;
|
||||
startOnView?: boolean;
|
||||
}
|
||||
|
||||
export const Terminal = ({
|
||||
@@ -239,45 +239,47 @@ export const Terminal = ({
|
||||
sequence = true,
|
||||
startOnView = true,
|
||||
}: TerminalProps) => {
|
||||
const containerRef = useRef<HTMLDivElement | null>(null)
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
const isInView = useInView(containerRef as React.RefObject<Element>, {
|
||||
amount: 0.3,
|
||||
once: true,
|
||||
})
|
||||
});
|
||||
|
||||
const [activeIndex, setActiveIndex] = useState(0)
|
||||
const sequenceHasStarted = sequence ? !startOnView || isInView : false
|
||||
const [activeIndex, setActiveIndex] = useState(0);
|
||||
const sequenceHasStarted = sequence ? !startOnView || isInView : false;
|
||||
|
||||
const contextValue = useMemo<SequenceContextValue | null>(() => {
|
||||
if (!sequence) return null
|
||||
if (!sequence) return null;
|
||||
return {
|
||||
completeItem: (index: number) => {
|
||||
setActiveIndex((current) => (index === current ? current + 1 : current))
|
||||
setActiveIndex((current) =>
|
||||
index === current ? current + 1 : current,
|
||||
);
|
||||
},
|
||||
activeIndex,
|
||||
sequenceStarted: sequenceHasStarted,
|
||||
}
|
||||
}, [sequence, activeIndex, sequenceHasStarted])
|
||||
};
|
||||
}, [sequence, activeIndex, sequenceHasStarted]);
|
||||
|
||||
const wrappedChildren = useMemo(() => {
|
||||
if (!sequence) return children
|
||||
const array = Children.toArray(children)
|
||||
if (!sequence) return children;
|
||||
const array = Children.toArray(children);
|
||||
return array.map((child, index) => (
|
||||
<ItemIndexContext.Provider key={index} value={index}>
|
||||
{child as React.ReactNode}
|
||||
</ItemIndexContext.Provider>
|
||||
))
|
||||
}, [children, sequence])
|
||||
));
|
||||
}, [children, sequence]);
|
||||
|
||||
const content = (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={cn(
|
||||
"border-border bg-background z-0 h-full max-h-100 w-full max-w-lg rounded-xl border",
|
||||
className
|
||||
"z-0 h-full max-h-100 w-full max-w-lg rounded-xl border border-[#27272a] bg-[#0a0a0a] text-white",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<div className="border-border flex flex-col gap-y-2 border-b p-4">
|
||||
<div className="flex flex-col gap-y-2 border-b border-[#27272a] p-4">
|
||||
<div className="flex flex-row gap-x-2">
|
||||
<div className="h-2 w-2 rounded-full bg-red-500"></div>
|
||||
<div className="h-2 w-2 rounded-full bg-yellow-500"></div>
|
||||
@@ -288,13 +290,13 @@ export const Terminal = ({
|
||||
<code className="grid gap-y-1 overflow-auto">{wrappedChildren}</code>
|
||||
</pre>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
|
||||
if (!sequence) return content
|
||||
if (!sequence) return content;
|
||||
|
||||
return (
|
||||
<SequenceContext.Provider value={contextValue}>
|
||||
{content}
|
||||
</SequenceContext.Provider>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user