UNPKG

@itwin/core-backend

Version:
225 lines • 9.32 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ import { FieldRun, formatFieldValue, QueryBinder, QueryRowFormat, traverseTextBlockComponent } from "@itwin/core-common"; import { assert, expectDefined, Logger } from "@itwin/core-bentley"; import { BackendLoggerCategory } from "../../BackendLoggerCategory"; import { isITextAnnotation } from "../../annotations/ElementDrivesTextAnnotation"; import { EntityClass, PrimitiveType, PropertyType, StructArrayProperty } from "@itwin/ecschema-metadata"; import { reshapePropertyValue } from "../ECSqlInstanceReshaper"; // Resolve the raw primitive value of the property that a field points to. function getFieldPropertyValue(field, iModel) { const host = field.propertyHost; const schemaItem = iModel.schemaContext.getSchemaItemSync(host.schemaName, host.className); if (!EntityClass.isEntityClass(schemaItem)) { return undefined; } let ecClass = schemaItem; const { propertyName, accessors } = field.propertyPath; let ecProp = ecClass.getPropertySync(propertyName); if (!ecProp) { return undefined; } const isAspect = ecClass.isSync("ElementAspect", "BisCore"); const where = ` WHERE ${isAspect ? "Element.Id" : "ECInstanceId"}=:elementId`; // `propertyName` may itself be a struct/array/point/navigation property, so its value can't be // decomposed into scalar sub-columns ahead of time. Query using the non-deprecated // UseECSqlPropertyNames format and reshape the value into the legacy UseJsPropertyNames shape using // ECSchema metadata (see ECSqlInstanceReshaper for why a naive, non-schema-aware rename isn't safe here). let curValue = iModel.withQueryReader(`SELECT ${propertyName} FROM ${host.schemaName}.${host.className} ${where}`, (reader) => { if (!reader.step()) { return undefined; } const rawRootValue = reader.current[0]; if (undefined === rawRootValue) { return undefined; } ecProp = expectDefined(ecProp); const rootValue = reshapePropertyValue(rawRootValue, ecProp, iModel); if (ecProp.isArray()) { return ecProp.isStruct() ? { structArray: rootValue } : { primitiveArray: rootValue }; } if (ecProp.isStruct()) { ecClass = ecProp.structClass; return { struct: rootValue }; } if (ecProp.isPrimitive()) { return { primitive: ecProp.primitiveType === PrimitiveType.DateTime ? new Date(rootValue) : rootValue, }; } return undefined; }, new QueryBinder().bindId("elementId", host.elementId), { rowFormat: QueryRowFormat.UseECSqlPropertyNames }); if (undefined === curValue) { return undefined; } if (accessors) { for (const accessor of accessors) { if (undefined !== curValue.primitive) { // Can't index into a primitive. return undefined; } if (typeof accessor === "number") { const array = curValue.primitiveArray ?? curValue.structArray; if (!array) { return undefined; } const index = accessor < 0 ? (array.length + accessor) : accessor; const item = array[index]; if (undefined === item) { return undefined; } else if (curValue.primitiveArray) { curValue = { primitive: curValue.primitiveArray[index] }; } else { assert(undefined !== curValue.structArray); assert(ecProp instanceof StructArrayProperty); ecClass = ecProp.structClass; curValue = { struct: curValue.structArray[index] }; } } else { if (undefined === curValue.struct) { return undefined; } const item = curValue.struct[accessor]; if (undefined === item) { return undefined; } ecProp = ecClass.getPropertySync(accessor); if (!ecProp) { return undefined; } if (ecProp.isArray()) { curValue = ecProp.isStruct() ? { structArray: item } : { primitiveArray: item }; } else if (ecProp.isStruct()) { ecClass = ecProp.structClass; curValue = { struct: item }; } else if (ecProp.isPrimitive()) { curValue = { primitive: item }; } else { return undefined; } } } } const propertyType = determineFieldPropertyType(ecProp); if (!propertyType) { return undefined; } // The ultimate result must be a primitive value. if (undefined === curValue.primitive) { return undefined; } return { value: curValue.primitive, type: propertyType }; } function determineFieldPropertyType(prop) { if (prop.isEnumeration()) { switch (prop.propertyType) { case PropertyType.Integer_Enumeration: return "int-enum"; case PropertyType.String_Enumeration: return "string-enum"; default: return undefined; } } if (prop.isPrimitive()) { switch (prop.primitiveType) { case PrimitiveType.Boolean: return "boolean"; case PrimitiveType.String: return prop.extendedTypeName === "DateTime" ? "datetime" : "string"; case PrimitiveType.DateTime: return "datetime"; case PrimitiveType.Double: case PrimitiveType.Long: return "quantity"; case PrimitiveType.Point2d: case PrimitiveType.Point3d: return "coordinate"; case PrimitiveType.Binary: return prop.extendedTypeName === "BeGuid" ? "string" : undefined; case PrimitiveType.Integer: case PrimitiveType.Long: return "string"; default: return undefined; } } return undefined; } export function createUpdateContext(hostElementId, iModel, deleted) { return { hostElementId, getProperty: deleted ? () => undefined : (field) => getFieldPropertyValue(field, iModel), }; } // Recompute the display value of a single field, return false if it couldn't be evaluated. export function updateField(field, context) { if (context.hostElementId && context.hostElementId !== field.propertyHost.elementId) { return false; } let newContent; try { const propValue = context.getProperty(field); if (undefined !== propValue) { newContent = formatFieldValue(propValue, field.formatOptions); } } catch (err) { Logger.logError(BackendLoggerCategory.IModelDb, err); } newContent = newContent ?? FieldRun.invalidContentIndicator; if (newContent === field.cachedContent) { return false; } field.setCachedContent(newContent); return true; } // Re-evaluates the display strings for all fields that target the element specified by `context` and returns the number // of fields whose display strings changed as a result. export function updateFields(textBlock, context) { let numUpdated = 0; for (const { child } of traverseTextBlockComponent(textBlock)) { if (child.type === "field" && updateField(child, context)) { ++numUpdated; } } return numUpdated; } function doUpdateFields(txn, annotationId, sourceId, deleted) { const iModel = txn.iModel; try { const target = iModel.elements.getElement(annotationId); if (isITextAnnotation(target)) { const context = createUpdateContext(sourceId, iModel, deleted); const updatedBlocks = []; for (const block of target.getTextBlocks()) { if (updateFields(block.textBlock, context)) { updatedBlocks.push(block); } } if (updatedBlocks.length > 0) { target.updateTextBlocks(updatedBlocks); target.update(txn); } } } catch (err) { Logger.logError(BackendLoggerCategory.IModelDb, err); } } // Invoked by ElementDrivesTextAnnotation to update fields in target element when source element changes or is deleted. export function updateElementFields(props, txn, deleted) { doUpdateFields(txn, props.targetId, props.sourceId, deleted); } export function updateAllFields(annotationElementId, txn) { doUpdateFields(txn, annotationElementId, undefined, false); } //# sourceMappingURL=fields.js.map