@deepkit/desktop-ui
Version:
Library for desktop UI widgets in Angular 10+
1 lines • 1.1 MB
Source Map (JSON)
{"version":3,"file":"deepkit-desktop-ui.mjs","sources":["../../src/core/utils.ts","../../src/components/app/utils.ts","../../src/components/window/window-menu.ts","../../src/components/app/menu.component.ts","../../src/components/app/dui-view.directive.ts","../../src/components/app/cd-counter.component.ts","../../src/components/app/dui-responsive.directive.ts","../../src/components/window/window-state.ts","../../src/components/app/token.ts","../../src/components/app/pipes.ts","../../src/components/app/reactivate-change-detection.ts","../../src/components/app/state.ts","../../src/components/app/index.ts","../../src/components/form/form.component.ts","../../src/core/form.ts","../../src/browser-text.ts","../../src/components/icon/icon.component.ts","../../src/components/checkbox/checkbox.component.ts","../../src/components/icon/index.ts","../../src/components/checkbox/index.ts","../../src/components/splitter/splitter.component.ts","../../src/components/window/window-content.component.ts","../../src/components/window/window-header.component.ts","../../src/components/window/window.component.ts","../../src/components/button/button.component.ts","../../src/components/button/dropdown.component.ts","../../src/components/button/tab-button.component.ts","../../src/components/button/index.ts","../../src/components/input/input.component.ts","../../src/components/input/index.ts","../../src/components/form/index.ts","../../src/components/radiobox/radiobox.component.ts","../../src/components/radiobox/index.ts","../../src/components/select/selectbox.component.ts","../../src/components/select/index.ts","../../src/components/window/window-footer.component.ts","../../src/components/window/window-sidebar.component.ts","../../src/components/splitter/index.ts","../../src/components/core/render-component.directive.ts","../../src/components/dialog/dialog.component.ts","../../src/components/dialog/progress-dialog.component.ts","../../src/components/dialog/dialog.ts","../../src/components/window/external-window.component.ts","../../src/components/window/external-window.ts","../../src/components/core/index.ts","../../src/components/window/index.ts","../../src/components/list/list.component.ts","../../src/components/list/index.ts","../../src/components/table/table.component.ts","../../src/components/table/index.ts","../../src/components/dialog/index.ts","../../src/components/emoji/emojis.ts","../../src/components/emoji/emoji-dropdown.component.ts","../../src/components/emoji/emoji.component.ts","../../src/components/emoji/index.ts","../../src/components/slider/slider.component.ts","../../src/components/slider/index.ts","../../src/components/indicator/indicator.component.ts","../../src/components/indicator/index.ts","../../src/components/tabs/tab.component.ts","../../src/components/tabs/tabs.component.ts","../../src/components/tabs/index.ts","../../src/components/layout/label.component.ts","../../src/components/layout/section-header.component.ts","../../src/components/layout/index.ts","../../src/index.ts","../../src/deepkit-desktop-ui.ts"],"sourcesContent":["/*\n * Deepkit Framework\n * Copyright (C) 2021 Deepkit UG, Marc J. Schmidt\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the MIT License.\n *\n * You should have received a copy of the MIT License along with this program.\n */\n\n/**\n * @reflection never\n */\nimport { Observable, Subscription } from 'rxjs';\nimport { ChangeDetectorRef, EventEmitter } from '@angular/core';\nimport { ActivatedRoute, Router, UrlTree } from '@angular/router';\nimport { nextTick } from '@deepkit/core';\nimport type Hammer from 'hammerjs';\n\nconst electron = 'undefined' === typeof window ? undefined : (window as any).electron || ((window as any).require ? (window as any).require('electron') : undefined);\n\nexport async function getHammer(): Promise<typeof Hammer | undefined> {\n if ('undefined' === typeof window) return;\n //@ts-ignore\n const hammer = await import('hammerjs');\n return hammer.default;\n}\n\nexport class Electron {\n public static getRemote(): any {\n if (!electron) {\n throw new Error('No Electron available.');\n }\n\n return electron.remote;\n }\n\n public static getIpc(): any {\n if (!electron) {\n throw new Error('No Electron available.');\n }\n\n return electron.ipcRenderer;\n }\n\n public static isAvailable(): any {\n return !!electron;\n }\n\n public static getRemoteOrUndefined(): any {\n return electron ? electron.remote : undefined;\n }\n\n public static getProcess() {\n return Electron.getRemote().process;\n }\n}\n\nexport class AsyncEventEmitter<T> extends EventEmitter<T> {\n emit(value?: T): void {\n super.emit(value);\n }\n\n subscribe(generatorOrNext?: any, error?: any, complete?: any): Subscription {\n return super.subscribe(generatorOrNext, error, complete);\n }\n}\n\n\nexport class ExecutionState {\n public running = false;\n public error: string = '';\n\n constructor(\n protected readonly cd: ChangeDetectorRef,\n protected readonly func: (...args: any[]) => Promise<any> | any,\n ) {\n }\n\n public async execute(...args: any[]) {\n if (this.running) {\n throw new Error('Executor still running');\n }\n\n this.running = true;\n this.error = '';\n this.cd.detectChanges();\n\n try {\n return await this.func(...args);\n } catch (error: any) {\n this.error = error.message || error.toString();\n throw error;\n } finally {\n this.running = false;\n this.cd.detectChanges();\n }\n\n }\n}\n\n/**\n * Checks if `target` is children of `parent` or if `target` is `parent`.\n */\nexport function isTargetChildOf(target: HTMLElement | EventTarget | null, parent: HTMLElement): boolean {\n if (!target) return false;\n\n if (target === parent) return true;\n\n if (target instanceof HTMLElement) {\n let targetElement: HTMLElement = target;\n while (targetElement.parentElement) {\n if (targetElement.parentElement === parent) {\n return true;\n }\n targetElement = targetElement.parentElement;\n }\n }\n\n return false;\n}\n\nexport function isMacOs() {\n if ('undefined' === typeof navigator) return false;\n return navigator.platform.indexOf('Mac') > -1;\n}\n\nexport function isWindows() {\n if ('undefined' === typeof navigator) return false;\n return navigator.platform.indexOf('Win') > -1;\n}\n\n/**\n * Checks if `target` is children of `parent` or if `target` is `parent`.\n */\nexport function findParentWithClass(start: HTMLElement, className: string): HTMLElement | undefined {\n let current: HTMLElement | null = start;\n do {\n if (current.classList.contains(className)) return current;\n current = current.parentElement;\n } while (current);\n\n return undefined;\n}\n\nexport function triggerResize() {\n if ('undefined' === typeof window) return;\n nextTick(() => {\n window.dispatchEvent(new Event('resize'));\n });\n}\n\nexport function focusWatcher(target: HTMLElement, allowedFocuses: HTMLElement[] = [], customChecker?: (currentlyFocused: HTMLElement | null) => boolean): Observable<void> {\n if (target.ownerDocument!.body.tabIndex === -1) target.ownerDocument!.body.tabIndex = 1;\n\n return new Observable<void>((observer) => {\n let currentlyFocused: HTMLElement | null = target;\n\n function isFocusAllowed() {\n if (!currentlyFocused) {\n return false;\n }\n\n if (isTargetChildOf(currentlyFocused, target)) {\n return true;\n }\n\n for (const focus of allowedFocuses) {\n if (isTargetChildOf(currentlyFocused, focus)) {\n return true;\n }\n }\n\n return customChecker ? customChecker(currentlyFocused) : false;\n }\n\n function check() {\n if (!currentlyFocused) {\n //shouldn't be possible to have no element at all with focus.\n //this means usually that the item that had previously focus was deleted.\n currentlyFocused = target;\n }\n if (!isFocusAllowed()) {\n observer.next();\n observer.complete();\n }\n }\n\n function onFocusOut() {\n currentlyFocused = null;\n check();\n }\n\n function onFocusIn(event: FocusEvent) {\n currentlyFocused = event.target as any;\n check();\n }\n\n function onMouseDown(event: FocusEvent) {\n currentlyFocused = event.target as any;\n check();\n }\n\n target.ownerDocument!.addEventListener('mousedown', onMouseDown, true);\n target.ownerDocument!.addEventListener('focusin', onFocusIn);\n target.ownerDocument!.addEventListener('focusout', onFocusOut);\n\n function unsubscribe(): void {\n target.ownerDocument!.removeEventListener('mousedown', onMouseDown);\n target.ownerDocument!.removeEventListener('focusin', onFocusIn);\n target.ownerDocument!.removeEventListener('focusout', onFocusOut);\n }\n\n return { unsubscribe: unsubscribe };\n });\n}\n\ninterface RouteLike {\n routerLink?: string | UrlTree | any[];\n routerLinkExact?: boolean;\n router?: Router,\n activatedRoute?: ActivatedRoute;\n queryParams?: { [name: string]: any };\n}\n\nexport function isRouteActive(route: RouteLike): boolean {\n if (!route.router) return false;\n\n if ('string' === typeof route.routerLink) {\n return route.router.isActive(route.routerLink, route.routerLinkExact === true);\n } else if (Array.isArray(route.routerLink)) {\n return route.router.isActive(route.router.createUrlTree(route.routerLink, {\n queryParams: route.queryParams,\n relativeTo: route.activatedRoute,\n }), route.routerLinkExact === true);\n } else {\n return route.router.isActive(route.routerLink!, route.routerLinkExact === true);\n }\n}\n\nexport function redirectScrollableParentsToWindowResize(node: Element, passive = true) {\n const parents = getScrollableParents(node);\n\n function redirect() {\n window.dispatchEvent(new Event('resize'));\n }\n\n for (const parent of parents) {\n parent.addEventListener('scroll', redirect, { passive });\n }\n\n return () => {\n for (const parent of parents) {\n parent.removeEventListener('scroll', redirect);\n }\n };\n}\n\nexport function getScrollableParents(node: Element): Element[] {\n const scrollableParents: Element[] = [];\n let parent = node.parentNode;\n\n while (parent) {\n if (!(parent instanceof Element)) {\n parent = parent.parentNode;\n continue;\n }\n const computedStyle = window.getComputedStyle(parent);\n const overflow = computedStyle.getPropertyValue('overflow');\n if (overflow === 'overlay' || overflow === 'scroll' || overflow === 'auto') {\n scrollableParents.push(parent);\n }\n\n parent = parent.parentNode;\n }\n\n return scrollableParents;\n}\n\nexport function trackByIndex(index: number) {\n return index;\n}\n","/*\n * Deepkit Framework\n * Copyright (C) 2021 Deepkit UG, Marc J. Schmidt\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the MIT License.\n *\n * You should have received a copy of the MIT License along with this program.\n */\n\nimport {\n ApplicationRef,\n ChangeDetectorRef,\n Directive,\n ElementRef,\n HostListener,\n Input,\n OnChanges,\n} from '@angular/core';\nimport { nextTick } from '@deepkit/core';\nimport { Electron } from '../../core/utils';\n\n\n@Directive({\n selector: '[openExternal], a[href]',\n standalone: false,\n})\nexport class OpenExternalDirective implements OnChanges {\n @Input('openExternal') openExternal: string = '';\n @Input('href') href: string = '';\n\n constructor(private element: ElementRef) {\n // this.element.nativeElement.href = '#';\n }\n\n ngOnChanges(): void {\n // this.element.nativeElement.href = this.getLink();\n if (this.element.nativeElement instanceof HTMLAnchorElement) {\n this.element.nativeElement.setAttribute('href', this.getLink());\n }\n }\n\n getLink() {\n return this.openExternal || this.href;\n }\n\n @HostListener('click', ['$event'])\n onClick(event: Event) {\n event.stopPropagation();\n event.preventDefault();\n\n if (Electron.isAvailable()) {\n event.preventDefault();\n Electron.getRemote().shell.openExternal(this.getLink());\n } else {\n window.open(this.getLink(), '_blank');\n }\n }\n}\n\nlet lastScheduleResize: any;\n\nexport function scheduleWindowResizeEvent() {\n if (lastScheduleResize) cancelAnimationFrame(lastScheduleResize);\n lastScheduleResize = nextTick(() => {\n window.dispatchEvent(new Event('resize'));\n lastScheduleResize = undefined;\n });\n}\n\n\nlet lastFrameRequest: any;\nlet lastFrameRequestStack = new Set<ChangeDetectorRef>();\nlet lastFrameRequestStackDoneCb: (() => void)[] = [];\n\nexport class ZonelessChangeDetector {\n static app: ApplicationRef | undefined = undefined;\n\n static getApp() {\n if (!ZonelessChangeDetector.app) {\n throw new Error('ZonelessChangeDetector.app not set yet');\n }\n\n return ZonelessChangeDetector.app;\n }\n}\n\n/**\n * This handy function makes sure that in the next animation frame the given ChangeDetectorRef is called.\n * It makes automatically sure that it is only called once per frame.\n */\nexport function detectChangesNextFrame(cd?: ChangeDetectorRef, done?: () => any) {\n if (cd) {\n if (lastFrameRequestStack.has(cd)) return;\n lastFrameRequestStack.add(cd);\n if (done) lastFrameRequestStackDoneCb.push(done);\n }\n\n if (lastFrameRequest) {\n return;\n }\n\n lastFrameRequest = nextTick(() => {\n lastFrameRequest = undefined;\n for (const i of lastFrameRequestStack) {\n i.detectChanges();\n }\n for (const i of lastFrameRequestStackDoneCb) {\n i();\n }\n //since ivy we have to use tick() and can not use i.detectChanges().\n lastFrameRequestStackDoneCb = [];\n lastFrameRequestStack.clear();\n ZonelessChangeDetector.getApp().tick();\n });\n}\n","/*\n * Deepkit Framework\n * Copyright (C) 2021 Deepkit UG, Marc J. Schmidt\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the MIT License.\n *\n * You should have received a copy of the MIT License along with this program.\n */\n\nimport { MenuDirective } from '../app/menu.component';\nimport { arrayRemoveItem, nextTick } from '@deepkit/core';\nimport { Subscription } from 'rxjs';\nimport { Injectable } from '@angular/core';\nimport { Electron } from '../../core/utils';\n\n@Injectable()\nexport class WindowMenuState {\n menus: MenuDirective[] = [];\n focused = true;\n\n subscriptions = new Map<MenuDirective, Subscription>();\n\n addMenu(menu: MenuDirective) {\n this.menus.push(menu);\n this.subscriptions.set(menu, menu.change.subscribe(() => {\n this.build();\n }));\n\n this.build();\n }\n\n removeMenu(menu: MenuDirective) {\n this.subscriptions.get(menu)!.unsubscribe();\n this.subscriptions.delete(menu);\n arrayRemoveItem(this.menus, menu);\n this.build();\n }\n\n build() {\n nextTick(() => {\n this._build();\n })\n }\n\n protected _build() {\n const template: any[] = [];\n\n //todo, merge menus with same id(), id falls back to role+label\n // then we can use fileMenu in sub views and add sub menu items as we want\n for (const menu of this.menus) {\n if (menu.validOs()) {\n template.push(menu.buildTemplate());\n }\n }\n\n if (!template.length) {\n template.push(...[\n { role: 'appMenu' },\n { role: 'fileMenu' },\n { role: 'editMenu' },\n { role: 'viewMenu' },\n { role: 'windowMenu' },\n ]);\n }\n\n if (Electron.isAvailable()) {\n const remote: any = Electron.getRemote();\n if (remote) {\n try {\n const menu = remote.Menu.buildFromTemplate(template);\n remote.Menu.setApplicationMenu(menu);\n } catch (error) {\n console.error('Could not buildFromTemplate', template);\n console.error(error);\n }\n } else {\n console.warn('Not in electron environment');\n }\n }\n }\n\n focus() {\n //set our electron menu\n //Menu.setApplicationMenu()\n this.build();\n }\n}\n","/*\n * Deepkit Framework\n * Copyright (C) 2021 Deepkit UG, Marc J. Schmidt\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the MIT License.\n *\n * You should have received a copy of the MIT License along with this program.\n */\n\nimport {\n AfterViewInit,\n ContentChildren,\n Directive,\n EventEmitter,\n forwardRef,\n Input,\n OnDestroy,\n Output,\n QueryList,\n} from '@angular/core';\nimport { WindowMenuState } from '../window/window-menu';\nimport { arrayHasItem } from '@deepkit/core';\nimport { Subscription } from 'rxjs';\nimport { Electron } from '../../core/utils';\n\n@Directive()\nexport class MenuBase implements AfterViewInit {\n @Input() label?: string;\n @Input() sublabel?: string;\n @Input() icon?: string;\n @Input() enabled: boolean = true;\n @Input() accelerator?: string;\n @Input() role?: string;\n\n @Input() visible: boolean = true;\n\n @Input() onlyMacOs: boolean | '' = false;\n @Input() noMacOs: boolean | '' = false;\n\n @Input() id?: string;\n @Input() before?: string;\n @Input() after?: string;\n @Input() beforeGroupContaining?: string;\n @Input() afterGroupContaining?: string;\n\n @Output() click = new EventEmitter();\n\n @Output() change = new EventEmitter;\n\n public type = '';\n\n protected registered = new Set<MenuBase>();\n protected subscriptions = new Map<MenuBase, Subscription>();\n\n @ContentChildren(MenuBase) public child?: QueryList<MenuBase>;\n\n constructor() {\n\n }\n\n buildTemplate() {\n const submenu: any[] = [];\n if (this.child) {\n for (const item of this.child.toArray()) {\n if (item === this) continue;\n if (!item.validOs()) {\n continue;\n }\n submenu.push(item.buildTemplate());\n }\n }\n\n const result: { [name: string]: any } = {\n click: () => {\n this.click.emit()\n },\n };\n\n if (this.label) result['label'] = this.label;\n if (this.sublabel) result['sublabel'] = this.sublabel;\n\n if (!this.enabled) result['enabled'] = false;\n if (this.type) result['type'] = this.type;\n\n if (this.accelerator) result['accelerator'] = this.accelerator;\n if (this.role) result['role'] = this.role;\n if (this.type) result['type'] = this.type;\n if (this.accelerator) result['accelerator'] = this.accelerator;\n if (submenu.length) result['submenu'] = submenu;\n\n return result;\n }\n\n public validOs(): boolean {\n if (Electron.isAvailable()) {\n if (this.onlyMacOs !== false && Electron.getProcess().platform !== 'darwin') {\n return false;\n }\n\n if (this.noMacOs !== false && Electron.getProcess().platform === 'darwin') {\n return false;\n }\n }\n\n return true;\n }\n\n ngAfterViewInit() {\n if (this.child) {\n this.child!.changes.subscribe((items: MenuBase[]) => {\n for (const item of items) {\n if (!this.registered.has(item)) {\n this.registered.add(item);\n this.subscriptions.set(item, item.change.subscribe(() => {\n this.change.emit();\n }));\n }\n }\n\n for (const item of this.registered) {\n if (!arrayHasItem(items, item)) {\n //got removed\n this.registered.delete(item);\n this.subscriptions.get(item)!.unsubscribe();\n this.subscriptions.delete(item);\n }\n }\n\n this.change.emit();\n });\n }\n }\n}\n\n@Directive({\n selector: 'dui-menu-item',\n standalone: false,\n providers: [{ provide: MenuBase, useExisting: forwardRef(() => MenuItemDirective) }]\n})\nexport class MenuItemDirective extends MenuBase {\n}\n\n@Directive({\n selector: 'dui-menu-checkbox',\n standalone: false,\n providers: [{ provide: MenuBase, useExisting: forwardRef(() => MenuCheckboxDirective) }]\n})\nexport class MenuCheckboxDirective extends MenuBase {\n @Input() checked: boolean = false;\n\n type = 'checkbox';\n\n buildTemplate() {\n return { ...super.buildTemplate(), checked: this.checked };\n }\n}\n\n@Directive({\n selector: 'dui-menu-radio',\n standalone: false,\n providers: [{ provide: MenuBase, useExisting: forwardRef(() => MenuRadioDirective) }]\n})\nexport class MenuRadioDirective extends MenuBase {\n @Input() checked: boolean = false;\n\n type = 'radio';\n\n buildTemplate() {\n return { ...super.buildTemplate(), checked: this.checked };\n }\n}\n\n@Directive({\n selector: 'dui-menu-separator',\n standalone: false,\n providers: [{ provide: MenuBase, useExisting: forwardRef(() => MenuSeparatorDirective) }]\n})\nexport class MenuSeparatorDirective extends MenuBase {\n type = 'separator';\n}\n\n@Directive({\n selector: 'dui-menu',\n standalone: false,\n})\nexport class MenuDirective extends MenuBase implements OnDestroy, AfterViewInit {\n @Input() position: number = 0;\n\n constructor(protected windowMenuState: WindowMenuState) {\n super();\n }\n\n ngAfterViewInit() {\n super.ngAfterViewInit();\n this.windowMenuState.addMenu(this);\n }\n\n ngOnDestroy() {\n this.windowMenuState.removeMenu(this);\n }\n}\n","/*\n * Deepkit Framework\n * Copyright (C) 2021 Deepkit UG, Marc J. Schmidt\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the MIT License.\n *\n * You should have received a copy of the MIT License along with this program.\n */\n\nimport { Directive, EmbeddedViewRef, Injectable, Input, OnDestroy, TemplateRef, ViewContainerRef } from '@angular/core';\nimport { detectChangesNextFrame, scheduleWindowResizeEvent } from './utils';\n\nlet i = 0;\n\nlet currentViewDirective: ViewDirective | undefined;\n\n@Injectable()\nexport class ViewState {\n public id = i++;\n\n public viewDirective?: ViewDirective = currentViewDirective;\n\n get attached() {\n return this.viewDirective ? this.viewDirective.isVisible() : true;\n }\n}\n\n@Directive({\n selector: '[duiView]',\n standalone: false,\n providers: [{ provide: ViewState, useClass: ViewState }]\n})\nexport class ViewDirective implements OnDestroy {\n protected view?: EmbeddedViewRef<any>;\n\n protected visible = false;\n\n public readonly parentViewDirective: ViewDirective | undefined;\n\n constructor(\n protected template: TemplateRef<any>,\n protected viewContainer: ViewContainerRef,\n ) {\n this.parentViewDirective = currentViewDirective;\n }\n\n public isVisible() {\n if (this.view) {\n for (const node of this.view.rootNodes) {\n if (node.style && node.offsetParent !== null) {\n return true;\n }\n }\n\n return false;\n }\n\n return this.visible;\n }\n\n @Input()\n set duiView(v: boolean) {\n if (this.visible === v) return;\n\n this.visible = v;\n\n if (this.visible) {\n if (this.view) {\n this.view.rootNodes.map(element => {\n if (element.style) {\n element.style.display = '';\n }\n });\n this.view!.reattach();\n this.view.markForCheck();\n scheduleWindowResizeEvent();\n return;\n }\n\n const old = currentViewDirective;\n currentViewDirective = this;\n this.view = this.viewContainer.createEmbeddedView(this.template);\n currentViewDirective = old;\n } else {\n if (this.view) {\n this.view!.rootNodes.map(element => {\n if (element.style) {\n element.style.display = 'none';\n }\n });\n //let the last change detection run so ViewState have correct state\n this.view!.detectChanges();\n this.view!.detach();\n this.view.markForCheck();\n detectChangesNextFrame(this.view);\n }\n }\n }\n\n ngOnDestroy() {\n if (this.view) {\n this.view.destroy();\n }\n }\n}\n","/*\n * Deepkit Framework\n * Copyright (C) 2021 Deepkit UG, Marc J. Schmidt\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the MIT License.\n *\n * You should have received a copy of the MIT License along with this program.\n */\n\nimport { Component, Input } from '@angular/core';\n\n@Component({\n selector: 'dui-cd-counter',\n standalone: false,\n template: `{{counter}}`\n})\nexport class CdCounterComponent {\n private i = 0;\n\n @Input() name?: string;\n\n get counter() {\n this.i++;\n return this.i;\n }\n}\n","/*\n * Deepkit Framework\n * Copyright (C) 2021 Deepkit UG, Marc J. Schmidt\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the MIT License.\n *\n * You should have received a copy of the MIT License along with this program.\n */\n\nimport { Directive, ElementRef, HostListener, Input, OnInit } from '@angular/core';\nimport { nextTick } from '@deepkit/core';\n\n@Directive({\n selector: '[duiClassMin]',\n standalone: false,\n})\nexport class DuiResponsiveDirective implements OnInit {\n clazz: { [className: string]: boolean } = {};\n protected lastRequest: any;\n\n @Input() duiClassMin: { [className: string]: number } = {};\n\n constructor(\n private element: ElementRef,\n ) { }\n\n ngOnInit() {\n this.onResize();\n }\n\n @HostListener('window:resize')\n onResize() {\n if (this.lastRequest) {\n cancelAnimationFrame(this.lastRequest);\n }\n\n this.lastRequest = nextTick(() => {\n const element: HTMLElement = this.element.nativeElement;\n for (const [name, number] of Object.entries(this.duiClassMin)) {\n const valid = element.offsetWidth > number;\n if (this.clazz[name] !== valid) {\n this.clazz[name] = valid;\n if (valid) {\n element.classList.add(name);\n } else {\n element.classList.remove(name);\n }\n }\n }\n });\n }\n}\n","/*\n * Deepkit Framework\n * Copyright (C) 2021 Deepkit UG, Marc J. Schmidt\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the MIT License.\n *\n * You should have received a copy of the MIT License along with this program.\n */\n\nimport { ChangeDetectorRef, Injectable, TemplateRef, ViewContainerRef } from '@angular/core';\nimport { arrayRemoveItem } from '@deepkit/core';\nimport { WindowMenuState } from './window-menu';\nimport { BehaviorSubject } from 'rxjs';\nimport { detectChangesNextFrame } from '../app/utils';\n\ninterface WinHeader {\n getBottomPosition(): number;\n}\n\ninterface Win {\n id: number;\n electronWindow?: any;\n getClosestNonDialogWindow(): Win | undefined;\n header?: WinHeader;\n viewContainerRef: ViewContainerRef;\n}\n\n@Injectable()\nexport class WindowRegistry {\n id = 0;\n\n registry = new Map<Win, {\n state: WindowState,\n menu: WindowMenuState,\n cd: ChangeDetectorRef,\n viewContainerRef: ViewContainerRef\n }>();\n\n windowHistory: Win[] = [];\n activeWindow?: Win;\n\n /**\n * When BrowserWindow of electron is focused.\n */\n public focused = new BehaviorSubject(false);\n\n constructor() {\n this.focused.subscribe((v) => {\n for (const win of this.registry.values()) {\n win.state.focus.next(v);\n }\n detectChangesNextFrame();\n });\n }\n\n getAllElectronWindows(): any[] {\n return [...this.registry.keys()].filter(v => !!v.electronWindow).map(v => v.electronWindow);\n }\n\n register(win: Win, cd: ChangeDetectorRef, state: WindowState, menu: WindowMenuState, viewContainerRef: ViewContainerRef) {\n this.id++;\n win.id = this.id;\n\n this.registry.set(win, {\n state, menu, cd, viewContainerRef,\n });\n }\n\n /**\n * Finds the activeWindow and returns its most outer parent.\n */\n getOuterActiveWindow(): Win | undefined {\n if (this.activeWindow) return this.activeWindow.getClosestNonDialogWindow();\n }\n\n getCurrentViewContainerRef(): ViewContainerRef {\n if (this.activeWindow) {\n return this.activeWindow.viewContainerRef;\n // const reg = this.registry.get(this.activeWindow);\n // if (reg) {\n // return reg.viewContainerRef;\n // }\n }\n\n throw new Error('No active window');\n }\n\n focus(win: Win) {\n if (this.activeWindow === win) return;\n\n const reg = this.registry.get(win);\n if (!reg) throw new Error('Window not registered');\n\n this.activeWindow = win;\n\n arrayRemoveItem(this.windowHistory, win);\n this.windowHistory.push(win);\n\n reg.state.focus.next(true);\n reg.menu.focus();\n detectChangesNextFrame();\n }\n\n blur(win: Win) {\n const reg = this.registry.get(win);\n if (reg) {\n reg.state.focus.next(false);\n }\n if (this.activeWindow === win) {\n delete this.activeWindow;\n }\n\n detectChangesNextFrame();\n }\n\n unregister(win: Win) {\n const reg = this.registry.get(win);\n if (reg) {\n reg.state.focus.next(false);\n }\n\n this.registry.delete(win);\n arrayRemoveItem(this.windowHistory, win);\n\n if (this.windowHistory.length) {\n this.focus(this.windowHistory[this.windowHistory.length - 1]);\n }\n detectChangesNextFrame();\n }\n}\n\ninterface AlignedButtonGroup {\n sidebarMoved: () => void;\n activateOneTimeAnimation: () => void;\n}\n\n@Injectable()\nexport class WindowState {\n public buttonGroupAlignedToSidebar?: AlignedButtonGroup;\n public focus = new BehaviorSubject<boolean>(false);\n public disableInputs = new BehaviorSubject<boolean>(false);\n\n public toolbars: { [name: string]: TemplateRef<any>[] } = {};\n public toolbarContainers: { [name: string]: { toolbarsUpdated: () => void } } = {};\n\n closable = true;\n maximizable = true;\n minimizable = true;\n\n constructor() {\n }\n\n public addToolbarContainer(forName: string, template: TemplateRef<any>) {\n if (!this.toolbars[forName]) {\n this.toolbars[forName] = [];\n }\n\n this.toolbars[forName].push(template);\n\n if (this.toolbarContainers[forName]) {\n this.toolbarContainers[forName].toolbarsUpdated();\n }\n }\n\n public removeToolbarContainer(forName: string, template: TemplateRef<any>) {\n arrayRemoveItem(this.toolbars[forName], template);\n if (this.toolbarContainers[forName]) {\n this.toolbarContainers[forName].toolbarsUpdated();\n }\n }\n}\n","/*\n * Deepkit Framework\n * Copyright (C) 2021 Deepkit UG, Marc J. Schmidt\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the MIT License.\n *\n * You should have received a copy of the MIT License along with this program.\n */\n\nimport { InjectionToken } from '@angular/core';\n\nexport const ELECTRON_WINDOW = new InjectionToken('Electron window');\n\nexport const IN_DIALOG = new InjectionToken('In dialog');\n","/*\n * Deepkit Framework\n * Copyright (C) 2021 Deepkit UG, Marc J. Schmidt\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the MIT License.\n *\n * You should have received a copy of the MIT License along with this program.\n */\n\nimport { ChangeDetectorRef, OnDestroy, Pipe, PipeTransform } from '@angular/core';\nimport { Observable, Subscription } from 'rxjs';\nimport { detectChangesNextFrame } from './utils';\nimport { DomSanitizer, SafeUrl } from '@angular/platform-browser';\nimport { humanBytes } from '@deepkit/core';\n\n/**\n * Almost the same as |async pipe, but renders directly (detectChanges() instead of marking it only(markForCheck())\n * on ChangeDetectorRef.\n */\n@Pipe({ name: 'asyncRender', standalone: false, pure: false })\nexport class AsyncRenderPipe implements OnDestroy, PipeTransform {\n protected subscription?: Subscription;\n protected lastValue?: any;\n protected lastReturnedValue?: any;\n\n constructor(\n protected cd: ChangeDetectorRef) {\n }\n\n ngOnDestroy(): void {\n if (this.subscription) this.subscription.unsubscribe();\n }\n\n transform<T>(value?: Observable<T> | Promise<T>): T | undefined {\n if (this.lastValue !== value) {\n if (this.subscription) this.subscription.unsubscribe();\n this.lastReturnedValue = undefined;\n this.lastValue = value;\n\n if (value instanceof Promise) {\n value.then((v) => {\n this.lastReturnedValue = v;\n detectChangesNextFrame(this.cd);\n });\n } else if (value) {\n this.subscription = value.subscribe((next) => {\n this.lastReturnedValue = next;\n detectChangesNextFrame(this.cd);\n });\n }\n }\n\n return this.lastReturnedValue;\n }\n}\n\n@Pipe({name: 'objectURL', standalone: false})\nexport class ObjectURLPipe implements PipeTransform, OnDestroy {\n protected lastUrl?: string;\n\n constructor(private sanitizer: DomSanitizer) {\n }\n\n ngOnDestroy(): void {\n if (this.lastUrl) URL.revokeObjectURL(this.lastUrl);\n }\n\n transform(buffer?: ArrayBuffer | ArrayBufferView, mimeType?: string): SafeUrl | undefined {\n if (buffer) {\n if (this.lastUrl) URL.revokeObjectURL(this.lastUrl);\n this.lastUrl = URL.createObjectURL(new Blob([buffer], {type: mimeType}));\n return this.sanitizer.bypassSecurityTrustResourceUrl(this.lastUrl);\n }\n }\n}\n\n@Pipe({name: 'fileSize', standalone: false})\nexport class HumanFileSizePipe implements PipeTransform {\n transform(bytes: number, si: boolean = false): string {\n return humanBytes(bytes, si);\n }\n}\n","/*\n * Deepkit Framework\n * Copyright (C) 2021 Deepkit UG, Marc J. Schmidt\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the MIT License.\n *\n * You should have received a copy of the MIT License along with this program.\n */\n\nimport {\n AfterViewInit,\n ApplicationRef,\n inject,\n NgModule,\n OnDestroy,\n Type,\n ɵComponentDef as ComponentDef,\n ɵNG_COMP_DEF as NG_COMP_DEF,\n} from '@angular/core';\nimport { getClassName, nextTick, throttleTime } from '@deepkit/core';\nimport { EventDispatcher, EventToken } from '@deepkit/event';\nimport { Subscription } from 'rxjs';\n\nexport function observeAction() {\n return function (target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<any>): TypedPropertyDescriptor<any> | void {\n const originalMethod = descriptor.value;\n\n descriptor.value = function (...args: any[]) {\n const result = originalMethod.apply(this, args);\n\n if (result && result.then) {\n result.then(() => {\n ReactiveChangeDetectionModule.tick();\n }, () => {\n ReactiveChangeDetectionModule.tick();\n });\n } else {\n ReactiveChangeDetectionModule.tick();\n }\n\n return result;\n };\n\n return descriptor;\n };\n}\n\nconst lazyValuesStore = new WeakMap<any, object>();\nconst lazyValuesSubscriptions = new WeakMap<any, object>();\nconst lazyValuesDestroyed = new WeakMap<any, object>();\n\nfunction lazyInitialize(target: any, map: WeakMap<any, object> = lazyValuesStore): any {\n let object = map.get(target);\n if (object) return object;\n object = {};\n map.set(target, object);\n return object;\n}\n\nfunction getRealMethodHookName(value: string): string {\n return 'ng' + value.substr(0, 1).toUpperCase() + value.substr(1);\n}\n\nfunction addComponentHook<T>(target: T, hookName: 'onDestroy' | 'onChanges' | 'onInit' | 'afterViewInit', fn: (this: T) => void) {\n const cdef: ComponentDef<any> = ((target as any).constructor as any)[NG_COMP_DEF];\n if (cdef) {\n //prod build\n const ori = (cdef as any)[hookName];\n ((cdef as any)['onDestroy'] as any) = function (this: any, ...args: any[]) {\n fn.call(this);\n ori && (ori as any).apply(this, args);\n };\n } else {\n const ori = (target as any).constructor.prototype[getRealMethodHookName(hookName)];\n (target as any).constructor.prototype[getRealMethodHookName(hookName)] = function (this: any, ...args: any[]) {\n fn.call(this);\n ori && (ori as any).apply(this, args);\n };\n }\n}\n\n/**\n * Automatically unsubscribe the value (calling unsubscribe() on the current value)\n * when ngOnDestroy is called or a new value has been set.\n * When the component is already destroyed, newly set values will be unscubribed immediately.\n * This makes sure when a component is destroyed too fast before a async operation is completed\n * that the result is unsubscribed, otherwise it would be a memory leak.\n */\nexport function unsubscribe<T extends OnDestroy>() {\n return function (target: T, propertyKey: string | symbol) {\n\n function unsub(value: any) {\n if (value && value.unsubscribe) {\n try {\n value.unsubscribe();\n } catch (error) {\n console.log('Subscription was already unsubscribed.', getClassName(target), propertyKey);\n }\n }\n }\n\n Object.defineProperty(target, propertyKey, {\n enumerable: true,\n configurable: false, //even with true the prop cant be deleted using `delete this.name`\n get() {\n const store = lazyInitialize(this);\n return store[propertyKey];\n },\n\n set(value) {\n const destroyed = lazyInitialize(this, lazyValuesDestroyed);\n const store = lazyInitialize(this);\n unsub(store[propertyKey]);\n if (destroyed['destroyed']) {\n unsub(value);\n }\n store[propertyKey] = value;\n }\n });\n\n addComponentHook(target, 'onDestroy', function () {\n const destroyed = lazyInitialize(this, lazyValuesDestroyed);\n destroyed['destroyed'] = true;\n const store = lazyInitialize(this);\n if (store[propertyKey]) {\n unsub(store[propertyKey]);\n }\n });\n };\n}\n\n/**\n * Listens on the given event token and calls the method when the event is triggered.\n *\n * @example\n * ```typescript\n *\n * const MyEvent = new EventToken('my-event');\n *\n * @Component({\n * //..\n * });\n * class MyComponent {\n * @EventListener(MyEvent)\n * onMyEvent(event: MyEvent) {\n * console.log('event triggered', event);\n * }\n * }\n * ```\n */\nexport function EventListener(eventToken: EventToken) {\n return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {\n const originalConstructor = target.constructor;\n\n const newConstructor: any = function (...args: any[]) {\n const instance = new originalConstructor(...args);\n const store = lazyInitialize(instance);\n const eventDispatcher = inject(EventDispatcher);\n console.log('listen', eventToken, propertyKey);\n store['Ωlistener_' + propertyKey] = eventDispatcher.listen(eventToken, (event) => {\n instance[propertyKey](event);\n });\n return instance;\n };\n newConstructor.prototype = originalConstructor.prototype;\n target.constructor = newConstructor;\n addComponentHook(newConstructor, 'onDestroy', function () {\n const store = lazyInitialize(this);\n const unsubscribe = store['Ωlistener_' + propertyKey];\n if (unsubscribe) unsubscribe();\n });\n };\n}\n\n/**\n * Important for components that use material design, which need Tick in AfterViewInit.\n */\nexport function reactiveComponent<T extends AfterViewInit>() {\n return function (target: Type<T>) {\n addComponentHook(target.prototype, 'afterViewInit', function () {\n ReactiveChangeDetectionModule.tick();\n });\n };\n}\n\n/**\n * Automatically subscribes on the value (when set) to trigger application ticks automatically.\n * When value is changed, the old subscription is cancelled and a new on the new value is created.\n *\n * Optionally @observe({unsubscribe: true}) unsubscribes the whole value as well (calling unsubscribe() on current value) on NgOnDestroy or when net property value is set.\n */\nexport function observe<T extends {}>(options: { unsubscribe?: true } = {}) {\n return function (target: T, propertyKey: string | symbol) {\n\n function unsub(value: any) {\n if (value && value.unsubscribe) {\n try {\n value.unsubscribe();\n } catch (error) {\n console.log('Subscription was already unsubscribed.', getClassName(target), propertyKey);\n }\n }\n }\n\n Object.defineProperty(target, propertyKey, {\n enumerable: true,\n configurable: false, //even with true the prop cant be deleted using `delete this.name`\n get() {\n const store = lazyInitialize(this);\n return store[propertyKey];\n },\n\n set(value) {\n const destroyed = lazyInitialize(this, lazyValuesDestroyed);\n const store = lazyInitialize(this);\n const subscriptions = lazyInitialize(this, lazyValuesSubscriptions);\n\n if (subscriptions[propertyKey]) {\n unsub(subscriptions[propertyKey] as Subscription);\n delete subscriptions[propertyKey];\n }\n\n if (options.unsubscribe && store[propertyKey] && store[propertyKey].unsubscribe) {\n unsub(store[propertyKey]);\n }\n\n if (!destroyed['destroyed'] && value && value.subscribe) {\n subscriptions[propertyKey] = value.subscribe(() => {\n ReactiveChangeDetectionModule.tick();\n });\n }\n if (destroyed['destroyed'] && options.unsubscribe) {\n unsub(value);\n }\n\n ReactiveChangeDetectionModule.tick();\n\n store[propertyKey] = value;\n }\n });\n\n addComponentHook(target, 'onDestroy', function () {\n const destroyed = lazyInitialize(this, lazyValuesDestroyed);\n destroyed['destroyed'] = true;\n const store = lazyInitialize(this);\n const subscriptions = lazyInitialize(this, lazyValuesSubscriptions);\n\n if (subscriptions[propertyKey]) {\n unsub(subscriptions[propertyKey]);\n delete subscriptions[propertyKey];\n }\n\n if (options.unsubscribe) {\n unsub(store[propertyKey]);\n }\n });\n };\n}\n\n@NgModule({})\nexport class ReactiveChangeDetectionModule {\n private static a: ApplicationRef;\n // private static lastAnimationFrame?: number;\n private static throttled: Function;\n\n constructor(a: ApplicationRef) {\n ReactiveChangeDetectionModule.a = a;\n\n ReactiveChangeDetectionModule.throttled = throttleTime(() => {\n ReactiveChangeDetectionModule.a.tick();\n }, 1000 / 25);\n }\n\n public static tick() {\n nextTick(() => {\n ReactiveChangeDetectionModule.throttled();\n });\n }\n}\n","import {\n deserialize,\n Excluded,\n ReflectionClass,\n ReflectionKind,\n resolveTypeMembers,\n serialize,\n Serializer,\n Type,\n typeAnnotation,\n TypeClass,\n} from '@deepkit/type';\nimport {\n ClassType,\n getClassTypeFromInstance,\n getPathValue,\n setPathValue,\n throttleTime,\n TypeAnnotation,\n} from '@deepkit/core';\nimport { EventToken } from '@deepkit/event';\nimport { ApplicationRef, Injector } from '@angular/core';\nimport { NavigationEnd, ResolveEnd, Router } from '@angular/router';\nimport onChange from 'on-change';\n\n/**\n * If this type decorator is used then the property is not persisted in localStorage,\n * but in the URL instead and recovered from the URL when reloaded.\n *\n * @example\n * ```typescript\n * class State extends EfficientState {\n * shop: number & PartOfUrl = 0;\n * }\n * ```\n */\nexport type PartOfUrl = TypeAnnotation<'partOfUrl'>;\n\nexport type FilterActions<T> = { [name in keyof T]: T[name] extends (a: infer A extends [...a: any[]], ...args: any[]) => infer R ? (...args: A) => R : never };\n\n/**\n * EfficientState is a base class for all states that are used in the frontend.\n *\n * @example\n * ```typescript\n * //state that won't be persisted is used as `VolatileState & Excluded`\n * class VolatileState {\n * shops: Shop[] = [];\n *\n * user?: User;\n * }\n *\n * export class State extends EfficientState {\n * shop: number & PartOfUrl = 0;\n * sidebarVisible: boolean = true;\n * searchQuery: string = '';\n *\n * volatile: VolatileState & Excluded = new VolatileState();\n *\n * //normal methods are supported\n * getShop(): Shop | undefined {\n * return this.volatile.getShop(this.shop);\n * }\n *\n * //actions have access to Angular's DI container\n * async authenticated([user]: [FrontendUser], client: ControllerClient) {\n * this.volatile.shops = await client.shop.getShops();\n * this.volatile.user = user;\n * }\n * }\n * ```\n */\nexport class EfficientState {\n call: FilterActions<this> & Excluded;\n\n constructor(injector?: Injector) {\n this.call = {} as any;\n if (!injector) return;\n const app = injector.get(ApplicationRef);\n\n for (const method of ReflectionClass.from(this.constructor as any).getMethods()) {\n if (method.name === 'constructor') continue;\n const params = method.getParameters().slice(1).map(v => v.type).filter(v => v.kind === ReflectionKind.class) as TypeClass[];\n (this.call as any)[method.name] = (...args: any[]) => {\n const deps: any = params.map(v => injector.get(v.classType));\n const res = (this as any)[method.name](args, ...deps);\n if (res instanceof Promise) {\n return res.finally(() => app.tick());\n }\n return res;\n };\n }\n }\n}\n\nexport function findPartsOfUrl(stateClass: ClassType) {\n const paths: string[] = [];\n const schema = ReflectionClass.from(stateClass);\n findPartsOfUrlForType(schema.type, paths);\n return paths;\n}\n\nexport function getQueryObject(state: EfficientState) {\n const query: any = {};\n