@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
TypeScript
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