2025-10-08 18:10:07 -06:00

130 lines
3.3 KiB
TypeScript

"use client";
import { useEffect, useRef, useState } from "react";
export type JsDosPlayerProps = {
zipUrl?: string;
className?: string;
};
export type JsDosHandle = {
stop: () => void;
};
interface DosInstance {
stop(): void;
}
export function JsDosPlayer({ zipUrl, className = "" }: JsDosPlayerProps) {
const containerRef = useRef<HTMLDivElement>(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const dosInstanceRef = useRef<DosInstance | null>(null);
const ensureJsDosCss = () => {
if (typeof document === "undefined") return;
if (!document.querySelector('link[data-jsdos]')) {
const link = document.createElement("link");
link.rel = "stylesheet";
link.href = "https://v8.js-dos.com/v7/js-dos.css";
link.setAttribute("data-jsdos", "true");
document.head.appendChild(link);
}
};
useEffect(() => {
if (!containerRef.current || !zipUrl) return;
let isMounted = true;
const loadEmulator = async () => {
try {
setIsLoading(true);
setError(null);
// Dynamically import js-dos at runtime (CSS injected via CDN)
ensureJsDosCss();
const jsDosModule = await import("js-dos");
const { Dos } = jsDosModule;
if (!isMounted || !containerRef.current) return;
// Create DOS instance
const dos = Dos(containerRef.current);
dosInstanceRef.current = dos;
// Run the provided archive
await dos.run(zipUrl);
if (isMounted) {
setIsLoading(false);
}
} catch (err) {
console.error("Failed to load js-dos:", err);
if (isMounted) {
setError(err instanceof Error ? err.message : "Failed to load emulator");
setIsLoading(false);
}
}
};
loadEmulator();
return () => {
isMounted = false;
// Cleanup DOS instance
if (dosInstanceRef.current) {
try {
dosInstanceRef.current.stop();
} catch (err) {
console.error("Error stopping DOS instance:", err);
}
dosInstanceRef.current = null;
}
};
}, [zipUrl]);
const handleStop = () => {
if (dosInstanceRef.current) {
try {
dosInstanceRef.current.stop();
dosInstanceRef.current = null;
} catch (err) {
console.error("Error stopping DOS instance:", err);
}
}
};
if (!zipUrl) {
return (
<div className={`flex items-center justify-center p-8 text-neutral-400 ${className}`}>
<p>No archive URL provided</p>
</div>
);
}
if (error) {
return (
<div className={`flex flex-col items-center justify-center p-8 text-neutral-400 ${className}`}>
<p className="text-red-400 mb-4">Error: {error}</p>
<p className="text-sm">Please check the archive URL or try a different file.</p>
</div>
);
}
return (
<div className={`relative ${className}`}>
{isLoading && (
<div className="absolute inset-0 flex items-center justify-center bg-black/50 z-10">
<div className="text-neutral-200">Loading emulator...</div>
</div>
)}
<div
ref={containerRef}
className="w-full h-full min-h-[400px] bg-black rounded"
style={{ minHeight: "400px" }}
/>
</div>
);
}