import { IRegion, region } from "@uxland/regions"; import { LitElement, css, html, unsafeCSS } from "lit"; import { state } from "lit/decorators.js"; import { PrimariaRegionHost, shellApi } from "../../../api/api"; import { disposeShell } from "../../../disposer"; import { BROKER_EVENTS } from "../../../api/broker/broker-events"; import { translate } from "../../../locales"; import { shellViews } from "./constants"; import styles from "./styles.css?inline"; import { template } from "./template"; import { BrokerDisposableHandler } from "@uxland/harmonix"; export class PrimariaShell extends PrimariaRegionHost(LitElement) { render() { return html`${template(this)}`; } static styles = css` ${unsafeCSS(styles)} `; connectedCallback() { super.connectedCallback(); this._subscribeEvents(); } disconnectedCallback() { super.disconnectedCallback(); this._unsubscribeEvents(); } @region({ targetId: "menu-region-container", name: shellApi.regionManager.regions.shell.navigationMenu }) navigationMenuRegion: IRegion | undefined; @region({ targetId: "main-region-container", name: shellApi.regionManager.regions.shell.main }) mainRegion: IRegion | undefined; @region({ targetId: "quick-actions-region-container", name: shellApi.regionManager.regions.shell.quickActions }) quickActionsRegion: IRegion | undefined; @region({ targetId: "floating-region-container", name: shellApi.regionManager.regions.shell.floating }) floatingRegion: IRegion | undefined; @region({ targetId: "navigation-menu-lower-region-container", name: shellApi.regionManager.regions.shell.navigationLowerLeftMenu, }) navLowerLeftMenu: IRegion | undefined; @state() sidebarExpanded = false; @state() viewSelected = shellViews.shell; @state() error: { message: string }; @state() quickActionBusy = false; _toggleSidebar() { this.sidebarExpanded = !this.sidebarExpanded; } private subscriptions: BrokerDisposableHandler[] = []; _subscribeEvents() { this.subscriptions.push( shellApi.broker.subscribe(BROKER_EVENTS.shell.appCrashed, (error: { message: string }) => { this._handleError(error); }), ); this.subscriptions.push( shellApi.broker.subscribe(BROKER_EVENTS.shell.refreshTokenFailed, (detail: any) => { this._handleError({ message: translate("errors.session") }); }), ); this.subscriptions.push( shellApi.broker.subscribe(BROKER_EVENTS.shell.mpidHeaderInvalid, (detail: any) => { this._handleError({ message: translate("errors.invalidPatient") }); }), ); this.subscriptions.push( shellApi.broker.subscribe(BROKER_EVENTS.shell.quickActionBusyChanged, (detail: { busy: boolean }) => { this.quickActionBusy = detail.busy; }), ); this.subscriptions.push( shellApi.broker.subscribe(BROKER_EVENTS.shell.scrollToNavItemRequested, (navItemMenuKey: string) => { this._scrollToNavItem(navItemMenuKey); }), ); } _handleError(error: { message: string }) { this.viewSelected = shellViews.error; this.error = error; disposeShell(); } _unsubscribeEvents() { this.subscriptions.forEach((s) => s.dispose()); } async _scrollToNavItem(navItemMenuKey: string) { const region = await shellApi.regionManager.getRegion(shellApi.regionManager.regions.shell.navigationMenu); const allViews = region.currentActiveViews; // Extract the actual view id from the key (remove plugin prefix) // navItemMenuKey comes as "primaria-shell::pdf-viewer", we need just "pdf-viewer" const viewId = navItemMenuKey.includes("::") ? navItemMenuKey.split("::")[1] : navItemMenuKey; const targetView = allViews.find((view: any) => view.id === viewId); if (!targetView) { shellApi.broker.publish(BROKER_EVENTS.shell.scrollToNavItemCompleted, { scrollTop: 0, containerTop: 0, itemIndex: -1, }); return; } // Sort views by sortHint to find the correct position const sortedViews = [...allViews].sort((a: any, b: any) => { const sortHintA = a.sortHint || "999"; const sortHintB = b.sortHint || "999"; return sortHintA.localeCompare(sortHintB); }); const targetIndex = sortedViews.findIndex((view: any) => view.id === viewId); if (targetIndex === -1) { shellApi.broker.publish(BROKER_EVENTS.shell.scrollToNavItemCompleted, { scrollTop: 0, containerTop: 0, itemIndex: -1, }); return; } const menuContainer = this.shadowRoot?.querySelector("#menu-region-container") as HTMLElement; if (!menuContainer) { shellApi.broker.publish(BROKER_EVENTS.shell.scrollToNavItemCompleted, { scrollTop: 0, containerTop: 0, itemIndex: targetIndex, }); return; } const itemHeight = 51; const targetPosition = itemHeight * targetIndex; const containerHeight = menuContainer.clientHeight; // Scroll to position the item in the middle of the container const scrollPosition = targetPosition - containerHeight / 2 + itemHeight / 2; menuContainer.scrollTo({ top: Math.max(0, scrollPosition), behavior: "smooth", }); // Wait for scroll to complete and then publish the completed event setTimeout(() => { const containerRect = menuContainer.getBoundingClientRect(); // Get all nav items from the container const navItems = Array.from(menuContainer.children) as HTMLElement[]; // Find the actual DOM element for the target item let itemAbsoluteY = window.innerHeight / 2; // default fallback if (navItems[targetIndex]) { const itemRect = navItems[targetIndex].getBoundingClientRect(); itemAbsoluteY = itemRect.top; } const data = { scrollTop: menuContainer.scrollTop, containerTop: containerRect.top, itemIndex: targetIndex, itemAbsoluteY: itemAbsoluteY, }; shellApi.broker.publish(BROKER_EVENTS.shell.scrollToNavItemCompleted, data); }, 300); } }