UNPKG

@itwin/presentation-frontend

Version:

Frontend of iModel.js Presentation library

309 lines • 13.6 kB
/*--------------------------------------------------------------------------------------------- * 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