Fixes some UI issues corresponding to the theme

- Fixes white page at 'legalnotice' and 'privacypolicy'
- Fixes the button to collapse the sidenav
- Fixes a too long text in the headbar
- Reworked the login data service:
    * make order of operations clear
    * prevent setting invalid data into the storage
This commit is contained in:
GabrielMeyer 2019-07-26 11:47:04 +02:00 committed by Sean Engelhardt
parent 20cbe37d74
commit b5b3e60e81
30 changed files with 294 additions and 193 deletions

View File

@ -1,4 +1,4 @@
import { TestBed } from '@angular/core/testing'; import { TestBed, inject } from '@angular/core/testing';
import { PwaService } from './pwa.service'; import { PwaService } from './pwa.service';
import { E2EImportsModule } from 'e2e-imports.module'; import { E2EImportsModule } from 'e2e-imports.module';
@ -6,12 +6,12 @@ import { E2EImportsModule } from 'e2e-imports.module';
describe('PwaService', () => { describe('PwaService', () => {
beforeEach(() => beforeEach(() =>
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [E2EImportsModule] imports: [E2EImportsModule],
providers: [PwaService]
}) })
); );
it('should be created', () => { it('should be created', inject([PwaService], (service: PwaService) => {
const service: PwaService = TestBed.get(PwaService);
expect(service).toBeTruthy(); expect(service).toBeTruthy();
}); }));
}); });

View File

@ -1,10 +1,12 @@
import { TestBed, inject } from '@angular/core/testing'; import { TestBed, inject } from '@angular/core/testing';
import { StorageService } from './storage.service'; import { StorageService } from './storage.service';
import { E2EImportsModule } from 'e2e-imports.module';
describe('StorageService', () => { describe('StorageService', () => {
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [E2EImportsModule],
providers: [StorageService] providers: [StorageService]
}); });
}); });

View File

@ -1,4 +1,4 @@
import { TestBed } from '@angular/core/testing'; import { TestBed, inject } from '@angular/core/testing';
import { DurationService } from './duration.service'; import { DurationService } from './duration.service';
import { E2EImportsModule } from 'e2e-imports.module'; import { E2EImportsModule } from 'e2e-imports.module';
@ -6,12 +6,12 @@ import { E2EImportsModule } from 'e2e-imports.module';
describe('DurationService', () => { describe('DurationService', () => {
beforeEach(() => beforeEach(() =>
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [E2EImportsModule] imports: [E2EImportsModule],
providers: [DurationService]
}) })
); );
it('should be created', () => { it('should be created', inject([DurationService], (service: DurationService) => {
const service: DurationService = TestBed.get(DurationService);
expect(service).toBeTruthy(); expect(service).toBeTruthy();
}); }));
}); });

View File

@ -1,12 +1,17 @@
import { TestBed } from '@angular/core/testing'; import { TestBed, inject } from '@angular/core/testing';
import { HtmlToPdfService } from './html-to-pdf.service'; import { HtmlToPdfService } from './html-to-pdf.service';
import { E2EImportsModule } from 'e2e-imports.module';
describe('HtmlToPdfService', () => { describe('HtmlToPdfService', () => {
beforeEach(() => TestBed.configureTestingModule({})); beforeEach(() => {
TestBed.configureTestingModule({
imports: [E2EImportsModule],
providers: [HtmlToPdfService]
});
});
it('should be created', () => { it('should be created', inject([HtmlToPdfService], (service: HtmlToPdfService) => {
const service: HtmlToPdfService = TestBed.get(HtmlToPdfService);
expect(service).toBeTruthy(); expect(service).toBeTruthy();
}); }));
}); });

View File

@ -1,12 +1,17 @@
import { TestBed } from '@angular/core/testing'; import { TestBed, inject } from '@angular/core/testing';
import { LoadFontService } from './load-font.service'; import { LoadFontService } from './load-font.service';
import { E2EImportsModule } from 'e2e-imports.module';
describe('LoadFontService', () => { describe('LoadFontService', () => {
beforeEach(() => TestBed.configureTestingModule({})); beforeEach(() => {
TestBed.configureTestingModule({
imports: [E2EImportsModule],
providers: [LoadFontService]
});
});
it('should be created', () => { it('should be created', inject([LoadFontService], (service: LoadFontService) => {
const service: LoadFontService = TestBed.get(LoadFontService);
expect(service).toBeTruthy(); expect(service).toBeTruthy();
}); }));
}); });

View File

@ -1,10 +1,12 @@
import { TestBed, inject } from '@angular/core/testing'; import { TestBed, inject } from '@angular/core/testing';
import { LoginDataService } from './login-data.service'; import { LoginDataService } from './login-data.service';
import { E2EImportsModule } from 'e2e-imports.module';
describe('LoginDataService', () => { describe('LoginDataService', () => {
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [E2EImportsModule],
providers: [LoginDataService] providers: [LoginDataService]
}); });
}); });

View File

@ -1,22 +1,35 @@
import { Injectable } from '@angular/core'; import { Injectable, EventEmitter } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs'; import { BehaviorSubject, Observable } from 'rxjs';
import { ConfigService } from './config.service'; import { ConfigService } from './config.service';
import { StorageService } from '../core-services/storage.service'; import { StorageService } from '../core-services/storage.service';
import { OpenSlidesStatusService } from '../core-services/openslides-status.service'; import { OpenSlidesStatusService } from '../core-services/openslides-status.service';
import { HttpService } from '../core-services/http.service';
import { environment } from 'environments/environment.prod';
import { auditTime } from 'rxjs/operators';
/** /**
* The login data send by the server. * The login data send by the server.
*/ */
export interface LoginData { export interface LoginData {
privacy_policy: string; privacy_policy?: string;
legal_notice: string; legal_notice?: string;
theme: string; theme: string;
logo_web_header: { logo_web_header: {
path: string; path: string;
display_name: string; display_name: string;
}; };
login_info_text?: string;
}
/**
* Checks, if the given object holds valid LoginData.
*
* @param obj The object to check
*/
function isLoginData(obj: any): obj is LoginData {
return !!obj && obj.theme && obj.logo_web_header;
} }
const LOGIN_DATA_STORAGE_KEY = 'LoginData'; const LOGIN_DATA_STORAGE_KEY = 'LoginData';
@ -29,28 +42,40 @@ const LOGIN_DATA_STORAGE_KEY = 'LoginData';
providedIn: 'root' providedIn: 'root'
}) })
export class LoginDataService { export class LoginDataService {
/**
* Holds the installation notice.
*/
private readonly _loginInfoText = new BehaviorSubject<string>('');
/**
* Returns the installation notice as observable.
*/
public get loginInfoText(): Observable<string> {
return this._loginInfoText.asObservable();
}
/** /**
* Holds the privacy policy * Holds the privacy policy
*/ */
private readonly _privacy_policy = new BehaviorSubject<string>(''); private readonly _privacyPolicy = new BehaviorSubject<string>('');
/** /**
* Returns an observable for the privacy policy * Returns an observable for the privacy policy
*/ */
public get privacy_policy(): Observable<string> { public get privacyPolicy(): Observable<string> {
return this._privacy_policy.asObservable(); return this._privacyPolicy.asObservable();
} }
/** /**
* Holds the legal notice * Holds the legal notice
*/ */
private readonly _legal_notice = new BehaviorSubject<string>(''); private readonly _legalNotice = new BehaviorSubject<string>('');
/** /**
* Returns an observable for the legal notice * Returns an observable for the legal notice
*/ */
public get legal_notice(): Observable<string> { public get legalNotice(): Observable<string> {
return this._legal_notice.asObservable(); return this._legalNotice.asObservable();
} }
/** /**
@ -68,7 +93,7 @@ export class LoginDataService {
/** /**
* Holds the custom web header * Holds the custom web header
*/ */
private readonly _logo_web_header = new BehaviorSubject<{ path: string; display_name: string }>({ private readonly _logoWebHeader = new BehaviorSubject<{ path: string; display_name: string }>({
path: '', path: '',
display_name: '' display_name: ''
}); });
@ -76,10 +101,27 @@ export class LoginDataService {
/** /**
* Returns an observable for the web header * Returns an observable for the web header
*/ */
public get logo_web_header(): Observable<{ path: string; display_name: string }> { public get logoWebHeader(): Observable<{ path: string; display_name: string }> {
return this._logo_web_header.asObservable(); return this._logoWebHeader.asObservable();
} }
/**
* Emit this event, if the current login data should be stored. This
* is debounced to minimize requests to the storage service.
*/
private storeLoginDataRequests = new EventEmitter<void>();
/**
* Holds, if `_refresh` can be called. This will be true fter the setup.
*/
private canRefresh = false;
/**
* Marks, if during the etup (with `canRefresh=false`) a refresh was requested.
* After the setup, this variabel will be checked and a refresh triggered, if it is true.
*/
private markRefresh = false;
/** /**
* Constructs this service. The config service is needed to update the privacy * Constructs this service. The config service is needed to update the privacy
* policy and legal notice, when their config values change. * policy and legal notice, when their config values change.
@ -88,67 +130,112 @@ export class LoginDataService {
public constructor( public constructor(
private configService: ConfigService, private configService: ConfigService,
private storageService: StorageService, private storageService: StorageService,
private OSStatus: OpenSlidesStatusService private OSStatus: OpenSlidesStatusService,
private httpService: HttpService
) { ) {
this.storeLoginDataRequests.pipe(auditTime(100)).subscribe(() => this.storeLoginData());
this.setup();
}
/**
* Loads the login data and *after* that the configs are subscribed. If a request for a refresh
* was issued while the setup, the refresh will be executed afterwards.
*/
private async setup(): Promise<void> {
await this.loadLoginData();
this.configService.get<string>('general_event_privacy_policy').subscribe(value => { this.configService.get<string>('general_event_privacy_policy').subscribe(value => {
this._privacy_policy.next(value); if (value !== undefined) {
this.storeLoginData(); this._privacyPolicy.next(value);
this.storeLoginDataRequests.next();
}
}); });
this.configService.get<string>('general_event_legal_notice').subscribe(value => { this.configService.get<string>('general_event_legal_notice').subscribe(value => {
this._legal_notice.next(value); if (value !== undefined) {
this.storeLoginData(); this._legalNotice.next(value);
this.storeLoginDataRequests.next();
}
}); });
configService.get<string>('openslides_theme').subscribe(value => { this.configService.get<string>('openslides_theme').subscribe(value => {
if (value) {
this._theme.next(value); this._theme.next(value);
this.storeLoginData(); this.storeLoginDataRequests.next();
}
}); });
configService.get<{ path: string; display_name: string }>('logo_web_header').subscribe(value => { this.configService.get<{ path: string; display_name: string }>('logo_web_header').subscribe(value => {
this._logo_web_header.next(value); if (value) {
this.storeLoginData(); this._logoWebHeader.next(value);
this.storeLoginDataRequests.next();
}
}); });
this.canRefresh = true;
if (this.markRefresh) {
this._refresh();
}
}
this.loadLoginData(); /**
* Explicit refresh the ata from the server.
*/
public refresh(): void {
if (this.canRefresh && !this.markRefresh) {
this._refresh();
} else if (!this.canRefresh) {
this.markRefresh = true;
}
}
/**
* The actual refresh implementation.
*/
private async _refresh(): Promise<void> {
try {
const loginData = await this.httpService.get<LoginData>(environment.urlPrefix + '/users/login/');
this.setLoginData(loginData);
this.storeLoginDataRequests.next();
} catch (e) {
console.log('Could not refresh login data', e);
}
this.markRefresh = false;
} }
/** /**
* Load the login data from the storage. If it there, set it. * Load the login data from the storage. If it there, set it.
*/ */
private async loadLoginData(): Promise<void> { private async loadLoginData(): Promise<void> {
const loginData = await this.storageService.get<LoginData | null>(LOGIN_DATA_STORAGE_KEY); const loginData = await this.storageService.get<any>(LOGIN_DATA_STORAGE_KEY);
if (loginData) { if (isLoginData(loginData)) {
this.setLoginData(loginData); this.setLoginData(loginData);
} }
} }
/** /**
* Setter for the login data * Triggers all subjects with the given data.
* *
* @param loginData the login data * @param loginData The data
*/ */
public setLoginData(loginData: LoginData): void { private setLoginData(loginData: LoginData): void {
this._privacy_policy.next(loginData.privacy_policy); this._privacyPolicy.next(loginData.privacy_policy);
this._legal_notice.next(loginData.legal_notice); this._legalNotice.next(loginData.legal_notice);
this._theme.next(loginData.theme); this._theme.next(loginData.theme);
this.storeLoginData(loginData); this._logoWebHeader.next(loginData.logo_web_header);
this._loginInfoText.next(loginData.login_info_text);
} }
/** /**
* Saves the login data in the storage. * Saves the login data to the storeage. Do not call this method and
* * use `storeLoginDataRequests` instead. The data to store will be
* @param loginData If given, this data is used. If it's null, the current values * taken form all subjects.
* from the behaviour subject are taken.
*/ */
private storeLoginData(loginData?: LoginData): void { private storeLoginData(): void {
if (!loginData) { if (this.OSStatus.isInHistoryMode) {
loginData = { return;
privacy_policy: this._privacy_policy.getValue(),
legal_notice: this._legal_notice.getValue(),
theme: this._theme.getValue(),
logo_web_header: this._logo_web_header.getValue()
};
} }
if (!this.OSStatus.isInHistoryMode) { const loginData = {
privacy_policy: this._privacyPolicy.getValue(),
legal_notice: this._legalNotice.getValue(),
theme: this._theme.getValue(),
logo_web_header: this._logoWebHeader.getValue()
};
this.storageService.set(LOGIN_DATA_STORAGE_KEY, loginData); this.storageService.set(LOGIN_DATA_STORAGE_KEY, loginData);
} }
} }
}

View File

@ -1,13 +1,17 @@
import { TestBed } from '@angular/core/testing'; import { TestBed, inject } from '@angular/core/testing';
import { MediaManageService } from './media-manage.service'; import { MediaManageService } from './media-manage.service';
import { E2EImportsModule } from 'e2e-imports.module'; import { E2EImportsModule } from 'e2e-imports.module';
describe('MediaManageService', () => { describe('MediaManageService', () => {
beforeEach(() => TestBed.configureTestingModule({ imports: [E2EImportsModule] })); beforeEach(() => {
TestBed.configureTestingModule({
imports: [E2EImportsModule],
providers: [MediaManageService]
});
});
it('should be created', () => { it('should be created', inject([MediaManageService], (service: MediaManageService) => {
const service: MediaManageService = TestBed.get(MediaManageService);
expect(service).toBeTruthy(); expect(service).toBeTruthy();
}); }));
}); });

View File

@ -1,17 +1,17 @@
import { TestBed } from '@angular/core/testing'; import { TestBed, inject } from '@angular/core/testing';
import { PdfDocumentService } from '../ui-services/pdf-document.service'; import { PdfDocumentService } from '../ui-services/pdf-document.service';
import { E2EImportsModule } from 'e2e-imports.module'; import { E2EImportsModule } from 'e2e-imports.module';
describe('PdfDocumentService', () => { describe('PdfDocumentService', () => {
beforeEach(() => beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [E2EImportsModule] imports: [E2EImportsModule],
}) providers: [PdfDocumentService]
); });
});
it('should be created', () => { it('should be created', inject([PdfDocumentService], (service: PdfDocumentService) => {
const service: PdfDocumentService = TestBed.get(PdfDocumentService);
expect(service).toBeTruthy(); expect(service).toBeTruthy();
}); }));
}); });

View File

@ -1,12 +1,17 @@
import { TestBed } from '@angular/core/testing'; import { TestBed, inject } from '@angular/core/testing';
import { PollService } from './poll.service'; import { PollService } from './poll.service';
import { E2EImportsModule } from 'e2e-imports.module';
describe('PollService', () => { describe('PollService', () => {
beforeEach(() => TestBed.configureTestingModule({})); beforeEach(() => {
TestBed.configureTestingModule({
imports: [E2EImportsModule],
providers: [PollService]
});
});
it('should be created', () => { it('should be created', inject([PollService], (service: PollService) => {
const service: PollService = TestBed.get(PollService);
expect(service).toBeTruthy(); expect(service).toBeTruthy();
}); }));
}); });

View File

@ -1,17 +1,17 @@
import { TestBed } from '@angular/core/testing'; import { TestBed, inject } from '@angular/core/testing';
import { RoutingStateService } from './routing-state.service'; import { RoutingStateService } from './routing-state.service';
import { E2EImportsModule } from 'e2e-imports.module'; import { E2EImportsModule } from 'e2e-imports.module';
describe('RoutingStateService', () => { describe('RoutingStateService', () => {
beforeEach(() => beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [E2EImportsModule] imports: [E2EImportsModule],
}) providers: [RoutingStateService]
); });
});
it('should be created', () => { it('should be created', inject([RoutingStateService], (service: RoutingStateService) => {
const service: RoutingStateService = TestBed.get(RoutingStateService);
expect(service).toBeTruthy(); expect(service).toBeTruthy();
}); }));
}); });

View File

@ -1,12 +1,17 @@
import { TestBed } from '@angular/core/testing'; import { TestBed, inject } from '@angular/core/testing';
import { SpinnerService } from './spinner.service'; import { SpinnerService } from './spinner.service';
import { E2EImportsModule } from 'e2e-imports.module';
describe('SpinnerService', () => { describe('SpinnerService', () => {
beforeEach(() => TestBed.configureTestingModule({})); beforeEach(() => {
TestBed.configureTestingModule({
imports: [E2EImportsModule],
providers: [SpinnerService]
});
});
it('should be created', () => { it('should be created', inject([SpinnerService], (service: SpinnerService) => {
const service: SpinnerService = TestBed.get(SpinnerService);
expect(service).toBeTruthy(); expect(service).toBeTruthy();
}); }));
}); });

View File

@ -1,12 +1,17 @@
import { TestBed } from '@angular/core/testing'; import { TestBed, inject } from '@angular/core/testing';
import { ThemeService } from './theme.service'; import { ThemeService } from './theme.service';
import { E2EImportsModule } from 'e2e-imports.module';
describe('ThemeService', () => { describe('ThemeService', () => {
beforeEach(() => TestBed.configureTestingModule({})); beforeEach(() => {
TestBed.configureTestingModule({
imports: [E2EImportsModule],
providers: [ThemeService]
});
});
it('should be created', () => { it('should be created', inject([ThemeService], (service: ThemeService) => {
const service: ThemeService = TestBed.get(ThemeService);
expect(service).toBeTruthy(); expect(service).toBeTruthy();
}); }));
}); });

View File

@ -77,17 +77,4 @@ export class ThemeService {
return null; return null;
} }
} }
/**
* Function to ensure, that there is at least one theme set to define
* the colors of the components.
*
* If a theme is already set, nothing happens, otherwise the
* `DEFAULT_THEME` will be set.
*/
public checkTheme(): void {
if (!this.currentTheme || this.currentTheme === '') {
this.changeTheme(ThemeService.DEFAULT_THEME);
}
}
} }

View File

@ -1,4 +1,4 @@
import { TestBed } from '@angular/core/testing'; import { TestBed, inject } from '@angular/core/testing';
import { UpdateService } from './update.service'; import { UpdateService } from './update.service';
import { E2EImportsModule } from 'e2e-imports.module'; import { E2EImportsModule } from 'e2e-imports.module';
@ -11,8 +11,7 @@ describe('UpdateService', () => {
}) })
); );
it('should be created', () => { it('should be created', inject([UpdateService], (service: UpdateService) => {
const service: UpdateService = TestBed.get(UpdateService);
expect(service).toBeTruthy(); expect(service).toBeTruthy();
}); }));
}); });

View File

@ -1,10 +1,12 @@
import { TestBed, inject } from '@angular/core/testing'; import { TestBed, inject } from '@angular/core/testing';
import { ViewportService } from './viewport.service'; import { ViewportService } from './viewport.service';
import { E2EImportsModule } from 'e2e-imports.module';
describe('ViewportService', () => { describe('ViewportService', () => {
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [E2EImportsModule],
providers: [ViewportService] providers: [ViewportService]
}); });
}); });

View File

@ -1,12 +1,17 @@
import { TestBed } from '@angular/core/testing'; import { TestBed, inject } from '@angular/core/testing';
import { XlsxExportServiceService } from './xlsx-export-service.service'; import { XlsxExportServiceService } from './xlsx-export-service.service';
import { E2EImportsModule } from 'e2e-imports.module';
describe('XlsxExportServiceService', () => { describe('XlsxExportServiceService', () => {
beforeEach(() => TestBed.configureTestingModule({})); beforeEach(() => {
TestBed.configureTestingModule({
imports: [E2EImportsModule],
providers: [XlsxExportServiceService]
});
});
it('should be created', () => { it('should be created', inject([XlsxExportServiceService], (service: XlsxExportServiceService) => {
const service: XlsxExportServiceService = TestBed.get(XlsxExportServiceService);
expect(service).toBeTruthy(); expect(service).toBeTruthy();
}); }));
}); });

View File

@ -13,6 +13,7 @@
.toolbar-left { .toolbar-left {
display: flex; display: flex;
max-width: calc(100% - 100px);
button { button {
margin: 12px 0; margin: 12px 0;
} }

View File

@ -1,6 +1,9 @@
<mat-card class="os-card"> <mat-card class="os-card">
<div> <div>
<div class="legal-notice-text" [innerHtml]="legalNotice"></div> <div *ngIf="legalNotice" class="legal-notice-text" [innerHtml]="legalNotice"></div>
<div *ngIf="!legalNotice" translate>
The event manager hasn't set up a legal notice yet.
</div>
<mat-divider></mat-divider> <mat-divider></mat-divider>
<div *ngIf="versionInfo" class="version-text"> <div *ngIf="versionInfo" class="version-text">
<a [attr.href]="versionInfo.openslides_url" target="_blank"> <a [attr.href]="versionInfo.openslides_url" target="_blank">

View File

@ -1,7 +1,5 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { LoginDataService } from 'app/core/ui-services/login-data.service'; import { LoginDataService } from 'app/core/ui-services/login-data.service';
import { environment } from 'environments/environment'; import { environment } from 'environments/environment';
import { HttpService } from 'app/core/core-services/http.service'; import { HttpService } from 'app/core/core-services/http.service';
@ -82,20 +80,14 @@ export class LegalNoticeContentComponent implements OnInit {
* @param translate * @param translate
* @param http * @param http
*/ */
public constructor( public constructor(private loginDataService: LoginDataService, private http: HttpService) {}
private loginDataService: LoginDataService,
private translate: TranslateService,
private http: HttpService
) {}
/** /**
* Subscribes for the legal notice text. * Subscribes for the legal notice text.
*/ */
public ngOnInit(): void { public ngOnInit(): void {
this.loginDataService.legal_notice.subscribe(legalNotice => { this.loginDataService.legalNotice.subscribe(legalNotice => {
if (legalNotice) { this.legalNotice = legalNotice;
this.legalNotice = this.translate.instant(legalNotice);
}
}); });
// Query the version info. // Query the version info.

View File

@ -42,7 +42,7 @@ export class LogoComponent implements OnInit, OnDestroy {
* On init method * On init method
*/ */
public ngOnInit(): void { public ngOnInit(): void {
this.logoSubscription = this.loginDataService.logo_web_header.subscribe(nextLogo => { this.logoSubscription = this.loginDataService.logoWebHeader.subscribe(nextLogo => {
if (nextLogo) { if (nextLogo) {
this.logoPath = nextLogo.path; this.logoPath = nextLogo.path;
} }

View File

@ -1,7 +1,5 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { LoginDataService } from 'app/core/ui-services/login-data.service'; import { LoginDataService } from 'app/core/ui-services/login-data.service';
/** /**
@ -24,16 +22,14 @@ export class PrivacyPolicyContentComponent implements OnInit {
* @param loginDataService Login Data * @param loginDataService Login Data
* @param translate for the translation * @param translate for the translation
*/ */
public constructor(private loginDataService: LoginDataService, private translate: TranslateService) {} public constructor(private loginDataService: LoginDataService) {}
/** /**
* Subscribes for the privacy policy text * Subscribes for the privacy policy text
*/ */
public ngOnInit(): void { public ngOnInit(): void {
this.loginDataService.privacy_policy.subscribe(privacyPolicy => { this.loginDataService.privacyPolicy.subscribe(privacyPolicy => {
if (privacyPolicy) { this.privacyPolicy = privacyPolicy;
this.privacyPolicy = this.translate.instant(privacyPolicy);
}
}); });
} }
} }

View File

@ -6,18 +6,13 @@ import { Title } from '@angular/platform-browser';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { BaseComponent } from 'app/base.component'; import { BaseViewComponent } from 'app/site/base/base-view';
import { AuthService } from 'app/core/core-services/auth.service'; import { AuthService } from 'app/core/core-services/auth.service';
import { OperatorService } from 'app/core/core-services/operator.service'; import { OperatorService } from 'app/core/core-services/operator.service';
import { environment } from 'environments/environment'; import { LoginDataService } from 'app/core/ui-services/login-data.service';
import { LoginDataService, LoginData } from 'app/core/ui-services/login-data.service';
import { ParentErrorStateMatcher } from 'app/shared/parent-error-state-matcher'; import { ParentErrorStateMatcher } from 'app/shared/parent-error-state-matcher';
import { HttpService } from 'app/core/core-services/http.service';
import { SpinnerService } from 'app/core/ui-services/spinner.service'; import { SpinnerService } from 'app/core/ui-services/spinner.service';
import { MatSnackBar } from '@angular/material';
interface LoginDataWithInfoText extends LoginData {
info_text?: string;
}
/** /**
* Login mask component. * Login mask component.
@ -29,7 +24,7 @@ interface LoginDataWithInfoText extends LoginData {
templateUrl: './login-mask.component.html', templateUrl: './login-mask.component.html',
styleUrls: ['./login-mask.component.scss'] styleUrls: ['./login-mask.component.scss']
}) })
export class LoginMaskComponent extends BaseComponent implements OnInit, OnDestroy { export class LoginMaskComponent extends BaseViewComponent implements OnInit, OnDestroy {
/** /**
* Show or hide password and change the indicator accordingly * Show or hide password and change the indicator accordingly
*/ */
@ -72,16 +67,16 @@ export class LoginMaskComponent extends BaseComponent implements OnInit, OnDestr
public constructor( public constructor(
title: Title, title: Title,
translate: TranslateService, translate: TranslateService,
matSnackBar: MatSnackBar,
private authService: AuthService, private authService: AuthService,
private operator: OperatorService, private operator: OperatorService,
private router: Router, private router: Router,
private route: ActivatedRoute, private route: ActivatedRoute,
private formBuilder: FormBuilder, private formBuilder: FormBuilder,
private httpService: HttpService,
private loginDataService: LoginDataService, private loginDataService: LoginDataService,
private spinnerService: SpinnerService private spinnerService: SpinnerService
) { ) {
super(title, translate); super(title, translate, matSnackBar);
// Hide the spinner if the user is at `login-mask` // Hide the spinner if the user is at `login-mask`
spinnerService.setVisibility(false); spinnerService.setVisibility(false);
this.createForm(); this.createForm();
@ -94,17 +89,8 @@ export class LoginMaskComponent extends BaseComponent implements OnInit, OnDestr
* Observes the operator, if a user was already logged in, recreate to user and skip the login * Observes the operator, if a user was already logged in, recreate to user and skip the login
*/ */
public ngOnInit(): void { public ngOnInit(): void {
// Get the login data. Save information to the login data service. If there is an this.subscriptions.push(
// error, ignore it. this.loginDataService.loginInfoText.subscribe(notice => (this.installationNotice = notice))
// TODO: This has to be caught by the offline service
this.httpService.get<LoginDataWithInfoText>(environment.urlPrefix + '/users/login/').then(
response => {
if (response.info_text) {
this.installationNotice = response.info_text;
}
this.loginDataService.setLoginData(response);
},
() => {}
); );
// Maybe the operator changes and the user is logged in. If so, redirect him and boot OpenSlides. // Maybe the operator changes and the user is logged in. If so, redirect him and boot OpenSlides.

View File

@ -4,6 +4,7 @@ import { Title } from '@angular/platform-browser';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { BaseComponent } from '../../../../base.component'; import { BaseComponent } from '../../../../base.component';
import { LoginDataService } from 'app/core/ui-services/login-data.service';
/** /**
* Login component. * Login component.
@ -22,7 +23,11 @@ export class LoginWrapperComponent extends BaseComponent implements OnInit {
* @param titleService to set the title * @param titleService to set the title
* @param translate just needed because super.setTitle depends in the `translator.instant` function * @param translate just needed because super.setTitle depends in the `translator.instant` function
*/ */
public constructor(protected titleService: Title, protected translate: TranslateService) { public constructor(
protected titleService: Title,
protected translate: TranslateService,
private loginDataService: LoginDataService
) {
super(titleService, translate); super(titleService, translate);
} }
@ -31,5 +36,6 @@ export class LoginWrapperComponent extends BaseComponent implements OnInit {
*/ */
public ngOnInit(): void { public ngOnInit(): void {
super.setTitle('Login'); super.setTitle('Login');
this.loginDataService.refresh();
} }
} }

View File

@ -21,10 +21,12 @@
color="primary" color="primary"
class="submit-button" class="submit-button"
[disabled]="newPasswordForm.invalid" [disabled]="newPasswordForm.invalid"
translate
> >
Reset password {{ 'Reset password' | translate }}
</button>
&nbsp;
<button type="button" mat-button routerLink="/login">
{{ 'Back to login' | translate }}
</button> </button>
<button type="button" class="back-button" routerLink="/login" translate>Back to login</button>
</form> </form>
</div> </div>

View File

@ -2,7 +2,14 @@
<form [formGroup]="resetPasswordForm" (ngSubmit)="resetPassword()" autocomplete="off"> <form [formGroup]="resetPasswordForm" (ngSubmit)="resetPassword()" autocomplete="off">
<h3 translate>Enter your email to send the password reset link</h3> <h3 translate>Enter your email to send the password reset link</h3>
<mat-form-field> <mat-form-field>
<input matInput required placeholder="{{ 'Email' | translate }}" formControlName="email" type="email" autocomplete="off" /> <input
matInput
required
placeholder="{{ 'Email' | translate }}"
formControlName="email"
type="email"
autocomplete="off"
/>
<mat-error *ngIf="resetPasswordForm.get('email').invalid" translate> <mat-error *ngIf="resetPasswordForm.get('email').invalid" translate>
Please enter a valid email address! Please enter a valid email address!
</mat-error> </mat-error>

View File

@ -8,7 +8,6 @@ import { TranslateService } from '@ngx-translate/core';
import { environment } from 'environments/environment'; import { environment } from 'environments/environment';
import { HttpService } from 'app/core/core-services/http.service'; import { HttpService } from 'app/core/core-services/http.service';
import { ThemeService } from 'app/core/ui-services/theme.service';
import { BaseViewComponent } from 'app/site/base/base-view'; import { BaseViewComponent } from 'app/site/base/base-view';
/** /**
@ -35,8 +34,7 @@ export class ResetPasswordComponent extends BaseViewComponent implements OnInit
matSnackBar: MatSnackBar, matSnackBar: MatSnackBar,
private http: HttpService, private http: HttpService,
formBuilder: FormBuilder, formBuilder: FormBuilder,
private router: Router, private router: Router
private themeService: ThemeService
) { ) {
super(titleService, translate, matSnackBar); super(titleService, translate, matSnackBar);
this.resetPasswordForm = formBuilder.group({ this.resetPasswordForm = formBuilder.group({
@ -49,7 +47,6 @@ export class ResetPasswordComponent extends BaseViewComponent implements OnInit
*/ */
public ngOnInit(): void { public ngOnInit(): void {
super.setTitle('Reset password'); super.setTitle('Reset password');
this.themeService.checkTheme();
} }
/** /**

View File

@ -86,6 +86,7 @@ mat-sidenav-container {
height: $size; height: $size;
width: $size; width: $size;
font-size: $size; font-size: $size;
margin: 0;
} }
} }
} }

View File

@ -697,20 +697,17 @@ class UserLoginView(WhoAmIDataView):
""" """
if self.request.method == "GET": if self.request.method == "GET":
if config["general_login_info_text"]: if config["general_login_info_text"]:
context["info_text"] = config["general_login_info_text"] context["login_info_text"] = config["general_login_info_text"]
else: else:
try: try:
user = User.objects.get(username="admin") user = User.objects.get(username="admin")
except User.DoesNotExist:
context["info_text"] = ""
else:
if user.check_password("admin"): if user.check_password("admin"):
context["info_text"] = ( context["login_info_text"] = (
f"Use <strong>admin</strong> and <strong>admin</strong> for your first login.<br>" f"Use <strong>admin</strong> and <strong>admin</strong> for your first login.<br>"
"Please change your password to hide this message!" "Please change your password to hide this message!"
) )
else: except User.DoesNotExist:
context["info_text"] = "" pass
# Add the privacy policy and legal notice, so the client can display it # Add the privacy policy and legal notice, so the client can display it
# even, it is not logged in. # even, it is not logged in.
context["privacy_policy"] = config["general_event_privacy_policy"] context["privacy_policy"] = config["general_event_privacy_policy"]

View File

@ -72,7 +72,7 @@ class TestUserLoginView(TestCase):
response = self.client.get(self.url) response = self.client.get(self.url)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertTrue(json.loads(response.content.decode()).get("info_text")) self.assertTrue(json.loads(response.content.decode()).get("login_info_text"))
def test_post_no_data(self): def test_post_no_data(self):
response = self.client.post(self.url) response = self.client.post(self.url)