@itwin/presentation-shared
Version:
The package contains types and utilities used across different iTwin.js Presentation packages.
160 lines • 6.95 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
import { createConcatenatedValueJsonSelector, createRawPropertyValueSelector, } from "./ecsql-snippets/ECSqlValueSelectorSnippets.js";
/**
* Parses an instance label from query result into a string or a `ConcatenatedValue`. The latter type of result
* is expected when label selector is created using `IInstanceLabelSelectClauseFactory.createSelectClause` with
* `createConcatenatedValueJsonSelector`.
*
* @public
*/
export function parseInstanceLabel(value) {
if (!value) {
return "";
}
if ((value.startsWith("[") && value.endsWith("]")) || (value.startsWith("{") && value.endsWith("}"))) {
try {
return JSON.parse(value);
}
catch {
// fall through
}
}
// not a JSON object/array
return value;
}
/**
* Creates a label select clause in a format `Class label [base36(briefcase id)-base36(local id)]`, where
* local and briefcase IDs are calculated based on ECInstance ID:
* - `{briefcase id} = ECInstanceId >> 40`
* - `{local id} = ECInstanceId & (1 << 40 - 1)`
*
* @see https://www.itwinjs.org/presentation/advanced/defaultbisrules/#label-overrides
* @public
*/
export function createDefaultInstanceLabelSelectClauseFactory() {
return {
async createSelectClause(props) {
return `(
SELECT
${concatenate(props, [
{
selector: `COALESCE(
${createRawPropertyValueSelector("c", "DisplayLabel")},
${createRawPropertyValueSelector("c", "Name")}
)`,
},
...createECInstanceIdSuffixSelectors(props.classAlias),
])}
FROM [meta].[ECClassDef] AS [c]
WHERE [c].[ECInstanceId] = ${createRawPropertyValueSelector(props.classAlias, "ECClassId")}
)`;
},
};
}
/**
* Creates an instance label select clause based on its class.
* @public
*/
export function createClassBasedInstanceLabelSelectClauseFactory(props) {
const { classHierarchyInspector, clauses: labelClausesByClass } = props;
const defaultClauseFactory = props.defaultClauseFactory ?? createDefaultInstanceLabelSelectClauseFactory();
async function getLabelClausesForClass(queryClassName) {
const matchingLabelClauses = await Promise.all(labelClausesByClass.map(async (entry) => {
if (await classHierarchyInspector.classDerivesFrom(entry.className, queryClassName)) {
// label selector is intended for a more specific class than we're selecting from - need to include it
// as query results (on polymorphic select) are going to include more specific class instances too
return entry;
}
if (await classHierarchyInspector.classDerivesFrom(queryClassName, entry.className)) {
// label selector is intended for a base class of what query is selecting - need to include it as
// we want base class label selectors to apply to subclass instances
return entry;
}
return undefined;
}));
function filterNotUndefined(x) {
return !!x;
}
return matchingLabelClauses.filter(filterNotUndefined);
}
return {
async createSelectClause(clauseProps) {
if (labelClausesByClass.length === 0) {
return defaultClauseFactory.createSelectClause(clauseProps);
}
const labelClausePromises = clauseProps.className ? await getLabelClausesForClass(clauseProps.className) : labelClausesByClass;
if (labelClausePromises.length === 0) {
return defaultClauseFactory.createSelectClause(clauseProps);
}
const labelClauses = await Promise.all(labelClausePromises.map(async ({ className, clause }) => ({
className,
clause: await clause(clauseProps),
})));
return `COALESCE(
${labelClauses
.map(({ className, clause }) => `
IIF(
${createRawPropertyValueSelector(clauseProps.classAlias, "ECClassId")} IS (${className}),
${clause.trim()},
NULL
)
`.trim())
.join(", ")},
${await defaultClauseFactory.createSelectClause(clauseProps)}
)`;
},
};
}
/**
* Creates a label select clause according to BIS instance label rules.
* @see https://www.itwinjs.org/presentation/advanced/defaultbisrules/#label-overrides
* @public
*/
export function createBisInstanceLabelSelectClauseFactory(props) {
const clauses = [];
const factory = createClassBasedInstanceLabelSelectClauseFactory({
classHierarchyInspector: props.classHierarchyInspector,
clauses,
});
clauses.push({
className: "BisCore.GeometricElement",
clause: async ({ classAlias, ...rest }) => `
COALESCE(
${createRawPropertyValueSelector(classAlias, "CodeValue")},
${concatenate(rest, [{ selector: createRawPropertyValueSelector(classAlias, "UserLabel") }, ...createECInstanceIdSuffixSelectors(classAlias)], `${createRawPropertyValueSelector(classAlias, "UserLabel")} IS NOT NULL`)}
)
`,
}, {
className: "BisCore.Element",
clause: async ({ classAlias }) => `
COALESCE(
${createRawPropertyValueSelector(classAlias, "UserLabel")},
${createRawPropertyValueSelector(classAlias, "CodeValue")}
)
`,
}, {
className: "BisCore.Model",
clause: async ({ classAlias, ...rest }) => `(
SELECT ${await factory.createSelectClause({ ...rest, classAlias: "e", className: "BisCore.Element" })}
FROM [bis].[Element] AS [e]
WHERE [e].[ECInstanceId] = ${createRawPropertyValueSelector(classAlias, "ModeledElement", "Id")}
)`,
});
return factory;
}
function createECInstanceIdSuffixSelectors(classAlias) {
return [
{ value: ` [`, type: "String" },
{ selector: `CAST(base36(${createRawPropertyValueSelector(classAlias, "ECInstanceId")} >> 40) AS TEXT)` },
{ value: `-`, type: "String" },
{ selector: `CAST(base36(${createRawPropertyValueSelector(classAlias, "ECInstanceId")} & ((1 << 40) - 1)) AS TEXT)` },
{ value: `]`, type: "String" },
];
}
function concatenate(props, selectors, checkSelector) {
return (props.selectorsConcatenator ?? createConcatenatedValueJsonSelector)(selectors, checkSelector);
}
//# sourceMappingURL=InstanceLabelSelectClauseFactory.js.map