UNPKG

@itwin/presentation-common

Version:

Common pieces for iModel.js presentation packages

239 lines • 10.8 kB
"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ /** @packageDocumentation * @module Core */ Object.defineProperty(exports, "__esModule", { value: true }); exports.RulesetsFactory = void 0; const core_bentley_1 = require("@itwin/core-bentley"); const TypeDescription_js_1 = require("./content/TypeDescription.js"); const Value_js_1 = require("./content/Value.js"); const EC_js_1 = require("./EC.js"); const ContentSpecification_js_1 = require("./rules/content/ContentSpecification.js"); const RelationshipDirection_js_1 = require("./rules/RelationshipDirection.js"); const Rule_js_1 = require("./rules/Rule.js"); /** * A factory class that can be used to create presentation rulesets targeted towards * specific use cases. * * @public */ class RulesetsFactory { createSimilarInstancesRulesetInfo(field, record) { if (!field.isPropertiesField()) { throw new Error("Can only create 'similar instances' ruleset for properties-based records"); } if (field.type.valueFormat !== TypeDescription_js_1.PropertyValueFormat.Primitive) { throw new Error("Can only create 'similar instances' ruleset for primitive properties"); } if (field.properties.length === 0) { throw new Error("Invalid properties' field with no properties"); } if (record.isFieldMerged(field.name)) { throw new Error("Can't create 'similar instances' ruleset for merged values"); } if (!record.classInfo) { throw new Error("Can't create 'similar instances' for records based on multiple different ECClass instances"); } const propertyName = getPropertyName(field); const propertyValue = getPropertyValue(record, field); const relatedInstanceInfo = createRelatedInstanceSpecInfo(field); const relatedClass = relatedInstanceInfo?.class; const ruleset = { id: `SimilarInstances/${propertyName}/${core_bentley_1.Guid.createValue()}`, rules: [], }; ruleset.rules.push({ ruleType: Rule_js_1.RuleTypes.Content, specifications: [ { specType: ContentSpecification_js_1.ContentSpecificationTypes.ContentInstancesOfSpecificClasses, classes: createMultiClassSpecification(record.classInfo), relatedInstances: relatedInstanceInfo ? [relatedInstanceInfo.spec] : [], instanceFilter: createInstanceFilter(relatedInstanceInfo?.spec, field.type, propertyName, propertyValue.raw), }, ], }); return { ruleset, relatedClass, propertyName, propertyValue }; } /** * Create a ruleset with content rules for getting instances are of the * same ECClass and have the same property value as the provided `record`. * @param field A field identifying which property of the record we should use * @param record A record whose similar instances should be found * @param computeDisplayValue Optional callback function to calculate display value that's * used in ruleset's description. If not provided, display value from record is used instead. */ async createSimilarInstancesRuleset(field, record, computeDisplayValue) { const info = this.createSimilarInstancesRulesetInfo(field, record); const description = await createDescriptionAsync(record, info.relatedClass, field, info.propertyValue, computeDisplayValue); return { ruleset: info.ruleset, description }; } } exports.RulesetsFactory = RulesetsFactory; const toString = (displayValue) => { if (!displayValue) { return "NULL"; } return displayValue.toString(); }; const createDescription = (record, relatedClass, field, value) => { const classInfo = relatedClass ?? record.classInfo; return `[${classInfo.label}].[${field.label}] = ${value}`; }; const createDescriptionAsync = async (record, relatedClass, field, value, computeDisplayValue) => { const displayValue = computeDisplayValue ? await computeDisplayValue(field.type.typeName, value.raw, value.display) : toString(value.display); return createDescription(record, relatedClass, field, displayValue); }; const getPropertyName = (field) => { let name = field.properties[0].property.name; if (field.type.typeName === "navigation") { name += ".Id"; } return name; }; const isPrimitivePropertyValue = (value) => { if (Value_js_1.Value.isPrimitive(value)) { return true; } if (Value_js_1.Value.isMap(value)) { if (typeof value.x === "number" && typeof value.y === "number" && (value.z === undefined || typeof value.z === "number")) { return true; } if (typeof value.className === "string" && typeof value.id === "string") { return true; } } return false; }; const getPropertyValue = (record, field) => { const fieldNamesStack = []; let currField = field; while (currField) { fieldNamesStack.push(currField.name); currField = currField.parent; } let currFieldName = fieldNamesStack.pop(); let displayValue = record.displayValues[currFieldName]; let value = record.values[currFieldName]; currFieldName = fieldNamesStack.pop(); while (currFieldName) { if (!Value_js_1.Value.isNestedContent(value) || value.length === 0) { throw new Error("Invalid record value"); } if (value.length > 1) { throw new Error("Can't create 'similar instances' for records related through many part of *-to-many relationship"); } if (value[0].mergedFieldNames.indexOf(currFieldName) !== -1) { throw new Error("Can't create 'similar instances' ruleset for merged values"); } displayValue = value[0].displayValues[currFieldName]; value = value[0].values[currFieldName]; currFieldName = fieldNamesStack.pop(); } if (!isPrimitivePropertyValue(value) || !(typeof displayValue === "undefined" || typeof displayValue === "string")) { throw new Error("Can only create 'similar instances' ruleset for primitive values"); } return { raw: value, display: toString(displayValue) }; }; const createInstanceFilter = (relatedInstanceSpec, propertyType, propertyName, propertyValue) => { const alias = relatedInstanceSpec ? relatedInstanceSpec.alias : "this"; return createComparison(propertyType, `${alias}.${propertyName}`, "=", propertyValue); }; const createComparison = (type, name, operator, value) => { let compareValue = ""; switch (typeof value) { case "undefined": compareValue = "NULL"; break; case "string": compareValue = `"${value.replace(/"/g, `""`)}"`; break; case "boolean": compareValue = value ? "TRUE" : "FALSE"; break; case "number": compareValue = value.toString(); break; } if (type.typeName === "navigation" && typeof value === "object" && core_bentley_1.Id64.isId64(value.id)) { // note: this is temporary until we support hex ids in instance filters compareValue = hexToDec(value.id); } if (type.typeName === "point2d" || type.typeName === "point3d") { if (typeof value !== "object") { throw new Error("Expecting point values to be supplied as objects"); } const dimensionType = { valueFormat: TypeDescription_js_1.PropertyValueFormat.Primitive, typeName: "double", }; const pointValue = value; let comparison = `${createComparison(dimensionType, `${name}.x`, operator, pointValue.x)}`; comparison += ` AND ${createComparison(dimensionType, `${name}.y`, operator, pointValue.y)}`; if (type.typeName === "point3d" && pointValue.z !== undefined) { comparison += ` AND ${createComparison(dimensionType, `${name}.z`, operator, pointValue.z)}`; } return comparison; } if (type.typeName === "double") { return `CompareDoubles(${name}, ${compareValue}) ${operator} 0`; } if (type.typeName === "dateTime") { return `CompareDateTimes(${name}, ${compareValue}) ${operator} 0`; } return `${name} ${operator} ${compareValue}`; }; const createMultiClassSpecification = (classInfo) => { const [schemaName, className] = classInfo.name.split(":"); return { schemaName, classNames: [className], arePolymorphic: true }; }; const createSingleClassSpecification = (classInfo) => { const [schemaName, className] = classInfo.name.split(":"); return { schemaName, className }; }; const createRelatedInstanceSpec = (pathFromSelectToPropertyClass, index) => ({ spec: { relationshipPath: pathFromSelectToPropertyClass.map((step) => ({ relationship: createSingleClassSpecification(step.relationshipInfo), direction: step.isForwardRelationship ? RelationshipDirection_js_1.RelationshipDirection.Forward : RelationshipDirection_js_1.RelationshipDirection.Backward, targetClass: createSingleClassSpecification(step.targetClassInfo), })), isRequired: true, alias: `related_${index}`, }, class: pathFromSelectToPropertyClass[pathFromSelectToPropertyClass.length - 1].targetClassInfo, }); const createPathFromSelectToPropertyClass = (field) => { let currField = field; const pathFromPropertyToSelectClass = []; while (currField.parent) { pathFromPropertyToSelectClass.push(...currField.parent.pathToPrimaryClass); currField = currField.parent; } return EC_js_1.RelationshipPath.reverse(pathFromPropertyToSelectClass); }; const createRelatedInstanceSpecInfo = (field) => { const path = createPathFromSelectToPropertyClass(field); return path.length ? createRelatedInstanceSpec(path, 0) : undefined; }; const hexToDec = (id) => { const digits = [0]; for (let i = 2; i < id.length; ++i) { let carry = parseInt(id.charAt(i), 16); for (let j = 0; j < digits.length; ++j) { digits[j] = digits[j] * 16 + carry; carry = (digits[j] / 10) | 0; digits[j] %= 10; } while (carry > 0) { digits.push(carry % 10); carry = (carry / 10) | 0; } } return digits.reverse().join(""); }; //# sourceMappingURL=RulesetsFactory.js.map