feat(web): complete product workflow implementation
This commit is contained in:
@@ -51,6 +51,9 @@ const FORMAT_EXTENSIONS: Record<IOFormatMatrixFormat, readonly string[]> = {
|
||||
ALEMBIC: [".abc"],
|
||||
};
|
||||
|
||||
/** Formats with a browser file boundary and a native WebEngine command route. */
|
||||
const LOCAL_SCENE_FORMATS: readonly IOFormatMatrixFormat[] = ["GLTF", "GLB", "OBJ", "STL", "PLY"];
|
||||
|
||||
function routeFor(
|
||||
matrix: IOFormatCapabilityMatrixIR,
|
||||
operation: IOFormatMatrixOperation,
|
||||
@@ -60,7 +63,8 @@ function routeFor(
|
||||
.filter((entry) => {
|
||||
const route = entry.operations[operation][execution.toLowerCase() as "local" | "server"];
|
||||
const runtimeStatus = operation === "IMPORT" ? entry.runtimeImportStatus : entry.runtimeExportStatus;
|
||||
return runtimeStatus === "AVAILABLE" && route.status === "READY" && route.execution === execution;
|
||||
return route.status === "READY" && route.execution === execution &&
|
||||
(execution === "SERVER" || (runtimeStatus === "AVAILABLE" && LOCAL_SCENE_FORMATS.includes(entry.format)));
|
||||
})
|
||||
.map((entry) => ({
|
||||
format: entry.format,
|
||||
@@ -70,6 +74,15 @@ function routeFor(
|
||||
}));
|
||||
}
|
||||
|
||||
function browserSceneRoutes(operation: IOFormatMatrixOperation): IOFormatUIRouteIR[] {
|
||||
return LOCAL_SCENE_FORMATS.map((format) => ({
|
||||
format,
|
||||
operation,
|
||||
execution: "LOCAL" as const,
|
||||
extensions: [...FORMAT_EXTENSIONS[format]],
|
||||
}));
|
||||
}
|
||||
|
||||
export function buildIOFormatUIRegistry(matrix: IOFormatCapabilityMatrixIR, parentMatrixSha256: string): IOFormatUIRegistryIR {
|
||||
if (!SHA256.test(parentMatrixSha256)) throw new IOFormatUIGateError("parent matrix SHA-256 is invalid");
|
||||
return {
|
||||
@@ -77,8 +90,8 @@ export function buildIOFormatUIRegistry(matrix: IOFormatCapabilityMatrixIR, pare
|
||||
task: IO_FORMAT_UI_TASK,
|
||||
parentMatrixSha256,
|
||||
projectFileAccept: IO_FORMAT_PROJECT_ACCEPT,
|
||||
importRoutes: routeFor(matrix, "IMPORT", "LOCAL"),
|
||||
exportRoutes: routeFor(matrix, "EXPORT", "LOCAL"),
|
||||
importRoutes: [...browserSceneRoutes("IMPORT"), ...routeFor(matrix, "IMPORT", "SERVER")],
|
||||
exportRoutes: [...browserSceneRoutes("EXPORT"), ...routeFor(matrix, "EXPORT", "SERVER")],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -96,9 +109,9 @@ function record(value: unknown, path: string): Record<string, unknown> {
|
||||
function parseRoute(value: unknown, path: string, operation: IOFormatMatrixOperation): IOFormatUIRouteIR {
|
||||
const input = record(value, path);
|
||||
exactKeys(input, ["format", "operation", "execution", "extensions"], path);
|
||||
if (typeof input.format !== "string" || !(input.format in FORMAT_EXTENSIONS) || input.operation !== operation || input.execution !== "LOCAL") throw new IOFormatUIGateError(`${path} route identity is invalid`);
|
||||
if (typeof input.format !== "string" || !(input.format in FORMAT_EXTENSIONS) || input.operation !== operation || !["LOCAL", "SERVER"].includes(input.execution as string)) throw new IOFormatUIGateError(`${path} route identity is invalid`);
|
||||
if (!Array.isArray(input.extensions) || input.extensions.length !== FORMAT_EXTENSIONS[input.format as IOFormatMatrixFormat].length || input.extensions.some((extension, index) => extension !== FORMAT_EXTENSIONS[input.format as IOFormatMatrixFormat][index])) throw new IOFormatUIGateError(`${path}.extensions are invalid`);
|
||||
return { format: input.format as IOFormatMatrixFormat, operation, execution: "LOCAL", extensions: [...input.extensions as string[]] };
|
||||
return { format: input.format as IOFormatMatrixFormat, operation, execution: input.execution as "LOCAL" | "SERVER", extensions: [...input.extensions as string[]] };
|
||||
}
|
||||
|
||||
export function parseIOFormatUIRegistry(value: unknown): IOFormatUIRegistryIR {
|
||||
@@ -135,11 +148,11 @@ export function filterIOFormatOperatorCommands<T extends { ioFormat?: IOFormatUI
|
||||
return commands.filter((command) => !command.ioFormat || routeMatches(registry, command.ioFormat));
|
||||
}
|
||||
|
||||
export function gateIOFormatFileSelection(fileName: string, registry: IOFormatUIRegistryIR): { status: "READY"; kind: "BLEND" } | { status: "BLOCKED"; code: "IO_FORMAT_UNSUPPORTED"; extension: string } {
|
||||
export function gateIOFormatFileSelection(fileName: string, registry: IOFormatUIRegistryIR): { status: "READY"; kind: "BLEND" } | { status: "READY"; kind: "SCENE"; route: IOFormatUIRouteIR } | { status: "BLOCKED"; code: "IO_FORMAT_UNSUPPORTED"; extension: string } {
|
||||
const extension = fileName.trim().toLowerCase().match(/\.[a-z0-9]+$/)?.[0] ?? "";
|
||||
if (extension === ".blend") return { status: "READY", kind: "BLEND" };
|
||||
const route = registry.importRoutes.find((candidate) => candidate.extensions.includes(extension));
|
||||
if (route) return { status: "BLOCKED", code: "IO_FORMAT_UNSUPPORTED", extension };
|
||||
if (route) return { status: "READY", kind: "SCENE", route };
|
||||
return { status: "BLOCKED", code: "IO_FORMAT_UNSUPPORTED", extension };
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user