UNPKG

@itwin/presentation-common

Version:

Common pieces for iModel.js presentation packages

569 lines • 24.9 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 Content */ var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) { if (value !== null && value !== void 0) { if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected."); var dispose, inner; if (async) { if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined."); dispose = value[Symbol.asyncDispose]; } if (dispose === void 0) { if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined."); dispose = value[Symbol.dispose]; if (async) inner = dispose; } if (typeof dispose !== "function") throw new TypeError("Object not disposable."); if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } }; env.stack.push({ value: value, dispose: dispose, async: async }); } else if (async) { env.stack.push({ async: true }); } return value; }; var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) { return function (env) { function fail(e) { env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e; env.hasError = true; } var r, s = 0; function next() { while (r = env.stack.pop()) { try { if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next); if (r.dispose) { var result = r.dispose.call(r.value); if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); }); } else s |= 1; } catch (e) { fail(e); } } if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve(); if (env.hasError) throw env.error; } return next(); }; })(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { var e = new Error(message); return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; }); Object.defineProperty(exports, "__esModule", { value: true }); exports.FIELD_NAMES_SEPARATOR = void 0; exports.traverseFieldHierarchy = traverseFieldHierarchy; exports.traverseContent = traverseContent; exports.traverseContentItem = traverseContentItem; exports.createContentTraverser = createContentTraverser; exports.createFieldHierarchies = createFieldHierarchies; exports.addFieldHierarchy = addFieldHierarchy; exports.combineFieldNames = combineFieldNames; exports.parseCombinedFieldNames = parseCombinedFieldNames; const core_bentley_1 = require("@itwin/core-bentley"); const Fields_js_1 = require("./Fields.js"); const Item_js_1 = require("./Item.js"); const TypeDescription_js_1 = require("./TypeDescription.js"); const Value_js_1 = require("./Value.js"); const NESTED_CONTENT_LABEL_SYMBOL = Symbol(); /** * An utility for traversing field hierarchy. Stops traversal as soon as `cb` returns `false`. * @public */ function traverseFieldHierarchy(hierarchy, cb) { if (cb(hierarchy)) { hierarchy.childFields.forEach((childHierarchy) => traverseFieldHierarchy(childHierarchy, cb)); } } /* c8 ignore start */ /** * An utility to traverse content using provided visitor. Provides means to parse content into different formats, * for different components. * @public * @deprecated in 5.4 - will not be removed until after 2026-12-02. Use [[createContentTraverser]] instead. */ function traverseContent(visitor, content) { return createContentTraverser(visitor, content.descriptor)(content.contentSet); } /** * An utility for calling [[traverseContent]] when there's only one content item. * @public * @deprecated in 5.4 - will not be removed until after 2026-12-02. Use [[createContentTraverser]] instead. */ function traverseContentItem(visitor, descriptor, item) { return createContentTraverser(visitor, descriptor)([item]); } /** @public */ function createContentTraverser(visitor, descriptorArg) { let memo; const traverseContentItems = (descriptor, items) => { if (!visitor.startContent({ descriptor })) { return; } try { if (memo?.descriptor !== descriptor) { const fieldHierarchies = createFieldHierarchies(descriptor.fields); visitor.processFieldHierarchies({ hierarchies: fieldHierarchies }); memo = { descriptor, fieldHierarchies }; } items.forEach((item) => { traverseContentItemFields(visitor, memo.fieldHierarchies, item); }); } finally { visitor.finishContent(); } }; return descriptorArg ? (items) => traverseContentItems(descriptorArg, items) : traverseContentItems; } class VisitedCategories { _visitor; _visitedCategories; _didVisitAllHierarchy; constructor(_visitor, category) { this._visitor = _visitor; const stack = []; let curr = category; while (curr) { stack.push(curr); curr = curr.parent; } stack.reverse(); this._didVisitAllHierarchy = true; this._visitedCategories = []; for (curr of stack) { if (this._visitor.startCategory({ category: curr })) { this._visitedCategories.push(curr); } else { this._didVisitAllHierarchy = false; break; } } } [Symbol.dispose]() { while (this._visitedCategories.pop()) { this._visitor.finishCategory(); } } get shouldContinue() { return this._didVisitAllHierarchy; } } function traverseContentItemFields(visitor, fieldHierarchies, item) { if (!visitor.startItem({ item })) { return; } try { fieldHierarchies.forEach((fieldHierarchy) => { const env_1 = { stack: [], error: void 0, hasError: false }; try { const res = __addDisposableResource(env_1, new VisitedCategories(visitor, fieldHierarchy.field.category), false); if (res.shouldContinue) { traverseContentItemField(visitor, fieldHierarchy, item); } } catch (e_1) { env_1.error = e_1; env_1.hasError = true; } finally { __disposeResources(env_1); } }); } finally { visitor.finishItem(); } } function traverseContentItemField(visitor, fieldHierarchy, item) { if (!visitor.startField({ hierarchy: fieldHierarchy })) { return; } try { const rootToThisField = createFieldPath(fieldHierarchy.field); let parentFieldName; const pathUpToField = rootToThisField.slice(undefined, -1); for (let i = 0; i < pathUpToField.length; ++i) { const parentField = pathUpToField[i]; const nextField = rootToThisField[i + 1]; if (item.isFieldMerged(parentField.name)) { visitor.processMergedValue({ requestedField: fieldHierarchy.field, mergedField: parentField, parentFieldName }); return; } const { emptyNestedItem, convertedItem } = convertNestedContentItemToStructArrayItem(item, parentField, nextField); if (emptyNestedItem) { return; } item = convertedItem; parentFieldName = combineFieldNames(parentField.name, parentFieldName); } if (item.isFieldMerged(fieldHierarchy.field.name)) { visitor.processMergedValue({ requestedField: fieldHierarchy.field, mergedField: fieldHierarchy.field, parentFieldName }); return; } if (fieldHierarchy.field.isNestedContentField()) { fieldHierarchy = convertNestedContentFieldHierarchyToStructArrayHierarchy(fieldHierarchy, parentFieldName); const { emptyNestedItem, convertedItem } = convertNestedContentFieldHierarchyItemToStructArrayItem(item, fieldHierarchy); if (emptyNestedItem) { return; } item = convertedItem; } else if (pathUpToField.length > 0) { fieldHierarchy = { ...fieldHierarchy, field: (function () { const clone = fieldHierarchy.field.clone(); clone.type = { valueFormat: TypeDescription_js_1.PropertyValueFormat.Array, typeName: `${fieldHierarchy.field.type.typeName}[]`, memberType: fieldHierarchy.field.type, }; return clone; })(), }; } traverseContentItemFieldValue(visitor, fieldHierarchy, item.mergedFieldNames, fieldHierarchy.field.type, parentFieldName, item.values[fieldHierarchy.field.name], item.displayValues[fieldHierarchy.field.name]); } finally { visitor.finishField(); } } function traverseContentItemFieldValue(visitor, fieldHierarchy, mergedFieldNames, valueType, parentFieldName, rawValue, displayValue) { if (rawValue !== undefined) { if (valueType.valueFormat === TypeDescription_js_1.PropertyValueFormat.Array) { (0, core_bentley_1.assert)(Value_js_1.Value.isArray(rawValue)); (0, core_bentley_1.assert)(Value_js_1.DisplayValue.isArray(displayValue)); return traverseContentItemArrayFieldValue(visitor, fieldHierarchy, mergedFieldNames, valueType, parentFieldName, rawValue, displayValue); } if (valueType.valueFormat === TypeDescription_js_1.PropertyValueFormat.Struct) { (0, core_bentley_1.assert)(Value_js_1.Value.isMap(rawValue)); (0, core_bentley_1.assert)(Value_js_1.DisplayValue.isMap(displayValue)); return traverseContentItemStructFieldValue(visitor, fieldHierarchy, mergedFieldNames, valueType, parentFieldName, rawValue, displayValue); } } traverseContentItemPrimitiveFieldValue(visitor, fieldHierarchy, mergedFieldNames, valueType, parentFieldName, rawValue, displayValue); } function traverseContentItemArrayFieldValue(visitor, fieldHierarchy, mergedFieldNames, valueType, parentFieldName, rawValues, displayValues) { (0, core_bentley_1.assert)(rawValues.length === displayValues.length); (0, core_bentley_1.assert)(valueType.valueFormat === TypeDescription_js_1.PropertyValueFormat.Array); if (!visitor.startArray({ hierarchy: fieldHierarchy, valueType, parentFieldName, rawValues, displayValues })) { return; } try { const itemType = valueType.memberType; rawValues.forEach((_, i) => { traverseContentItemFieldValue(visitor, fieldHierarchy, mergedFieldNames, itemType, parentFieldName, rawValues[i], displayValues[i]); }); } finally { visitor.finishArray(); } } function traverseContentItemStructFieldValue(visitor, fieldHierarchy, mergedFieldNames, valueType, parentFieldName, rawValues, displayValues) { (0, core_bentley_1.assert)(valueType.valueFormat === TypeDescription_js_1.PropertyValueFormat.Struct); const label = rawValues[NESTED_CONTENT_LABEL_SYMBOL]; if (!visitor.startStruct({ hierarchy: fieldHierarchy, valueType, parentFieldName, rawValues, displayValues, label })) { return; } try { if (fieldHierarchy.field.isNestedContentField()) { parentFieldName = combineFieldNames(fieldHierarchy.field.name, parentFieldName); } valueType.members.forEach((memberDescription) => { let memberField = fieldHierarchy.childFields.find((f) => f.field.name === memberDescription.name); if (!memberField) { // Not finding a member field means we're traversing an ECStruct. If current field is `StructPropertiesField` // try looking up member field in there. Otherwise, just create fake field to propagate information. const structMemberField = fieldHierarchy.field.isPropertiesField() && fieldHierarchy.field.isStructPropertiesField() ? fieldHierarchy.field.memberFields.find((mf) => mf.name === memberDescription.name) : undefined; memberField = { field: structMemberField ?? new Fields_js_1.Field({ category: fieldHierarchy.field.category, name: memberDescription.name, label: memberDescription.label, type: memberDescription.type, isReadonly: fieldHierarchy.field.isReadonly, priority: 0, }), childFields: [], }; } traverseContentItemFieldValue(visitor, memberField, mergedFieldNames, memberDescription.type, parentFieldName, rawValues[memberDescription.name], displayValues[memberDescription.name]); }); } finally { visitor.finishStruct(); } } function traverseContentItemPrimitiveFieldValue(visitor, fieldHierarchy, mergedFieldNames, valueType, parentFieldName, rawValue, displayValue) { if (-1 !== mergedFieldNames.indexOf(fieldHierarchy.field.name)) { visitor.processMergedValue({ mergedField: fieldHierarchy.field, requestedField: fieldHierarchy.field, parentFieldName }); return; } visitor.processPrimitiveValue({ field: fieldHierarchy.field, valueType, parentFieldName, rawValue, displayValue }); } /** * Parses a list of [[Field]] objects into a list of [[FieldHierarchy]]. * * @param ignoreCategories Enables adding all of the `nestedFields` to parent field's `childFields` * without considering categories. * * @public */ function createFieldHierarchies(fields, ignoreCategories) { const hierarchies = new Array(); const visitField = (category, field, parentField) => { let childFields = []; if (field.isNestedContentField()) { // visit all nested fields childFields = visitFields(field.nestedFields, field); if (0 === childFields.length) { return undefined; } } const fieldHierarchy = { field, childFields }; if (category === parentField?.category || (ignoreCategories && parentField)) { // if categories of this field and its parent field match - return the field hierarchy without // including it as a top level field return fieldHierarchy; } addFieldHierarchy(hierarchies, fieldHierarchy); return undefined; }; const visitFields = (visitedFields, parentField) => { const includedFields = []; visitedFields.forEach((field) => { const visitedField = visitField(field.category, field, parentField); if (visitedField) { includedFields.push(visitedField); } }); return includedFields; }; visitFields(fields, undefined); return hierarchies; } function findRelatedFields(rootFields, hierarchy) { // build a list of parent fields in hierarchy const fields = []; let currField = hierarchy.field; while (currField) { fields.push(currField); currField = currField.parent; } for (let rootIndex = 0; rootIndex < rootFields.length; ++rootIndex) { const rootFieldHierarchy = rootFields[rootIndex]; if (rootFieldHierarchy.field.category !== hierarchy.field.category) { // only interested in fields with the same category continue; } let first = true; currField = rootFieldHierarchy.field; while (currField) { const index = fields.findIndex((f) => f.name === currField.name); if (-1 !== index) { return { existing: { field: currField, hierarchy: first ? rootFieldHierarchy : undefined, index: rootIndex, }, matchingField: fields[index], }; } currField = currField.parent; first = false; } } return undefined; } function buildParentHierarchy(hierarchy, parentField) { // note: parentField is found by walking up the parentship relationship // from hierarchy.field, so we expect to always find it here while (hierarchy.field !== parentField) { const hierarchyParent = hierarchy.field.parent; (0, core_bentley_1.assert)(hierarchyParent !== undefined); hierarchy = { field: hierarchyParent, childFields: [hierarchy] }; } return hierarchy; } function mergeHierarchies(lhs, rhs) { (0, core_bentley_1.assert)(lhs.field.name === rhs.field.name); const result = { field: lhs.field.clone(), childFields: [...lhs.childFields], }; rhs.childFields.forEach((rhsChildHierarchy) => { const indexInResult = result.childFields.findIndex((resultHierarchy) => resultHierarchy.field.name === rhsChildHierarchy.field.name); if (indexInResult !== -1) { result.childFields[indexInResult] = mergeHierarchies(result.childFields[indexInResult], rhsChildHierarchy); } else { result.childFields.push(rhsChildHierarchy); } }); return result; } /** * Adds a field hierarchy into root field hierarchies list. * @public */ function addFieldHierarchy(rootHierarchies, hierarchy) { const match = findRelatedFields(rootHierarchies, hierarchy); if (!match) { rootHierarchies.push(hierarchy); return; } const targetHierarchy = rootHierarchies[match.existing.index]; const existingHierarchy = match.existing.hierarchy ?? buildParentHierarchy(targetHierarchy, match.existing.field); const insertHierarchy = buildParentHierarchy(hierarchy, match.matchingField); const mergedHierarchy = mergeHierarchies(existingHierarchy, insertHierarchy); mergedHierarchy.field.category = hierarchy.field.category; rootHierarchies[match.existing.index] = mergedHierarchy; } function createFieldPath(field) { const path = [field]; let currField = field; while (currField.parent) { currField = currField.parent; path.push(currField); } path.reverse(); return path; } /** @internal */ exports.FIELD_NAMES_SEPARATOR = "$"; /** * Combines given field names in a way that allows them to be parsed back into a list of individual names using the [[parseCombinedFieldNames]] function. * @public */ function combineFieldNames(fieldName, parentFieldName) { return parentFieldName ? `${parentFieldName}${exports.FIELD_NAMES_SEPARATOR}${fieldName}` : fieldName; } /** * Parses given combined field names string, constructed using [[combineFieldNames]], into a list of individual field names. * @public */ function parseCombinedFieldNames(combinedName) { return combinedName ? combinedName.split(exports.FIELD_NAMES_SEPARATOR) : []; } function convertNestedContentItemToStructArrayItem(item, field, nextField) { const value = item.values[field.name] ?? []; (0, core_bentley_1.assert)(Value_js_1.Value.isNestedContent(value)); if (value.length === 0) { return { emptyNestedItem: true, convertedItem: item }; } let nextFieldValues = { raw: [], display: [] }; if (nextField.isNestedContentField()) { const nextNestedContent = []; value.forEach((ncv) => { const nextRawValue = ncv.values[nextField.name]; if (nextRawValue) { (0, core_bentley_1.assert)(Value_js_1.Value.isNestedContent(nextRawValue)); nextNestedContent.push(...nextRawValue); } }); nextFieldValues.raw = nextNestedContent; } else { const nextValues = { raw: [], display: [] }; value.forEach((ncv) => { nextValues.raw.push(ncv.values[nextField.name]); nextValues.display.push(ncv.displayValues[nextField.name]); }); nextFieldValues = nextValues; } const convertedItem = new Item_js_1.Item({ ...item, values: { [nextField.name]: nextFieldValues.raw }, displayValues: { [nextField.name]: nextFieldValues.display }, }); return { emptyNestedItem: false, convertedItem }; } function convertNestedContentFieldHierarchyToStructArrayHierarchy(fieldHierarchy, parentFieldName) { const fieldName = fieldHierarchy.field.name; const convertedChildFieldHierarchies = fieldHierarchy.childFields.map((child) => { if (child.field.isNestedContentField()) { return convertNestedContentFieldHierarchyToStructArrayHierarchy(child, combineFieldNames(fieldName, parentFieldName)); } return child; }); const convertedFieldHierarchy = { field: (function () { const clone = fieldHierarchy.field.clone(); clone.type = { valueFormat: TypeDescription_js_1.PropertyValueFormat.Array, typeName: `${fieldHierarchy.field.type.typeName}[]`, memberType: { valueFormat: TypeDescription_js_1.PropertyValueFormat.Struct, typeName: fieldHierarchy.field.type.typeName, members: convertedChildFieldHierarchies.map((member) => ({ name: member.field.name, label: member.field.label, type: member.field.type, })), }, }; return clone; })(), childFields: convertedChildFieldHierarchies, }; return convertedFieldHierarchy; } function convertNestedContentValuesToStructArrayValuesRecursive(fieldHierarchy, ncvs) { const result = { raw: [], display: [], mergedFieldNames: [] }; ncvs.forEach((ncv) => { const values = { ...ncv.values }; const displayValues = { ...ncv.displayValues }; const mergedFieldNames = [...ncv.mergedFieldNames]; // eslint-disable-next-line @typescript-eslint/no-deprecated values[NESTED_CONTENT_LABEL_SYMBOL] = ncv.label ?? ncv.labelDefinition; fieldHierarchy.childFields.forEach((childFieldHierarchy) => { const childFieldName = childFieldHierarchy.field.name; if (-1 !== ncv.mergedFieldNames.indexOf(childFieldName)) { return; } if (childFieldHierarchy.field.isNestedContentField()) { const value = values[childFieldName]; (0, core_bentley_1.assert)(Value_js_1.Value.isNestedContent(value)); const convertedValues = convertNestedContentValuesToStructArrayValuesRecursive(childFieldHierarchy, value); values[childFieldName] = convertedValues.raw; displayValues[childFieldName] = convertedValues.display; mergedFieldNames.push(...convertedValues.mergedFieldNames); } }); result.raw.push(values); result.display.push(displayValues); result.mergedFieldNames.push(...mergedFieldNames); }); return result; } function convertNestedContentFieldHierarchyItemToStructArrayItem(item, fieldHierarchy) { const fieldName = fieldHierarchy.field.name; const rawValue = item.values[fieldName]; (0, core_bentley_1.assert)(Value_js_1.Value.isNestedContent(rawValue)); if (rawValue.length === 0) { return { emptyNestedItem: true, convertedItem: item }; } const converted = convertNestedContentValuesToStructArrayValuesRecursive(fieldHierarchy, rawValue); const convertedItem = new Item_js_1.Item({ ...item, values: { [fieldName]: converted.raw }, displayValues: { [fieldName]: converted.display }, mergedFieldNames: converted.mergedFieldNames, }); return { emptyNestedItem: false, convertedItem }; } //# sourceMappingURL=ContentTraverser.js.map