@itwin/presentation-frontend
Version:
Frontend of iModel.js Presentation library
309 lines • 13.6 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 Core
*/
import { compareStrings, Dictionary, Guid, isDisposable } from "@itwin/core-bentley";
import { InternetConnectivityStatus } from "@itwin/core-common";
import { IModelApp } from "@itwin/core-frontend";
import { PresentationError, PresentationStatus } from "@itwin/presentation-common";
import { ConnectivityInformationProvider } from "../ConnectivityInformationProvider.js";
/** @internal */
export const IMODELJS_PRESENTATION_SETTING_NAMESPACE = "imodeljs.presentation";
/** @internal */
export const DEPRECATED_PROPERTIES_SETTING_NAMESPACE = "Properties";
/** @internal */
export const FAVORITE_PROPERTIES_SETTING_NAME = "FavoriteProperties";
/** @internal */
export const FAVORITE_PROPERTIES_ORDER_INFO_SETTING_NAME = "FavoritePropertiesOrderInfo";
/**
* Available implementations of [[IFavoritePropertiesStorage]].
* @public
*/
export var DefaultFavoritePropertiesStorageTypes;
(function (DefaultFavoritePropertiesStorageTypes) {
/** A no-op storage that doesn't store or return anything. Used for cases when favorite properties aren't used by the application. */
DefaultFavoritePropertiesStorageTypes[DefaultFavoritePropertiesStorageTypes["Noop"] = 0] = "Noop";
/** A storage that stores favorite properties information in a browser local storage. */
DefaultFavoritePropertiesStorageTypes[DefaultFavoritePropertiesStorageTypes["BrowserLocalStorage"] = 1] = "BrowserLocalStorage";
/** A storage that stores favorite properties in a user preferences storage (see [[IModelApp.userPreferences]]). */
DefaultFavoritePropertiesStorageTypes[DefaultFavoritePropertiesStorageTypes["UserPreferencesStorage"] = 2] = "UserPreferencesStorage";
})(DefaultFavoritePropertiesStorageTypes || (DefaultFavoritePropertiesStorageTypes = {}));
/**
* A factory method to create one of the available [[IFavoritePropertiesStorage]] implementations.
* @public
*/
export function createFavoritePropertiesStorage(type) {
switch (type) {
case DefaultFavoritePropertiesStorageTypes.Noop:
return new NoopFavoritePropertiesStorage();
case DefaultFavoritePropertiesStorageTypes.BrowserLocalStorage:
return new BrowserLocalFavoritePropertiesStorage();
case DefaultFavoritePropertiesStorageTypes.UserPreferencesStorage:
return new OfflineCachingFavoritePropertiesStorage({ impl: new IModelAppFavoritePropertiesStorage() });
}
}
/**
* @internal
*/
export class IModelAppFavoritePropertiesStorage {
async ensureIsSignedIn() {
const accessToken = IModelApp.authorizationClient ? await IModelApp.authorizationClient.getAccessToken() : "";
if (accessToken) {
return { accessToken };
}
throw new PresentationError(PresentationStatus.Error, "Current user is not authorized to use the settings service");
}
async loadProperties(iTwinId, imodelId) {
if (!IModelApp.userPreferences) {
throw new PresentationError(PresentationStatus.Error, "User preferences service is not set up");
}
const { accessToken } = await this.ensureIsSignedIn();
let setting = await IModelApp.userPreferences.get({
accessToken,
iTwinId,
iModelId: imodelId,
namespace: IMODELJS_PRESENTATION_SETTING_NAMESPACE,
key: FAVORITE_PROPERTIES_SETTING_NAME,
});
if (setting !== undefined) {
return new Set(setting);
}
// try to check the old namespace
setting = await IModelApp.userPreferences.get({
accessToken,
iTwinId,
iModelId: imodelId,
namespace: DEPRECATED_PROPERTIES_SETTING_NAMESPACE,
key: FAVORITE_PROPERTIES_SETTING_NAME,
});
if (setting !== undefined &&
setting.hasOwnProperty("nestedContentInfos") &&
setting.hasOwnProperty("propertyInfos") &&
setting.hasOwnProperty("baseFieldInfos")) {
return new Set([...setting.nestedContentInfos, ...setting.propertyInfos, ...setting.baseFieldInfos]);
}
return undefined;
}
async saveProperties(properties, iTwinId, imodelId) {
if (!IModelApp.userPreferences) {
throw new PresentationError(PresentationStatus.Error, "User preferences service is not set up");
}
const { accessToken } = await this.ensureIsSignedIn();
await IModelApp.userPreferences.save({
accessToken,
iTwinId,
iModelId: imodelId,
namespace: IMODELJS_PRESENTATION_SETTING_NAMESPACE,
key: FAVORITE_PROPERTIES_SETTING_NAME,
content: Array.from(properties),
});
}
async loadPropertiesOrder(iTwinId, imodelId) {
if (!IModelApp.userPreferences) {
throw new PresentationError(PresentationStatus.Error, "User preferences service is not set up");
}
const { accessToken } = await this.ensureIsSignedIn();
const setting = await IModelApp.userPreferences.get({
accessToken,
iTwinId,
iModelId: imodelId,
namespace: IMODELJS_PRESENTATION_SETTING_NAMESPACE,
key: FAVORITE_PROPERTIES_ORDER_INFO_SETTING_NAME,
});
return setting;
}
async savePropertiesOrder(orderInfos, iTwinId, imodelId) {
if (!IModelApp.userPreferences) {
throw new PresentationError(PresentationStatus.Error, "User preferences service is not set up");
}
const { accessToken } = await this.ensureIsSignedIn();
await IModelApp.userPreferences.save({
accessToken,
iTwinId,
iModelId: imodelId,
namespace: IMODELJS_PRESENTATION_SETTING_NAMESPACE,
key: FAVORITE_PROPERTIES_ORDER_INFO_SETTING_NAME,
content: orderInfos,
});
}
}
/** @internal */
export class OfflineCachingFavoritePropertiesStorage {
_connectivityInfo;
_impl;
_propertiesOfflineCache = new DictionaryWithReservations(iTwinAndIModelIdsKeyComparer);
_propertiesOrderOfflineCache = new DictionaryWithReservations(iTwinAndIModelIdsKeyComparer);
constructor(props) {
this._impl = props.impl;
/* c8 ignore next */
this._connectivityInfo = props.connectivityInfo ?? new ConnectivityInformationProvider();
this._connectivityInfo.onInternetConnectivityChanged.addListener(this.onConnectivityStatusChanged);
}
[Symbol.dispose]() {
isDisposable(this._connectivityInfo) && this._connectivityInfo[Symbol.dispose]();
}
get impl() {
return this._impl;
}
onConnectivityStatusChanged = (args) => {
if (args.status === InternetConnectivityStatus.Online) {
// note: we're copying the cached values to temp arrays because `saveProperties` and `savePropertiesOrder` both
// attempt to modify cache dictionaries
const propertiesCache = new Array();
this._propertiesOfflineCache.forEach((key, value) => propertiesCache.push({ properties: value, iTwinId: key[0], imodelId: key[1] }));
propertiesCache.forEach(async (cached) => this.saveProperties(cached.properties, cached.iTwinId, cached.imodelId));
const ordersCache = new Array();
this._propertiesOrderOfflineCache.forEach((key, value) => ordersCache.push({ order: value, iTwinId: key[0], imodelId: key[1] }));
ordersCache.forEach(async (cached) => this.savePropertiesOrder(cached.order, cached.iTwinId, cached.imodelId));
}
};
async loadProperties(iTwinId, imodelId) {
if (this._connectivityInfo.status === InternetConnectivityStatus.Online) {
try {
return await this._impl.loadProperties(iTwinId, imodelId);
}
catch {
// return from offline cache if the above fails
}
}
return this._propertiesOfflineCache.get([iTwinId, imodelId]);
}
async saveProperties(properties, iTwinId, imodelId) {
const key = [iTwinId, imodelId];
if (this._connectivityInfo.status === InternetConnectivityStatus.Offline) {
this._propertiesOfflineCache.set(key, properties);
return;
}
const reservationId = this._propertiesOfflineCache.reserve(key);
try {
await this._impl.saveProperties(properties, iTwinId, imodelId);
this._propertiesOfflineCache.reservedDelete(key, reservationId);
}
catch {
this._propertiesOfflineCache.reservedSet(key, properties, reservationId);
}
}
async loadPropertiesOrder(iTwinId, imodelId) {
if (this._connectivityInfo.status === InternetConnectivityStatus.Online) {
try {
return await this._impl.loadPropertiesOrder(iTwinId, imodelId);
}
catch {
// return from offline cache if the above fails
}
}
return this._propertiesOrderOfflineCache.get([iTwinId, imodelId]);
}
async savePropertiesOrder(orderInfos, iTwinId, imodelId) {
const key = [iTwinId, imodelId];
if (this._connectivityInfo.status === InternetConnectivityStatus.Offline) {
this._propertiesOrderOfflineCache.set(key, orderInfos);
return;
}
const reservationId = this._propertiesOrderOfflineCache.reserve(key);
try {
await this._impl.savePropertiesOrder(orderInfos, iTwinId, imodelId);
this._propertiesOrderOfflineCache.reservedDelete(key, reservationId);
}
catch {
this._propertiesOrderOfflineCache.reservedSet(key, orderInfos, reservationId);
}
}
}
class DictionaryWithReservations {
_impl;
constructor(compareKeys) {
this._impl = new Dictionary(compareKeys);
}
get(key) {
return this._impl.get(key)?.value;
}
forEach(func) {
this._impl.forEach((key, entry) => {
if (entry.value) {
func(key, entry.value);
}
});
}
reserve(key) {
const reservationId = Guid.createValue();
this._impl.set(key, { lastReservationId: reservationId });
return reservationId;
}
set(key, value) {
return this._impl.set(key, { value });
}
reservedSet(key, value, reservationId) {
const entry = this._impl.get(key);
if (entry && entry.lastReservationId === reservationId) {
this._impl.set(key, { value });
}
}
reservedDelete(key, reservationId) {
const entry = this._impl.get(key);
if (entry && entry.lastReservationId === reservationId) {
this._impl.delete(key);
}
}
}
/* c8 ignore next 4 */
function iTwinAndIModelIdsKeyComparer(lhs, rhs) {
const iTwinIdCompare = compareStrings(lhs[0] ?? "", rhs[0] ?? "");
return iTwinIdCompare !== 0 ? iTwinIdCompare : compareStrings(lhs[1] ?? "", rhs[1] ?? "");
}
/** @internal */
/* c8 ignore start */
export class NoopFavoritePropertiesStorage {
async loadProperties(_iTwinId, _imodelId) {
return undefined;
}
async saveProperties(_properties, _iTwinId, _imodelId) { }
async loadPropertiesOrder(_iTwinId, _imodelId) {
return undefined;
}
async savePropertiesOrder(_orderInfos, _iTwinId, _imodelId) { }
}
/* c8 ignore end */
/** @internal */
export class BrowserLocalFavoritePropertiesStorage {
_localStorage;
constructor(props) {
/* c8 ignore next */
this._localStorage = props?.localStorage ?? window.localStorage;
}
createFavoritesSettingItemKey(iTwinId, imodelId) {
return `${IMODELJS_PRESENTATION_SETTING_NAMESPACE}${FAVORITE_PROPERTIES_SETTING_NAME}?iTwinId=${iTwinId}&imodelId=${imodelId}`;
}
createOrderSettingItemKey(iTwinId, imodelId) {
return `${IMODELJS_PRESENTATION_SETTING_NAMESPACE}${FAVORITE_PROPERTIES_ORDER_INFO_SETTING_NAME}?iTwinId=${iTwinId}&imodelId=${imodelId}`;
}
async loadProperties(iTwinId, imodelId) {
const value = this._localStorage.getItem(this.createFavoritesSettingItemKey(iTwinId, imodelId));
if (!value) {
return undefined;
}
const properties = JSON.parse(value);
return new Set(properties);
}
async saveProperties(properties, iTwinId, imodelId) {
this._localStorage.setItem(this.createFavoritesSettingItemKey(iTwinId, imodelId), JSON.stringify([...properties]));
}
async loadPropertiesOrder(iTwinId, imodelId) {
const value = this._localStorage.getItem(this.createOrderSettingItemKey(iTwinId, imodelId));
if (!value) {
return undefined;
}
const orderInfos = JSON.parse(value).map((json) => ({
...json,
orderedTimestamp: new Date(json.orderedTimestamp),
}));
return orderInfos;
}
async savePropertiesOrder(orderInfos, iTwinId, imodelId) {
this._localStorage.setItem(this.createOrderSettingItemKey(iTwinId, imodelId), JSON.stringify(orderInfos));
}
}
//# sourceMappingURL=FavoritePropertiesStorage.js.map