import { Router } from '@vaadin/router'; import { css, html, LitElement } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { defineComponents, IgcIconComponent, IgcNavDrawerComponent, IgcNavDrawerItemComponent, registerIcon, } from 'igniteui-webcomponents'; import { routes, type AppRoute } from './app-routing.js'; import { UserStore } from './authentication/services/userStore.js'; import './authentication/login-bar/login-bar.js'; defineComponents( IgcIconComponent, IgcNavDrawerComponent, IgcNavDrawerItemComponent, ); const materialIcons = [ ['home', 'action/svg/production/ic_home_24px.svg'], ['menu', 'navigation/svg/production/ic_menu_24px.svg'], ['apps', 'navigation/svg/production/ic_apps_24px.svg'], ['code', 'action/svg/production/ic_code_24px.svg'], ['build', 'action/svg/production/ic_build_24px.svg'], ['palette', 'image/svg/production/ic_palette_24px.svg'], ['account_circle', 'action/svg/production/ic_account_circle_24px.svg'], ['lock', 'action/svg/production/ic_lock_24px.svg'], ['assignment_ind', 'action/svg/production/ic_assignment_ind_24px.svg'], ] as const; materialIcons.forEach(([name, path]) => registerIcon(name, `https://unpkg.com/material-design-icons@3.0.1/${path}`, 'material') ); @customElement('app-root') export default class App extends LitElement { @state() private drawerOpen = true; @state() private drawerPosition: 'relative' | 'start' = 'relative'; @state() private currentPath = window.location.pathname; @state() private isLoggedIn = Boolean(UserStore.getUser()); private mediaQuery?: MediaQueryList; static styles = css` :host { display: flex; height: 100%; } .app { display: flex; flex-flow: column nowrap; width: 100%; height: 100%; overflow: hidden; } .app__navbar { display: flex; align-items: center; flex: 0 0 auto; height: 56px; padding: 0 16px; background: #239ef0; box-shadow: 0 2px 4px rgba(0, 0, 0, .24); box-sizing: border-box; position: relative; z-index: 10; } .app__menu-button { display: inline-flex; align-items: center; justify-content: center; width: 40px; height: 40px; padding: 0; color: #000; border: 0; background: transparent; cursor: pointer; } .app__menu-button igc-icon { font-size: 24px; } .app__title { margin: 0 0 0 16px; color: #000; font-size: 1.25rem; font-weight: 600; line-height: 1; } .app__navbar-spacer { flex: 1 1 auto; } .app__body { display: flex; flex: 1 1 auto; min-height: 0; } .app__drawer { flex: 0 0 auto; height: 100%; --menu-full-width: 280px; } igc-nav-drawer-item::part(base) { min-height: 48px; color: #2d2d2d; } igc-nav-drawer-item[active]::part(base) { background: #e0f2ff; color: #0075d2; } igc-nav-drawer-item[active] igc-icon { color: #0075d2; } igc-nav-drawer-item:not([active]) igc-icon { color: #2d2d2d; } router-outlet { flex: 1 1 auto; display: flex; align-items: stretch; justify-content: center; min-width: 0; overflow: auto; } `; render() { const visibleRoutes = (routes as AppRoute[]).filter((route) => { if (!route.name) return false; if (route.requiresAuth && !this.isLoggedIn) return false; return true; }); return html`

$(name)

${visibleRoutes.map((route) => html` this.navigate(route.path)} > ${route.name} `)}
`; } connectedCallback() { super.connectedCallback(); this.mediaQuery = window.matchMedia('(min-width: 1025px)'); this.updateDrawerState(); this.mediaQuery.addEventListener('change', this.updateDrawerState); window.addEventListener('popstate', this.updateCurrentPath); // Listen globally so redirect components (Google/Facebook/Microsoft) in the router // outlet can also trigger a shell state update after a successful OAuth redirect. window.addEventListener('auth-change', this.handleAuthChange); } disconnectedCallback() { this.mediaQuery?.removeEventListener('change', this.updateDrawerState); window.removeEventListener('popstate', this.updateCurrentPath); window.removeEventListener('auth-change', this.handleAuthChange); super.disconnectedCallback(); } firstUpdated() { const outlet = this.shadowRoot?.querySelector('router-outlet'); const router = new Router(outlet); router.setRoutes(routes); } private toggleDrawer = () => { this.drawerOpen = !this.drawerOpen; }; private navigate(path: string) { this.currentPath = path; Router.go(path); if (!this.mediaQuery?.matches) { this.drawerOpen = false; } } private updateDrawerState = () => { const pinned = Boolean(this.mediaQuery?.matches); this.drawerOpen = pinned; this.drawerPosition = pinned ? 'relative' : 'start'; }; private updateCurrentPath = () => { this.currentPath = window.location.pathname; }; private handleAuthChange = () => { this.isLoggedIn = Boolean(UserStore.getUser()); this.currentPath = window.location.pathname; }; }