{"version":3,"file":"c8y-ngx-components-context-dashboard-state.mjs","sources":["../../context-dashboard-state/context-dashboard-state.service.ts","../../context-dashboard-state/c8y-ngx-components-context-dashboard-state.ts"],"sourcesContent":["import { Injectable, inject, signal } from '@angular/core';\nimport { cloneDeep, isEqual } from 'lodash-es';\nimport { BehaviorSubject } from 'rxjs';\nimport { InventoryService } from '@c8y/client';\n\n/**\n * Dashboard structure with embedded state at the c8y_Dashboard.dashboardState path.\n *\n * This interface defines the standard Cumulocity dashboard structure where custom\n * state is stored in the c8y_Dashboard.dashboardState property. The actual state\n * type is generic to support different dashboard implementations.\n *\n * @typeParam StateType - The type of state object stored in dashboardState\n */\ninterface DashboardWithState<StateType> {\n  /** Cumulocity dashboard configuration containing state and other dashboard properties */\n  c8y_Dashboard?: {\n    /** Custom state object specific to this dashboard's context */\n    dashboardState?: StateType;\n    [key: string]: any;\n  };\n  [key: string]: any;\n}\n\n/**\n * Manages dashboard-level state with dirty tracking and persistence capabilities.\n *\n * This service provides a centralized state management system for context-aware dashboards.\n * It tracks the original (default) state from the dashboard configuration, maintains a\n * working copy of the state, and determines when changes need to be saved based on deep\n * equality comparison.\n *\n * **Key responsibilities:**\n * - Extract and store the dashboard's default state on selection\n * - Maintain a mutable working state (globalState) that can be updated\n * - Track whether current state differs from default (dirty tracking)\n * - Enable/disable save functionality based on state changes\n * - Persist state changes back to the dashboard via inventory API\n *\n * @typeParam StateType - The type of state object managed by this service\n *\n * @example\n * ```ts\n * // Dashboard with global time context state\n * interface TimeContextState {\n *   dateFrom: string;\n *   dateTo: string;\n *   interval: string;\n * }\n *\n * const stateService = inject(ContextDashboardStateService<TimeContextState>);\n *\n * // Set dashboard and extract its default state\n * stateService.setSelectedDashboard(dashboard);\n *\n * // Update state (triggers dirty tracking)\n * stateService.updateGlobalState({ dateFrom: '2024-01-01' });\n *\n * // Check if save is needed\n * stateService.isSaveDisabled.subscribe(disabled => {\n *   if (!disabled) {\n *     // Save button enabled - state has changed\n *   }\n * });\n * ```\n */\n@Injectable({\n  providedIn: 'root'\n})\nexport class ContextDashboardStateService<StateType extends object> {\n  /** Currently selected dashboard object with its embedded state */\n  readonly selectedDashboard = signal<DashboardWithState<StateType> | null>(null);\n\n  /** Immutable snapshot of the dashboard's original state, used for comparison */\n  readonly dashboardDefaultState = signal<StateType | null>(null);\n\n  /** Current working state that can be modified, separate from the default state */\n  readonly globalState = signal<StateType | null>(null);\n\n  /** Observable for dashboard selection changes (legacy support) */\n  readonly selected$ = new BehaviorSubject<DashboardWithState<StateType> | null>(null);\n\n  /** Observable indicating whether save should be disabled (true when no changes detected) */\n  readonly isSaveDisabled = new BehaviorSubject<boolean>(true);\n\n  /** Observable emitting the dashboard object after successful save operations */\n  readonly dashboardSaved = new BehaviorSubject<any>(null);\n\n  readonly inventory = inject(InventoryService);\n\n  /**\n   * Selects a dashboard and initializes state management for it.\n   *\n   * This method performs the complete initialization workflow when a dashboard is selected:\n   * 1. Extracts the dashboard's embedded state from c8y_Dashboard.dashboardState\n   * 2. Creates an immutable snapshot as the default state (for dirty tracking)\n   * 3. Creates a working copy as the current global state (for modifications)\n   * 4. Resets the save button state to disabled (no changes yet)\n   *\n   * **Deep cloning strategy:**\n   * Both default state and global state are deep cloned to ensure complete isolation.\n   * This prevents accidental mutations from affecting the comparison baseline.\n   *\n   * **Null handling:**\n   * When null/undefined is passed, all state is cleared and signals/observables are reset.\n   *\n   * @param dashboard - Dashboard object containing state at c8y_Dashboard.dashboardState,\n   *   or null to clear the selection\n   */\n  setSelectedDashboard(dashboard: DashboardWithState<StateType>): void {\n    if (!dashboard) {\n      this.selected$.next(dashboard);\n      this.dashboardDefaultState.set(null);\n      this.globalState.set(null);\n      this.selectedDashboard.set(null);\n      return;\n    }\n\n    const defaultState = this.extractStateFromDashboard(dashboard);\n    const initialDefaultState = cloneDeep(defaultState) ?? null;\n\n    this.dashboardDefaultState.set(initialDefaultState);\n    this.globalState.set(initialDefaultState ? cloneDeep(initialDefaultState) : null);\n    this.selectedDashboard.set(dashboard);\n\n    this.selected$.next(dashboard);\n    this.isSaveDisabled.next(true);\n  }\n\n  /**\n   * Extracts state from the standard dashboard structure.\n   *\n   * Retrieves the state object stored at the conventional path:\n   * `dashboard.c8y_Dashboard.dashboardState`\n   *\n   * @param dashboard - Dashboard object with potential state at c8y_Dashboard.dashboardState\n   * @returns Extracted state object, or null if dashboard is null or has no state\n   */\n  extractStateFromDashboard(dashboard: DashboardWithState<StateType> | null): StateType | null {\n    return dashboard?.c8y_Dashboard?.dashboardState ?? null;\n  }\n\n  /**\n   * Merges partial state updates into the current working state with dirty tracking.\n   *\n   * This method applies partial updates to the global state using a shallow merge strategy,\n   * then performs deep equality comparison against the default state to determine if save\n   * should be enabled.\n   *\n   * @param newState - Partial state object with properties to merge into current state\n   *\n   * @example\n   * ```ts\n   * // Initial state: { dateFrom: '2024-01-01', dateTo: '2024-01-02' }\n   * stateService.updateGlobalState({ dateFrom: '2024-02-01' });\n   * // Result: { dateFrom: '2024-02-01', dateTo: '2024-01-02' }\n   * // isSaveDisabled = false (changed from default)\n   * ```\n   */\n  updateGlobalState(newState: Partial<StateType>): void {\n    const currentState = this.globalState();\n\n    if (currentState === null && Object.keys(newState).length === 0) {\n      return;\n    }\n\n    const updatedState = {\n      ...(currentState ? structuredClone(currentState) : ({} as StateType)),\n      ...structuredClone(newState)\n    };\n\n    if (!isEqual(currentState, updatedState)) {\n      this.globalState.set(updatedState);\n\n      const defaultState = this.dashboardDefaultState();\n      this.isSaveDisabled.next(isEqual(defaultState, updatedState));\n    }\n  }\n\n  /**\n   * Returns a deep clone of the current working state.\n   *\n   * The returned object is a deep copy, so modifications won't affect the internal state.\n   * Use this when you need to read state without risking accidental mutations.\n   *\n   * @returns Deep cloned copy of current state, or null if no state exists\n   */\n  getGlobalState(): StateType | null {\n    const state = this.globalState();\n    return structuredClone(state);\n  }\n\n  /**\n   * Reverts the working state back to the dashboard's original default state.\n   *\n   * This effectively discards all changes made since the dashboard was selected,\n   * resetting to the state that was extracted from c8y_Dashboard.dashboardState.\n   * Also disables the save button since state now matches the default.\n   *\n   * **Use case:**\n   * Called when user clicks \"Cancel\" or \"Reset\" to discard unsaved changes.\n   */\n  resetGlobalState(): void {\n    const defaultState = this.dashboardDefaultState();\n    this.globalState.set(defaultState ? defaultState : null);\n    this.isSaveDisabled.next(true);\n  }\n\n  /**\n   * Persists state changes to the dashboard via the Cumulocity inventory API.\n   *\n   * Updates the dashboard's c8y_Dashboard.dashboardState property with the provided\n   * state object and saves it to the backend. Creates the c8y_Dashboard object if\n   * it doesn't exist.\n   *\n   * **Note:** This method doesn't automatically update the local signals. After a\n   * successful save, you should emit to dashboardSaved to notify listeners.\n   *\n   * @param mo - Managed object (dashboard) to update\n   * @param state - State object to persist to c8y_Dashboard.dashboardState\n   * @returns Promise resolving to the updated dashboard object from the API\n   * @throws Error if the inventory update fails\n   */\n  async saveDashboardState(mo: any, state: Record<string, any>): Promise<any> {\n    if (!('c8y_Dashboard' in mo)) {\n      mo.c8y_Dashboard = {};\n    }\n    mo.c8y_Dashboard.dashboardState = state;\n    return await this.inventory.update(mo);\n  }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;;;AAwBA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyCG;MAIU,4BAA4B,CAAA;AAHzC,IAAA,WAAA,GAAA;;AAKW,QAAA,IAAA,CAAA,iBAAiB,GAAG,MAAM,CAAuC,IAAI,6DAAC;;AAGtE,QAAA,IAAA,CAAA,qBAAqB,GAAG,MAAM,CAAmB,IAAI,iEAAC;;AAGtD,QAAA,IAAA,CAAA,WAAW,GAAG,MAAM,CAAmB,IAAI,uDAAC;;AAG5C,QAAA,IAAA,CAAA,SAAS,GAAG,IAAI,eAAe,CAAuC,IAAI,CAAC;;AAG3E,QAAA,IAAA,CAAA,cAAc,GAAG,IAAI,eAAe,CAAU,IAAI,CAAC;;AAGnD,QAAA,IAAA,CAAA,cAAc,GAAG,IAAI,eAAe,CAAM,IAAI,CAAC;AAE/C,QAAA,IAAA,CAAA,SAAS,GAAG,MAAM,CAAC,gBAAgB,CAAC;AA8I9C,IAAA;AA5IC;;;;;;;;;;;;;;;;;;AAkBG;AACH,IAAA,oBAAoB,CAAC,SAAwC,EAAA;QAC3D,IAAI,CAAC,SAAS,EAAE;AACd,YAAA,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC;AAC9B,YAAA,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,IAAI,CAAC;AACpC,YAAA,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC;AAC1B,YAAA,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC;YAChC;QACF;QAEA,MAAM,YAAY,GAAG,IAAI,CAAC,yBAAyB,CAAC,SAAS,CAAC;QAC9D,MAAM,mBAAmB,GAAG,SAAS,CAAC,YAAY,CAAC,IAAI,IAAI;AAE3D,QAAA,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,mBAAmB,CAAC;AACnD,QAAA,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,mBAAmB,GAAG,SAAS,CAAC,mBAAmB,CAAC,GAAG,IAAI,CAAC;AACjF,QAAA,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,SAAS,CAAC;AAErC,QAAA,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC;AAC9B,QAAA,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC;IAChC;AAEA;;;;;;;;AAQG;AACH,IAAA,yBAAyB,CAAC,SAA+C,EAAA;AACvE,QAAA,OAAO,SAAS,EAAE,aAAa,EAAE,cAAc,IAAI,IAAI;IACzD;AAEA;;;;;;;;;;;;;;;;AAgBG;AACH,IAAA,iBAAiB,CAAC,QAA4B,EAAA;AAC5C,QAAA,MAAM,YAAY,GAAG,IAAI,CAAC,WAAW,EAAE;AAEvC,QAAA,IAAI,YAAY,KAAK,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE;YAC/D;QACF;AAEA,QAAA,MAAM,YAAY,GAAG;AACnB,YAAA,IAAI,YAAY,GAAG,eAAe,CAAC,YAAY,CAAC,GAAI,EAAgB,CAAC;YACrE,GAAG,eAAe,CAAC,QAAQ;SAC5B;QAED,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,YAAY,CAAC,EAAE;AACxC,YAAA,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,YAAY,CAAC;AAElC,YAAA,MAAM,YAAY,GAAG,IAAI,CAAC,qBAAqB,EAAE;AACjD,YAAA,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC;QAC/D;IACF;AAEA;;;;;;;AAOG;IACH,cAAc,GAAA;AACZ,QAAA,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,EAAE;AAChC,QAAA,OAAO,eAAe,CAAC,KAAK,CAAC;IAC/B;AAEA;;;;;;;;;AASG;IACH,gBAAgB,GAAA;AACd,QAAA,MAAM,YAAY,GAAG,IAAI,CAAC,qBAAqB,EAAE;AACjD,QAAA,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,YAAY,GAAG,YAAY,GAAG,IAAI,CAAC;AACxD,QAAA,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC;IAChC;AAEA;;;;;;;;;;;;;;AAcG;AACH,IAAA,MAAM,kBAAkB,CAAC,EAAO,EAAE,KAA0B,EAAA;AAC1D,QAAA,IAAI,EAAE,eAAe,IAAI,EAAE,CAAC,EAAE;AAC5B,YAAA,EAAE,CAAC,aAAa,GAAG,EAAE;QACvB;AACA,QAAA,EAAE,CAAC,aAAa,CAAC,cAAc,GAAG,KAAK;QACvC,OAAO,MAAM,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC;IACxC;+GAhKW,4BAA4B,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,UAAA,EAAA,CAAA,CAAA;AAA5B,IAAA,SAAA,IAAA,CAAA,KAAA,GAAA,EAAA,CAAA,qBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,SAAA,EAAA,QAAA,EAAA,EAAA,EAAA,IAAA,EAAA,4BAA4B,cAF3B,MAAM,EAAA,CAAA,CAAA;;4FAEP,4BAA4B,EAAA,UAAA,EAAA,CAAA;kBAHxC,UAAU;AAAC,YAAA,IAAA,EAAA,CAAA;AACV,oBAAA,UAAU,EAAE;AACb,iBAAA;;;ACpED;;AAEG;;;;"}