OpenSlides theming (Fixes #4205)

using 3 built-in themes (default, dark, green)
This commit is contained in:
GabrielMeyer 2019-01-29 15:20:00 +01:00 committed by Emanuel Schütze
parent ec8fa3eb91
commit 30535dd21f
12 changed files with 228 additions and 32 deletions

View File

@ -5,6 +5,7 @@ import { LoginDataService } from './core/services/login-data.service';
import { ConfigService } from './core/services/config.service'; import { ConfigService } from './core/services/config.service';
import { ConstantsService } from './core/services/constants.service'; import { ConstantsService } from './core/services/constants.service';
import { ServertimeService } from './core/services/servertime.service'; import { ServertimeService } from './core/services/servertime.service';
import { ThemeService } from './core/services/theme.service';
/** /**
* Angular's global App Component * Angular's global App Component
@ -25,6 +26,7 @@ export class AppComponent {
* @param autoupdateService * @param autoupdateService
* @param notifyService * @param notifyService
* @param translate * @param translate
* @param themeService used to listen to theme-changes
*/ */
public constructor( public constructor(
translate: TranslateService, translate: TranslateService,
@ -32,7 +34,8 @@ export class AppComponent {
configService: ConfigService, configService: ConfigService,
loginDataService: LoginDataService, loginDataService: LoginDataService,
constantsService: ConstantsService, // Needs to be started, so it can register itself to the WebsocketService constantsService: ConstantsService, // Needs to be started, so it can register itself to the WebsocketService
servertimeService: ServertimeService servertimeService: ServertimeService,
themeService: ThemeService
) { ) {
// manually add the supported languages // manually add the supported languages
translate.addLangs(['en', 'de', 'cs']); translate.addLangs(['en', 'de', 'cs']);

View File

@ -0,0 +1,12 @@
import { TestBed } from '@angular/core/testing';
import { ThemeService } from './theme.service';
describe('ThemeService', () => {
beforeEach(() => TestBed.configureTestingModule({}));
it('should be created', () => {
const service: ThemeService = TestBed.get(ThemeService);
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,31 @@
import { Injectable } from '@angular/core';
import { ConfigService } from './config.service';
/**
* Service to set the theme for the OpenSlides.
* Reads related data from server,
* the server sends the value of the new theme --> the new theme has only to be added to the body.
*/
@Injectable({
providedIn: 'root'
})
export class ThemeService {
/**
* Here it will subscribe to the observer from config-service to read data.
*
* @param configService must be injected to get the data from server.
*/
public constructor(configService: ConfigService) {
configService.get('openslides_theme').subscribe(newTheme => {
// Listen to the related event.
const classList = document.getElementsByTagName('body')[0].classList; // Get the classlist of the body.
if (newTheme) {
const toRemove = Array.from(classList).filter((item: string) => item.includes('theme'));
if (toRemove.length) {
classList.remove(...toRemove); // Remove all old themes.
}
classList.add(newTheme); // Add the new theme.
}
});
}
}

View File

@ -1,5 +1,6 @@
import { Component, OnInit, Input } from '@angular/core'; import { Component, OnInit, Input } from '@angular/core';
import { MediaManageService } from '../../../site/mediafiles/services/media-manage.service'; import { MediaManageService } from '../../../site/mediafiles/services/media-manage.service';
import { ConfigService } from 'app/core/services/config.service';
/** /**
* Reusable Logo component for Apps. * Reusable Logo component for Apps.
@ -37,10 +38,15 @@ import { MediaManageService } from '../../../site/mediafiles/services/media-mana
}) })
export class LogoComponent implements OnInit { export class LogoComponent implements OnInit {
/** /**
* Constant path of the dark logo * Constant path of the logo with dark colors for bright themes
*/ */
public static STANDARD_LOGO = '/assets/img/openslides-logo-h.svg'; public static STANDARD_LOGO = '/assets/img/openslides-logo-h.svg';
/**
* Constant path of the logo with white colors for dark themes
*/
public static STANDARD_LOGO_DARK_THEME = '/assets/img/openslides-logo-h-dark-transparent.svg';
/** /**
* Holds the actions for logos. Updated via an observable * Holds the actions for logos. Updated via an observable
*/ */
@ -65,11 +71,12 @@ export class LogoComponent implements OnInit {
@Input() @Input()
public alignment = 'center'; public alignment = 'center';
/** /**
* The consotructor * The constructor
* *
* @param mmservice The Media Manage Service * @param mmservice The Media Manage Service
* @param configService The ConfigService to subscribe to theme-changes
*/ */
public constructor(private mmservice: MediaManageService) {} public constructor(private mmservice: MediaManageService, private configService: ConfigService) {}
/** /**
* Initialization function * Initialization function
@ -96,6 +103,21 @@ export class LogoComponent implements OnInit {
} }
} }
/**
* Check if the user uses a dark theme or a 'bright' theme.
* In relation to the theme this will return the corresponding imagepath.
*
* @returns path of the image corresponding to the chosen theme.
*/
protected getImagePathRelatedToTheme(): string {
const theme = this.configService.instant<string>('openslides_theme');
if (theme) {
return theme.includes('dark') ? LogoComponent.STANDARD_LOGO_DARK_THEME : LogoComponent.STANDARD_LOGO;
} else {
return LogoComponent.STANDARD_LOGO;
}
}
/** /**
* gets the header image based on logo action * gets the header image based on logo action
* *
@ -119,7 +141,7 @@ export class LogoComponent implements OnInit {
} }
} }
if (path === '') { if (path === '') {
path = LogoComponent.STANDARD_LOGO; path = this.getImagePathRelatedToTheme();
} }
return path; return path;
} }
@ -146,10 +168,14 @@ export class LogoComponent implements OnInit {
* logo was set * logo was set
*/ */
protected getFooterImage(logoAction: string): string { protected getFooterImage(logoAction: string): string {
if (this.getHeaderImage(logoAction) === LogoComponent.STANDARD_LOGO || this.getHeaderImage(logoAction) === '') { if (
this.getHeaderImage(logoAction) === LogoComponent.STANDARD_LOGO ||
this.getHeaderImage(logoAction) === LogoComponent.STANDARD_LOGO_DARK_THEME ||
this.getHeaderImage(logoAction) === ''
) {
return ''; return '';
} else { } else {
return LogoComponent.STANDARD_LOGO; return this.getImagePathRelatedToTheme();
} }
} }
} }

View File

@ -11,7 +11,7 @@
os-site { os-site {
/* main background color */ /* main background color */
.main-container { .main-container {
background-color: $os-background; background-color: mat-color($background, background);
} }
.nav-toolbar { .nav-toolbar {
background-color: mat-color($background, card); //TODO background-color: mat-color($background, card); //TODO
@ -37,10 +37,10 @@
/** style the active link */ /** style the active link */
.active { .active {
mat-icon { mat-icon {
color: $os-primary; color: mat-color($primary);
} }
span { span {
color: $os-primary; color: mat-color($primary);
} }
} }
} }

View File

@ -0,0 +1,34 @@
// Material theming import
@import '~@angular/material/theming';
/**
* Mixin-style to style global classes and tags with theme-related colors.
*/
@mixin os-components-style($theme) {
$primary: map-get($theme, primary);
$accent: map-get($theme, accent);
$warn: map-get($theme, warn);
$foreground: map-get($theme, foreground);
$background: map-get($theme, background);
h1, h3.accent {
color: mat-color($primary);
}
a {
color: mat-color($primary);
}
.accent-text {
color: mat-color($accent);
}
.button24 {
color: mat-color($primary);
}
//custom table header for search button, filtering and more. Used in ListViews
.custom-table-header {
background: mat-color($background, background);
}
}

View File

@ -0,0 +1,9 @@
$openslides-primary: mat-palette($mat-light-blue, 500, 100, 700);
$openslides-accent: mat-palette($mat-orange, 700, 300, 900);
$openslides-warn: mat-palette($mat-red, 600, 500, 700);
$openslides-dark-theme: mat-dark-theme(
$openslides-primary,
$openslides-accent,
$openslides-warn
);

View File

@ -0,0 +1,42 @@
$openslides-green: (
50: #e9f2e6,
100: #c8e0bf,
200: #a3cb95,
300: #7eb66b,
400: #62a64b,
500: #46962b,
600: #3f8e26,
700: #0a321e,
800: #092d1a,
900: #072616,
A100: #acff9d,
A200: #80ff6a,
A400: #55ff37,
A700: #3fff1e,
contrast: (
50: #000000,
100: #000000,
200: #000000,
300: #000000,
400: #000000,
500: #ffffff,
600: #ffffff,
700: #ffffff,
800: #ffffff,
900: #ffffff,
A100: #000000,
A200: #000000,
A400: #000000,
A700: #000000
)
);
$openslides-primary: mat-palette($openslides-green);
$openslides-accent: mat-palette($mat-amber);
$openslides-warn: mat-palette($mat-red);
$openslides-green-theme: mat-light-theme(
$openslides-primary,
$openslides-accent,
$openslides-warn
)

View File

@ -79,9 +79,22 @@ $openslides-warn: mat-palette($mat-red);
$openslides-theme: mat-light-theme($openslides-primary, $openslides-accent, $openslides-warn); $openslides-theme: mat-light-theme($openslides-primary, $openslides-accent, $openslides-warn);
// Create Sass color vars (for using in scss files). // Create Sass color vars (for using in scss files).
$os-primary: mat-color($openslides-primary); // This will be set dynamically with the selecting theme
$os-accent: mat-color($openslides-accent); // $os-primary: mat-color($openslides-primary);
$os-warn: mat-color($openslides-warn); // $os-accent: mat-color($openslides-accent);
// $os-warn: mat-color($openslides-warn);
$os-outline: mat-color($mat-grey, 300); $os-outline: mat-color($mat-grey, 300);
$os-background: mat-color($mat-grey, 100); $os-background: mat-color($mat-grey, 100);
/** This is the workaround to set a custom background-color
* In the first step the color must be merged, in order to have to a map.
* The components will get a value from this map.
*/
$background: map-get($openslides-theme, background);
$background: map_merge($background, (background: $os-background));
/**
* Merge the theme with the custom-background.
*/
$openslides-theme: map_merge($openslides-theme, (background: $background));

View File

@ -12,7 +12,7 @@
<meta name="theme-color" content="#317796"> <meta name="theme-color" content="#317796">
</head> </head>
<body> <body class="openslides-theme">
<os-root></os-root> <os-root></os-root>
<noscript>Please enable JavaScript to continue using this application.</noscript> <noscript>Please enable JavaScript to continue using this application.</noscript>
</body> </body>

View File

@ -1,23 +1,45 @@
/** Imports the material-design-theming */
@import '~@angular/material/theming'; @import '~@angular/material/theming';
@include mat-core(); @include mat-core();
/** Import brand theme and (new) component themes */ /** Import brand theme */
@import './assets/styles/openslides-theme.scss'; @import './assets/styles/openslides-theme.scss';
@import './app/site/site.component.scss-theme'; @import './assets/styles/openslides-dark-theme.scss';
@import './assets/styles/openslides-green-theme.scss';
/** Import the component-related stylesheets here */
@import './app/site/site.component.scss-theme.scss';
@import './assets/styles/global-components-style.scss';
/** fonts */ /** fonts */
@import './assets/styles/fonts.scss'; @import './assets/styles/fonts.scss';
/** Mix the component-related style-rules */
@mixin openslides-components-theme($theme) { @mixin openslides-components-theme($theme) {
@include os-site-theme($theme); @include os-site-theme($theme);
@include os-components-style($theme);
/** More components are added here */ /** More components are added here */
} }
@include angular-material-theme($openslides-theme);
@include openslides-components-theme($openslides-theme);
@import '~angular-tree-component/dist/angular-tree-component.css'; @import '~angular-tree-component/dist/angular-tree-component.css';
/** Define the classes to switch between themes */
.openslides-theme {
@include angular-material-theme($openslides-theme);
@include openslides-components-theme($openslides-theme);
}
.openslides-dark-theme {
@include angular-material-theme($openslides-dark-theme);
@include openslides-components-theme($openslides-dark-theme);
}
.openslides-green-theme {
@include angular-material-theme($openslides-green-theme);
@include openslides-components-theme($openslides-green-theme);
}
/** Define the general style-rules */
* { * {
font-family: OSFont, Fira Sans, Roboto, Arial, Helvetica, sans-serif; font-family: OSFont, Fira Sans, Roboto, Arial, Helvetica, sans-serif;
} }
@ -30,12 +52,10 @@ mat-icon {
} }
body { body {
// background: #e8eaed;
margin: 0 auto; margin: 0 auto;
padding: 0; padding: 0;
line-height: 1.5; line-height: 1.5;
font-size: 14px; font-size: 14px;
color: #222;
} }
h1, h1,
@ -50,7 +70,6 @@ h3,
h1 { h1 {
padding-bottom: 10px; padding-bottom: 10px;
line-height: 1.2; line-height: 1.2;
color: $os-primary;
margin: 0; margin: 0;
font-weight: normal; font-weight: normal;
font-size: 36px; font-size: 36px;
@ -59,13 +78,10 @@ h3 {
font-weight: 500; font-weight: 500;
margin-bottom: 0; margin-bottom: 0;
} }
h3.accent {
color: $os-accent;
}
h4 { h4 {
font-weight: 400; font-weight: 400;
font-size: 12px; font-size: 12px;
color: rgba(0, 0, 0, 0.54);
margin-bottom: 5px; margin-bottom: 5px;
.mat-icon-button mat-icon { .mat-icon-button mat-icon {
@ -83,7 +99,6 @@ img {
a { a {
text-decoration: none; text-decoration: none;
color: #039be5; /*TODO: move to theme*/
&:hover { &:hover {
text-decoration: underline; text-decoration: underline;
@ -121,9 +136,6 @@ b {
} }
} }
.accent-text {
color: mat-color($openslides-accent);
}
.green-text { .green-text {
// TODO better name/theming // TODO better name/theming
@ -209,7 +221,6 @@ mat-card {
height: 60px; height: 60px;
line-height: 60px; line-height: 60px;
text-align: right; text-align: right;
background: white;
border-bottom: 1px solid rgba(0, 0, 0, 0.12); border-bottom: 1px solid rgba(0, 0, 0, 0.12);
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
@ -228,6 +239,7 @@ mat-card {
mat-icon { mat-icon {
vertical-align: text-bottom; vertical-align: text-bottom;
margin-right: 2px;
} }
} }
@ -386,7 +398,6 @@ button.mat-menu-item.selected {
} }
.button24 { .button24 {
background-color: white; background-color: white;
color: $os-primary !important;
width: 24px !important; width: 24px !important;
height: 24px !important; height: 24px !important;
} }

View File

@ -112,6 +112,21 @@ def get_config_variables():
subgroup="System", subgroup="System",
) )
yield ConfigVariable(
name="openslides_theme",
default_value="openslides-theme",
input_type="choice",
label="OpenSlides Theme",
choices=(
{"value": "openslides-theme", "display_name": "OpenSlides Default"},
{"value": "openslides-dark-theme", "display_name": "OpenSlides Dark"},
{"value": "openslides-green-theme", "display_name": "OpenSlides Green"},
),
weight=141,
group="General",
subgroup="System",
)
# General export settings # General export settings
yield ConfigVariable( yield ConfigVariable(