diff --git a/.idea/.idea.WebDesktop 2.0/.idea/dataSources.local.xml b/.idea/.idea.WebDesktop 2.0/.idea/dataSources.local.xml index d625f38..646bf29 100644 --- a/.idea/.idea.WebDesktop 2.0/.idea/dataSources.local.xml +++ b/.idea/.idea.WebDesktop 2.0/.idea/dataSources.local.xml @@ -1,6 +1,6 @@ - + #@ diff --git a/.idea/.idea.WebDesktop 2.0/.idea/dataSources/95aba07a-0fe8-4ac6-bdce-406f8acafcd0.xml b/.idea/.idea.WebDesktop 2.0/.idea/dataSources/95aba07a-0fe8-4ac6-bdce-406f8acafcd0.xml index 6431f9d..20ecec0 100644 --- a/.idea/.idea.WebDesktop 2.0/.idea/dataSources/95aba07a-0fe8-4ac6-bdce-406f8acafcd0.xml +++ b/.idea/.idea.WebDesktop 2.0/.idea/dataSources/95aba07a-0fe8-4ac6-bdce-406f8acafcd0.xml @@ -5,9 +5,9 @@ exact 10.3.34 - + + 1 - \ No newline at end of file diff --git a/.idea/.idea.WebDesktop 2.0/.idea/workspace.xml b/.idea/.idea.WebDesktop 2.0/.idea/workspace.xml index c3c5ee6..c2e80f0 100644 --- a/.idea/.idea.WebDesktop 2.0/.idea/workspace.xml +++ b/.idea/.idea.WebDesktop 2.0/.idea/workspace.xml @@ -6,29 +6,24 @@ - - - - - - - + + + + + + + + + + + + - - - - - - - - - - - + + + + - { + "keyToString": { + "ASKED_ADD_EXTERNAL_FILES": "true", + "RunOnceActivity.OpenProjectViewOnStart": "true", + "RunOnceActivity.ShowReadmeOnStart": "true", + "SHARE_PROJECT_CONFIGURATION_FILES": "true", + "WebServerToolWindowFactoryState": "false", + "list.type.of.created.stylesheet": "SCSS", + "nodejs_package_manager_path": "npm", + "settings.editor.selected.configurable": "project.propVCSSupport.DirectoryMappings", + "ts.external.directory.path": "D:\\Programmierstuff\\Projekte\\WebDesktop 2.0\\Frontend\\node_modules\\typescript\\lib", + "vue.rearranger.settings.migration": "true" }, - "keyToStringList": { - "DatabaseDriversLRU": [ - "mariadb" + "keyToStringList": { + "DatabaseDriversLRU": [ + "mariadb" ] } -}]]> +} @@ -178,5 +190,7 @@ \ No newline at end of file diff --git a/Frontend/src/app/app-routing.module.ts b/Frontend/src/app/app-routing.module.ts index 8ca5f12..fca7ef6 100644 --- a/Frontend/src/app/app-routing.module.ts +++ b/Frontend/src/app/app-routing.module.ts @@ -2,10 +2,13 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import {LoginComponent} from "./sites/login/login.component"; import {RegisterComponent} from "./sites/register/register.component"; +import {DesktopComponent} from "./sites/desktop/desktop.component"; const routes: Routes = [ - {path: "login", component: LoginComponent}, - {path: "register", component: RegisterComponent} + {path: '', component: DesktopComponent}, + {path: 'login', component: LoginComponent}, + {path: 'register', component: RegisterComponent}, + {path: '*', redirectTo: ''} ]; @NgModule({ diff --git a/Frontend/src/app/app.module.ts b/Frontend/src/app/app.module.ts index 81fee20..25d2482 100644 --- a/Frontend/src/app/app.module.ts +++ b/Frontend/src/app/app.module.ts @@ -12,12 +12,19 @@ import {MatButtonModule} from "@angular/material/button"; import {MatDividerModule} from "@angular/material/divider"; import { RegisterComponent } from './sites/register/register.component'; import {ReactiveFormsModule} from "@angular/forms"; +import { DesktopComponent } from './sites/desktop/desktop.component'; +import { TaskbarIcon } from './sites/desktop/taskbar-icon/taskbar-icon.component'; +import { WindowWrapper } from './components/window-wrapper/window-wrapper.component'; +import {MatIconModule} from "@angular/material/icon"; @NgModule({ declarations: [ AppComponent, LoginComponent, - RegisterComponent + RegisterComponent, + DesktopComponent, + TaskbarIcon, + WindowWrapper ], imports: [ BrowserModule, @@ -28,7 +35,8 @@ import {ReactiveFormsModule} from "@angular/forms"; MatInputModule, MatButtonModule, MatDividerModule, - ReactiveFormsModule + ReactiveFormsModule, + MatIconModule ], providers: [], bootstrap: [AppComponent] diff --git a/Frontend/src/app/components/window-wrapper/window-wrapper.component.html b/Frontend/src/app/components/window-wrapper/window-wrapper.component.html new file mode 100644 index 0000000..8ef6ba6 --- /dev/null +++ b/Frontend/src/app/components/window-wrapper/window-wrapper.component.html @@ -0,0 +1,25 @@ +
+
+ logo + {{title}} + +
+
+ minimize +
+
+ {{resizeIcon}} +
+
+ close +
+
+
+ +
diff --git a/Frontend/src/app/components/window-wrapper/window-wrapper.component.scss b/Frontend/src/app/components/window-wrapper/window-wrapper.component.scss new file mode 100644 index 0000000..d1beb31 --- /dev/null +++ b/Frontend/src/app/components/window-wrapper/window-wrapper.component.scss @@ -0,0 +1,67 @@ +@use "src/colors"; + +.window-wrapper { + position: absolute; + width: 800px; + height: 600px; + border: 2px solid colors.$medium; + border-radius: 10px; + background-color: colors.$dark; + overflow: hidden; + box-sizing: border-box; + user-select: none; + + display: flex; + flex-direction: column; + + /*transition: width 300ms, height 300ms, left 300ms, top 300ms;*/ + + header { + width: 100%; + height: 30px; + background-color: colors.$medium; + display: flex; + gap: 5px; + align-items: center; + + img[alt="logo"] { + width: 20px; + height: 20px; + margin-left: 5px; + } + + .buttons { + margin-left: auto; + margin-right: 5px; + display: flex; + gap: 2px; + + div { + width: 26px; + height: 26px; + border-radius: 13px; + transition: background-color 150ms; + line-height: 26px; + text-align: center; + + &:hover { + background-color: rgba(colors.$text, 0.2); + } + + mat-icon { + position: absolute; + transform: translate(-50%, 5%); + + &.minimize { + transform: translate(-50%, -7px); + } + } + } + } + } + + iframe { + border: none; + flex-grow: 1; + } +} diff --git a/Frontend/src/app/components/window-wrapper/window-wrapper.component.ts b/Frontend/src/app/components/window-wrapper/window-wrapper.component.ts new file mode 100644 index 0000000..92ff49c --- /dev/null +++ b/Frontend/src/app/components/window-wrapper/window-wrapper.component.ts @@ -0,0 +1,267 @@ +import {Component, ElementRef, ViewChild} from '@angular/core'; +import {DesktopComponent, ProgramArgs} from "../../sites/desktop/desktop.component"; +import {TaskbarIcon} from "../../sites/desktop/taskbar-icon/taskbar-icon.component"; + +@Component({ + selector: 'app-window-wrapper', + templateUrl: './window-wrapper.component.html', + styleUrls: ['./window-wrapper.component.scss'] +}) +export class WindowWrapper { + @ViewChild('wrapper') wrapper: ElementRef; + @ViewChild('content') content: ElementRef; + public program: ProgramArgs; + public uuid: number; + public taskbar: TaskbarIcon; + public dragHandler: DragHandler; + public resizeHandler: ResizeHandler; + public title: string; + public focused: boolean; + public contentLoaded: boolean = false; + + public resizeIcon: string = "fullscreen"; + public maximized: boolean = false; + private lastPos: { left: string, top: string, width: string, height: string }; + + constructor(private object: ElementRef) { + this.uuid = DesktopComponent.instance.generateWindowUUID(); + } + + public initialize(taskbar: TaskbarIcon) { + this.title = this.program.name; + this.dragHandler = new DragHandler(this.wrapper.nativeElement, this); + this.resizeHandler = new ResizeHandler(this.wrapper.nativeElement, this); + this.taskbar = taskbar; + this.content.nativeElement.src = this.program.handlerUrl; + this.focus(); + } + + public close() { + this.object.nativeElement.parentElement.removeChild(this.object.nativeElement); + this.taskbar.onClose(this); + } + + public toggleMinimized() { + const minimized = this.object.nativeElement.style.display == 'none'; + + if (minimized) { + this.object.nativeElement.style.display = 'block'; + this.taskbar.setIndicator('wide'); + } else { + this.object.nativeElement.style.display = 'none'; + this.taskbar.setIndicator('dot'); + } + } + + public toggleMaximized() { + const wrapper = this.object.nativeElement.children.item(0) as HTMLElement; + + if (this.lastPos == undefined) { + this.lastPos = { + width: wrapper.style.width, + height: wrapper.style.height, + left: wrapper.style.left, + top: wrapper.style.top + }; + + wrapper.style.width = '100%'; + wrapper.style.height = '100%'; + wrapper.style.left = '0'; + wrapper.style.top = '0'; + + this.resizeIcon = "fullscreen_exit"; + this.maximized = true; + } else { + wrapper.style.width = this.lastPos.width; + wrapper.style.height = this.lastPos.height; + wrapper.style.left = this.lastPos.left; + wrapper.style.top = this.lastPos.top; + delete this.lastPos; + + this.resizeIcon = "fullscreen"; + this.maximized = false; + } + } + + public focus() { + if (this.focused) return; + DesktopComponent.instance.unfocusAll(this); + + this.object.nativeElement.style.display = 'block'; + this.taskbar.setIndicator('wide'); + + this.wrapper.nativeElement.style.zIndex = '7'; + + DesktopComponent.focusedWindow = this; + this.focused = true; + } + + public unfocus() { + this.taskbar.setIndicator('dot'); + + this.wrapper.nativeElement.style.zIndex = '5'; + + this.focused = false; + } + + public applyContentListeners(content: HTMLIFrameElement) { + content.contentDocument.addEventListener('mousemove', this.onMove.bind(this)); + content.contentDocument.addEventListener('mousedown', this.focus.bind(this)); + content.contentDocument.addEventListener('mouseup', this.resizeHandler?.windowResizeStop.bind(this.resizeHandler)); + + this.contentLoaded = true; + } + + public onMove(event: MouseEvent) { + this.dragHandler?.windowDrag(event); + this.resizeHandler?.windowResize(event); + } + +} + +class DragHandler { + private offsetX: number; + private offsetY: number; + public dragging: boolean; + private origTransitions: string; + + public constructor(private object: HTMLElement, private wrapper: WindowWrapper) { + } + + public windowDrag(event: MouseEvent): void { + if (!this.dragging) return; + if (!this.wrapper.contentLoaded) return; + const x = event.clientX - this.offsetX; + const y = event.clientY - this.offsetY; + this.object.style.left = x + 'px'; + this.object.style.top = y + 'px'; + } + + public windowDragStart(event: MouseEvent): void { + if (this.wrapper.maximized) return; + if (this.wrapper.resizeHandler?.resizing) return; + + if (this.origTransitions == undefined) + this.origTransitions = this.object.style.transition; + this.object.style.transition = 'none'; + + this.offsetX = event.clientX - this.object.offsetLeft; + this.offsetY = event.clientY - this.object.offsetTop; + this.dragging = true; + } + + public windowDragStop(): void { + if (!this.dragging) return; + this.object.style.transition = this.origTransitions; + delete this.origTransitions; + this.dragging = false; + } + + public get isDragging(): boolean { + return this.dragging; + } +} + +class ResizeHandler { + public minSize: { width: number, height: number } = {width: 800, height: 600}; + private readonly resizingArea: number = 10; + public resizing: boolean = false; + private lastResizeManager: string; + + public constructor(private object: HTMLElement, private wrapper: WindowWrapper) { + } + + public windowResizeStart(event: MouseEvent): void { + if (this.wrapper.maximized) return; + if (!this.wrapper.contentLoaded) return; + if (this.wrapper.dragHandler?.dragging) return; + if (!this.isHoverBorder(event)) return; + + this.object.classList.add("unselectable"); + this.lastResizeManager = this.isHoverBorder(event); + + this.resizing = true; + } + + public windowResizeStop(): void { + if (!this.resizing) return; + this.resizing = false; + delete this.lastResizeManager; + this.object.classList.remove("unselectable"); + } + + public windowResize(event: MouseEvent) { + if (!this.wrapper.focused) return; + if (this.wrapper.maximized) return; + + if (!this.resizing) { + const c = this.isHoverBorder(event); + + if (c) { + this.object.style.cursor = c + "-resize"; + this.lastResizeManager = c; + } else + this.object.style.cursor = "auto"; + } + + if (this.resizing) this.handleResizing(event); + } + + private handleResizing(event: MouseEvent): void { + let newDimensions: {x: number, y: number, width: number, height: number} = { + x: undefined, + y: undefined, + width: undefined, + height: undefined + }; + + if (this.lastResizeManager.includes("n")) { //TOP + newDimensions.y = this.object.offsetTop + event.movementY; + newDimensions.height = this.object.offsetHeight - event.movementY; + } else if (this.lastResizeManager.includes("s")) { //BOTTOM + //this.object.style.height = (this.resizeOrigin.height + event.movementY) + "px"; + newDimensions.height = this.object.offsetHeight + event.movementY; + } + if (this.lastResizeManager.includes("w")) { //LEFT + /*this.object.style.left = (this.resizeOrigin.x + event.movementX) + "px"; + this.object.style.width = (this.resizeOrigin.width - event.movementX) + "px";*/ + newDimensions.x = this.object.offsetLeft + event.movementX; + newDimensions.width = this.object.offsetWidth - event.movementX; + } else if (this.lastResizeManager.includes("e")) { //RIGHT + //this.object.style.width = (this.resizeOrigin.width + event.movementX) + "px"; + newDimensions.width = this.object.offsetWidth + event.movementX; + } + + if (newDimensions.width < this.minSize.width) { + newDimensions.width = this.minSize.width; + this.windowResizeStop(); + } + if (newDimensions.height < this.minSize.height) { + newDimensions.height = this.minSize.height; + this.windowResizeStop(); + } + + if (newDimensions.x) this.object.style.left = newDimensions.x + 'px'; + if (newDimensions.y) this.object.style.top = newDimensions.y + 'px'; + if (newDimensions.width) this.object.style.width = newDimensions.width + 'px'; + if (newDimensions.height) this.object.style.height = newDimensions.height + 'px'; + } + + private isHoverBorder(event: MouseEvent): string { + const delta = this.resizingArea; // the thickness of the hovered border area + + const rect = this.object.getBoundingClientRect(); + const x = event.clientX - rect.left, // the relative mouse position to the element + y = event.clientY - rect.top, // ... + w = rect.right - rect.left, // width of the element + h = rect.bottom - rect.top; // height of the element + + let c = ""; // which cursor to use + if (y < delta) c += "n"; // north + else if (y > h - delta) c += "s"; // south + if (x < delta) c += "w"; // west + else if (x > w - delta) c += "e"; // east + + return c; + } +} diff --git a/Frontend/src/app/services/backend.service.ts b/Frontend/src/app/services/backend.service.ts index 837af99..f6bb396 100644 --- a/Frontend/src/app/services/backend.service.ts +++ b/Frontend/src/app/services/backend.service.ts @@ -73,6 +73,8 @@ export class BackendService { return this.sendRequest(type, endpoint, body, options); } } + if (error.status == 0) + return {content: undefined, success: false, code: error.status, message: "Server nicht erreichbar!"}; return {content: undefined, success: false, code: error.status, message: error.error}; } diff --git a/Frontend/src/app/sites/desktop/desktop.component.html b/Frontend/src/app/sites/desktop/desktop.component.html new file mode 100644 index 0000000..9632727 --- /dev/null +++ b/Frontend/src/app/sites/desktop/desktop.component.html @@ -0,0 +1,20 @@ +
+
+ +
+ +
+
+ home-button +
+
+ defender + +
DEU
+
+ {{time}} + {{date}} +
+
+
+
diff --git a/Frontend/src/app/sites/desktop/desktop.component.scss b/Frontend/src/app/sites/desktop/desktop.component.scss new file mode 100644 index 0000000..57879a9 --- /dev/null +++ b/Frontend/src/app/sites/desktop/desktop.component.scss @@ -0,0 +1,88 @@ +@use "src/styles"; +@use "src/colors"; + +.desktop { + width: 100vw; + height: 100vh; + + background-image: url(styles.$background); + + display: flex; + flex-direction: column; + + .windows { + width: 100%; + flex-grow: 1; + position: relative; + } + + .taskbar { + width: 100%; + height: 50px; + background-color: rgba(colors.$medium, 0.6); + backdrop-filter: blur(10px); + box-sizing: border-box; + z-index: 100; + + display: flex; + padding: 5px; + + .icons { + margin-right: auto; + display: flex; + gap: 10px; + + & > * { + padding: 5px; + border-radius: 5px; + transition: background-color 200ms; + + &:hover, &.focus { + background-color: rgba(colors.$light, 0.3); + } + } + } + + .right { + display: flex; + gap: 10px; + margin-left: auto; + + img { + width: 20px; + height: 20px; + align-self: center; + + &:hover { + cursor: pointer; + } + } + + .lang { + align-self: center; + font-size: 11px; + user-select: none; + } + + .datetime { + display: flex; + flex-direction: column; + justify-content: space-evenly; + margin-right: 10px; + border-radius: 5px; + padding-inline: 5px; + transition: background-color 200ms; + + span { + text-align: right; + user-select: none; + font-size: 12px; + } + + &:hover, &.focus { + background-color: rgba(colors.$light, 0.3); + } + } + } + } +} diff --git a/Frontend/src/app/sites/desktop/desktop.component.ts b/Frontend/src/app/sites/desktop/desktop.component.ts new file mode 100644 index 0000000..8bc3661 --- /dev/null +++ b/Frontend/src/app/sites/desktop/desktop.component.ts @@ -0,0 +1,142 @@ +import {ChangeDetectorRef, Component, OnInit, ViewChild, ViewContainerRef} from '@angular/core'; +import {TaskbarIcon} from "./taskbar-icon/taskbar-icon.component"; +import {WindowWrapper} from "../../components/window-wrapper/window-wrapper.component"; + +export interface IconType { + uuid: number; + icon: string; + + program: ProgramArgs; +} + +export interface ProgramArgs { + identifier: string; + name: string; + handlerUrl: string; + + permission?: string[]; + args?: string[]; + openFiles?: string[]; +} + +export const programs: {[programUUID: string]: ProgramArgs} = { + ['defender']: { + identifier: 'defender', + name: 'Windows Defender', + handlerUrl: 'http://localhost:4200/', + } +} + +@Component({ + selector: 'app-desktop', + templateUrl: './desktop.component.html', + styleUrls: ['./desktop.component.scss'] +}) +export class DesktopComponent implements OnInit { + @ViewChild('windows', {read: ViewContainerRef}) windowsRef: ViewContainerRef; + @ViewChild('taskbarIcons', {read: ViewContainerRef}) taskbarIconsRef: ViewContainerRef; + public static instance: DesktopComponent; + public static focusedWindow: WindowWrapper; + + time: string; + date: string; + + private taskbarIcons: {icon: TaskbarIcon, removeOnClose: boolean}[] = []; + + constructor(public cdr: ChangeDetectorRef) { } + + ngOnInit(): void { + DesktopComponent.instance = this; + setInterval(() => { + const dt = new Date(); + this.time = dt.toLocaleTimeString(); + this.date = dt.toLocaleDateString(); + }, 200); + + document.addEventListener('mousemove', this.mouseMove); + + setTimeout(() => { + this.addTaskbarIcon(programs['defender']); + }); + } + + public openProgram(programUUID: string) { + const program = programs[programUUID]; + const exists = this.getTaskbarIcon(programUUID) != undefined; + + if (!exists) + this.addTaskbarIcon(program, true); + + this.getTaskbarIcon(programUUID).icon.openProgram(); + } + + public addTaskbarIcon(program: ProgramArgs, removeOnClose: boolean = false, index?: number) { + const type: IconType = { + uuid: 1, + icon: program.handlerUrl + 'favicon.ico', + + program: program + }; + + const icon = this.taskbarIconsRef.createComponent(TaskbarIcon, {index}); + icon.instance.initialize(type); + this.taskbarIcons.push({icon: icon.instance, removeOnClose}); + } + + public removeTaskbarIcon(programUUID: string) { + const icon = this.getTaskbarIcon(programUUID)?.icon; + if (icon == undefined) return; + if (icon.windows.length > 0) return; + icon.object.nativeElement.parentElement.removeChild(icon.object.nativeElement); + this.taskbarIcons.slice(this.taskbarIcons.indexOf(this.getTaskbarIcon(programUUID)), 1); + } + + public getTaskbarIcon(programUUID: string): {icon: TaskbarIcon, removeOnClose: boolean} { + for (let icon of this.taskbarIcons) { + if (icon.icon.type.program.identifier == programUUID) + return icon; + } + return undefined; + } + + public onAllWindowsClosed(programUUID: string) { + const icon = this.getTaskbarIcon(programUUID); + if (icon?.removeOnClose) + this.removeTaskbarIcon(programUUID); + } + + public unfocusAll(caller?: WindowWrapper) { + for (let icon of this.taskbarIcons) { + for (let window of icon.icon.windows) { + if (window == caller || window == undefined) continue; + window.unfocus(); + } + } + } + + public getWindow(uuid: number): WindowWrapper { + for (let icon of this.taskbarIcons) { + for (let window of icon.icon.windows) { + if (window.uuid == uuid) return window; + } + } + + return undefined; + } + + public generateWindowUUID(): number { + let uuid = 0; + while (this.getWindow(uuid) != undefined) { + uuid++; + } + return uuid; + } + + public mouseMove(event: MouseEvent) { + DesktopComponent.focusedWindow?.onMove(event); + } + + public static get windowContainer(): ViewContainerRef { + return this.instance.windowsRef; + } +} diff --git a/Frontend/src/app/sites/desktop/taskbar-icon/taskbar-icon.component.html b/Frontend/src/app/sites/desktop/taskbar-icon/taskbar-icon.component.html new file mode 100644 index 0000000..6f36e5c --- /dev/null +++ b/Frontend/src/app/sites/desktop/taskbar-icon/taskbar-icon.component.html @@ -0,0 +1,18 @@ +
+
+ {{type.uuid}} + {{type.program.name}} +
+
+ +
+
+ {{type.uuid}} + {{instance.title}} + +
+
+
diff --git a/Frontend/src/app/sites/desktop/taskbar-icon/taskbar-icon.component.scss b/Frontend/src/app/sites/desktop/taskbar-icon/taskbar-icon.component.scss new file mode 100644 index 0000000..3ea4561 --- /dev/null +++ b/Frontend/src/app/sites/desktop/taskbar-icon/taskbar-icon.component.scss @@ -0,0 +1,129 @@ +@use "src/colors"; + +.icon-wrapper { + position: relative; + height: 40px; + width: 40px; + aspect-ratio: 1 / 1; + + .inner-wrapper { + width: 100%; + height: 100%; + padding: 5px; + box-sizing: border-box; + border-radius: 5px; + transition: background-color 200ms; + + img { + height: 30px; + width: auto; + aspect-ratio: 1 / 1; + transition: all 200ms; + + &.click { + height: 85%; + margin: 7.5%; + } + } + + &:hover { + background-color: rgba(colors.$light, 0.3); + + .icon-tooltip { + opacity: 1; + visibility: visible; + } + } + + .icon-tooltip { + position: absolute; + bottom: 52px; + display: block; + background-color: rgba(colors.$dark, 0.4); + color: var(--colors-text); + border-radius: 2px; + padding: 5px; + transition: opacity 100ms; + visibility: hidden; + opacity: 0; + width: max-content; + transform: translateX(-30%); + } + + + .icon-indicator { + --dot: 5px; + --wide: 15px; + + position: absolute; + width: 0; + height: 3px; + border-radius: 1.5px; + background-color: rgba(colors.$text, 0.4); + transition: width 300ms ease-out; + opacity: 0; + + top: 90%; + left: 50%; + transform: translate(-50%, 0); + } + } + + .instances { + display: flex; + flex-direction: column; + position: absolute; + bottom: 52px; + background-color: rgba(colors.$dark, 0.4); + color: colors.$text; + border-radius: 2px; + padding: 5px; + transition: opacity 100ms; + visibility: hidden; + opacity: 0; + width: max-content; + height: max-content; + transform: translateX(-30%); + gap: 5px; + max-width: 200px; + + div { + width: 100%; + display: flex; + justify-content: space-evenly; + align-items: center; + gap: 5px; + overflow: hidden; + + &:hover { + background-color: rgba(colors.$light, 0.3); + } + + img { + height: 24px; + width: 24px; + flex-shrink: 0; + } + + span { + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + cursor: default; + } + + button { + height: 24px; + width: 24px; + display: grid; + place-items: center; + flex-shrink: 0; + + mat-icon { + font-size: 20px; + transform: translateY(-8px); + } + } + } + } +} diff --git a/Frontend/src/app/sites/desktop/taskbar-icon/taskbar-icon.component.ts b/Frontend/src/app/sites/desktop/taskbar-icon/taskbar-icon.component.ts new file mode 100644 index 0000000..77d6a35 --- /dev/null +++ b/Frontend/src/app/sites/desktop/taskbar-icon/taskbar-icon.component.ts @@ -0,0 +1,91 @@ +import {Component, ElementRef, ViewChild} from '@angular/core'; +import {DesktopComponent, IconType} from "../desktop.component"; +import {WindowWrapper} from "../../../components/window-wrapper/window-wrapper.component"; + +@Component({ + selector: 'app-taskbar-icon', + templateUrl: './taskbar-icon.component.html', + styleUrls: ['./taskbar-icon.component.scss'] +}) +export class TaskbarIcon { + @ViewChild('indicator') indicator: ElementRef; + @ViewChild('instances') instances: ElementRef; + + public type: IconType; + public windows: WindowWrapper[] = []; + public instancesOpen: boolean = false; + + constructor(public object: ElementRef) { } + + public initialize(type: IconType) { + this.type = type; + } + + public openProgram() { + const window = DesktopComponent.windowContainer.createComponent(WindowWrapper); + window.instance.program = this.type.program; + DesktopComponent.instance.cdr.detectChanges(); + window.instance.initialize(this); + + this.windows.push(window.instance); + this.setIndicator('wide'); + } + + public onTaskbarClick(event: MouseEvent) { + if (this.instancesOpen) return; + + if (this.windows.length == 0 || event.shiftKey) { + this.openProgram(); + return; + } + + if (this.windows.length == 1) { + this.windows[0].toggleMinimized(); + return; + } + + this.instances.nativeElement.style.visibility = 'visible'; + this.instances.nativeElement.style.opacity = '1'; + this.instancesOpen = true; + } + + public updateInstances() { + if (this.windows.length <= 1) { + this.instances.nativeElement.style.visibility = 'hidden'; + this.instances.nativeElement.style.opacity = '0'; + this.instancesOpen = false; + } + } + + public setIndicator(mode: 'wide' | 'dot' | 'hidden') { + switch (mode) { + case "hidden": + this.indicator.nativeElement.style.opacity = '0'; + this.indicator.nativeElement.style.width = '0'; + break; + + case "dot": + this.indicator.nativeElement.style.opacity = '1'; + this.indicator.nativeElement.style.width = 'var(--dot)'; + break; + + case "wide": + this.indicator.nativeElement.style.opacity = '1'; + this.indicator.nativeElement.style.width = 'var(--wide)'; + break; + } + } + + public onClose(window: WindowWrapper) { + this.windows.splice(this.windows.indexOf(window), 1); + + if (this.windows.length > 0) + this.setIndicator('dot'); + else + this.setIndicator('hidden'); + + if (this.windows.length <= 0) + DesktopComponent.instance.onAllWindowsClosed(this.type.program.identifier); + } + +} diff --git a/Frontend/src/app/sites/login/login.component.ts b/Frontend/src/app/sites/login/login.component.ts index 5c19247..869fe7a 100644 --- a/Frontend/src/app/sites/login/login.component.ts +++ b/Frontend/src/app/sites/login/login.component.ts @@ -15,6 +15,7 @@ export class LoginComponent { constructor(private users: UserApi, private router: Router) { } public async login(form: HTMLFormElement, username: string, password: string) { + if (this.disableLogin) return; if (!form.reportValidity()) return; const login: UserLogin = {usernameOrEmail: username, password: password}; diff --git a/Frontend/src/app/sites/register/register.component.ts b/Frontend/src/app/sites/register/register.component.ts index eaada3e..e00343a 100644 --- a/Frontend/src/app/sites/register/register.component.ts +++ b/Frontend/src/app/sites/register/register.component.ts @@ -15,6 +15,7 @@ export class RegisterComponent { constructor(private users: UserApi, private router: Router) { } public async register(form: HTMLFormElement, register: UserEditor, pwRepeat: string) { + if (this.disableRegister) return; if (!form.reportValidity()) return; if (register.password !== pwRepeat) { diff --git a/Frontend/src/assets/icons/defender.png b/Frontend/src/assets/icons/defender.png new file mode 100644 index 0000000..90bfe5f Binary files /dev/null and b/Frontend/src/assets/icons/defender.png differ diff --git a/Frontend/src/styles.scss b/Frontend/src/styles.scss index 8bd0938..fcac00f 100644 --- a/Frontend/src/styles.scss +++ b/Frontend/src/styles.scss @@ -10,6 +10,10 @@ $background: "/assets/background.png"; color: colors.$text; } +.unselectable { + user-select: none; +} + .glass { background: rgba(colors.$light, 0.15); backdrop-filter: blur(10px);