diff --git a/packages/desktop-ui/package.json b/packages/desktop-ui/package.json index 50dc1e309..7f5e617cd 100644 --- a/packages/desktop-ui/package.json +++ b/packages/desktop-ui/package.json @@ -19,6 +19,7 @@ "emoji": "ts-node emoji-map.ts", "font": "node bin/create-font.js", "build": "rm -rf .angular && ng-packagr --config tsconfig.prod.json", + "watch": "ng-packagr --config tsconfig.prod.json --watch", "prepublishOnly": "webpack && node bin/create-font.js && npm run docs", "docs": "typedoc --plugin none --json src/assets/docs.json src" }, diff --git a/packages/desktop-ui/src/components/app/index.ts b/packages/desktop-ui/src/components/app/index.ts index 432c56f2a..d5da3f6f1 100644 --- a/packages/desktop-ui/src/components/app/index.ts +++ b/packages/desktop-ui/src/components/app/index.ts @@ -16,7 +16,7 @@ import { CdCounterComponent } from './cd-counter.component'; import { DuiResponsiveDirective } from './dui-responsive.directive'; import { CommonModule, DOCUMENT } from '@angular/common'; import { Electron } from '../../core/utils'; -import { ActivationEnd, Event as RouterEvent, NavigationEnd, Router } from '@angular/router'; +import { ActivationEnd, NavigationEnd, Router } from '@angular/router'; import { WindowRegistry } from '../window/window-state'; import { ELECTRON_WINDOW, IN_DIALOG } from './token'; import { AsyncRenderPipe, HumanFileSizePipe, ObjectURLPipe } from './pipes'; @@ -207,7 +207,7 @@ export class DuiApp { //necessary to render all router-outlet once the router changes if (this.router) { - this.router.events.subscribe((event: RouterEvent) => { + this.router.events.subscribe((event) => { if (event instanceof NavigationEnd || event instanceof ActivationEnd) { detectChangesNextFrame(); } diff --git a/packages/desktop-ui/src/components/window/external-window.component.ts b/packages/desktop-ui/src/components/window/external-window.component.ts index e611ad2d9..9a7138695 100644 --- a/packages/desktop-ui/src/components/window/external-window.component.ts +++ b/packages/desktop-ui/src/components/window/external-window.component.ts @@ -27,7 +27,7 @@ import { TemplateRef, Type, ViewChild, - ViewContainerRef + ViewContainerRef, } from '@angular/core'; import { ComponentPortal, DomPortalHost, PortalHost } from '@angular/cdk/portal'; import { WindowComponent } from '../window/window.component'; @@ -72,8 +72,8 @@ function PopupCenter(url: string, title: string, w: number, h: number): Window { `, host: { - '[attr.tabindex]': '1' - } + '[attr.tabindex]': '1', + }, }) export class ExternalDialogWrapperComponent { @Input() component?: Type; @@ -225,14 +225,14 @@ export class ExternalWindowComponent implements AfterViewInit, OnDestroy, OnChan }); this.observerClass.observe(window.document.body, { - attributeFilter: ['class'] + attributeFilter: ['class'], }); const document = this.externalWindow!.document; copyBodyClass(); this.electronWindow = Electron.isAvailable() ? Electron.getRemote().BrowserWindow.getAllWindows()[0] : undefined; - this.parentWindow = this.registry.getOuterActiveWindow(); + this.parentWindow = this.registry.getOuterActiveWindow() as WindowComponent; if (this.parentWindow && this.alwaysRaised) { this.parentWindow.windowState.disableInputs.next(true); @@ -249,7 +249,7 @@ export class ExternalWindowComponent implements AfterViewInit, OnDestroy, OnChan document.body, this.componentFactoryResolver, this.applicationRef, - this.injector + this.injector, ); document.addEventListener('click', () => detectChangesNextFrame()); diff --git a/packages/desktop-ui/src/components/window/window-content.component.ts b/packages/desktop-ui/src/components/window/window-content.component.ts index fcc8e8cbb..afe6d8c6f 100644 --- a/packages/desktop-ui/src/components/window/window-content.component.ts +++ b/packages/desktop-ui/src/components/window/window-content.component.ts @@ -8,12 +8,15 @@ * You should have received a copy of the MIT License along with this program. */ -import { AfterViewInit, ChangeDetectorRef, Component, ElementRef, EventEmitter, Input, OnChanges, Output, SimpleChanges, ViewChild } from '@angular/core'; -import { WindowSidebarComponent } from './window-sidebar.component'; +import { AfterViewInit, ChangeDetectorRef, Component, ElementRef, EventEmitter, Input, OnChanges, Output, SimpleChanges, TemplateRef, ViewChild } from '@angular/core'; import { Subject } from 'rxjs'; import { WindowState } from './window-state'; import { triggerResize } from '../../core/utils'; +interface WinSidebar { + template: TemplateRef; +} + @Component({ selector: 'dui-window-content', template: ` @@ -57,7 +60,7 @@ export class WindowContentComponent implements OnChanges, AfterViewInit { @Output() sidebarWidthChange = new EventEmitter(); - toolbar?: WindowSidebarComponent; + toolbar?: WinSidebar; @ViewChild('sidebar', { static: false }) public sidebar?: ElementRef; @ViewChild('sidebarContainer', { static: false }) public sidebarContainer?: ElementRef; @@ -84,7 +87,7 @@ export class WindowContentComponent implements OnChanges, AfterViewInit { } } - registerSidebar(sidebar: WindowSidebarComponent) { + registerSidebar(sidebar: WinSidebar) { this.toolbar = sidebar; setTimeout(() => { this.sidebarMoved(); diff --git a/packages/desktop-ui/src/components/window/window-header.component.ts b/packages/desktop-ui/src/components/window/window-header.component.ts index ee7f5f462..6b9be0d1a 100644 --- a/packages/desktop-ui/src/components/window/window-header.component.ts +++ b/packages/desktop-ui/src/components/window/window-header.component.ts @@ -58,7 +58,6 @@ export class WindowHeaderComponent implements OnDestroy { protected element: ElementRef, ) { this.windowState = windowState; - windowState.header = this; } ngOnDestroy() { diff --git a/packages/desktop-ui/src/components/window/window-state.ts b/packages/desktop-ui/src/components/window/window-state.ts index c1adb8709..ed3a59dbb 100644 --- a/packages/desktop-ui/src/components/window/window-state.ts +++ b/packages/desktop-ui/src/components/window/window-state.ts @@ -9,27 +9,36 @@ */ import { ChangeDetectorRef, Injectable, TemplateRef, ViewContainerRef } from '@angular/core'; -import { ButtonGroupComponent } from '../button/button.component'; -import { WindowHeaderComponent, WindowToolbarContainerComponent } from './window-header.component'; import { arrayRemoveItem } from '@deepkit/core'; -import { WindowComponent } from './window.component'; import { WindowMenuState } from './window-menu'; import { BehaviorSubject } from 'rxjs'; import { detectChangesNextFrame } from '../app/utils'; +interface WinHeader { + getBottomPosition(): number; +} + +interface Win { + id: number; + electronWindow?: any; + getClosestNonDialogWindow(): Win | undefined; + header?: WinHeader; + viewContainerRef: ViewContainerRef; +} + @Injectable() export class WindowRegistry { id = 0; - registry = new Map(); - windowHistory: WindowComponent[] = []; - activeWindow?: WindowComponent; + windowHistory: Win[] = []; + activeWindow?: Win; /** * When BrowserWindow of electron is focused. @@ -49,19 +58,19 @@ export class WindowRegistry { return [...this.registry.keys()].filter(v => !!v.electronWindow).map(v => v.electronWindow); } - register(win: WindowComponent, cd: ChangeDetectorRef, state: WindowState, menu: WindowMenuState, viewContainerRef: ViewContainerRef) { + register(win: Win, cd: ChangeDetectorRef, state: WindowState, menu: WindowMenuState, viewContainerRef: ViewContainerRef) { this.id++; win.id = this.id; this.registry.set(win, { - state, menu, cd, viewContainerRef + state, menu, cd, viewContainerRef, }); } /** * Finds the activeWindow and returns its most outer parent. */ - getOuterActiveWindow(): WindowComponent | undefined { + getOuterActiveWindow(): Win | undefined { if (this.activeWindow) return this.activeWindow.getClosestNonDialogWindow(); } @@ -77,7 +86,7 @@ export class WindowRegistry { throw new Error('No active window'); } - focus(win: WindowComponent) { + focus(win: Win) { if (this.activeWindow === win) return; const reg = this.registry.get(win); @@ -93,7 +102,7 @@ export class WindowRegistry { detectChangesNextFrame(); } - blur(win: WindowComponent) { + blur(win: Win) { const reg = this.registry.get(win); if (reg) { reg.state.focus.next(false); @@ -105,7 +114,7 @@ export class WindowRegistry { detectChangesNextFrame(); } - unregister(win: WindowComponent) { + unregister(win: Win) { const reg = this.registry.get(win); if (reg) { reg.state.focus.next(false); @@ -121,15 +130,19 @@ export class WindowRegistry { } } +interface AlignedButtonGroup { + sidebarMoved: () => void; + activateOneTimeAnimation: () => void; +} + @Injectable() export class WindowState { - public buttonGroupAlignedToSidebar?: ButtonGroupComponent; - public header?: WindowHeaderComponent; + public buttonGroupAlignedToSidebar?: AlignedButtonGroup; public focus = new BehaviorSubject(false); public disableInputs = new BehaviorSubject(false); public toolbars: { [name: string]: TemplateRef[] } = {}; - public toolbarContainers: { [name: string]: WindowToolbarContainerComponent } = {}; + public toolbarContainers: { [name: string]: { toolbarsUpdated: () => void } } = {}; closable = true; maximizable = true; @@ -140,7 +153,7 @@ export class WindowState { public addToolbarContainer(forName: string, template: TemplateRef) { if (!this.toolbars[forName]) { - this.toolbars[forName] = [] + this.toolbars[forName] = []; } this.toolbars[forName].push(template); diff --git a/website/src/pages/documentation/ui/getting-started.md b/website/src/pages/documentation/ui/getting-started.md new file mode 100644 index 000000000..87d472ff3 --- /dev/null +++ b/website/src/pages/documentation/ui/getting-started.md @@ -0,0 +1,186 @@ +# Getting Started + +```sh +npm install @deepkit/desktop-ui @angular/cdk +``` + +Next you need to path alias it to point to @deepkit/desktop-ui/dist since desktop-ui is a TypeScript only package per default. + + +```json +{ + "paths": { + "@deepkit/desktop-ui": ["@deepkit/desktop-ui/dist"] + } +} +``` + +Include the styles and icons: + +```json +{ + "projects": { + "test-dui": { + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/test-dui", + "index": "src/index.html", + "main": "src/main.ts", + "styles": [ + "node_modules/@deepkit/desktop-ui/src/scss/all.scss", + "node_modules/@deepkit/desktop-ui/src/scss/icon.scss", + "src/styles.scss" + ] + } + } + } + } + } +} +``` + +Now use it either in a module: + +```typescript +import { + DuiButtonModule, + DuiCheckboxModule, + DuiFormComponent, + DuiInputModule, + DuiRadioboxModule, + DuiSelectModule, + DuiWindowModule, + DuiIconModule, + DuiListModule, + DuiTableModule, + DuiAppModule, + DuiDialogModule, + DuiSliderModule, + DuiEmojiModule, +} from '@deepkit/desktop-ui'; + +@NgModule({ + declarations: [ + AppComponent, + ], + imports: [ + FormsModule, + ReactiveFormsModule, + BrowserModule, + AppRoutingModule, + + DuiAppModule.forRoot(), //<--- important# + DuiWindowModule.forRoot(), + + DuiCheckboxModule, + DuiButtonModule, + DuiInputModule, + DuiFormComponent, + DuiRadioboxModule, + DuiSelectModule, + DuiIconModule, + DuiListModule, + DuiTableModule, + DuiButtonModule, + DuiDialogModule, + DuiEmojiModule, + DuiSliderModule, + ], + providers: [], + bootstrap: [AppComponent] +}) +export class AppModule { +} +``` + +or use it with newer Angular without modules: + +```typescript +import { ApplicationConfig, importProvidersFrom, ɵprovideZonelessChangeDetection } from '@angular/core'; +import { DuiAppModule, DuiWindowModule } from "@deepkit/desktop-ui"; + +export const appConfig: ApplicationConfig = { + providers: [ + importProvidersFrom(DuiAppModule.forRoot()), + importProvidersFrom(DuiWindowModule.forRoot()), + ], +}; + +import { Component } from '@angular/core'; +import { RouterOutlet } from '@angular/router'; +import { DuiWindowModule } from "@deepkit/desktop-ui"; + +@Component({ + selector: 'app-root', + standalone: true, + imports: [ + RouterOutlet, + DuiWindowModule, + ], + template: ` + + + + + + + `, + styles: [], +}) +export class RootComponent { + constructor() { + } +} +``` + +# Disable Zone.js + +To get better performance it's recommended to disable Zone.js. +This library implemented some workarounds to make most other libraries work without Zonejs, but that's not guaranteed. + +It's best to update your dependencies that support zoneless already. + +Open `main.ts` and adjust accordingly. The important part is `ngZone: 'noop'. + +```typescript +export const appConfig: ApplicationConfig = { + providers: [ + ApiClient, + { provide: RpcWebSocketClient, useFactory: () => new RpcWebSocketClient(ApiClient.getServerHost()) }, + provideRouter(routes), + ɵprovideZonelessChangeDetection() + ] +}; +``` + +Then use Signals, RxJS, or ChangeDetectorRef to trigger change detection. + +# Window + +```angular2html + + + + Angular Desktop UI + + + + + + + + + + +
+ This is the window content +
+
+
+``` + +Please note that you need at least one and max one `dui-window` element. +Multiple windows are currently not supported except if you use a new Electron Window instance (and thus bootstrap the whole Angular application again). This is currently a limitation with Angular itself not supporting multiple HTML documents. +