@itwin/core-frontend
Version:
iTwin.js frontend components
190 lines • 8.9 kB
JavaScript
"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 IModelConnection
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.GraphicalEditingScope = void 0;
const core_bentley_1 = require("@itwin/core-bentley");
const core_common_1 = require("@itwin/core-common");
const BriefcaseTxns_1 = require("./BriefcaseTxns");
const IpcApp_1 = require("./IpcApp");
class ModelChanges extends core_bentley_1.SortedArray {
geometryGuid;
range;
constructor(geometryGuid, range) {
super((lhs, rhs) => (0, core_bentley_1.compareStrings)(lhs.id, rhs.id), core_bentley_1.DuplicatePolicy.Replace);
this.geometryGuid = geometryGuid;
this.range = range;
}
}
/** Represents a period of time within an [interactive editing]($docs/learning/InteractiveEditing.md) session during which the
* geometry of elements being displayed in one or more [[Viewport]]s is being modified. Outside of such a scope, whenever the
* geometry within a [GeometricModel]($backend) changes new [[Tile]]s must be generated to reflect those changes in a viewport.
* Regenerating entire tiles each time individual elements change can be time-consuming, which may introduce an unacceptable delay
* between making a modification and seeing its result on the screen.
*
* Within the context of a graphical editing scope, no new tiles are generated. Instead, the geometry for any deleted or modified elements
* is hidden in the tile graphics, and additional temporary graphics are displayed for any newly-inserted or modified elements. Only when the
* scope exits are new tiles produced.
*
* The application decides when to enter and exit a graphical editing scope. A single interactive editing session may involve any number of
* editing scopes. Typically, applications will enter a new editing scope (after first exiting a previous scope, if one exists):
* - When switching from a non-graphical workflow to one that involves editing geometry; or
* - When changing which geometric model is being edited; or
* - After performing an operation that creates or modifies a "large" number (perhaps hundreds?) of elements.
*
* An application should typically exit any graphical editing scope before:
* - Pulling changesets; or
* - Switching from a graphical editing workflow to some non-graphical workflow.
*
* Graphical editing scopes are only supported for [[BriefcaseConnection]]s opened in read-write mode that contain version 1.0.11 or newer of the BisCore schema.
* @see [[BriefcaseConnection.enterEditingScope]] to create a scope for a briefcase.
* @see [[BriefcaseConnection.editingScope]] to obtain a briefcase's current scope.
* @see [[exit]] to terminate a scope.
* @public
*/
class GraphicalEditingScope extends BriefcaseTxns_1.BriefcaseNotificationHandler {
get briefcaseChannelName() { return core_common_1.ipcAppChannels.editingScope; }
/** Maps model Id to accumulated changes to geometric elements within the associated model. */
_geometryChanges = new Map();
_disposed = false;
_cleanup;
/** The connection to the iModel being edited. */
iModel;
/** Event raised when a new scope is created for any [[BriefcaseConnection]].
* @see [[onExiting]] and [[onExited]] for complementary events.
*/
static onEnter = new core_bentley_1.BeEvent();
/** Event raised when this scope is about to exit.
* @see [[onEnter]] for the complementary event.
* @see [[onExited]] for an event raised after the scope exits.
*/
onExiting = new core_bentley_1.BeEvent();
/** Event raised when this scope has exited.
* @see [[onEnter]] for the complementary event.
* @see [[onExiting]] for an event raised just before the scope is exited.
*/
onExited = new core_bentley_1.BeEvent();
/** Event raised after geometric changes are written to the iModel. */
onGeometryChanges = new core_bentley_1.BeEvent();
/** Don't call this directly - use BriefcaseConnection.enterEditingScope.
* @internal
*/
static async enter(imodel) {
if (imodel.editingScope)
throw new Error("Cannot create an editing scope for an iModel that already has one");
// Register the scope synchronously, in case enter() is called again for same iModel while awaiting asynchronous initialization.
const scope = new GraphicalEditingScope(imodel);
try {
const scopeStarted = await IpcApp_1.IpcApp.appFunctionIpc.toggleGraphicalEditingScope(imodel.key, true);
(0, core_bentley_1.assert)(scopeStarted); // If it didn't, the backend threw an error.
}
catch (e) {
scope[Symbol.dispose]();
throw e;
}
this.onEnter.raiseEvent(scope);
return scope;
}
/** Exits this editing scope. The associated [[BriefcaseConnection]]'s `editingScope` will be reset to `undefined`.
* @throws Error if the scope could not be exited, e.g., if it has already been exited.
* @see [[BriefcaseConnection.enterEditingScope]] to enter an editing scope.
*/
async exit() {
if (this._disposed || this.iModel.editingScope !== this)
throw new Error("Cannot exit editing scope after it is disconnected from the iModel");
this._disposed = true;
try {
this.onExiting.raiseEvent(this);
}
finally {
const scopeExited = await IpcApp_1.IpcApp.appFunctionIpc.toggleGraphicalEditingScope(this.iModel.key, false);
(0, core_bentley_1.assert)(!scopeExited);
try {
this.onExited.raiseEvent(this);
}
finally {
this[Symbol.dispose]();
}
}
}
/** Obtain all geometric changes to elements within the specified model accumulated within this scope. */
getGeometryChangesForModel(modelId) {
return this._geometryChanges.get(modelId);
}
/** Obtain all geometric changes to models accumulated within this scope. */
getGeometryChanges() {
return { [Symbol.iterator]: () => this.geometryChangeIterator() };
}
/** @internal */
get isDisposed() {
return this._disposed;
}
*geometryChangeIterator() {
for (const [key, value] of this._geometryChanges) {
yield {
id: key,
geometryGuid: value.geometryGuid,
range: value.range,
elements: value,
};
}
}
constructor(iModel) {
super(iModel.key);
this.iModel = iModel;
this._cleanup = this.registerImpl();
}
[Symbol.dispose]() {
this._disposed = true;
this.onExiting.clear();
this.onGeometryChanges.clear();
this.onExited.clear();
this._geometryChanges.clear();
if (this._cleanup) {
this._cleanup();
this._cleanup = undefined;
}
}
/** @deprecated in 5.0 - will not be removed until after 2026-06-13. Use [Symbol.dispose] instead. */
dispose() {
this[Symbol.dispose]();
}
/** @internal */
notifyGeometryChanged(props) {
const changes = core_common_1.ModelGeometryChanges.iterable(props);
const modelIds = [];
let deletedIds;
for (const modelChanges of changes) {
// ###TODO do we care about the model range?
let list = this._geometryChanges.get(modelChanges.id);
modelIds.push(modelChanges.id);
for (const elementChange of modelChanges.elements) {
if (!list) {
this._geometryChanges.set(modelChanges.id, list = new ModelChanges(modelChanges.geometryGuid, modelChanges.range));
}
else {
list.geometryGuid = modelChanges.geometryGuid;
modelChanges.range.clone(list.range);
}
list.insert(elementChange);
if (core_bentley_1.DbOpcode.Delete === elementChange.type) {
if (undefined === deletedIds)
deletedIds = new Set();
deletedIds.add(elementChange.id);
}
}
}
if (deletedIds) {
this.iModel.selectionSet.remove(deletedIds);
this.iModel.hilited.remove({ elements: deletedIds });
}
this.onGeometryChanges.raiseEvent(changes, this);
}
}
exports.GraphicalEditingScope = GraphicalEditingScope;
//# sourceMappingURL=GraphicalEditingScope.js.map