UNPKG

@santinobch/os-window-angular

Version:

Create windows inside a browser window!

734 lines (729 loc) 727 kB
import * as i0 from '@angular/core'; import { Injectable, Directive, Component, ViewEncapsulation, HostBinding, Input, NgModule } from '@angular/core'; import { coerceNumberProperty, coerceBooleanProperty } from '@angular/cdk/coercion'; import * as i2 from '@angular/cdk/drag-drop'; import { DragDropModule } from '@angular/cdk/drag-drop'; import * as i3 from 'ngx-scrollbar'; import { NgScrollbarModule } from 'ngx-scrollbar'; import { CommonModule } from '@angular/common'; const THEME_LIST = [ { name: 'arc', variants: ['light', 'dark'], palette: [ 'red', 'orange', 'yellow', 'lime', 'green', 'aqua', 'blue', 'purple', 'magenta', ], }, { name: 'win98', variants: ['classic', 'vaporwave'], palette: [ 'red', 'orange', 'yellow', 'lime', 'green', 'aqua', 'blue', 'purple', 'magenta', ], }, ]; class OsConfigService { constructor() { this.globalTheme = { name: 'arc', variant: 'light', }; this.zIndex = 1; this.userThemeList = THEME_LIST; } // This stores windows information (id and styles) getInstances() { return this.instances; } setInstances(shared) { this.instances.push(shared); } getGlobalTheme() { const GLOBAL_THEME = localStorage.getItem('GLOBAL_THEME'); if (GLOBAL_THEME === null) { return this.globalTheme; } return JSON.parse(GLOBAL_THEME); } setGlobalTheme(style) { this.globalTheme = style; localStorage.setItem('GLOBAL_THEME', JSON.stringify(this.globalTheme)); } /** * Returns last zIndex value * * When a window is focused, it's zIndex value changes, * making it appear in front of the other windows */ getZIndex() { return this.zIndex; } /** * Sets last zIndex value * * When a window is focused, it's zIndex value changes, * making it appear in front of the other windows */ setZIndex(zIndex) { this.zIndex = zIndex; } addTheme(theme) { this.userThemeList.push(theme); localStorage.setItem('THEME_LIST', JSON.stringify(this.userThemeList)); } getThemes() { const THEME_LIST = localStorage.getItem('THEME_LIST'); if (THEME_LIST === null) { return THEME_LIST; } return JSON.parse(THEME_LIST); } } OsConfigService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.1.4", ngImport: i0, type: OsConfigService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); OsConfigService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.1.4", ngImport: i0, type: OsConfigService, providedIn: 'root' }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.1.4", ngImport: i0, type: OsConfigService, decorators: [{ type: Injectable, args: [{ providedIn: 'root', }] }], ctorParameters: function () { return []; } }); class StyleClass { constructor(_componentElement, _renderer, _globalConfigService, _componentName) { this._componentElement = _componentElement; this._renderer = _renderer; this._globalConfigService = _globalConfigService; this._componentName = _componentName; this.globalConfigData = { name: '', variant: '', }; this.style = { name: '', variant: '', color: '', }; this.previousStyle = { name: '', variant: '', color: '', }; this.componentElement = _componentElement; this.renderer = _renderer; this.globalConfigService = _globalConfigService; this.componentName = _componentName; } isValidStyle() { if (this.style.name !== '' && this.style.name !== undefined && this.style.variant !== '' && this.style.variant !== undefined) { for (const i of this.globalConfigService.getThemes()) { if (i.name == this.style.name) { for (const v of i.variants) { if (v == this.style.variant) { return true; } } console.error('Invalid variant at ' + this.componentName + ' component: ' + this.style.variant); return false; } } console.error('Invalid Theme at ' + this.componentName + ' component: ' + this.style.name); return false; } return false; } isValidColor() { if (this.style.name !== '' && this.style.name !== undefined && this.style.color !== '' && this.style.color !== undefined) { for (const i of this.globalConfigService.getThemes()) { if (i.name == this.style.name) { for (const p of i.palette) { if (p == this.style.color) { return true; } } console.error('Invalid color at ' + this.componentName + ' component: ' + this.style.color); return false; } } console.error('Invalid Theme at ' + this.componentName + ' component: ' + this.style.name); return false; } return false; } getStyle() { return (this.style.name + '-' + this.style.variant + '-os-' + this.componentName); } getPreviousStyle() { return (this.previousStyle.name + '-' + this.previousStyle.variant + '-os-' + this.componentName); } getColor() { return this.style.color + '-os-' + this.componentName; } getPreviousColor() { return this.previousStyle.color + '-os-' + this.componentName; } loadGlobalStyles() { //Global theme config this.globalConfigData = this.globalConfigService.getGlobalTheme(); this.style.name = this.globalConfigData.name; this.style.variant = this.globalConfigData.variant; this.renderer.addClass(this.componentElement.nativeElement, this.getStyle()); } loadStyles() { if (this.isValidStyle()) { //Removes old theme class if (this.previousStyle.name !== '' && this.previousStyle.name !== undefined && this.previousStyle.variant !== '' && this.previousStyle.variant !== undefined) { this.renderer.removeClass(this.componentElement.nativeElement, this.getPreviousStyle()); } this.previousStyle.name = this.style.name; this.previousStyle.variant = this.style.variant; //Adds theme class this.renderer.addClass(this.componentElement.nativeElement, this.getStyle()); } else { this.loadGlobalStyles(); } } loadColor() { if (this.isValidColor()) { if (this.previousStyle.color !== '' && this.previousStyle.color !== undefined) { this.renderer.removeClass(this.componentElement.nativeElement, this.getPreviousColor()); } this.previousStyle.color = this.style.color; this.renderer.addClass(this.componentElement.nativeElement, this.getColor()); } } onChanges(changes) { if (changes != undefined) { if (changes['theme'] != undefined) { this.style.name = changes['theme'].currentValue; } if (changes.variant != undefined) { this.style.variant = changes['variant'].currentValue; } if (changes['theme'] != undefined || changes['variant'] != undefined) { this.loadStyles(); } if (changes['color'] != undefined) { this.loadColor(); } } } } function clamp(v, min = 0, max = Number.MAX_SAFE_INTEGER) { return Math.max(min, Math.min(max, coerceNumberProperty(v))); } class OsWindowClass { constructor(componentElement, renderer, globalConfigService) { this.componentElement = componentElement; this.renderer = renderer; this.globalConfigService = globalConfigService; this.mousePos = { x: 0, y: 0 }; //Anchor stores temporary point of the current resize CdkDragMove event this.anchor = { x: 0, y: 0 }; this.minHeight = 200; this.minWidth = 200; this.size = { height: { previous: 200, current: 200, unit: 'px', }, width: { previous: 200, current: 200, unit: 'px', }, }; this.position = { resize: { x: 0, y: 0 }, current: { x: 0, y: 0 }, next: { x: 0, y: 0 }, zIndex: { current: 0, next: 1, }, }; this.cdkAnchors = { n: { x: 0, y: 0 }, ne: { x: 0, y: 0 }, e: { x: 0, y: 0 }, se: { x: 0, y: 0 }, s: { x: 0, y: 0 }, sw: { x: 0, y: 0 }, w: { x: 0, y: 0 }, nw: { x: 0, y: 0 }, }; this.state = { minimized: false, maximized: false, }; this.rules = { disableResize: false, minimizable: true, maximizable: true, closable: true, }; this.styleConfig = new StyleClass(componentElement, renderer, globalConfigService, 'window'); } setStyle(_elementRef, property, value) { _elementRef.nativeElement.style.setProperty(property, value); } getStyle(_elementRef, property) { return getComputedStyle(_elementRef.nativeElement).getPropertyValue(property); } clamp(input, max) { return input >= max ? input : max; } clampHeight(_elementRef, _height, _minHeight) { if (_minHeight) { _height = this.clamp(_height, _minHeight); } return _height; } clampWidth(_elementRef, _width, _minWidth) { if (_minWidth) { _width = this.clamp(_width, _minWidth); } return _width; } setDimesions() { this.size.width.current = this.clampWidth(this.componentElement, this.size.width.current, this.minWidth); this.size.height.current = this.clampHeight(this.componentElement, this.size.height.current, this.minHeight); } //////////////////////// // Position // //////////////////////// setPosition(positionStr) { const X = parseInt(positionStr[0]); const Y = parseInt(positionStr[1]); if (!Number.isNaN(X)) { this.position.next.x = X; } else { switch (positionStr[0]) { case 'left': this.position.next.x = 0; break; case 'center': this.position.next.x = window.innerWidth / 2 - this.size.width.current / 2; break; case 'right': this.position.next.x = window.innerWidth - this.size.width.current; break; default: this.position.next.x = 0; break; } } if (!Number.isNaN(Y)) { this.position.next.y = Y + window.innerHeight; } else { //To hide the window element we need to set it top: -100% in scss, //so we later need to calculate everything + innerHeight switch (positionStr[1]) { case 'top': this.position.next.y = window.innerHeight; break; case 'center': this.position.next.y = window.innerHeight + (window.innerHeight / 2 - this.size.height.current / 2); break; case 'bottom': this.position.next.y = window.innerHeight + (window.innerHeight - this.size.height.current); break; default: this.position.next.y = window.innerHeight; break; } } this.position.current = this.position.next; } //////////////////////// // Style // //////////////////////// //////////////////////// // Rules // //////////////////////// loadRules() { //Minimizable? if (!this.rules.minimizable) { this.setStyle(this.componentElement, '--minimizeButton', 'none'); } //Maximizable? if (!this.rules.maximizable) { this.setStyle(this.componentElement, '--maximizeButton', 'none'); } //Closable? if (!this.rules.closable) { this.setStyle(this.componentElement, '--closeButton', 'none'); } //Resizable? if (this.rules.disableResize) { this.setStyle(this.componentElement, '--cursorN', 'auto'); this.setStyle(this.componentElement, '--cursorNE', 'auto'); this.setStyle(this.componentElement, '--cursorE', 'auto'); this.setStyle(this.componentElement, '--cursorSE', 'auto'); this.setStyle(this.componentElement, '--cursorS', 'auto'); this.setStyle(this.componentElement, '--cursorSW', 'auto'); this.setStyle(this.componentElement, '--cursorW', 'auto'); this.setStyle(this.componentElement, '--cursorNW', 'auto'); } } //////////////////////// // Controls // //////////////////////// minimize() { //TODO } maximize() { if (this.rules.maximizable) { if (this.state.maximized == false) { //Saving value for later this.size.height.previous = this.size.height.current; this.size.width.previous = this.size.width.current; this.size.height.current = 100; this.size.height.unit = 'vh'; this.size.width.current = 100; this.size.width.unit = 'vw'; this.position.current = { x: 0, y: window.innerHeight }; this.state.maximized = true; this.rules.disableResize = true; } else { //Restoring window size this.size.height.current = this.size.height.previous; this.size.height.unit = 'px'; this.size.width.current = this.size.width.previous; this.size.width.unit = 'px'; this.position.current = this.position.next; this.state.maximized = false; this.rules.disableResize = false; } } } //When maximized and then dragged the window demaximizes //and puts itself aligned with the mouse position demaximize() { if (this.state.maximized == true) { this.position.next = { x: this.mousePos.x - this.size.width.current / 2, y: this.mousePos.y + window.innerHeight - 20, }; this.maximize(); } } close() { this.componentElement.nativeElement.remove(); } //////////////////////// // Resize & movement // //////////////////////// storeMousePos(event) { this.mousePos = { x: event.x, y: event.y, }; } //Sets some variables when the resize drag starts, we use them later startResize() { this.size.height.previous = this.size.height.current; this.size.width.previous = this.size.width.current; } resize(dragEvent, direction) { let directionSplit = [direction.charAt(0), direction.charAt(1)]; this.anchor = dragEvent.source.getFreeDragPosition(); directionSplit.forEach(dir => { this.resizeDirection(dir); }); //Reset anchor position switch (direction) { case 'n': this.cdkAnchors.n = { x: 0, y: 0 }; break; case 'ne': this.cdkAnchors.n = { x: 0, y: 0 }; break; case 'e': this.cdkAnchors.e = { x: 0, y: 0 }; break; case 'se': this.cdkAnchors.se = { x: 0, y: 0 }; break; case 's': this.cdkAnchors.s = { x: 0, y: 0 }; break; case 'sw': this.cdkAnchors.sw = { x: 0, y: 0 }; break; case 'w': this.cdkAnchors.w = { x: 0, y: 0 }; break; case 'nw': this.cdkAnchors.nw = { x: 0, y: 0 }; break; } } resizeDirection(direction) { this.position.resize = this.position.next; switch (direction) { case 'n': //Checks that the new position and dimesions produce a minHeight lower than the required if (this.size.height.previous - this.anchor.y >= this.minHeight) { this.position.resize = { x: this.position.resize.x, y: this.position.next.y + this.anchor.y, }; this.size.height.current = this.size.height.previous - this.anchor.y; this.size.height.current = this.clampHeight(this.componentElement, this.size.height.current, this.minHeight); this.position.current = { x: this.position.current.x, y: this.position.resize.y, }; } break; case 'e': this.size.width.current = this.size.width.previous + this.anchor.x; this.size.width.current = this.clampWidth(this.componentElement, this.size.width.current, this.minWidth); break; case 's': this.size.height.current = this.size.height.previous + this.anchor.y; this.size.height.current = this.clampHeight(this.componentElement, this.size.height.current, this.minHeight); break; case 'w': //Checks that the new position and dimesions produce a minHeight lower than the required if (this.size.width.previous - this.anchor.x >= this.minWidth) { this.position.resize = { x: this.position.next.x + this.anchor.x, y: this.position.resize.y, }; this.size.width.current = this.size.width.previous - this.anchor.x; this.size.width.current = this.clampWidth(this.componentElement, this.size.width.current, this.minWidth); this.position.current = { x: this.position.resize.x, y: this.position.current.y, }; } break; } } endResize() { this.position.next = this.position.current; } //When releasing the os-window the user may leave it outside of the browser window //which would make it imposible to interact with the component again, //this makes the window 'bounce' back into sight correctEndPosition(event) { this.position.next = event.source.getFreeDragPosition(); //Fix for Y position, the window-bar will always be visible if (this.position.next.y < window.innerHeight) { this.position.next.y = window.innerHeight; } else if (this.position.next.y > window.innerHeight * 2 - 40) { this.position.next.y = window.innerHeight * 2 - 40; } //Fix for X position, a quarter of the window will always be visible if (this.position.next.x < -((this.size.width.current / 4) * 3)) { this.position.next.x = -((this.size.width.current / 4) * 3); } else if (this.position.next.x > window.innerWidth - this.size.width.current / 4) { this.position.next.x = window.innerWidth - this.size.width.current / 4; } this.position.current = this.position.next; } ////////////////////////////// // Other user interaction // ////////////////////////////// //When a window is clicked we want to change it's z-index value and apply some styles focus() { //We get the current global z-index this.position.zIndex.next = this.globalConfigService.getZIndex(); //This will be unequal if another window has been focused on if (this.position.zIndex.current != this.position.zIndex.next) { this.position.zIndex.next++; this.position.zIndex.current = this.position.zIndex.next; //Updating global z-index this.globalConfigService.setZIndex(this.position.zIndex.current); } //After that we remove the 'focused' class from all the windows let focused = document.getElementsByClassName('focused'); let i = 0; while (i < focused.length) { this.renderer.removeClass(focused[i], 'focused'); i++; } //We add the 'focused' class to the current window this.renderer.addClass(this.componentElement.nativeElement.firstChild, 'focused'); } } class OsWindowTitle { } OsWindowTitle.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.1.4", ngImport: i0, type: OsWindowTitle, deps: [], target: i0.ɵɵFactoryTarget.Directive }); OsWindowTitle.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "15.1.4", type: OsWindowTitle, selector: "window-title, [window-title], [windowTitle]", exportAs: ["OsWindowTitle"], ngImport: i0 }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.1.4", ngImport: i0, type: OsWindowTitle, decorators: [{ type: Directive, args: [{ selector: `window-title, [window-title], [windowTitle]`, exportAs: 'OsWindowTitle', }] }] }); class OsWindowContent { } OsWindowContent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.1.4", ngImport: i0, type: OsWindowContent, deps: [], target: i0.ɵɵFactoryTarget.Directive }); OsWindowContent.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "15.1.4", type: OsWindowContent, selector: "window-content, [window-content], [windowContent]", exportAs: ["WindowContent"], ngImport: i0 }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.1.4", ngImport: i0, type: OsWindowContent, decorators: [{ type: Directive, args: [{ selector: `window-content, [window-content], [windowContent]`, exportAs: 'WindowContent', }] }] }); class OsWindowComponent { constructor(componentElement, renderer, globalConfigService) { this.componentElement = componentElement; this.renderer = renderer; this.globalConfigService = globalConfigService; this.win = new OsWindowClass(this.componentElement, this.renderer, this.globalConfigService); } ///////////////////////// //// Host bindings //// ///////////////////////// get zIndex() { return this.win.position.zIndex.current; } ////////////////////// //// Inputs //// ////////////////////// // Component theme // get theme() { return this.win.styleConfig.style.name; } set theme(v) { this.win.styleConfig.style.name = v; } get variant() { return this.win.styleConfig.style.variant; } set variant(v) { this.win.styleConfig.style.variant = v; } // Size & position /// get minHeight() { return this.win.minHeight; } set minHeight(v) { this.win.minHeight = clamp(v || this.win.minHeight); } get minWidth() { return this.win.minWidth; } set minWidth(v) { this.win.minWidth = clamp(v || this.win.minWidth); } get height() { return this.win.size.height.current; } set height(v) { this.win.size.height.current = clamp(v || this.win.minHeight); } get width() { return this.win.size.width.current; } set width(v) { this.win.size.width.current = clamp(v || this.win.minWidth); } set position(v) { this.positionStr = v.split(' ', 2); } // Rules // get resizable() { return this.win.rules.disableResize; } set resizable(v) { this.win.rules.disableResize = !v; } get minimizable() { return this.win.rules.minimizable; } set minimizable(v) { this.win.rules.minimizable = v; } get maximizable() { return this.win.rules.maximizable; } set maximizable(v) { this.win.rules.maximizable = v; } get closable() { return this.win.rules.closable; } set closable(v) { this.win.rules.closable = v; } ngOnInit() { } ngAfterViewInit() { /* We first care about the dimensions and position of the window */ //Initial width & height, also returns corrected value if bellow minimal this.win.setDimesions(); //Sets initial position this.win.setPosition(this.positionStr); /* After dimensions & position we set the themes and rules */ //Setting theme of component this.win.styleConfig.loadStyles(); //Colored windows someday? //this.win.styleConfig.loadColor(); this.win.loadRules(); } ngOnChanges(changes) { //Changing styles on runtime this.win.styleConfig.onChanges(changes); } } OsWindowComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.1.4", ngImport: i0, type: OsWindowComponent, deps: [{ token: i0.ElementRef }, { token: i0.Renderer2 }, { token: OsConfigService }], target: i0.ɵɵFactoryTarget.Component }); OsWindowComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.1.4", type: OsWindowComponent, selector: "os-window", inputs: { theme: "theme", variant: "variant", minHeight: "minHeight", minWidth: "minWidth", height: "height", width: "width", position: "position", resizable: "resizable", minimizable: "minimizable", maximizable: "maximizable", closable: "closable" }, host: { properties: { "style.z-index": "this.zIndex" }, classAttribute: "os-window" }, usesOnChanges: true, ngImport: i0, template: "<!--\nTO DO:\n- Maximized change screen DELAYED\n- Arc Theme HALF DONE\n- Win98 Theme Check inner shadow scroll right & bottom, next release\n- Revise code\n- Documentation\n- Licencing\n- Release\n\n-->\n\n<div\n class=\"window-child\"\n [style.width]=\"win.size.width.current + win.size.width.unit\"\n [style.height]=\"win.size.height.current + win.size.height.unit\"\n cdkDrag\n (cdkDragStarted)=\"this.win.demaximize()\"\n (cdkDragEnded)=\"this.win.correctEndPosition($event)\"\n [cdkDragFreeDragPosition]=\"this.win.position.current\"\n (mousedown)=\"this.win.storeMousePos($event)\"\n (mousedown)=\"this.win.focus()\">\n <div\n cdkDrag\n cdkDragLockAxis=\"y\"\n (cdkDragStarted)=\"this.win.startResize()\"\n (cdkDragMoved)=\"this.win.resize($event, 'n')\"\n (cdkDragEnded)=\"this.win.endResize()\"\n [cdkDragFreeDragPosition]=\"this.win.cdkAnchors.n\"\n [cdkDragDisabled]=\"this.win.rules.disableResize\"\n (mousedown)=\"this.win.focus()\"\n class=\"window-resize n\"></div>\n <div\n cdkDrag\n (cdkDragStarted)=\"this.win.startResize()\"\n (cdkDragMoved)=\"this.win.resize($event, 'ne')\"\n [cdkDragFreeDragPosition]=\"this.win.cdkAnchors.ne\"\n [cdkDragDisabled]=\"this.win.rules.disableResize\"\n (mousedown)=\"this.win.focus()\"\n class=\"window-resize ne\"></div>\n <div\n cdkDrag\n cdkDragLockAxis=\"x\"\n (cdkDragStarted)=\"this.win.startResize()\"\n (cdkDragMoved)=\"this.win.resize($event, 'e')\"\n [cdkDragFreeDragPosition]=\"this.win.cdkAnchors.e\"\n [cdkDragDisabled]=\"this.win.rules.disableResize\"\n (mousedown)=\"this.win.focus()\"\n class=\"window-resize e\"></div>\n <div\n cdkDrag\n (cdkDragStarted)=\"this.win.startResize()\"\n (cdkDragMoved)=\"this.win.resize($event, 'se')\"\n [cdkDragFreeDragPosition]=\"this.win.cdkAnchors.se\"\n [cdkDragDisabled]=\"this.win.rules.disableResize\"\n (mousedown)=\"this.win.focus()\"\n class=\"window-resize se\"></div>\n <div\n cdkDrag\n cdkDragLockAxis=\"y\"\n (cdkDragStarted)=\"this.win.startResize()\"\n (cdkDragMoved)=\"this.win.resize($event, 's')\"\n [cdkDragFreeDragPosition]=\"this.win.cdkAnchors.s\"\n [cdkDragDisabled]=\"this.win.rules.disableResize\"\n (mousedown)=\"this.win.focus()\"\n class=\"window-resize s\"></div>\n <div\n cdkDrag\n (cdkDragStarted)=\"this.win.startResize()\"\n (cdkDragMoved)=\"this.win.resize($event, 'sw')\"\n (cdkDragEnded)=\"this.win.endResize()\"\n [cdkDragFreeDragPosition]=\"this.win.cdkAnchors.sw\"\n [cdkDragDisabled]=\"this.win.rules.disableResize\"\n (mousedown)=\"this.win.focus()\"\n class=\"window-resize sw\"></div>\n <div\n cdkDrag\n cdkDragLockAxis=\"x\"\n (cdkDragStarted)=\"this.win.startResize()\"\n (cdkDragMoved)=\"this.win.resize($event, 'w')\"\n (cdkDragEnded)=\"this.win.endResize()\"\n [cdkDragFreeDragPosition]=\"this.win.cdkAnchors.w\"\n [cdkDragDisabled]=\"this.win.rules.disableResize\"\n (mousedown)=\"this.win.focus()\"\n class=\"window-resize w\"></div>\n <div\n cdkDrag\n (cdkDragStarted)=\"this.win.startResize()\"\n (cdkDragMoved)=\"this.win.resize($event, 'nw')\"\n (cdkDragEnded)=\"this.win.endResize()\"\n [cdkDragFreeDragPosition]=\"this.win.cdkAnchors.nw\"\n [cdkDragDisabled]=\"this.win.rules.disableResize\"\n (mousedown)=\"this.win.focus()\"\n class=\"window-resize nw\"></div>\n\n <div class=\"window-bar\">\n <div\n class=\"window-bar-anchor\"\n cdkDragHandle\n (dblclick)=\"this.win.maximize()\">\n <div class=\"window-title\">\n <ng-content\n select=\"window-title, [window-title], [windowTitle]\"></ng-content>\n </div>\n\n <div class=\"controls-separator\"></div>\n\n <div class=\"controls-container\">\n <button class=\"control minimize\" (click)=\"this.win.minimize()\"></button>\n <button class=\"control maximize\" (click)=\"this.win.maximize()\"></button>\n <button class=\"control close\" (click)=\"this.win.close()\"></button>\n </div>\n </div>\n </div>\n\n <div class=\"window-content\">\n <ng-scrollbar\n [class]=\"theme + '-scrollbar'\"\n track=\"all\"\n trackClass=\"os-scrollbar-track\"\n thumbClass=\"os-scrollbar-thumb\">\n <ng-content select=\"window-content, [window-content], [windowContent]\">\n </ng-content>\n </ng-scrollbar>\n </div>\n</div>\n", styles: [".os-window{--minimizeButton: block;--maximizeButton: block;--closeButton: block;--cursorN: n-resize;--cursorNE: ne-resize;--cursorE: e-resize;--cursorSE: se-resize;--cursorS: s-resize;--cursorSW: sw-resize;--cursorW: w-resize;--cursorNW: nw-resize;position:fixed;top:-100%;left:0}.window-child{display:flex;flex-direction:column}.window-child .window-resize{position:absolute}.window-child .n{top:calc(-4px - var(--winBorder));left:calc(4px - var(--winBorder));height:8px;width:calc(100% - (8px - 2 * var(--winBorder)));cursor:var(--cursorN)}.window-child .ne{top:calc(-4px - var(--winBorder));right:calc(-4px - var(--winBorder));height:8px;width:8px;cursor:var(--cursorNE)}.window-child .e{top:calc(4px - var(--winBorder));right:calc(-4px - var(--winBorder));height:calc(100% - (8px - 2 * var(--winBorder)));width:8px;cursor:var(--cursorE)}.window-child .se{bottom:calc(-4px - var(--winBorder));right:calc(-4px - var(--winBorder));height:8px;width:8px;cursor:var(--cursorSE)}.window-child .s{bottom:calc(-4px - var(--winBorder));left:calc(4px - var(--winBorder));height:8px;width:calc(100% - (8px - 2 * var(--winBorder)));cursor:var(--cursorS)}.window-child .sw{bottom:calc(-4px - var(--winBorder));left:calc(-4px - var(--winBorder));height:8px;width:8px;cursor:var(--cursorSW)}.window-child .w{top:calc(4px - var(--winBorder));left:calc(-4px - var(--winBorder));height:calc(100% - (8px - 2 * var(--winBorder)));width:8px;cursor:var(--cursorW)}.window-child .nw{top:calc(-4px - var(--winBorder));left:calc(-4px - var(--winBorder));height:8px;width:8px;cursor:var(--cursorNW)}.window-child .window-bar{padding-top:4px;padding-right:4px;padding-left:4px}.window-child .window-bar .window-bar-anchor{display:flex;flex-direction:row;align-items:center}.window-child .window-bar .window-bar-anchor .window-title{flex:1 1 0%}.window-child .window-bar .window-bar-anchor .controls-container{display:flex;align-items:center}.window-child .window-bar .window-bar-anchor .controls-container .minimize{display:var(--minimizeButton)}.window-child .window-bar .window-bar-anchor .controls-container .maximize{display:var(--maximizeButton)}.window-child .window-bar .window-bar-anchor .controls-container .close{display:var(--closeButton)}.window-child .window-content{height:100%;width:100%;overflow:auto}.window-child .window-content ng-scrollbar ng-content{flex:1 1 0%}.maximized{width:100vw;height:100vh}\n", "@import\"https://fonts.googleapis.com/css2?family=Open+Sans&display=swap\";.arc-light-os-window{--winBorder: 1px;--winRadius: 5px}.arc-light-os-window .window-child{border-radius:var(--winRadius);border:var(--winBorder) solid rgba(212,213,219,.95);box-shadow:0 0 0 1px #0000001a,0 8px 8px #0003;font-family:Futura Bk bt,\"Open Sans\",sans,Sans-Serif;font-size:9pt}.arc-light-os-window .window-child .window-bar{height:44px;background-color:#e7e8ebf2;border-color:#d4d5dbf2;border-bottom:1px solid rgba(212,213,219,.95);border-top-left-radius:calc(var(--winRadius) - 1);border-top-right-radius:calc(var(--winRadius) - 1)}.arc-light-os-window .window-child .window-bar .window-bar-anchor{height:36px;width:100%}.arc-light-os-window .window-child .window-bar .window-bar-anchor .window-title{margin-left:30px;color:#525d76cc}.arc-light-os-window .window-child .window-content{background-color:#fff;color:#5c616c}.arc-light-os-window .focused .window-bar{background-color:#e7e8eb}.arc-light-os-window .maximized{border:0px;border-radius:0}.arc-light-os-window .maximized .window-bar{border:0px;border-radius:0;background-color:#e7e8eb}.arc-light-os-window .controls-separator{margin-right:14px;transform:translate(4px,-1px);width:1px;height:15px;background:rgba(82,93,118,.15)}.arc-light-os-window .controls-container{margin-right:8px;width:76px;height:auto;display:flex;gap:14px}.arc-light-os-window .controls-container .control{padding:0;min-height:16px;width:16px;height:16px;border:0px;background-color:#0000;box-shadow:none;outline:0}.arc-light-os-window a{color:#2679db}.arc-light-os-window a:visited{color:#1e61b0}.arc-light-os-window a:visited:hover{color:#2679db}.arc-light-os-window a:hover{color:#5294e2}.arc-light-os-window .os-scrollbar-track{background-color:#ffffff1a;margin-bottom:5px}.arc-light-os-window .os-scrollbar-thumb{background-color:#b8babf99}.arc-light-os-window .minimize{background:url(\"data:image/svg+xml,%3C%3Fxml version%3D%221.0%22 encoding%3D%22UTF-8%22 standalone%3D%22no%22%3F%3E%3C!-- Created with Inkscape (http%3A%2F%2Fwww.inkscape.org%2F) --%3E%3Csvg width%3D%2216%22 height%3D%2216%22 id%3D%22svg9892%22 version%3D%221.1%22 inkscape%3Aversion%3D%220.91 r13725%22 sodipodi%3Adocname%3D%22assets.svg%22 xmlns%3Ainkscape%3D%22http%3A%2F%2Fwww.inkscape.org%2Fnamespaces%2Finkscape%22 xmlns%3Asodipodi%3D%22http%3A%2F%2Fsodipodi.sourceforge.net%2FDTD%2Fsodipodi-0.dtd%22 xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22 xmlns%3Asvg%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22 xmlns%3Ardf%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2F02%2F22-rdf-syntax-ns%23%22 xmlns%3Acc%3D%22http%3A%2F%2Fcreativecommons.org%2Fns%23%22 xmlns%3Adc%3D%22http%3A%2F%2Fpurl.org%2Fdc%2Felements%2F1.1%2F%22%3E %3Cdefs id%3D%22defs9894%22%3E %3ClinearGradient id%3D%22linearGradient6651%22%3E %3Cstop style%3D%22stop-color%3A%23ffffff%3Bstop-opacity%3A1%3B%22 offset%3D%220%22 id%3D%22stop6653%22 %2F%3E %3C%2FlinearGradient%3E %3ClinearGradient id%3D%22selected_fg_color%22 inkscape%3Aswatch%3D%22solid%22%3E %3Cstop style%3D%22stop-color%3A%23ffffffgit%3Bstop-opacity%3A1%3B%22 offset%3D%220%22 id%3D%22stop6555%22 %2F%3E %3C%2FlinearGradient%3E %3ClinearGradient id%3D%22selected_bg_color%22 inkscape%3Aswatch%3D%22solid%22%3E %3Cstop style%3D%22stop-color%3A%235294e2%3Bstop-opacity%3A1%3B%22 offset%3D%220%22 id%3D%22stop5956%22 %2F%3E %3C%2FlinearGradient%3E %3C%2Fdefs%3E %3Csodipodi%3Anamedview id%3D%22base%22 pagecolor%3D%22%23ffffff%22 bordercolor%3D%22%23666666%22 borderopacity%3D%221.0%22 inkscape%3Apageopacity%3D%220.0%22 inkscape%3Apageshadow%3D%222%22 inkscape%3Azoom%3D%228%22 inkscape%3Acx%3D%2237.764915%22 inkscape%3Acy%3D%2268.141665%22 inkscape%3Adocument-units%3D%22px%22 inkscape%3Acurrent-layer%3D%22layer3%22 showgrid%3D%22false%22 showborder%3D%22false%22 inkscape%3Awindow-width%3D%221259%22 inkscape%3Awindow-height%3D%22630%22 inkscape%3Awindow-x%3D%2265%22 inkscape%3Awindow-y%3D%2224%22 inkscape%3Awindow-maximized%3D%221%22 inkscape%3Asnap-nodes%3D%22true%22 inkscape%3Asnap-bbox%3D%22true%22 inkscape%3Abbox-paths%3D%22true%22 inkscape%3Asnap-global%3D%22true%22 showguides%3D%22false%22 inkscape%3Aguide-bbox%3D%22true%22 inkscape%3Abbox-nodes%3D%22true%22 inkscape%3Aobject-nodes%3D%22true%22 inkscape%3Asnap-bbox-midpoints%3D%22false%22 fit-margin-top%3D%220%22 fit-margin-left%3D%220%22 fit-margin-right%3D%220%22 fit-margin-bottom%3D%220%22 inkscape%3Ashowpageshadow%3D%222%22 inkscape%3Apagecheckerboard%3D%220%22 inkscape%3Adeskcolor%3D%22%23d1d1d1%22%3E %3Cinkscape%3Agrid type%3D%22xygrid%22 id%3D%22grid10919%22 empspacing%3D%225%22 visible%3D%22true%22 enabled%3D%22true%22 snapvisiblegridlinesonly%3D%22true%22 originx%3D%228.000005%22 originy%3D%22-1062%22 %2F%3E %3Csodipodi%3Aguide orientation%3D%221%2C0%22 position%3D%22255.875%2C-490.75%22 id%3D%22guide8384%22 inkscape%3Alocked%3D%22false%22 %2F%3E %3C%2Fsodipodi%3Anamedview%3E %3Cmetadata id%3D%22metadata9897%22%3E %3Crdf%3ARDF%3E %3Ccc%3AWork rdf%3Aabout%3D%22%22%3E %3Cdc%3Aformat%3Eimage%2Fsvg%2Bxml%3C%2Fdc%3Aformat%3E %3Cdc%3Atype rdf%3Aresource%3D%22http%3A%2F%2Fpurl.org%2Fdc%2Fdcmitype%2FStillImage%22 %2F%3E %3Cdc%3Atitle %2F%3E %3C%2Fcc%3AWork%3E %3C%2Frdf%3ARDF%3E %3C%2Fmetadata%3E %3Cg inkscape%3Alabel%3D%22assets%22 inkscape%3Agroupmode%3D%22layer%22 id%3D%22layer1%22 style%3D%22display%3Ainline%22 transform%3D%22translate(-116%2C228.63782)%22%3E %3Cg id%3D%22titlebutton-minimize%22 inkscape%3Alabel%3D%22%23g6247%22 transform%3D%22translate(-471%2C-43)%22 style%3D%22opacity%3A0.8%22%3E %3Cg id%3D%22g7138%22 style%3D%22display%3Ainline%3Bopacity%3A1%22 transform%3D%22translate(-781%2C-432.63782)%22%3E %3Cg transform%3D%22translate(-58)%22 style%3D%22display%3Ainline%3Bopacity%3A1%22 id%3D%22g4490-3-75%22%3E %3Cg id%3D%22g4092-0-7-0%22 style%3D%22display%3Ainline%22 transform%3D%22translate(58)%22 %2F%3E %3C%2Fg%3E %3Cpath inkscape%3Aconnector-curvature%3D%220%22 d%3D%22m 1373%2C254 v 2 h 6 v -2 z%22 id%3D%22rect9057-4-3%22 style%3D%22color%3A%23000000%3Bfont-style%3Anormal%3Bfont-variant%3Anormal%3Bfont-weight%3Anormal%3Bfont-stretch%3Anormal%3Bfont-size%3Amedium%3Bline-height%3Anormal%3Bfont-family%3ASans%3B-inkscape-font-specification%3ASans%3Btext-indent%3A0%3Btext-align%3Astart%3Btext-decoration%3Anone%3Btext-decoration-line%3Anone%3Bletter-spacing%3Anormal%3Bword-spacing%3Anormal%3Btext-transform%3Anone%3Bwriting-mode%3Alr-tb%3Bdirection%3Altr%3Bbaseline-shift%3Abaseline%3Btext-anchor%3Astart%3Bdisplay%3Ainline%3Boverflow%3Avisible%3Bvisibility%3Avisible%3Bopacity%3A1%3Bfill%3A%237a7f8b%3Bfill-opacity%3A1%3Bstroke%3Anone%3Bstroke-width%3A2%3Bmarker%3Anone%3Benable-background%3Aaccumulate%22 sodipodi%3Anodetypes%3D%22ccccc%22 %2F%3E %3C%2Fg%3E %3Crect y%3D%22-185.63782%22 x%3D%22587%22 height%3D%2216%22 width%3D%2216%22 id%3D%22rect17883-32%22 style%3D%22display%3Ainline%3Bopacity%3A1%3Bfill%3Anone%3Bfill-opacity%3A1%3Bstroke%3Anone%3Bstroke-width%3A1%3Bstroke-linecap%3Abutt%3Bstroke-linejoin%3Amiter%3Bstroke-miterlimit%3A4%3Bstroke-dasharray%3Anone%3Bstroke-dashoffset%3A0%3Bstroke-opacity%3A0%22 %2F%3E %3C%2Fg%3E %3C%2Fg%3E%3C%2Fsvg%3E\")}.arc-light-os-window .minimize:hover{background:url(\"data:image/svg+xml,%3C%3Fxml version%3D%221.0%22 encoding%3D%22UTF-8%22 standalone%3D%22no%22%3F%3E%3C!-- Created with Inkscape (http%3A%2F%2Fwww.inkscape.org%2F) --%3E%3Csvg width%3D%2216%22 height%3D%2216%22 id%3D%22svg9892%22 version%3D%221.1%22 inkscape%3Aversion%3D%220.91 r13725%22 sodipodi%3Adocname%3D%22assets.svg%22 xmlns%3Ainkscape%3D%22http%3A%2F%2Fwww.inkscape.org%2Fnamespaces%2Finkscape%22 xmlns%3Asodipodi%3D%22http%3A%2F%2Fsodipodi.sourceforge.net%2FDTD%2Fsodipodi-0.dtd%22 xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22 xmlns%3Asvg%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22 xmlns%3Ardf%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2F02%2F22-rdf-syntax-ns%23%22 xmlns%3Acc%3D%22http%3A%2F%2Fcreativecommons.org%2Fns%23%22 xmlns%3Adc%3D%22http%3A%2F%2Fpurl.org%2Fdc%2Felements%2F1.1%2F%22%3E %3Cdefs id%3D%22defs9894%22%3E %3ClinearGradient id%3D%22linearGradient6651%22%3E %3Cstop style%3D%22stop-color%3A%23ffffff%3Bstop-opacity%3A1%3B%22 offset%3D%220%22 id%3D%22stop6653%22 %2F%3E %3C%2FlinearGradient%3E %3ClinearGradient id%3D%22selected_fg_color%22 inkscape%3Aswatch%3D%22solid%22%3E %3Cstop style%3D%22stop-color%3A%23ffffffgit%3Bstop-opacity%3A1%3B%22 offset%3D%220%22 id%3D%22stop6555%22 %2F%3E %3C%2FlinearGradient%3E %3ClinearGradient id%3D%22selected_bg_color%22 inkscape%3Aswatch%3D%22solid%22%3E %3Cstop style%3D%22stop-color%3A%235294e2%3Bstop-opacity%3A1%3B%22 offset%3D%220%22 id%3D%22stop5956%22 %2F%3E %3C%2FlinearGradient%3E %3C%2Fdefs%3E %3Csodipodi%3Anamedview id%3D%22base%22 pagecolor%3D%22%23ffffff%22 bordercolor%3D%22%23666666%22 borderopacity%3D%221.0%22 inkscape%3Apageopacity%3D%220.0%22 inkscape%3Apageshadow%3D%222%22 inkscape%3Azoom%3D%228%22 inkscape%3Acx%3D%2237.764915%22 inkscape%3Acy%3D%2268.141665%22 inkscape%3Adocument-units%3D%22px%22 inkscape%3Acurrent-layer%3D%22layer3%22 showgrid%3D%22false%22 showborder%3D%22false%22 inkscape%3Awindow-width%3D%221259%22 inkscape%3Awindow-height%3D%22630%22 inkscape%3Awindow-x%3D%2265%22 inkscape%3Awindow-y%3D%2224%22 inkscape%3Awindow-maximized%3D%221%22 inkscape%3Asnap-nodes%3D%22true%22 inkscape%3Asnap-bbox%3D%22true%22 inkscape%3Abbox-paths%3D%22true%22 inkscape%3Asnap-global%3D%22true%22 showguides%3D%22false%22 inkscape%3Aguide-bbox%3D%22true%22 inkscape%3Abbox-nodes%3D%22true%22 inkscape%3Aobject-nodes%3D%22true%22 inkscape%3Asnap-bbox-midpoints%3D%22false%22 fit-margin-top%3D%220%22 fit-margin-left%3D%220%22 fit-margin-right%3D%220%22 fit-margin-bottom%3D%220%22 inkscape%3Ashowpageshadow%3D%222%22 inkscape%3Apagecheckerboard%3D%220%22 inkscape%3Adeskcolor%3D%22%23d1d1d1%22%3E %3Cinkscape%3Agrid type%3D%22xygrid%22 id%3D%22grid10919%22 empspacing%3D%225%22 visible%3D%22true%22 enabled%3D%22true%22 snapvisiblegridlinesonly%3D%22true%22 originx%3D%228.000005%22 originy%3D%22-1062%22 %2F%3E %3Csodipodi%3Aguide orientation%3D%221%2C0%22 position%3D%22255.875%2C-456.75%22 id%3D%22guide8384%22 inkscape%3Alocked%3D%22false%22 %2F%3E %3C%2Fsodipodi%3Anamedview%3E %3Cmetadata id%3D%22metadata9897%22%3E %3Crdf%3ARDF%3E %3Ccc%3AWork rdf%3Aabout%3D%22%22%3E %3Cdc%3Aformat%3Eimage%2Fsvg%2Bxml%3C%2Fdc%3Aformat%3E %3Cdc%3Atype rdf%3Aresource%3D%22http%3A%2F%2Fpurl.org%2Fdc%2Fdcmitype%2FStillImage%22 %2F%3E %3Cdc%3Atitle %2F%3E %3C%2Fcc%3AWork%3E %3C%2Frdf%3ARDF%3E %3C%2Fmetadata%3E %3Cg inkscape%3Alabel%3D%22assets%22 inkscape%3Agroupmode%3D%22layer%22 id%3D%22layer1%22 style%3D%22display%3Ainline%22 transform%3D%22translate(-116%2C194.63782)%22%3E %3Cg id%3D%22titlebutton-minimize-hover%22 inkscape%3Alabel%3D%22%23g6308%22 transform%3D%22translate(-368%2C-9)%22%3E %3Cg id%3D%22g4909%22 style%3D%22display%3Ainline%3Bopacity%3A1%22 transform%3D%22translate(-781%2C-432.63782)%22%3E %3Cellipse cy%3D%22255%22 cx%3D%221273%22 style%3D%22display%3Ainline%3Bopacity%3A0.95%3Bfill%3A%23ffffff%3Bfill-opacity%3A1%3Bstroke%3Anone%3Bstroke-width%3A0%3Bstroke-linecap%3Abutt%3Bstroke-linejoin%3Amiter%3Bstroke-miterlimit%3A4%3Bstroke-dasharray%3Anone%3Bstroke-dashoffset%3A0%3Bstroke-opacity%3A1%22 id%3D%22path4068-7-5-9-6-7-2-1%22 rx%3D%226%22 ry%3D%226.0000005%22 %2F%3E %3Cpath style%3D%22display%3Ainline%3Bopacity%3A0.15%3Bfill%3A%23525d76%3Bfill-opacity%3A1%3Bstroke%3Anone%3Bstroke-width%3A0%3Bstroke-linecap%3Abutt%3Bstroke-linejoin%3Amiter%3Bstroke-miterlimit%3A4%3Bstroke-dasharray%3Anone%3Bstroke-dashoffset%3A0%3Bstroke-opacity%3A1%22 d%3D%22m 1273%2C248 a 7%2C7 0 0 0 -7%2C7 7%2C7 0 0 0 7%2C7 7%2C7 0 0 0 7%2C-7 7%2C7 0 0 0 -7%2C-7 z m 0%2C1 a 6%2C6 0 0 1 6%2C6 6%2C6 0 0 1 -6%2C6 6%2C6 0 0 1 -6%2C-6 6%2C6 0 0 1 6%2C-6 z%22 id%3D%22path4068-7-5-9-6-7-2-5-23%22 inkscape%3Aconnector-curvature%3D%220%22 %2F%3E %3Cg style%3D%22display%3Ainline%3Bopacity%3A1%3Bfill%3A%23c0e3ff%3Bfill-opacity%3A1%22 id%3D%22g4834-0%22 transform%3D%22translate(1265%2C247)%22%3E %3Cg transform%3D%22translate(-81.0002%2C-967)%22 style%3D%22display%3Ainline%3Bfill%3A%23c0e3ff%3Bfill-opacity%3A1%22 inkscape%3Alabel%3D%22status%22 id%3D%22layer9-3-4%22 %2F%3E %3Cg transform%3D%22translate(-81.0002%2C-967)%22 inkscape%3Alabel%3D%22devices%22 id%3D%22layer10-4-1%22 style%3D%22fill%3A%23c0e3ff%3Bfill-opacity%3A1%22 %2F%3E %3Cg transform%3D%22translate(-81.0002%2C-967)%22 inkscape%3Alabel%3D%22apps%22 id%3D%22layer11-2-6%22 style%3D%22fill%3A%23c0e3ff%3Bfill-opacity%3A1%22 %2F%3E %3Cg transform%3D%22translate(-81.0002%2C-967)%22 inkscape%3Alabel%3D%22places%22 id%3D%22layer13-5-4%22 style%3D%22fill%3A%23c0e3ff%3Bfill-opacity%3A1%22 %2F%3E %3Cg transform%3D%22translate(-81.0002%2C-967)%22 inkscape%3Alabel%3D%22mimetypes%22 id%3D%22layer14-6-0%22 style%3D%22fill%3A%23c0e3ff%3Bfill-opacity%3A1%22 %2F%3E %3Cg transform%3D%22translate(-81.0002%2C-967)%22 style%3D%22display%3Ainline%3Bfill%3A%23c0e3ff%3Bfill-opacity%3A1%22 inkscape%3Alabel%3D%22emblems%22 id%3D%22layer15-52-1%22 %2F%3E %3Cg transform%3D%22translate(-81.0002%2C-967)%22 style%3D%22display%3Ainline%3Bfill%3A%23c0e3ff%3Bfill-opacity%3A1%22 inkscape%3Alabel%3D%22emotes%22 id%3D%22g71291-3-9%22 %2F%3E %3Cg transform%3D%22translate(-81.0002%2C-967)%22 style%3D%22display%3Ainline%3Bfill%3A%23c0e3ff%3Bfill-opacity%3A1%22 inkscape%3Alabel%3D%22categories%22 id%3D%22g4953-8-2%22 %2F%3E %3Cg transform%3D%22translate(-81.0002%2C-967)%22 style%3D%22display%3Ainline%3Bfill%3A%23c0e3ff%3Bfill-opacity%3A1%22 inkscape%3Alabel%3D%22actions%22 id%3D%22layer12-45-6%22%3E %3Cpath sodipodi%3Anodetypes%3D%22ccccc%22