@itwin/presentation-common
Version:
Common pieces for iModel.js presentation packages
569 lines • 24.9 kB
JavaScript
;
/*---------------------------------------------------------------------------------------------
* 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