UNPKG

@memberjunction/ng-shared

Version:

MemberJunction: MJ Explorer Angular Shared Package - utility functions and other reusable elements used across other MJ Angular packages within the MJ Explorer App - do not use outside of MJ Explorer.

318 lines 15.9 kB
import { OnDestroy } from '@angular/core'; import { WorkspaceStateManager, NavItem, ApplicationManager } from '@memberjunction/ng-base-application'; import { NavigationOptions } from './navigation.interfaces'; import { CompositeKey } from '@memberjunction/core'; import { BehaviorSubject, Subject, Observable } from 'rxjs'; import type { AppContextSnapshot } from '@memberjunction/ai-core-plus'; import { BaseResourceComponent } from './base-resource-component'; import * as i0 from "@angular/core"; /** * Event emitted when query params change on a tab (e.g., from browser back/forward). * Includes the tab ID so that only the component in the affected tab reacts, * preventing cross-tab leakage in multi-tab scenarios. */ export interface QueryParamChangeEvent { TabId: string; Params: Record<string, string>; } /** * Event emitted when a resource component reports its agent context or tools. * The shell (which owns the ComponentCacheManager) subscribes to these events * and updates the cache + active AppContextSnapshot accordingly. */ export interface AgentContextUpdate { /** The component instance that reported the update */ Caller: BaseResourceComponent; /** Dashboard-specific context for the agent (undefined = no change) */ AgentContext?: Record<string, unknown>; /** Client tools available from this dashboard (undefined = no change) */ AgentClientTools?: Array<{ Name: string; Description: string; ParameterSchema: Record<string, unknown>; Handler: (params: Record<string, unknown>) => Promise<unknown>; }>; } /** * System application ID for non-app-specific resources (fallback only) * Uses double underscore prefix to indicate system-level resource * @deprecated Prefer using NavigationService.getDefaultApplicationId() instead */ export declare const SYSTEM_APP_ID = "__explorer"; /** * Centralized navigation service that handles all navigation operations * with automatic shift-key detection for power user workflows */ export declare class NavigationService implements OnDestroy { private workspaceManager; private appManager; private shiftKeyPressed; private subscriptions; private queryParamChanged$; /** Observable that emits when query params change on a tab (back/forward navigation). */ QueryParamChanged$: Observable<QueryParamChangeEvent>; /** Cached Home app ID (null means not found, undefined means not checked) */ private _homeAppId; /** Cached Home app color */ private _homeAppColor; constructor(workspaceManager: WorkspaceStateManager, appManager: ApplicationManager); /** * Get the neutral color used for system-wide resources (entities, views, dashboards) * Returns a light neutral gray * @deprecated Use getDefaultAppColor() for better UX with Home app integration */ get ExplorerAppColor(): string; /** * Gets the default application ID for orphan resources. * Priority: Home app > Active app > SYSTEM_APP_ID * * This ensures orphan resources (entity records, dashboards, views opened directly) * are grouped under the Home app instead of being orphaned in the tab system. */ private getDefaultApplicationId; /** * Gets the default app color for orphan resources. * Returns Home app color if available, otherwise neutral gray. */ private getDefaultAppColor; /** * Clears the cached Home app info. * Call this if apps are reloaded or user logs out. */ clearHomeAppCache(): void; /** * Observable stream of agent context updates from resource components. * The shell subscribes to this to update the ComponentCacheManager and * push changes to the chat overlay's AppContextSnapshot.DashboardContext. */ readonly AgentContextUpdated$: Subject<AgentContextUpdate>; /** * Latest `AppContextSnapshot` published by the Explorer app shell. * * Why: any embedded `<mj-conversation-chat-area>` instance outside the * floating chat overlay (Form Builder cockpit, future domain dashboards * that pop their own AI pane) needs to feed the SAME context the overlay * does so the agent sees what app + view + dashboard state the user is * looking at. Without this, the agent only sees the embedder's narrow * `AdditionalContext` slice and treats the user as if they have no app * context at all — which is the bug we just fixed. * * `MJExplorerAppComponent` is the canonical publisher (it owns the * snapshot construction); consumers SUBSCRIBE and bind the value to * their chat-area's `[appContext]`. Non-Explorer apps (custom MJ apps * that don't include explorer-app at all) build their own snapshot via * `BuildAppContextSnapshot()` in `@memberjunction/ai-core-plus`. * * Initial value is `null`; the publisher emits the first real snapshot * after the active app + nav state resolve on bootstrap. */ readonly AppContextSnapshot$: BehaviorSubject<AppContextSnapshot | null>; /** * Push a fresh AppContextSnapshot. Called by MJExplorerAppComponent * after each (a) app/tab change, (b) `handleAgentContextUpdate` * merging in `AdditionalContext` from a dashboard. Idempotent — no * de-duplication; embedders should treat the stream as "the latest * value is canonical." */ PublishAppContextSnapshot(snapshot: AppContextSnapshot | null): void; /** * Report the current agent-visible state from a resource component. * Call this whenever the dashboard's internal state changes (tab switch, * filter change, pipeline status change, drill-down, etc.). * * @param caller - Pass `this` from the calling component. Used to match * against the ComponentCacheManager to identify which cached component * this update belongs to. * @param context - Key-value pairs representing dashboard state the agent * should know about. Each dashboard defines its own shape. */ SetAgentContext(caller: BaseResourceComponent, context: Record<string, unknown>): void; /** * Register the client tools available from a resource component. * Call this on component init and whenever the available tools change. * Tools are automatically unregistered when the component becomes inactive * (tab switch) and re-registered when it becomes active again. * * @param caller - Pass `this` from the calling component. * @param tools - Array of tool definitions with Name, Description, * ParameterSchema (JSON Schema), and Handler function. */ SetAgentClientTools(caller: BaseResourceComponent, tools: Array<{ Name: string; Description: string; ParameterSchema: Record<string, unknown>; Handler: (params: Record<string, unknown>) => Promise<unknown>; }>): void; ngOnDestroy(): void; /** * Set up global keyboard event listeners to track shift key state */ private setupGlobalShiftKeyDetection; /** * Get current shift key state */ private isShiftPressed; /** * Determine if a new tab should be forced based on options and shift key state */ private shouldForceNewTab; /** * Returns whether the caller should use OpenTabForced (force-new path) or * OpenTab (replace-temp path). * * Rule: only honor an explicit force-new request — from the user via * shift+click, or from the caller via `options.forceNewTab`. We deliberately * do NOT apply heuristics that auto-switch the workspace out of * single-resource mode on cross-resource navigation. A previous version of * this method tried to do that ("force new if single-resource + different * resource") and it caused a regression: every plain hyperlink click on a * record opened a new tab and dropped the user into multi-tab mode, even * though they didn't ask for it. That violated the principle that mode * transitions are user-driven (shift) or explicitly requested (options). * * If a particular caller really needs the parent context preserved when * creating/navigating to a child resource (e.g. "+New" on a related-entity * grid inside an open record), the caller should pass `forceNewTab: true` * in `NavigationOptions`. That keeps intent explicit at the call site * instead of buried in a global heuristic. */ private handleSingleResourceModeTransition; /** * Check if a tab request matches an existing tab's resource */ private isSameResource; /** * Open a navigation item within an app */ OpenNavItem(appId: string, navItem: NavItem, appColor: string, options?: NavigationOptions): string; /** * Open an entity record view * Uses Home app if available, otherwise falls back to active app or system app */ OpenEntityRecord(entityName: string, recordPkey: CompositeKey, options?: NavigationOptions): string; /** * Open a view * Uses Home app if available, otherwise falls back to active app or system app */ OpenView(viewId: string, viewName: string, options?: NavigationOptions): string; /** * Open a dashboard * Uses Home app if available, otherwise falls back to active app or system app */ OpenDashboard(dashboardId: string, dashboardName: string, options?: NavigationOptions): string; /** * Open a report * Uses Home app if available, otherwise falls back to active app or system app */ OpenReport(reportId: string, reportName: string, options?: NavigationOptions): string; /** * Open an artifact * Artifacts are versioned content containers (reports, dashboards, UI components, etc.) * Uses Home app if available, otherwise falls back to active app or system app */ OpenArtifact(artifactId: string, artifactName?: string, options?: NavigationOptions): string; /** * Open a dynamic view * Dynamic views are entity-based views with custom filters, not saved views * Uses Home app if available, otherwise falls back to active app or system app */ OpenDynamicView(entityName: string, extraFilter?: string, options?: NavigationOptions): string; /** * Open a query * Uses Home app if available, otherwise falls back to active app or system app */ OpenQuery(queryId: string, queryName: string, options?: NavigationOptions): string; /** * Open a new entity record creation form * Uses Home app if available, otherwise falls back to active app or system app * @param entityName The name of the entity to create a new record for * @param options Navigation options including optional newRecordValues for pre-populating fields */ OpenNewEntityRecord(entityName: string, options?: NavigationOptions): string; /** * Open a universal search results tab for the given query. * This is the primary way to open search results from anywhere in the application. * * @param query The search query text * @param searchOptions Optional search-specific options (e.g., minRelevance, scopeIDs) * @param options Navigation options */ OpenSearch(query: string, searchOptions?: { minRelevance?: number; scopeIDs?: string[]; }, options?: NavigationOptions): string; /** * Navigate to a nav item by name within the current or specified application. * Allows passing additional configuration parameters to merge with the nav item's config. * This is useful for cross-resource navigation where a component needs to navigate * to another nav item with specific parameters (e.g., navigate to Conversations with a specific conversationId). * * @param navItemName The label/name of the nav item to navigate to * @param configuration Additional configuration to merge (e.g., conversationId, artifactId) * @param appId Optional app ID (defaults to current active app) * @param options Navigation options * @returns The tab ID if successful, null if nav item not found */ OpenNavItemByName(navItemName: string, configuration?: Record<string, unknown>, appId?: string, options?: NavigationOptions): Promise<string | null>; /** * Switch to an application by ID. * This sets the app as active and either opens a specific nav item or creates a default tab. * If the requested nav item already has an open tab, switches to that tab instead of creating a new one. * @param appId The application ID to switch to * @param navItemName Optional name of a nav item to open within the app. If provided, opens that nav item. * @param queryParams Optional query params to apply to the target tab. Applied SYNCHRONOUSLY once the * target tab is active — critical when navigating (e.g. from a Home pin) to an * app whose resource component is cached: the params must be in the tab config * BEFORE the tab-container reattaches the cached component, otherwise the cache * restores its own (stale) saved params and the navigation intent is lost. */ SwitchToApp(appId: string, navItemName?: string, queryParams?: Record<string, string | null>): Promise<void>; /** * Update the query params for the currently active tab. * This updates the tab's configuration and triggers a URL sync via the shell's * workspace configuration subscription. * * Use this instead of directly calling router.navigate() to ensure proper * URL management that respects app-scoped routes. * * @param queryParams Object containing query param key-value pairs. * Use null values to remove a query param. * @example * // Add or update query params * navigationService.UpdateActiveTabQueryParams({ category: 'abc123', dashboard: 'xyz789' }); * * // Remove a query param * navigationService.UpdateActiveTabQueryParams({ category: null }); */ UpdateActiveTabQueryParams(queryParams: Record<string, string | null>): void; /** * Notify subscribers that query params changed on a specific tab. * Called by the shell when back/forward navigation changes query params on the active tab. * The notification includes the tab ID so only the component in that tab reacts. */ NotifyQueryParamsChanged(tabId: string, params: Record<string, string>): void; /** * Reactively observe the query params for a specific tab. * * Backed by the workspace BehaviorSubject, so a subscriber receives the current * params *immediately* on subscribe AND every subsequent change — including the * deep-link params that the ResourceResolver merges into the tab configuration on * a cold/direct URL load. * * This is the race-free counterpart to {@link NotifyQueryParamsChanged} (a plain * Subject that drops events fired before a component has subscribed). A resource * component that mounts from workspace restoration can subscribe here and still * pick up its initial deep-link state regardless of whether the params landed in * the tab config before or after it mounted. */ ObserveTabQueryParams(tabId: string): Observable<Record<string, string>>; private shallowParamsEqual; /** * Apply query params to a specific tab by ID. * Merges with any existing query params on the tab. Use null values to remove params. */ private applyQueryParamsToTab; static ɵfac: i0.ɵɵFactoryDeclaration<NavigationService, never>; static ɵprov: i0.ɵɵInjectableDeclaration<NavigationService>; } //# sourceMappingURL=navigation.service.d.ts.map