Added theming
All checks were successful
Build dev on push / build-api (push) Successful in 7s
Build dev on push / build-redirector (push) Successful in 7s
Build dev on push / build-web (push) Successful in 48s

This commit is contained in:
Sven Heidemann 2025-01-10 21:57:47 +01:00
parent 581462d22a
commit 865e6465cf
11 changed files with 830 additions and 391 deletions

View File

@ -1 +1 @@
1.1.0
1.2.0

View File

@ -1,4 +1,4 @@
<main *ngIf="isLoggedIn && !hideUI; else home">
<main *ngIf="isLoggedIn && !hideUI; else home" [class]="theme">
<app-header></app-header>
<div class="app">

View File

@ -1,16 +1,17 @@
import { Component, OnDestroy } from "@angular/core";
import { SidebarService } from "src/app/service/sidebar.service";
import { Subject } from "rxjs";
import { takeUntil } from "rxjs/operators";
import { AuthService } from "src/app/service/auth.service";
import { GuiService } from "src/app/service/gui.service";
import { Component, OnDestroy } from '@angular/core';
import { SidebarService } from 'src/app/service/sidebar.service';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { AuthService } from 'src/app/service/auth.service';
import { GuiService } from 'src/app/service/gui.service';
@Component({
selector: "app-root",
templateUrl: "./app.component.html",
styleUrl: "./app.component.scss",
selector: 'app-root',
templateUrl: './app.component.html',
styleUrl: './app.component.scss',
})
export class AppComponent implements OnDestroy {
theme = 'open-redirect';
showSidebar = false;
hideUI = false;
isLoggedIn = false;
@ -19,23 +20,27 @@ export class AppComponent implements OnDestroy {
constructor(
private sidebar: SidebarService,
private auth: AuthService,
private gui: GuiService,
private gui: GuiService
) {
this.auth.loadUser();
this.auth.user$.pipe(takeUntil(this.unsubscribe$)).subscribe((user) => {
this.auth.user$.pipe(takeUntil(this.unsubscribe$)).subscribe(user => {
this.isLoggedIn = user !== null && user !== undefined;
});
this.sidebar.visible$
.pipe(takeUntil(this.unsubscribe$))
.subscribe((visible) => {
.subscribe(visible => {
this.showSidebar = visible;
});
this.gui.hideGui$.pipe(takeUntil(this.unsubscribe$)).subscribe((hide) => {
this.gui.hideGui$.pipe(takeUntil(this.unsubscribe$)).subscribe(hide => {
this.hideUI = hide;
});
this.gui.theme$.pipe(takeUntil(this.unsubscribe$)).subscribe(theme => {
this.theme = theme;
});
}
ngOnDestroy() {

View File

@ -17,6 +17,18 @@
</div>
<div class="flex items-center justify-center">
<div class="flex items-center justify-center">
<p-button
type="button"
icon="pi pi-palette"
class="btn icon-btn p-button-text"
(onClick)="themeMenu.toggle($event)"></p-button>
<p-menu
#themeMenu
[popup]="true"
[model]="themeList"
class="lang-menu"></p-menu>
</div>
<div class="flex items-center justify-center">
<p-button
type="button"

View File

@ -1,21 +1,24 @@
import { Component, OnDestroy, OnInit } from "@angular/core";
import { MenuItem, PrimeNGConfig } from "primeng/api";
import { Subject } from "rxjs";
import { takeUntil } from "rxjs/operators";
import { TranslateService } from "@ngx-translate/core";
import { GuiService } from "src/app/service/gui.service";
import { User } from "src/app/model/auth/user";
import { AuthService } from "src/app/service/auth.service";
import { MenuElement } from "src/app/model/view/menu-element";
import { SidebarService } from "src/app/service/sidebar.service";
import { Component, OnDestroy, OnInit } from '@angular/core';
import { MenuItem, PrimeNGConfig } from 'primeng/api';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { TranslateService } from '@ngx-translate/core';
import { GuiService } from 'src/app/service/gui.service';
import { User } from 'src/app/model/auth/user';
import { AuthService } from 'src/app/service/auth.service';
import { MenuElement } from 'src/app/model/view/menu-element';
import { SidebarService } from 'src/app/service/sidebar.service';
import { SettingsService } from 'src/app/service/settings.service';
@Component({
selector: "app-header",
templateUrl: "./header.component.html",
styleUrls: ["./header.component.scss"],
selector: 'app-header',
templateUrl: './header.component.html',
styleUrls: ['./header.component.scss'],
})
export class HeaderComponent implements OnInit, OnDestroy {
langList: MenuItem[] = [];
themeList: MenuItem[] = [];
userMenuList!: MenuItem[];
user: User | null = null;
private unsubscribe$ = new Subject<void>();
@ -29,22 +32,30 @@ export class HeaderComponent implements OnInit, OnDestroy {
private guiService: GuiService,
private auth: AuthService,
private sidebarService: SidebarService,
private settings: SettingsService
) {
this.guiService.isMobile$
.pipe(takeUntil(this.unsubscribe$))
.subscribe((isMobile) => {
.subscribe(isMobile => {
this.isMobile = isMobile;
if (isMobile) {
this.sidebarService.hide();
}
});
this.auth.user$
.pipe(takeUntil(this.unsubscribe$))
.subscribe(async (user) => {
this.auth.user$.pipe(takeUntil(this.unsubscribe$)).subscribe(async user => {
this.user = user;
await this.initMenuLists();
});
this.themeList = this.settings.settings.themes.map(theme => {
return {
label: theme.label,
command: () => {
this.guiService.theme$.next(theme.name);
},
};
});
}
async ngOnInit() {
@ -69,17 +80,17 @@ export class HeaderComponent implements OnInit, OnDestroy {
async initLangMenuList() {
this.langList = [
{
label: "English",
label: 'English',
command: () => {
this.translate("en");
this.setLang("en");
this.translate('en');
this.setLang('en');
},
},
{
label: "Deutsch",
label: 'Deutsch',
command: () => {
this.translate("de");
this.setLang("de");
this.translate('de');
this.setLang('de');
},
},
];
@ -96,13 +107,13 @@ export class HeaderComponent implements OnInit, OnDestroy {
separator: true,
},
{
label: this.translateService.instant("header.logout"),
label: this.translateService.instant('header.logout'),
command: () => {
this.auth.logout().then(() => {
console.log("logout");
console.log('logout');
});
},
icon: "pi pi-sign-out",
icon: 'pi pi-sign-out',
},
];
}
@ -110,19 +121,19 @@ export class HeaderComponent implements OnInit, OnDestroy {
translate(lang: string) {
this.translateService.use(lang);
this.translateService
.get("primeng")
.subscribe((res) => this.config.setTranslation(res));
.get('primeng')
.subscribe(res => this.config.setTranslation(res));
}
async loadLang() {
const lang = "en";
const lang = 'en';
this.setLang(lang);
this.translate(lang);
}
setLang(lang: string) {
// this.settings.setSetting(`lang`, lang);
console.log("setLang", lang);
console.log('setLang', lang);
}
toggleSidebar() {

View File

@ -3,6 +3,7 @@ import { BehaviorSubject, filter } from 'rxjs';
import { Logger } from 'src/app/service/logger.service';
import { NavigationEnd, Router } from '@angular/router';
import { SidebarService } from 'src/app/service/sidebar.service';
import { SettingsService } from 'src/app/service/settings.service';
const logger = new Logger('GuiService');
@ -14,10 +15,14 @@ export class GuiService {
isTablet$ = new BehaviorSubject<boolean>(this.isTabletByWindowWith());
hideGui$ = new BehaviorSubject<boolean>(false);
theme$ = new BehaviorSubject<string>(
this.settingsService.settings.themes[0].name
);
constructor(
private router: Router,
private sidebarService: SidebarService
private sidebarService: SidebarService,
private settingsService: SettingsService
) {
this.router.events
.pipe(filter(event => event instanceof NavigationEnd))

View File

@ -1,12 +1,12 @@
import { HttpClient } from "@angular/common/http";
import { Injectable } from "@angular/core";
import { throwError } from "rxjs";
import { catchError } from "rxjs/operators";
import { AppSettings } from "src/app/model/config/app-settings";
import { environment } from "src/environments/environment";
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { AppSettings } from 'src/app/model/config/app-settings';
import { environment } from 'src/environments/environment';
@Injectable({
providedIn: "root",
providedIn: 'root',
})
export class SettingsService {
settings!: AppSettings;
@ -18,13 +18,19 @@ export class SettingsService {
this.http
.get<AppSettings>(`/assets/config/${environment.config}`)
.pipe(
catchError((error) => {
catchError(error => {
reject(error);
return throwError(() => error);
}),
})
)
.subscribe((settings) => {
.subscribe(settings => {
this.settings = settings;
if (this.settings.themes.length === 0) {
this.settings.themes.push({
label: 'Open-redirect',
name: 'open-redirect',
});
}
resolve();
});
});

View File

@ -5,7 +5,11 @@
"themes": [
{
"label": "Open-redirect",
"name": "Open-redirect"
"name": "open-redirect"
},
{
"label": "Maxlan",
"name": "maxlan"
}
],
"api": {

View File

@ -10,6 +10,7 @@
@import 'tailwindcss/utilities';
@import 'styles/theme';
@import 'styles/theme_maxlan';
@import 'styles/tablet';
@import 'styles/mobile';
@ -58,7 +59,6 @@ body {
input,
.p-checkbox-box,
.p-dropdown {
border: 1px solid $accentColor;
padding: 10px;
}
@ -70,16 +70,6 @@ body {
}
}
@layer utilities {
.divider {
border-bottom: 1px solid $accentColor;
}
.v-divider {
border-left: 1px solid $accentColor;
}
}
header {
height: $headerHeight;
display: flex;
@ -129,7 +119,6 @@ main {
.component {
margin: 0 10px;
overflow: auto;
background-color: $backgroundColor;
flex: 1;
padding: 10px;
}
@ -141,7 +130,6 @@ footer {
display: flex;
justify-content: center;
align-items: center;
color: $textColor;
}
.btn {

View File

@ -1,9 +1,10 @@
$headerColor: #a2271f;
.open-redirect {
$headerColor: #ef9d0d;
// generated with https://colorffy.com/dark-theme-generator?colors=314390-121212
$textColor: #fff;
$textColorHighlight: #314390;
$textColorHighlight2: #a2271f;
$textColorHighlight: #ef9d0d;
$textColorHighlight2: #b76f00;
// https://tailwindcss.com/docs/customizing-colors
$backgroundColor: #1e293b; // slate-800
@ -15,9 +16,8 @@ $infoColor: #1ea97c;
$warningColor: #ffc107;
$errorColor: #ff5252;
body {
background-color: $backgroundColor2;
}
@layer utilities {
.highlight {
@ -404,3 +404,4 @@ p-slider {
}
}
}
}

View File

@ -0,0 +1,407 @@
.maxlan {
$headerColor: #a2271f;
// generated with https://colorffy.com/dark-theme-generator?colors=314390-121212
$textColor: #fff;
$textColorHighlight: #314390;
$textColorHighlight2: #a2271f;
// https://tailwindcss.com/docs/customizing-colors
$backgroundColor: #1e293b; // slate-800
$backgroundColor2: #0f172a; // slate-900
$backgroundColor3: #334155; // slate-700
$accentColor: #475569; // slate-600
$infoColor: #1ea97c;
$warningColor: #ffc107;
$errorColor: #ff5252;
background-color: $backgroundColor2;
@layer utilities {
.highlight {
color: $textColorHighlight;
}
.highlight2 {
color: $textColorHighlight2 !important;
}
.bg {
background-color: $backgroundColor;
}
.bg2 {
background-color: $backgroundColor2;
}
.bg3 {
background-color: $backgroundColor3;
}
.accent {
color: $accentColor;
}
.info {
color: $infoColor;
}
.warning {
color: $warningColor;
}
.error {
color: $errorColor;
}
.deleted {
color: $accentColor !important;
}
}
h1,
h2,
h3 {
color: $headerColor;
}
.app {
.component {
background-color: $backgroundColor;
}
}
.btn-base {
color: $textColor;
background: $textColorHighlight !important;
border: 1px solid $textColorHighlight;
&:focus {
box-shadow: none !important;
}
&:hover {
background: transparent !important;
}
}
.btn {
.p-button {
@extend .btn-base;
}
}
.icon-btn {
background-color: transparent !important;
border: none;
.p-button {
color: $textColor;
background: transparent !important;
padding: 0;
&:hover {
color: $textColorHighlight;
}
}
}
.text-btn {
background-color: transparent !important;
.p-button {
color: $textColor;
background: transparent !important;
border: none !important;
&:hover {
color: $textColorHighlight;
background-color: transparent !important;
}
}
}
.icon-btn-without-hover {
&:hover {
background-color: transparent !important;
}
}
.icon-btn {
&:hover {
background-color: transparent !important;
}
}
.danger-btn,
.danger-icon-btn {
background-color: transparent !important;
.p-button {
color: $textColor;
background: transparent !important;
&:hover {
color: $errorColor;
}
}
}
.hidden-columns-select {
.active-while-panel-open {
.p-button {
color: $textColorHighlight;
}
}
}
.custom-spinner {
.p-progress-spinner-circle {
stroke: $textColorHighlight !important;
}
}
p-paginator {
.p-paginator-element {
&:hover {
color: $textColorHighlight;
}
}
}
.p-highlight {
color: $textColorHighlight2;
box-shadow: none !important;
&:hover {
color: $textColorHighlight2 !important;
}
}
.p-multiselect,
.p-paginator,
.p-dropdown,
input {
background-color: $backgroundColor;
}
.p-inputtext:enabled:focus,
.p-inputtext:enabled:hover,
.p-multiselect:not(.p-disabled):hover,
.p-multiselect:not(.p-disabled).p-focus,
.p-dropdown:not(.p-disabled):hover,
.p-checkbox:not(.p-checkbox-disabled) .p-checkbox-box:hover,
.p-checkbox:not(.p-checkbox-disabled) .p-checkbox-box.p-focus {
border-color: $textColorHighlight;
box-shadow: none !important;
}
.p-checkbox .p-checkbox-box.p-highlight {
border-color: $textColorHighlight;
background: $textColorHighlight;
box-shadow: none !important;
}
p-checkbox {
.p-checkbox {
.p-checkbox-box {
background-color: $backgroundColor;
}
.p-checkbox-box.p-highlight {
border-color: $textColorHighlight;
background-color: $textColorHighlight;
}
}
}
p-inputSwitch {
.p-inputswitch {
&.p-focus .p-inputswitch-slider {
box-shadow: none !important;
}
.p-inputswitch-slider {
background-color: $headerColor;
&.p-highlight {
border-color: $textColorHighlight;
background: $textColorHighlight;
box-shadow: none !important;
}
}
&.p-inputswitch-checked {
.p-inputswitch-slider {
background-color: $headerColor;
&:before {
background-color: $textColor;
}
}
}
}
}
p-panel-menu {
.p-panelmenu {
.p-panelmenu-content
.p-menuitem:not(.p-highlight):not(.p-disabled)
> .p-menuitem-content:hover
.p-menuitem-link
.p-menuitem-text,
.p-panelmenu-content
.p-menuitem:not(.p-highlight):not(.p-disabled)
> .p-menuitem-content:hover
.p-menuitem-link
.p-menuitem-icon,
.p-panelmenu
.p-panelmenu-content
.p-menuitem:not(.p-highlight):not(.p-disabled)
> .p-menuitem-content:hover
.p-menuitem-link
.p-submenu-icon {
color: $textColorHighlight;
}
.p-panelmenu-header-content {
&:hover {
.p-panelmenu-header-action {
color: $textColorHighlight;
}
}
}
}
}
p-menubar {
.p-menubar {
background-color: $backgroundColor2;
}
.p-menubar-root-list {
display: flex;
gap: 10px;
}
.p-menuitem {
border-radius: 0.5rem;
&:hover {
.p-menuitem-content .p-menuitem-link .p-menuitem-text {
color: $textColorHighlight2;
}
}
}
.p-focus {
background-color: $backgroundColor2;
.p-menuitem-content {
background-color: $backgroundColor2;
}
}
}
p-dropdown {
.p-dropdown:not(.p-disabled).p-focus {
box-shadow: none !important;
border-color: $headerColor !important;
}
.p-dropdown-panel {
background-color: $backgroundColor2;
}
}
p-multiselect {
.p-multiselect-panel,
.p-multiselect-panel .p-multiselect-header {
background-color: $backgroundColor2;
}
}
p-table {
.p-datatable {
.p-datatable-header,
.p-datatable-footer,
.p-datatable-thead > tr > th,
.p-datatable-tbody > tr {
background-color: $backgroundColor;
border-bottom: 1px solid $accentColor;
}
.p-sortable-column.p-highlight,
.p-sortable-column:not(.p-highlight):hover {
background-color: transparent !important;
}
.p-sortable-column:not(.p-highlight):hover,
.p-sortable-column:not(.p-highlight):hover .p-sortable-column-icon {
color: $textColorHighlight;
}
}
}
p-sidebar {
.p-sidebar {
background-color: $backgroundColor;
}
}
.p-steps .p-steps-item.p-highlight .p-steps-number {
background-color: $headerColor;
color: $textColor;
}
.p-dialog {
.p-dialog-header {
background-color: $backgroundColor;
}
.p-dialog-content {
background-color: $backgroundColor;
padding-top: 1rem;
}
.p-dialog-footer {
background-color: $backgroundColor;
}
}
.p-badge {
background: $headerColor;
color: $textColor;
}
p-password {
background-color: transparent;
border: none;
.p-password-panel {
background-color: transparent;
}
}
p-slider {
.p-slider {
.p-slider-range {
background: $headerColor;
}
.p-slider-handle {
border-color: $headerColor;
&:hover {
background: $headerColor;
}
&:focus {
box-shadow: none !important;
}
}
}
}
}