Integrated Ionic framework

This commit is contained in:
Jim Martens 2023-10-26 23:12:00 +02:00
parent fcfa54cc4e
commit 87869ce604
57 changed files with 11581 additions and 5719 deletions

View File

@ -8,6 +8,7 @@
# You can see what browsers were selected by your queries by running:
# npx browserslist
last 2 ChromeAndroid versions
last 2 Chrome versions
last 1 Firefox version
last 2 Edge major versions

View File

@ -27,7 +27,7 @@ steps:
commands:
- npm install
- npm run build:production
- cp /drone/src/.htaccess /drone/src/dist/tsw-timetable-frontend/
- cp /drone/src/.htaccess /drone/src/dist/www/
- name: rebuild-cache
privileged: true
image: 2martens/drone-volume-cache
@ -44,7 +44,7 @@ steps:
settings:
hosts: [ "gienah.uberspace.de" ]
user: wahlfron
source: /drone/src/dist/tsw-timetable-frontend/.
source: /drone/src/dist/www/.
target: ~/tmp/build
recursive: true
delete: true

59
.eslintrc.json Normal file
View File

@ -0,0 +1,59 @@
{
"root": true,
"ignorePatterns": [
"projects/**/*"
],
"overrides": [
{
"files": [
"*.ts"
],
"parserOptions": {
"project": [
"tsconfig.json"
],
"createDefaultProgram": true
},
"extends": [
"plugin:@angular-eslint/recommended",
"plugin:@angular-eslint/template/process-inline-templates"
],
"rules": {
"@angular-eslint/component-class-suffix": [
"error",
{
"suffixes": [
"Page",
"Component"
]
}
],
"@angular-eslint/component-selector": [
"error",
{
"type": "element",
"prefix": "app",
"style": "kebab-case"
}
],
"@angular-eslint/directive-selector": [
"error",
{
"type": "attribute",
"prefix": "app",
"style": "camelCase"
}
]
}
},
{
"files": [
"*.html"
],
"extends": [
"plugin:@angular-eslint/template/recommended"
],
"rules": {}
}
]
}

27
.gitignore vendored
View File

@ -1,4 +1,27 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# Specifies intentionally untracked files to ignore when using Git
# http://git-scm.com/docs/gitignore
*~
*.sw[mnpcod]
.tmp
*.tmp
*.tmp.*
UserInterfaceState.xcuserstate
$RECYCLE.BIN/
*.log
log.txt
/.sourcemaps
/.versions
/coverage
# Ionic
/.ionic
/www
/platforms
/plugins
# Compiled output
/dist
@ -18,6 +41,7 @@ yarn-error.log
.c9/
*.launch
.settings/
*.sublime-project
*.sublime-workspace
# Visual Studio Code
@ -29,6 +53,7 @@ yarn-error.log
.history/*
# Miscellaneous
/.angular
/.angular/cache
.sass-cache/
/connect.lock

View File

@ -3,11 +3,12 @@
"version": 1,
"newProjectRoot": "projects",
"projects": {
"tsw-timetable-frontend": {
"app": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "scss"
"@ionic/angular-toolkit:page": {
"styleext": "scss",
"standalone": true
}
},
"root": "",
@ -25,26 +26,29 @@
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/tsw-timetable-frontend",
"outputPath": "www",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": [
"zone.js"
],
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": [
"src/favicon.ico",
"src/assets",
{
"glob": "**/*",
"input": "src/assets",
"output": "assets"
},
"src/manifest.webmanifest"
],
"styles": [
"@angular/material/prebuilt-themes/deeppurple-amber.css",
"src/sass/styles.scss"
"src/theme/variables.scss",
"src/global.scss"
],
"stylePreprocessorOptions": {
"includePaths": [
"src/sass"
"src/sass",
"src/theme",
"src"
]
},
"scripts": [],
@ -61,8 +65,8 @@
"budgets": [
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "1mb"
"maximumWarning": "2mb",
"maximumError": "5mb"
},
{
"type": "anyComponentStyle",
@ -76,8 +80,8 @@
"budgets": [
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "1mb"
"maximumWarning": "2mb",
"maximumError": "5mb"
},
{
"type": "anyComponentStyle",
@ -112,6 +116,9 @@
"sourceMap": true,
"namedChunks": true,
"localize": ["de"]
},
"ci": {
"progress": false
}
},
"defaultConfiguration": "production"
@ -120,16 +127,19 @@
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"browserTarget": "tsw-timetable-frontend:build:production"
"browserTarget": "app:build:production"
},
"productionDebug": {
"browserTarget": "tsw-timetable-frontend:build:productionDebug"
"browserTarget": "app:build:productionDebug"
},
"development": {
"browserTarget": "tsw-timetable-frontend:build:development"
"browserTarget": "app:build:development"
},
"developmentRemote": {
"browserTarget": "tsw-timetable-frontend:build:developmentRemote"
"browserTarget": "app:build:developmentRemote"
},
"ci": {
"progress": false
}
},
"defaultConfiguration": "development"
@ -137,36 +147,71 @@
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "tsw-timetable-frontend:build"
"browserTarget": "app:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"polyfills": [
"zone.js",
"zone.js/testing"
],
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"inlineStyleLanguage": "scss",
"assets": [
"src/favicon.ico",
"src/assets",
"src/manifest.webmanifest"
{
"glob": "**/*",
"input": "src/assets",
"output": "assets"
}
],
"styles": [
"@angular/material/prebuilt-themes/deeppurple-amber.css",
"src/sass/styles.scss"
"src/theme/variables.scss",
"src/global.scss"
],
"stylePreprocessorOptions": {
"includePaths": [
"src/sass"
"src/theme"
]
},
"scripts": []
},
"configurations": {
"ci": {
"progress": false,
"watch": false
}
}
},
"lint": {
"builder": "@angular-eslint/builder:lint",
"options": {
"lintFilePatterns": [
"src/**/*.ts",
"src/**/*.html"
]
}
}
}
}
},
"cli": {
"schematicCollections": [
"@ionic/angular-toolkit"
]
},
"schematics": {
"@ionic/angular-toolkit:component": {
"styleext": "scss"
},
"@ionic/angular-toolkit:page": {
"styleext": "scss"
},
"@angular-eslint/schematics:application": {
"setParserOptionsProject": true
},
"@angular-eslint/schematics:library": {
"setParserOptionsProject": true
}
}
}

12
capacitor.config.ts Normal file
View File

@ -0,0 +1,12 @@
import {CapacitorConfig} from '@capacitor/cli';
const config: CapacitorConfig = {
appId: 'io.ionic.starter',
appName: 'tsw-timetable-frontend',
webDir: 'www',
server: {
androidScheme: 'https'
}
};
export default config;

7
ionic.config.json Normal file
View File

@ -0,0 +1,7 @@
{
"name": "tsw-timetable-frontend",
"integrations": {
"capacitor": {}
},
"type": "angular-standalone"
}

44
karma.conf.js Normal file
View File

@ -0,0 +1,44 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage'),
require('@angular-devkit/build-angular/plugins/karma')
],
client: {
jasmine: {
// you can add configuration options for Jasmine here
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
// for example, you can disable the random execution with `random: false`
// or set a specific seed with `seed: 4321`
},
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
jasmineHtmlReporter: {
suppressAll: true // removes the duplicated traces
},
coverageReporter: {
dir: require('path').join(__dirname, './coverage/app'),
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
});
};

15685
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,8 @@
{
"name": "tsw-timetable-frontend",
"version": "0.0.0",
"version": "0.0.1",
"author": "Ionic Framework",
"homepage": "https://ionicframework.com/",
"scripts": {
"ng": "ng",
"start": "ng serve",
@ -11,45 +13,69 @@
"build:production": "ng build -c production",
"build:production:sourcemap": "ng build -c productionDebug",
"build:production:stats": "ng build -c production --stats-json",
"analyze": "webpack-bundle-analyzer dist/tsw-timetable-frontend/de/stats.json",
"analyze": "webpack-bundle-analyzer www/de/stats.json",
"watch": "ng build --watch --configuration development",
"test": "ng test",
"extract-i18n": "ng extract-i18n --output-path src/locale"
"extract-i18n": "ng extract-i18n --output-path src/locale",
"lint": "ng lint"
},
"private": true,
"dependencies": {
"@angular/animations": "^16.2.1",
"@angular/cdk": "^16.2.1",
"@angular/common": "^16.2.1",
"@angular/compiler": "^16.2.1",
"@angular/core": "^16.2.1",
"@angular/forms": "^16.2.1",
"@angular/material": "^16.2.1",
"@angular/platform-browser": "^16.2.1",
"@angular/platform-browser-dynamic": "^16.2.1",
"@angular/router": "^16.2.1",
"@angular/service-worker": "^16.2.1",
"@capacitor/app": "5.0.6",
"@capacitor/core": "5.5.1",
"@capacitor/haptics": "5.0.6",
"@capacitor/keyboard": "5.0.6",
"@capacitor/status-bar": "5.0.6",
"@ionic/angular": "^7.5.0",
"@ngrx/effects": "^16.2.0",
"@ngrx/store": "^16.2.0",
"ionicons": "^7.2.1",
"keycloak-angular": "^14.0.0",
"rxjs": "~7.8.1",
"tslib": "^2.6.2",
"zone.js": "~0.13.1"
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.13.0"
},
"devDependencies": {
"@angular-devkit/build-angular": "^16.2.0",
"@angular/cli": "~16.2.0",
"@angular-devkit/build-angular": "^16.2.1",
"@angular-eslint/builder": "^16.2.0",
"@angular-eslint/eslint-plugin": "^16.2.0",
"@angular-eslint/eslint-plugin-template": "^16.2.0",
"@angular-eslint/schematics": "^16.2.0",
"@angular-eslint/template-parser": "^16.2.0",
"@angular/cli": "^16.2.1",
"@angular/compiler-cli": "^16.2.1",
"@angular/localize": "^16.2.1",
"@angular/language-service": "^16.2.1",
"@angular/localize": "^16.2.11",
"@capacitor/cli": "5.5.1",
"@ionic/angular-toolkit": "^9.0.0",
"@types/jasmine": "~4.3.0",
"@types/node": "^12.11.1",
"@typescript-eslint/eslint-plugin": "5.3.0",
"@typescript-eslint/parser": "5.3.0",
"eslint": "^7.26.0",
"eslint-plugin-import": "2.22.1",
"eslint-plugin-jsdoc": "30.7.6",
"eslint-plugin-prefer-arrow": "1.2.2",
"jasmine-core": "~4.6.0",
"jasmine-spec-reporter": "~5.0.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.2.0",
"karma-coverage": "~2.2.0",
"karma-coverage-istanbul-reporter": "~3.0.2",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.1.0",
"typescript": "~5.1.6",
"webpack-bundle-analyzer": "^4.9.0",
"webpack-cli": "^5.1.4"
}
"karma-jasmine-html-reporter": "~2.0.0",
"ts-node": "^8.3.0",
"typescript": "~5.0.2",
"webpack-bundle-analyzer": "^4.9.0"
},
"description": "An Ionic project"
}

View File

@ -1,3 +1,30 @@
<app-navigation>
<router-outlet></router-outlet>
</app-navigation>
<ion-app>
<ion-split-pane contentId="main-content">
<ion-menu contentId="main-content" type="overlay">
<ion-content>
<ion-list id="inbox-list">
<ion-list-header>Inbox</ion-list-header>
<ion-note>hi@ionicframework.com</ion-note>
<ion-menu-toggle auto-hide="false" *ngFor="let p of appPages; let i = index">
<ion-item routerDirection="root" [routerLink]="[p.url]" lines="none" detail="false"
routerLinkActive="selected">
<ion-icon aria-hidden="true" slot="start" [ios]="p.icon + '-outline'" [md]="p.icon + '-sharp'"></ion-icon>
<ion-label>{{ p.title }}</ion-label>
</ion-item>
</ion-menu-toggle>
</ion-list>
<ion-list id="labels-list" *ngIf="labels.length > 0">
<ion-list-header>Labels</ion-list-header>
<ion-item *ngFor="let label of labels" lines="none">
<ion-icon aria-hidden="true" slot="start" ios="bookmark-outline" md="bookmark-sharp"></ion-icon>
<ion-label>{{ label }}</ion-label>
</ion-item>
</ion-list>
</ion-content>
</ion-menu>
<ion-router-outlet id="main-content"></ion-router-outlet>
</ion-split-pane>
</ion-app>

View File

@ -0,0 +1,119 @@
ion-menu ion-content {
--background: var(--ion-item-background, var(--ion-background-color, #fff));
}
ion-menu.md ion-content {
--padding-start: 8px;
--padding-end: 8px;
--padding-top: 20px;
--padding-bottom: 20px;
}
ion-menu.md ion-list {
padding: 20px 0;
}
ion-menu.md ion-note {
margin-bottom: 30px;
}
ion-menu.md ion-list-header,
ion-menu.md ion-note {
padding-left: 10px;
}
ion-menu.md ion-list#inbox-list {
border-bottom: 1px solid var(--ion-color-step-150, #d7d8da);
}
ion-menu.md ion-list#inbox-list ion-list-header {
font-size: 22px;
font-weight: 600;
min-height: 20px;
}
ion-menu.md ion-list#labels-list ion-list-header {
font-size: 16px;
margin-bottom: 18px;
color: #757575;
min-height: 26px;
}
ion-menu.md ion-item {
--padding-start: 10px;
--padding-end: 10px;
border-radius: 4px;
}
ion-menu.md ion-item.selected {
--background: rgba(var(--ion-color-primary-rgb), 0.14);
}
ion-menu.md ion-item.selected ion-icon {
color: var(--ion-color-primary);
}
ion-menu.md ion-item ion-icon {
color: #616e7e;
}
ion-menu.md ion-item ion-label {
font-weight: 500;
}
ion-menu.ios ion-content {
--padding-bottom: 20px;
}
ion-menu.ios ion-list {
padding: 20px 0 0 0;
}
ion-menu.ios ion-note {
line-height: 24px;
margin-bottom: 20px;
}
ion-menu.ios ion-item {
--padding-start: 16px;
--padding-end: 16px;
--min-height: 50px;
}
ion-menu.ios ion-item.selected ion-icon {
color: var(--ion-color-primary);
}
ion-menu.ios ion-item ion-icon {
font-size: 24px;
color: #73849a;
}
ion-menu.ios ion-list#labels-list ion-list-header {
margin-bottom: 8px;
}
ion-menu.ios ion-list-header,
ion-menu.ios ion-note {
padding-left: 16px;
padding-right: 16px;
}
ion-menu.ios ion-note {
margin-bottom: 8px;
}
ion-note {
display: inline-block;
font-size: 16px;
color: var(--ion-color-medium-shade);
}
ion-item.selected {
--color: var(--ion-color-primary);
}

View File

@ -1,24 +1,44 @@
import {TestBed} from '@angular/core/testing';
import {RouterTestingModule} from '@angular/router/testing';
import {AppComponent} from './app.component';
import {NavigationComponent} from "./navigation/navigation.component";
import {KeycloakAngularModule} from "keycloak-angular";
import {MatSidenavModule} from "@angular/material/sidenav";
import {NoopAnimationsModule} from "@angular/platform-browser/animations";
import {MatToolbarModule} from "@angular/material/toolbar";
import {MatListModule} from "@angular/material/list";
import {MatIconModule} from "@angular/material/icon";
describe('AppComponent', () => {
beforeEach(() => TestBed.configureTestingModule({
imports: [RouterTestingModule, KeycloakAngularModule, MatSidenavModule, NoopAnimationsModule,
MatToolbarModule, MatListModule, MatIconModule],
declarations: [AppComponent, NavigationComponent]
}));
beforeEach(async () => {
TestBed.overrideComponent(AppComponent, {
add: {
imports: [RouterTestingModule]
}
});
});
it('should create the app', () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app).toBeTruthy();
});
it('should have menu labels', () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const app = fixture.nativeElement;
const menuItems = app.querySelectorAll('ion-label');
expect(menuItems.length).toEqual(12);
expect(menuItems[0].textContent).toContain('Inbox');
expect(menuItems[1].textContent).toContain('Outbox');
});
it('should have urls', () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const app = fixture.nativeElement;
const menuItems = app.querySelectorAll('ion-item');
expect(menuItems.length).toEqual(12);
expect(menuItems[0].getAttribute('ng-reflect-router-link')).toEqual(
'/folder/inbox'
);
expect(menuItems[1].getAttribute('ng-reflect-router-link')).toEqual(
'/folder/outbox'
);
});
});

View File

@ -1,13 +1,73 @@
import {CommonModule} from '@angular/common';
import {Component} from '@angular/core';
import {NavigationComponent} from "./navigation/navigation.component";
import {RouterOutlet} from "@angular/router";
import {RouterLink, RouterLinkActive} from '@angular/router';
import {
IonApp,
IonContent,
IonIcon,
IonItem,
IonLabel,
IonList,
IonListHeader,
IonMenu,
IonMenuToggle,
IonNote,
IonRouterOutlet,
IonSplitPane
} from '@ionic/angular/standalone';
import {addIcons} from 'ionicons';
import {
archiveOutline,
archiveSharp,
bookmarkOutline,
bookmarkSharp,
heartOutline,
heartSharp,
mailOutline,
mailSharp,
paperPlaneOutline,
paperPlaneSharp,
trashOutline,
trashSharp,
warningOutline,
warningSharp
} from 'ionicons/icons';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
templateUrl: 'app.component.html',
styleUrls: ['app.component.scss'],
standalone: true,
imports: [NavigationComponent, RouterOutlet]
imports: [RouterLink, RouterLinkActive, CommonModule, IonApp, IonSplitPane, IonMenu, IonContent, IonList,
IonListHeader, IonNote, IonMenuToggle, IonItem, IonIcon, IonLabel, IonRouterOutlet],
})
export class AppComponent {
public appPages = [
{title: 'Inbox', url: '/folder/inbox', icon: 'mail'},
{title: 'Outbox', url: '/folder/outbox', icon: 'paper-plane'},
{title: 'Favorites', url: '/folder/favorites', icon: 'heart'},
{title: 'Archived', url: '/folder/archived', icon: 'archive'},
{title: 'Trash', url: '/folder/trash', icon: 'trash'},
{title: 'Spam', url: '/folder/spam', icon: 'warning'},
];
public labels = [];
constructor() {
addIcons({
mailOutline,
mailSharp,
paperPlaneOutline,
paperPlaneSharp,
heartOutline,
heartSharp,
archiveOutline,
archiveSharp,
trashOutline,
trashSharp,
warningOutline,
warningSharp,
bookmarkOutline,
bookmarkSharp
});
}
}

View File

@ -1,7 +1,7 @@
import {APP_INITIALIZER, ApplicationConfig, isDevMode} from "@angular/core";
import {KeycloakBearerInterceptor, KeycloakService} from "keycloak-angular";
import {Location} from "@angular/common";
import {provideRouter, withComponentInputBinding} from "@angular/router";
import {provideRouter, RouteReuseStrategy, withComponentInputBinding} from "@angular/router";
import {ROOT_ROUTES} from "./app.routes";
import {provideStore} from "@ngrx/store";
import {provideEffects} from "@ngrx/effects";
@ -9,6 +9,7 @@ import {provideAnimations} from "@angular/platform-browser/animations";
import {HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi} from "@angular/common/http";
import {provideServiceWorker} from "@angular/service-worker";
import {environment} from "../environments/environment";
import {IonicRouteStrategy, provideIonicAngular} from "@ionic/angular/standalone";
function initializeKeycloak(keycloak: KeycloakService, locationService: Location) {
return () =>
@ -39,6 +40,8 @@ export const appConfig: ApplicationConfig = {
multi: true,
deps: [KeycloakService, Location],
},
{provide: RouteReuseStrategy, useClass: IonicRouteStrategy},
provideIonicAngular(),
provideRouter(ROOT_ROUTES, withComponentInputBinding()),
provideStore(),
provideEffects(),

View File

@ -1,6 +1,11 @@
import {Route} from "@angular/router";
import {Routes} from '@angular/router';
export const ROOT_ROUTES: Route[] = [
export const ROOT_ROUTES: Routes = [
{
path: '',
loadComponent: () => import("./dashboard/dashboard.component").then(mod => mod.DashboardComponent),
pathMatch: 'full',
},
{
path: 'permission-denied',
loadComponent: () => import("./permission-denied/permission-denied.component")
@ -14,9 +19,4 @@ export const ROOT_ROUTES: Route[] = [
path: 'privacy-policy',
loadComponent: () => import("./privacy-policy/privacy-policy.component").then(mod => mod.PrivacyPolicyComponent)
},
{
path: '',
loadComponent: () => import("./dashboard/dashboard.component").then(mod => mod.DashboardComponent),
pathMatch: 'full'
}
];

View File

@ -1,13 +1,22 @@
<div class="row">
<h1 class="mat-headline-5 mainContentColumn" i18n="page title">Timetable</h1>
</div>
<ion-header [translucent]="true">
<ion-toolbar>
<ion-buttons slot="start">
<ion-menu-button></ion-menu-button>
</ion-buttons>
<ion-title i18n="page title">Timetable</ion-title>
</ion-toolbar>
</ion-header>
<div class="row">
<div class="mainContentColumn">
<p class="mat-body-1" i18n="welcome text|A welcome to users">
<ion-content [fullscreen]="true">
<ion-header collapse="condense">
<ion-toolbar>
<ion-title size="large" i18n="page title">Timetable</ion-title>
</ion-toolbar>
</ion-header>
<div id="container">
<p i18n="welcome text|A welcome to users">
TODO
</p>
</div>
</div>
</ion-content>

View File

@ -1,10 +1,19 @@
import {Component} from '@angular/core';
import {IonButtons, IonContent, IonHeader, IonMenuButton, IonTitle, IonToolbar} from "@ionic/angular/standalone";
@Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.scss'],
standalone: true,
imports: [
IonButtons,
IonHeader,
IonMenuButton,
IonTitle,
IonToolbar,
IonContent
]
})
export class DashboardComponent {

View File

@ -1,5 +1,22 @@
<div class="row">
<div class="mainContentColumn">
<ion-header [translucent]="true">
<ion-toolbar>
<ion-buttons slot="start">
<ion-menu-button></ion-menu-button>
</ion-buttons>
<ion-title i18n="page title">Legal Notice</ion-title>
</ion-toolbar>
</ion-header>
<ion-content [fullscreen]="true">
<ion-header collapse="condense">
<ion-toolbar>
<ion-title size="large" i18n="page title">Legal Notice</ion-title>
</ion-toolbar>
</ion-header>
<ion-grid id="container">
<ion-row>
<ion-col>
<h2 i18n>Information provided according to Sec. 5 German Telemedia Act (TMG):</h2>
<p i18n>Jim Richard Martens<br/>
Flaßheide 45<br/>
@ -15,28 +32,48 @@
22525 Hamburg</p>
<h2 i18n>Liability for Contents</h2>
<p i18n>As service providers, we are liable for own contents of these websites according to Sec. 7, paragraph 1 German
<p i18n>As service providers, we are liable for own contents of these websites according to Sec. 7, paragraph 1
German
Telemedia Act (TMG). However, according to Sec. 8 to 10 German Telemedia Act (TMG), service providers are not
obligated to permanently monitor submitted or stored information or to search for evidences that indicate illegal
activities.</p> <p i18n>Legal obligations to removing information or to blocking the use of information remain unchallenged.
In this case, liability is only possible at the time of knowledge about a specific violation of law. Illegal contents
obligated to permanently monitor submitted or stored information or to search for evidences that indicate
illegal
activities.</p>
<p i18n>Legal obligations to removing information or to blocking the use of information remain unchallenged.
In this case, liability is only possible at the time of knowledge about a specific violation of law. Illegal
contents
will be removed immediately at the time we get knowledge of them.</p>
<h2 i18n>Liability for Links</h2>
<p i18n>Our offer includes links to external third party websites. We have no influence on the contents of those websites,
therefore we cannot guarantee for those contents. Providers or administrators of linked websites are always responsible
<p i18n>Our offer includes links to external third party websites. We have no influence on the contents of those
websites,
therefore we cannot guarantee for those contents. Providers or administrators of linked websites are always
responsible
for their own contents.</p>
<p i18n>The linked websites had been checked for possible violations of law at the time of the establishment of the link.
Illegal contents were not detected at the time of the linking. A permanent monitoring of the contents of linked
websites cannot be imposed without reasonable indications that there has been a violation of law. Illegal links
<p i18n>The linked websites had been checked for possible violations of law at the time of the establishment of
the
link.
Illegal contents were not detected at the time of the linking. A permanent monitoring of the contents of
linked
websites cannot be imposed without reasonable indications that there has been a violation of law. Illegal
links
will be removed immediately at the time we get knowledge of them.</p>
<h2 i18n>Copyright</h2>
<p i18n>Contents and compilations published on these websites by the providers are subject to German copyright laws.
Reproduction, editing, distribution as well as the use of any kind outside the scope of the copyright law require a
written permission of the author or originator. Downloads and copies of these websites are permitted for private use
<p i18n>Contents and compilations published on these websites by the providers are subject to German copyright
laws.
Reproduction, editing, distribution as well as the use of any kind outside the scope of the copyright law
require
a
written permission of the author or originator. Downloads and copies of these websites are permitted for
private
use
only.<br/> The commercial use of our contents without permission of the originator is prohibited.</p>
<p i18n>Copyright laws of third parties are respected as long as the contents on these websites do not originate from the
provider. Contributions of third parties on this site are indicated as such. However, if you notice any violations of
<p i18n>Copyright laws of third parties are respected as long as the contents on these websites do not originate
from the
provider. Contributions of third parties on this site are indicated as such. However, if you notice any
violations
of
copyright law, please inform us. Such contents will be removed immediately.</p>
<p></p>
</div>
</div>
</ion-col>
</ion-row>
</ion-grid>
</ion-content>

View File

@ -1,7 +1,5 @@
@use 'mixins';
@include mixins.centralColumnLayout();
p {
@include mixins.justifiedText();
}

View File

@ -1,9 +1,29 @@
import {Component} from '@angular/core';
import {
IonButtons, IonCol,
IonContent,
IonGrid,
IonHeader,
IonMenuButton, IonRow,
IonTitle,
IonToolbar
} from "@ionic/angular/standalone";
@Component({
selector: 'app-legal-notice',
standalone: true,
templateUrl: './legal-notice.component.html',
imports: [
IonButtons,
IonContent,
IonHeader,
IonMenuButton,
IonTitle,
IonToolbar,
IonGrid,
IonRow,
IonCol
],
styleUrls: ['./legal-notice.component.scss']
})
export class LegalNoticeComponent {

View File

@ -1,39 +0,0 @@
<mat-sidenav-container class="sidenav-container">
<mat-sidenav #drawer class="sidenav"
[attr.role]="'dialog'"
[mode]="'side'">
<mat-toolbar i18n="title|Title of the sidebar menu">Menu</mat-toolbar>
<mat-nav-list>
<a mat-list-item routerLink="legal-notice" i18n="link name|The name of the legal notice page">Legal Notice</a>
<a mat-list-item routerLink="privacy-policy" i18n="link name|The name of the privacy policy page">Privacy
Policy</a>
</mat-nav-list>
</mat-sidenav>
<mat-sidenav-content>
<mat-toolbar color="primary">
<button
type="button"
aria-label="Toggle sidenav"
mat-icon-button
(click)="drawer.toggle()">
<mat-icon aria-label="Side nav toggle icon">menu</mat-icon>
</button>
<button
type="button"
mat-icon-button
[routerLink]="''">
<mat-icon>home</mat-icon>
</button>
<span i18n="application title|The application title in the toolbar">Timetable</span>
<span class="spacer"></span>
<ng-container *ngIf="isLoggedIn$ | async; else loginButton">
<span class="logged-user" i18n>Logged in as {{loggedUserName$ | async}}</span>
<button mat-raised-button class="app-nav-icon" (click)="logout()" i18n>Logout</button>
</ng-container>
<ng-template #loginButton>
<button mat-raised-button class="app-nav-icon" (click)="login()" i18n>Login</button>
</ng-template>
</mat-toolbar>
<ng-content></ng-content>
</mat-sidenav-content>
</mat-sidenav-container>

View File

@ -1,25 +0,0 @@
.sidenav-container {
height: 100%;
}
.sidenav {
width: 200px;
}
.sidenav .mat-toolbar {
background: inherit;
}
.mat-toolbar.mat-primary {
position: sticky;
top: 0;
z-index: 5;
}
.spacer {
flex: 1 1 auto;
}
.logged-user {
margin-right: 1em;
}

View File

@ -1,42 +0,0 @@
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
import {MatButtonModule} from '@angular/material/button';
import {MatIconModule} from '@angular/material/icon';
import {MatListModule} from '@angular/material/list';
import {MatSidenavModule} from '@angular/material/sidenav';
import {MatToolbarModule} from '@angular/material/toolbar';
import {NavigationComponent} from './navigation.component';
import {KeycloakAngularModule} from "keycloak-angular";
import {RouterTestingModule} from "@angular/router/testing";
describe('NavigationComponent', () => {
let component: NavigationComponent;
let fixture: ComponentFixture<NavigationComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [NavigationComponent],
imports: [
NoopAnimationsModule,
MatButtonModule,
MatIconModule,
MatListModule,
MatSidenavModule,
MatToolbarModule,
KeycloakAngularModule,
RouterTestingModule
]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(NavigationComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should compile', () => {
expect(component).toBeTruthy();
});
});

View File

@ -1,57 +0,0 @@
import {Component} from '@angular/core';
import {KeycloakService} from "keycloak-angular";
import {from, of, switchMap} from "rxjs";
import {ActivatedRoute, RouterLink} from "@angular/router";
import {MatSidenavModule} from "@angular/material/sidenav";
import {MatToolbarModule} from "@angular/material/toolbar";
import {MatListModule} from "@angular/material/list";
import {MatButtonModule} from "@angular/material/button";
import {MatIconModule} from "@angular/material/icon";
import {AsyncPipe, NgIf} from "@angular/common";
@Component({
selector: 'app-navigation',
templateUrl: './navigation.component.html',
styleUrls: ['./navigation.component.scss'],
imports: [
MatSidenavModule,
MatToolbarModule,
MatListModule,
MatButtonModule,
MatIconModule,
NgIf,
AsyncPipe,
RouterLink
],
standalone: true
})
export class NavigationComponent {
loggedUserName$;
isLoggedIn$;
url: string;
constructor(private keycloakService: KeycloakService,
route: ActivatedRoute) {
this.url = route.snapshot.url.join('');
this.isLoggedIn$ = from(this.keycloakService.isLoggedIn());
this.loggedUserName$ = this.isLoggedIn$.pipe(
switchMap(loggedIn => {
if (loggedIn) {
return of(this.keycloakService.getUsername());
} else {
return of('');
}
})
)
}
login(): void {
this.keycloakService.login({
redirectUri: window.location.origin + this.url,
});
}
logout(): void {
this.keycloakService.logout(window.location.origin);
}
}

View File

@ -1,3 +1,7 @@
<div class="container">
<ion-grid>
<ion-row>
<ion-col>
<h1>Permission Denied to Access this Page!!!!</h1>
</div>
</ion-col>
</ion-row>
</ion-grid>

View File

@ -1,10 +1,12 @@
import {Component} from '@angular/core';
import {IonCol, IonGrid, IonRow} from "@ionic/angular/standalone";
@Component({
selector: 'app-permission-denied',
templateUrl: './permission-denied.component.html',
styleUrls: ['./permission-denied.component.scss'],
standalone: true
standalone: true,
imports: [IonGrid, IonRow, IonCol]
})
export class PermissionDeniedComponent {

View File

@ -1,35 +1,74 @@
<div class="row">
<div class="mainContentColumn">
<ion-header [translucent]="true">
<ion-toolbar>
<ion-buttons slot="start">
<ion-menu-button></ion-menu-button>
</ion-buttons>
<ion-title i18n="page title">Privacy Policy</ion-title>
</ion-toolbar>
</ion-header>
<ion-content [fullscreen]="true">
<ion-header collapse="condense">
<ion-toolbar>
<ion-title size="large" i18n="page title">Privacy Policy</ion-title>
</ion-toolbar>
</ion-header>
<ion-grid>
<ion-row>
<ion-col>
<h2 i18n>1. An overview of data protection</h2>
<h3 i18n>General</h3>
<p i18n>The following gives a simple overview of what happens to your personal information when you visit our website.
Personal information is any data with which you could be personally identified. Detailed information on the subject
<p i18n>The following gives a simple overview of what happens to your personal information when you visit our
website.
Personal information is any data with which you could be personally identified. Detailed information on the
subject
of data protection can be found in our privacy policy found below.</p>
<h3 i18n>Data collection on our website</h3>
<p><strong i18n>Who is responsible for the data collection on this website?</strong></p>
<p i18n>The data collected on this website are processed by the website operator. The operator's contact details can be found
<p i18n>The data collected on this website are processed by the website operator. The operator's contact details
can
be found
in the website's required legal notice.</p>
<p><strong i18n>How do we collect your data?</strong></p>
<p i18n>Some data are collected when you provide it to us. This could, for example, be data you enter on a contact form.</p>
<p i18n>Other data are collected automatically by our IT systems when you visit the website. These data are primarily technical
data such as the browser and operating system you are using or when you accessed the page. These data are collected
<p i18n>Some data are collected when you provide it to us. This could, for example, be data you enter on a
contact
form.</p>
<p i18n>Other data are collected automatically by our IT systems when you visit the website. These data are
primarily technical
data such as the browser and operating system you are using or when you accessed the page. These data are
collected
automatically as soon as you enter our website.</p>
<p><strong i18n>What do we use your data for?</strong></p>
<p i18n>Part of the data is collected to ensure the proper functioning of the website. Other data can be used to analyze how
<p i18n>Part of the data is collected to ensure the proper functioning of the website. Other data can be used to
analyze how
visitors use the site.</p>
<p><strong i18n>What rights do you have regarding your data?</strong></p>
<p i18n>You always have the right to request information about your stored data, its origin, its recipients, and the purpose
of its collection at no charge. You also have the right to request that it be corrected, blocked, or deleted. You can
contact us at any time using the address given in the legal notice if you have further questions about the issue of
privacy and data protection. You may also, of course, file a complaint with the competent regulatory authorities.</p>
<p i18n>You always have the right to request information about your stored data, its origin, its recipients, and
the
purpose
of its collection at no charge. You also have the right to request that it be corrected, blocked, or deleted.
You
can
contact us at any time using the address given in the legal notice if you have further questions about the
issue
of
privacy and data protection. You may also, of course, file a complaint with the competent regulatory
authorities.</p>
<h2 i18n>2. General information and mandatory information</h2>
<h3 i18n>Data protection</h3>
<p i18n>The operators of this website take the protection of your personal data very seriously. We treat your personal data as
<p i18n>The operators of this website take the protection of your personal data very seriously. We treat your
personal data as
confidential and in accordance with the statutory data protection regulations and this privacy policy.</p>
<p i18n>If you use this website, various pieces of personal data will be collected. Personal information is any data with which
you could be personally identified. This privacy policy explains what information we collect and what we use it for.
<p i18n>If you use this website, various pieces of personal data will be collected. Personal information is any
data
with which
you could be personally identified. This privacy policy explains what information we collect and what we use
it
for.
It also explains how and for what purpose this happens.</p>
<p i18n>Please note that data transmitted via the internet (e.g. via email communication) may be subject to security breaches.
<p i18n>Please note that data transmitted via the internet (e.g. via email communication) may be subject to
security
breaches.
Complete protection of your data from third-party access is not possible.</p>
<h3 i18n>Notice concerning the party responsible for this website</h3>
<p i18n>The party responsible for processing data on this website is:</p>
@ -39,22 +78,31 @@
<p i18n>Telephone: 04021082122<br/>
Email: admin@2martens.de</p>
<p i18n>The responsible party is the natural or legal person who alone or jointly with others decides on the purposes and means
<p i18n>The responsible party is the natural or legal person who alone or jointly with others decides on the
purposes and means
of processing personal data (names, email addresses, etc.).</p>
<h3 i18n>SSL or TLS encryption</h3>
<p i18n>This site uses SSL or TLS encryption for security reasons and for the protection of the
transmission of confidential content, such as the inquiries you send to us as the site operator. You can recognize an
encrypted connection in your browser's address line when it changes from "http://" to "https://" and the lock icon is
transmission of confidential content, such as the inquiries you send to us as the site operator. You can
recognize
an
encrypted connection in your browser's address line when it changes from "http://" to "https://" and the lock
icon
is
displayed in your browser's address bar.</p>
<p i18n>If SSL or TLS encryption is activated, the data you transfer to us cannot be read by third parties.</p>
<h3 i18n>Opposition to promotional emails</h3>
<p i18n>We hereby expressly prohibit the use of contact data published in the context
of website legal notice requirements with regard to sending promotional and informational materials not expressly
requested. The website operator reserves the right to take specific legal action if unsolicited advertising material,
of website legal notice requirements with regard to sending promotional and informational materials not
expressly
requested. The website operator reserves the right to take specific legal action if unsolicited advertising
material,
such as email spam, is received.</p>
<h2 i18n>3. Data collection on our website</h2>
<h3 i18n>Server log files</h3>
<p i18n>The website provider automatically collects and stores information that your browser automatically transmits to us in
<p i18n>The website provider automatically collects and stores information that your browser automatically
transmits
to us in
"server log files". These are:</p>
<ul>
<li i18n>Browser type and browser version</li>
@ -65,7 +113,10 @@
<li i18n>IP address</li>
</ul>
<p i18n>These data will not be combined with data from other sources.</p>
<p i18n>The basis for data processing is Art. 6 (1) (b) DSGVO, which allows the processing of data to fulfill a contract or
<p i18n>The basis for data processing is Art. 6 (1) (b) DSGVO, which allows the processing of data to fulfill a
contract or
for measures preliminary to a contract.</p>
</div>
</div>
</ion-col>
</ion-row>
</ion-grid>
</ion-content>

View File

@ -1,7 +1,5 @@
@use 'mixins';
@include mixins.centralColumnLayout();
p {
@include mixins.justifiedText();
}

View File

@ -1,10 +1,30 @@
import {Component} from '@angular/core';
import {
IonButtons, IonCol,
IonContent,
IonGrid,
IonHeader,
IonMenuButton, IonRow,
IonTitle,
IonToolbar
} from "@ionic/angular/standalone";
@Component({
selector: 'app-privacy-policy',
standalone: true,
templateUrl: './privacy-policy.component.html',
styleUrls: ['./privacy-policy.component.scss']
styleUrls: ['./privacy-policy.component.scss'],
imports: [
IonButtons,
IonContent,
IonHeader,
IonMenuButton,
IonTitle,
IonToolbar,
IonGrid,
IonRow,
IonCol
],
})
export class PrivacyPolicyComponent {

View File

BIN
src/assets/icon/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 930 B

16
src/assets/shapes.svg Normal file
View File

@ -0,0 +1,16 @@
<svg width="350" height="140" xmlns="http://www.w3.org/2000/svg" style="background:#f6f7f9">
<g fill="none" fill-rule="evenodd">
<path fill="#F04141" style="mix-blend-mode:multiply" d="M61.905-34.23l96.194 54.51-66.982 54.512L22 34.887z"/>
<circle fill="#10DC60" style="mix-blend-mode:multiply" cx="155.5" cy="135.5" r="57.5"/>
<path fill="#3880FF" style="mix-blend-mode:multiply" d="M208.538 9.513l84.417 15.392L223.93 93.93z"/>
<path fill="#FFCE00" style="mix-blend-mode:multiply"
d="M268.625 106.557l46.332-26.75 46.332 26.75v53.5l-46.332 26.75-46.332-26.75z"/>
<circle fill="#7044FF" style="mix-blend-mode:multiply" cx="299.5" cy="9.5" r="38.5"/>
<rect fill="#11D3EA" style="mix-blend-mode:multiply" transform="rotate(-60 148.47 37.886)" x="143.372" y="-7.056"
width="10.196" height="89.884" rx="5.098"/>
<path
d="M-25.389 74.253l84.86 8.107c5.498.525 9.53 5.407 9.004 10.905a10 10 0 0 1-.057.477l-12.36 85.671a10.002 10.002 0 0 1-11.634 8.42l-86.351-15.226c-5.44-.959-9.07-6.145-8.112-11.584l13.851-78.551a10 10 0 0 1 10.799-8.219z"
fill="#7044FF" style="mix-blend-mode:multiply"/>
<circle fill="#0CD1E8" style="mix-blend-mode:multiply" cx="273.5" cy="106.5" r="20.5"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1,4 +1,5 @@
export const environment = {
production: false,
backendURL: "http://localhost:12000/timetable/v1",
keycloakURL: "https://id.2martens.de",
realm: "2martens",

View File

@ -4,3 +4,12 @@ export const environment = {
realm: "2martens",
clientId: "tsw-timetable-frontend"
};
/*
* For easier debugging in development mode, you can import the following file
* to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
*
* This import should be commented out in production mode because it will have a negative impact
* on performance if an error is thrown.
*/
// import 'zone.js/plugins/zone-error'; // Included with Angular CLI.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 948 B

26
src/global.scss Normal file
View File

@ -0,0 +1,26 @@
/*
* App Global CSS
* ----------------------------------------------------------------------------
* Put style rules here that you want to apply globally. These styles are for
* the entire app and not just one component. Additionally, this file can be
* used as an entry point to import other CSS/Sass files to be included in the
* output CSS.
* For more information on global stylesheets, visit the documentation:
* https://ionicframework.com/docs/layout/global-stylesheets
*/
/* Core CSS required for Ionic components to work properly */
@import "@ionic/angular/css/core.css";
/* Basic CSS for apps built with Ionic */
@import "@ionic/angular/css/normalize.css";
@import "@ionic/angular/css/structure.css";
@import "@ionic/angular/css/typography.css";
@import "@ionic/angular/css/display.css";
/* Optional CSS utils that can be commented out */
@import "@ionic/angular/css/padding.css";
@import "@ionic/angular/css/float-elements.css";
@import "@ionic/angular/css/text-alignment.css";
@import "@ionic/angular/css/text-transformation.css";
@import "@ionic/angular/css/flex-utils.css";

View File

@ -1,18 +1,21 @@
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta charset="utf-8"/>
<title i18n="application title|Title of browser tab">Timetable</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link rel="preconnect" href="https://fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&amp;display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<base href="/"/>
<meta name="color-scheme" content="light dark"/>
<meta name="viewport"
content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
<meta name="format-detection" content="telephone=no"/>
<meta name="msapplication-tap-highlight" content="no"/>
<link rel="icon" type="image/png" href="assets/icon/favicon.png"/>
<!-- add to homescreen for ios -->
<meta name="apple-mobile-web-app-capable" content="yes"/>
<meta name="apple-mobile-web-app-status-bar-style" content="black"/>
<link rel="manifest" href="manifest.webmanifest">
<meta name="theme-color" content="#1976d2">
</head>
<body class="mat-typography">
<body>
<app-root></app-root>
<noscript>Please enable JavaScript to continue using this application.</noscript>
</body>

View File

@ -210,73 +210,6 @@
<context context-type="linenumber">37,39</context>
</context-group>
</trans-unit>
<trans-unit id="4330763172337088682" datatype="html">
<source>Menu</source>
<target>Menü</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/navigation/navigation.component.html</context>
<context context-type="linenumber">5</context>
</context-group>
<note priority="1" from="description">Title of the sidebar menu</note>
<note priority="1" from="meaning">title</note>
</trans-unit>
<trans-unit id="1213249934851573317" datatype="html">
<source>Legal Notice</source>
<target>Impressum</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/navigation/navigation.component.html</context>
<context context-type="linenumber">7</context>
</context-group>
<note priority="1" from="description">The name of the legal notice page</note>
<note priority="1" from="meaning">link name</note>
</trans-unit>
<trans-unit id="6132019390784336883" datatype="html">
<source>Privacy Policy</source>
<target>Datenschutzerklärung</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/navigation/navigation.component.html</context>
<context context-type="linenumber">8,9</context>
</context-group>
<note priority="1" from="description">The name of the privacy policy page</note>
<note priority="1" from="meaning">link name</note>
</trans-unit>
<trans-unit id="8837138757935170328" datatype="html">
<source>Timetable</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/navigation/navigation.component.html</context>
<context context-type="linenumber">27</context>
</context-group>
<note priority="1" from="description">The application title in the toolbar</note>
<note priority="1" from="meaning">application title</note>
</trans-unit>
<trans-unit id="2448391510242468907" datatype="html">
<source>Logged in as
<x id="INTERPOLATION" equiv-text="{{loggedUserName$ | async}}"/>
</source>
<target>Eingeloggt als
<x id="INTERPOLATION" equiv-text="{{loggedUserName$ | async}}"/>
</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/navigation/navigation.component.html</context>
<context context-type="linenumber">30,31</context>
</context-group>
</trans-unit>
<trans-unit id="3797778920049399855" datatype="html">
<source>Logout</source>
<target>Ausloggen</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/navigation/navigation.component.html</context>
<context context-type="linenumber">31</context>
</context-group>
</trans-unit>
<trans-unit id="2454050363478003966" datatype="html">
<source>Login</source>
<target>Login</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/navigation/navigation.component.html</context>
<context context-type="linenumber">34</context>
</context-group>
</trans-unit>
<trans-unit id="7094009074606381897" datatype="html">
<source>1. An overview of data protection</source>
<target>1. Datenschutz auf einen Blick</target>

View File

@ -161,65 +161,6 @@
<context context-type="linenumber">37,39</context>
</context-group>
</trans-unit>
<trans-unit id="4330763172337088682" datatype="html">
<source>Menu</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/navigation/navigation.component.html</context>
<context context-type="linenumber">5</context>
</context-group>
<note priority="1" from="description">Title of the sidebar menu</note>
<note priority="1" from="meaning">title</note>
</trans-unit>
<trans-unit id="1213249934851573317" datatype="html">
<source>Legal Notice</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/navigation/navigation.component.html</context>
<context context-type="linenumber">7</context>
</context-group>
<note priority="1" from="description">The name of the legal notice page</note>
<note priority="1" from="meaning">link name</note>
</trans-unit>
<trans-unit id="6132019390784336883" datatype="html">
<source>Privacy Policy</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/navigation/navigation.component.html</context>
<context context-type="linenumber">8,9</context>
</context-group>
<note priority="1" from="description">The name of the privacy policy page</note>
<note priority="1" from="meaning">link name</note>
</trans-unit>
<trans-unit id="8837138757935170328" datatype="html">
<source>Timetable</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/navigation/navigation.component.html</context>
<context context-type="linenumber">27</context>
</context-group>
<note priority="1" from="description">The application title in the toolbar</note>
<note priority="1" from="meaning">application title</note>
</trans-unit>
<trans-unit id="2448391510242468907" datatype="html">
<source>Logged in as
<x id="INTERPOLATION" equiv-text="{{loggedUserName$ | async}}"/>
</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/navigation/navigation.component.html</context>
<context context-type="linenumber">30,31</context>
</context-group>
</trans-unit>
<trans-unit id="3797778920049399855" datatype="html">
<source>Logout</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/navigation/navigation.component.html</context>
<context context-type="linenumber">31</context>
</context-group>
</trans-unit>
<trans-unit id="2454050363478003966" datatype="html">
<source>Login</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/navigation/navigation.component.html</context>
<context context-type="linenumber">34</context>
</context-group>
</trans-unit>
<trans-unit id="7094009074606381897" datatype="html">
<source>1. An overview of data protection</source>
<context-group purpose="location">

View File

@ -1,8 +1,7 @@
/// <reference types="@angular/localize" />
import {bootstrapApplication} from "@angular/platform-browser";
import {AppComponent} from "./app/app.component";
import "@angular/localize/init";
import {bootstrapApplication} from '@angular/platform-browser';
import {AppComponent} from './app/app.component';
import {appConfig} from "./app/app.config";
bootstrapApplication(AppComponent, appConfig).catch(err => console.error(err));

55
src/polyfills.ts Normal file
View File

@ -0,0 +1,55 @@
/**
* 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.
*
* (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
* (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
* (window 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
*
* (window as any).__Zone_enable_cross_context_check = true;
*
*/
import './zone-flags';
/***************************************************************************************************
* Zone JS is required by default for Angular itself.
*/
import 'zone.js'; // Included with Angular CLI.
/***************************************************************************************************
* APPLICATION IMPORTS
*/

View File

@ -1,57 +0,0 @@
@mixin container() {
display: flex;
}
@mixin flexDirection($value: row) {
flex-direction: $value;
}
@mixin flexWrap($value: nowrap) {
flex-wrap: $value;
}
@mixin justifyContent($value) {
justify-content: $value;
}
@mixin alignItems($value) {
align-items: $value;
}
@mixin order($order: 0) {
order: $order;
}
@mixin flex($grow: 0, $shrink: 1, $basis: auto) {
flex: $grow $shrink $basis;
}
.row {
@include container();
@include flexDirection(row);
@include justifyContent(center);
@include alignItems(stretch)
}
// Two-column layout with one main column and one sidebar column (left or right)
@mixin twoColumnLayout() {
.mainContentColumn {
@include flex(9, 0);
}
.sidebarColumn {
@include flex(3, 0)
}
}
// One column that does not grow but takes up 60% of the space
@mixin centralColumnLayout() {
.mainContentColumn {
@include flex(0, 1, 60%);
}
}
@mixin justifiedText() {
text-align: justify;
text-justify: inter-word;
}

View File

@ -1,4 +0,0 @@
/* You can add global styles to this file, and also import other style files */
html, body { height: 100%; }
body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; }

11
src/test.ts Normal file
View File

@ -0,0 +1,11 @@
// 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';
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting(),
);

28
src/theme/_mixins.scss Normal file
View File

@ -0,0 +1,28 @@
@mixin flexDirection($value: row) {
flex-direction: $value;
}
@mixin flexWrap($value: nowrap) {
flex-wrap: $value;
}
@mixin justifyContent($value) {
justify-content: $value;
}
@mixin alignItems($value) {
align-items: $value;
}
@mixin order($order: 0) {
order: $order;
}
@mixin flex($grow: 0, $shrink: 1, $basis: auto) {
flex: $grow $shrink $basis;
}
@mixin justifiedText() {
text-align: justify;
text-justify: inter-word;
}

244
src/theme/variables.scss Normal file
View File

@ -0,0 +1,244 @@
// Ionic Variables and Theming. For more info, please see:
// http://ionicframework.com/docs/theming/
/** Ionic CSS Variables **/
:root {
/** primary **/
--ion-color-primary: #3880ff;
--ion-color-primary-rgb: 56, 128, 255;
--ion-color-primary-contrast: #ffffff;
--ion-color-primary-contrast-rgb: 255, 255, 255;
--ion-color-primary-shade: #3171e0;
--ion-color-primary-tint: #4c8dff;
/** secondary **/
--ion-color-secondary: #3dc2ff;
--ion-color-secondary-rgb: 61, 194, 255;
--ion-color-secondary-contrast: #ffffff;
--ion-color-secondary-contrast-rgb: 255, 255, 255;
--ion-color-secondary-shade: #36abe0;
--ion-color-secondary-tint: #50c8ff;
/** tertiary **/
--ion-color-tertiary: #5260ff;
--ion-color-tertiary-rgb: 82, 96, 255;
--ion-color-tertiary-contrast: #ffffff;
--ion-color-tertiary-contrast-rgb: 255, 255, 255;
--ion-color-tertiary-shade: #4854e0;
--ion-color-tertiary-tint: #6370ff;
/** success **/
--ion-color-success: #2dd36f;
--ion-color-success-rgb: 45, 211, 111;
--ion-color-success-contrast: #ffffff;
--ion-color-success-contrast-rgb: 255, 255, 255;
--ion-color-success-shade: #28ba62;
--ion-color-success-tint: #42d77d;
/** warning **/
--ion-color-warning: #ffc409;
--ion-color-warning-rgb: 255, 196, 9;
--ion-color-warning-contrast: #000000;
--ion-color-warning-contrast-rgb: 0, 0, 0;
--ion-color-warning-shade: #e0ac08;
--ion-color-warning-tint: #ffca22;
/** danger **/
--ion-color-danger: #eb445a;
--ion-color-danger-rgb: 235, 68, 90;
--ion-color-danger-contrast: #ffffff;
--ion-color-danger-contrast-rgb: 255, 255, 255;
--ion-color-danger-shade: #cf3c4f;
--ion-color-danger-tint: #ed576b;
/** dark **/
--ion-color-dark: #222428;
--ion-color-dark-rgb: 34, 36, 40;
--ion-color-dark-contrast: #ffffff;
--ion-color-dark-contrast-rgb: 255, 255, 255;
--ion-color-dark-shade: #1e2023;
--ion-color-dark-tint: #383a3e;
/** medium **/
--ion-color-medium: #92949c;
--ion-color-medium-rgb: 146, 148, 156;
--ion-color-medium-contrast: #ffffff;
--ion-color-medium-contrast-rgb: 255, 255, 255;
--ion-color-medium-shade: #808289;
--ion-color-medium-tint: #9d9fa6;
/** light **/
--ion-color-light: #f4f5f8;
--ion-color-light-rgb: 244, 245, 248;
--ion-color-light-contrast: #000000;
--ion-color-light-contrast-rgb: 0, 0, 0;
--ion-color-light-shade: #d7d8da;
--ion-color-light-tint: #f5f6f9;
}
@media (prefers-color-scheme: dark) {
/*
* Dark Colors
* -------------------------------------------
*/
body {
--ion-color-primary: #428cff;
--ion-color-primary-rgb: 66, 140, 255;
--ion-color-primary-contrast: #ffffff;
--ion-color-primary-contrast-rgb: 255, 255, 255;
--ion-color-primary-shade: #3a7be0;
--ion-color-primary-tint: #5598ff;
--ion-color-secondary: #50c8ff;
--ion-color-secondary-rgb: 80, 200, 255;
--ion-color-secondary-contrast: #ffffff;
--ion-color-secondary-contrast-rgb: 255, 255, 255;
--ion-color-secondary-shade: #46b0e0;
--ion-color-secondary-tint: #62ceff;
--ion-color-tertiary: #6a64ff;
--ion-color-tertiary-rgb: 106, 100, 255;
--ion-color-tertiary-contrast: #ffffff;
--ion-color-tertiary-contrast-rgb: 255, 255, 255;
--ion-color-tertiary-shade: #5d58e0;
--ion-color-tertiary-tint: #7974ff;
--ion-color-success: #2fdf75;
--ion-color-success-rgb: 47, 223, 117;
--ion-color-success-contrast: #000000;
--ion-color-success-contrast-rgb: 0, 0, 0;
--ion-color-success-shade: #29c467;
--ion-color-success-tint: #44e283;
--ion-color-warning: #ffd534;
--ion-color-warning-rgb: 255, 213, 52;
--ion-color-warning-contrast: #000000;
--ion-color-warning-contrast-rgb: 0, 0, 0;
--ion-color-warning-shade: #e0bb2e;
--ion-color-warning-tint: #ffd948;
--ion-color-danger: #ff4961;
--ion-color-danger-rgb: 255, 73, 97;
--ion-color-danger-contrast: #ffffff;
--ion-color-danger-contrast-rgb: 255, 255, 255;
--ion-color-danger-shade: #e04055;
--ion-color-danger-tint: #ff5b71;
--ion-color-dark: #f4f5f8;
--ion-color-dark-rgb: 244, 245, 248;
--ion-color-dark-contrast: #000000;
--ion-color-dark-contrast-rgb: 0, 0, 0;
--ion-color-dark-shade: #d7d8da;
--ion-color-dark-tint: #f5f6f9;
--ion-color-medium: #989aa2;
--ion-color-medium-rgb: 152, 154, 162;
--ion-color-medium-contrast: #000000;
--ion-color-medium-contrast-rgb: 0, 0, 0;
--ion-color-medium-shade: #86888f;
--ion-color-medium-tint: #a2a4ab;
--ion-color-light: #222428;
--ion-color-light-rgb: 34, 36, 40;
--ion-color-light-contrast: #ffffff;
--ion-color-light-contrast-rgb: 255, 255, 255;
--ion-color-light-shade: #1e2023;
--ion-color-light-tint: #383a3e;
}
/*
* iOS Dark Theme
* -------------------------------------------
*/
.ios body {
--ion-background-color: #000000;
--ion-background-color-rgb: 0, 0, 0;
--ion-text-color: #ffffff;
--ion-text-color-rgb: 255, 255, 255;
--ion-color-step-50: #0d0d0d;
--ion-color-step-100: #1a1a1a;
--ion-color-step-150: #262626;
--ion-color-step-200: #333333;
--ion-color-step-250: #404040;
--ion-color-step-300: #4d4d4d;
--ion-color-step-350: #595959;
--ion-color-step-400: #666666;
--ion-color-step-450: #737373;
--ion-color-step-500: #808080;
--ion-color-step-550: #8c8c8c;
--ion-color-step-600: #999999;
--ion-color-step-650: #a6a6a6;
--ion-color-step-700: #b3b3b3;
--ion-color-step-750: #bfbfbf;
--ion-color-step-800: #cccccc;
--ion-color-step-850: #d9d9d9;
--ion-color-step-900: #e6e6e6;
--ion-color-step-950: #f2f2f2;
--ion-item-background: #000000;
--ion-card-background: #1c1c1d;
}
.ios ion-modal {
--ion-background-color: var(--ion-color-step-100);
--ion-toolbar-background: var(--ion-color-step-150);
--ion-toolbar-border-color: var(--ion-color-step-250);
}
/*
* Material Design Dark Theme
* -------------------------------------------
*/
.md body {
--ion-background-color: #121212;
--ion-background-color-rgb: 18, 18, 18;
--ion-text-color: #ffffff;
--ion-text-color-rgb: 255, 255, 255;
--ion-border-color: #222222;
--ion-color-step-50: #1e1e1e;
--ion-color-step-100: #2a2a2a;
--ion-color-step-150: #363636;
--ion-color-step-200: #414141;
--ion-color-step-250: #4d4d4d;
--ion-color-step-300: #595959;
--ion-color-step-350: #656565;
--ion-color-step-400: #717171;
--ion-color-step-450: #7d7d7d;
--ion-color-step-500: #898989;
--ion-color-step-550: #949494;
--ion-color-step-600: #a0a0a0;
--ion-color-step-650: #acacac;
--ion-color-step-700: #b8b8b8;
--ion-color-step-750: #c4c4c4;
--ion-color-step-800: #d0d0d0;
--ion-color-step-850: #dbdbdb;
--ion-color-step-900: #e7e7e7;
--ion-color-step-950: #f3f3f3;
--ion-item-background: #1e1e1e;
--ion-toolbar-background: #1f1f1f;
--ion-tab-bar-background: #1f1f1f;
--ion-card-background: #1e1e1e;
}
}
html {
/*
* For more information on dynamic font scaling, visit the documentation:
* https://ionicframework.com/docs/layout/dynamic-font-scaling
*/
--ion-dynamic-font: var(--ion-default-dynamic-font);
}

6
src/zone-flags.ts Normal file
View File

@ -0,0 +1,6 @@
/**
* Prevents Angular change detection from
* running with certain Web Component callbacks
*/
// eslint-disable-next-line no-underscore-dangle
(window as any).__Zone_disable_customElements = true;

View File

@ -8,7 +8,8 @@
]
},
"files": [
"src/main.ts"
"src/main.ts",
"src/polyfills.ts"
],
"include": [
"src/**/*.d.ts"

View File

@ -16,14 +16,14 @@
"experimentalDecorators": true,
"moduleResolution": "node",
"importHelpers": true,
"target": "ES2022",
"module": "ES2022",
"target": "es2022",
"module": "es2022",
"esModuleInterop": true,
"useDefineForClassFields": true,
"lib": [
"ES2022",
"es2022",
"dom"
]
],
"useDefineForClassFields": true
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,

View File

@ -8,6 +8,10 @@
"@angular/localize"
]
},
"files": [
"src/test.ts",
"src/polyfills.ts"
],
"include": [
"src/**/*.spec.ts",
"src/**/*.d.ts"