Files
webstudio/apps/builder/app/services/api-runtime.server.ts
Oleg Isonen 878d6f1de1 feat: Add asset folders to asset manager and improve asset manager UX (#5892)
Closes https://github.com/webstudio-is/webstudio/issues/5877
Closes https://github.com/webstudio-is/webstudio/issues/4775


- Organize assets into nested folders.
- Navigate folders with breadcrumbs and Back controls.
- Select and manage multiple assets and folders at once.
- Drag, copy, cut, paste, duplicate, move, and delete items.
- Use context menus and familiar keyboard shortcuts for common actions.
- Search and sort assets and folders, with folders grouped first.
- Upload files directly into the active folder.
- Manage folder placement from asset and folder settings.
- Automate asset and folder management through MCP.
2026-07-17 23:39:14 +02:00

71 lines
2.2 KiB
TypeScript

import type { CompactBuild } from "@webstudio-is/project-build";
import type { RuntimeOperationId } from "@webstudio-is/project-build/contracts";
import { executeBuilderRuntimeOperation } from "@webstudio-is/project-build/runtime";
import { BuilderRuntimeError } from "@webstudio-is/project-build/runtime";
import { builderRuntimeContext } from "@webstudio-is/project-build/runtime";
import { type BuilderRuntimeMutation } from "@webstudio-is/project-build/runtime";
import type { BuilderState } from "@webstudio-is/project-build/state";
import { createBuilderStateFromCompactBuild } from "@webstudio-is/project-build/state";
import { type Asset, type AssetFolder } from "@webstudio-is/sdk";
import { throwApiError } from "./api-errors.server";
export const createBuilderRuntimeState = (
build: CompactBuild,
assets?: Asset[],
assetFolders?: AssetFolder[]
): BuilderState =>
createBuilderStateFromCompactBuild(
assets === undefined && assetFolders === undefined
? build
: { ...build, assets, assetFolders }
);
export const executeApiRuntimeOperation = async <Result>({
id,
build,
assets,
assetFolders,
input,
}: {
id: RuntimeOperationId;
build: CompactBuild;
assets?: Asset[];
assetFolders?: AssetFolder[];
input: unknown;
}): Promise<Result> => {
try {
return await executeBuilderRuntimeOperation<Result>({
id,
state: createBuilderRuntimeState(build, assets, assetFolders),
input,
context: {
createId: builderRuntimeContext.createId,
projectId: build.projectId,
projectVersion: build.version,
},
});
} catch (error) {
if (error instanceof BuilderRuntimeError) {
return throwApiError(
error.code === "INVALID_INPUT" ? "BAD_REQUEST" : error.code,
error.message,
{
webstudioCode: error.code,
issues: error.issues,
}
);
}
throw error;
}
};
export const executeApiRuntimeMutation = <
Result extends Record<string, unknown> = Record<string, unknown>,
>(args: {
id: RuntimeOperationId;
build: CompactBuild;
assets?: Asset[];
assetFolders?: AssetFolder[];
input: unknown;
}) => executeApiRuntimeOperation<BuilderRuntimeMutation<Result>>(args);