Initial commit
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
import { useEffect, useState, type ComponentType } from "react";
|
||||
|
||||
import { modules as discoveredModules } from "./.generated/mockup-components";
|
||||
|
||||
type ModuleMap = Record<string, () => Promise<Record<string, unknown>>>;
|
||||
|
||||
function _resolveComponent(
|
||||
mod: Record<string, unknown>,
|
||||
name: string,
|
||||
): ComponentType | undefined {
|
||||
const fns = Object.values(mod).filter(
|
||||
(v) => typeof v === "function",
|
||||
) as ComponentType[];
|
||||
return (
|
||||
(mod.default as ComponentType) ||
|
||||
(mod.Preview as ComponentType) ||
|
||||
(mod[name] as ComponentType) ||
|
||||
fns[fns.length - 1]
|
||||
);
|
||||
}
|
||||
|
||||
function PreviewRenderer({
|
||||
componentPath,
|
||||
modules,
|
||||
}: {
|
||||
componentPath: string;
|
||||
modules: ModuleMap;
|
||||
}) {
|
||||
const [Component, setComponent] = useState<ComponentType | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
|
||||
setComponent(null);
|
||||
setError(null);
|
||||
|
||||
async function loadComponent(): Promise<void> {
|
||||
const key = `./components/mockups/${componentPath}.tsx`;
|
||||
const loader = modules[key];
|
||||
if (!loader) {
|
||||
setError(`No component found at ${componentPath}.tsx`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const mod = await loader();
|
||||
if (cancelled) {
|
||||
return;
|
||||
}
|
||||
const name = componentPath.split("/").pop()!;
|
||||
const comp = _resolveComponent(mod, name);
|
||||
if (!comp) {
|
||||
setError(
|
||||
`No exported React component found in ${componentPath}.tsx\n\nMake sure the file has at least one exported function component.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
setComponent(() => comp);
|
||||
} catch (e) {
|
||||
if (cancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const message = e instanceof Error ? e.message : String(e);
|
||||
setError(`Failed to load preview.\n${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
void loadComponent();
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [componentPath, modules]);
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<pre style={{ color: "red", padding: "2rem", fontFamily: "system-ui" }}>
|
||||
{error}
|
||||
</pre>
|
||||
);
|
||||
}
|
||||
|
||||
if (!Component) return null;
|
||||
|
||||
return <Component />;
|
||||
}
|
||||
|
||||
function getBasePath(): string {
|
||||
return import.meta.env.BASE_URL.replace(/\/$/, "");
|
||||
}
|
||||
|
||||
function getPreviewExamplePath(): string {
|
||||
const basePath = getBasePath();
|
||||
return `${basePath}/preview/ComponentName`;
|
||||
}
|
||||
|
||||
function Gallery() {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 flex items-center justify-center p-8">
|
||||
<div className="text-center max-w-md">
|
||||
<h1 className="text-2xl font-semibold text-gray-900 mb-3">
|
||||
Component Preview Server
|
||||
</h1>
|
||||
<p className="text-gray-500 mb-4">
|
||||
This server renders individual components for the workspace canvas.
|
||||
</p>
|
||||
<p className="text-sm text-gray-400">
|
||||
Access component previews at{" "}
|
||||
<code className="bg-gray-100 px-1.5 py-0.5 rounded text-gray-600">
|
||||
{getPreviewExamplePath()}
|
||||
</code>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function getPreviewPath(): string | null {
|
||||
const basePath = getBasePath();
|
||||
const { pathname } = window.location;
|
||||
const local =
|
||||
basePath && pathname.startsWith(basePath)
|
||||
? pathname.slice(basePath.length) || "/"
|
||||
: pathname;
|
||||
const match = local.match(/^\/preview\/(.+)$/);
|
||||
return match ? match[1] : null;
|
||||
}
|
||||
|
||||
function App() {
|
||||
const previewPath = getPreviewPath();
|
||||
|
||||
if (previewPath) {
|
||||
return (
|
||||
<PreviewRenderer
|
||||
componentPath={previewPath}
|
||||
modules={discoveredModules}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return <Gallery />;
|
||||
}
|
||||
|
||||
export default App;
|
||||
Reference in New Issue
Block a user