Add editable project floors and rooms

This commit is contained in:
2026-07-31 14:46:06 +02:00
parent 1e3c721cfa
commit 1eed19ef6b
15 changed files with 743 additions and 80 deletions
@@ -3,8 +3,10 @@ import type { SerializedProjectCommand } from "./project-command.model.js";
export const projectFloorInsertCommandType = "project-floor.insert" as const;
export const projectFloorDeleteCommandType = "project-floor.delete" as const;
export const projectFloorUpdateCommandType = "project-floor.update" as const;
export const projectRoomInsertCommandType = "project-room.insert" as const;
export const projectRoomDeleteCommandType = "project-room.delete" as const;
export const projectRoomUpdateCommandType = "project-room.update" as const;
export const projectLocationStructureCommandSchemaVersion = 1 as const;
export interface ProjectFloorSnapshot {
@@ -30,6 +32,16 @@ interface ProjectRoomStructureCommandPayload {
room: ProjectRoomSnapshot;
}
interface ProjectFloorUpdateCommandPayload {
expected: ProjectFloorSnapshot;
target: ProjectFloorSnapshot;
}
interface ProjectRoomUpdateCommandPayload {
expected: ProjectRoomSnapshot;
target: ProjectRoomSnapshot;
}
export interface ProjectFloorInsertProjectCommand
extends SerializedProjectCommand<ProjectFloorStructureCommandPayload> {
schemaVersion: typeof projectLocationStructureCommandSchemaVersion;
@@ -42,6 +54,12 @@ export interface ProjectFloorDeleteProjectCommand
type: typeof projectFloorDeleteCommandType;
}
export interface ProjectFloorUpdateProjectCommand
extends SerializedProjectCommand<ProjectFloorUpdateCommandPayload> {
schemaVersion: typeof projectLocationStructureCommandSchemaVersion;
type: typeof projectFloorUpdateCommandType;
}
export interface ProjectRoomInsertProjectCommand
extends SerializedProjectCommand<ProjectRoomStructureCommandPayload> {
schemaVersion: typeof projectLocationStructureCommandSchemaVersion;
@@ -54,11 +72,19 @@ export interface ProjectRoomDeleteProjectCommand
type: typeof projectRoomDeleteCommandType;
}
export interface ProjectRoomUpdateProjectCommand
extends SerializedProjectCommand<ProjectRoomUpdateCommandPayload> {
schemaVersion: typeof projectLocationStructureCommandSchemaVersion;
type: typeof projectRoomUpdateCommandType;
}
export type ProjectLocationStructureProjectCommand =
| ProjectFloorInsertProjectCommand
| ProjectFloorDeleteProjectCommand
| ProjectFloorUpdateProjectCommand
| ProjectRoomInsertProjectCommand
| ProjectRoomDeleteProjectCommand;
| ProjectRoomDeleteProjectCommand
| ProjectRoomUpdateProjectCommand;
export function createProjectFloorSnapshot(
projectId: string,
@@ -138,6 +164,30 @@ export function createProjectRoomDeleteProjectCommand(
};
}
export function createProjectFloorUpdateProjectCommand(
expected: ProjectFloorSnapshot,
target: ProjectFloorSnapshot
): ProjectFloorUpdateProjectCommand {
assertMatchingFloorUpdate(expected, target);
return {
schemaVersion: projectLocationStructureCommandSchemaVersion,
type: projectFloorUpdateCommandType,
payload: { expected, target },
};
}
export function createProjectRoomUpdateProjectCommand(
expected: ProjectRoomSnapshot,
target: ProjectRoomSnapshot
): ProjectRoomUpdateProjectCommand {
assertMatchingRoomUpdate(expected, target);
return {
schemaVersion: projectLocationStructureCommandSchemaVersion,
type: projectRoomUpdateCommandType,
payload: { expected, target },
};
}
export function assertProjectFloorInsertProjectCommand(
command: SerializedProjectCommand<unknown>
): asserts command is ProjectFloorInsertProjectCommand {
@@ -174,6 +224,59 @@ export function assertProjectRoomDeleteProjectCommand(
);
}
export function assertProjectFloorUpdateProjectCommand(
command: SerializedProjectCommand<unknown>
): asserts command is ProjectFloorUpdateProjectCommand {
if (
command.schemaVersion !== projectLocationStructureCommandSchemaVersion ||
command.type !== projectFloorUpdateCommandType ||
!isPlainObject(command.payload) ||
Object.keys(command.payload).length !== 2
) {
throw new Error("Unsupported project-floor update command.");
}
assertMatchingFloorUpdate(command.payload.expected, command.payload.target);
}
export function assertProjectRoomUpdateProjectCommand(
command: SerializedProjectCommand<unknown>
): asserts command is ProjectRoomUpdateProjectCommand {
if (
command.schemaVersion !== projectLocationStructureCommandSchemaVersion ||
command.type !== projectRoomUpdateCommandType ||
!isPlainObject(command.payload) ||
Object.keys(command.payload).length !== 2
) {
throw new Error("Unsupported project-room update command.");
}
assertMatchingRoomUpdate(command.payload.expected, command.payload.target);
}
function assertMatchingFloorUpdate(expected: unknown, target: unknown) {
assertProjectFloorSnapshot(expected);
assertProjectFloorSnapshot(target);
if (
expected.id !== target.id ||
expected.projectId !== target.projectId ||
expected.sortOrder !== target.sortOrder ||
expected.name === target.name
) {
throw new Error("Project-floor update must change only the name.");
}
}
function assertMatchingRoomUpdate(expected: unknown, target: unknown) {
assertProjectRoomSnapshot(expected);
assertProjectRoomSnapshot(target);
if (
expected.id !== target.id ||
expected.projectId !== target.projectId ||
sameSnapshot(expected, target)
) {
throw new Error("Project-room update must change room values.");
}
}
export function assertProjectFloorSnapshot(
floor: unknown
): asserts floor is ProjectFloorSnapshot {
@@ -257,3 +360,9 @@ function assertNormalizedNonEmptyString(
function isPlainObject(value: unknown): value is Record<string, unknown> {
return value !== null && typeof value === "object" && !Array.isArray(value);
}
function sameSnapshot(left: object, right: object) {
return Object.entries(left).every(
([key, value]) => (right as Record<string, unknown>)[key] === value
);
}
@@ -72,12 +72,16 @@ import {
import {
assertProjectFloorDeleteProjectCommand,
assertProjectFloorInsertProjectCommand,
assertProjectFloorUpdateProjectCommand,
assertProjectRoomDeleteProjectCommand,
assertProjectRoomInsertProjectCommand,
assertProjectRoomUpdateProjectCommand,
projectFloorDeleteCommandType,
projectFloorInsertCommandType,
projectFloorUpdateCommandType,
projectRoomDeleteCommandType,
projectRoomInsertCommandType,
projectRoomUpdateCommandType,
} from "../models/project-location-structure-project-command.model.js";
import {
assertProjectStateRestoreCommand,
@@ -470,6 +474,10 @@ export class ProjectCommandService implements ProjectCommandExecutor {
command: input.command,
}).revision;
}
case projectFloorUpdateCommandType: {
assertProjectFloorUpdateProjectCommand(input.command);
return this.projectLocationStructureStore.execute({ ...input, command: input.command }).revision;
}
case projectRoomInsertCommandType: {
assertProjectRoomInsertProjectCommand(input.command);
return this.projectLocationStructureStore.execute({
@@ -484,6 +492,10 @@ export class ProjectCommandService implements ProjectCommandExecutor {
command: input.command,
}).revision;
}
case projectRoomUpdateCommandType: {
assertProjectRoomUpdateProjectCommand(input.command);
return this.projectLocationStructureStore.execute({ ...input, command: input.command }).revision;
}
case circuitSectionReorderCommandType: {
assertCircuitSectionReorderProjectCommand(input.command);
return this.circuitSectionReorderStore.execute({