Complete Revit object editor assignments
This commit is contained in:
parent
4489cd76f0
commit
77d775fde1
25 changed files with 853 additions and 77 deletions
|
|
@ -1,4 +1,11 @@
|
|||
import type { SerializedProjectCommand } from "./project-command.model.js";
|
||||
import type { ExternalModelObjectSnapshot } from "../../external-model/domain/external-model-contracts.js";
|
||||
import { parseExternalModelStateSnapshot } from "./project-state-snapshot.model.js";
|
||||
|
||||
export interface ExternalObjectLinkSnapshotTransition {
|
||||
expected: ExternalModelObjectSnapshot;
|
||||
target: ExternalModelObjectSnapshot;
|
||||
}
|
||||
|
||||
export const circuitDeviceRowInsertCommandType =
|
||||
"circuit-device-row.insert" as const;
|
||||
|
|
@ -32,11 +39,13 @@ export interface CircuitDeviceRowSnapshot {
|
|||
|
||||
export interface CircuitDeviceRowInsertCommandPayload {
|
||||
row: CircuitDeviceRowSnapshot;
|
||||
externalObjects?: ExternalObjectLinkSnapshotTransition[];
|
||||
}
|
||||
|
||||
export interface CircuitDeviceRowDeleteCommandPayload {
|
||||
rowId: string;
|
||||
expectedCircuitId: string;
|
||||
externalObjects?: ExternalObjectLinkSnapshotTransition[];
|
||||
}
|
||||
|
||||
export interface CircuitDeviceRowInsertProjectCommand
|
||||
|
|
@ -56,7 +65,8 @@ export type CircuitDeviceRowStructureProjectCommand =
|
|||
| CircuitDeviceRowDeleteProjectCommand;
|
||||
|
||||
export function createCircuitDeviceRowInsertProjectCommand(
|
||||
row: CircuitDeviceRowSnapshot
|
||||
row: CircuitDeviceRowSnapshot,
|
||||
externalObjects?: ExternalObjectLinkSnapshotTransition[]
|
||||
): CircuitDeviceRowInsertProjectCommand {
|
||||
const normalizedRow = {
|
||||
...row,
|
||||
|
|
@ -65,7 +75,7 @@ export function createCircuitDeviceRowInsertProjectCommand(
|
|||
const command: CircuitDeviceRowInsertProjectCommand = {
|
||||
schemaVersion: circuitDeviceRowStructureCommandSchemaVersion,
|
||||
type: circuitDeviceRowInsertCommandType,
|
||||
payload: { row: normalizedRow },
|
||||
payload: { row: normalizedRow, ...(externalObjects?.length ? { externalObjects } : {}) },
|
||||
};
|
||||
assertCircuitDeviceRowInsertProjectCommand(command);
|
||||
return command;
|
||||
|
|
@ -73,12 +83,13 @@ export function createCircuitDeviceRowInsertProjectCommand(
|
|||
|
||||
export function createCircuitDeviceRowDeleteProjectCommand(
|
||||
rowId: string,
|
||||
expectedCircuitId: string
|
||||
expectedCircuitId: string,
|
||||
externalObjects?: ExternalObjectLinkSnapshotTransition[]
|
||||
): CircuitDeviceRowDeleteProjectCommand {
|
||||
const command: CircuitDeviceRowDeleteProjectCommand = {
|
||||
schemaVersion: circuitDeviceRowStructureCommandSchemaVersion,
|
||||
type: circuitDeviceRowDeleteCommandType,
|
||||
payload: { rowId, expectedCircuitId },
|
||||
payload: { rowId, expectedCircuitId, ...(externalObjects?.length ? { externalObjects } : {}) },
|
||||
};
|
||||
assertCircuitDeviceRowDeleteProjectCommand(command);
|
||||
return command;
|
||||
|
|
@ -143,6 +154,7 @@ export function assertCircuitDeviceRowInsertProjectCommand(
|
|||
throw new Error("row.cosPhi must be positive or null.");
|
||||
}
|
||||
}
|
||||
assertExternalObjectLinkTransitions(command.payload.externalObjects);
|
||||
}
|
||||
|
||||
export function assertCircuitDeviceRowDeleteProjectCommand(
|
||||
|
|
@ -163,6 +175,41 @@ export function assertCircuitDeviceRowDeleteProjectCommand(
|
|||
command.payload.expectedCircuitId,
|
||||
"expectedCircuitId"
|
||||
);
|
||||
assertExternalObjectLinkTransitions(command.payload.externalObjects);
|
||||
}
|
||||
|
||||
export function assertExternalObjectLinkTransitions(value: unknown) {
|
||||
if (value === undefined) return;
|
||||
if (!Array.isArray(value)) throw new Error("externalObjects must be an array.");
|
||||
const ids = new Set<string>();
|
||||
for (const transition of value) {
|
||||
if (!isPlainObject(transition) || !isPlainObject(transition.expected) || !isPlainObject(transition.target)) {
|
||||
throw new Error("Invalid external object link transition.");
|
||||
}
|
||||
const expectedId = transition.expected.id;
|
||||
const targetId = transition.target.id;
|
||||
if (typeof expectedId !== "string" || expectedId !== targetId || ids.has(expectedId)) {
|
||||
throw new Error("External object link transition has mismatched or duplicate ids.");
|
||||
}
|
||||
ids.add(expectedId);
|
||||
const expectedRowId = transition.expected.circuitDeviceRowId;
|
||||
const targetRowId = transition.target.circuitDeviceRowId;
|
||||
if ((expectedRowId !== null && typeof expectedRowId !== "string") || (targetRowId !== null && typeof targetRowId !== "string")) {
|
||||
throw new Error("Invalid external object row link.");
|
||||
}
|
||||
const parsed = parseExternalModelStateSnapshot({ source: null, importBatches: [], roomMappings: [], objects: [transition.expected, transition.target] }).objects;
|
||||
const expectedRest = { ...parsed[0], circuitDeviceRowId: null };
|
||||
const targetRest = { ...parsed[1], circuitDeviceRowId: null };
|
||||
if (canonicalJson(expectedRest) !== canonicalJson(targetRest) || expectedRowId === targetRowId) {
|
||||
throw new Error("External object transition may only change the row link.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function canonicalJson(value: unknown): string {
|
||||
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
|
||||
if (isPlainObject(value)) return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`).join(",")}}`;
|
||||
return JSON.stringify(value) ?? "null";
|
||||
}
|
||||
|
||||
function assertNonEmptyString(value: unknown, field: string) {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import {
|
|||
type CircuitGroupSubtreeSnapshot,
|
||||
} from "./circuit-group-subtree-snapshot.model.js";
|
||||
import type { SerializedProjectCommand } from "./project-command.model.js";
|
||||
import { assertExternalObjectLinkTransitions, type ExternalObjectLinkSnapshotTransition } from "./circuit-device-row-structure-project-command.model.js";
|
||||
|
||||
export const circuitGroupDeleteSubtreeCommandType =
|
||||
"circuit-group.delete-subtree" as const;
|
||||
|
|
@ -12,6 +13,7 @@ export const circuitGroupSubtreeCommandSchemaVersion = 1 as const;
|
|||
|
||||
interface Payload {
|
||||
snapshot: CircuitGroupSubtreeSnapshot;
|
||||
externalObjects?: ExternalObjectLinkSnapshotTransition[];
|
||||
}
|
||||
|
||||
export interface CircuitGroupDeleteSubtreeProjectCommand
|
||||
|
|
@ -31,24 +33,26 @@ export type CircuitGroupSubtreeProjectCommand =
|
|||
| CircuitGroupRestoreSubtreeProjectCommand;
|
||||
|
||||
export function createCircuitGroupDeleteSubtreeProjectCommand(
|
||||
snapshot: CircuitGroupSubtreeSnapshot
|
||||
snapshot: CircuitGroupSubtreeSnapshot,
|
||||
externalObjects?: ExternalObjectLinkSnapshotTransition[]
|
||||
): CircuitGroupDeleteSubtreeProjectCommand {
|
||||
const command: CircuitGroupDeleteSubtreeProjectCommand = {
|
||||
schemaVersion: circuitGroupSubtreeCommandSchemaVersion,
|
||||
type: circuitGroupDeleteSubtreeCommandType,
|
||||
payload: { snapshot },
|
||||
payload: { snapshot, ...(externalObjects?.length ? { externalObjects } : {}) },
|
||||
};
|
||||
assertCircuitGroupDeleteSubtreeProjectCommand(command);
|
||||
return command;
|
||||
}
|
||||
|
||||
export function createCircuitGroupRestoreSubtreeProjectCommand(
|
||||
snapshot: CircuitGroupSubtreeSnapshot
|
||||
snapshot: CircuitGroupSubtreeSnapshot,
|
||||
externalObjects?: ExternalObjectLinkSnapshotTransition[]
|
||||
): CircuitGroupRestoreSubtreeProjectCommand {
|
||||
const command: CircuitGroupRestoreSubtreeProjectCommand = {
|
||||
schemaVersion: circuitGroupSubtreeCommandSchemaVersion,
|
||||
type: circuitGroupRestoreSubtreeCommandType,
|
||||
payload: { snapshot },
|
||||
payload: { snapshot, ...(externalObjects?.length ? { externalObjects } : {}) },
|
||||
};
|
||||
assertCircuitGroupRestoreSubtreeProjectCommand(command);
|
||||
return command;
|
||||
|
|
@ -76,11 +80,12 @@ function assertCommand(
|
|||
command.schemaVersion !== circuitGroupSubtreeCommandSchemaVersion ||
|
||||
command.type !== type ||
|
||||
!isPlainObject(command.payload) ||
|
||||
Object.keys(command.payload).length !== 1
|
||||
Object.keys(command.payload).some((key) => key !== "snapshot" && key !== "externalObjects")
|
||||
) {
|
||||
throw new Error("Unsupported circuit-group subtree command.");
|
||||
}
|
||||
assertCircuitGroupSubtreeSnapshot(command.payload.snapshot);
|
||||
assertExternalObjectLinkTransitions(command.payload.externalObjects);
|
||||
}
|
||||
|
||||
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ import {
|
|||
circuitDeviceRowInsertCommandType,
|
||||
circuitDeviceRowStructureCommandSchemaVersion,
|
||||
type CircuitDeviceRowSnapshot,
|
||||
type ExternalObjectLinkSnapshotTransition,
|
||||
assertExternalObjectLinkTransitions,
|
||||
} from "./circuit-device-row-structure-project-command.model.js";
|
||||
import type { SerializedProjectCommand } from "./project-command.model.js";
|
||||
import {
|
||||
|
|
@ -37,11 +39,13 @@ export interface CircuitSnapshot {
|
|||
|
||||
export interface CircuitInsertCommandPayload {
|
||||
circuit: CircuitSnapshot;
|
||||
externalObjects?: ExternalObjectLinkSnapshotTransition[];
|
||||
}
|
||||
|
||||
export interface CircuitDeleteCommandPayload {
|
||||
circuitId: string;
|
||||
expectedCircuitListId: string;
|
||||
externalObjects?: ExternalObjectLinkSnapshotTransition[];
|
||||
}
|
||||
|
||||
export interface CircuitInsertProjectCommand
|
||||
|
|
@ -61,12 +65,13 @@ export type CircuitStructureProjectCommand =
|
|||
| CircuitDeleteProjectCommand;
|
||||
|
||||
export function createCircuitInsertProjectCommand(
|
||||
circuit: CircuitSnapshot
|
||||
circuit: CircuitSnapshot,
|
||||
externalObjects?: ExternalObjectLinkSnapshotTransition[]
|
||||
): CircuitInsertProjectCommand {
|
||||
const command: CircuitInsertProjectCommand = {
|
||||
schemaVersion: circuitStructureCommandSchemaVersion,
|
||||
type: circuitInsertCommandType,
|
||||
payload: { circuit },
|
||||
payload: { circuit, ...(externalObjects?.length ? { externalObjects } : {}) },
|
||||
};
|
||||
assertCircuitInsertProjectCommand(command);
|
||||
return command;
|
||||
|
|
@ -74,12 +79,13 @@ export function createCircuitInsertProjectCommand(
|
|||
|
||||
export function createCircuitDeleteProjectCommand(
|
||||
circuitId: string,
|
||||
expectedCircuitListId: string
|
||||
expectedCircuitListId: string,
|
||||
externalObjects?: ExternalObjectLinkSnapshotTransition[]
|
||||
): CircuitDeleteProjectCommand {
|
||||
const command: CircuitDeleteProjectCommand = {
|
||||
schemaVersion: circuitStructureCommandSchemaVersion,
|
||||
type: circuitDeleteCommandType,
|
||||
payload: { circuitId, expectedCircuitListId },
|
||||
payload: { circuitId, expectedCircuitListId, ...(externalObjects?.length ? { externalObjects } : {}) },
|
||||
};
|
||||
assertCircuitDeleteProjectCommand(command);
|
||||
return command;
|
||||
|
|
@ -168,6 +174,7 @@ export function assertCircuitInsertProjectCommand(
|
|||
}
|
||||
rowIds.add(row.id);
|
||||
}
|
||||
assertExternalObjectLinkTransitions(command.payload.externalObjects);
|
||||
}
|
||||
|
||||
export function assertCircuitDeleteProjectCommand(
|
||||
|
|
@ -187,6 +194,7 @@ export function assertCircuitDeleteProjectCommand(
|
|||
command.payload.expectedCircuitListId,
|
||||
"expectedCircuitListId"
|
||||
);
|
||||
assertExternalObjectLinkTransitions(command.payload.externalObjects);
|
||||
}
|
||||
|
||||
function assertNonEmptyString(value: unknown, field: string) {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ export interface CircuitTreeDeviceRow {
|
|||
remark?: string;
|
||||
overriddenFields?: string;
|
||||
rowTotalPower: number;
|
||||
externalObjectCount?: number;
|
||||
}
|
||||
|
||||
export interface CircuitTreeCircuit {
|
||||
|
|
|
|||
|
|
@ -146,8 +146,8 @@ function assertOnlyAssignmentChanged(
|
|||
expected: ExternalModelObjectSnapshot,
|
||||
target: ExternalModelObjectSnapshot
|
||||
) {
|
||||
const expectedRest = { ...expected, circuitDeviceRowId: null };
|
||||
const targetRest = { ...target, circuitDeviceRowId: null };
|
||||
const expectedRest = { ...expected, circuitDeviceRowId: null, distributionBoardId: null };
|
||||
const targetRest = { ...target, circuitDeviceRowId: null, distributionBoardId: null };
|
||||
if (canonicalJson(expectedRest) !== canonicalJson(targetRest)) {
|
||||
throw new Error("New-circuit assignment may only change the external object row link.");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -141,8 +141,8 @@ function assertOnlyAssignmentChanged(
|
|||
expected: ExternalModelObjectSnapshot,
|
||||
target: ExternalModelObjectSnapshot
|
||||
) {
|
||||
const expectedRest = { ...expected, circuitDeviceRowId: null };
|
||||
const targetRest = { ...target, circuitDeviceRowId: null };
|
||||
const expectedRest = { ...expected, circuitDeviceRowId: null, distributionBoardId: null };
|
||||
const targetRest = { ...target, circuitDeviceRowId: null, distributionBoardId: null };
|
||||
if (canonicalJson(expectedRest) !== canonicalJson(targetRest)) {
|
||||
throw new Error("New-row assignment may only change the external object row link.");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -104,10 +104,10 @@ export function assertExternalObjectRowAssignmentProjectCommand(
|
|||
if (expected.circuitDeviceRowId === target.circuitDeviceRowId) {
|
||||
throw new Error("External object row assignment must change its row link.");
|
||||
}
|
||||
assertOnlyFieldChanged(
|
||||
assertOnlyFieldsChanged(
|
||||
expected as unknown as Record<string, unknown>,
|
||||
target as unknown as Record<string, unknown>,
|
||||
"circuitDeviceRowId",
|
||||
new Set(["circuitDeviceRowId", "distributionBoardId", "linkedProjectDeviceId", "planningValues", "overriddenFields"]),
|
||||
"external object"
|
||||
);
|
||||
if (expected.circuitDeviceRowId) affectedRowIds.add(expected.circuitDeviceRowId);
|
||||
|
|
@ -161,11 +161,22 @@ function assertOnlyFieldChanged(
|
|||
target: Record<string, unknown>,
|
||||
allowedField: string,
|
||||
label: string
|
||||
) {
|
||||
assertOnlyFieldsChanged(expected, target, new Set([allowedField]), label);
|
||||
}
|
||||
|
||||
function assertOnlyFieldsChanged(
|
||||
expected: Record<string, unknown>,
|
||||
target: Record<string, unknown>,
|
||||
allowedFields: ReadonlySet<string>,
|
||||
label: string
|
||||
) {
|
||||
const expectedRest = { ...expected };
|
||||
const targetRest = { ...target };
|
||||
delete expectedRest[allowedField];
|
||||
delete targetRest[allowedField];
|
||||
for (const field of allowedFields) {
|
||||
delete expectedRest[field];
|
||||
delete targetRest[field];
|
||||
}
|
||||
if (canonicalJson(expectedRest) !== canonicalJson(targetRest)) {
|
||||
throw new Error(`Row assignment may only change the ${label} assignment state.`);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue