diff --git a/client/src/app/app.component.html b/client/src/app/app.component.html
index e7f953e2e..be412ce9e 100644
--- a/client/src/app/app.component.html
+++ b/client/src/app/app.component.html
@@ -1,3 +1,4 @@
-
+
+
+
diff --git a/client/src/app/shared/components/logo/logo.component.scss b/client/src/app/shared/components/logo/logo.component.scss
index 5d3fec5f5..e7580d8a2 100644
--- a/client/src/app/shared/components/logo/logo.component.scss
+++ b/client/src/app/shared/components/logo/logo.component.scss
@@ -2,6 +2,7 @@ img {
max-width: 100%;
height: auto;
max-height: 100%;
+ margin: 0 auto;
}
.logo-container {
diff --git a/client/src/app/shared/components/logo/logo.component.ts b/client/src/app/shared/components/logo/logo.component.ts
index fa2b0a5d9..3d6c21832 100644
--- a/client/src/app/shared/components/logo/logo.component.ts
+++ b/client/src/app/shared/components/logo/logo.component.ts
@@ -1,63 +1,27 @@
-import { Component, OnInit, Input } from '@angular/core';
+import { Component, Input, OnInit, OnDestroy } from '@angular/core';
-import { MediaManageService } from 'app/core/ui-services/media-manage.service';
-import { ConfigService } from 'app/core/ui-services/config.service';
+import { ThemeService } from 'app/core/ui-services/theme.service';
+import { LoginDataService } from 'app/core/ui-services/login-data.service';
+import { Subscription } from 'rxjs';
/**
- * Reusable Logo component for Apps.
- *
- * Following actions are possible:
- * * "logo_projector_main"
- * * "logo_projector_header"
- * * "logo_web_header"
- * * "logo_pdf_header_L"
- * * "logo_pdf_header_R"
- * * "logo_pdf_footer_L"
- * * "logo_pdf_footer_R"
- * * "logo_pdf_ballot_paper"
- *
- * ## Examples:
- *
- * ### Usage of the selector:
- *
- * ```html
- *
- *
- * ```
- *
- * Sidenote: The footer variable is optional. Only if you want
- * alternating logos, i.E. in the sidenav. the Alignment is also
- * optional.
+ * Component to hold the logo for the app.
*/
@Component({
selector: 'os-logo',
templateUrl: './logo.component.html',
styleUrls: ['./logo.component.scss']
})
-export class LogoComponent implements OnInit {
+export class LogoComponent implements OnInit, OnDestroy {
/**
- * Constant path of the logo with dark colors for bright themes
+ * Local variable to hold the path for a custom web header.
*/
- public static STANDARD_LOGO = '/assets/img/openslides-logo-h.svg';
+ private logoPath: string;
/**
- * Constant path of the logo with white colors for dark themes
+ * Local variable to hold the subscription to unsubscribe if exists.
*/
- public static STANDARD_LOGO_DARK_THEME = '/assets/img/openslides-logo-h-dark-transparent.svg';
-
- /**
- * Holds the actions for logos. Updated via an observable
- */
- public logoActions: string[];
-
- /**
- * decides based on the actionString how to display the logo
- */
- @Input()
- public inputAction: string;
+ private logoSubscription: Subscription;
/**
* determines if the current picture is displayed in the footer.
@@ -66,117 +30,46 @@ export class LogoComponent implements OnInit {
@Input()
public footer = false;
- /**
- * influences text-alignment in the .logo-container css class
- */
- @Input()
- public alignment = 'center';
/**
* The constructor
*
- * @param mmservice The Media Manage Service
- * @param configService The ConfigService to subscribe to theme-changes
+ * @param loginDataService Reference to the `LoginDataService`
+ * @param themeService Reference to the `ThemeService`
*/
- public constructor(private mmservice: MediaManageService, private configService: ConfigService) {}
+ public constructor(private loginDataService: LoginDataService, private themeService: ThemeService) {}
/**
- * Initialization function
+ * On init method
*/
public ngOnInit(): void {
- this.mmservice.getLogoActions().subscribe(action => {
- this.logoActions = action;
+ this.logoSubscription = this.loginDataService.logo_web_header.subscribe(nextLogo => {
+ if (nextLogo) {
+ this.logoPath = nextLogo.path;
+ }
});
}
/**
- * gets the image based on the inputAction and location.
- * Possible inputActions are in the class description.
+ * On destroy method
+ */
+ public ngOnDestroy(): void {
+ if (this.logoSubscription) {
+ this.logoSubscription.unsubscribe();
+ this.logoSubscription = null;
+ }
+ }
+
+ /**
+ * Get the image based on custom images and footer.
+ * If a custom image is set and this component is displayed as footer or there is no custom image, then the OpenSlides logo is used.
*
* @returns path to image
*/
- public getImage(): string {
- if (this.footer) {
- const path = this.getFooterImage(this.inputAction);
- return path;
+ public getImage(shouldDefault?: boolean): string {
+ if ((!this.logoPath && !this.footer) || (!!this.logoPath && this.footer)) {
+ return this.themeService.getLogoRelativeToTheme(shouldDefault);
} else {
- const path = this.getHeaderImage(this.inputAction, this.alignment);
- return path;
- }
- }
-
- /**
- * 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
('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
- *
- * @param logoAction the logo action to be used
- * @param alignment the alignment of the logo (optional)
- * @returns path to image
- */
- protected getHeaderImage(logoAction: string, alignment: string = 'center'): string {
- if (alignment !== 'center') {
- this.setAlignment(alignment);
- }
- let path = '';
- /* check if datastore is loaded and custom logo can be read */
- if (this.logoActions === undefined) {
- return '';
- }
- if (this.mmservice !== undefined) {
- if (this.mmservice.isImageConfigObject(this.mmservice.getMediaConfig(logoAction))) {
- const imageConfig = this.mmservice.getMediaConfig(logoAction);
- path = imageConfig.path;
- }
- }
- if (path === '') {
- path = this.getImagePathRelatedToTheme();
- }
- return path;
- }
-
- /**
- * Changes the alignment from center to either 'left' or 'right'
- *
- * @param alignment either 'right' or 'left'
- */
- private setAlignment(alignment: string): void {
- if (alignment === 'left' || alignment === 'right') {
- const cssLogoContainer = document.getElementsByClassName('logo-container') as HTMLCollectionOf;
- if (cssLogoContainer.length !== 0) {
- cssLogoContainer[0].style.textAlign = alignment;
- }
- }
- }
-
- /**
- * Returns the image-path for the footer
- *
- * @param logoAction the logo action to be used
- * @returns '' if no logo is set and path to standard logo if a custom
- * logo was set
- */
- protected getFooterImage(logoAction: string): string {
- if (
- this.getHeaderImage(logoAction) === LogoComponent.STANDARD_LOGO ||
- this.getHeaderImage(logoAction) === LogoComponent.STANDARD_LOGO_DARK_THEME ||
- this.getHeaderImage(logoAction) === ''
- ) {
- return '';
- } else {
- return this.getImagePathRelatedToTheme();
+ return this.logoPath;
}
}
}
diff --git a/client/src/app/site/global-spinner/global-spinner.component.html b/client/src/app/site/global-spinner/global-spinner.component.html
new file mode 100644
index 000000000..6e1791159
--- /dev/null
+++ b/client/src/app/site/global-spinner/global-spinner.component.html
@@ -0,0 +1,11 @@
+
diff --git a/client/src/app/site/global-spinner/global-spinner.component.scss b/client/src/app/site/global-spinner/global-spinner.component.scss
new file mode 100644
index 000000000..213c9e135
--- /dev/null
+++ b/client/src/app/site/global-spinner/global-spinner.component.scss
@@ -0,0 +1,83 @@
+@import '~@angular/material/theming';
+
+@mixin os-global-spinner-theme($theme) {
+ $primary: map-get($theme, primary);
+ $accent: map-get($theme, accent);
+ $warn: map-get($theme, warn);
+ $background: map-get($theme, background);
+ $foreground: map-get($theme, foreground);
+
+ $contrast-primary: map-get($primary, contrast);
+ $contrast-accent: map-get($accent, contrast);
+
+ .global-spinner-component,
+ .backdrop,
+ .spinner-container {
+ position: absolute;
+ left: 0;
+ right: 0;
+ top: 0;
+ bottom: 0;
+ z-index: 999;
+ }
+
+ .global-spinner-component {
+ position: fixed;
+
+ .spinner-container {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ .spinner {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ margin: -136px 0 0 -53px;
+
+ height: 100px;
+ width: 100px;
+ border: 6px solid #000;
+ border-radius: 100%;
+ opacity: 0.2;
+
+ animation: rotation 1s infinite linear;
+
+ &:before {
+ position: absolute;
+ top: -6px;
+ left: -6px;
+
+ content: '';
+ display: block;
+ height: 100%;
+ width: 100%;
+ border-radius: 100%;
+ border-style: solid;
+ border-width: 6px;
+ border-color: white transparent transparent;
+ }
+
+ @keyframes rotation {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(359deg);
+ }
+ }
+ }
+
+ .text {
+ text-align: center;
+ color: white;
+ font-size: 1.4rem;
+ }
+ }
+ .backdrop {
+ z-index: 899;
+ background-color: #303030;
+ opacity: 0.8;
+ }
+ }
+}
diff --git a/client/src/app/site/global-spinner/global-spinner.component.spec.ts b/client/src/app/site/global-spinner/global-spinner.component.spec.ts
new file mode 100644
index 000000000..4cb83cc0e
--- /dev/null
+++ b/client/src/app/site/global-spinner/global-spinner.component.spec.ts
@@ -0,0 +1,24 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { GlobalSpinnerComponent } from './global-spinner.component';
+
+describe('GlobalSpinnerComponent', () => {
+ let component: GlobalSpinnerComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [GlobalSpinnerComponent]
+ }).compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(GlobalSpinnerComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/client/src/app/site/global-spinner/global-spinner.component.ts b/client/src/app/site/global-spinner/global-spinner.component.ts
new file mode 100644
index 000000000..dab096a3c
--- /dev/null
+++ b/client/src/app/site/global-spinner/global-spinner.component.ts
@@ -0,0 +1,74 @@
+// External imports
+import { Component, OnInit, OnDestroy, ChangeDetectorRef } from '@angular/core';
+import { Subscription } from 'rxjs';
+
+// Internal imports
+import { SpinnerService } from 'app/core/ui-services/spinner.service';
+
+/**
+ * Component for the global spinner.
+ */
+@Component({
+ selector: 'os-global-spinner',
+ templateUrl: './global-spinner.component.html',
+ styleUrls: ['./global-spinner.component.scss']
+})
+export class GlobalSpinnerComponent implements OnInit, OnDestroy {
+ /**
+ * Text, which will be shown if the spinner is shown.
+ */
+ public text: string;
+
+ /**
+ * Flag, that defines when the spinner is shown.
+ */
+ public isVisible = false;
+
+ /**
+ * Subscription for the service to handle the visibility and text for the spinner.
+ */
+ private spinnerSubscription: Subscription;
+
+ /**
+ * Constant string as default message when the spinner is shown.
+ */
+ private LOADING = 'Loading data. Please wait...';
+
+ /**
+ *
+ * @param spinnerService Reference to the service for this spinner.
+ * @param translate Service to get translations for the messages.
+ * @param detector Service to manual initiate a change of the UI.
+ */
+ public constructor(private spinnerService: SpinnerService, private detector: ChangeDetectorRef) {}
+
+ /**
+ * Init method
+ */
+ public ngOnInit(): void {
+ this.spinnerSubscription = this.spinnerService // subscribe to the service.
+ .getVisibility()
+ .subscribe((value: { isVisible: boolean; text: string }) => {
+ this.isVisible = value.isVisible;
+ this.text = value.text;
+
+ if (!this.text) {
+ this.text = this.LOADING;
+ }
+ this.detector.detectChanges();
+ });
+ }
+
+ /**
+ * Destroy method
+ *
+ * Deletes the subscription and marks the spinner as invisible.
+ */
+ public ngOnDestroy(): void {
+ if (this.spinnerSubscription) {
+ this.spinnerSubscription.unsubscribe();
+ this.isVisible = false;
+ }
+ this.spinnerSubscription = null;
+ }
+}
diff --git a/client/src/app/site/login/components/login-mask/login-mask.component.html b/client/src/app/site/login/components/login-mask/login-mask.component.html
index 8817b4a7d..3cc0e2b86 100644
--- a/client/src/app/site/login/components/login-mask/login-mask.component.html
+++ b/client/src/app/site/login/components/login-mask/login-mask.component.html
@@ -1,6 +1,4 @@