OpenSlides theming (Fixes #4205)
using 3 built-in themes (default, dark, green)
This commit is contained in:
parent
ec8fa3eb91
commit
30535dd21f
@ -5,6 +5,7 @@ import { LoginDataService } from './core/services/login-data.service';
|
||||
import { ConfigService } from './core/services/config.service';
|
||||
import { ConstantsService } from './core/services/constants.service';
|
||||
import { ServertimeService } from './core/services/servertime.service';
|
||||
import { ThemeService } from './core/services/theme.service';
|
||||
|
||||
/**
|
||||
* Angular's global App Component
|
||||
@ -25,6 +26,7 @@ export class AppComponent {
|
||||
* @param autoupdateService
|
||||
* @param notifyService
|
||||
* @param translate
|
||||
* @param themeService used to listen to theme-changes
|
||||
*/
|
||||
public constructor(
|
||||
translate: TranslateService,
|
||||
@ -32,7 +34,8 @@ export class AppComponent {
|
||||
configService: ConfigService,
|
||||
loginDataService: LoginDataService,
|
||||
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
|
||||
translate.addLangs(['en', 'de', 'cs']);
|
||||
|
12
client/src/app/core/services/theme.service.spec.ts
Normal file
12
client/src/app/core/services/theme.service.spec.ts
Normal 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();
|
||||
});
|
||||
});
|
31
client/src/app/core/services/theme.service.ts
Normal file
31
client/src/app/core/services/theme.service.ts
Normal 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.
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
import { Component, OnInit, Input } from '@angular/core';
|
||||
import { MediaManageService } from '../../../site/mediafiles/services/media-manage.service';
|
||||
import { ConfigService } from 'app/core/services/config.service';
|
||||
|
||||
/**
|
||||
* Reusable Logo component for Apps.
|
||||
@ -37,10 +38,15 @@ import { MediaManageService } from '../../../site/mediafiles/services/media-mana
|
||||
})
|
||||
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';
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@ -65,11 +71,12 @@ export class LogoComponent implements OnInit {
|
||||
@Input()
|
||||
public alignment = 'center';
|
||||
/**
|
||||
* The consotructor
|
||||
* The constructor
|
||||
*
|
||||
* @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
|
||||
@ -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
|
||||
*
|
||||
@ -119,7 +141,7 @@ export class LogoComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
if (path === '') {
|
||||
path = LogoComponent.STANDARD_LOGO;
|
||||
path = this.getImagePathRelatedToTheme();
|
||||
}
|
||||
return path;
|
||||
}
|
||||
@ -146,10 +168,14 @@ export class LogoComponent implements OnInit {
|
||||
* logo was set
|
||||
*/
|
||||
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 '';
|
||||
} else {
|
||||
return LogoComponent.STANDARD_LOGO;
|
||||
return this.getImagePathRelatedToTheme();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,7 @@
|
||||
os-site {
|
||||
/* main background color */
|
||||
.main-container {
|
||||
background-color: $os-background;
|
||||
background-color: mat-color($background, background);
|
||||
}
|
||||
.nav-toolbar {
|
||||
background-color: mat-color($background, card); //TODO
|
||||
@ -37,10 +37,10 @@
|
||||
/** style the active link */
|
||||
.active {
|
||||
mat-icon {
|
||||
color: $os-primary;
|
||||
color: mat-color($primary);
|
||||
}
|
||||
span {
|
||||
color: $os-primary;
|
||||
color: mat-color($primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
34
client/src/assets/styles/global-components-style.scss
Normal file
34
client/src/assets/styles/global-components-style.scss
Normal 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);
|
||||
}
|
||||
}
|
9
client/src/assets/styles/openslides-dark-theme.scss
Normal file
9
client/src/assets/styles/openslides-dark-theme.scss
Normal 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
|
||||
);
|
42
client/src/assets/styles/openslides-green-theme.scss
Normal file
42
client/src/assets/styles/openslides-green-theme.scss
Normal 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
|
||||
)
|
@ -79,9 +79,22 @@ $openslides-warn: mat-palette($mat-red);
|
||||
$openslides-theme: mat-light-theme($openslides-primary, $openslides-accent, $openslides-warn);
|
||||
|
||||
// Create Sass color vars (for using in scss files).
|
||||
$os-primary: mat-color($openslides-primary);
|
||||
$os-accent: mat-color($openslides-accent);
|
||||
$os-warn: mat-color($openslides-warn);
|
||||
// This will be set dynamically with the selecting theme
|
||||
// $os-primary: mat-color($openslides-primary);
|
||||
// $os-accent: mat-color($openslides-accent);
|
||||
// $os-warn: mat-color($openslides-warn);
|
||||
|
||||
$os-outline: mat-color($mat-grey, 300);
|
||||
$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));
|
||||
|
@ -12,7 +12,7 @@
|
||||
<meta name="theme-color" content="#317796">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<body class="openslides-theme">
|
||||
<os-root></os-root>
|
||||
<noscript>Please enable JavaScript to continue using this application.</noscript>
|
||||
</body>
|
||||
|
@ -1,23 +1,45 @@
|
||||
/** Imports the material-design-theming */
|
||||
@import '~@angular/material/theming';
|
||||
@include mat-core();
|
||||
|
||||
/** Import brand theme and (new) component themes */
|
||||
/** Import brand theme */
|
||||
@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 */
|
||||
@import './assets/styles/fonts.scss';
|
||||
|
||||
/** Mix the component-related style-rules */
|
||||
@mixin openslides-components-theme($theme) {
|
||||
@include os-site-theme($theme);
|
||||
@include os-components-style($theme);
|
||||
/** 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';
|
||||
|
||||
/** 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;
|
||||
}
|
||||
@ -30,12 +52,10 @@ mat-icon {
|
||||
}
|
||||
|
||||
body {
|
||||
// background: #e8eaed;
|
||||
margin: 0 auto;
|
||||
padding: 0;
|
||||
line-height: 1.5;
|
||||
font-size: 14px;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
h1,
|
||||
@ -50,7 +70,6 @@ h3,
|
||||
h1 {
|
||||
padding-bottom: 10px;
|
||||
line-height: 1.2;
|
||||
color: $os-primary;
|
||||
margin: 0;
|
||||
font-weight: normal;
|
||||
font-size: 36px;
|
||||
@ -59,13 +78,10 @@ h3 {
|
||||
font-weight: 500;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
h3.accent {
|
||||
color: $os-accent;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
color: rgba(0, 0, 0, 0.54);
|
||||
margin-bottom: 5px;
|
||||
|
||||
.mat-icon-button mat-icon {
|
||||
@ -83,7 +99,6 @@ img {
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: #039be5; /*TODO: move to theme*/
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
@ -121,9 +136,6 @@ b {
|
||||
}
|
||||
}
|
||||
|
||||
.accent-text {
|
||||
color: mat-color($openslides-accent);
|
||||
}
|
||||
|
||||
.green-text {
|
||||
// TODO better name/theming
|
||||
@ -209,7 +221,6 @@ mat-card {
|
||||
height: 60px;
|
||||
line-height: 60px;
|
||||
text-align: right;
|
||||
background: white;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
@ -228,6 +239,7 @@ mat-card {
|
||||
|
||||
mat-icon {
|
||||
vertical-align: text-bottom;
|
||||
margin-right: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -386,7 +398,6 @@ button.mat-menu-item.selected {
|
||||
}
|
||||
.button24 {
|
||||
background-color: white;
|
||||
color: $os-primary !important;
|
||||
width: 24px !important;
|
||||
height: 24px !important;
|
||||
}
|
||||
|
@ -112,6 +112,21 @@ def get_config_variables():
|
||||
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
|
||||
|
||||
yield ConfigVariable(
|
||||
|
Loading…
Reference in New Issue
Block a user