54 lines
1.7 KiB
TypeScript
54 lines
1.7 KiB
TypeScript
import React, { useMemo, useSyncExternalStore } from "react";
|
|
import type { AppManifest } from "../kernel/types";
|
|
import { makeOSApi } from "../kernel/sdk";
|
|
import {
|
|
AppErrorBoundary,
|
|
getCompiled,
|
|
} from "../kernel/appRuntime";
|
|
import { onRuntimeVersionChange, runtimeVersionOf } from "../kernel/store";
|
|
|
|
// Renders a single app instance inside a window body.
|
|
// Built-in apps render their component directly; generated apps are compiled.
|
|
export function AppHost({ app, windowId }: { app: AppManifest; windowId: string }) {
|
|
const version = useSyncExternalStore(
|
|
onRuntimeVersionChange,
|
|
() => runtimeVersionOf(app.id)
|
|
);
|
|
const os = useMemo(() => makeOSApi(app.id, windowId), [app.id, windowId]);
|
|
// Only meaningful for generated apps; cheap no-op for built-ins (cached).
|
|
const compiled = useMemo(
|
|
() => (app.builtin ? null : getCompiled(app, version)),
|
|
[app, version]
|
|
);
|
|
|
|
if (app.builtin && app.component) {
|
|
const C = app.component;
|
|
return (
|
|
<AppErrorBoundary appId={app.id} appName={app.name}>
|
|
<C os={os} windowId={windowId} />
|
|
</AppErrorBoundary>
|
|
);
|
|
}
|
|
|
|
// generated app
|
|
if (!compiled) return null;
|
|
if (compiled.error) {
|
|
return (
|
|
<AppErrorBoundary appId={app.id} appName={app.name}>
|
|
<CompileError message={compiled.error} />
|
|
</AppErrorBoundary>
|
|
);
|
|
}
|
|
const Generated = compiled.Component!;
|
|
return (
|
|
<AppErrorBoundary appId={app.id} appName={app.name} key={version}>
|
|
<Generated os={os} />
|
|
</AppErrorBoundary>
|
|
);
|
|
}
|
|
|
|
function CompileError({ message }: { message: string }): React.ReactElement {
|
|
// Thrown so the error boundary picks it up and offers self-heal.
|
|
throw new Error("Compile error: " + message);
|
|
}
|