Archived
Private
Public Access
1
0

Finished window system

This commit is contained in:
2022-10-17 16:11:06 +02:00
parent e5fdfce2d6
commit 0fd41608b9
19 changed files with 922 additions and 42 deletions

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="dataSourceStorageLocal" created-in="RD-222.3962.23">
<component name="dataSourceStorageLocal" created-in="RD-222.4167.23">
<data-source name="WebDesktop" uuid="95aba07a-0fe8-4ac6-bdce-406f8acafcd0">
<database-info product="MariaDB" version="10.3.34-MariaDB-0+deb10u1" jdbc-version="4.2" driver-name="MariaDB Connector/J" driver-version="2.7.3" dbms="MARIADB" exact-version="10.3.34" exact-driver-version="2.7">
<extra-name-characters>#@</extra-name-characters>

View File

@@ -5,9 +5,9 @@
<DefaultCasing>exact</DefaultCasing>
<ServerVersion>10.3.34</ServerVersion>
</root>
<schema id="2" parent="1" name="WebDesktop">
<schema id="2" parent="1" name="information_schema"/>
<schema id="3" parent="1" name="WebDesktop">
<Current>1</Current>
</schema>
<schema id="3" parent="1" name="information_schema"/>
</database-model>
</dataSource>

View File

@@ -6,29 +6,24 @@
</component>
<component name="ChangeListManager">
<list default="true" id="041c7675-58ae-4243-af88-0d29855e558f" name="Changes" comment="">
<change afterPath="$PROJECT_DIR$/.idea/.idea.WebDesktop 2.0/.idea/inspectionProfiles/Project_Default.xml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/.idea/.idea.WebDesktop 2.0/.idea/watcherTasks.xml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/Frontend/src/app/sites/register/register.component.html" afterDir="false" />
<change afterPath="$PROJECT_DIR$/Frontend/src/app/sites/register/register.component.scss" afterDir="false" />
<change afterPath="$PROJECT_DIR$/Frontend/src/app/sites/register/register.component.ts" afterDir="false" />
<change afterPath="$PROJECT_DIR$/Frontend/src/assets/background.png" afterDir="false" />
<change afterPath="$PROJECT_DIR$/Frontend/src/colors.scss" afterDir="false" />
<change afterPath="$PROJECT_DIR$/Frontend/src/app/components/window-wrapper/window-wrapper.component.html" afterDir="false" />
<change afterPath="$PROJECT_DIR$/Frontend/src/app/components/window-wrapper/window-wrapper.component.scss" afterDir="false" />
<change afterPath="$PROJECT_DIR$/Frontend/src/app/components/window-wrapper/window-wrapper.component.ts" afterDir="false" />
<change afterPath="$PROJECT_DIR$/Frontend/src/app/sites/desktop/desktop.component.html" afterDir="false" />
<change afterPath="$PROJECT_DIR$/Frontend/src/app/sites/desktop/desktop.component.scss" afterDir="false" />
<change afterPath="$PROJECT_DIR$/Frontend/src/app/sites/desktop/desktop.component.ts" afterDir="false" />
<change afterPath="$PROJECT_DIR$/Frontend/src/app/sites/desktop/taskbar-icon/taskbar-icon.component.html" afterDir="false" />
<change afterPath="$PROJECT_DIR$/Frontend/src/app/sites/desktop/taskbar-icon/taskbar-icon.component.scss" afterDir="false" />
<change afterPath="$PROJECT_DIR$/Frontend/src/app/sites/desktop/taskbar-icon/taskbar-icon.component.ts" afterDir="false" />
<change afterPath="$PROJECT_DIR$/Frontend/src/assets/icons/defender.png" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/.idea.WebDesktop 2.0/.idea/dataSources.local.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/.idea.WebDesktop 2.0/.idea/dataSources.local.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/.idea.WebDesktop 2.0/.idea/dataSources/95aba07a-0fe8-4ac6-bdce-406f8acafcd0.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/.idea.WebDesktop 2.0/.idea/dataSources/95aba07a-0fe8-4ac6-bdce-406f8acafcd0.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/.idea.WebDesktop 2.0/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/.idea.WebDesktop 2.0/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Backend/Controllers/UserController.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Backend/Controllers/UserController.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Backend/appsettings.json" beforeDir="false" afterPath="$PROJECT_DIR$/Backend/appsettings.json" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Frontend/angular.json" beforeDir="false" afterPath="$PROJECT_DIR$/Frontend/angular.json" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Frontend/package-lock.json" beforeDir="false" afterPath="$PROJECT_DIR$/Frontend/package-lock.json" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Frontend/package.json" beforeDir="false" afterPath="$PROJECT_DIR$/Frontend/package.json" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Frontend/src/app/app-routing.module.ts" beforeDir="false" afterPath="$PROJECT_DIR$/Frontend/src/app/app-routing.module.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Frontend/src/app/app.component.ts" beforeDir="false" afterPath="$PROJECT_DIR$/Frontend/src/app/app.component.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Frontend/src/app/app.module.ts" beforeDir="false" afterPath="$PROJECT_DIR$/Frontend/src/app/app.module.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Frontend/src/app/entitys/user.ts" beforeDir="false" afterPath="$PROJECT_DIR$/Frontend/src/app/entitys/user.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Frontend/src/app/services/backend.service.ts" beforeDir="false" afterPath="$PROJECT_DIR$/Frontend/src/app/services/backend.service.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Frontend/src/app/services/users.service.ts" beforeDir="false" afterPath="$PROJECT_DIR$/Frontend/src/app/services/users.service.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Frontend/src/app/sites/login/login.component.html" beforeDir="false" afterPath="$PROJECT_DIR$/Frontend/src/app/sites/login/login.component.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Frontend/src/app/sites/login/login.component.scss" beforeDir="false" afterPath="$PROJECT_DIR$/Frontend/src/app/sites/login/login.component.scss" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Frontend/src/app/sites/login/login.component.ts" beforeDir="false" afterPath="$PROJECT_DIR$/Frontend/src/app/sites/login/login.component.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Frontend/src/index.html" beforeDir="false" afterPath="$PROJECT_DIR$/Frontend/src/index.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Frontend/src/app/sites/register/register.component.ts" beforeDir="false" afterPath="$PROJECT_DIR$/Frontend/src/app/sites/register/register.component.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Frontend/src/styles.scss" beforeDir="false" afterPath="$PROJECT_DIR$/Frontend/src/styles.scss" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
@@ -36,6 +31,9 @@
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="EditorConfigPreviewManager">
<editorConfig file="$PROJECT_DIR$/Frontend/.editorconfig" previewFile="$PROJECT_DIR$/Frontend/src/app/services/users.service.ts" />
</component>
<component name="FileTemplateManagerImpl">
<option name="RECENT_TEMPLATES">
<list>
@@ -62,25 +60,25 @@
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent"><![CDATA[{
"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"
<component name="PropertiesComponent">{
&quot;keyToString&quot;: {
&quot;ASKED_ADD_EXTERNAL_FILES&quot;: &quot;true&quot;,
&quot;RunOnceActivity.OpenProjectViewOnStart&quot;: &quot;true&quot;,
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
&quot;SHARE_PROJECT_CONFIGURATION_FILES&quot;: &quot;true&quot;,
&quot;WebServerToolWindowFactoryState&quot;: &quot;false&quot;,
&quot;list.type.of.created.stylesheet&quot;: &quot;SCSS&quot;,
&quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
&quot;settings.editor.selected.configurable&quot;: &quot;project.propVCSSupport.DirectoryMappings&quot;,
&quot;ts.external.directory.path&quot;: &quot;D:\\Programmierstuff\\Projekte\\WebDesktop 2.0\\Frontend\\node_modules\\typescript\\lib&quot;,
&quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
},
"keyToStringList": {
"DatabaseDriversLRU": [
"mariadb"
&quot;keyToStringList&quot;: {
&quot;DatabaseDriversLRU&quot;: [
&quot;mariadb&quot;
]
}
}]]></component>
}</component>
<component name="RunManager" selected=".NET Launch Settings Profile.Backend">
<configuration name="Backend" type="LaunchSettings" factoryName=".NET Launch Settings Profile">
<option name="LAUNCH_PROFILE_PROJECT_FILE_PATH" value="$PROJECT_DIR$/Backend/Backend.csproj" />
@@ -152,8 +150,22 @@
<workItem from="1662126115999" duration="9904000" />
<workItem from="1662204907636" duration="15449000" />
<workItem from="1662228779224" duration="302000" />
<workItem from="1662539031966" duration="6394000" />
<workItem from="1662539031966" duration="6549000" />
<workItem from="1662554420874" duration="3645000" />
<workItem from="1662897735428" duration="9014000" />
<workItem from="1663265761332" duration="60000" />
<workItem from="1663331951327" duration="183000" />
<workItem from="1663510436522" duration="149000" />
<workItem from="1666001823049" duration="12894000" />
</task>
<task id="LOCAL-00001" summary="Finished Login / Register system">
<created>1662545562560</created>
<option name="number" value="00001" />
<option name="presentableId" value="LOCAL-00001" />
<option name="project" value="LOCAL" />
<updated>1662545562560</updated>
</task>
<option name="localTasksCounter" value="2" />
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
@@ -178,5 +190,7 @@
<path value="$PROJECT_DIR$/../.." />
</ignored-roots>
<option name="CLEAR_INITIAL_COMMIT_MESSAGE" value="true" />
<MESSAGE value="Finished Login / Register system" />
<option name="LAST_COMMIT_MESSAGE" value="Finished Login / Register system" />
</component>
</project>

View File

@@ -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({

View File

@@ -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]

View File

@@ -0,0 +1,25 @@
<div class="window-wrapper" #wrapper
(mousedown)="focus(); resizeHandler?.windowResizeStart($event)"
(mouseup)="resizeHandler?.windowResizeStop()"
>
<header
(mousedown)="dragHandler?.windowDragStart($event)"
(mouseup)="dragHandler?.windowDragStop()"
>
<img src="{{program?.handlerUrl + 'favicon.ico'}}" alt="logo" draggable="false">
<span>{{title}}</span>
<div class="buttons">
<div class="header-button">
<mat-icon class="minimize" (click)="toggleMinimized()">minimize</mat-icon>
</div>
<div class="header-button">
<mat-icon (click)="toggleMaximized()">{{resizeIcon}}</mat-icon>
</div>
<div class="header-button">
<mat-icon (click)="close()">close</mat-icon>
</div>
</div>
</header>
<iframe #content (load)="applyContentListeners(content)"></iframe>
</div>

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -73,6 +73,8 @@ export class BackendService {
return this.sendRequest<T>(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};
}

View File

@@ -0,0 +1,20 @@
<section class="desktop">
<div class="windows">
<meta #windows>
</div>
<div class="taskbar">
<div class="icons">
<img src="/favicon.ico" alt="home-button" #taskbarIcons (click)="openProgram('defender')">
</div>
<div class="right">
<img src="/assets/icons/defender.png" alt="defender">
<div class="lang">DEU</div>
<div class="datetime">
<span>{{time}}</span>
<span>{{date}}</span>
</div>
</div>
</div>
</section>

View File

@@ -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);
}
}
}
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,18 @@
<div class="icon-wrapper" (click)="onTaskbarClick($event)">
<div class="inner-wrapper"
(mousedown)="icon.classList.add('click')"
(click)="icon.classList.remove('click')"
>
<img src="{{type.icon}}" alt="{{type.uuid}}" #icon>
<span class="icon-tooltip">{{type.program.name}}</span>
<div class="icon-indicator" #indicator></div>
</div>
<div class="instances" #instances>
<div *ngFor="let instance of windows">
<img src="{{type.icon}}" alt="{{type.uuid}}">
<span>{{instance.title}}</span>
<button mat-icon-button (mouseup)="instance.close(); updateInstances()"><mat-icon>close</mat-icon></button>
</div>
</div>
</div>

View File

@@ -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);
}
}
}
}
}

View File

@@ -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);
}
}

View File

@@ -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};

View File

@@ -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) {

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

@@ -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);