import { CallContextLike, HoistModel, LoadSpec, PlainObject, TaskObserver, Thunkable } from '@xh/hoist/core'; import type { ViewManagerProvider } from '@xh/hoist/core'; import { ReactNode } from 'react'; import { ViewInfo } from './ViewInfo'; import { View } from './View'; export interface ViewCreateSpec { name: string; group: string; description: string; isShared: boolean; isGlobal: boolean; isPinned?: boolean; value: PlainObject; } export type ViewUpdateSpec = Partial>; export interface ViewUserState { currentView?: string; userPinned: Record; autoSave: boolean; } /** * Configuration for a {@link ViewManagerModel} - persists and manages named user views * (saved configurations) for grids, dashboards, or other stateful components. * * @see ViewManagerModel */ export interface ViewManagerConfig { /** * Required discriminator for the particular class of views to be loaded and managed by this * model. Used to set the `type` property on all JSONBlobs persisted by this model. * * Choose something descriptive and specific enough to be identifiable and allow for different * ViewManagers to be added to your app in the future - e.g. `portfolioGridView` or * `tradeBlotterDashboard`. */ type: string; /** * Optional user-facing qualifier (default "default") for the special in-code default view * option, if enabled. Will be prepended to `typeDisplayName`. * * A use case is to support a ViewManager persisted Dashboard, where the in-code default is an * empty layout, this config is set to "New", and the `typeDisplayName` is set to "Dashboard". * This results in a "New Dashboard" option in the menu, allowing users to quickly access a * blank dashboard to start building from scratch, while forcing a save-as to persist. */ defaultDisplayName?: string; /** True (default) to allow users to opt-in to auto-saving changes to their current view. */ enableAutoSave?: boolean; /** * True (default) to allow the user to select a special view from the menu that restores all * persisted objects to their in-code defaults. If not enabled, at least one globally shared * view should be added to provide an initial selection for users without any private views. */ enableDefault?: boolean; /** * True (default) to enable "global" views - i.e. views that are not owned by a user and are * available to all. At least some users should have `manageGlobal` set to true to allow * creation and management of these views. */ enableGlobal?: boolean; /** True (default) to allow users to share their views with other users. */ enableSharing?: boolean; /** * User-facing qualifier for labelling globally shared views - default "global". A use case * would be to set to the name of the company/team that manages these canonical views, e.g. * "Acme Corp". */ globalDisplayName?: string; /** * Function to determine the initial view for a user, when they have no prior view already * persisted. Called with a list of views available to the current user. * * Must be set when `enableDefault: false`. Developers should take care to return *some* view * in this case, if any are available. If no view is returned, the control will be forced to * fall back to the in-code default. */ initialViewSpec?: (views: ViewInfo[]) => ViewInfo; /** * Optional discriminator for the particular area of an app in which this instance of the * ViewManager appears, for apps that have multiple manager instances that load the same `type` * of views. A particular `currentView` and `pendingValue` will be maintained for each instance, * but all other options and the available library of views will be shared across the `type`. */ instance?: string; /** * True to allow the user to creat and manage Global views. Apps are expected to commonly set * this based on user roles - e.g. `XH.getUser().hasRole('MANAGE_GRID_VIEWS')`. */ manageGlobal?: Thunkable; /** * True (default) to save pending state to SessionStorage so that it can be restored across * browser refreshes. Unlike auto-save, this does not write to the database. */ preserveUnsavedChanges?: boolean; /** * User-facing display name for the type of views being managed - e.g. "report" or "dashboard". * Displayed in the `ViewManager` menu and associated management dialogs and prompts. * Defaulted from `type` if not provided. */ typeDisplayName?: string; /** * Optional render function to customize the BlueprintJS `menuItem` shown for each view in the * ViewManager menu. */ viewMenuItemFn?: (view: ViewInfo, model: ViewManagerModel) => ReactNode; } /** * ViewManagerModel coordinates the loading, saving, and management of user-defined bundles of * {@link Persistable} component/model state. * * - Models to be persisted are bound to this model via their `persistWith` config. One or more * models can be bound to a single ViewManagerModel, allowing a single view to capture the state * of multiple components - e.g. grouping and filtering options along with grid state. * - Views are persisted back to the server as JsonBlob objects. * - Views can be private to their owner, or optionally enabled for sharing to (all) other users. * - Views can be marked as pinned for quick access. * - See the desktop {@link ViewManager} component - the initial Hoist UI for this model. * * See the view manager package README (`cmp/viewmanager/README.md`) for architecture, * integration patterns, and access control configuration. */ export declare class ViewManagerModel extends HoistModel { telemetryPrefix: string; /** * Factory to create new instances of this model and await its initial load before binding to * any persistable component models. This ensures that bound models will have the expected * initial persisted state applied within their constructor, before their components have * rendered, and avoids thrashing of component state during initial load. * * To minimize the impact this async requirement has on the design and lifecycle of individual * components within an app, consider eagerly constructing any viewManagerModels required within * your `AppModel.initAsync` method and saving a reference to them there for component models * to then use when they are mounted. The VM model instances will then be "ready to go" and * usable within model constructors. (Initializing and referencing from one or more app * services would be another, similar option.) * * Note that this method may throw if the ViewManager cannot be initialized successfully, * but should generally fail quietly due to the early instantiation. */ static createAsync(config: ViewManagerConfig, ctx?: CallContextLike): Promise; /** Immutable configuration for this model. */ readonly type: string; readonly instance: string; readonly typeDisplayName: string; readonly defaultDisplayName: string; readonly globalDisplayName: string; readonly viewMenuItemFn: (view: ViewInfo, model: ViewManagerModel) => ReactNode; readonly enableAutoSave: boolean; readonly enableDefault: boolean; readonly enableGlobal: boolean; readonly enableSharing: boolean; readonly preserveUnsavedChanges: boolean; readonly manageGlobal: boolean; readonly initialViewSpec: (views: ViewInfo[]) => ViewInfo; /** Current view. Will not include uncommitted changes */ view: View; /** Loaded saved view library - both private and global */ views: ViewInfo[]; /** * Map of user's preferred pinned state for views. * * Note that the actual pinned state for the views is determined by this value, layered * over the default state of the views themselves. */ userPinned: Record; /** * True if user has opted-in to automatically saving changes to personal views (if auto-save * generally available as per `enableAutoSave`). */ autoSave: boolean; /** * TaskObserver linked to {@link selectViewAsync}. If a change to the active view is likely to * require intensive layout/grid work, consider masking affected components with this task. */ selectTask: TaskObserver; /** TaskObserver linked to {@link saveAsync}. */ saveTask: TaskObserver; /** Unsaved changes on the current view.*/ private pendingValue; /** * Array of {@link ViewManagerProvider} instances bound to this model. Used to proactively push * state to the target components when the model's selected `value` changes. */ private providers; /** Data access for persisting views. */ private dataAccess; get isValueDirty(): boolean; get isViewSavable(): boolean; get isViewAutoSavable(): boolean; get autoSaveUnavailableReason(): string; get pinnedViews(): ViewInfo[]; /** Views owned by me */ get ownedViews(): ViewInfo[]; /** Views shared *with* me */ get sharedViews(): ViewInfo[]; /** Global views */ get globalViews(): ViewInfo[]; /** True if any async tasks are pending. */ get isLoading(): boolean; /** * Use the static {@link createAsync} factory to create an instance of this model and await its * initial load before binding to persistable components. */ private constructor(); doLoadAsync(loadSpec: LoadSpec): Promise; selectViewAsync(view: string | ViewInfo, opts?: { alertUnsavedChanges: boolean; }): Promise; saveAsAsync(spec: ViewCreateSpec): Promise; saveAsync(): Promise; resetAsync(): Promise; getValue(): Partial; setValue(value: Partial): void; userPin(view: ViewInfo): void; userUnpin(view: ViewInfo): void; isUserPinned(view: ViewInfo): boolean | null; /** * Validate a name for a view. * @param name - candidate name to validate * @param existing - existing view that will have the name. null if the name is for a new view. * @param isGlobal - true if the name is for a global view. */ validateViewNameAsync(name: string, existing: ViewInfo, isGlobal: boolean): Promise; /** Update all aspects of a view's metadata.*/ updateViewInfoAsync(view: ViewInfo, updates: ViewUpdateSpec): Promise>; /** Apply the same metadata updates to multiple views. */ updateViewsInfoAsync(views: ViewInfo[], updates: ViewUpdateSpec): Promise; /** * Rename or re-parent a group, cascading to every view at or nested under `from`. Groups are * namespaced separately for global vs. user-owned views - `isGlobal` selects which to rename. */ renameGroupAsync(from: string, to: string, isGlobal: boolean): Promise; deleteViewsAsync(toDelete: ViewInfo[]): Promise; /** * Called by {@link ViewManagerProvider} to receive state changes from this model. * @internal */ registerProvider(provider: ViewManagerProvider): void; /** * Called by {@link ViewManagerProvider} to stop receiving state changes. * @internal */ unregisterProvider(provider: ViewManagerProvider): void; private initAsync; private unsavedChangesReaction; private autoSaveReaction; private stateReactions; private loadViewAsync; private maybeAutoSaveAsync; private setAsView; private handleException; private noteSuccess; private get pendingValueStorageKey(); /** * Stringify and parse to ensure that any value set here is valid, serializable JSON. */ private cleanState; private confirmDiscardChangesAsync; private maybeConfirmSaveAsync; }