Archived
Private
Public Access
1
0

Initial commit

This commit is contained in:
2022-09-04 12:45:01 +02:00
commit f4a01d6a69
11601 changed files with 4206660 additions and 0 deletions

View File

@@ -0,0 +1,16 @@
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
# For the full list of supported browsers by the Angular framework, please see:
# https://angular.io/guide/browser-support
# You can see what browsers were selected by your queries by running:
# npx browserslist
last 1 Chrome version
last 1 Firefox version
last 2 Edge major versions
last 2 Safari major versions
last 2 iOS major versions
Firefox ESR

View File

@@ -0,0 +1,9 @@
.git
.firebase
.editorconfig
/node_modules
/e2e
/docs
.gitignore
*.zip
*.md

View File

@@ -0,0 +1,16 @@
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.ts]
quote_type = single
[*.md]
max_line_length = off
trim_trailing_whitespace = false

View File

@@ -0,0 +1,46 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
/dist
/tmp
/out-tsc
# Only exists if Bazel was run
/bazel-out
# dependencies
/node_modules
# profiling files
chrome-profiler-events*.json
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# misc
/.angular/cache
/.sass-cache
/connect.lock
/coverage
/libpeerconnection.log
npm-debug.log
yarn-error.log
testem.log
/typings
# System Files
.DS_Store
Thumbs.db

View File

@@ -0,0 +1,10 @@
#stage 1
FROM node:latest as node
WORKDIR /app
COPY . .
RUN npm install
RUN npm run build --prod
#stage 2
FROM nginx:alpine
COPY nginx.conf /etc/nginx/sites-available/default
COPY --from=node /app/dist/WebDesktopFrontend /usr/share/nginx/html

View File

@@ -0,0 +1,27 @@
# WebDesktopFrontend
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 13.0.1.
## Development server
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
## Code scaffolding
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
## Build
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.
## Running unit tests
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Running end-to-end tests
Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.

View File

@@ -0,0 +1,244 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"WebDesktopFrontend": {
"projectType": "application",
"schematics": {
"@schematics/angular:application": {
"strict": true,
"style": "scss"
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/WebDesktopFrontend",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json",
"assets": [
"src/favicon.ico",
"src/assets",
"src/manifest.webmanifest"
],
"styles": [
"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css",
"./node_modules/bootstrap/dist/css/bootstrap.css",
"src/fonts.scss",
"src/variables.scss",
"src/styles.scss",
"src/themes.scss"
],
"scripts": [
"./node_modules/bootstrap/dist/js/bootstrap.bundle.js",
"./node_modules/codemirror/lib/codemirror.js",
"./node_modules/codemirror/addon/mode/simple.js",
"./node_modules/codemirror/mode/apl/apl.js",
"./node_modules/codemirror/mode/asciiarmor/asciiarmor.js",
"./node_modules/codemirror/mode/asn.1/asn.1.js",
"./node_modules/codemirror/mode/asterisk/asterisk.js",
"./node_modules/codemirror/mode/brainfuck/brainfuck.js",
"./node_modules/codemirror/mode/clike/clike.js",
"./node_modules/codemirror/mode/clojure/clojure.js",
"./node_modules/codemirror/mode/cmake/cmake.js",
"./node_modules/codemirror/mode/cobol/cobol.js",
"./node_modules/codemirror/mode/coffeescript/coffeescript.js",
"./node_modules/codemirror/mode/commonlisp/commonlisp.js",
"./node_modules/codemirror/mode/crystal/crystal.js",
"./node_modules/codemirror/mode/css/css.js",
"./node_modules/codemirror/mode/cypher/cypher.js",
"./node_modules/codemirror/mode/d/d.js",
"./node_modules/codemirror/mode/dart/dart.js",
"./node_modules/codemirror/mode/diff/diff.js",
"./node_modules/codemirror/mode/django/django.js",
"./node_modules/codemirror/mode/dockerfile/dockerfile.js",
"./node_modules/codemirror/mode/dtd/dtd.js",
"./node_modules/codemirror/mode/dylan/dylan.js",
"./node_modules/codemirror/mode/ebnf/ebnf.js",
"./node_modules/codemirror/mode/ecl/ecl.js",
"./node_modules/codemirror/mode/eiffel/eiffel.js",
"./node_modules/codemirror/mode/elm/elm.js",
"./node_modules/codemirror/mode/erlang/erlang.js",
"./node_modules/codemirror/mode/factor/factor.js",
"./node_modules/codemirror/mode/fcl/fcl.js",
"./node_modules/codemirror/mode/forth/forth.js",
"./node_modules/codemirror/mode/fortran/fortran.js",
"./node_modules/codemirror/mode/gas/gas.js",
"./node_modules/codemirror/mode/gfm/gfm.js",
"./node_modules/codemirror/mode/gherkin/gherkin.js",
"./node_modules/codemirror/mode/go/go.js",
"./node_modules/codemirror/mode/groovy/groovy.js",
"./node_modules/codemirror/mode/haml/haml.js",
"./node_modules/codemirror/mode/handlebars/handlebars.js",
"./node_modules/codemirror/mode/haskell/haskell.js",
"./node_modules/codemirror/mode/haskell-literate/haskell-literate.js",
"./node_modules/codemirror/mode/haxe/haxe.js",
"./node_modules/codemirror/mode/htmlembedded/htmlembedded.js",
"./node_modules/codemirror/mode/htmlmixed/htmlmixed.js",
"./node_modules/codemirror/mode/http/http.js",
"./node_modules/codemirror/mode/idl/idl.js",
"./node_modules/codemirror/mode/javascript/javascript.js",
"./node_modules/codemirror/mode/jinja2/jinja2.js",
"./node_modules/codemirror/mode/jsx/jsx.js",
"./node_modules/codemirror/mode/julia/julia.js",
"./node_modules/codemirror/mode/livescript/livescript.js",
"./node_modules/codemirror/mode/lua/lua.js",
"./node_modules/codemirror/mode/markdown/markdown.js",
"./node_modules/codemirror/mode/mathematica/mathematica.js",
"./node_modules/codemirror/mode/mbox/mbox.js",
"./node_modules/codemirror/mode/mirc/mirc.js",
"./node_modules/codemirror/mode/mllike/mllike.js",
"./node_modules/codemirror/mode/modelica/modelica.js",
"./node_modules/codemirror/mode/mscgen/mscgen.js",
"./node_modules/codemirror/mode/mumps/mumps.js",
"./node_modules/codemirror/mode/nginx/nginx.js",
"./node_modules/codemirror/mode/nsis/nsis.js",
"./node_modules/codemirror/mode/ntriples/ntriples.js",
"./node_modules/codemirror/mode/octave/octave.js",
"./node_modules/codemirror/mode/oz/oz.js",
"./node_modules/codemirror/mode/pascal/pascal.js",
"./node_modules/codemirror/mode/pegjs/pegjs.js",
"./node_modules/codemirror/mode/perl/perl.js",
"./node_modules/codemirror/mode/php/php.js",
"./node_modules/codemirror/mode/pig/pig.js",
"./node_modules/codemirror/mode/powershell/powershell.js",
"./node_modules/codemirror/mode/properties/properties.js",
"./node_modules/codemirror/mode/protobuf/protobuf.js",
"./node_modules/codemirror/mode/pug/pug.js",
"./node_modules/codemirror/mode/puppet/puppet.js",
"./node_modules/codemirror/mode/python/python.js",
"./node_modules/codemirror/mode/q/q.js",
"./node_modules/codemirror/mode/r/r.js",
"./node_modules/codemirror/mode/rpm/rpm.js",
"./node_modules/codemirror/mode/rst/rst.js",
"./node_modules/codemirror/mode/ruby/ruby.js",
"./node_modules/codemirror/mode/rust/rust.js",
"./node_modules/codemirror/mode/sas/sas.js",
"./node_modules/codemirror/mode/sass/sass.js",
"./node_modules/codemirror/mode/scheme/scheme.js",
"./node_modules/codemirror/mode/shell/shell.js",
"./node_modules/codemirror/mode/sieve/sieve.js",
"./node_modules/codemirror/mode/slim/slim.js",
"./node_modules/codemirror/mode/smalltalk/smalltalk.js",
"./node_modules/codemirror/mode/smarty/smarty.js",
"./node_modules/codemirror/mode/solr/solr.js",
"./node_modules/codemirror/mode/soy/soy.js",
"./node_modules/codemirror/mode/sparql/sparql.js",
"./node_modules/codemirror/mode/spreadsheet/spreadsheet.js",
"./node_modules/codemirror/mode/sql/sql.js",
"./node_modules/codemirror/mode/stex/stex.js",
"./node_modules/codemirror/mode/stylus/stylus.js",
"./node_modules/codemirror/mode/swift/swift.js",
"./node_modules/codemirror/mode/tcl/tcl.js",
"./node_modules/codemirror/mode/textile/textile.js",
"./node_modules/codemirror/mode/tiddlywiki/tiddlywiki.js",
"./node_modules/codemirror/mode/tiki/tiki.js",
"./node_modules/codemirror/mode/toml/toml.js",
"./node_modules/codemirror/mode/tornado/tornado.js",
"./node_modules/codemirror/mode/troff/troff.js",
"./node_modules/codemirror/mode/ttcn/ttcn.js",
"./node_modules/codemirror/mode/ttcn-cfg/ttcn-cfg.js",
"./node_modules/codemirror/mode/turtle/turtle.js",
"./node_modules/codemirror/mode/twig/twig.js",
"./node_modules/codemirror/mode/vb/vb.js",
"./node_modules/codemirror/mode/vbscript/vbscript.js",
"./node_modules/codemirror/mode/velocity/velocity.js",
"./node_modules/codemirror/mode/verilog/verilog.js",
"./node_modules/codemirror/mode/vhdl/vhdl.js",
"./node_modules/codemirror/mode/vue/vue.js",
"./node_modules/codemirror/mode/wast/wast.js",
"./node_modules/codemirror/mode/webidl/webidl.js",
"./node_modules/codemirror/mode/xml/xml.js",
"./node_modules/codemirror/mode/xquery/xquery.js",
"./node_modules/codemirror/mode/yacas/yacas.js",
"./node_modules/codemirror/mode/yaml/yaml.js",
"./node_modules/codemirror/mode/yaml-frontmatter/yaml-frontmatter.js",
"./node_modules/codemirror/mode/z80/z80.js"
],
"serviceWorker": true,
"ngswConfigPath": "ngsw-config.json"
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500mb",
"maximumError": "500mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "1024mb",
"maximumError": "1024mb"
}
],
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"outputHashing": "all"
},
"development": {
"buildOptimizer": false,
"optimization": false,
"vendorChunk": true,
"extractLicenses": false,
"sourceMap": true,
"namedChunks": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"browserTarget": "WebDesktopFrontend:build:production"
},
"development": {
"browserTarget": "WebDesktopFrontend:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "WebDesktopFrontend:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"assets": [
"src/favicon.ico",
"src/assets",
"src/manifest.webmanifest"
],
"styles": [
"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css",
"src/styles.css"
],
"scripts": []
}
}
}
}
},
"defaultProject": "WebDesktopFrontend"
}

View File

@@ -0,0 +1,44 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage'),
require('@angular-devkit/build-angular/plugins/karma')
],
client: {
jasmine: {
// you can add configuration options for Jasmine here
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
// for example, you can disable the random execution with `random: false`
// or set a specific seed with `seed: 4321`
},
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
jasmineHtmlReporter: {
suppressAll: true // removes the duplicated traces
},
coverageReporter: {
dir: require('path').join(__dirname, './coverage/WebDesktopFrontend'),
subdir: '.',
reporters: [
{ type: 'html' },
{ type: 'text-summary' }
]
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
restartOnFileChange: true
});
};

View File

@@ -0,0 +1,13 @@
server {
listen 80 default_server;
listen [::]:80 default_server;
root /usr/share/nginx/html;
index index.html index.htm index.nginx-debian.html;
server_name _;
location / {
try_files $uri $uri/ /index.html;
}
}

View File

@@ -0,0 +1,30 @@
{
"$schema": "node_modules/@angular/service-worker/config/schema.json",
"index": "/index.html",
"assetGroups": [
{
"name": "app",
"installMode": "prefetch",
"resources": {
"files": [
"/favicon.ico",
"/index.html",
"/manifest.webmanifest",
"/*.css",
"/*.js"
]
}
},
{
"name": "assets",
"installMode": "lazy",
"updateMode": "prefetch",
"resources": {
"files": [
"/assets/**",
"/*.(svg|cur|jpg|jpeg|png|apng|webp|avif|gif|otf|ttf|woff|woff2)"
]
}
}
]
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,50 @@
{
"name": "web-desktop-frontend",
"version": "0.0.1",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test"
},
"private": true,
"dependencies": {
"@angular/animations": "~13.0.0",
"@angular/cdk": "^13.0.0",
"@angular/common": "~13.0.0",
"@angular/compiler": "~13.0.0",
"@angular/core": "~13.0.0",
"@angular/forms": "~13.0.0",
"@angular/material": "^13.0.2",
"@angular/platform-browser": "~13.0.0",
"@angular/platform-browser-dynamic": "~13.0.0",
"@angular/router": "~13.0.0",
"@angular/service-worker": "~13.0.0",
"@ctrl/ngx-codemirror": "^5.1.1",
"bootstrap": "^5.1.3",
"codemirror": "^5.65.2",
"file-saver": "^2.0.5",
"npm": "^8.3.0",
"rxjs": "~7.4.0",
"sweetalert2": "^11.4.4",
"tslib": "^2.3.0",
"zone.js": "~0.11.4"
},
"devDependencies": {
"@angular-devkit/build-angular": "~13.0.1",
"@angular/cli": "~13.0.1",
"@angular/compiler-cli": "~13.0.0",
"@types/codemirror": "^5.60.5",
"@types/file-saver": "^2.0.5",
"@types/jasmine": "~3.10.0",
"@types/node": "^12.11.1",
"jasmine-core": "~3.10.0",
"karma": "~6.3.0",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage": "~2.0.3",
"karma-jasmine": "~4.0.0",
"karma-jasmine-html-reporter": "~1.7.0",
"typescript": "~4.4.3"
}
}

View File

@@ -0,0 +1,90 @@
import { Injectable } from '@angular/core';
import { CrudService } from "../crud.service";
import {DirectoryContent, DirectoryInformation, FileInformation} from "../entitys/files";
import {Observable} from "rxjs";
import {HttpEvent} from "@angular/common/http";
@Injectable({
providedIn: 'root'
})
export class FileAPI {
constructor(private service: CrudService) {}
public async createDirectory(directory: string, name: string): Promise<boolean> {
try {
await this.service.sendPostRequest<boolean>("files/upload/directory?directory=" + directory + "&name=" + name, null, {authorized: true});
return true;
}catch {
return false;
}
}
public async uploadFile(file: File, directory: string): Promise<Observable<HttpEvent<object>>> {
const form = new FormData();
form.append("file", file, file.name);
form.append("directory", directory);
await this.service.getNewToken();
return this.service.HttpClient.post(this.service.endpoint + "files/upload/file", form, {headers: this.service.httpHeader, reportProgress: true, observe: "events"});
}
public async quickUpload(file: File, directory: string) {
const form = new FormData();
form.append("file", file, file.name);
form.append("directory", directory);
await this.service.sendPostRequest("files/upload/file", form);
}
public async uploadJson(directory: string, name: string, content: any) {
await this.service.sendPostRequest("files/upload/json?directory=" + directory + "&name=" + name, content, {authorized: true});
}
public async downloadJson<T>(file: string) {
return await this.service.sendGetRequest<T>("files/download/json?file=" + file, {authorized: true});
}
public async downloadFile(directory: string, file: string): Promise<Observable<HttpEvent<object>>> {
await this.service.getNewToken();
return this.service.HttpClient.get(this.service.endpoint + "files/download/file?directory=" + directory + "&file=" + file, {headers: this.service.httpHeader, responseType: 'blob', reportProgress: true, observe: "events"});
}
public async delete(url: string) {
await this.service.sendDeleteRequest("files/delete?url=" + url, {authorized: true});
}
public async getDirectoryContent(directory: string): Promise<DirectoryContent> {
try {
return await this.service.sendGetRequest<DirectoryContent>("files/content?directory=" + directory, {authorized: true});
} catch {
return { files: [], directories: [] };
}
}
public async getDirectoryInfo(directory: string): Promise<DirectoryInformation> {
const info = await this.service.sendGetRequest<DirectoryInformation>("files/info/directory?directory=" + directory, {authorized: true});
info.created = new Date(info.created);
return info;
}
public async getFileInfo(directory: string, file: string): Promise<FileInformation> {
const info = await this.service.sendGetRequest<FileInformation>("files/info/files?directory=" + directory + "&file=" + file, {authorized: true});
info.created = new Date(info.created);
return info;
}
public async moveDirectory(directory: string, name: string, to: string) {
await this.service.sendPutRequest("files/move/directory?directory=" + directory + "&name=" + name + "&to=" + to, null, {authorized: true});
}
public async moveFile(directory: string, file: string, to: string) {
await this.service.sendPutRequest("files/move/file?directory=" + directory + "&file=" + file + "&to=" + to, null, {authorized: true});
}
public toFile(content: string, name: string): File {
const blob = new Blob([content], {type: "text/plain"});
return new File([blob], name, {type: "text/plain"});
}
}

View File

@@ -0,0 +1,20 @@
import {Injectable} from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class SettingsService {
public static eventChange: CustomEvent = new CustomEvent<any>("settings_change");
constructor() { }
public setSetting(key: string, value: any) {
localStorage.setItem(key, JSON.stringify(value));
document.dispatchEvent(SettingsService.eventChange);
}
public getSetting<T>(key: string, defaultValue?: T): T {
return JSON.parse(localStorage.getItem(key)) || defaultValue;
}
}

View File

@@ -0,0 +1,160 @@
import { Injectable } from '@angular/core';
import { CrudService } from '../crud.service';
import { Token } from '../entitys/token';
import { User, UserEditor, UserLogin } from '../entitys/user';
@Injectable({
providedIn: 'root'
})
export class UserAPI {
private user: User;
constructor(private service: CrudService) { }
async login(login: UserLogin): Promise<boolean> {
try {
const accessToken = await this.service.sendPutRequest<Token>("users/login", JSON.stringify(login), {withCredentials: true});
this.service.setAccessToken(accessToken.id);
return true;
}catch(err) {
return false;
}
}
async register(editor: UserEditor): Promise<boolean> {
try {
const accessToken = await this.service.sendPostRequest<Token>("users/register", JSON.stringify(editor), {withCredentials: true});
this.service.setAccessToken(accessToken.id);
return true;
}catch {
return false;
}
}
async logout(): Promise<void> {
await this.service.sendDeleteRequest("users/logout", {authorized: true, withCredentials: true});
}
async editUser(id: string, editor: UserEditor): Promise<boolean> {
try {
await this.service.sendPutRequest("users/" + id, JSON.stringify(editor), {authorized: true});
return true;
}catch {
return false;
}
}
async editOwnUser(editor: UserEditor) {
try {
await this.service.sendPutRequest("users/ownuser", JSON.stringify(editor), {authorized: true});
return true;
}catch {
return false;
}
}
async deleteOwnUser() {
try {
await this.service.sendDeleteRequest("users/ownuser", {authorized: true, withCredentials: true});
localStorage.clear();
return true;
}catch {
return false;
}
}
async deleteUser(id: string): Promise<boolean> {
try {
await this.service.sendDeleteRequest("users/" + id, {authorized: true});
return true;
}catch {
return false;
}
}
async getUser(data: {id?: string, username?: string, email?: string}): Promise<User | null> {
if (data.id !== undefined) {
return this.service.sendGetRequest("users/" + data.id);
}
const users = await this.getUsers();
if (data.username !== undefined) {
return users.find(user => user.username == data.username) as User;
}
if (data.email !== undefined) {
return users.find(user => user.email == data.email) as User;
}
return null;
}
async getUsers(): Promise<User[]> {
return this.service.sendGetRequest<User[]>("users");
}
async isLoggedIn(): Promise<boolean> {
try {
return await this.service.sendGetRequest("users/validate", {authorized: true, withCredentials: true, dontResendOnExpiration: true, returnNewTokenResponse: true}) === true;
}catch {
return false;
}
}
async loadUser(): Promise<boolean> {
if (!(await this.isLoggedIn())) return false;
this.user = await this.service.sendGetRequest<User>("users/ownuser", {authorized: true});
this.user.created = new Date(this.user.created.toString());
return true;
}
async checkForPermission(permission: string): Promise<boolean> {
await this.getCurrentUser();
const permissions = await this.service.sendGetRequest<string[]>("users/permissions", {authorized: true});
if (permissions.includes("*") || permissions.includes(permission))
return true;
const splice = permission.split(".");
let builder: string = "";
for (let pp of splice) {
builder += pp + ".";
if (permissions.includes(builder + "*"))
return true;
}
return false;
}
async getOwnPermissions(): Promise<string[]> {
await this.getCurrentUser();
return this.service.sendGetRequest<string[]>("users/permissions", {authorized: true});
}
async getPermissions(id: string): Promise<string[]> {
return this.service.sendGetRequest<string[]>("users/" + id + "/permissions/raw", {authorized: true});
}
async addPermission(id: string, permission: string): Promise<boolean> {
try {
await this.service.sendPostRequest("users/" + id + "/permissions/" + permission, undefined, {authorized: true});
return true;
}catch {
return false;
}
}
async removePermission(id: string, permission: string): Promise<boolean> {
try {
await this.service.sendDeleteRequest("users/" + id + "/permissions/" + permission, {authorized: true});
return true;
}catch {
return false;
}
}
public async getCurrentUser(forceReload: boolean = false): Promise<User> {
if (this.user != undefined && !forceReload)
return this.user;
else {
await this.loadUser();
return this.user;
}
}
}

View File

@@ -0,0 +1,18 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { LoginComponent } from "./login/login.component";
import { Desktop } from "./desktop/desktop.component";
import { RegisterComponent } from './register/register.component';
const routes: Routes = [
{ path: 'login', component: LoginComponent },
{ path: 'register', component: RegisterComponent },
{ path: '', component: Desktop },
{ path: '*', redirectTo: '' }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }

View File

@@ -0,0 +1 @@
<router-outlet *ngIf="ready"></router-outlet>

View File

@@ -0,0 +1,38 @@
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { UserAPI } from './api/userapi.service';
import {CrudService} from "./crud.service";
import {defaultPersonalization, Personalization} from "./entitys/settings";
import {SettingsService} from "./api/settings.service";
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
title = 'WebDesktop';
ready = false;
constructor(private users: UserAPI, private router: Router, private service: CrudService, private settings: SettingsService) {}
async ngOnInit() {
const personalization = this.settings.getSetting<Personalization>("settings.personalization", defaultPersonalization);
const theme = personalization.theme;
document.body.classList.add(theme);
const background = personalization.background;
document.body.style.setProperty("--background", "url(" + background + ")");
if (this.router.url == "/register" || location.href.endsWith("/register")) this.ready = true;
else if (this.router.url == "/login" || location.href.endsWith("/login")) this.ready = true;
else {
this.ready = await this.users.loadUser();
if (!this.ready) {
await this.router.navigate(["/login"]);
this.ready = true;
}
}
}
}

View File

@@ -0,0 +1,90 @@
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { LoginComponent } from './login/login.component';
import { Desktop } from './desktop/desktop.component';
import { AppRoutingModule } from './app-routing.module';
import { RegisterComponent } from './register/register.component';
import { HttpClientModule } from '@angular/common/http';
import { WindowRoot } from './desktop/windows/window.component';
import { SettingsWindow } from './desktop/windows/settings/settings.component';
import { MatButtonModule } from '@angular/material/button';
import { MatExpansionModule } from '@angular/material/expansion';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatNativeDateModule } from '@angular/material/core';
import { MatDividerModule } from '@angular/material/divider';
import { DesktopIconComponent } from './desktop/components/desktop-icon/desktop-icon.component';
import { TaskbarIconComponent } from './desktop/components/taskbar-icon/taskbar-icon.component';
import { ExplorerWindow } from './desktop/windows/explorer/explorer.component';
import { ServiceWorkerModule } from '@angular/service-worker';
import { environment } from '../environments/environment';
import { AdminWindow } from './desktop/windows/admin/admin.component';
import { MatTabsModule } from "@angular/material/tabs";
import { CodeWindow } from './desktop/windows/code/code.component';
import { MatSelectModule } from "@angular/material/select";
import {MatTableModule} from "@angular/material/table";
import { SearchComponent } from './desktop/windows/search/search.component';
import {MatTooltipModule} from "@angular/material/tooltip";
import {MatProgressBarModule} from "@angular/material/progress-bar";
import { EditorWindow } from './desktop/windows/editor/editor.component';
import {CodemirrorModule} from "@ctrl/ngx-codemirror";
import { Notifications } from './desktop/components/notifications/notifications.component';
import { NotificationWrapper } from './desktop/components/notifications/notification/notification.component';
import { ApiWindow } from './desktop/windows/api-window/api-window.component';
@NgModule({
declarations: [
AppComponent,
LoginComponent,
Desktop,
RegisterComponent,
WindowRoot,
DesktopIconComponent,
TaskbarIconComponent,
SettingsWindow,
ExplorerWindow,
AdminWindow,
CodeWindow,
SearchComponent,
EditorWindow,
Notifications,
NotificationWrapper,
ApiWindow
],
entryComponents: [
SettingsWindow
],
imports: [
BrowserModule,
BrowserAnimationsModule,
HttpClientModule,
AppRoutingModule,
MatButtonModule,
MatExpansionModule,
MatFormFieldModule,
MatIconModule,
MatInputModule,
MatDatepickerModule,
MatNativeDateModule,
MatTabsModule,
MatDividerModule,
MatSelectModule,
ServiceWorkerModule.register('ngsw-worker.js', {
enabled: environment.production,
registrationStrategy: 'registerWhenStable:30000'
}),
MatTableModule,
MatTooltipModule,
MatProgressBarModule,
CodemirrorModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

View File

@@ -0,0 +1,142 @@
import {Injectable} from '@angular/core';
import {HttpClient, HttpErrorResponse, HttpHeaders} from '@angular/common/http';
import {firstValueFrom} from 'rxjs';
import { Router } from '@angular/router';
import { Token } from './entitys/token';
import {environment} from "../environments/environment";
@Injectable({
providedIn: 'root'
})
export class CrudService {
public endpoint: string;
public authKey: string;
constructor(private httpClient: HttpClient) {
this.endpoint = environment.backendHost;
}
public httpHeader = new HttpHeaders({
'Content-Type': 'application/json'
});
setAccessToken(key?: string, contentType?: string, additionalHeaders?: [{name: string, value: string}]) {
if (key === undefined) return;
this.httpHeader = new HttpHeaders({
'Accept': '*/*',
'Authorization': key
});
if (contentType !== null) {
if (contentType === undefined) contentType = 'application/json';
this.httpHeader.append("Content-Type", contentType);
}
if (additionalHeaders !== undefined) {
for (let i = 0; i < additionalHeaders.length; i++) {
const header = additionalHeaders[i];
this.httpHeader = this.httpHeader.append(header.name, header.value);
}
}
this.authKey = key;
}
async sendGetRequest<T>(url: string, options?: Options): Promise<T> {
try {
this.setAccessToken(this.authKey, options?.contentType, options?.additionalHeaders);
return await firstValueFrom(this.httpClient.get<T>(this.endpoint + url, {headers: this.httpHeader, withCredentials: options?.withCredentials}));
}catch(e) {
const error = e as HttpErrorResponse;
if (options?.authorized && error.status == 401) {
const tokenResponse = await this.getNewToken();
if (!options.dontResendOnExpiration)
return await firstValueFrom(this.httpClient.get<T>(this.endpoint + url, {headers: this.httpHeader, withCredentials: options?.withCredentials}));
if (options?.returnNewTokenResponse)
return tokenResponse as unknown as T;
else throw e;
}else {
throw e;
}
}
}
async sendPostRequest<T>(url: string, body?: any, options?: Options): Promise<T> {
try {
this.setAccessToken(this.authKey, options?.contentType, options?.additionalHeaders);
return await firstValueFrom(this.httpClient.post<T>(this.endpoint + url, body, {headers: this.httpHeader, withCredentials: options?.withCredentials}));
}catch(e) {
const error = e as HttpErrorResponse;
if (options?.authorized && error.status == 401) {
await this.getNewToken();
if (!options.dontResendOnExpiration)
return await firstValueFrom(this.httpClient.post<T>(this.endpoint + url, body, {headers: this.httpHeader, withCredentials: options?.withCredentials}));
else throw e;
}else {
throw e;
}
}
}
async sendPutRequest<T>(url: string, body?: any, options?: Options): Promise<T> {
try {
this.setAccessToken(this.authKey, options?.contentType, options?.additionalHeaders);
return await firstValueFrom(this.httpClient.put<T>(this.endpoint + url, body, {headers: this.httpHeader, withCredentials: true}));
}catch(e) {
const error = e as HttpErrorResponse;
if (options?.authorized && error.status == 401) {
await this.getNewToken();
if (!options.dontResendOnExpiration)
return await firstValueFrom(this.httpClient.put<T>(this.endpoint + url, body, {headers: this.httpHeader, withCredentials: options?.withCredentials}));
else throw e;
}else {
throw e;
}
}
}
async sendDeleteRequest<T>(url: string, options?: Options): Promise<T> {
try {
this.setAccessToken(this.authKey, options?.contentType, options?.additionalHeaders);
return await firstValueFrom(this.httpClient.delete<T>(this.endpoint + url, {headers: this.httpHeader, withCredentials: options?.withCredentials}));
}catch(e) {
const error = e as HttpErrorResponse;
if (options?.authorized && error.status == 401) {
await this.getNewToken();
if (!options.dontResendOnExpiration)
return await firstValueFrom(this.httpClient.delete<T>(this.endpoint + url, {headers: this.httpHeader, withCredentials: options?.withCredentials}));
else throw e;
}else {
throw e;
}
}
}
async createWebSocketConnection(url: string): Promise<WebSocket> {
await this.getNewToken();
return new WebSocket(url + "?token=" + this.authKey);
}
async getNewToken(): Promise<boolean> {
try {
const token: Token = await firstValueFrom(this.httpClient.get<Token>(this.endpoint + "users/token", {headers: this.httpHeader, withCredentials: true}));
this.setAccessToken(token.id);
return true;
}catch {
return false;
}
}
public get HttpClient(): HttpClient { return this.httpClient; }
}
export interface Options {
authorized?: boolean;
withCredentials?: boolean;
dontResendOnExpiration?: boolean;
contentType?: string;
additionalHeaders?: [{name: string, value: string}];
returnNewTokenResponse?: boolean;
}

View File

@@ -0,0 +1,4 @@
<div class="desktop-icon" (dblclick)="onDoubleClick()">
<img src="{{type.icon}}" alt="Logo" draggable="false">
<span>{{type.name}}</span>
</div>

View File

@@ -0,0 +1,31 @@
@import "/src/variables.scss";
.desktop-icon {
display: inline-block;
margin: 10px;
padding: 10px;
width: $icon-size;
height: $icon-size;
border-radius: calc(100% / 10);
transition: 100ms;
z-index: 0;
img {
width: 70%;
height: 70%;
margin-left: 15%;
}
span {
display: block;
width: 100%;
text-align: center;
font-size: var(--icon-font-size);
color: var(--desktop-icon-text);
text-shadow: 0 0 5px var(--desktop-icon-shaddow);
}
&:hover {
background-color: var(--icon-hover);
}
}

View File

@@ -0,0 +1,29 @@
import {Component, ElementRef} from '@angular/core';
import {Desktop, DesktopIcon, WindowType} from "../../desktop.component";
@Component({
selector: 'app-desktop-icon',
templateUrl: './desktop-icon.component.html',
styleUrls: ['./desktop-icon.component.scss']
})
export class DesktopIconComponent implements DesktopIcon {
desktopRef: Desktop;
type: WindowType;
focus: boolean;
instance: HTMLElement;
constructor(object: ElementRef) {
this.instance = object.nativeElement;
}
public onFocus() {}
public onLostFocus() {}
public onClose() {}
public onOpen() {}
onDoubleClick(): void {
this.desktopRef.openWindow(this.type.id, this.type.args);
}
}

View File

@@ -0,0 +1,22 @@
<main #element>
<div class="header">
<button mat-icon-button class="collapse-btn" (click)="collapse()" *ngIf="collapsable"><mat-icon>{{collapseIcon}}</mat-icon></button>
<img src="{{type?.icon}}" alt="" class="program-icon">
<span class="program">{{type?.name}}</span>
<button mat-icon-button class="close" (click)="close()"><mat-icon>close</mat-icon></button>
</div>
<div class="content" #content>
<div class="cont-container">
<img src="{{message?.icon}}" alt="" *ngIf="message?.icon" class="icon">
<div>
<span class="title">{{message?.title}}</span>
<span class="message">{{message?.message}}</span>
</div>
</div>
<div class="notify-buttons" *ngIf="message?.buttons !== undefined">
<button *ngFor="let button of message?.buttons" mat-flat-button (click)="button.onClick(); close()" color="{{button.color}}">{{button.name}}</button>
</div>
</div>
</main>

View File

@@ -0,0 +1,88 @@
main {
$transition: 200ms ease-in-out;
width: 100%;
height: max-content;
background-color: var(--colors-first-background);
border: 2px solid var(--colors-second-background);
border-radius: 10px;
padding: 5px;
display: none;
flex-direction: column;
color: var(--colors-text);
position: relative;
margin-top: 5px;
.header {
width: 100%;
height: 30px;
overflow: hidden;
flex-shrink: 0;
display: flex;
align-items: center;
margin-left: 5px;
gap: 5px;
.program-icon {
width: 20px;
height: 20px;
}
button {
width: 20px;
height: 20px;
position: relative;
mat-icon {
position: absolute;
top: -2px;
left: -2px;
}
}
.close {
margin-left: auto;
margin-right: 10px;
}
}
.cont-container {
width: 100%;
min-height: 70px;
display: flex;
span {
display: block;
}
.title {
font-size: 18px;
font-weight: 500;
margin-bottom: 5px;
}
.icon {
height: 70px;
aspect-ratio: 1;
margin-right: 10px;
}
}
.notify-buttons {
margin-top: 10px;
display: flex;
justify-content: space-between;
gap: 15px;
button {
flex-grow: 1;
}
}
.content {
overflow: hidden;
transition: max-height $transition, padding-block $transition;
padding-inline: 5px;
padding-block: 5px;
}
}

View File

@@ -0,0 +1,102 @@
import {ChangeDetectorRef, Component, ElementRef, ViewChild} from '@angular/core';
import {WindowType} from "../../../desktop.component";
import {Notifications} from "../notifications.component";
export interface Notifier {
title: string;
message: string;
icon?: string;
buttons?: {name: string, color?: "primary" | "accent" | "warn", onClick(): void}[];
}
@Component({
selector: 'app-notification',
templateUrl: './notification.component.html',
styleUrls: ['./notification.component.scss']
})
export class NotificationWrapper {
@ViewChild('element') element: ElementRef;
@ViewChild('content') content: ElementRef;
type: WindowType;
message: Notifier;
animate: {speed: number, duration: number};
collapsed: boolean;
collapseIcon: string = "expand_more";
collapsable: boolean;
height: number;
private closed: boolean = false;
constructor(private cdr: ChangeDetectorRef) { }
public init(type: WindowType, message: Notifier, animate?: {speed: number, duration: number}, collapsed?: boolean) {
this.type = type;
this.message = message;
this.animate = animate;
this.collapsed = collapsed || false;
this.collapsable = collapsed !== undefined;
if (animate !== undefined) {
this.element.nativeElement.style.transform = "translateX(150%)";
this.element.nativeElement.style.transition = `transform ${animate.speed}ms ease-in-out, opacity ${animate.speed}ms linear`;
setTimeout(() => {
if (this.closed == true) return;
this.element.nativeElement.style.transform = "translateX(0)"
if (message.buttons === undefined || message.buttons?.length == 0)
setTimeout(() => this.simpleClose(), animate.duration);
}, animate.speed);
}
this.element.nativeElement.style.display = "flex";
this.cdr.detectChanges();
this.height = this.content.nativeElement.clientHeight;
this.content.nativeElement.style.maxHeight = this.height + "px";
if (collapsed == true) {
this.collapseIcon = "chevron_right";
this.content.nativeElement.style.maxHeight = "0";
this.content.nativeElement.style.paddingBlock = "0";
}
}
public close() {
if (this.closed == true) return;
this.closed = true;
if (this.animate !== undefined) {
this.element.nativeElement.style.transition = `transform ${this.animate.speed / 2}ms ease-in-out, opacity ${this.animate.speed / 2}ms linear`;
this.element.nativeElement.style.transform = "scale(0.75)";
setTimeout(() => {
this.element.nativeElement.style.transform = "scale(0.75) translateY(-100%)";
this.element.nativeElement.style.opacity = "0";
}, this.animate.speed / 2);
setTimeout(() => this.element.nativeElement.parentElement.parentElement.removeChild(this.element.nativeElement.parentElement), this.animate.speed);
}else this.element.nativeElement.parentElement.parentElement.removeChild(this.element.nativeElement.parentElement);
Notifications.remove(this);
}
public simpleClose() {
if (this.closed == true) return;
this.element.nativeElement.style.transform = "translateX(150%)"
setTimeout(() => {
if (this.closed == true) return;
this.closed = true;
this.element.nativeElement.parentElement.parentElement.removeChild(this.element.nativeElement.parentElement);
}, this.animate.speed + 50);
}
public collapse(toggle?: boolean) {
this.collapsed = toggle || !this.collapsed;
if (this.collapsed) {
this.collapseIcon = "chevron_right";
this.content.nativeElement.style.maxHeight = "0";
this.content.nativeElement.style.paddingBlock = "0";
}else {
this.collapseIcon = "expand_more";
this.content.nativeElement.style.maxHeight = this.height + "px";
this.content.nativeElement.style.paddingBlock = "5px";
}
}
}

View File

@@ -0,0 +1,12 @@
<main (click)="toggleTrace()">
<div>
<span class="time">{{time}}</span>
<span class="time">{{date}}</span>
</div>
<span class="notifications-count" *ngIf="notifications.length > 0">{{notifications.length}}</span>
</main>
<section class="notifications" #trace>
<span class="notifications-header">Benachrichtigungen</span>
<meta #notificationsRef>
</section>

View File

@@ -0,0 +1,65 @@
main {
margin-top: 5px;
margin-right: 10px;
height: 40px;
width: max-content;
border-radius: 10px;
padding-inline: 10px;
display: flex;
align-items: center;
gap: 7px;
&:hover {
background-color: rgba(255, 255, 255, 0.1);
}
div {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
.time {
color: var(--colors-text);
font-size: 11px;
}
}
.notifications-count {
background-color: var(--colors-second-background);
color: var(--colors-text);
width: 25px;
height: 25px;
line-height: 25px;
text-align: center;
border-radius: 50%;
}
}
.notifications {
position: absolute;
width: 450px;
max-height: 500px;
min-height: 50px;
right: 0;
bottom: 60px;
overflow-y: scroll;
background-color: var(--colors-first-background);
border: 3px solid var(--colors-second-background);
border-radius: 10px;
display: flex;
flex-direction: column;
transform: translateY(570px);
transition: transform 250ms ease-in-out;
.notifications-header {
display: block;
width: 100%;
height: 30px;
text-align: center;
font-size: 25px;
line-height: 30px;
color: var(--colors-text);
background-color: var(--colors-second-background);
}
}

View File

@@ -0,0 +1,95 @@
import {ChangeDetectorRef, Component, ElementRef, OnInit, ViewChild, ViewContainerRef} from '@angular/core';
import {NotificationWrapper, Notifier} from "./notification/notification.component";
import {Window} from "../../windows/window.component";
import {Desktop} from "../../desktop.component";
@Component({
selector: 'notifications',
templateUrl: './notifications.component.html',
styleUrls: ['./notifications.component.scss']
})
export class Notifications implements OnInit {
private static _instance: Notifications;
private closed: boolean = false;
private trace: boolean = false;
@ViewChild('notificationsRef', {read: ViewContainerRef}) notificationsRef: ViewContainerRef;
@ViewChild('trace') traceRef: ElementRef;
notifications: { desktop: NotificationWrapper, trace: NotificationWrapper }[] = [];
time: string;
date: string;
constructor(private cdr: ChangeDetectorRef) {
Notifications._instance = this;
}
ngOnInit(): void {
setInterval(() => {
const dt = new Date();
this.time = dt.toLocaleTimeString();
this.date = dt.toLocaleDateString();
}, 200);
}
public static async send(window: Window, notification: Notifier, animationSpeed: number = 300, animationDuration: number = 5000): Promise<NotificationWrapper> {
const type = window.properties.windowType;
const notify = Desktop.instance.notificationsRef.createComponent(NotificationWrapper);
Desktop.instance.cdr.detectChanges();
await notify.instance.init(type, notification, {speed: animationSpeed, duration: animationDuration});
// TRACE
const trace = this._instance.notificationsRef.createComponent(NotificationWrapper);
this._instance.cdr.detectChanges();
await trace.instance.init(type, notification, undefined, this._instance.notifications.length >= 2);
this._instance.notifications.push({desktop: notify.instance, trace: trace.instance});
if (this._instance.notifications.length > 2 && !this._instance.closed) {
this._instance.closed = true;
setTimeout(() => {
this._instance.notifications.forEach((element) => {
element.trace.collapse(true);
})
}, 1);
}
return notify.instance;
}
public static remove(notification: NotificationWrapper) {
for (let i = 0; i < this._instance.notifications.length; i++) {
const data = this._instance.notifications[i];
if (data.desktop === notification) {
this._instance.notifications.splice(i, 1);
data.trace?.close();
break;
}
if (data.trace === notification) {
this._instance.notifications.splice(i, 1);
data.desktop?.close();
break;
}
}
if (this._instance.notifications.length <= 2)
this._instance.closed = false;
if (this._instance.notifications.length == 0)
this._instance.toggleTrace(false);
}
public toggleTrace(toggle?: boolean) {
if (this.trace == toggle) return;
this.trace = toggle || !this.trace;
if (this.trace) {
this.traceRef.nativeElement.style.opacity = '1';
this.traceRef.nativeElement.style.transform = "translateY(0)";
}else {
this.traceRef.nativeElement.style.transform = "translateY(570px)";
setTimeout(() => this.traceRef.nativeElement.style.opacity = '0', 250);
}
}
}

View File

@@ -0,0 +1,5 @@
<div class="taskbar-icon" (click)="onClick()">
<img src="{{type.icon}}" alt="taskbar-icon">
<span class="taskbar-tooltip">{{type.name}}</span>
<div class="taskbar-opened"></div>
</div>

View File

@@ -0,0 +1,64 @@
@import "/src/variables.scss";
.taskbar-icon {
position: relative;
height: 40px;
width: 40px;
display: inline-block;
margin: 5px;
padding: 5px;
border-radius: 10px;
transition: all 200ms;
img {
height: 100%;
width: 100%;
transition: all 200ms;
&.click {
height: 80%;
width: 80%;
margin: 10%;
}
}
&:hover {
background-color: rgba(255, 255, 255, 0.1);
.taskbar-tooltip {
opacity: 100;
visibility: visible;
}
}
.taskbar-tooltip {
position: absolute;
bottom: 52px;
display: block;
background-color: var(--colors-first-background);
color: var(--colors-text);
border-radius: 2px;
padding: 2px;
transition: opacity 100ms;
visibility: hidden;
opacity: 0;
width: max-content;
}
.taskbar-opened {
--opened: 5px;
--focused: 20px;
position: absolute;
width: var(--focused);
height: 3px;
border-radius: 1.5px;
background-color: var(--opened-color);
transition: all 100ms;
opacity: 0;
top: 90%;
left: 50%;
transform: translate(-50%, 0);
}
}

View File

@@ -0,0 +1,53 @@
import {Component, ElementRef} from '@angular/core';
import {Desktop, DesktopIcon, WindowType} from "../../desktop.component";
@Component({
selector: 'app-taskbar-icon',
templateUrl: './taskbar-icon.component.html',
styleUrls: ['./taskbar-icon.component.scss']
})
export class TaskbarIconComponent implements DesktopIcon {
type: WindowType;
desktopRef: Desktop;
focus: boolean;
instance: HTMLElement;
constructor(object: ElementRef) {
this.instance = object.nativeElement;
}
onClick(): void {
//Click Animation
const container = this.instance.children.item(0).children.item(0) as HTMLElement;
container.classList.add("click");
setTimeout(() => container.classList.remove("click"), 100);
const unfocusedWindow = this.desktopRef.doesUnfocusedWindowExist(this.type.id);
if (unfocusedWindow !== undefined) {
unfocusedWindow.focusable = true;
unfocusedWindow.object.style.opacity = "100";
unfocusedWindow.isMinimized = false;
this.desktopRef.requestFocus(unfocusedWindow);
}else this.desktopRef.openWindow(this.type.id, this.type.args);
}
public onFocus() {
const focusBar = this.instance.children.item(0).children.item(2) as HTMLElement;
focusBar.style.width = "var(--focused)";
}
public onLostFocus() {
const focusBar = this.instance.children.item(0).children.item(2) as HTMLElement;
focusBar.style.width = "var(--opened)";
}
public onClose() {
if (this.desktopRef.isWindowTypeOpen(this.type.id)) return;
const focusBar = this.instance.children.item(0).children.item(2) as HTMLElement;
focusBar.style.opacity = "0";
}
public onOpen() {
const focusBar = this.instance.children.item(0).children.item(2) as HTMLElement;
focusBar.style.opacity = "100";
}
}

View File

@@ -0,0 +1,26 @@
<main class="desktop"
(click)="onClick($event)"
(mousemove)="onMouseMove($event)"
(mousedown)="onMouseDown($event)"
(mouseup)="onMouseUp($event)">
<div class="desktop-icons unselectable" #windows>
<meta #desktopIcons>
</div>
<section class="notifications"><meta #notifications></section>
<app-search #search></app-search>
</main>
<section class="taskbar unselectable">
<div class="taskbar-icon" id="search-icon" (click)="search.toggleSearch(); search.playClickAnimation()">
<img src="/assets/icons/search.png" alt="search-icon">
<span class="taskbar-tooltip">Suche</span>
<div class="taskbar-opened" id="search-opened"></div>
</div>
<div class="taskbar-icons">
<meta #taskbarIcons>
</div>
<notifications></notifications>
</section>

View File

@@ -0,0 +1,41 @@
@import "components/taskbar-icon/taskbar-icon.component.scss";
.taskbar {
position: absolute;
left: 15%;
top: calc(100% - 50px);
width: 70%;
height: 50px;
background-color: var(--taskbar-background);
z-index: 10;
border-top-left-radius: 20px;
border-top-right-radius: 20px;
display: flex;
justify-content: space-between;
}
.taskbar.wide {
left: 0;
width: 100%;
border-radius: 0;
}
.desktop {
width: 100%;
height: 100%;
z-index: 1;
.desktop-icons {
width: 100%;
height: 100%;
overflow: hidden;
}
.notifications {
position: absolute;
bottom: 70px;
right: 10px;
width: 450px;
height: max-content;
}
}

View File

@@ -0,0 +1,311 @@
import {AfterViewInit, ChangeDetectorRef, Component, Type, ViewChild, ViewContainerRef} from '@angular/core';
import {SettingsWindow} from './windows/settings/settings.component';
import {Window, WindowEvents, WindowRoot} from './windows/window.component';
import {DesktopIconComponent} from "./components/desktop-icon/desktop-icon.component";
import {TaskbarIconComponent} from "./components/taskbar-icon/taskbar-icon.component";
import {ExplorerWindow} from "./windows/explorer/explorer.component";
import {UserAPI} from "../api/userapi.service";
import {AdminWindow} from "./windows/admin/admin.component";
import {CodeWindow} from "./windows/code/code.component";
import {SearchComponent} from "./windows/search/search.component";
import {FileType} from "../entitys/files";
import {EditorWindow} from "./windows/editor/editor.component";
import {SettingsService} from "../api/settings.service";
import {defaultPersonalization, Personalization} from "../entitys/settings";
import {ApiWindow} from "./windows/api-window/api-window.component";
export interface WindowType {
type: Type<Window>;
id: string,
name: string,
icon: string,
permission?: string;
args?: string[];
canOpen?: (FileType | string)[];
url?: string;
}
export interface DesktopIcon {
type: WindowType;
desktopRef: Desktop;
focus: boolean;
instance: HTMLElement;
onFocus(): void;
onLostFocus(): void;
onOpen(): void;
onClose(): void;
}
export const windowTypes: WindowType[] = [
{type: SettingsWindow, id: "settings", name: "Einstellungen", icon: "/assets/icons/settings.png"},
{type: undefined, id: "browser", name: "Web Browser", icon: "/assets/icons/browser.png"},
{type: EditorWindow, id: "editor", name: "Editor", icon: "/assets/icons/editor.png", canOpen: [FileType.DATA, FileType.INTERNET]},
{type: ExplorerWindow, id: "explorer", name: "Explorer", icon: "/assets/icons/explorer.png"},
{type: undefined, id: "messenger", name: "Messenger", icon: "/assets/icons/messenger.png"},
{type: AdminWindow, id: "admin", name: "Admin Panel", icon: "/assets/icons/admin.png", permission: "app.admin.use"},
{type: CodeWindow, id: "code", name: "Code Editor", icon: "/assets/icons/code.png", permission: "app.code.use"},
{type: ApiWindow, id: "cloud", name: "WindowAPI Test", icon: "/assets/images/logo.png", url: "/assets/apiTest/iframe.html"}
];
@Component({
selector: 'app-desktop',
templateUrl: './desktop.component.html',
styleUrls: ['./desktop.component.scss']
})
export class Desktop implements AfterViewInit, WindowEvents {
private static _instance: Desktop;
public static get instance(): Desktop { return this._instance; }
public static get pinnedIcons(): DesktopIcon[] { return this._instance.pinnedIcons; }
@ViewChild('windows', {read: ViewContainerRef}) windowsContainerRef: ViewContainerRef;
@ViewChild('desktopIcons', {read: ViewContainerRef}) desktopIconsRef: ViewContainerRef;
@ViewChild('taskbarIcons', {read: ViewContainerRef}) taskbarIconsRef: ViewContainerRef;
@ViewChild('notifications', {read: ViewContainerRef}) notificationsRef: ViewContainerRef;
@ViewChild('search') search: SearchComponent;
private nextWindowId: number = 0;
private windows: WindowRoot[] = [];
private pinnedIcons: DesktopIcon[] = [];
private openedProgramIcons: DesktopIcon[] = [];
private focused: WindowRoot;
constructor(private userApi: UserAPI, public cdr: ChangeDetectorRef, private settings: SettingsService) { }
ngAfterViewInit(): void {
Desktop._instance = this;
document.body.style.backgroundImage = "var(--background)";
setTimeout(this.setupDesktop.bind(this), 0);
const settings = this.settings.getSetting<Personalization>("settings.personalization", defaultPersonalization);
document.querySelector(".taskbar").classList.toggle("wide", settings.taskbar == "big");
document.body.style.setProperty("--taskbar-left", !document.querySelector(".taskbar").classList.contains("wide") ? "15%" : "0px");
const icons = document.querySelector(".taskbar-icons") as HTMLElement;
if (settings.taskbar_icons == "left") icons.style.marginRight = "auto";
if (settings.taskbar_icons == "right") icons.style.marginLeft = "auto";
}
public addDesktopIcon(windowType: WindowType, index?: number): DesktopIcon {
let icon;
if (index != undefined)
icon = this.desktopIconsRef.createComponent(DesktopIconComponent, {index: index});
else
icon = this.desktopIconsRef.createComponent(DesktopIconComponent);
icon.instance.type = windowType;
icon.instance.desktopRef = this;
this.pinnedIcons.push(icon.instance);
return icon.instance;
}
public addTaskbarIcon(windowType: WindowType, index?: number): DesktopIcon {
let icon;
if (index != undefined)
icon = this.taskbarIconsRef.createComponent(TaskbarIconComponent, {index: index});
else
icon = this.taskbarIconsRef.createComponent(TaskbarIconComponent);
icon.instance.type = windowType;
icon.instance.desktopRef = this;
this.pinnedIcons.push(icon.instance);
return icon.instance;
}
public async addIcon(id: string, options: {taskbar?: boolean, desktop?: boolean, index?: {taskbar?: number, desktop?: number}}): Promise<DesktopIcon[]> {
const windowType = this.getWindowTypeRef(id);
if (windowType.permission != undefined && !(await this.userApi.checkForPermission(windowType.permission))) return undefined;
const icons: DesktopIcon[] = [];
if (options?.taskbar) {
icons.push(this.addTaskbarIcon(windowType, options?.index?.taskbar));
}
if (options?.desktop) {
icons.push(this.addDesktopIcon(windowType, options?.index?.desktop));
}
return icons;
}
public async addTempTaskbarIcon(id: string) {
if (this.openedProgramIcons.filter(icon => icon.type.id === id).length != 0) return;
const icon = (await this.addIcon(id, {taskbar: true}))[0];
this.pinnedIcons.splice(this.pinnedIcons.indexOf(icon), 1);
this.openedProgramIcons.push(icon);
}
public removeTaskbarIcon(id: string) {
const icon = this.pinnedIcons.filter(icon => icon.type.id === id && icon.instance.tagName === "APP-TASKBAR-ICON")[0] as TaskbarIconComponent;
icon.instance.parentElement.removeChild(icon.instance);
this.pinnedIcons.splice(this.pinnedIcons.indexOf(icon), 1);
}
public removeDesktopIcon(id: string) {
const icon = this.pinnedIcons.filter(icon => icon.type.id === id && icon.instance.tagName === "APP-DESKTOP-ICON")[0] as DesktopIconComponent;
icon.instance.parentElement.removeChild(icon.instance);
this.pinnedIcons.splice(this.pinnedIcons.indexOf(icon), 1);
}
public getWindow(windowId: number): WindowRoot {
for (let window of this.windows) {
if (window.windowId === windowId)
return window;
}
return undefined;
}
public getWindowReferences(id: string): WindowRoot[] {
const windows: WindowRoot[] = [];
for (let window of this.windows) {
if (window.contentType.id === id)
windows.push(window);
}
return windows;
}
public async openWindow(id: string, args?: string[]): Promise<WindowRoot> {
const type = this.getWindowTypeRef(id);
if (type.type === undefined) {
alert("This Application is not yet implemented!");
return undefined;
}
if (this.getWindowTypeIcons(id).filter(icon => icon.instance.tagName === "APP-TASKBAR-ICON").length == 0) {
await this.addTempTaskbarIcon(id);
}
const window = this.windowsContainerRef.createComponent(WindowRoot);
window.instance.desktopRef = this;
window.instance.windowId = this.nextWindowId;
this.nextWindowId++;
window.instance.contentType = type;
window.instance.windowArgs = args === undefined ? [] : args;
this.cdr.detectChanges();
await window.instance.onOpen();
this.windows.push(window.instance);
const icons = this.getWindowTypeIcons(id);
icons.forEach((icon) => {
icon.focus = true;
icon.onOpen();
});
this.openedProgramIcons.filter(icon => icon.type.id == window.instance.contentType.id).forEach((icon: DesktopIcon) => {
icon.focus = true;
icon.onOpen();
});
this.requestFocus(window.instance);
return window.instance;
}
public async closeWindow(windowId: number) {
const window = this.getWindow(windowId);
if (!await window.contentRef.onClose()) return;
window.object.parentElement?.removeChild(window.object);
this.windows.splice(this.windows.indexOf(window), 1);
const icons = this.getWindowTypeIcons(window.id);
icons.forEach((icon) => {
icon.focus = false;
icon.onClose();
});
if (icons.filter(icon => icon.instance.tagName === "APP-TASKBAR-ICON").length == 0) {
if (this.isWindowTypeOpen(window.contentType.id)) return;
const icon = this.openedProgramIcons.filter(icon => icon.type.id == window.contentType.id)[0];
icon.instance.parentElement.removeChild(icon.instance);
this.openedProgramIcons.splice(this.openedProgramIcons.indexOf(icon), 1);
}
}
public isWindowTypeOpen(id: string): boolean {
for (let window of this.windows) {
if (window.id === id)
return true;
}
return false;
}
public doesUnfocusedWindowExist(id: string): WindowRoot {
for (let window of this.windows) {
if (window.id !== id) continue;
if (window !== this.focused)
return window;
}
return undefined;
}
public requestFocus(window: WindowRoot): void {
delete this.focused;
this.windows.forEach((window: WindowRoot) => window.onFocusLost());
this.pinnedIcons.forEach((icon: DesktopIcon) => {
icon.focus = false;
icon.onLostFocus();
});
this.openedProgramIcons.forEach((icon: DesktopIcon) => {
icon.focus = false;
icon.onLostFocus();
});
if (window !== undefined) {
window.onFocusGranted();
const icons = this.getWindowTypeIcons(window.id);
icons.forEach((icon: DesktopIcon) => {
icon.focus = true;
icon.onFocus();
});
this.openedProgramIcons.filter(icon => icon.type.id == window.contentType.id).forEach((icon: DesktopIcon) => {
icon.focus = true;
icon.onFocus();
});
}
this.focused = window;
}
public getWindowTypeRef(id: string): WindowType {
for (let window of windowTypes) {
if (window.id === id)
return window;
}
return undefined;
}
private getWindowTypeIcons(id: string): DesktopIcon[] {
const icons: DesktopIcon[] = [];
for (let icon of this.pinnedIcons) {
if (icon.type.id === id)
icons.push(icon);
}
return icons;
}
public get currentlyFocused(): WindowRoot { return this.focused; }
private setupDesktop() {
this.addIcon("settings", {taskbar: true, desktop: true});
this.addIcon("explorer", {taskbar: true, desktop: true});
this.addIcon("code", {taskbar: true, desktop: true});
this.addIcon("admin", {taskbar: true, index: {taskbar: 0}});
}
onClick(event: MouseEvent): void {
if (!this.search.mouseOver)
this.search.toggleSearch(false);
let focused = false;
for (let window of this.windows) {
window.onClick(event);
if (window.isMouseOver)
focused = true;
}
if (!focused)
this.requestFocus(undefined);
}
onMouseDown(event: MouseEvent): void {
for (let window of this.windows)
window.onMouseDown(event);
}
onMouseUp(event: MouseEvent): void {
for (let window of this.windows)
window.onMouseUp(event);
}
onMouseMove(event: MouseEvent): void {
for (let window of this.windows) {
window.onMouseMove(event);
window.onGlobalMove(event);
}
}
}

View File

@@ -0,0 +1,108 @@
<mat-tab-group mat-align-tabs="center" class="unselectable">
<mat-tab label="Updater">
<div class="console" #console></div>
<button mat-raised-button class="update" (click)="onUpdate()">Update</button>
</mat-tab>
<mat-tab label="Users">
<table mat-table [dataSource]="users" class="users">
<ng-container matColumnDef="id">
<th mat-header-cell *matHeaderCellDef>#Id</th>
<td mat-cell *matCellDef="let user">{{user.id}}</td>
</ng-container>
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef>Name</th>
<td mat-cell *matCellDef="let user">{{user.firstName}} {{user.lastName}}</td>
</ng-container>
<ng-container matColumnDef="username">
<th mat-header-cell *matHeaderCellDef>Username</th>
<td mat-cell *matCellDef="let user">{{user.username}}</td>
</ng-container>
<ng-container matColumnDef="email">
<th mat-header-cell *matHeaderCellDef>Email</th>
<td mat-cell *matCellDef="let user">{{user.email}}</td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef>Actions</th>
<td mat-cell *matCellDef="let user">
<button mat-mini-fab color="accent" (click)="editPermissions(user)"><mat-icon>manage_accounts</mat-icon></button>
<button mat-mini-fab color="primary" (click)="editUser(user)"><mat-icon>edit</mat-icon></button>
<button mat-mini-fab color="warn" (click)="deleteUser(user)"><mat-icon>delete</mat-icon></button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef='["id", "name", "username", "email", "actions"]'></tr>
<tr mat-row *matRowDef='let row; columns: ["id", "name", "username", "email", "actions"]'></tr>
</table>
</mat-tab>
<mat-tab label="Third">Content 3</mat-tab>
</mat-tab-group>
<form class="option edit" #edit>
<div class="row">
<div class="col">
<label for="firstname">First name</label>
<input type="text" class="form-control" placeholder="{{current?.firstName}}" value="{{current?.firstName}}" id="firstname" #firstname>
</div>
<div class="col">
<label for="lastname">Last name</label>
<input type="text" class="form-control" placeholder="{{current?.lastName}}" value="{{current?.lastName}}" id="lastname" #lastname>
</div>
</div>
<br>
<div class="form-group">
<label for="username">Username</label>
<input type="text" class="form-control" id="username" placeholder="{{current?.username}}" value="{{current?.username}}" #username>
</div>
<br>
<div class="form-group">
<label for="email">Email address</label>
<input type="email" class="form-control" id="email" aria-describedby="emailHelp" placeholder="{{current?.email}}" value="{{current?.email}}" #email>
</div>
<br>
<div class="form-group">
<label for="password">Password</label>
<input type="password" class="form-control" id="password" placeholder="Password" #password>
</div>
<br>
<div class="buttons">
<button type="reset" mat-raised-button color="warn" (click)="cancelEdit($event)">Cancel</button>
<button type="submit" mat-raised-button color="primary" (click)="onEdit($event)">Edit</button>
</div>
</form>
<section class="option permissions unselectable" #permissions>
<div class="row">
<div class="col-9">
<input type="text" class="form-control" placeholder="PermissionName" #currPermission>
</div>
<div class="col-3">
<button mat-raised-button color="primary" (click)="addPermission()">Add Permission</button>
<button mat-raised-button color="warn" (click)="cancelPermission()">Back</button>
</div>
</div>
<br>
<table class="table table-sm">
<thead class="thead-dark">
<tr>
<th scope="col">Permission</th>
<th scope="col">Actions</th>
</tr>
</thead>
<tbody *ngFor="let permission of userPermissions" class="selectable">
<tr>
<td class="selectable">{{permission}}</td>
<td>
<button mat-button (click)="deletePermission(permission)" class="unselectable" color="warn">Delete</button>
</td>
</tr>
</tbody>
</table>
</section>

View File

@@ -0,0 +1,79 @@
@import "/src/variables.scss";
::ng-deep .mat-tab-label {
color: var(--colors-text);
}
mat-tab-group {
height: 100%;
}
::ng-deep .mat-tab-body-wrapper {
height: 100%;
}
.console {
width: 80%;
height: 70%;
background-color: var(--colors-first-background);
font-family: monospace;
color: var(--colors-text);
margin: 5% 10%;
font-size: 11px;
overflow-y: scroll;
padding: 5px;
}
.update {
left: 50%;
transform: translate(-50%);
}
.users {
width: 100%;
background: none;
button {
margin: 2px;
}
}
.option {
position: absolute;
opacity: 0;
top: -100%;
left: 0;
transition: 500ms;
background-color: var(--colors-second-background);
color: var(--colors-text);
width: 100%;
height: 100%;
z-index: 1;
padding: 20px;
}
.option.enable {
opacity: 100;
top: 0;
}
.permissions {
.table {
color: var(--colors-text);
border-color: var(--colors-text);
}
.col-3 {
display: flex;
justify-content: center;
align-items: center;
button {
margin: 0 5px;
}
}
.mat-button.mat-warn {
color: var(--colors-danger);
}
}

View File

@@ -0,0 +1,190 @@
import {Component, ElementRef, ViewChild} from '@angular/core';
import {Window, WindowProperties} from "../window.component";
import {UserAPI} from "../../../api/userapi.service";
import {User, UserEditor} from "../../../entitys/user";
import Swal from "sweetalert2";
import {environment} from "../../../../environments/environment";
import {CrudService} from "../../../crud.service";
@Component({
selector: 'app-admin',
templateUrl: './admin.component.html',
styleUrls: ['./admin.component.scss']
})
export class AdminWindow implements Window {
@ViewChild('console') console: ElementRef;
@ViewChild('edit') edit: ElementRef;
@ViewChild('permissions') permissions: ElementRef;
@ViewChild('firstname') firstname: ElementRef;
@ViewChild('lastname') lastname: ElementRef;
@ViewChild('username') username: ElementRef;
@ViewChild('email') email: ElementRef;
@ViewChild('password') password: ElementRef;
@ViewChild('currPermission') currentPermission: ElementRef;
constructor(public userApi: UserAPI, private service: CrudService) { }
properties: WindowProperties;
updater: WebSocket;
users: User[];
current: User;
userPermissions: string[];
onClose(): boolean {
this.updater.close();
return true;
}
async onOpen() {
this.properties.title = "Admin Panel";
this.properties.icon = "/assets/icons/admin.png";
this.properties.resizable = false;
this.properties.setSize(1200, 800);
//Update
this.updater = await this.service.createWebSocketConnection(environment.updateUrl);
this.updater.onopen = () => this.updater.send("Ping");
this.updater.onmessage = (e) => {
this.console.nativeElement.innerText += e.data + '\n';
this.console.nativeElement.scrollTop = this.console.nativeElement.scrollHeight;
}
this.users = await this.userApi.getUsers();
}
onUpdate() {
this.updater.send("Start");
}
async onEdit(event: MouseEvent) {
event.preventDefault();
let user: UserEditor = {firstName: "", lastName: "", username: "", email: "", password: ""};
user.firstName = this.firstname.nativeElement.value == this.current.firstName ? "" : this.firstname.nativeElement.value;
user.lastName = this.lastname.nativeElement.value == this.current.lastName ? "" : this.lastname.nativeElement.value;
user.username = this.username.nativeElement.value == this.current.username ? "" : this.username.nativeElement.value;
user.email = this.email.nativeElement.value == this.current.email ? "" : this.email.nativeElement.value;
user.password = this.password.nativeElement.value == this.current.password ? "" : this.password.nativeElement.value;
const success = await this.userApi.editUser(this.current.id, user);
if (success) {
await Swal.fire({
icon: 'success',
title: 'User edited',
text: 'The User was successfully edited',
timer: 2000,
showConfirmButton: false
});
this.cancelEdit(event);
this.users = await this.userApi.getUsers();
}else {
await Swal.fire({
icon: 'error',
title: 'Editing failed',
text: 'The User could not be edited',
timer: 2000,
showConfirmButton: false
});
}
}
cancelEdit(event: MouseEvent) {
event.preventDefault();
delete this.current;
this.edit.nativeElement.classList.remove("enable");
}
editUser(user: User) {
this.current = user;
this.edit.nativeElement.classList.add("enable");
}
async deleteUser(user: User) {
const confirm = await Swal.fire({
title: 'Do you really want to DELETE this User?',
showDenyButton: false,
showCancelButton: true,
confirmButtonText: 'Delete',
});
if (!confirm.isConfirmed) return;
const success = await this.userApi.deleteUser(user.id);
if (success) {
await Swal.fire({
icon: 'success',
title: 'User deleted',
text: 'The User was successfully deleted',
timer: 2000,
showConfirmButton: false
});
this.users = await this.userApi.getUsers();
}else {
await Swal.fire({
icon: 'error',
title: 'Deleting failed',
text: 'The User could not be deleted',
timer: 2000,
showConfirmButton: false
});
}
}
async editPermissions(user: User) {
this.current = user;
this.userPermissions = await this.userApi.getPermissions(user.id);
this.permissions.nativeElement.classList.add("enable");
}
async deletePermission(permission: string) {
const success = await this.userApi.removePermission(this.current.id, permission);
if (success) {
await Swal.fire({
icon: 'success',
title: 'Permission deleted',
text: 'The Permission was successfully deleted',
timer: 2000,
showConfirmButton: false
});
this.userPermissions = await this.userApi.getPermissions(this.current.id);
}else {
await Swal.fire({
icon: 'error',
title: 'Deleting failed',
text: 'The Permission could not be deleted',
timer: 2000,
showConfirmButton: false
});
}
}
async addPermission() {
const success = await this.userApi.addPermission(this.current.id, this.currentPermission.nativeElement.value);
if (success) {
await Swal.fire({
icon: 'success',
title: 'Permission added',
text: 'The Permission was successfully added',
timer: 2000,
showConfirmButton: false
});
this.userPermissions = await this.userApi.getPermissions(this.current.id);
}else {
await Swal.fire({
icon: 'error',
title: 'Adding failed',
text: 'The Permission could not be added',
timer: 2000,
showConfirmButton: false
});
}
}
cancelPermission() {
delete this.current;
delete this.userPermissions;
this.permissions.nativeElement.classList.remove("enable");
}
}

View File

@@ -0,0 +1 @@
<iframe #frame (blur)="properties.requestFocus()"></iframe>

View File

@@ -0,0 +1,4 @@
iframe {
width: 100%;
height: 100%;
}

View File

@@ -0,0 +1,170 @@
import {Component, ElementRef, ViewChild} from '@angular/core';
import {Window, WindowProperties} from "../window.component";
import {Desktop, windowTypes} from "../../desktop.component";
type PromiseType = "toggleMaximize" | "minimize" | "close" | "windowTypes" | "openWindow" | "closeWindow" | "requestFocus" | "properties" | "update" | "triggerListener" |
"title" | "headerMessage" | "icon" | "resizable" | "size" | "minSize" | "position" | "windowType" | "additionalArgs";
interface Packet {
type: PromiseType;
data?: any;
}
@Component({
selector: 'app-api-window',
templateUrl: './api-window.component.html',
styleUrls: ['./api-window.component.scss']
})
export class ApiWindow implements Window {
@ViewChild('frame') frameRef: ElementRef;
frame: HTMLIFrameElement;
properties: WindowProperties;
eventListener: any;
constructor() { }
onOpen(args: string[]): void {
this.frame = this.frameRef.nativeElement as HTMLIFrameElement;
this.frame.src = this.properties.windowType.url;
this.eventListener = (event: MessageEvent) => {
this.handleEvent(event.data.type, event.data.data);
};
window.addEventListener("message", this.eventListener, false);
}
onClose(): boolean {
window.removeEventListener("message", this.eventListener);
return true;
}
private sendMessage(packet: Packet): void {
this.frame.contentWindow.postMessage(packet, new URL(this.frame.src).origin)
}
private handleEvent(type: PromiseType, data: any): void {
switch (type) {
case "windowTypes":
const types = [];
for (let w of windowTypes) {
types.push({
type: w.type?.name,
id: w.id,
name: w.name,
icon: w.icon,
permission: w.permission,
args: w.args,
canOpen: w.canOpen,
url: w.url
});
}
this.sendMessage({type, data: types});
break;
case "openWindow":
Desktop.instance.openWindow(data.id, data.args);
break;
case "closeWindow":
Desktop.instance.closeWindow(data)
break;
case "requestFocus":
this.properties.requestFocus();
break;
case "minimize":
this.properties.windowRef.minimize();
break;
case "close":
this.properties.windowRef.close();
break;
case "properties":
this.handlePropertyChange(data.name, data.value)
break;
case "title":
this.sendMessage({type, data: this.properties.title});
break;
case "headerMessage":
this.sendMessage({type, data: this.properties.headerMessage});
break;
case "icon":
this.sendMessage({type, data: this.properties.icon});
break;
case "resizable":
this.sendMessage({type, data: this.properties.resizable});
break;
case "size":
this.sendMessage({type, data: this.properties.size});
break;
case "minSize":
this.sendMessage({type, data: this.properties.minSize});
break;
case "position":
this.sendMessage({type, data: this.properties.position});
break;
case "windowType":
this.sendMessage({type, data: this.properties.windowType});
break;
case "additionalArgs":
this.sendMessage({type, data: this.properties.additionalArgs});
break;
case "update":
this.properties.updateWindow();
break;
}
}
private handlePropertyChange(property: string, value: any): void {
switch (property) {
case "title":
this.properties.title = value;
break;
case "headerMessage":
this.properties.headerMessage = value;
break;
case "icon":
this.properties.icon = value;
break;
case "resizable":
this.properties.resizable = value;
break;
case "minSize":
this.properties.minSize = value;
break;
case "maximized":
this.properties.setMaximized(value);
break;
case "size":
this.properties.setSize(value.width, value.heigth);
break;
case "position":
this.properties.setPosition(value.x, value.y);
break;
}
}
}

View File

@@ -0,0 +1 @@
<iframe src="https://code.leon-hoppe.de"></iframe>

View File

@@ -0,0 +1,4 @@
iframe {
width: 100%;
height: 100%;
}

View File

@@ -0,0 +1,24 @@
import { Component } from '@angular/core';
import {Window, WindowProperties} from "../window.component";
@Component({
selector: 'app-code',
templateUrl: './code.component.html',
styleUrls: ['./code.component.scss']
})
export class CodeWindow implements Window {
properties: WindowProperties;
constructor() { }
onClose(): boolean { return true; }
onOpen(): void {
this.properties.title = "Code Editor";
this.properties.headerMessage = "Bearbeite deine Datein in der Cloud";
this.properties.icon = "/assets/icons/code.png";
this.properties.setMaximized(true);
}
}

View File

@@ -0,0 +1,41 @@
<header>
<button mat-button (click)="onClose(); onOpen([])">Neu</button>
<button mat-button (click)="open()">Öffnen</button>
<button mat-button (click)="save()">Speichern</button>
<button mat-button (click)="saveAs()">Speichern unter</button>
</header>
<ngx-codemirror [options]="options" (drop)="handleDrop($event[1])" (keypress)="edit()" #editor></ngx-codemirror>
<footer class="unselectable">
<p>UTF-8</p>
<p (click)="properties.dialog.toggle(changeMode)" style="cursor: pointer">{{options?.mode || "text"}}</p>
<p>Line: {{editor.codeMirror?.getCursor().line + 1}}</p>
</footer>
<form class="dialog change-mode"
#changeMode (reset)="properties.dialog.toggle(changeMode, false)"
(submit)="$event.preventDefault(); options.mode = label.value; properties.dialog.toggle(changeMode, false); changeMode.reset()">
<div class="dialog-main">
<mat-form-field appearance="fill">
<mat-label>Modus</mat-label>
<input matInput required #label>
</mat-form-field>
<a mat-icon-button matTooltip="Alle Modis" href="https://codemirror.net/mode/index.html" target="_blank" rel="noopener noreferrer"><mat-icon>open_in_new</mat-icon></a>
</div>
<div class="buttons">
<button mat-raised-button color="primary" type="submit">Modus ändern</button>
<button mat-raised-button color="warn" type="reset">Abbrechen</button>
</div>
</form>
<form class="dialog save-changes" #dialog (submit)="$event.preventDefault(); saveOnClose()" (reset)="saved = true; properties.windowRef.close()">
<span>Ungespeicherte Änderungen speichern?</span>
<div class="buttons">
<button mat-raised-button color="primary" type="submit">Ja</button>
<button mat-raised-button color="warn" type="reset">Nein</button>
</div>
</form>

View File

@@ -0,0 +1,62 @@
header {
width: 100%;
height: 35px;
background-color: var(--colors-first-background);
position: relative;
button {
height: 100%;
margin: 0 5px;
}
}
ngx-codemirror {
display: block;
width: 100%;
height: calc(100% - 55px);
position: relative;
}
footer {
width: 100%;
height: 20px;
background-color: var(--colors-first-background);
p {
height: 100%;
line-height: 20px;
text-align: center;
margin: 0 5px;
width: max-content;
display: inline-block;
}
}
.change-mode .dialog-main {
display: flex;
justify-content: space-between;
align-items: center;
mat-form-field {
width: 80%;
margin: 5%;
}
a {
margin: 5%;
}
}
.save-changes {
span {
position: absolute;
width: 100%;
top: 10%;
font-size: 20px;
text-align: center;
}
.buttons {
margin-top: 30%;
}
}

View File

@@ -0,0 +1,150 @@
import {Component, ElementRef, ViewChild} from '@angular/core';
import {Window, WindowProperties} from "../window.component";
import {EditorConfiguration} from "codemirror";
import * as CodeMirror from "codemirror";
import {FileAPI} from "../../../api/fileapi.service";
import {ExplorerWindow} from "../explorer/explorer.component";
import {HttpEventType} from "@angular/common/http";
import Swal from "sweetalert2";
import {SettingsService} from "../../../api/settings.service";
import {defaultPersonalization, Personalization} from "../../../entitys/settings";
import {CodemirrorComponent} from "@ctrl/ngx-codemirror";
@Component({
selector: 'app-editor',
templateUrl: './editor.component.html',
styleUrls: ['./editor.component.scss']
})
export class EditorWindow implements Window {
@ViewChild('editor') editor: CodemirrorComponent;
@ViewChild('dialog') saveChanges: ElementRef;
constructor(private files: FileAPI, private settings: SettingsService) { }
properties: WindowProperties;
options: EditorConfiguration;
file: {name: string, directory: string, ext: string, url: string};
eventListener: EventListenerOrEventListenerObject;
saved: boolean = true;
onClose(): boolean {
document.removeEventListener(SettingsService.eventChange.type, this.eventListener);
if (!this.saved) {
this.properties.dialog.toggle(this.saveChanges.nativeElement, true);
return false;
}
return true;
}
async onOpen(args: string[]) {
this.properties.title = "Editor";
this.properties.headerMessage = "untitled.txt";
this.properties.icon = "/assets/icons/editor.png";
this.properties.minSize = {width: 800, height: 600};
this.options = {
lineNumbers: true,
theme: this.getThemeBySystemTheme(),
mode: "text",
value: "",
}
this.file = undefined;
this.editor.codeMirror?.setValue("");
if (args.filter((arg) => arg.includes("--file=")).length !== 0) {
const url = args.filter((arg) => arg.includes("--file="))[0].replace("--file=", "");
const {file, directory, ext} = ExplorerWindow.extractFileAndFolderFromURL(url);
this.properties.headerMessage = url;
this.file = {name: file, directory: directory, ext: ext, url: url};
const download = await this.files.downloadFile(directory, file);
download.subscribe(async (event) => {
if (event.type === HttpEventType.Response) {
const blob = event.body as Blob;
this.options.value = await blob.text();
this.options.mode = CodeMirror.findModeByExtension(ext).mode || "text";
}
});
}
this.eventListener = (() => this.options.theme = this.getThemeBySystemTheme()).bind(this);
document.addEventListener(SettingsService.eventChange.type, this.eventListener);
}
handleDrop(event: DragEvent) {
if (event === undefined) return;
if (ExplorerWindow.dragItem === undefined ||
ExplorerWindow.dragItem?.isDirectory === true) {
Swal.fire({
icon: 'error',
title: 'Ungültiger Inhalt',
text: 'Du kannst nur Dateien öffnen',
timer: 2000,
showConfirmButton: false
});
return;
}
this.onOpen(["--file=" + ExplorerWindow.dragItem.url]);
}
private getThemeBySystemTheme(): string {
const theme = this.settings.getSetting<Personalization>("settings.personalization", defaultPersonalization).theme;
if (theme === "theme-dark")
return "material";
if (theme === "theme-light")
return "idea";
return "material";
}
async saveAs() {
return new Promise((resolve => {
ExplorerWindow.openPopup("save-file", async (url: string) => {
const file = ExplorerWindow.extractFileAndFolderFromURL(url);
const data = this.files.toFile(this.editor.value as string, file.file);
await this.files.quickUpload(data, file.directory);
this.properties.headerMessage = url;
this.file = {
name: file.file,
directory: file.directory,
ext: file.ext,
url: url
};
this.saved = true;
resolve(null);
}, this.file?.name || "untitled.txt", this.file?.directory || "/");
}))
}
async save() {
if (this.file == undefined) await this.saveAs();
else {
const data = this.files.toFile(this.editor.value as string, this.file.name);
await this.files.quickUpload(data, this.file.directory);
this.properties.headerMessage = this.file.url;
this.saved = true;
}
}
open() {
ExplorerWindow.openPopup("choose-file", async (url: string) => {
await this.onOpen(["--file=" + url]);
}, "");
}
edit() {
if (!this.properties.headerMessage.endsWith("*"))
this.properties.headerMessage += "*";
this.saved = false;
}
async saveOnClose() {
await this.save();
this.properties.windowRef.close();
}
}

View File

@@ -0,0 +1,107 @@
<section class="actions">
<input type="file" #upload style="display: none" (change)="uploadFiles(upload)">
<button mat-icon-button matTooltip="Hochladen" class="action" (click)="upload.click()">
<mat-icon>file_upload</mat-icon>
</button>
<button mat-icon-button matTooltip="Übergeortneter Ordner" class="action" (click)="changeDir(getUpFolder())" (dragover)="onDragUpOver($event)" (drop)="onDropUp($event, getUpFolder())">
<mat-icon>drive_folder_upload</mat-icon>
</button>
<button mat-icon-button matTooltip="Ordner erstellen" class="action" (click)="properties.dialog.toggle(addDirectory, true)">
<mat-icon>create_new_folder</mat-icon>
</button>
<form class="directory input" (submit)="$event.preventDefault(); directory.blur(); changeDir(directory.value)">
<mat-icon>folder</mat-icon>
<input type="text" [value]="currentDirectory" #directory>
</form>
<form class="search input" (submit)="$event.preventDefault(); search.blur(); searchFiles(search.value)">
<mat-icon>search</mat-icon>
<input type="text" placeholder="Suche" #search>
</form>
<mat-progress-bar mode="determinate" value="{{progress}}"></mat-progress-bar>
</section>
<main class="directory-content" #content (mousedown)="currentItem = undefined">
<div class="content"
*ngFor="let directory of currentDirectoryInfo?.directories"
(dblclick)="changeDir(currentDirectory + directory + '/')"
(click)="selectItem(self, directory, true)"
(dragover)="onDragOver($event, directory)" (dragstart)="onDrag(directory, true)" (drop)="onDrop($event, directory)"
#self draggable="true">
<mat-icon class="icon">folder</mat-icon>
<span>{{directory}}</span>
<button mat-icon-button (click)="deleteItem(directory)"><mat-icon>delete</mat-icon></button>
</div>
<div class="content"
*ngFor="let file of currentDirectoryInfo?.files"
(dblclick)="openWith(file)"
(click)="selectItem(self, file, false)"
(dragstart)="onDrag(file, false)"
#self draggable="true">
<mat-icon class="icon">{{getFileImage(file)}}</mat-icon>
<span>{{file}}</span>
<button mat-icon-button (click)="openWith(file)"><mat-icon>open_in_new</mat-icon></button>
<button mat-icon-button (click)="downloadFile(file)"><mat-icon>file_download</mat-icon></button>
<button mat-icon-button (click)="deleteItem(file)"><mat-icon>delete</mat-icon></button>
</div>
</main>
<form *ngIf="nameBarOptions.enabled" class="name-bar" (submit)="$event.preventDefault(); onSave(currentDirectory + nameBar.value)">
<div class="input">
<mat-icon>insert_drive_file</mat-icon>
<input type="text" [value]="nameBarOptions.value" (change)="nameBarOptions.value = nameBar.value" autofocus #nameBar>
</div>
<button mat-icon-button>
<mat-icon *ngIf="nameBarOptions.type == 'save-file'">save</mat-icon>
<mat-icon *ngIf="nameBarOptions.type != 'save-file'">open_in_new</mat-icon>
</button>
</form>
<section class="directory-information" *ngIf="currentDirectoryInformation !== undefined">
<span>{{currentDirectoryInfo?.files.length + currentDirectoryInfo?.directories.length}} Elemente</span>
<span>Größe: {{humanFileSize(currentDirectoryInformation?.size)}}</span>
<span>Erstellt: {{currentDirectoryInformation?.created.toLocaleString()}}</span>
</section>
<form class="dialog create-directory" #addDirectory (reset)="properties.dialog.toggle(addDirectory, false)" (submit)="$event.preventDefault(); createDirectory(label.value); properties.dialog.toggle(addDirectory, false); addDirectory.reset()">
<mat-form-field appearance="fill">
<mat-label>Name</mat-label>
<input matInput required #label>
</mat-form-field>
<div class="buttons">
<button mat-raised-button color="primary" type="submit">Erstellen</button>
<button mat-raised-button color="warn" type="reset">Abbrechen</button>
</div>
</form>
<form class="dialog override" #override (reset)="properties.dialog.toggle(override, false)" (submit)="onSave(currentDirectory + nameBarOptions.value, true)">
<span>Möchtest du dieses Element überschreiben?</span>
<div class="buttons">
<button mat-raised-button color="primary" type="submit">Ja</button>
<button mat-raised-button color="warn" type="reset">Nein</button>
</div>
</form>
<form class="dialog select-program unselectable" #selectProgram (submit)="$event.preventDefault();">
<div class="buttons-right">
<button mat-icon-button (click)="properties.dialog.toggle(selectProgram, false)"><mat-icon>close</mat-icon></button>
</div>
<div class="program" *ngFor="let program of openPrograms" (click)="openProgram(program); properties.dialog.toggle(selectProgram, false)">
<img src="{{program.icon}}" alt="{{program.id}}">
<span>{{program.name}}</span>
</div>
</form>

View File

@@ -0,0 +1,192 @@
.actions {
width: 100%;
height: 40px;
background-color: var(--colors-first-background);
position: relative;
display: flex;
.action {
width: 40px;
height: 40px;
margin-right: 2px;
}
.directory {
margin: 5px auto;
width: 50%;
}
.search {
margin: 5px 5px 0 auto;
width: 200px;
}
mat-progress-bar {
position: absolute;
top: 100%;
left: 0;
width: 100%;
}
}
.input {
position: relative;
border-radius: 10px;
border: 2px solid var(--colors-scroll);
height: 30px;
mat-icon {
width: 25px;
height: 25px;
line-height: 25px;
text-align: center;
font-size: 20px;
margin: 1px 2.5px 0 5px;
}
input {
position: absolute;
top: 0;
left: 30px;
width: calc(100% - 30px);
height: 100%;
background: none;
border: none;
color: var(--colors-text);
&:focus {
outline: none;
}
}
}
.directory-content {
overflow-y: scroll;
width: 100%;
height: calc(100% - 70px);
user-select: none;
margin-top: 10px;
padding-left: 5px;
.content {
width: 100%;
height: 30px;
display: flex;
border: 2px solid rgba(0, 0, 0, 0);
.icon {
width: 30px;
height: 30px;
line-height: 25px;
font-size: 25px;
text-align: center;
margin-right: 10px;
}
span {
line-height: 30px;
height: 30px;
width: calc(100% - 30px);
}
button {
align-self: center;
justify-self: flex-end;
}
&:hover {
cursor: pointer;
border: 2px solid var(--colors-first-background);
}
}
.selected {
background-color: var(--colors-first-background);
}
}
.directory-information {
width: 100%;
height: 20px;
span {
margin: 0 10px;
}
}
.create-directory mat-form-field {
width: 90%;
margin: 5%;
}
.select-program {
overflow-x: hidden;
overflow-y: scroll;
.program {
margin-left: 10%;
width: 80%;
height: 30px;
margin-bottom: 5px;
border-radius: 5px;
img {
width: 30px;
height: 30px;
margin-right: 2px;
}
span {
height: 30px;
line-height: 30px;
text-align: center;
}
&:hover {
background-color: var(--colors-first-background);
cursor: pointer;
}
}
}
.override {
span {
position: absolute;
width: 100%;
top: 10%;
font-size: 20px;
text-align: center;
}
.buttons {
margin-top: 30%;
}
}
.name-bar {
position: absolute;
bottom: 20px;
left: 0;
width: 100%;
height: 40px;
background-color: var(--colors-first-background);
display: flex;
div {
height: 30px;
margin: 5px;
flex-grow: 1;
}
button {
width: 30px;
height: 30px;
flex-shrink: 0;
margin: 5px;
mat-icon {
position: absolute;
transform: translate(-50%, -50%);
}
}
}

View File

@@ -0,0 +1,411 @@
import {Component, ElementRef, ViewChild} from '@angular/core';
import {Window, WindowProperties} from "../window.component";
import {FileAPI} from "../../../api/fileapi.service";
import {DirectoryContent, DirectoryInformation, FileType} from "../../../entitys/files";
import {HttpDownloadProgressEvent, HttpEventType, HttpUploadProgressEvent} from "@angular/common/http";
import {saveAs} from "file-saver";
import {Desktop, WindowType, windowTypes} from "../../desktop.component";
import Swal from "sweetalert2";
export type HandlerFunction = (url: string) => void;
@Component({
selector: 'app-explorer',
templateUrl: './explorer.component.html',
styleUrls: ['./explorer.component.scss']
})
export class ExplorerWindow implements Window {
public static dragItem: {name: string, url: string, isDirectory: boolean};
@ViewChild('search') search: ElementRef;
@ViewChild('content') content: ElementRef;
@ViewChild('selectProgram') selectProgram: ElementRef;
@ViewChild('override') override: ElementRef;
public properties: WindowProperties;
public currentDirectory: string = "/";
public currentDirectoryInfo: DirectoryContent;
public currentDirectoryInformation: DirectoryInformation;
public currentItem: {name: string, isDirectory: boolean};
public progress: number = 0;
public openPrograms: WindowType[];
public nameBarOptions: {enabled: boolean, value: string, type: string} = {enabled: false, value: "", type: ""};
public constructor(public files: FileAPI) {}
public async onOpen(args: string[]) {
this.properties.title = "Explorer";
this.properties.headerMessage = "Verwalte deine Dateien";
this.properties.icon = "/assets/icons/explorer.png";
this.properties.minSize = {width: 800, height: 600};
await this.loadDirectory();
if (args.includes("--save-file") || args.includes("--choose-file") || args.includes("--choose-folder")) {
this.nameBarOptions.enabled = true;
setTimeout(() => {
this.nameBarOptions.value = this.properties.additionalArgs[0];
this.nameBarOptions.type = this.properties.additionalArgs[2];
this.changeDir(this.properties.additionalArgs[3]);
}, 0);
}
}
public onClose(): boolean { return true; }
humanFileSize(size: number): string {
if (size === 0) return '0 B';
let i = Math.floor( Math.log(size) / Math.log(1024) );
return ( size / Math.pow(1024, i) ).toFixed(2) + ' ' + ['B', 'kB', 'MB', 'GB', 'TB'][i];
};
async loadDirectory() {
this.search.nativeElement.value = "";
this.currentDirectoryInformation = undefined;
this.currentDirectoryInfo = await this.files.getDirectoryContent(this.currentDirectory);
this.currentDirectoryInfoSave = this.currentDirectoryInfo;
for (let i = 0; i < this.currentDirectoryInfo.directories.length; i++) {
if (this.currentDirectoryInfo.directories[i].startsWith("~"))
this.currentDirectoryInfo.directories.splice(i, 1);
}
this.currentDirectoryInformation = await this.files.getDirectoryInfo(this.currentDirectory);
}
async changeDir(directory: string) {
this.currentDirectory = directory;
if (!this.currentDirectory.endsWith("/"))
this.currentDirectory += "/";
await this.loadDirectory();
}
getUpFolder(): string {
let copy = this.currentDirectory;
if (copy.replace("/", "") === "") return "/";
if (copy.endsWith("/")) copy = copy.substr(0, copy.length - 1);
let result = "/";
const folders = copy.split("/");
for (let i = 0; i < folders.length - 1; i++) {
if (folders[i] === "") continue;
result += folders[i] + "/";
}
return result;
}
public currentDirectoryInfoSave: DirectoryContent;
async searchFiles(keyword: string) {
if (keyword === "") {
this.currentDirectoryInfo = this.currentDirectoryInfoSave;
return;
}
const files = [];
for (let file of this.currentDirectoryInfoSave.files) {
if (file.includes(keyword))
files.push(file);
}
const directories = [];
for (let directory of this.currentDirectoryInfoSave.directories) {
if (directory.includes(keyword))
directories.push(directory);
}
this.currentDirectoryInfo = {files: files, directories: directories};
}
selectItem(element: HTMLDivElement, item: string, isDirectory: boolean) {
for (let child of this.content.nativeElement.children)
child.classList.remove("selected");
element?.classList.add("selected");
this.currentItem = {name: item, isDirectory: isDirectory};
if (this.nameBarOptions.enabled) {
if (this.nameBarOptions.type == "choose-folder" && !isDirectory) return;
if (this.nameBarOptions.type != "choose-folder" && isDirectory) return;
this.nameBarOptions.value = item;
}
}
async deleteItem(name: string) {
const confirm = await Swal.fire({
title: 'Möchtest du diese Datei wirklich löschen?',
showDenyButton: true,
showCancelButton: false,
confirmButtonText: 'Löschen',
denyButtonText: 'Abbrechen',
});
if (confirm.isConfirmed) {
await this.files.delete(this.currentDirectory + name);
await this.loadDirectory();
}
}
async createDirectory(name: string) {
await this.files.createDirectory(this.currentDirectory, name);
await this.loadDirectory();
}
async uploadFiles(input: HTMLInputElement) {
const file = input.files[0];
const observer = await this.files.uploadFile(file, this.currentDirectory);
observer.subscribe(async event => {
if (event.type === HttpEventType.UploadProgress) {
const e = event as HttpUploadProgressEvent;
this.progress = e.loaded / e.total * 100;
}
if (event.type === HttpEventType.Response) {
input.value = "";
await this.loadDirectory();
setTimeout(() => this.progress = 0, 2000);
}
});
}
async downloadFile(name: string) {
const observer = await this.files.downloadFile(this.currentDirectory, name);
observer.subscribe(event => {
if (event.type === HttpEventType.DownloadProgress) {
const e = event as HttpDownloadProgressEvent;
this.progress = e.loaded / e.total * 100;
}
if (event.type === HttpEventType.Response) {
saveAs(event.body as Blob, name);
setTimeout(() => this.progress = 0, 2000);
}
})
}
onDrag(item: string, isDirectory: boolean) {
ExplorerWindow.dragItem = {name: item, url: this.currentDirectory + item, isDirectory: isDirectory};
}
async onDrop(event: DragEvent, directory: string) {
await this.onDropUp(event, this.currentDirectory + directory);
}
onDragOver(event: DragEvent, directory: string) {
if (directory != ExplorerWindow.dragItem.name)
event.preventDefault();
}
onDragUpOver(event: DragEvent) {
if (this.currentDirectory == "/") return;
event.preventDefault();
}
async onDropUp(event: DragEvent, directory: string) {
event.preventDefault();
if (ExplorerWindow.dragItem.isDirectory) {
await this.files.moveDirectory(this.currentDirectory, ExplorerWindow.dragItem.name, directory);
}else {
await this.files.moveFile(this.currentDirectory, ExplorerWindow.dragItem.name, directory);
}
delete ExplorerWindow.dragItem;
await this.loadDirectory();
}
getFileImage(name: string): string {
const type = ExplorerWindow.getFileType(name);
//IMAGES
if (type == FileType.IMAGE)
return "image";
//VIDEO
if (type == FileType.VIDEO)
return "video_file";
//AUDIO
if (type == FileType.AUDIO)
return "audio_file";
//COMPRESSED
if (type == FileType.COMPRESSED)
return "folder_zip";
//DISK
if (type == FileType.DISK)
return "album";
//DATABASE
if (type == FileType.DATABASE)
return "storage";
//DATA
if (type == FileType.DATA)
return "description";
//EMAIL
if (type == FileType.EMAIL)
return "email";
//EXECUTABLE
if (type == FileType.EXECUTABLE)
return "terminal";
//FONTS
if (type == FileType.FONT)
return "segment";
//INTERNET
if (type == FileType.INTERNET)
return "language";
//PRESENTATION
if (type == FileType.PRESENTATION)
return "co_present";
//SPREADSHEET
if (type == FileType.SPREADSHEET)
return "view_week";
//SYSTEM
if (type == FileType.SYSTEM)
return "settings";
//WORD
if (type == FileType.WORD)
return "description";
return "insert_drive_file";
}
openWith(name: string) {
const type = ExplorerWindow.getFileType(name);
const programs = windowTypes.filter(wType => wType.canOpen?.includes(type));
if (programs.length != 0) {
if (programs.length == 1) {
this.currentItem = {name: name, isDirectory: false};
this.openProgram(programs[0]);
}else {
this.openPrograms = programs;
this.properties.dialog.toggle(this.selectProgram.nativeElement, true);
}
}
}
openProgram(program: WindowType) {
Desktop.instance.openWindow(program.id, ["--file=" + this.currentDirectory + this.currentItem.name]);
}
onSave(url: string, checked: boolean = false) {
if (
(this.currentDirectoryInfoSave.files.includes(url.replace(this.currentDirectory, "")) ||
this.currentDirectoryInfoSave.directories.includes(url.replace(this.currentDirectory, "")))
&& !checked && this.nameBarOptions.type == "save-file") {
this.properties.dialog.toggle(this.override.nativeElement, true);
return;
}
this.properties.windowRef.close();
this.properties.additionalArgs[1](url);
}
public static getFileType(name: string): FileType {
const includes = (extensions: string[]) => {
let found: boolean = false;
extensions.forEach(ext => {
if (ext === extension)
found = true;
})
return found;
}
const extension = (() => {
const split = name.split(".");
return split[split.length - 1].toLowerCase();
})();
//IMAGES
if (includes(["png", "jpg", "gif", "webp", "tiff", "tif", "ps", "psd", "raw", "bmp", "heif", "indd", "jpeg", "svg", "ai", "eps", "ico"]))
return FileType.IMAGE;
//VIDEO
if (includes(["3g2", "3gp", "avi", "flv", "h264", "m4v", "mkv", "mov", "mp4", "mpg", "mpeg", "rm", "swf", "vob", "wmv"]))
return FileType.VIDEO;
//AUDIO
if (includes(["aif", "cda", "mid", "midi", "mp3", "mpa", "ogg", "wav", "wma", "wpl"]))
return FileType.AUDIO;
//COMPRESSED
if (includes(["7z", "arj", "deb", "pkg", "rar", "rpm", "gz", "z", "zip"]))
return FileType.COMPRESSED;
//DISK
if (includes(["bin", "dmg", "iso", "toast", "vcd"]))
return FileType.DISK;
//DATABASE
if (includes(["db", "dbf", "mdb", "sql"]))
return FileType.DATABASE;
//DATA
if (includes(["c", "cgi", "pl", "class", "cpp", "cs", "h", "java", "php", "py", "sh", "swift", "vb", "xml", "json", "js", "jsx", "ts", "tsx", "md", "txt"]))
return FileType.DATA;
//EMAIL
if (includes(["email", "eml", "emlx", "msg", "oft", "ost", "pst", "vcf"]))
return FileType.EMAIL;
//EXECUTABLE
if (includes(["apk", "bat", "bin", "com", "exe", "gadget", "jar", "msi", "wsf"]))
return FileType.EXECUTABLE;
//FONTS
if (includes(["fnt", "fon", "otf", "ttf"]))
return FileType.FONT;
//INTERNET
if (includes(["asp", "aspx", "cer", "cfm", "css", "htm", "html", "js", "jsp", "part", "rss", "xhtml"]))
return FileType.INTERNET;
//PRESENTATION
if (includes(["key", "odp", "pps", "ppt", "pptx"]))
return FileType.PRESENTATION;
//SPREADSHEET
if (includes(["ods", "xls", "xlsm", "xlsx"]))
return FileType.SPREADSHEET;
//SYSTEM
if (includes(["bak", "cab", "cfg", "cpl", "cur", "dll", "bmp", "drv", "icns", "ini", "lnk", "msi", "sys", "tmp"]))
return FileType.SYSTEM;
//WORD
if (includes(["doc", "docs", "odt", "pdf", "rtf", "tex", "wpd"]))
return FileType.WORD;
return FileType.FILE;
}
public static extractFileAndFolderFromURL(url: string): {file: string, directory: string, ext: string} {
const split = url.split("/");
const file = split[split.length - 1];
const directory = url.replace(file, "");
const fileSplit = file.split(".");
return {file, directory, ext: fileSplit[fileSplit.length - 1]};
}
public static async openPopup(type: "choose-file" | "choose-folder" | "save-file", onSelected: HandlerFunction, defaultFileName: string, defaultDirectory: string = "/") {
const window = await Desktop.instance.openWindow("explorer", ["--" + type]);
window.addAdditionalArgs(defaultFileName, onSelected, type, defaultDirectory);
}
}

View File

@@ -0,0 +1,18 @@
<section class="search unselectable" #container
(keyup.escape)="toggleSearch(false)"
(mouseenter)="mouseOver = true" (mouseleave)="mouseOver = false"
(keydown.arrowUp)="moveDown()" (keydown.arrowDown)="moveUp()">
<span class="title">Alle Apps</span>
<input type="text" placeholder="Suche" (keyup)="search(searchInput.value, $event.code)" (keyup.enter)="openProgram(programs[selected === -1 ? 0 : selected].id)" #searchInput>
<div *ngFor="let window of programs" class="program" [ngStyle]="{'background-color': programs.indexOf(window) === selected ? 'var(--colors-second-background)' : 'transparent'}">
<div (click)="openProgram(window.id)" class="info">
<img src="{{window.icon}}" alt="window-icon" draggable="false">
<span>{{window.name}}</span>
</div>
<div class="action-buttons">
<button mat-icon-button (click)="togglePin(window)" [ngStyle]="{'color': isPinned(window) ? 'var(--colors-primary)' : 'var(--colors-text)'}"><mat-icon>push_pin</mat-icon></button>
</div>
</div>
</section>

View File

@@ -0,0 +1,95 @@
.search {
top: 100%;
left: calc(var(--taskbar-left) + 20px);
position: absolute;
height: 40%;
aspect-ratio: 7 / 10;
background-color: var(--colors-first-background);
transition: transform 200ms ease;
z-index: 7;
overflow-y: scroll;
overflow-x: hidden;
&.show {
transform: translateY(calc(-100% - 60px));
}
.title {
margin-top: 5px;
padding-bottom: 5px;
display: block;
width: 100%;
text-align: center;
color: var(--colors-text);
font-size: 20px;
}
input {
margin-top: 2px;
width: 100%;
background: none;
transition: 200ms ease;
color: var(--colors-text);
border: 2px solid transparent;
border-top-color: var(--colors-second-background);
border-radius: 2px;
&:focus {
outline: none;
border-color: var(--colors-primary);
}
}
.program {
margin: 3px auto;
width: 95%;
height: 30px;
position: relative;
user-select: none;
transition: background-color 100ms ease;
.info {
height: 100%;
width: max-content;
img {
height: 100%;
width: auto;
margin-right: 5px;
}
span {
color: var(--colors-text);
height: 100%;
line-height: 100%;
}
&:hover {
cursor: pointer;
}
}
.action-buttons {
position: absolute;
height: 100%;
width: max-content;
top: 0;
left: 100%;
transform: translateX(-110%);
button {
height: 100%;
width: auto;
aspect-ratio: 1/1;
color: var(--colors-text);
mat-icon {
width: 100%;
height: 100%;
line-height: 100%;
text-align: center;
}
}
}
}
}

View File

@@ -0,0 +1,109 @@
import {Component, ElementRef, OnInit, Type, ViewChild} from '@angular/core';
import {Desktop, WindowType, windowTypes} from "../../desktop.component";
import {UserAPI} from "../../../api/userapi.service";
@Component({
selector: 'app-search',
templateUrl: './search.component.html',
styleUrls: ['./search.component.scss']
})
export class SearchComponent implements OnInit {
@ViewChild('container') container: ElementRef;
@ViewChild('searchInput') searchInput: ElementRef;
programs: WindowType[];
permissions: string[];
mouseOver: boolean = false;
selected: number = -1;
constructor(private users: UserAPI) { }
ngOnInit() {
setTimeout( async () => {
this.searchInput.nativeElement.value = "";
this.permissions = await this.users.getOwnPermissions();
this.programs = windowTypes.filter(type => this.hasPermission(type.permission)).sort((n1, n2) => n1.name < n2.name ? -1 : 1);
}, 0);
}
toggleSearch(toggle?: boolean) {
const open = this.container.nativeElement.classList.toggle("show", toggle);
if (open) {
this.ngOnInit();
setTimeout(() => this.searchInput.nativeElement.focus(), 201);
}
const focusBar = document.querySelector("#search-opened") as HTMLElement;
focusBar.style.opacity = open ? "100" : "0";
}
playClickAnimation() {
const container = document.querySelector("#search-icon").children.item(0) as HTMLElement;
container.classList.add("click");
setTimeout(() => container.classList.remove("click"), 100);
}
search(key: string, keyCode: string) {
if (keyCode !== "ArrowDown" && keyCode !== "ArrowUp" && keyCode !== "Enter")
this.selected = -1;
if (key == "") {
this.programs = windowTypes.filter(type => this.hasPermission(type.permission)).sort((n1, n2) => n1.name < n2.name ? -1 : 1);
return;
}
this.programs = windowTypes.filter(type =>
this.hasPermission(type.permission) &&
(type.name.toLowerCase().includes(key.toLowerCase()) || type.id.toLowerCase().includes(key.toLowerCase()))
).sort((n1, n2) => n1.name < n2.name ? -1 : 1);
}
hasPermission(permission: string): boolean {
if (permission == undefined) return true;
if (this.permissions.includes("*") || this.permissions.includes(permission))
return true;
const splice = permission.split(".");
let builder: string = "";
for (let pp of splice) {
builder += pp + ".";
if (this.permissions.includes(builder + "*"))
return true;
}
return false;
}
openProgram(id: string) {
Desktop.instance.openWindow(id);
this.toggleSearch();
}
togglePin(type: WindowType) {
const hasIcon = this.isPinned(type);
if (hasIcon)
Desktop.instance.removeTaskbarIcon(type.id);
else
Desktop.instance.addTaskbarIcon(type);
}
isPinned(type: WindowType): boolean {
return Desktop.pinnedIcons.filter(icon => icon.type.id === type.id && icon.instance.tagName === "APP-TASKBAR-ICON").length != 0;
}
moveUp() {
this.selected++;
if (this.selected >= this.programs.length)
this.selected = 0;
}
moveDown() {
this.selected--;
if (this.selected < 0)
this.selected = this.programs.length - 1;
}
}

View File

@@ -0,0 +1,156 @@
<mat-accordion class="settings">
<mat-expansion-panel hideToggle class="infos" (expandedChange)="defaultEditState()">
<mat-expansion-panel-header class="unselectable">
<mat-panel-title>Account Informationen</mat-panel-title>
<mat-panel-description>
{{userInfoDesc}}
<mat-icon>account_circle</mat-icon>
</mat-panel-description>
</mat-expansion-panel-header>
<div class="user-info" #userInfo>
<div class="row info">
<div class="col-sm label unselectable">Identifikationsnummer:</div>
<div class="col-sm value">{{user?.id}}</div>
</div>
<div class="row info">
<div class="col-sm label unselectable">Name:</div>
<div class="col-sm value">{{user?.firstName}} {{user?.lastName}}</div>
</div>
<div class="row info">
<div class="col-sm label unselectable">Email:</div>
<div class="col-sm value">{{user?.email}}</div>
</div>
<div class="row info">
<div class="col-sm label unselectable">Benutzername:</div>
<div class="col-sm value">{{user?.username}}</div>
</div>
<div class="row info">
<div class="col-sm label unselectable">Beigetreten am:</div>
<div class="col-sm value">{{user?.created.toLocaleString()}}</div>
</div>
</div>
<div class="user-editor hidden" #userEditor>
<form>
<table>
<tr>
<td>
<mat-form-field class="example-full-width" appearance="fill">
<mat-label>Vorname</mat-label>
<input matInput id="firstname">
</mat-form-field>
</td>
<td>
<mat-form-field class="example-full-width" appearance="fill">
<mat-label>Nachname</mat-label>
<input matInput id="lastname">
</mat-form-field>
</td>
</tr>
</table>
<mat-form-field appearance="fill">
<mat-label>Benutzername</mat-label>
<input matInput id="username">
</mat-form-field>
<br>
<mat-form-field appearance="fill">
<mat-label>Email Adresse</mat-label>
<input type="email" matInput id="email">
</mat-form-field>
<table>
<tr>
<td>
<mat-form-field class="example-full-width" appearance="fill">
<mat-label>Passwort</mat-label>
<input matInput id="password">
</mat-form-field>
</td>
<td>
<mat-form-field class="example-full-width" appearance="fill">
<mat-label>Passwort wiederholen</mat-label>
<input matInput id="passwordRepeat">
</mat-form-field>
</td>
</tr>
</table>
</form>
</div>
<mat-divider></mat-divider>
<div class="buttons-right">
<button mat-mini-fab color="primary" aria-label="Edit Userdata" (click)="toggleUserEditor()" class="toggle-edit">
<mat-icon>edit</mat-icon>
</button>
<button mat-mini-fab color="warn" aria-label="Edit Userdata" (click)="deleteUser()" class="toggle-edit">
<mat-icon>delete</mat-icon>
</button>
<button mat-mini-fab color="warn" aria-label="Edit Userdata" (click)="logout()" class="toggle-edit">
<mat-icon>exit_to_app</mat-icon>
</button>
</div>
</mat-expansion-panel>
<mat-expansion-panel hideToggle class="infos" (expandedChange)="defaultEditState()">
<mat-expansion-panel-header class="unselectable">
<mat-panel-title>Personalisierung</mat-panel-title>
<mat-panel-description>
Gestalte deinen Desktop
<mat-icon>brush</mat-icon>
</mat-panel-description>
</mat-expansion-panel-header>
<div class="personalization">
<div class="row">
<div class="col-sm label unselectable">Theme</div>
<div class="col-sm">
<mat-select [(value)]="currentTheme" (valueChange)="setTheme()">
<mat-option *ngFor="let theme of themes" [value]="theme.key">{{theme.name}}</mat-option>
</mat-select>
</div>
</div>
<div class="row">
<div class="col-sm label unselectable">Taskleiste</div>
<div class="col-sm">
<mat-select [(value)]="taskbar" (valueChange)="setTaskbar()">
<mat-option [value]='"small"'>Klein</mat-option>
<mat-option [value]='"big"'>Groß</mat-option>
</mat-select>
</div>
</div>
<div class="row">
<div class="col-sm label unselectable">Taskleisten Icons</div>
<div class="col-sm">
<mat-select [(value)]="taskbar_icons" (valueChange)="setTaskbarIcons()">
<mat-option [value]='"center"'>Mittig</mat-option>
<mat-option [value]='"left"'>Links</mat-option>
<mat-option [value]='"right"'>Rechts</mat-option>
</mat-select>
</div>
</div>
<div class="row">
<div class="col-sm label unselectable">Hintergrund</div>
<div class="col-sm">
<mat-select [(value)]="background" (valueChange)="setBackground()">
<mat-option [value]='"assets/images/moon.jpg"'>Mond</mat-option>
<mat-option [value]='"assets/images/sea.png"'>See</mat-option>
</mat-select>
</div>
</div>
</div>
</mat-expansion-panel>
</mat-accordion>

View File

@@ -0,0 +1,57 @@
@import "/src/variables.scss";
.mat-expansion-panel-header-title,
.mat-expansion-panel-header-description {
flex-basis: 0;
}
.mat-expansion-panel-header-description {
justify-content: space-between;
align-items: center;
}
.mat-form-field + .mat-form-field {
margin-left: 8px;
}
.mat-expansion-panel {
background: var(--colors-first-background);
}
.infos {
color: var(--colors-text);
.info {
background: none;
margin-bottom: 5px;
}
mat-divider {
background-color: var(--colors-text);
}
}
.user-editor {
width: 100%;
table {
width: 100%;
}
mat-form-field {
width: 100%;
}
}
.personalization {
width: 100%;
height: max-content;
mat-select {
width: 30%;
}
.row {
height: 30px;
}
}

View File

@@ -0,0 +1,181 @@
import {Component, ElementRef, ViewChild} from '@angular/core';
import { Window, WindowProperties } from '../window.component';
import {UserAPI} from "../../../api/userapi.service";
import {User, UserEditor} from "../../../entitys/user";
import Swal from "sweetalert2";
import {Router} from "@angular/router";
import {defaultPersonalization, Personalization} from "../../../entitys/settings";
import {SettingsService} from "../../../api/settings.service";
import {Desktop} from "../../desktop.component";
import {Notifications} from "../../components/notifications/notifications.component";
@Component({
selector: 'window-settings',
templateUrl: './settings.component.html',
styleUrls: ['./settings.component.scss']
})
export class SettingsWindow implements Window {
@ViewChild('userEditor') userEditor: ElementRef;
@ViewChild('userInfo') userInfo: ElementRef;
constructor(public userApi: UserAPI, public router: Router, private settings: SettingsService) { }
properties: WindowProperties;
user: User;
userInfoDesc: string = "Siehe deine Accountinformationen ein";
themes: {name: string, key: string}[] = [
{name: "Dunkel", key: "theme-dark"},
{name: "Hell", key: "theme-light"}
];
lastTheme: string;
currentTheme: string;
taskbar: string;
taskbar_icons: string;
background: string;
async onOpen() {
this.properties.icon = "/assets/icons/settings.png"
this.properties.title = "Einstellungen";
this.properties.minSize = {width: 800, height: 600};
this.user = await this.userApi.getCurrentUser();
this.currentTheme = document.body.classList.item(1);
this.lastTheme = this.currentTheme;
const personalization = this.settings.getSetting<Personalization>("settings.personalization", defaultPersonalization);
this.taskbar = personalization.taskbar;
this.background = personalization.background;
this.taskbar_icons = personalization.taskbar_icons;
}
onClose(): boolean { return true; }
//USER EDIT
toggleUserEditor(): void {
this.userInfo.nativeElement.classList.toggle("hidden");
this.userEditor.nativeElement.classList.toggle("hidden");
if (this.userInfo.nativeElement.classList.contains("hidden"))
this.userInfoDesc = "Bearbeite nur das, was verändert werden soll!";
else
this.userInfoDesc = "Siehe deine Accountinformationen ein";
}
cancelEdit(event: MouseEvent): void {
event.preventDefault();
this.toggleUserEditor();
}
defaultEditState(): void {
this.userInfo.nativeElement.classList.toggle("hidden", false);
this.userEditor.nativeElement.classList.toggle("hidden", true);
this.userInfoDesc = "Siehe deine Accountinformationen ein";
}
async onEdit(event: MouseEvent) {
event.preventDefault();
const form = this.userEditor.nativeElement.children[0] as HTMLFormElement;
if (form.reportValidity()) {
const firstName = form.querySelector("#firstname") as HTMLInputElement;
const lastName = form.querySelector("#lastname") as HTMLInputElement;
const username = form.querySelector("#username") as HTMLInputElement;
const email = form.querySelector("#email") as HTMLInputElement;
const password = form.querySelector("#password") as HTMLInputElement;
const passwordRepeat = form.querySelector("#passwordRepeat") as HTMLInputElement;
if (passwordRepeat.value != password.value) {
await Swal.fire({
icon: 'error',
title: 'Bearbeitung fehlgeschlagen',
text: 'Die Passwörter stimmen nicht überein',
timer: 2000,
showConfirmButton: false
});
return
}
const edit: UserEditor = {
firstName: firstName.value,
lastName: lastName.value,
username: username.value,
email: email.value,
password: password.value
}
const success = await this.userApi.editOwnUser(edit);
if (success) {
await Swal.fire({
icon: 'success',
title: 'Accountdaten Bearbeitet',
text: 'Du hast deine Accountdaten erfolgreich bearbeitet',
timer: 2000,
showConfirmButton: false
});
this.user = await this.userApi.getCurrentUser(true);
this.cancelEdit(event);
}else {
await Swal.fire({
icon: 'error',
title: 'Bearbeitung fehlgeschlagen',
text: 'Die Bearbeitung der Accountdaten ist fehlgeschlagen',
timer: 2000,
showConfirmButton: false
});
}
}
}
async deleteUser() {
const result = await Swal.fire({
title: 'Möchtest du deinen Account wirklich löschen?',
showDenyButton: false,
showCancelButton: true,
confirmButtonText: 'Ja',
});
if (result.isConfirmed) {
await this.userApi.deleteOwnUser();
await this.router.navigate(["/register"]);
}
}
async logout() {
await this.userApi.logout();
await this.router.navigate(["/login"]);
}
//PERSONALIZATION
setTheme() {
document.body.classList.remove(this.lastTheme);
document.body.classList.add(this.currentTheme);
this.lastTheme = this.currentTheme;
//localStorage.setItem("theme", this.currentTheme);
const personalization = this.settings.getSetting<Personalization>("settings.personalization", defaultPersonalization);
personalization.theme = this.currentTheme;
this.settings.setSetting("settings.personalization", personalization);
}
setTaskbar() {
const taskbar = document.querySelector(".taskbar") as HTMLElement;
taskbar.classList.toggle("wide", this.taskbar == "big");
const personalization = this.settings.getSetting<Personalization>("settings.personalization", defaultPersonalization);
personalization.taskbar = this.taskbar;
this.settings.setSetting("settings.personalization", personalization);
document.body.style.setProperty("--taskbar-left", !taskbar.classList.contains("wide") ? "15%" : "0px");
}
setTaskbarIcons() {
const icons = document.querySelector(".taskbar-icons") as HTMLElement;
const personalization = this.settings.getSetting<Personalization>("settings.personalization", defaultPersonalization);
personalization.taskbar_icons = this.taskbar_icons;
this.settings.setSetting("settings.personalization", personalization);
icons.style.marginLeft = "0";
icons.style.marginRight = "0";
if (this.taskbar_icons == "left") icons.style.marginRight = "auto";
if (this.taskbar_icons == "right") icons.style.marginLeft = "auto";
}
setBackground() {
document.body.style.setProperty("--background", "url(" + this.background + ")");
//localStorage.setItem("background", this.background);
const personalization = this.settings.getSetting<Personalization>("settings.personalization", defaultPersonalization);
personalization.background = this.background;
this.settings.setSetting("settings.personalization", personalization);
}
}

View File

@@ -0,0 +1,36 @@
<section id="{{windowId}}" class="window-container"
(mouseenter)="mouseEnter()"
(mouseleave)="mouseLeave()"
(click)="desktopRef.requestFocus(this)">
<header class="window-header unselectable"
(mousedown)="windowDragStart($event)"
(mouseup)="windowDragStop()">
<img src="{{contentRef?.properties.icon}}" alt="logo" class="window-logo" draggable="false">
<span class="window-title">{{contentRef?.properties.title}}</span>
<span class="window-message">{{contentRef?.properties.headerMessage}}</span>
<div class="header-buttons">
<div class="header-button" *ngIf="contentRef?.properties.headerActions.length > 0" (click)="actions.classList.toggle('visible')">
<mat-icon class="icon">expand_more</mat-icon>
<div class="header-actions" #actions (mouseleave)="actions.classList.remove('visible')">
<div class="action" *ngFor="let action of contentRef?.properties.headerActions" (click)="action.click($event)">
<mat-icon>{{action.icon}}</mat-icon>
<span>{{action.name}}</span>
</div>
</div>
</div>
<div class="header-button" (click)="minimize()">
<mat-icon class="icon minimize">minimize</mat-icon>
</div>
<div class="header-button" (click)="toggleMaximize()">
<mat-icon class="icon">{{resizeIcon}}</mat-icon>
</div>
<div class="header-button" (click)="close()">
<mat-icon class="icon">close</mat-icon>
</div>
</div>
</header>
<section class="content">
<meta #content>
</section>
</section>

View File

@@ -0,0 +1,140 @@
@import "/src/variables.scss";
.window-container {
--focused: 5;
--opened: 4;
--selected-shadow: 5px 5px 5px rgba(0, 0, 0, 0.2);
width: 100%;
height: 100%;
border: 2px solid var(--window-border-color);
border-radius: 10px;
background-color: var(--colors-second-background);
overflow: hidden;
position: relative;
z-index: var(--opened);
box-shadow: none;
color: var(--colors-text);
}
.window-header {
width: 100%;
height: 30px;
background-color: var(--colors-second-background);
z-index: 3;
.window-logo {
width: 20px;
height: 20px;
display: inline-block;
position: absolute;
top: 5px;
left: 5px;
}
.window-title {
color: var(--colors-text);
position: absolute;
top: 5px;
left: 30px;
}
.window-message {
color: var(--colors-text);
position: absolute;
left: 50%;
top: 5px;
transform: translate(-50%, 0);
}
.header-buttons {
display: inline-block;
position: absolute;
left: 100%;
height: 30px;
transform: translate(-105%, 0);
width: max-content;
.header-button {
width: 26px;
height: 26px;
border-radius: 13px;
transition: background-color 150ms;
margin: 2px;
color: var(--colors-text);
line-height: 26px;
text-align: center;
display: inline-block;
position: relative;
&:hover {
background-color: rgba(255, 255, 255, 0.2);
}
.icon {
position: absolute;
transform: translate(-50%, 5%);
}
.minimize {
position: absolute;
top: -25%;
transform: translateX(-50%);
}
}
.header-actions {
position: absolute;
top: 120%;
display: none;
background-color: var(--colors-second-background);
width: max-content;
height: max-content;
transform: translateX(-100%);
left: 100%;
pointer-events: none;
.action {
width: 100%;
height: 30px;
display: flex;
margin: 5px 0;
border-radius: 4px;
mat-icon {
height: 100%;
width: 30px;
line-height: 30px;
text-align: center;
color: var(--colors-text);
margin-right: 5px;
}
span {
line-height: 30px;
margin-right: 5px;
text-align: center;
}
&:hover {
background-color: var(--colors-first-background);
}
}
&.visible {
display: block;
pointer-events: auto;
}
}
}
}
.content {
position: relative;
top: 0;
left: 0;
width: 100%;
height: calc(100% - 30px);
overflow: hidden;
z-index: -1;
}

View File

@@ -0,0 +1,364 @@
import {ChangeDetectorRef, Component, ElementRef, Type, ViewChild, ViewContainerRef} from '@angular/core';
import {Desktop, WindowType} from "../desktop.component";
export interface WindowProperties {
title: string;
headerMessage: string;
icon: string;
resizable: boolean;
size: {width: number, height: number};
minSize: {width: number, height: number};
position: {x: number, y: number};
htmlWindowRef: HTMLElement;
htmlContentRef: HTMLElement;
windowRef: WindowRoot;
headerActions: HeaderAction[];
windowType: WindowType;
dialog: {open: boolean, toggle(element: HTMLElement, toggle?: boolean): void};
additionalArgs: any[];
setMaximized(value: boolean): void;
setSize(width: number, height: number): void;
setPosition(x: number, y: number): void;
updateWindow(): void;
getWindow(): HTMLElement;
addEventListener(listener: WindowEvents): void;
requestFocus(): void;
}
export interface Window {
properties: WindowProperties;
onOpen(args: string[]): void;
onClose(): boolean;
}
export interface WindowEvents {
onClick(event: MouseEvent): void;
onMouseDown(event: MouseEvent): void;
onMouseUp(event: MouseEvent): void;
onMouseMove(event: MouseEvent): void;
}
export interface HeaderAction {
icon: string;
name: string;
click(event: MouseEvent): void;
}
@Component({
selector: 'app-window',
templateUrl: './window.component.html',
styleUrls: ['./window.component.scss']
})
export class WindowRoot implements WindowEvents {
@ViewChild('content', {read: ViewContainerRef}) content: ViewContainerRef;
resizeIcon: string = "fullscreen";
public contentType: WindowType;
public object: HTMLElement;
public contentRef: Window;
public desktopRef: Desktop;
public windowId: number;
public isMinimized: boolean = false;
public focusable: boolean = true;
public windowArgs: string[];
private hover: boolean = false;
private windowEventHandler: WindowEvents[] = [];
public get windowType(): WindowType { return this.contentRef?.properties.windowType };
public get id(): string { return this.windowType.id; }
constructor(object: ElementRef, private cdr: ChangeDetectorRef) {
this.object = object.nativeElement;
this.object.style.visibility = "hidden";
}
async onOpen() {
const element = this.content.createComponent(this.contentType.type);
this.cdr.detectChanges();
this.contentRef = element.instance;
this.object.style.position = "absolute";
const offset = this.desktopRef.getWindowReferences(this.contentType.id).length * 10;
this.contentRef.properties = {
title: '',
headerMessage: '',
icon: '/assets/images/logo.png',
resizable: true,
size: {width: 800, height: 600},
minSize: {width: 400, height: 300},
position: {x: 200 + offset, y: 200 + offset},
htmlContentRef: this.object.children.item(0).children.item(1) as HTMLElement,
windowRef: this,
headerActions: [],
windowType: this.contentType,
dialog: {open: false, toggle: this.toggleDialog.bind(this)},
additionalArgs: [],
setMaximized: this.setMaximized.bind(this),
setSize: this.setSize.bind(this),
setPosition: this.setPosition.bind(this),
htmlWindowRef: this.object,
updateWindow: this.updateWindow.bind(this),
getWindow: this.getWindow.bind(this),
addEventListener: this.addEventListener.bind(this),
requestFocus: this.requestFocus.bind(this)
};
this.object.style.width = this.contentRef.properties.size.width + 'px';
this.object.style.height = this.contentRef.properties.size.height + 'px';
this.object.style.top = this.contentRef.properties.position.y + 'px';
this.object.style.left = this.contentRef.properties.position.x + 'px';
await this.contentRef.onOpen(this.windowArgs);
this.object.style.visibility = "visible";
}
private setMaximized(value: boolean): void {
this.maximized = !value;
this.toggleMaximize();
}
private setSize(width: number, height: number): void {
this.contentRef.properties.size.width = width;
this.contentRef.properties.size.height = height;
this.object.style.width = (this.contentRef.properties.size.width + 4) + "px";
this.object.style.height = (this.contentRef.properties.size.height + 34) + "px";
}
private setPosition(x: number, y: number): void {
this.contentRef.properties.position.x = x;
this.contentRef.properties.position.y = y;
this.object.style.top = this.contentRef.properties.position.x + 'px';
this.object.style.left = this.contentRef.properties.position.y + 'px';
}
private updateWindow(): void {
this.object.style.width = (this.contentRef.properties.size.width + 4) + "px";
this.object.style.height = (this.contentRef.properties.size.height + 34) + "px";
this.object.style.top = this.contentRef.properties.position.x + 'px';
this.object.style.left = this.contentRef.properties.position.y + 'px';
}
private getWindow(): HTMLElement {
return this.object.children.item(0) as HTMLElement;
}
private addEventListener(listener: WindowEvents): void {
this.windowEventHandler.push(listener);
}
private toggleDialog(element: HTMLElement, toggle?: boolean) {
this.contentRef.properties.dialog.open = element.classList.toggle("visible", toggle);
}
private requestFocus() {
Desktop.instance.requestFocus(this);
}
public addAdditionalArgs(...args: any[]) {
for (let i = 0; i < args.length; i++) {
this.contentRef.properties.additionalArgs.push(args[i]);
}
}
// WINDOW FUNCTIONS
public mouseEnter(): void { this.hover = true; }
public mouseLeave(): void { this.hover = false; }
public get isMouseOver(): boolean { return this.hover; }
private offsetX: number;
private offsetY: number;
private drag: boolean;
private tempSize: {width: number, height: number};
private windowDrag(event: MouseEvent): void {
if (!this.drag || this.isResizing) 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.maximized) return;
this.offsetX = event.clientX - this.object.offsetLeft;
this.offsetY = event.clientY - this.object.offsetTop;
this.drag = true;
this.object.classList.add("unselectable");
}
public windowDragStop(): void {
if (!this.drag) return;
this.drag = false;
this.contentRef.properties.position.x = this.object.offsetLeft;
this.contentRef.properties.position.y = this.object.offsetTop;
this.object.classList.remove("unselectable");
}
private maximized: boolean = false;
public toggleMaximize(): void {
if (!this.contentRef.properties.resizable) return;
this.maximized = !this.maximized;
this.object.style.transition = 'all 300ms';
if (this.maximized) {
this.tempSize = {width: this.contentRef.properties.size.width, height: this.contentRef.properties.size.height};
this.contentRef.properties.position.x = this.object.offsetLeft;
this.contentRef.properties.position.y = this.object.offsetTop;
this.object.style.width = "100%";
this.object.style.height = "calc(100% - 50px)";
this.object.style.top = '0';
this.object.style.left = '0';
this.contentRef.properties.size.width = this.object.clientWidth;
this.contentRef.properties.size.height = this.object.clientHeight;
//img.src = "/assets/icons/window/minimize.png";
this.resizeIcon = "fullscreen_exit";
}else {
this.setSize(this.tempSize.width, this.tempSize.height);
this.object.style.top = this.contentRef.properties.position.y + 'px';
this.object.style.left = this.contentRef.properties.position.x + 'px';
//img.src = "/assets/icons/window/maximize.png";
this.resizeIcon = "fullscreen";
}
setTimeout(() => this.object.style.transition = 'none', 310);
}
public minimize(): void {
this.focusable = false;
this.object.style.opacity = "0";
this.isMinimized = true;
setTimeout(() => this.desktopRef.requestFocus(undefined), 0);
}
public close(): void {
this.focusable = false;
this.desktopRef.closeWindow(this.windowId);
}
private resizeOrigin: {x: number, y: number, width: number, height: number};
private isResizing: boolean = false;
private readonly resizingArea: number = 10;
private lastResizeManager: string;
private handleResizeCursor(event: MouseEvent) {
if (this.desktopRef.currentlyFocused !== this) return;
if (this.maximized) return;
if (!this.contentRef.properties.resizable) return;
const window = this.getWindow();
const c = this.isHoverBorder(event);
if(c) {
window.style.cursor = c + "-resize";
this.lastResizeManager = c;
}
else
window.style.cursor = "auto";
if (this.isResizing) this.handleResizing(event);
}
private startResizing(event: MouseEvent): void {
if (this.desktopRef.currentlyFocused !== this) return;
if (this.maximized) return;
if (!this.isHoverBorder(event)) return;
this.isResizing = true;
this.resizeOrigin = {
x: this.object.offsetLeft,
y: this.object.offsetTop,
width: this.object.offsetWidth,
height: this.object.offsetHeight
};
this.object.classList.add("unselectable");
}
private stopResizing(): void {
if (!this.isResizing) return;
this.isResizing = false;
this.contentRef.properties.position = {x: this.resizeOrigin.x, y: this.resizeOrigin.y};
this.contentRef.properties.size = {width: this.resizeOrigin.width, height: this.resizeOrigin.height};
delete this.resizeOrigin;
this.object.classList.remove("unselectable");
}
private handleResizing(event: MouseEvent) {
const window = this.getWindow().getBoundingClientRect();
const threshold = 20;
if (this.lastResizeManager.includes("n") && event.clientY <= window.y + threshold) { //TOP
this.object.style.top = (this.resizeOrigin.y + event.movementY) + "px";
this.object.style.height = (this.resizeOrigin.height - event.movementY) + "px";
}else if (this.lastResizeManager.includes("s") && event.clientY >= window.bottom - threshold) { //BOTTOM
this.object.style.height = (this.resizeOrigin.height + event.movementY) + "px";
}if (this.lastResizeManager.includes("w") && event.clientX <= window.x + threshold) { //LEFT
this.object.style.left = (this.resizeOrigin.x + event.movementX) + "px";
this.object.style.width = (this.resizeOrigin.width - event.movementX) + "px";
}else if (this.lastResizeManager.includes("e") && event.clientX >= window.right - threshold) { //RIGHT
this.object.style.width = (this.resizeOrigin.width + event.movementX) + "px";
}
if (this.object.offsetWidth < this.contentRef.properties.minSize.width)
this.object.style.width = this.contentRef.properties.minSize.width + "px";
if (this.object.offsetHeight < this.contentRef.properties.minSize.height)
this.object.style.height = this.contentRef.properties.minSize.height + "px";
this.resizeOrigin = {
x: this.object.offsetLeft,
y: this.object.offsetTop,
width: this.object.offsetWidth,
height: this.object.offsetHeight
};
}
private isHoverBorder(event: MouseEvent): string {
const window = this.getWindow();
const delta = this.resizingArea; // the thickness of the hovered border area
const rect = window.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;
}
public onClick(event: MouseEvent): void {
if (this.desktopRef.currentlyFocused !== this) return;
if (!this.hover) return;
for (let listener of this.windowEventHandler)
listener.onClick(event);
}
public onMouseDown(event: MouseEvent): void {
if (this.desktopRef.currentlyFocused !== this) return;
if (!this.hover) return;
this.startResizing(event);
for (let listener of this.windowEventHandler)
listener.onMouseDown(event);
}
public onMouseUp(event: MouseEvent): void {
this.stopResizing();
if (this.desktopRef.currentlyFocused !== this) return;
if (!this.hover) return;
for (let listener of this.windowEventHandler)
listener.onMouseUp(event);
}
public onMouseMove(event: MouseEvent): void {
if (this.desktopRef.currentlyFocused !== this) return;
if (!this.hover) return;
for (let listener of this.windowEventHandler)
listener.onMouseMove(event);
}
public onGlobalMove(event: MouseEvent) {
this.windowDrag(event);
if (this.desktopRef.currentlyFocused !== this) return;
this.handleResizeCursor(event);
}
public onFocusGranted(): void {
if (!this.focusable) return;
const container = this.object.children.item(0) as HTMLElement;
container.style.zIndex = "var(--focused)";
this.getWindow().style.boxShadow = "var(--selected-shadow)";
}
public onFocusLost(): void {
this.stopResizing();
this.windowDragStop();
const container = this.object.children.item(0) as HTMLElement;
container.style.zIndex = "var(--opened)";
const window = this.getWindow();
window.style.cursor = "default";
window.style.boxShadow = "none";
}
}

View File

@@ -0,0 +1,35 @@
export interface DirectoryContent {
files: string[];
directories: string[];
}
export interface DirectoryInformation {
name: string;
created: Date;
size: number;
}
export interface FileInformation {
name: string;
created: Date;
size: number;
}
export enum FileType {
FILE,
IMAGE,
VIDEO,
AUDIO,
COMPRESSED,
DISK,
DATABASE,
DATA,
EMAIL,
EXECUTABLE,
FONT,
INTERNET,
PRESENTATION,
SPREADSHEET,
SYSTEM,
WORD
}

View File

@@ -0,0 +1,13 @@
export interface Personalization {
theme?: string;
taskbar?: string;
taskbar_icons?: string;
background?: string;
}
export const defaultPersonalization: Personalization = {
theme: "theme-dark",
taskbar: "small",
taskbar_icons: "left",
background: "assets/images/moon.jpg"
};

View File

@@ -0,0 +1,3 @@
export interface Token {
id: string;
}

View File

@@ -0,0 +1,18 @@
export interface User extends UserEditor {
id: string;
created: Date;
}
export interface UserEditor {
firstName: string;
lastName: string;
email: string;
username: string;
password: string;
}
export interface UserLogin {
email?: string;
username?: string;
password: string;
}

View File

@@ -0,0 +1,21 @@
<div class="pic unselectable">
<form class="login">
<h1>Einloggen</h1>
<div class="box">
<input type="text" class="input" id="identifier" placeholder="Benutzername / Email" required #identifier>
<input type="password" class="input" id="password" placeholder="Passwort" required #password>
<input type="submit" id="submit" value="Einloggen" (click)="login($event)">
</div>
<div class="btm-txt">
<p>Du besitzt keinen Account? <a (click)="register()">Registrieren</a></p>
<br>
</div>
</form>
<div id="snackbar" #snackbar>Login fehlgeschlagen!</div>
</div>

View File

@@ -0,0 +1,125 @@
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300&display=swap');
@import "/src/variables";
* {
margin: 0;
padding: 0;
text-decoration: none;
font-family: 'Poppins', sans-serif;
box-sizing: border-box;
color: var(--colors-text);
}
.pic {
min-height: 100vh;
background-image: var(--background);
background-size: cover;
}
.login {
width: 460px;
background: var(--colors-first-background);
height: 480px;
padding: 80px 40px;
border-radius: 10px;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
.login h1 {
text-align: center;
margin-bottom: 60px;
text-transform: uppercase;
font-weight: 500;
}
.box {
border-bottom: 2px solid var(--colors-second-background);
position: relative;
margin: 30px 0;
}
.input {
font-size: 15px;
height: 40px;
background: none;
display: block;
margin: 20px auto;
text-align: center;
border: 2px solid var(--colors-second);
padding: 14px 10px;
width: 350px;
outline: none;
border-radius: 10px;
transition: 0.25s;
}
.input:focus {
width: 380px;
border-color: var(--colors-primary);
}
#submit {
background: none;
display: block;
margin: 20px auto;
text-align: center;
border: 2px solid var(--colors-primary);
padding: 14px 40px;
outline: none;
border-radius: 10px;
transition: 0.25s;
cursor: pointer;
height: 50px;
}
#submit:hover {
background: var(--colors-primary);
}
@-webkit-keyframes fadein {
from {
bottom: 0;
opacity: 0;
}
to {
bottom: 30px;
opacity: 1;
}
}
@keyframes fadein {
from {
bottom: 0;
opacity: 0;
}
to {
bottom: 30px;
opacity: 1;
}
}
@-webkit-keyframes fadeout {
from {
bottom: 30px;
opacity: 1;
}
to {
bottom: 0;
opacity: 0;
}
}
@keyframes fadeout {
from {
bottom: 30px;
opacity: 1;
}
to {
bottom: 0;
opacity: 0;
}
}

View File

@@ -0,0 +1,101 @@
import {Component, ElementRef, OnInit, ViewChild} from '@angular/core';
import { Router } from '@angular/router';
import Swal from 'sweetalert2';
import { UserAPI } from '../api/userapi.service';
import { UserLogin } from '../entitys/user';
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.scss']
})
export class LoginComponent implements OnInit {
@ViewChild('identifier') identifier: ElementRef;
@ViewChild('password') password: ElementRef;
@ViewChild('snackbar') snackbar: ElementRef;
private failedAttempts: number = 0;
private isResetting: boolean = false;
private readonly resetTimeout: number = 20000;
constructor(private users: UserAPI, private router: Router) { }
ngOnInit(): void {
}
async login(event: MouseEvent) {
event.preventDefault();
if (!this.identifier.nativeElement.reportValidity() ||
!this.password.nativeElement.reportValidity())
return;
if (this.failedAttempts >= 3 || this.handleResetWithLocalStorage()) {
await Swal.fire({
icon: 'error',
title: 'Fehler',
text: 'Bitte warte einen moment, bevor du dich einloggst!',
});
if (!this.isResetting) {
this.isResetting = true;
localStorage.setItem("login_timeout", String(new Date().getTime()));
setTimeout(() => {
this.isResetting = false;
this.failedAttempts = 0;
localStorage.removeItem("\"login_timeout\"");
}, this.resetTimeout);
}
return;
}
const identifier = this.identifier.nativeElement as HTMLInputElement;
const password = this.password.nativeElement as HTMLInputElement;
if (identifier.value == "" || password.value == "") this.showSnackbar();
else {
const login: UserLogin = {password: password.value};
if (identifier.value.includes('@')) {
login.email = identifier.value;
}else {
login.username = identifier.value;
}
const success = await this.users.login(login);
if (success) {
await Swal.fire({
icon: 'success',
title: 'Erfolgreich eingeloggt',
text: 'Du wist gleich weitergeleitet',
timer: 2000,
showConfirmButton: false
});
await this.users.getCurrentUser(true);
await this.router.navigate([""]);
}
else this.showSnackbar();
}
}
async register() {
await this.router.navigate(["/register"]);
}
showSnackbar() {
this.failedAttempts++;
this.snackbar.nativeElement.classList.add('show');
setTimeout(() => {this.snackbar.nativeElement.classList.remove('show');}, 4000);
}
handleResetWithLocalStorage(): boolean {
const lsEntry = localStorage.getItem("login_timeout");
if (lsEntry === null) return false;
const time = Number(lsEntry);
const now = new Date().getTime();
if (now - time >= this.resetTimeout) {
localStorage.removeItem("login_timeout");
return false;
}
return true;
}
}

View File

@@ -0,0 +1,19 @@
<div class="pic unselectable">
<form class="login">
<h1>Registrieren</h1>
<div class="box">
<input id="firstname" class="input" type="text" name="firstname" placeholder="Vorname" required #firstname>
<input id="lastname" class="input" type="text" name="lastname" placeholder="Nachname" required #lastname>
<br><br>
<input id="email" class="input" type="email" name="email" placeholder="E-Mail" required #email>
<input id="username" class="input" type="text" name="username" placeholder="Benutzername" required #username>
<input id="password" class="input" type="password" name="password" placeholder="Passwort" required #password>
<input id="passwordRepeat" class="input" type="password" name="passwordRepeat" placeholder="Passwort wiederholen" required #passwordRepeat>
<input id="submit" type="submit" name="submit" value="Registrieren" (click)="register($event)">
</div>
<p>Du besitzt bereits einen Account? <a (click)="login()">Anmelden</a></p>
</form>
<div id="snackbar" #snackbar>Registrierung fehlgeschlagen!</div>
</div>

View File

@@ -0,0 +1,147 @@
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300&display=swap');
@import "/src/variables";
* {
margin: 0;
padding: 0;
text-decoration: none;
font-family: 'Poppins', sans-serif;
box-sizing: border-box;
color: var(--colors-text);
}
.pic {
height: 100vh;
background-image: var(--background);
background-size: cover;
}
.login {
width: 680px;
background: var(--colors-first-background);
height: 650px;
padding: 80px 40px;
border-radius: 10px;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
.login h1 {
text-align: center;
margin-bottom: 50px;
text-transform: uppercase;
font-weight: 500;
}
.box {
border-bottom: 2px solid var(--colors-second-background);
position: relative;
margin: 30px 0;
}
.input {
font-size: 15px;
height: 40px;
background: none;
display: block;
margin: 20px auto;
text-align: center;
border: 2px solid var(--colors-second);
padding: 14px 10px;
width: 550px;
outline: none;
border-radius: 10px;
transition: 0.25s;
}
#firstname {
position: absolute;
top: 0;
left: 25%;
width: 250px;
transform: translate(-50%, -50%);
}
#lastname {
position: absolute;
top: 0;
left: 75%;
width: 250px;
transform: translate(-50%, -50%);
}
#firstname:focus, #lastname:focus {
width: 280px;
}
.input:focus {
width: 580px;
border-color: var(--colors-primary);
}
#submit {
background: none;
display: block;
margin: 20px auto;
text-align: center;
border: 2px solid var(--colors-primary);
padding: 14px 40px;
outline: none;
border-radius: 10px;
transition: 0.25s;
height: 50px;
}
#submit:hover {
background: var(--colors-primary);
}
@-webkit-keyframes fadein {
from {
bottom: 0;
opacity: 0;
}
to {
bottom: 30px;
opacity: 1;
}
}
@keyframes fadein {
from {
bottom: 0;
opacity: 0;
}
to {
bottom: 30px;
opacity: 1;
}
}
@-webkit-keyframes fadeout {
from {
bottom: 30px;
opacity: 1;
}
to {
bottom: 0;
opacity: 0;
}
}
@keyframes fadeout {
from {
bottom: 30px;
opacity: 1;
}
to {
bottom: 0;
opacity: 0;
}
}

View File

@@ -0,0 +1,77 @@
import {Component, ElementRef, OnInit, ViewChild} from '@angular/core';
import { Router } from '@angular/router';
import { UserAPI } from '../api/userapi.service';
import { UserEditor } from '../entitys/user';
import Swal from "sweetalert2";
@Component({
selector: 'app-register',
templateUrl: './register.component.html',
styleUrls: ['./register.component.scss']
})
export class RegisterComponent implements OnInit {
@ViewChild('firstname') firstname: ElementRef;
@ViewChild('lastname') lastname: ElementRef;
@ViewChild('email') email: ElementRef;
@ViewChild('username') username: ElementRef;
@ViewChild('password') password: ElementRef;
@ViewChild('passwordRepeat') passwordRepeat: ElementRef;
@ViewChild('snackbar') snackbar: ElementRef;
constructor(private users: UserAPI, private router: Router) { }
ngOnInit(): void {
}
async register(event: MouseEvent) {
event.preventDefault();
const firstname = this.firstname.nativeElement as HTMLInputElement;
const lastname = this.lastname.nativeElement as HTMLInputElement;
const email = this.email.nativeElement as HTMLInputElement;
const username = this.username.nativeElement as HTMLInputElement;
const password = this.password.nativeElement as HTMLInputElement;
const passwordRepeat = this.passwordRepeat.nativeElement as HTMLInputElement;
if (!firstname.reportValidity() ||
!lastname.reportValidity() ||
!email.reportValidity() ||
!username.reportValidity() ||
!password.reportValidity() ||
!passwordRepeat.reportValidity())
return;
if (firstname.value == "" ||
lastname.value == "" ||
email.value == "" ||
username.value == "" ||
password.value == "" ||
passwordRepeat.value == "" ||
password.value != passwordRepeat.value) this.showSnackbar();
else {
const register: UserEditor = {firstName: firstname.value, lastName: lastname.value, email: email.value, username: username.value, password: password.value};
const success = await this.users.register(register);
if (success) {
await Swal.fire({
icon: 'success',
title: 'Erfolgreich registriert',
text: 'Du wist gleich weitergeleitet',
timer: 2000,
showConfirmButton: false
});
await this.users.getCurrentUser(true);
await this.router.navigate([""]);
}
else this.showSnackbar();
}
}
async login() {
await this.router.navigate(["/login"]);
}
showSnackbar() {
this.snackbar.nativeElement.classList.add('show');
setTimeout(() => {this.snackbar.nativeElement.classList.remove('show');}, 4000);
}
}

View File

@@ -0,0 +1,201 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
export class WindowAPI {
constructor() {
this.handlers = [];
this.properties = new WindowProperties();
window.addEventListener("message", (event) => {
const data = event.data;
const removeHandler = [];
for (let i = 0; i < this.handlers.length; i++) {
const handler = this.handlers[i];
if (handler.type !== data.type)
continue;
handler.handler.call(this, data);
if (handler.oneTimeCall)
removeHandler.push(i);
}
removeHandler.forEach(index => this.handlers.splice(index, 1));
if (data.type === "triggerListener") {
this.properties.triggerEventListeners(data.data.type, data.data.event);
}
});
}
postMessage(packet) {
parent.postMessage(packet, '*');
}
getWindowTypes() {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve => {
this.addMessageHandler((data) => {
resolve(data.data);
}, "windowTypes", true);
this.postMessage({ type: "windowTypes" });
}));
});
}
openWindow(id, args) {
windowApi.postMessage({ type: "openWindow", data: { id, args } });
}
closeWindow(windowId) {
windowApi.postMessage({ type: "closeWindow", data: windowId });
}
requestFocus() {
windowApi.postMessage({ type: "requestFocus" });
}
toggleMaximize() {
windowApi.postMessage({ type: "toggleMaximize" });
}
minimize() {
windowApi.postMessage({ type: "minimize" });
}
close() {
windowApi.postMessage({ type: "close" });
}
addMessageHandler(handler, type, oneTimeCall = false) {
this.handlers.push({ handler, type, oneTimeCall });
}
removeMessageHandler(handler) {
for (let i = 0; i < this.handlers.length; i++) {
const h = this.handlers[i];
if (h.handler == handler) {
this.handlers.splice(i, 1);
return;
}
}
}
}
export class WindowProperties {
constructor() {
this.eventListeners = [];
}
setTitle(value) {
windowApi.postMessage({ type: "properties", data: { name: "title", value } });
}
setHeaderMessage(value) {
windowApi.postMessage({ type: "properties", data: { name: "headerMessage", value } });
}
setIcon(value) {
windowApi.postMessage({ type: "properties", data: { name: "icon", value } });
}
setResizable(value) {
windowApi.postMessage({ type: "properties", data: { name: "resizable", value } });
}
setMinSize(width, height) {
windowApi.postMessage({ type: "properties", data: { name: "minSize", value: { width, height } } });
}
setMaximized(value) {
windowApi.postMessage({ type: "properties", data: { name: "maximized", value } });
}
setSize(width, height) {
windowApi.postMessage({ type: "properties", data: { name: "size", value: { width, height } } });
}
setPosition(x, y) {
windowApi.postMessage({ type: "properties", data: { name: "position", value: { x, y } } });
}
getTitle() {
return new Promise((resolve => {
windowApi.addMessageHandler((data) => {
resolve(data.data);
}, "title", true);
windowApi.postMessage({ type: "title" });
}));
}
getHeaderMessage() {
return new Promise((resolve => {
windowApi.addMessageHandler((data) => {
resolve(data.data);
}, "headerMessage", true);
windowApi.postMessage({ type: "headerMessage" });
}));
}
getIcon() {
return new Promise((resolve => {
windowApi.addMessageHandler((data) => {
resolve(data.data);
}, "icon", true);
windowApi.postMessage({ type: "icon" });
}));
}
isResizable() {
return new Promise((resolve => {
windowApi.addMessageHandler((data) => {
resolve(data.data);
}, "resizable", true);
windowApi.postMessage({ type: "resizable" });
}));
}
getSize() {
return new Promise((resolve => {
windowApi.addMessageHandler((data) => {
resolve(data.data);
}, "size", true);
windowApi.postMessage({ type: "size" });
}));
}
getMinSize() {
return new Promise((resolve => {
windowApi.addMessageHandler((data) => {
resolve(data.data);
}, "minSize", true);
windowApi.postMessage({ type: "minSize" });
}));
}
getPosition() {
return new Promise((resolve => {
windowApi.addMessageHandler((data) => {
resolve(data.data);
}, "position", true);
windowApi.postMessage({ type: "position" });
}));
}
getWindowType() {
return new Promise((resolve => {
windowApi.addMessageHandler((data) => {
resolve(data.data);
}, "windowType", true);
windowApi.postMessage({ type: "windowType" });
}));
}
getAdditionalArgs() {
return new Promise((resolve => {
windowApi.addMessageHandler((data) => {
resolve(data.data);
}, "additionalArgs", true);
windowApi.postMessage({ type: "additionalArgs" });
}));
}
updateWindow() {
windowApi.postMessage({ type: "update" });
}
addEventListener(listener) {
this.eventListeners.push(listener);
}
triggerEventListeners(type, event) {
this.eventListeners.forEach(listener => {
switch (type) {
case "click":
listener.onClick(event);
return;
case "down":
listener.onMouseDown(event);
return;
case "up":
listener.onMouseUp(event);
return;
case "move":
listener.onMouseMove(event);
return;
}
});
}
}
export const windowApi = new WindowAPI();
//# sourceMappingURL=api.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,222 @@
import {WindowEvents, WindowType} from "./types";
export type MessageEventHandler = (packet: Packet) => void;
export type PromiseType = "toggleMaximize" | "minimize" | "close" | "windowTypes" | "openWindow" | "closeWindow" | "requestFocus" | "properties" | "update" | "triggerListener" |
"title" | "headerMessage" | "icon" | "resizable" | "size" | "minSize" | "position" | "windowType" | "additionalArgs";
export class WindowAPI {
private readonly handlers: {
handler: MessageEventHandler,
type: PromiseType,
oneTimeCall: boolean
}[];
public properties: WindowProperties;
public constructor() {
this.handlers = [];
this.properties = new WindowProperties();
window.addEventListener("message", (event: MessageEvent) => {
const data: Packet = event.data;
const removeHandler: number[] = [];
for (let i = 0; i < this.handlers.length; i++) {
const handler = this.handlers[i];
if (handler.type !== data.type) continue;
handler.handler.call(this, data);
if (handler.oneTimeCall)
removeHandler.push(i);
}
removeHandler.forEach(index => this.handlers.splice(index, 1));
if (data.type === "triggerListener") {
this.properties.triggerEventListeners(data.data.type, data.data.event);
}
});
}
public postMessage(packet: Packet) {
parent.postMessage(packet, '*');
}
public async getWindowTypes(): Promise<WindowType[]> {
return new Promise<WindowType[]>((resolve => {
this.addMessageHandler((data) => {
resolve(data.data);
}, "windowTypes", true);
this.postMessage({type: "windowTypes"});
}));
}
public openWindow(id: string, args?: string[]) {
windowApi.postMessage({type: "openWindow", data: {id, args}});
}
public closeWindow(windowId: number) {
windowApi.postMessage({type: "closeWindow", data: windowId});
}
public requestFocus() {
windowApi.postMessage({type: "requestFocus"});
}
public toggleMaximize() {
windowApi.postMessage({type: "toggleMaximize"});
}
public minimize() {
windowApi.postMessage({type: "minimize"});
}
public close() {
windowApi.postMessage({type: "close"});
}
public addMessageHandler(handler: MessageEventHandler, type: PromiseType, oneTimeCall: boolean = false) {
this.handlers.push({handler, type, oneTimeCall});
}
public removeMessageHandler(handler: MessageEventHandler) {
for (let i = 0; i < this.handlers.length; i++) {
const h = this.handlers[i];
if (h.handler == handler) {
this.handlers.splice(i, 1);
return;
}
}
}
}
export class WindowProperties {
private eventListeners: WindowEvents[] = [];
public setTitle(value: string) {
windowApi.postMessage({type: "properties", data: {name: "title", value}});
}
public setHeaderMessage(value: string) {
windowApi.postMessage({type: "properties", data: {name: "headerMessage", value}});
}
public setIcon(value: string) {
windowApi.postMessage({type: "properties", data: {name: "icon", value}});
}
public setResizable(value: boolean) {
windowApi.postMessage({type: "properties", data: {name: "resizable", value}});
}
public setMinSize(width: number, height: number) {
windowApi.postMessage({type: "properties", data: {name: "minSize", value: {width, height}}});
}
public setMaximized(value: boolean): void {
windowApi.postMessage({type: "properties", data: {name: "maximized", value}});
}
public setSize(width: number, height: number): void {
windowApi.postMessage({type: "properties", data: {name: "size", value: {width, height}}});
}
public setPosition(x: number, y: number): void {
windowApi.postMessage({type: "properties", data: {name: "position", value: {x, y}}});
}
public getTitle(): Promise<string> {
return new Promise<string>((resolve => {
windowApi.addMessageHandler((data) => {
resolve(data.data);
}, "title", true);
windowApi.postMessage({type: "title"});
}));
}
public getHeaderMessage(): Promise<string> {
return new Promise<string>((resolve => {
windowApi.addMessageHandler((data) => {
resolve(data.data);
}, "headerMessage", true);
windowApi.postMessage({type: "headerMessage"});
}));
}
public getIcon(): Promise<string> {
return new Promise<string>((resolve => {
windowApi.addMessageHandler((data) => {
resolve(data.data);
}, "icon", true);
windowApi.postMessage({type: "icon"});
}));
}
public isResizable(): Promise<boolean> {
return new Promise<boolean>((resolve => {
windowApi.addMessageHandler((data) => {
resolve(data.data);
}, "resizable", true);
windowApi.postMessage({type: "resizable"});
}));
}
public getSize(): Promise<{width: number, height: number}> {
return new Promise<{width: number, height: number}>((resolve => {
windowApi.addMessageHandler((data) => {
resolve(data.data);
}, "size", true);
windowApi.postMessage({type: "size"});
}));
}
public getMinSize(): Promise<{width: number, height: number}> {
return new Promise<{width: number, height: number}>((resolve => {
windowApi.addMessageHandler((data) => {
resolve(data.data);
}, "minSize", true);
windowApi.postMessage({type: "minSize"});
}));
}
public getPosition(): Promise<{x: number, y: number}> {
return new Promise<{x: number, y: number}>((resolve => {
windowApi.addMessageHandler((data) => {
resolve(data.data);
}, "position", true);
windowApi.postMessage({type: "position"});
}));
}
public getWindowType(): Promise<WindowType> {
return new Promise<WindowType>((resolve => {
windowApi.addMessageHandler((data) => {
resolve(data.data);
}, "windowType", true);
windowApi.postMessage({type: "windowType"});
}));
}
public getAdditionalArgs(): Promise<any[]> {
return new Promise<any[]>((resolve => {
windowApi.addMessageHandler((data) => {
resolve(data.data);
}, "additionalArgs", true);
windowApi.postMessage({type: "additionalArgs"});
}));
}
public updateWindow(): void {
windowApi.postMessage({type: "update"});
}
public addEventListener(listener: WindowEvents): void {
this.eventListeners.push(listener);
}
public triggerEventListeners(type: "click" | "down" | "up" | "move", event: MouseEvent) {
this.eventListeners.forEach(listener => {
switch (type) {
case "click":
listener.onClick(event); return;
case "down":
listener.onMouseDown(event); return;
case "up":
listener.onMouseUp(event); return;
case "move":
listener.onMouseMove(event); return;
}
})
}
}
export interface Packet {
type: PromiseType;
data?: any;
}
export const windowApi = new WindowAPI();

View File

@@ -0,0 +1,18 @@
export interface WindowType {
type: string;
id: string,
name: string,
icon: string,
permission?: string;
args?: string[];
canOpen?: (number | string)[];
url?: string;
}
export interface WindowEvents {
onClick(event: MouseEvent): void;
onMouseDown(event: MouseEvent): void;
onMouseUp(event: MouseEvent): void;
onMouseMove(event: MouseEvent): void;
}

View File

@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Inner Test</title>
<script type="module" defer>
import {windowApi} from "../api/api.js";
windowApi.properties.setTitle("WindowAPI Test");
windowApi.properties.setHeaderMessage("A Program to test the Window API");
console.log(await windowApi.getWindowTypes())
windowApi.properties.setResizable(false);
</script>
</head>
<body>
<h1>WindowAPI Test</h1>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 488 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 MiB

View File

@@ -0,0 +1,5 @@
export const environment = {
production: true,
backendHost: "https://api.leon-hoppe.de/",
updateUrl: "wss://api.leon-hoppe.de/update"
};

View File

@@ -0,0 +1,18 @@
// This file can be replaced during build by using the `fileReplacements` array.
// `ng build` replaces `environment.ts` with `environment.prod.ts`.
// The list of file replacements can be found in `angular.json`.
export const environment = {
production: false,
backendHost: "http://localhost:4041/",
updateUrl: "ws://localhost:4041/update"
};
/*
* For easier debugging in development mode, you can import the following file
* to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
*
* This import should be commented out in production mode because it will have a negative impact
* on performance if an error is thrown.
*/
// import 'zone.js/plugins/zone-error'; // Included with Angular CLI.

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

@@ -0,0 +1,24 @@
/* Material Icons */
@font-face {
font-family: 'Material Icons';
font-style: normal;
font-weight: 400;
src: url("/assets/fonts/MaterialIcons.woff2") format('woff2');
}
.material-icons {
font-family: 'Material Icons', monospace;
font-weight: normal;
font-style: normal;
font-size: 24px;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
-webkit-font-feature-settings: 'liga';
-webkit-font-smoothing: antialiased;
}

View File

@@ -0,0 +1,18 @@
<!doctype html>
<html lang="de" style="height: 100%">
<head>
<meta charset="utf-8">
<title>WebDesktop</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link rel="preconnect" href="https://fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&amp;display=swap" rel="stylesheet">
<!--<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">-->
<link rel="manifest" href="manifest.webmanifest">
</head>
<body class="mat-typography" style="height: 100% !important;">
<app-root></app-root>
<noscript>Please enable JavaScript to continue using this application.</noscript>
</body>
</html>

View File

@@ -0,0 +1,14 @@
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
import 'codemirror/mode/meta';
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.error(err));

View File

@@ -0,0 +1,26 @@
{
"name": "WebDesktop",
"short_name": "WD",
"theme_color": "#1976d2",
"background_color": "#fafafa",
"display": "standalone",
"scope": "./",
"start_url": "./",
"icons": [
{
"src": "favicon.ico",
"sizes": "256x256",
"type": "image/ico"
},
{
"src": "assets/images/logo.png",
"sizes": "512x512",
"type": "image/png"
},
{
"src": "assets/images/logo-small.png",
"sizes": "144x144",
"type": "image/png"
}
]
}

View File

@@ -0,0 +1,53 @@
/**
* This file includes polyfills needed by Angular and is loaded before the app.
* You can add your own extra polyfills to this file.
*
* This file is divided into 2 sections:
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main
* file.
*
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
* automatically update themselves. This includes recent versions of Safari, Chrome (including
* Opera), Edge on the desktop, and iOS and Chrome on mobile.
*
* Learn more in https://angular.io/guide/browser-support
*/
/***************************************************************************************************
* BROWSER POLYFILLS
*/
/**
* By default, zone.js will patch all possible macroTask and DomEvents
* user can disable parts of macroTask/DomEvents patch by setting following flags
* because those flags need to be set before `zone.js` being loaded, and webpack
* will put import in the top of bundle, so user need to create a separate file
* in this directory (for example: zone-flags.ts), and put the following flags
* into that file, and then add the following code before importing zone.js.
* import './zone-flags';
*
* The flags allowed in zone-flags.ts are listed here.
*
* The following flags will work for all browsers.
*
* (windows as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
* (windows as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
* (windows as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
*
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
* with the following flag, it will bypass `zone.js` patch for IE/Edge
*
* (windows as any).__Zone_enable_cross_context_check = true;
*
*/
/***************************************************************************************************
* Zone JS is required by default for Angular itself.
*/
import 'zone.js'; // Included with Angular CLI.
/***************************************************************************************************
* APPLICATION IMPORTS
*/

View File

@@ -0,0 +1,142 @@
/* You can add global styles to this file, and also import other style files */
body {
margin: 0;
font-family: Roboto, "Helvetica Neue", sans-serif;
overflow: hidden;
background-size: cover;
}
* {
box-sizing: border-box;
}
::-webkit-scrollbar {
width: 10px;
}
/* Track */
::-webkit-scrollbar-track {
background: var(--colors-second-background);
}
/* Handle */
::-webkit-scrollbar-thumb {
background: var(--colors-scroll);
}
/* Handle on hover */
::-webkit-scrollbar-thumb:hover {
background: var(--colors-scroll-hover);
}
.unselectable {
-webkit-touch-callout: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.selectable {
-webkit-touch-callout: default;
-webkit-user-select: text;
-moz-user-select: text;
-ms-user-select: text;
user-select: text;
}
a {
cursor: pointer;
}
a:not([href]):not([class]),
a:not([href]):not([class]):hover {
color: #0d6efd;
}
#snackbar {
visibility: hidden;
width: 290px;
margin-left: -145px;
background-color: var(--colors-danger);
color: var(--colors-first);
text-align: center;
border-radius: 2px;
padding: 20px;
left: 50%;
position: fixed;
z-index: 1;
bottom: 30px;
}
#snackbar.show {
visibility: visible;
animation: fadein 0.5s, fadeout 0.5s 4.5s;
}
.visible {
display: block;
}
.hidden {
display: none;
}
.buttons {
width: 100%;
height: max-content;
display: flex;
justify-content: center;
align-items: center;
button {
margin: 5px;
}
}
.buttons-left {
width: 100%;
height: max-content;
display: flex;
justify-content: left;
align-items: flex-start;
button {
margin: 5px;
}
}
.buttons-right {
width: 100%;
height: max-content;
display: flex;
justify-content: right;
align-items: flex-end;
button {
margin: 5px;
}
}
.CodeMirror {
height: 100%;
}
.dialog {
display: none;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 40%;
height: 40%;
border-color: var(--colors-first-background);
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
background-color: var(--colors-second-background);
&.visible {
display: block;
}
}

View File

@@ -0,0 +1,26 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'zone.js/testing';
import { getTestBed } from '@angular/core/testing';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing';
declare const require: {
context(path: string, deep?: boolean, filter?: RegExp): {
keys(): string[];
<T>(id: string): T;
};
};
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting(),
);
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);

View File

@@ -0,0 +1,47 @@
body {
--colors-primary: #387ef5;
--colors-secondary: #32db64;
--colors-danger: #f53d3d;
--colors-favorite: #69BB7B;
--desktop-icon-text: #f4f4f4;
--desktop-icon-shaddow: #2D3047;
--background: url("assets/images/moon.jpg");
}
.theme-dark {
--colors-first: #f4f4f4;
--colors-second: #2D3047;
--colors-first-background: #202225;
--colors-second-background: #2f3136;
--colors-text: #f4f4f4;
--colors-scroll: #3a3c42;
--colors-scroll-hover: #2D3047;
--icon-hover: #f2f2f255;
--opened-color: #ffffff88;
--window-border-color: #3a3c42;
--taskbar-background: rgb(32, 34, 37, 0.8);
}
.theme-light {
--colors-first: #ffffff;
--colors-second: #d7d7d7;
--colors-first-background: #b8c4d5;
--colors-second-background: #a0a8b9;
--colors-text: #000000;
--colors-scroll: #3a3c42;
--colors-scroll-hover: #54565e;
--icon-hover: #f2f2f255;
--opened-color: rgba(96, 96, 96, 0.53);
--window-border-color: #aeb9cb;
--taskbar-background: rgba(217, 229, 248, 0.8);
}

Some files were not shown because too many files have changed in this diff Show More