Initial commit
16
Projekte/WebDesktop/WebDesktopFrontend/.browserslistrc
Normal 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
|
||||
9
Projekte/WebDesktop/WebDesktopFrontend/.dockerignore
Normal file
@@ -0,0 +1,9 @@
|
||||
.git
|
||||
.firebase
|
||||
.editorconfig
|
||||
/node_modules
|
||||
/e2e
|
||||
/docs
|
||||
.gitignore
|
||||
*.zip
|
||||
*.md
|
||||
16
Projekte/WebDesktop/WebDesktopFrontend/.editorconfig
Normal 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
|
||||
46
Projekte/WebDesktop/WebDesktopFrontend/.gitignore
vendored
Normal 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
|
||||
10
Projekte/WebDesktop/WebDesktopFrontend/Dockerfile
Normal 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
|
||||
27
Projekte/WebDesktop/WebDesktopFrontend/README.md
Normal 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.
|
||||
244
Projekte/WebDesktop/WebDesktopFrontend/angular.json
Normal 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"
|
||||
}
|
||||
44
Projekte/WebDesktop/WebDesktopFrontend/karma.conf.js
Normal 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
|
||||
});
|
||||
};
|
||||
13
Projekte/WebDesktop/WebDesktopFrontend/nginx.conf
Normal 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;
|
||||
}
|
||||
}
|
||||
30
Projekte/WebDesktop/WebDesktopFrontend/ngsw-config.json
Normal 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)"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
26320
Projekte/WebDesktop/WebDesktopFrontend/package-lock.json
generated
Normal file
50
Projekte/WebDesktop/WebDesktopFrontend/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
@@ -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"});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 { }
|
||||
@@ -0,0 +1 @@
|
||||
<router-outlet *ngIf="ready"></router-outlet>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
90
Projekte/WebDesktop/WebDesktopFrontend/src/app/app.module.ts
Normal 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 { }
|
||||
142
Projekte/WebDesktop/WebDesktopFrontend/src/app/crud.service.ts
Normal 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;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<div class="desktop-icon" (dblclick)="onDoubleClick()">
|
||||
<img src="{{type.icon}}" alt="Logo" draggable="false">
|
||||
<span>{{type.name}}</span>
|
||||
</div>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<iframe #frame (blur)="properties.requestFocus()"></iframe>
|
||||
@@ -0,0 +1,4 @@
|
||||
iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<iframe src="https://code.leon-hoppe.de"></iframe>
|
||||
@@ -0,0 +1,4 @@
|
||||
iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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%;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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%);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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"
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface Token {
|
||||
id: string;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
201
Projekte/WebDesktop/WebDesktopFrontend/src/assets/api/api.js
Normal 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
|
||||
222
Projekte/WebDesktop/WebDesktopFrontend/src/assets/api/api.ts
Normal 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();
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 15 KiB |
BIN
Projekte/WebDesktop/WebDesktopFrontend/src/assets/icons/code.png
Normal file
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 488 KiB |
BIN
Projekte/WebDesktop/WebDesktopFrontend/src/assets/images/sea.png
Normal file
|
After Width: | Height: | Size: 12 MiB |
@@ -0,0 +1,5 @@
|
||||
export const environment = {
|
||||
production: true,
|
||||
backendHost: "https://api.leon-hoppe.de/",
|
||||
updateUrl: "wss://api.leon-hoppe.de/update"
|
||||
};
|
||||
@@ -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.
|
||||
BIN
Projekte/WebDesktop/WebDesktopFrontend/src/favicon.ico
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
24
Projekte/WebDesktop/WebDesktopFrontend/src/fonts.scss
Normal 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;
|
||||
}
|
||||
18
Projekte/WebDesktop/WebDesktopFrontend/src/index.html
Normal 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&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>
|
||||
14
Projekte/WebDesktop/WebDesktopFrontend/src/main.ts
Normal 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));
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
53
Projekte/WebDesktop/WebDesktopFrontend/src/polyfills.ts
Normal 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
|
||||
*/
|
||||
142
Projekte/WebDesktop/WebDesktopFrontend/src/styles.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
26
Projekte/WebDesktop/WebDesktopFrontend/src/test.ts
Normal 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);
|
||||
47
Projekte/WebDesktop/WebDesktopFrontend/src/themes.scss
Normal 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);
|
||||
}
|
||||