more work on projector, countdowns, clos
- splitted clos-slide and clos-overlay. - Synchronize to server, more little changes
This commit is contained in:
parent
c52b82e77d
commit
965d23be50
@ -1,15 +1,23 @@
|
|||||||
import { TestBed, async } from '@angular/core/testing';
|
import { TestBed, async } from '@angular/core/testing';
|
||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
import { E2EImportsModule } from './../e2e-imports.module';
|
import { E2EImportsModule } from './../e2e-imports.module';
|
||||||
|
import { ServertimeService } from './core/services/servertime.service';
|
||||||
|
|
||||||
describe('AppComponent', () => {
|
describe('AppComponent', () => {
|
||||||
|
let servertimeService;
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [E2EImportsModule]
|
imports: [E2EImportsModule]
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
|
|
||||||
|
servertimeService = TestBed.get(ServertimeService);
|
||||||
|
spyOn(servertimeService, 'startScheduler').and.stub();
|
||||||
}));
|
}));
|
||||||
it('should create the app', async(() => {
|
it('should create the app', async(() => {
|
||||||
const fixture = TestBed.createComponent(AppComponent);
|
const fixture = TestBed.createComponent(AppComponent);
|
||||||
const app = fixture.debugElement.componentInstance;
|
const app = fixture.debugElement.componentInstance;
|
||||||
expect(app).toBeTruthy();
|
expect(app).toBeTruthy();
|
||||||
|
expect(servertimeService.startScheduler).toHaveBeenCalled();
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
@ -4,6 +4,7 @@ import { OperatorService } from './core/services/operator.service';
|
|||||||
import { LoginDataService } from './core/services/login-data.service';
|
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';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Angular's global App Component
|
* Angular's global App Component
|
||||||
@ -30,7 +31,8 @@ export class AppComponent {
|
|||||||
operator: OperatorService,
|
operator: OperatorService,
|
||||||
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
|
||||||
) {
|
) {
|
||||||
// manually add the supported languages
|
// manually add the supported languages
|
||||||
translate.addLangs(['en', 'de', 'cs']);
|
translate.addLangs(['en', 'de', 'cs']);
|
||||||
@ -42,6 +44,8 @@ export class AppComponent {
|
|||||||
translate.use(translate.getLangs().includes(browserLang) ? browserLang : 'en');
|
translate.use(translate.getLangs().includes(browserLang) ? browserLang : 'en');
|
||||||
// change default JS functions
|
// change default JS functions
|
||||||
this.overloadArrayToString();
|
this.overloadArrayToString();
|
||||||
|
|
||||||
|
servertimeService.startScheduler();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -76,8 +76,6 @@ export class AutoupdateService extends OpenSlidesComponent {
|
|||||||
* Handles the change ids of all autoupdates.
|
* Handles the change ids of all autoupdates.
|
||||||
*/
|
*/
|
||||||
private async storeResponse(autoupdate: AutoupdateFormat): Promise<void> {
|
private async storeResponse(autoupdate: AutoupdateFormat): Promise<void> {
|
||||||
console.log('got autoupdate', autoupdate);
|
|
||||||
|
|
||||||
if (autoupdate.all_data) {
|
if (autoupdate.all_data) {
|
||||||
await this.storeAllData(autoupdate);
|
await this.storeAllData(autoupdate);
|
||||||
} else {
|
} else {
|
||||||
|
@ -407,7 +407,6 @@ export class DataStoreService {
|
|||||||
* @param changeId The changeId from the update. If it's the highest change id seen, it will be set into the cache.
|
* @param changeId The changeId from the update. If it's the highest change id seen, it will be set into the cache.
|
||||||
*/
|
*/
|
||||||
public async flushToStorage(changeId: number): Promise<void> {
|
public async flushToStorage(changeId: number): Promise<void> {
|
||||||
console.log('flush to storage');
|
|
||||||
this._maxChangeId = changeId;
|
this._maxChangeId = changeId;
|
||||||
await this.storageService.set(DataStoreService.cachePrefix + 'DS', this.jsonStore);
|
await this.storageService.set(DataStoreService.cachePrefix + 'DS', this.jsonStore);
|
||||||
await this.storageService.set(DataStoreService.cachePrefix + 'maxChangeId', changeId);
|
await this.storageService.set(DataStoreService.cachePrefix + 'maxChangeId', changeId);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
import { OpenSlidesComponent } from 'app/openslides.component';
|
import { OpenSlidesComponent } from 'app/openslides.component';
|
||||||
import { Projectable } from 'app/site/base/projectable';
|
import { Projectable, ProjectorElementBuildDeskriptor, isProjectable } from 'app/site/base/projectable';
|
||||||
import { MatDialog } from '@angular/material';
|
import { MatDialog } from '@angular/material';
|
||||||
import {
|
import {
|
||||||
ProjectionDialogComponent,
|
ProjectionDialogComponent,
|
||||||
@ -32,19 +32,27 @@ export class ProjectionDialogService extends OpenSlidesComponent {
|
|||||||
*
|
*
|
||||||
* @param obj The projectable.
|
* @param obj The projectable.
|
||||||
*/
|
*/
|
||||||
public async openProjectDialogFor(obj: Projectable): Promise<void> {
|
public async openProjectDialogFor(obj: Projectable | ProjectorElementBuildDeskriptor): Promise<void> {
|
||||||
const dialogRef = this.dialog.open<ProjectionDialogComponent, Projectable, ProjectionDialogReturnType>(
|
let descriptor: ProjectorElementBuildDeskriptor;
|
||||||
|
if (isProjectable(obj)) {
|
||||||
|
descriptor = obj.getSlide();
|
||||||
|
} else {
|
||||||
|
descriptor = obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dialogRef = this.dialog.open<
|
||||||
ProjectionDialogComponent,
|
ProjectionDialogComponent,
|
||||||
{
|
ProjectorElementBuildDeskriptor,
|
||||||
minWidth: '500px',
|
ProjectionDialogReturnType
|
||||||
maxHeight: '90vh',
|
>(ProjectionDialogComponent, {
|
||||||
data: obj
|
minWidth: '500px',
|
||||||
}
|
maxHeight: '90vh',
|
||||||
);
|
data: descriptor
|
||||||
|
});
|
||||||
const response = await dialogRef.afterClosed().toPromise();
|
const response = await dialogRef.afterClosed().toPromise();
|
||||||
if (response) {
|
if (response) {
|
||||||
const [projectors, projectorElement]: ProjectionDialogReturnType = response;
|
const [projectors, projectorElement]: ProjectionDialogReturnType = response;
|
||||||
this.projectorService.projectOn(projectors, projectorElement);
|
this.projectorService.projectOnMultiple(projectors, projectorElement);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,22 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
import { OpenSlidesComponent } from 'app/openslides.component';
|
import { OpenSlidesComponent } from 'app/openslides.component';
|
||||||
import { Projectable } from 'app/site/base/projectable';
|
import {
|
||||||
|
Projectable,
|
||||||
|
ProjectorElementBuildDeskriptor,
|
||||||
|
isProjectable,
|
||||||
|
isProjectorElementBuildDeskriptor
|
||||||
|
} from 'app/site/base/projectable';
|
||||||
import { DataStoreService } from './data-store.service';
|
import { DataStoreService } from './data-store.service';
|
||||||
import { Projector, ProjectorElement } from 'app/shared/models/core/projector';
|
import {
|
||||||
import { DataSendService } from './data-send.service';
|
Projector,
|
||||||
|
ProjectorElement,
|
||||||
|
ProjectorElements,
|
||||||
|
IdentifiableProjectorElement
|
||||||
|
} from 'app/shared/models/core/projector';
|
||||||
|
import { HttpService } from './http.service';
|
||||||
|
import { SlideManager } from 'app/slides/services/slide-manager.service';
|
||||||
|
import { BaseModel } from 'app/shared/models/base/base-model';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This service cares about Projectables being projected and manage all projection-related
|
* This service cares about Projectables being projected and manage all projection-related
|
||||||
@ -22,7 +34,7 @@ export class ProjectorService extends OpenSlidesComponent {
|
|||||||
* @param DS
|
* @param DS
|
||||||
* @param dataSend
|
* @param dataSend
|
||||||
*/
|
*/
|
||||||
public constructor(private DS: DataStoreService, private dataSend: DataSendService) {
|
public constructor(private DS: DataStoreService, private http: HttpService, private slideManager: SlideManager) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,10 +44,16 @@ export class ProjectorService extends OpenSlidesComponent {
|
|||||||
* @param obj The object in question
|
* @param obj The object in question
|
||||||
* @returns true, if the object is projected on one projector.
|
* @returns true, if the object is projected on one projector.
|
||||||
*/
|
*/
|
||||||
public isProjected(obj: Projectable): boolean {
|
public isProjected(obj: Projectable | ProjectorElementBuildDeskriptor): boolean {
|
||||||
return this.DS.getAll<Projector>('core/projector').some(projector => {
|
if (isProjectable(obj)) {
|
||||||
return projector.isElementShown(obj.getNameForSlide(), obj.getIdForSlide());
|
return this.DS.getAll<Projector>('core/projector').some(projector => {
|
||||||
});
|
return projector.isElementShown(obj.getSlide().getBasicProjectorElement());
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return this.DS.getAll<Projector>('core/projector').some(projector => {
|
||||||
|
return projector.isElementShown(obj.getBasicProjectorElement());
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -44,10 +62,28 @@ export class ProjectorService extends OpenSlidesComponent {
|
|||||||
* @param obj The object in question
|
* @param obj The object in question
|
||||||
* @return All projectors, where this Object is projected on
|
* @return All projectors, where this Object is projected on
|
||||||
*/
|
*/
|
||||||
public getProjectorsWhichAreProjecting(obj: Projectable): Projector[] {
|
public getProjectorsWhichAreProjecting(obj: Projectable | ProjectorElementBuildDeskriptor): Projector[] {
|
||||||
return this.DS.getAll<Projector>('core/projector').filter(projector => {
|
if (isProjectable(obj)) {
|
||||||
return projector.isElementShown(obj.getNameForSlide(), obj.getIdForSlide());
|
return this.DS.getAll<Projector>('core/projector').filter(projector => {
|
||||||
});
|
return projector.isElementShown(obj.getSlide().getBasicProjectorElement());
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return this.DS.getAll<Projector>('core/projector').filter(projector => {
|
||||||
|
return projector.isElementShown(obj.getBasicProjectorElement());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getProjectorElement(
|
||||||
|
obj: Projectable | ProjectorElementBuildDeskriptor | IdentifiableProjectorElement
|
||||||
|
): IdentifiableProjectorElement {
|
||||||
|
if (isProjectable(obj)) {
|
||||||
|
return obj.getSlide().getBasicProjectorElement();
|
||||||
|
} else if (isProjectorElementBuildDeskriptor(obj)) {
|
||||||
|
return obj.getBasicProjectorElement();
|
||||||
|
} else {
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -57,8 +93,11 @@ export class ProjectorService extends OpenSlidesComponent {
|
|||||||
* @param projector The projector to test
|
* @param projector The projector to test
|
||||||
* @returns true, if the object is projected on the projector.
|
* @returns true, if the object is projected on the projector.
|
||||||
*/
|
*/
|
||||||
public isProjectedOn(obj: Projectable, projector: Projector): boolean {
|
public isProjectedOn(
|
||||||
return projector.isElementShown(obj.getNameForSlide(), obj.getIdForSlide());
|
obj: Projectable | ProjectorElementBuildDeskriptor | IdentifiableProjectorElement,
|
||||||
|
projector: Projector
|
||||||
|
): boolean {
|
||||||
|
return projector.isElementShown(this.getProjectorElement(obj));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -72,23 +111,86 @@ export class ProjectorService extends OpenSlidesComponent {
|
|||||||
* @param projectors All projectors where to add the element.
|
* @param projectors All projectors where to add the element.
|
||||||
* @param element The element in question.
|
* @param element The element in question.
|
||||||
*/
|
*/
|
||||||
public projectOn<T extends ProjectorElement>(projectors: Projector[], element: T): void {
|
public projectOnMultiple(projectors: Projector[], element: IdentifiableProjectorElement): void {
|
||||||
const changedProjectors: Projector[] = [];
|
|
||||||
this.DS.getAll<Projector>('core/projector').forEach(projector => {
|
this.DS.getAll<Projector>('core/projector').forEach(projector => {
|
||||||
if (projectors.includes(projector)) {
|
if (projectors.includes(projector)) {
|
||||||
projector.removeAllNonStableElements();
|
this.projectOn(projector, element);
|
||||||
projector.addElement(element);
|
} else if (projector.isElementShown(element)) {
|
||||||
changedProjectors.push(projector);
|
this.removeFrom(projector, element);
|
||||||
} else if (projector.isElementShown(element.name, element.id)) {
|
|
||||||
projector.removeElementByNameAndId(element.name, element.id);
|
|
||||||
changedProjectors.push(projector);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Use new 'project' route.
|
public async projectOn(
|
||||||
changedProjectors.forEach(projector => {
|
projector: Projector,
|
||||||
this.dataSend.updateModel(projector);
|
obj: Projectable | ProjectorElementBuildDeskriptor | IdentifiableProjectorElement
|
||||||
});
|
): Promise<void> {
|
||||||
|
const element = this.getProjectorElement(obj);
|
||||||
|
|
||||||
|
if (element.stable) {
|
||||||
|
// Just add this stable element
|
||||||
|
projector.addElement(element);
|
||||||
|
await this.projectRequest(projector, projector.elements);
|
||||||
|
} else {
|
||||||
|
// For non-stable elements remove all other non-stable elements, add them to the history and
|
||||||
|
// add the one new element to the projector.
|
||||||
|
const removedElements = projector.removeAllNonStableElements();
|
||||||
|
let changed = removedElements.length > 0;
|
||||||
|
|
||||||
|
if (element) {
|
||||||
|
projector.addElement(element);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
if (changed) {
|
||||||
|
await this.projectRequest(projector, projector.elements, null, removedElements);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async removeFrom(
|
||||||
|
projector: Projector,
|
||||||
|
obj: Projectable | ProjectorElementBuildDeskriptor | IdentifiableProjectorElement
|
||||||
|
): Promise<void> {
|
||||||
|
const element = this.getProjectorElement(obj);
|
||||||
|
|
||||||
|
if (element.stable) {
|
||||||
|
// Just remove this stable element
|
||||||
|
projector.removeElements(element);
|
||||||
|
await this.projectRequest(projector, projector.elements);
|
||||||
|
} else {
|
||||||
|
// For non-stable elements remove all current non-stable elements and add them to the history
|
||||||
|
const removedElements = projector.removeElements(element);
|
||||||
|
if (removedElements.length > 0) {
|
||||||
|
console.log(projector.elements, removedElements);
|
||||||
|
await this.projectRequest(projector, projector.elements, null, removedElements);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async projectRequest(
|
||||||
|
projector: Projector,
|
||||||
|
elements?: ProjectorElements,
|
||||||
|
preview?: ProjectorElements,
|
||||||
|
appendToHistory?: ProjectorElements,
|
||||||
|
deleteLastHistroyElement?: boolean
|
||||||
|
): Promise<void> {
|
||||||
|
const requestData: any = {};
|
||||||
|
if (elements) {
|
||||||
|
requestData.elements = elements;
|
||||||
|
}
|
||||||
|
if (preview) {
|
||||||
|
requestData.preview = preview;
|
||||||
|
}
|
||||||
|
if (appendToHistory && appendToHistory.length) {
|
||||||
|
requestData.append_to_history = appendToHistory;
|
||||||
|
}
|
||||||
|
if (deleteLastHistroyElement) {
|
||||||
|
requestData.delete_last_history_element = true;
|
||||||
|
}
|
||||||
|
if (appendToHistory && appendToHistory.length && deleteLastHistroyElement) {
|
||||||
|
throw new Error('You cannot append to the history and delete the last element at the same time');
|
||||||
|
}
|
||||||
|
await this.http.post(`/rest/core/projector/${projector.id}/project/`, requestData);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -103,4 +205,60 @@ export class ProjectorService extends OpenSlidesComponent {
|
|||||||
return projector.projectiondefaults.map(pd => pd.name).includes(projectiondefault);
|
return projector.projectiondefaults.map(pd => pd.name).includes(projectiondefault);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getModelFromProjectorElement<T extends BaseModel>(element: IdentifiableProjectorElement): T {
|
||||||
|
if (!this.slideManager.canSlideBeMappedToModel(element.name)) {
|
||||||
|
throw new Error('THis projectorelement cannot be mapped to a model');
|
||||||
|
}
|
||||||
|
const identifiers = element.getIdentifiers();
|
||||||
|
if (!identifiers.includes('name') || !identifiers.includes('name')) {
|
||||||
|
throw new Error('To map this element to a model, a name and id is needed.');
|
||||||
|
}
|
||||||
|
return this.DS.get<T>(element.name, element.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async projectNextSlide(projector: Projector): Promise<void> {
|
||||||
|
await this.projectPreviewSlide(projector, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async projectPreviewSlide(projector: Projector, previewIndex: number): Promise<void> {
|
||||||
|
if (projector.elements_preview.length === 0 || previewIndex >= projector.elements_preview.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const removedElements = projector.removeAllNonStableElements();
|
||||||
|
projector.addElement(projector.elements_preview.splice(previewIndex, 1)[0]);
|
||||||
|
await this.projectRequest(projector, projector.elements, projector.elements_preview, removedElements);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async projectPreviousSlide(projector: Projector): Promise<void> {
|
||||||
|
if (projector.elements_history.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Get the last element from the history
|
||||||
|
const lastElements: ProjectorElements = projector.elements_history[projector.elements_history.length - 1];
|
||||||
|
let lastElement: ProjectorElement = null;
|
||||||
|
if (lastElements.length > 0) {
|
||||||
|
lastElement = lastElements[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add all current elements to the preview.
|
||||||
|
const removedElements = projector.removeAllNonStableElements();
|
||||||
|
removedElements.forEach(e => projector.elements_preview.unshift(e));
|
||||||
|
|
||||||
|
// Add last element
|
||||||
|
if (lastElement) {
|
||||||
|
projector.addElement(lastElement);
|
||||||
|
}
|
||||||
|
await this.projectRequest(projector, projector.elements, projector.elements_preview, null, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async savePreview(projector: Projector): Promise<void> {
|
||||||
|
await this.projectRequest(projector, null, projector.elements_preview);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async addElementToPreview(projector: Projector, element: ProjectorElement): Promise<void> {
|
||||||
|
projector.elements_preview.push(element);
|
||||||
|
await this.projectRequest(projector, null, projector.elements_preview);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
17
client/src/app/core/services/servertime.service.spec.ts
Normal file
17
client/src/app/core/services/servertime.service.spec.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { TestBed, inject } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { E2EImportsModule } from '../../../e2e-imports.module';
|
||||||
|
import { ServertimeService } from './servertime.service';
|
||||||
|
|
||||||
|
describe('ServertimeService', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [E2EImportsModule],
|
||||||
|
providers: [ServertimeService]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', inject([ServertimeService], (service: ServertimeService) => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
}));
|
||||||
|
});
|
58
client/src/app/core/services/servertime.service.ts
Normal file
58
client/src/app/core/services/servertime.service.ts
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
|
import { OpenSlidesComponent } from 'app/openslides.component';
|
||||||
|
import { HttpService } from './http.service';
|
||||||
|
import { environment } from 'environments/environment.prod';
|
||||||
|
import { isNumber } from 'util';
|
||||||
|
import { BehaviorSubject, Observable } from 'rxjs';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class ServertimeService extends OpenSlidesComponent {
|
||||||
|
private static FAILURE_TIMEOUT = 30;
|
||||||
|
private static NORMAL_TIMEOUT = 60 * 5;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In milliseconds
|
||||||
|
*/
|
||||||
|
private serverOffsetSubject = new BehaviorSubject<number>(0);
|
||||||
|
|
||||||
|
public constructor(private http: HttpService) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public startScheduler(): void {
|
||||||
|
this.scheduleNextRefresh(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getServerOffsetObservable(): Observable<number> {
|
||||||
|
return this.serverOffsetSubject.asObservable();
|
||||||
|
}
|
||||||
|
|
||||||
|
private scheduleNextRefresh(seconds: number): void {
|
||||||
|
setTimeout(async () => {
|
||||||
|
let timeout = ServertimeService.NORMAL_TIMEOUT;
|
||||||
|
try {
|
||||||
|
await this.refreshServertime();
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
timeout = ServertimeService.FAILURE_TIMEOUT;
|
||||||
|
}
|
||||||
|
this.scheduleNextRefresh(timeout);
|
||||||
|
}, 1000 * seconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async refreshServertime(): Promise<void> {
|
||||||
|
// servertime is the time in seconds.
|
||||||
|
const servertime = await this.http.get<number>(environment.urlPrefix + '/core/servertime/');
|
||||||
|
if (!isNumber(servertime)) {
|
||||||
|
throw new Error('The returned servertime is not a number');
|
||||||
|
}
|
||||||
|
this.serverOffsetSubject.next(Math.floor(Date.now() - servertime * 1000));
|
||||||
|
}
|
||||||
|
|
||||||
|
public getServertime(): number {
|
||||||
|
return Date.now() - this.serverOffsetSubject.getValue();
|
||||||
|
}
|
||||||
|
}
|
@ -6,7 +6,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<ng-container *ngTemplateOutlet="title"></ng-container>
|
<ng-container *ngTemplateOutlet="title"></ng-container>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div *ngIf="showActionRow">
|
||||||
<ng-container *ngTemplateOutlet="actionRow"></ng-container>
|
<ng-container *ngTemplateOutlet="actionRow"></ng-container>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
@ -20,6 +20,10 @@
|
|||||||
.title-container {
|
.title-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
||||||
|
::ng-deep button {
|
||||||
|
color: rgba(0, 0, 0, 0.54);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -9,8 +9,7 @@ describe('MetaTextBlockComponent', () => {
|
|||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [E2EImportsModule],
|
imports: [E2EImportsModule]
|
||||||
declarations: [MetaTextBlockComponent]
|
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
}));
|
}));
|
||||||
|
|
@ -1,7 +1,7 @@
|
|||||||
import { Component, Input } from '@angular/core';
|
import { Component, Input } from '@angular/core';
|
||||||
|
|
||||||
import { BaseComponent } from '../../../../base.component';
|
import { BaseComponent } from '../../../base.component';
|
||||||
import { ViewportService } from '../../../../core/services/viewport.service';
|
import { ViewportService } from '../../../core/services/viewport.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component for the motion comments view
|
* Component for the motion comments view
|
||||||
@ -15,9 +15,6 @@ export class MetaTextBlockComponent extends BaseComponent {
|
|||||||
@Input()
|
@Input()
|
||||||
public showActionRow: boolean;
|
public showActionRow: boolean;
|
||||||
|
|
||||||
@Input()
|
|
||||||
public icon: string;
|
|
||||||
|
|
||||||
public constructor(public vp: ViewportService) {
|
public constructor(public vp: ViewportService) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
<h2 mat-dialog-title>{{ projectable.getTitle() }}</h2>
|
<h2 mat-dialog-title>{{ projectorElementBuildDescriptor.getTitle() }}</h2>
|
||||||
<mat-dialog-content>
|
<mat-dialog-content>
|
||||||
<mat-card>
|
<mat-card>
|
||||||
<mat-card-title> <span translate>Projectors</span> </mat-card-title>
|
<mat-card-title> <span translate>Projectors</span> </mat-card-title>
|
||||||
|
@ -4,10 +4,6 @@ mat-dialog-content {
|
|||||||
div.projectors {
|
div.projectors {
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
|
|
||||||
&.projected {
|
|
||||||
background-color: lightblue;
|
|
||||||
}
|
|
||||||
|
|
||||||
.right {
|
.right {
|
||||||
float: right;
|
float: right;
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
import { Component, Inject } from '@angular/core';
|
import { Component, Inject } from '@angular/core';
|
||||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material';
|
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material';
|
||||||
import { Projectable } from 'app/site/base/projectable';
|
import { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable';
|
||||||
import { DataStoreService } from 'app/core/services/data-store.service';
|
import { DataStoreService } from 'app/core/services/data-store.service';
|
||||||
import { Projector, ProjectorElement } from 'app/shared/models/core/projector';
|
import { Projector, IdentifiableProjectorElement } from 'app/shared/models/core/projector';
|
||||||
import { ProjectorService } from 'app/core/services/projector.service';
|
import { ProjectorService } from 'app/core/services/projector.service';
|
||||||
import {
|
import {
|
||||||
ProjectorOption,
|
SlideOption,
|
||||||
isProjectorDecisionOption,
|
isSlideDecisionOption,
|
||||||
isProjectorChoiceOption,
|
isSlideChoiceOption,
|
||||||
ProjectorDecisionOption,
|
SlideDecisionOption,
|
||||||
ProjectorChoiceOption,
|
SlideChoiceOption,
|
||||||
ProjectorOptions
|
SlideOptions
|
||||||
} from 'app/site/base/projector-options';
|
} from 'app/site/base/slide-options';
|
||||||
|
|
||||||
export type ProjectionDialogReturnType = [Projector[], ProjectorElement];
|
export type ProjectionDialogReturnType = [Projector[], IdentifiableProjectorElement];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
@ -25,40 +25,40 @@ export type ProjectionDialogReturnType = [Projector[], ProjectorElement];
|
|||||||
export class ProjectionDialogComponent {
|
export class ProjectionDialogComponent {
|
||||||
public projectors: Projector[];
|
public projectors: Projector[];
|
||||||
private selectedProjectors: Projector[] = [];
|
private selectedProjectors: Projector[] = [];
|
||||||
public projectorElement: ProjectorElement;
|
public projectorElement: IdentifiableProjectorElement;
|
||||||
public options: ProjectorOptions;
|
public options: SlideOptions;
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
public dialogRef: MatDialogRef<ProjectionDialogComponent, ProjectionDialogReturnType>,
|
public dialogRef: MatDialogRef<ProjectionDialogComponent, ProjectionDialogReturnType>,
|
||||||
@Inject(MAT_DIALOG_DATA) public projectable: Projectable,
|
@Inject(MAT_DIALOG_DATA) public projectorElementBuildDescriptor: ProjectorElementBuildDeskriptor,
|
||||||
private DS: DataStoreService,
|
private DS: DataStoreService,
|
||||||
private projectorService: ProjectorService
|
private projectorService: ProjectorService
|
||||||
) {
|
) {
|
||||||
this.projectors = this.DS.getAll<Projector>('core/projector');
|
this.projectors = this.DS.getAll<Projector>('core/projector');
|
||||||
// TODO: Maybe watch. But this may not be necessary for the short living time of this dialog.
|
// TODO: Maybe watch. But this may not be necessary for the short living time of this dialog.
|
||||||
|
|
||||||
this.selectedProjectors = this.projectorService.getProjectorsWhichAreProjecting(this.projectable);
|
this.selectedProjectors = this.projectorService.getProjectorsWhichAreProjecting(
|
||||||
|
this.projectorElementBuildDescriptor
|
||||||
|
);
|
||||||
|
|
||||||
// Add default projector, if the projectable is not projected on it.
|
// Add default projector, if the projectable is not projected on it.
|
||||||
const defaultProjector: Projector = this.projectorService.getProjectorForDefault(
|
if (this.projectorElementBuildDescriptor.projectionDefaultName) {
|
||||||
this.projectable.getProjectionDefaultName()
|
const defaultProjector: Projector = this.projectorService.getProjectorForDefault(
|
||||||
);
|
this.projectorElementBuildDescriptor.projectionDefaultName
|
||||||
if (!this.selectedProjectors.includes(defaultProjector)) {
|
);
|
||||||
this.selectedProjectors.push(defaultProjector);
|
if (!this.selectedProjectors.includes(defaultProjector)) {
|
||||||
|
this.selectedProjectors.push(defaultProjector);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.projectorElement = {
|
this.projectorElement = this.projectorElementBuildDescriptor.getBasicProjectorElement();
|
||||||
id: this.projectable.getIdForSlide(),
|
|
||||||
name: this.projectable.getNameForSlide(),
|
|
||||||
stable: this.projectable.isStableSlide()
|
|
||||||
};
|
|
||||||
|
|
||||||
// Set option defaults
|
// Set option defaults
|
||||||
this.projectable.getProjectorOptions().forEach(option => {
|
this.projectorElementBuildDescriptor.slideOptions.forEach(option => {
|
||||||
this.projectorElement[option.key] = option.default;
|
this.projectorElement[option.key] = option.default;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.options = this.projectable.getProjectorOptions();
|
this.options = this.projectorElementBuildDescriptor.slideOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
public toggleProjector(projector: Projector): void {
|
public toggleProjector(projector: Projector): void {
|
||||||
@ -75,15 +75,15 @@ export class ProjectionDialogComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public isProjectedOn(projector: Projector): boolean {
|
public isProjectedOn(projector: Projector): boolean {
|
||||||
return this.projectorService.isProjectedOn(this.projectable, projector);
|
return this.projectorService.isProjectedOn(this.projectorElementBuildDescriptor, projector);
|
||||||
}
|
}
|
||||||
|
|
||||||
public isDecisionOption(option: ProjectorOption): option is ProjectorDecisionOption {
|
public isDecisionOption(option: SlideOption): option is SlideDecisionOption {
|
||||||
return isProjectorDecisionOption(option);
|
return isSlideDecisionOption(option);
|
||||||
}
|
}
|
||||||
|
|
||||||
public isChoiceOption(option: ProjectorOption): option is ProjectorChoiceOption {
|
public isChoiceOption(option: SlideOption): option is SlideChoiceOption {
|
||||||
return isProjectorChoiceOption(option);
|
return isSlideChoiceOption(option);
|
||||||
}
|
}
|
||||||
|
|
||||||
public onOk(): void {
|
public onOk(): void {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Component, OnInit, Input } from '@angular/core';
|
import { Component, OnInit, Input } from '@angular/core';
|
||||||
import { Projectable } from 'app/site/base/projectable';
|
import { Projectable, ProjectorElementBuildDeskriptor } from 'app/site/base/projectable';
|
||||||
import { ProjectionDialogService } from 'app/core/services/projection-dialog.service';
|
import { ProjectionDialogService } from 'app/core/services/projection-dialog.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -11,7 +11,7 @@ import { ProjectionDialogService } from 'app/core/services/projection-dialog.ser
|
|||||||
})
|
})
|
||||||
export class ProjectorButtonComponent implements OnInit {
|
export class ProjectorButtonComponent implements OnInit {
|
||||||
@Input()
|
@Input()
|
||||||
public object: Projectable;
|
public object: Projectable | ProjectorElementBuildDeskriptor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The consotructor
|
* The consotructor
|
||||||
|
@ -5,6 +5,8 @@ import { BaseModel } from '../base/base-model';
|
|||||||
* @ignore
|
* @ignore
|
||||||
*/
|
*/
|
||||||
export class Countdown extends BaseModel<Countdown> {
|
export class Countdown extends BaseModel<Countdown> {
|
||||||
|
public static COLLECTIONSTRING = 'core/countdown';
|
||||||
|
|
||||||
public id: number;
|
public id: number;
|
||||||
public description: string;
|
public description: string;
|
||||||
public default_time: number;
|
public default_time: number;
|
||||||
@ -12,7 +14,7 @@ export class Countdown extends BaseModel<Countdown> {
|
|||||||
public running: boolean;
|
public running: boolean;
|
||||||
|
|
||||||
public constructor(input?: any) {
|
public constructor(input?: any) {
|
||||||
super('core/countdown', 'Countdown', input);
|
super(Countdown.COLLECTIONSTRING, 'Countdown', input);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getTitle(): string {
|
public getTitle(): string {
|
||||||
|
@ -5,11 +5,13 @@ import { BaseModel } from '../base/base-model';
|
|||||||
* @ignore
|
* @ignore
|
||||||
*/
|
*/
|
||||||
export class ProjectorMessage extends BaseModel<ProjectorMessage> {
|
export class ProjectorMessage extends BaseModel<ProjectorMessage> {
|
||||||
|
public static COLLECTIONSTRING = 'core/projector-message';
|
||||||
|
|
||||||
public id: number;
|
public id: number;
|
||||||
public message: string;
|
public message: string;
|
||||||
|
|
||||||
public constructor(input?: any) {
|
public constructor(input?: any) {
|
||||||
super('core/projector-message', 'Message', input);
|
super(ProjectorMessage.COLLECTIONSTRING, 'Message', input);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getTitle(): string {
|
public getTitle(): string {
|
||||||
|
@ -23,6 +23,10 @@ export interface ProjectorElement {
|
|||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IdentifiableProjectorElement extends ProjectorElement {
|
||||||
|
getIdentifiers(): (keyof IdentifiableProjectorElement)[];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Multiple elements.
|
* Multiple elements.
|
||||||
*/
|
*/
|
||||||
@ -45,6 +49,8 @@ export interface ProjectionDefault {
|
|||||||
export class Projector extends BaseModel<Projector> {
|
export class Projector extends BaseModel<Projector> {
|
||||||
public id: number;
|
public id: number;
|
||||||
public elements: ProjectorElements;
|
public elements: ProjectorElements;
|
||||||
|
public elements_preview: ProjectorElements;
|
||||||
|
public elements_history: ProjectorElements[];
|
||||||
public scale: number;
|
public scale: number;
|
||||||
public scroll: number;
|
public scroll: number;
|
||||||
public name: string;
|
public name: string;
|
||||||
@ -57,22 +63,30 @@ export class Projector extends BaseModel<Projector> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true, if there is an element with the given name (and optionally
|
* Must match all given identifiers. If a projectorelement does not have all keys
|
||||||
* an id). If the id is given, the element to search MUST have this id.
|
* to identify, it will be removed, if all existing keys match
|
||||||
*
|
*
|
||||||
* @param name The name of the element
|
* @returns true, TODO
|
||||||
* @param id The optional id to check.
|
|
||||||
* @returns true, if there is at least one element with the given name (and id).
|
|
||||||
*/
|
*/
|
||||||
public isElementShown(name: string, id?: number): boolean {
|
public isElementShown(element: IdentifiableProjectorElement): boolean {
|
||||||
return this.elements.some(element => element.name === name && (!id || element.id === id));
|
return this.elements.some(elementOnProjector => {
|
||||||
|
return element.getIdentifiers().every(identifier => {
|
||||||
|
return !elementOnProjector[identifier] || elementOnProjector[identifier] === element[identifier];
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes all elements, that do not have `stable=true`.
|
* Removes all elements, that do not have `stable=true`.
|
||||||
|
*
|
||||||
|
* TODO: use this.partitionArray
|
||||||
|
*
|
||||||
|
* @returns all removed unstable elements
|
||||||
*/
|
*/
|
||||||
public removeAllNonStableElements(): void {
|
public removeAllNonStableElements(): ProjectorElements {
|
||||||
|
const unstableElements = this.elements.filter(element => !element.stable);
|
||||||
this.elements = this.elements.filter(element => element.stable);
|
this.elements = this.elements.filter(element => element.stable);
|
||||||
|
return unstableElements;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -85,17 +99,28 @@ export class Projector extends BaseModel<Projector> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes elements given by the name and optional id. If no id is given
|
* Must match everything. If a projectorelement does not have all keys
|
||||||
* all elements with a matching name are removed.
|
* to identify, it will be removed, if all existing keys match
|
||||||
*
|
|
||||||
* If an id is given, ut the element dies not specify an id, it will be removed.
|
|
||||||
*
|
|
||||||
* @param name The name to search
|
|
||||||
* @param id The optional id to search.
|
|
||||||
*/
|
*/
|
||||||
public removeElementByNameAndId(name: string, id?: number): void {
|
public removeElements(element: IdentifiableProjectorElement): ProjectorElements {
|
||||||
this.elements = this.elements.filter(
|
let removedElements: ProjectorElements;
|
||||||
element => element.name !== name || (!id && !element.id && element.id !== id)
|
let nonRemovedElements: ProjectorElements;
|
||||||
|
[removedElements, nonRemovedElements] = this.partitionArray(this.elements, elementOnProjector => {
|
||||||
|
return element.getIdentifiers().every(identifier => {
|
||||||
|
return !elementOnProjector[identifier] || elementOnProjector[identifier] === element[identifier];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.elements = nonRemovedElements;
|
||||||
|
return removedElements;
|
||||||
|
}
|
||||||
|
|
||||||
|
private partitionArray<T>(array: T[], callback: (element: T) => boolean): [T[], T[]] {
|
||||||
|
return array.reduce(
|
||||||
|
(result, element) => {
|
||||||
|
result[callback(element) ? 0 : 1].push(element);
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
[[], []] as [T[], T[]]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,6 +77,7 @@ import { C4DialogComponent, CopyrightSignComponent } from './components/copyrigh
|
|||||||
import { ProjectorButtonComponent } from './components/projector-button/projector-button.component';
|
import { ProjectorButtonComponent } from './components/projector-button/projector-button.component';
|
||||||
import { ProjectionDialogComponent } from './components/projection-dialog/projection-dialog.component';
|
import { ProjectionDialogComponent } from './components/projection-dialog/projection-dialog.component';
|
||||||
import { ResizedDirective } from './directives/resized.directive';
|
import { ResizedDirective } from './directives/resized.directive';
|
||||||
|
import { MetaTextBlockComponent } from './components/meta-text-block/meta-text-block.component';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Share Module for all "dumb" components and pipes.
|
* Share Module for all "dumb" components and pipes.
|
||||||
@ -188,7 +189,8 @@ import { ResizedDirective } from './directives/resized.directive';
|
|||||||
C4DialogComponent,
|
C4DialogComponent,
|
||||||
ProjectorButtonComponent,
|
ProjectorButtonComponent,
|
||||||
ProjectionDialogComponent,
|
ProjectionDialogComponent,
|
||||||
ResizedDirective
|
ResizedDirective,
|
||||||
|
MetaTextBlockComponent
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
PermsDirective,
|
PermsDirective,
|
||||||
@ -211,7 +213,8 @@ import { ResizedDirective } from './directives/resized.directive';
|
|||||||
C4DialogComponent,
|
C4DialogComponent,
|
||||||
ProjectorButtonComponent,
|
ProjectorButtonComponent,
|
||||||
ProjectionDialogComponent,
|
ProjectionDialogComponent,
|
||||||
ResizedDirective
|
ResizedDirective,
|
||||||
|
MetaTextBlockComponent
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: DateAdapter, useClass: OpenSlidesDateAdapter },
|
{ provide: DateAdapter, useClass: OpenSlidesDateAdapter },
|
||||||
|
@ -1,47 +1,9 @@
|
|||||||
import { Projectable } from './projectable';
|
import { Projectable, ProjectorElementBuildDeskriptor } from './projectable';
|
||||||
import { BaseViewModel } from './base-view-model';
|
import { BaseViewModel } from './base-view-model';
|
||||||
import { ProjectorOptions } from './projector-options';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base view class for projectable models.
|
* Base view class for projectable models.
|
||||||
*/
|
*/
|
||||||
export abstract class BaseProjectableModel extends BaseViewModel implements Projectable {
|
export abstract class BaseProjectableModel extends BaseViewModel implements Projectable {
|
||||||
/**
|
public abstract getSlide(): ProjectorElementBuildDeskriptor;
|
||||||
* Per default, a slide does not have any options
|
|
||||||
*
|
|
||||||
* @override
|
|
||||||
*/
|
|
||||||
public getProjectorOptions(): ProjectorOptions {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @override
|
|
||||||
*/
|
|
||||||
public abstract getProjectionDefaultName(): string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The id should match the model's id.
|
|
||||||
*
|
|
||||||
* @override
|
|
||||||
*/
|
|
||||||
public getIdForSlide(): number {
|
|
||||||
return this.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A model s return the collection string
|
|
||||||
*
|
|
||||||
* @override
|
|
||||||
*/
|
|
||||||
public abstract getNameForSlide(): string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Per default a model is a non-stable element.
|
|
||||||
*
|
|
||||||
* @override
|
|
||||||
*/
|
|
||||||
public isStableSlide(): boolean {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,32 +1,34 @@
|
|||||||
import { ProjectorOptions } from './projector-options';
|
|
||||||
import { Displayable } from 'app/shared/models/base/displayable';
|
import { Displayable } from 'app/shared/models/base/displayable';
|
||||||
|
import { IdentifiableProjectorElement } from 'app/shared/models/core/projector';
|
||||||
|
import { SlideOptions } from './slide-options';
|
||||||
|
|
||||||
|
export function isProjectorElementBuildDeskriptor(obj: any): obj is ProjectorElementBuildDeskriptor {
|
||||||
|
const deskriptor = <ProjectorElementBuildDeskriptor>obj;
|
||||||
|
return (
|
||||||
|
deskriptor.slideOptions !== undefined &&
|
||||||
|
deskriptor.getBasicProjectorElement !== undefined &&
|
||||||
|
deskriptor.getTitle !== undefined
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProjectorElementBuildDeskriptor {
|
||||||
|
slideOptions: SlideOptions;
|
||||||
|
projectionDefaultName?: string;
|
||||||
|
getBasicProjectorElement(): IdentifiableProjectorElement;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The title to show in the projection dialog
|
||||||
|
*/
|
||||||
|
getTitle(): string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isProjectable(obj: any): obj is Projectable {
|
||||||
|
return (<Projectable>obj).getSlide !== undefined;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for every model, that should be projectable.
|
* Interface for every model, that should be projectable.
|
||||||
*/
|
*/
|
||||||
export interface Projectable extends Displayable {
|
export interface Projectable extends Displayable {
|
||||||
/**
|
getSlide(): ProjectorElementBuildDeskriptor;
|
||||||
* All options for the slide
|
|
||||||
*/
|
|
||||||
getProjectorOptions(): ProjectorOptions;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The projection default name for the slide
|
|
||||||
*/
|
|
||||||
getProjectionDefaultName(): string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The (optional) id for the slide
|
|
||||||
*/
|
|
||||||
getIdForSlide(): number | null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The slide's name
|
|
||||||
*/
|
|
||||||
getNameForSlide(): string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The stable attribute for the slide.
|
|
||||||
*/
|
|
||||||
isStableSlide(): boolean;
|
|
||||||
}
|
}
|
||||||
|
@ -1,32 +0,0 @@
|
|||||||
export interface ProjectorDecisionOption {
|
|
||||||
key: string;
|
|
||||||
displayName: string;
|
|
||||||
default: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ProjectorChoiceOption extends ProjectorDecisionOption {
|
|
||||||
choices: { value: string; displayName: string }[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ProjectorOption = ProjectorDecisionOption | ProjectorChoiceOption;
|
|
||||||
export type ProjectorOptions = ProjectorOption[];
|
|
||||||
|
|
||||||
export function isProjectorDecisionOption(object: any): object is ProjectorDecisionOption {
|
|
||||||
const option = <ProjectorDecisionOption>object;
|
|
||||||
return (
|
|
||||||
option.key !== undefined &&
|
|
||||||
option.displayName !== undefined &&
|
|
||||||
option.default !== undefined &&
|
|
||||||
(<ProjectorChoiceOption>object).choices === undefined
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isProjectorChoiceOption(object: any): object is ProjectorChoiceOption {
|
|
||||||
const option = <ProjectorChoiceOption>object;
|
|
||||||
return (
|
|
||||||
option.key !== undefined &&
|
|
||||||
option.displayName !== undefined &&
|
|
||||||
option.default !== undefined &&
|
|
||||||
option.choices !== undefined
|
|
||||||
);
|
|
||||||
}
|
|
32
client/src/app/site/base/slide-options.ts
Normal file
32
client/src/app/site/base/slide-options.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
export interface SlideDecisionOption {
|
||||||
|
key: string;
|
||||||
|
displayName: string;
|
||||||
|
default: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SlideChoiceOption extends SlideDecisionOption {
|
||||||
|
choices: { value: string; displayName: string }[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SlideOption = SlideDecisionOption | SlideChoiceOption;
|
||||||
|
export type SlideOptions = SlideOption[];
|
||||||
|
|
||||||
|
export function isSlideDecisionOption(object: any): object is SlideDecisionOption {
|
||||||
|
const option = <SlideDecisionOption>object;
|
||||||
|
return (
|
||||||
|
option.key !== undefined &&
|
||||||
|
option.displayName !== undefined &&
|
||||||
|
option.default !== undefined &&
|
||||||
|
(<SlideChoiceOption>object).choices === undefined
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isSlideChoiceOption(object: any): object is SlideChoiceOption {
|
||||||
|
const option = <SlideChoiceOption>object;
|
||||||
|
return (
|
||||||
|
option.key !== undefined &&
|
||||||
|
option.displayName !== undefined &&
|
||||||
|
option.default !== undefined &&
|
||||||
|
option.choices !== undefined
|
||||||
|
);
|
||||||
|
}
|
@ -4,6 +4,8 @@ import { PrivacyPolicyComponent } from './components/privacy-policy/privacy-poli
|
|||||||
import { StartComponent } from './components/start/start.component';
|
import { StartComponent } from './components/start/start.component';
|
||||||
import { LegalNoticeComponent } from './components/legal-notice/legal-notice.component';
|
import { LegalNoticeComponent } from './components/legal-notice/legal-notice.component';
|
||||||
import { SearchComponent } from './components/search/search.component';
|
import { SearchComponent } from './components/search/search.component';
|
||||||
|
import { CountdownListComponent } from './components/countdown-list/countdown-list.component';
|
||||||
|
import { ProjectorMessageListComponent } from './components/projectormessage-list/projectormessage-list.component';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
@ -21,6 +23,14 @@ const routes: Routes = [
|
|||||||
{
|
{
|
||||||
path: 'search',
|
path: 'search',
|
||||||
component: SearchComponent
|
component: SearchComponent
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'countdowns',
|
||||||
|
component: CountdownListComponent
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'messages',
|
||||||
|
component: ProjectorMessageListComponent
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -7,9 +7,20 @@ import { PrivacyPolicyComponent } from './components/privacy-policy/privacy-poli
|
|||||||
import { StartComponent } from './components/start/start.component';
|
import { StartComponent } from './components/start/start.component';
|
||||||
import { LegalNoticeComponent } from './components/legal-notice/legal-notice.component';
|
import { LegalNoticeComponent } from './components/legal-notice/legal-notice.component';
|
||||||
import { SearchComponent } from './components/search/search.component';
|
import { SearchComponent } from './components/search/search.component';
|
||||||
|
import { CountdownRepositoryService } from './services/countdown-repository.service';
|
||||||
|
import { CountdownListComponent } from './components/countdown-list/countdown-list.component';
|
||||||
|
import { ProjectorMessageListComponent } from './components/projectormessage-list/projectormessage-list.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
providers: [CountdownRepositoryService],
|
||||||
imports: [AngularCommonModule, CommonRoutingModule, SharedModule],
|
imports: [AngularCommonModule, CommonRoutingModule, SharedModule],
|
||||||
declarations: [PrivacyPolicyComponent, StartComponent, LegalNoticeComponent, SearchComponent]
|
declarations: [
|
||||||
|
PrivacyPolicyComponent,
|
||||||
|
StartComponent,
|
||||||
|
LegalNoticeComponent,
|
||||||
|
SearchComponent,
|
||||||
|
CountdownListComponent,
|
||||||
|
ProjectorMessageListComponent
|
||||||
|
]
|
||||||
})
|
})
|
||||||
export class CommonModule {}
|
export class CommonModule {}
|
||||||
|
@ -0,0 +1,92 @@
|
|||||||
|
<os-head-bar [nav]="false" [goBack]="true" [mainButton]="true" (mainEvent)="onPlusButton()">
|
||||||
|
<!-- Title -->
|
||||||
|
<div class="title-slot">
|
||||||
|
<h2 translate>Countdowns</h2>
|
||||||
|
</div>
|
||||||
|
</os-head-bar>
|
||||||
|
|
||||||
|
<div class="head-spacer"></div>
|
||||||
|
<mat-card *ngIf="countdownToCreate">
|
||||||
|
<mat-card-title translate>New countdown</mat-card-title>
|
||||||
|
<mat-card-content>
|
||||||
|
<form [formGroup]="createForm"
|
||||||
|
(keydown)="onKeyDownCreate($event)">
|
||||||
|
<p>
|
||||||
|
<mat-form-field>
|
||||||
|
<input formControlName="description" matInput placeholder="{{'Description' | translate}}" required>
|
||||||
|
<mat-hint *ngIf="!createForm.controls.description.valid">
|
||||||
|
<span translate>Required</span>
|
||||||
|
</mat-hint>
|
||||||
|
</mat-form-field>
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
</mat-card-content>
|
||||||
|
<mat-card-actions>
|
||||||
|
<button mat-button (click)="create()">
|
||||||
|
<span translate>Save</span>
|
||||||
|
</button>
|
||||||
|
<button mat-button (click)="onCancelCreate()">
|
||||||
|
<span translate>Cancel</span>
|
||||||
|
</button>
|
||||||
|
</mat-card-actions>
|
||||||
|
</mat-card>
|
||||||
|
|
||||||
|
<mat-accordion class="os-card">
|
||||||
|
<mat-expansion-panel *ngFor="let countdown of countdowns" (opened)="openId = countdown.id"
|
||||||
|
(closed)="panelClosed(countdown)" [expanded]="openId === countdown.id" multiple="false">
|
||||||
|
|
||||||
|
<!-- Projector button and countdown description-->
|
||||||
|
<mat-expansion-panel-header>
|
||||||
|
<mat-panel-title>
|
||||||
|
<div class="header-container">
|
||||||
|
<div class="header-projector-button">
|
||||||
|
<os-projector-button [object]="countdown"></os-projector-button>
|
||||||
|
</div>
|
||||||
|
<div class="header-name">
|
||||||
|
{{ countdown.description }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</mat-panel-title>
|
||||||
|
</mat-expansion-panel-header>
|
||||||
|
<form [formGroup]="updateForm"
|
||||||
|
*ngIf="editId === countdown.id"
|
||||||
|
(keydown)="onKeyDownUpdate($event)">
|
||||||
|
<h5 translate>Edit countdown</h5>
|
||||||
|
<p>
|
||||||
|
<mat-form-field>
|
||||||
|
<input formControlName="description" matInput placeholder="{{ 'Description' | translate}}" required>
|
||||||
|
<mat-hint *ngIf="!updateForm.controls.description.valid">
|
||||||
|
<span translate>Required</span>
|
||||||
|
</mat-hint>
|
||||||
|
</mat-form-field>
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
<ng-container *ngIf="editId !== countdown.id">
|
||||||
|
TODO: Show countdown time etc.
|
||||||
|
</ng-container>
|
||||||
|
<mat-action-row>
|
||||||
|
<button *ngIf="editId !== countdown.id" mat-button class="on-transition-fade" (click)="onEditButton(countdown)"
|
||||||
|
mat-icon-button>
|
||||||
|
<mat-icon>edit</mat-icon>
|
||||||
|
</button>
|
||||||
|
<button *ngIf="editId === countdown.id" mat-button class="on-transition-fade" (click)="onCancelUpdate()"
|
||||||
|
mat-icon-button>
|
||||||
|
<mat-icon>close</mat-icon>
|
||||||
|
</button>
|
||||||
|
<button *ngIf="editId === countdown.id" mat-button class="on-transition-fade" (click)="onSaveButton(countdown)"
|
||||||
|
mat-icon-button>
|
||||||
|
<mat-icon>save</mat-icon>
|
||||||
|
</button>
|
||||||
|
<button mat-button class='on-transition-fade' (click)=onDeleteButton(countdown) mat-icon-button>
|
||||||
|
<mat-icon>delete</mat-icon>
|
||||||
|
</button>
|
||||||
|
</mat-action-row>
|
||||||
|
</mat-expansion-panel>
|
||||||
|
</mat-accordion>
|
||||||
|
|
||||||
|
<mat-card *ngIf="countdowns.length === 0">
|
||||||
|
<mat-card-content>
|
||||||
|
<div class="no-content" translate>No countdowns</div>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
@ -0,0 +1,34 @@
|
|||||||
|
.head-spacer {
|
||||||
|
width: 100%;
|
||||||
|
height: 60px;
|
||||||
|
line-height: 60px;
|
||||||
|
text-align: right;
|
||||||
|
background: white; /* TODO: remove this and replace with theme */
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-card {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-container {
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: auto;
|
||||||
|
grid-template-columns: 40px 1fr;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
grid-row-start: 1;
|
||||||
|
grid-row-end: span 1;
|
||||||
|
grid-column-end: span 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-projector-button {
|
||||||
|
grid-column-start: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-name {
|
||||||
|
grid-column-start: 2;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { CountdownListComponent } from './countdown-list.component';
|
||||||
|
import { E2EImportsModule } from 'e2e-imports.module';
|
||||||
|
|
||||||
|
describe('CountdownListComponent', () => {
|
||||||
|
let component: CountdownListComponent;
|
||||||
|
let fixture: ComponentFixture<CountdownListComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [E2EImportsModule],
|
||||||
|
declarations: [CountdownListComponent]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(CountdownListComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,188 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { Title } from '@angular/platform-browser';
|
||||||
|
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
|
||||||
|
import { PromptService } from '../../../../core/services/prompt.service';
|
||||||
|
import { BaseViewComponent } from '../../../base/base-view';
|
||||||
|
import { MatSnackBar } from '@angular/material';
|
||||||
|
import { ViewCountdown } from '../../models/view-countdown';
|
||||||
|
import { CountdownRepositoryService } from '../../services/countdown-repository.service';
|
||||||
|
import { Countdown } from 'app/shared/models/core/countdown';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List view for the statute paragraphs.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'os-countdown-list',
|
||||||
|
templateUrl: './countdown-list.component.html',
|
||||||
|
styleUrls: ['./countdown-list.component.scss']
|
||||||
|
})
|
||||||
|
export class CountdownListComponent extends BaseViewComponent implements OnInit {
|
||||||
|
public countdownToCreate: Countdown | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Source of the Data
|
||||||
|
*/
|
||||||
|
public countdowns: ViewCountdown[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current focussed formgroup
|
||||||
|
*/
|
||||||
|
public updateForm: FormGroup;
|
||||||
|
|
||||||
|
public createForm: FormGroup;
|
||||||
|
|
||||||
|
public openId: number | null;
|
||||||
|
public editId: number | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
public constructor(
|
||||||
|
titleService: Title,
|
||||||
|
translate: TranslateService,
|
||||||
|
matSnackBar: MatSnackBar,
|
||||||
|
private repo: CountdownRepositoryService,
|
||||||
|
private formBuilder: FormBuilder,
|
||||||
|
private promptService: PromptService
|
||||||
|
) {
|
||||||
|
super(titleService, translate, matSnackBar);
|
||||||
|
|
||||||
|
const form = {
|
||||||
|
description: ['', Validators.required]
|
||||||
|
};
|
||||||
|
this.createForm = this.formBuilder.group(form);
|
||||||
|
this.updateForm = this.formBuilder.group(form);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Init function.
|
||||||
|
*
|
||||||
|
* Sets the title and gets/observes countdowns from DataStore
|
||||||
|
*/
|
||||||
|
public ngOnInit(): void {
|
||||||
|
super.setTitle('Countdowns');
|
||||||
|
this.repo.getViewModelListObservable().subscribe(newCountdowns => {
|
||||||
|
this.countdowns = newCountdowns;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new Section.
|
||||||
|
*/
|
||||||
|
public onPlusButton(): void {
|
||||||
|
if (!this.countdownToCreate) {
|
||||||
|
this.createForm.reset();
|
||||||
|
this.createForm.setValue({
|
||||||
|
description: ''
|
||||||
|
});
|
||||||
|
this.countdownToCreate = new Countdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler when clicking on create to create a new statute paragraph
|
||||||
|
*/
|
||||||
|
public create(): void {
|
||||||
|
if (this.createForm.valid) {
|
||||||
|
this.countdownToCreate.patchValues(this.createForm.value as Countdown);
|
||||||
|
this.repo.create(this.countdownToCreate).then(() => {
|
||||||
|
this.countdownToCreate = null;
|
||||||
|
}, this.raiseError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executed on edit button
|
||||||
|
* @param countdown
|
||||||
|
*/
|
||||||
|
public onEditButton(countdown: ViewCountdown): void {
|
||||||
|
this.editId = countdown.id;
|
||||||
|
|
||||||
|
this.updateForm.setValue({
|
||||||
|
description: countdown.description
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the countdown
|
||||||
|
* @param countdown The countdown to save
|
||||||
|
*/
|
||||||
|
public onSaveButton(countdown: ViewCountdown): void {
|
||||||
|
if (this.updateForm.valid) {
|
||||||
|
this.repo.update(this.updateForm.value as Partial<Countdown>, countdown).then(() => {
|
||||||
|
this.openId = this.editId = null;
|
||||||
|
}, this.raiseError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is executed, when the delete button is pressed
|
||||||
|
*
|
||||||
|
* @param countdown The countdown to delete
|
||||||
|
*/
|
||||||
|
public async onDeleteButton(countdown: ViewCountdown): Promise<void> {
|
||||||
|
const content = this.translate.instant('Delete') + ` ${countdown.description}?`;
|
||||||
|
if (await this.promptService.open('Are you sure?', content)) {
|
||||||
|
this.repo.delete(countdown).then(() => (this.openId = this.editId = null), this.raiseError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is executed when a mat-extension-panel is closed
|
||||||
|
*
|
||||||
|
* @param countdown the statute paragraph in the panel
|
||||||
|
*/
|
||||||
|
public panelClosed(countdown: ViewCountdown): void {
|
||||||
|
this.openId = null;
|
||||||
|
if (this.editId) {
|
||||||
|
this.onSaveButton(countdown);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* clicking Shift and Enter will save automatically
|
||||||
|
* clicking Escape will cancel the process
|
||||||
|
*
|
||||||
|
* @param event has the code
|
||||||
|
*/
|
||||||
|
public onKeyDownCreate(event: KeyboardEvent): void {
|
||||||
|
if (event.key === 'Enter' && event.shiftKey) {
|
||||||
|
this.create();
|
||||||
|
}
|
||||||
|
if (event.key === 'Escape') {
|
||||||
|
this.onCancelCreate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancels the current form action
|
||||||
|
*/
|
||||||
|
public onCancelCreate(): void {
|
||||||
|
this.countdownToCreate = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* clicking Shift and Enter will save automatically
|
||||||
|
* clicking Escape will cancel the process
|
||||||
|
*
|
||||||
|
* @param event has the code
|
||||||
|
*/
|
||||||
|
public onKeyDownUpdate(event: KeyboardEvent): void {
|
||||||
|
if (event.key === 'Enter' && event.shiftKey) {
|
||||||
|
const countdown = this.countdowns.find(x => x.id === this.editId);
|
||||||
|
this.onSaveButton(countdown);
|
||||||
|
}
|
||||||
|
if (event.key === 'Escape') {
|
||||||
|
this.onCancelUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancels the current form action
|
||||||
|
*/
|
||||||
|
public onCancelUpdate(): void {
|
||||||
|
this.editId = null;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
<os-head-bar [nav]="false" [goBack]="true" [mainButton]="true" (mainEvent)="onPlusButton()">
|
||||||
|
<!-- Title -->
|
||||||
|
<div class="title-slot">
|
||||||
|
<h2 translate>Messages</h2>
|
||||||
|
</div>
|
||||||
|
</os-head-bar>
|
||||||
|
|
||||||
|
<div class="head-spacer"></div>
|
||||||
|
<p>TODO</p>
|
@ -0,0 +1,26 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { E2EImportsModule } from 'e2e-imports.module';
|
||||||
|
import { ProjectorMessageListComponent } from './projectormessage-list.component';
|
||||||
|
|
||||||
|
describe('CountdownListComponent', () => {
|
||||||
|
let component: ProjectorMessageListComponent;
|
||||||
|
let fixture: ComponentFixture<ProjectorMessageListComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [E2EImportsModule],
|
||||||
|
declarations: [ProjectorMessageListComponent]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(ProjectorMessageListComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,32 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { Title } from '@angular/platform-browser';
|
||||||
|
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
import { BaseViewComponent } from '../../../base/base-view';
|
||||||
|
import { MatSnackBar } from '@angular/material';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List view for the statute paragraphs.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'os-projectormessage-list',
|
||||||
|
templateUrl: './projectormessage-list.component.html',
|
||||||
|
styleUrls: ['./projectormessage-list.component.scss']
|
||||||
|
})
|
||||||
|
export class ProjectorMessageListComponent extends BaseViewComponent implements OnInit {
|
||||||
|
public constructor(titleService: Title, translate: TranslateService, matSnackBar: MatSnackBar) {
|
||||||
|
super(titleService, translate, matSnackBar);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Init function.
|
||||||
|
*
|
||||||
|
* Sets the title and gets/observes countdowns from DataStore
|
||||||
|
*/
|
||||||
|
public ngOnInit(): void {
|
||||||
|
super.setTitle('Messages');
|
||||||
|
}
|
||||||
|
|
||||||
|
public onPlusButton(): void {}
|
||||||
|
}
|
46
client/src/app/site/common/models/view-countdown.ts
Normal file
46
client/src/app/site/common/models/view-countdown.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import { Countdown } from '../../../shared/models/core/countdown';
|
||||||
|
import { BaseProjectableModel } from 'app/site/base/base-projectable-model';
|
||||||
|
import { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable';
|
||||||
|
|
||||||
|
export class ViewCountdown extends BaseProjectableModel {
|
||||||
|
private _countdown: Countdown;
|
||||||
|
|
||||||
|
public get countdown(): Countdown {
|
||||||
|
return this._countdown ? this._countdown : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get id(): number {
|
||||||
|
return this.countdown ? this.countdown.id : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get description(): string {
|
||||||
|
return this.countdown ? this.countdown.description : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public constructor(countdown?: Countdown) {
|
||||||
|
super();
|
||||||
|
this._countdown = countdown;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTitle(): string {
|
||||||
|
return this.description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public updateValues(countdown: Countdown): void {
|
||||||
|
console.log('Update countdown TODO with vals:', countdown);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getSlide(): ProjectorElementBuildDeskriptor {
|
||||||
|
return {
|
||||||
|
getBasicProjectorElement: () => ({
|
||||||
|
stable: true,
|
||||||
|
name: Countdown.COLLECTIONSTRING,
|
||||||
|
id: this.id,
|
||||||
|
getIdentifiers: () => ['name', 'id']
|
||||||
|
}),
|
||||||
|
slideOptions: [],
|
||||||
|
projectionDefaultName: 'countdowns',
|
||||||
|
getTitle: () => this.getTitle()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
46
client/src/app/site/common/models/view-projectormessage.ts
Normal file
46
client/src/app/site/common/models/view-projectormessage.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import { BaseProjectableModel } from 'app/site/base/base-projectable-model';
|
||||||
|
import { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable';
|
||||||
|
import { ProjectorMessage } from 'app/shared/models/core/projector-message';
|
||||||
|
|
||||||
|
export class ViewProjectorMessage extends BaseProjectableModel {
|
||||||
|
private _message: ProjectorMessage;
|
||||||
|
|
||||||
|
public get messaage(): ProjectorMessage {
|
||||||
|
return this._message ? this._message : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get id(): number {
|
||||||
|
return this.messaage ? this.messaage.id : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get message(): string {
|
||||||
|
return this.messaage ? this.messaage.message : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public constructor(message?: ProjectorMessage) {
|
||||||
|
super();
|
||||||
|
this._message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTitle(): string {
|
||||||
|
return 'Message 1';
|
||||||
|
}
|
||||||
|
|
||||||
|
public updateValues(message: ProjectorMessage): void {
|
||||||
|
console.log('Update message TODO with vals:', message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getSlide(): ProjectorElementBuildDeskriptor {
|
||||||
|
return {
|
||||||
|
getBasicProjectorElement: () => ({
|
||||||
|
stable: true,
|
||||||
|
name: ProjectorMessage.COLLECTIONSTRING,
|
||||||
|
id: this.id,
|
||||||
|
getIdentifiers: () => ['name', 'id']
|
||||||
|
}),
|
||||||
|
slideOptions: [],
|
||||||
|
projectionDefaultName: 'messages',
|
||||||
|
getTitle: () => this.getTitle()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
import { TestBed, inject } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { E2EImportsModule } from 'e2e-imports.module';
|
||||||
|
import { CountdownRepositoryService } from './countdown-repository.service';
|
||||||
|
|
||||||
|
describe('StatuteParagraphRepositoryService', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [E2EImportsModule],
|
||||||
|
providers: [CountdownRepositoryService]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', inject([CountdownRepositoryService], (service: CountdownRepositoryService) => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
}));
|
||||||
|
});
|
@ -0,0 +1,39 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { DataSendService } from '../../../core/services/data-send.service';
|
||||||
|
import { DataStoreService } from '../../../core/services/data-store.service';
|
||||||
|
import { BaseRepository } from '../../base/base-repository';
|
||||||
|
import { Identifiable } from '../../../shared/models/base/identifiable';
|
||||||
|
import { CollectionStringModelMapperService } from '../../../core/services/collectionStringModelMapper.service';
|
||||||
|
import { ViewCountdown } from '../models/view-countdown';
|
||||||
|
import { Countdown } from '../../../shared/models/core/countdown';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class CountdownRepositoryService extends BaseRepository<ViewCountdown, Countdown> {
|
||||||
|
public constructor(
|
||||||
|
DS: DataStoreService,
|
||||||
|
mapperService: CollectionStringModelMapperService,
|
||||||
|
private dataSend: DataSendService
|
||||||
|
) {
|
||||||
|
super(DS, mapperService, Countdown);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected createViewModel(countdown: Countdown): ViewCountdown {
|
||||||
|
return new ViewCountdown(countdown);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async create(countdown: Countdown): Promise<Identifiable> {
|
||||||
|
return await this.dataSend.createModel(countdown);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async update(countdown: Partial<Countdown>, viewCountdown: ViewCountdown): Promise<void> {
|
||||||
|
const update = viewCountdown.countdown;
|
||||||
|
update.patchValues(countdown);
|
||||||
|
await this.dataSend.updateModel(update);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async delete(viewCountdown: ViewCountdown): Promise<void> {
|
||||||
|
await this.dataSend.deleteModel(viewCountdown.countdown);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
import { TestBed, inject } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { E2EImportsModule } from 'e2e-imports.module';
|
||||||
|
import { ProjectorMessageRepositoryService } from './projectormessage-repository.service';
|
||||||
|
|
||||||
|
describe('ProjectorMessageRepositoryService', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [E2EImportsModule],
|
||||||
|
providers: [ProjectorMessageRepositoryService]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', inject(
|
||||||
|
[ProjectorMessageRepositoryService],
|
||||||
|
(service: ProjectorMessageRepositoryService) => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
}
|
||||||
|
));
|
||||||
|
});
|
@ -0,0 +1,32 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { DataStoreService } from '../../../core/services/data-store.service';
|
||||||
|
import { BaseRepository } from '../../base/base-repository';
|
||||||
|
import { Identifiable } from '../../../shared/models/base/identifiable';
|
||||||
|
import { CollectionStringModelMapperService } from '../../../core/services/collectionStringModelMapper.service';
|
||||||
|
import { ProjectorMessage } from 'app/shared/models/core/projector-message';
|
||||||
|
import { ViewProjectorMessage } from '../models/view-projectormessage';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class ProjectorMessageRepositoryService extends BaseRepository<ViewProjectorMessage, ProjectorMessage> {
|
||||||
|
public constructor(DS: DataStoreService, mapperService: CollectionStringModelMapperService) {
|
||||||
|
super(DS, mapperService, ProjectorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected createViewModel(message: ProjectorMessage): ViewProjectorMessage {
|
||||||
|
return new ViewProjectorMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async create(message: ProjectorMessage): Promise<Identifiable> {
|
||||||
|
throw new Error('TODO');
|
||||||
|
}
|
||||||
|
|
||||||
|
public async update(message: Partial<ProjectorMessage>, viewMessage: ViewProjectorMessage): Promise<void> {
|
||||||
|
throw new Error('TODO');
|
||||||
|
}
|
||||||
|
|
||||||
|
public async delete(viewMessage: ViewProjectorMessage): Promise<void> {
|
||||||
|
throw new Error('TODO');
|
||||||
|
}
|
||||||
|
}
|
@ -2,7 +2,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
|||||||
|
|
||||||
import { MotionCommentsComponent } from './motion-comments.component';
|
import { MotionCommentsComponent } from './motion-comments.component';
|
||||||
import { E2EImportsModule } from '../../../../../e2e-imports.module';
|
import { E2EImportsModule } from '../../../../../e2e-imports.module';
|
||||||
import { MetaTextBlockComponent } from '../meta-text-block/meta-text-block.component';
|
|
||||||
|
|
||||||
describe('MotionCommentsComponent', () => {
|
describe('MotionCommentsComponent', () => {
|
||||||
let component: MotionCommentsComponent;
|
let component: MotionCommentsComponent;
|
||||||
@ -11,7 +10,7 @@ describe('MotionCommentsComponent', () => {
|
|||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [E2EImportsModule],
|
imports: [E2EImportsModule],
|
||||||
declarations: [MetaTextBlockComponent, MotionCommentsComponent]
|
declarations: [MotionCommentsComponent]
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<os-meta-text-block showActionRow="true">
|
<os-meta-text-block showActionRow="true">
|
||||||
<ng-container class="meta-text-block-title">
|
<ng-container class="meta-text-block-title">
|
||||||
<span translate>Voting result</span> <span *ngIf="pollIndex"> ({{ pollIndex + 1 }})</span>
|
<span translate>Voting result</span>
|
||||||
|
<span *ngIf="pollIndex"> ({{ pollIndex + 1 }})</span>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container class="meta-text-block-content">
|
<ng-container class="meta-text-block-content">
|
||||||
<div *ngIf="poll.has_votes" class="on-transition-fade poll-result">
|
<div *ngIf="poll.has_votes" class="on-transition-fade poll-result">
|
||||||
@ -51,12 +52,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container class="meta-text-block-action-row" *osPerms="'motions.can_manage_metadata'">
|
<ng-container class="meta-text-block-action-row" *osPerms="'motions.can_manage_metadata'">
|
||||||
<button mat-icon-button class="main-nav-color" matTooltip="{{ 'Edit' | translate }}" (click)="editPoll()">
|
<button mat-icon-button matTooltip="{{ 'Edit' | translate }}" (click)="editPoll()">
|
||||||
<mat-icon inline>edit</mat-icon>
|
<mat-icon inline>edit</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
mat-icon-button
|
mat-icon-button
|
||||||
class="main-nav-color"
|
|
||||||
matTooltip="{{ 'Print ballot papers' | translate }}"
|
matTooltip="{{ 'Print ballot papers' | translate }}"
|
||||||
(click)="printBallots()"
|
(click)="printBallots()"
|
||||||
>
|
>
|
||||||
@ -64,7 +64,6 @@
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
mat-icon-button
|
mat-icon-button
|
||||||
class="main-nav-color"
|
|
||||||
matTooltip="{{ 'Delete' | translate }}"
|
matTooltip="{{ 'Delete' | translate }}"
|
||||||
(click)="deletePoll()"
|
(click)="deletePoll()"
|
||||||
>
|
>
|
||||||
|
@ -47,6 +47,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-nav-color {
|
.main-nav-color {
|
||||||
color: rgba(0, 0, 0, 0.54);
|
color: rgba(0, 0, 0, 0.54);
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
|||||||
|
|
||||||
import { PersonalNoteComponent } from './personal-note.component';
|
import { PersonalNoteComponent } from './personal-note.component';
|
||||||
import { E2EImportsModule } from 'e2e-imports.module';
|
import { E2EImportsModule } from 'e2e-imports.module';
|
||||||
import { MetaTextBlockComponent } from '../meta-text-block/meta-text-block.component';
|
|
||||||
|
|
||||||
describe('PersonalNoteComponent', () => {
|
describe('PersonalNoteComponent', () => {
|
||||||
let component: PersonalNoteComponent;
|
let component: PersonalNoteComponent;
|
||||||
@ -11,7 +10,7 @@ describe('PersonalNoteComponent', () => {
|
|||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [E2EImportsModule],
|
imports: [E2EImportsModule],
|
||||||
declarations: [MetaTextBlockComponent, PersonalNoteComponent]
|
declarations: [PersonalNoteComponent]
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ import { User } from '../../../shared/models/users/user';
|
|||||||
import { ViewMotionCommentSection } from './view-motion-comment-section';
|
import { ViewMotionCommentSection } from './view-motion-comment-section';
|
||||||
import { Workflow } from '../../../shared/models/motions/workflow';
|
import { Workflow } from '../../../shared/models/motions/workflow';
|
||||||
import { WorkflowState } from '../../../shared/models/motions/workflow-state';
|
import { WorkflowState } from '../../../shared/models/motions/workflow-state';
|
||||||
import { ProjectorOptions } from 'app/site/base/projector-options';
|
import { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The line numbering mode for the motion detail view.
|
* The line numbering mode for the motion detail view.
|
||||||
@ -459,28 +459,29 @@ export class ViewMotion extends BaseProjectableModel {
|
|||||||
return this.amendment_paragraphs.length > 0;
|
return this.amendment_paragraphs.length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getProjectorOptions(): ProjectorOptions {
|
public getSlide(): ProjectorElementBuildDeskriptor {
|
||||||
return [
|
return {
|
||||||
{
|
getBasicProjectorElement: () => ({
|
||||||
key: 'mode',
|
name: Motion.COLLECTIONSTRING,
|
||||||
displayName: 'Mode',
|
id: this.id,
|
||||||
default: 'original',
|
getIdentifiers: () => ['name', 'id']
|
||||||
choices: [
|
}),
|
||||||
{ value: 'original', displayName: 'Original' },
|
slideOptions: [
|
||||||
{ value: 'changed', displayName: 'Changed' },
|
{
|
||||||
{ value: 'diff', displayName: 'Diff' },
|
key: 'mode',
|
||||||
{ value: 'agreed', displayName: 'Agreed' }
|
displayName: 'Mode',
|
||||||
]
|
default: 'original',
|
||||||
}
|
choices: [
|
||||||
];
|
{ value: 'original', displayName: 'Original' },
|
||||||
}
|
{ value: 'changed', displayName: 'Changed' },
|
||||||
|
{ value: 'diff', displayName: 'Diff' },
|
||||||
public getProjectionDefaultName(): string {
|
{ value: 'agreed', displayName: 'Agreed' }
|
||||||
return 'motions';
|
]
|
||||||
}
|
}
|
||||||
|
],
|
||||||
public getNameForSlide(): string {
|
projectionDefaultName: 'motions',
|
||||||
return Motion.COLLECTIONSTRING;
|
getTitle: () => this.getTitle()
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -12,7 +12,6 @@ import { MotionChangeRecommendationComponent } from './components/motion-change-
|
|||||||
import { MotionDetailOriginalChangeRecommendationsComponent } from './components/motion-detail-original-change-recommendations/motion-detail-original-change-recommendations.component';
|
import { MotionDetailOriginalChangeRecommendationsComponent } from './components/motion-detail-original-change-recommendations/motion-detail-original-change-recommendations.component';
|
||||||
import { MotionDetailDiffComponent } from './components/motion-detail-diff/motion-detail-diff.component';
|
import { MotionDetailDiffComponent } from './components/motion-detail-diff/motion-detail-diff.component';
|
||||||
import { MotionCommentsComponent } from './components/motion-comments/motion-comments.component';
|
import { MotionCommentsComponent } from './components/motion-comments/motion-comments.component';
|
||||||
import { MetaTextBlockComponent } from './components/meta-text-block/meta-text-block.component';
|
|
||||||
import { PersonalNoteComponent } from './components/personal-note/personal-note.component';
|
import { PersonalNoteComponent } from './components/personal-note/personal-note.component';
|
||||||
import { CallListComponent } from './components/call-list/call-list.component';
|
import { CallListComponent } from './components/call-list/call-list.component';
|
||||||
import { AmendmentCreateWizardComponent } from './components/amendment-create-wizard/amendment-create-wizard.component';
|
import { AmendmentCreateWizardComponent } from './components/amendment-create-wizard/amendment-create-wizard.component';
|
||||||
@ -36,7 +35,6 @@ import { MotionExportDialogComponent } from './components/motion-export-dialog/m
|
|||||||
MotionDetailOriginalChangeRecommendationsComponent,
|
MotionDetailOriginalChangeRecommendationsComponent,
|
||||||
MotionDetailDiffComponent,
|
MotionDetailDiffComponent,
|
||||||
MotionCommentsComponent,
|
MotionCommentsComponent,
|
||||||
MetaTextBlockComponent,
|
|
||||||
PersonalNoteComponent,
|
PersonalNoteComponent,
|
||||||
CallListComponent,
|
CallListComponent,
|
||||||
AmendmentCreateWizardComponent,
|
AmendmentCreateWizardComponent,
|
||||||
@ -53,7 +51,6 @@ import { MotionExportDialogComponent } from './components/motion-export-dialog/m
|
|||||||
StatuteParagraphListComponent,
|
StatuteParagraphListComponent,
|
||||||
MotionCommentsComponent,
|
MotionCommentsComponent,
|
||||||
MotionCommentSectionListComponent,
|
MotionCommentSectionListComponent,
|
||||||
MetaTextBlockComponent,
|
|
||||||
PersonalNoteComponent,
|
PersonalNoteComponent,
|
||||||
ManageSubmittersComponent,
|
ManageSubmittersComponent,
|
||||||
MotionPollDialogComponent,
|
MotionPollDialogComponent,
|
||||||
|
@ -5,15 +5,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</os-head-bar>
|
</os-head-bar>
|
||||||
|
|
||||||
<div class="content-container">
|
<div class="content-container" *ngIf="projector">
|
||||||
<div class="column-left">
|
<div class="column-left">
|
||||||
<div id="projector">
|
<a [routerLink]="['/projector', projector.id]">
|
||||||
<os-projector [projector]="projector"></os-projector>
|
<div id="projector">
|
||||||
</div>
|
<os-projector [projector]="projector"></os-projector>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="column-right">
|
<div class="column-right">
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
{{ projector?.scroll }}
|
<div class="button-size">{{ projector.scroll }}</div>
|
||||||
<button type="button" mat-icon-button (click)="scroll(scrollScaleDirection.Up)">
|
<button type="button" mat-icon-button (click)="scroll(scrollScaleDirection.Up)">
|
||||||
<mat-icon>arrow_upward</mat-icon>
|
<mat-icon>arrow_upward</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
@ -24,9 +26,8 @@
|
|||||||
<mat-icon>refresh</mat-icon>
|
<mat-icon>refresh</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
{{ projector?.scale }}
|
<div class="button-size">{{ projector.scale }}</div>
|
||||||
<button type="button" mat-icon-button (click)="scale(scrollScaleDirection.Up)">
|
<button type="button" mat-icon-button (click)="scale(scrollScaleDirection.Up)">
|
||||||
<mat-icon>zoom_in</mat-icon>
|
<mat-icon>zoom_in</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
@ -37,5 +38,121 @@
|
|||||||
<mat-icon>refresh</mat-icon>
|
<mat-icon>refresh</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<hr>
|
||||||
|
<div class="control-group">
|
||||||
|
<button type="button" mat-button (click)="projectPreviousSlide()" [disabled]="projector?.elements_history.length === 0">
|
||||||
|
<mat-icon>arrow_back</mat-icon>
|
||||||
|
<span translate>Previous</span>
|
||||||
|
</button>
|
||||||
|
<button type="button" mat-button (click)="projectNextSlide()" [disabled]="projector?.elements_preview.length === 0">
|
||||||
|
<span translate>Next</span>
|
||||||
|
<mat-icon>arrow_forward</mat-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="queue">
|
||||||
|
<h5 translate>History</h5>
|
||||||
|
<p *ngFor="let elements of projector?.elements_history">
|
||||||
|
{{ getElementDescription(elements[0]) }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h5 translate>Current</h5>
|
||||||
|
|
||||||
|
<div *ngIf="projector.non_stable_elements.length">
|
||||||
|
<h4 translate>Slides</h4>
|
||||||
|
<mat-list>
|
||||||
|
<mat-list-item *ngFor="let element of projector.non_stable_elements" class="projected">
|
||||||
|
<button type="button" mat-icon-button (click)="unprojectCurrent(element)">
|
||||||
|
<mat-icon>videocam</mat-icon>
|
||||||
|
</button>
|
||||||
|
{{ getElementDescription(element) }}
|
||||||
|
</mat-list-item>
|
||||||
|
</mat-list>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="countdowns.length">
|
||||||
|
<h4>
|
||||||
|
<span translate>Countdowns</span>
|
||||||
|
<button type="button" mat-icon-button disableRipple routerLink="/countdowns">
|
||||||
|
<mat-icon>edit</mat-icon>
|
||||||
|
</button>
|
||||||
|
</h4>
|
||||||
|
<mat-list>
|
||||||
|
<mat-list-item *ngFor="let countdown of countdowns" [ngClass]="{'projected': isProjected(countdown)}">
|
||||||
|
<button type="button" mat-icon-button (click)="project(countdown)">
|
||||||
|
<mat-icon>videocam</mat-icon>
|
||||||
|
</button>
|
||||||
|
{{ countdown.description }}
|
||||||
|
</mat-list-item>
|
||||||
|
</mat-list>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="messages.length">
|
||||||
|
<h4>
|
||||||
|
<span translate>Messages</span>
|
||||||
|
<button type="button" mat-icon-button disableRipple routerLink="/messages">
|
||||||
|
<mat-icon>edit</mat-icon>
|
||||||
|
</button>
|
||||||
|
</h4>
|
||||||
|
<mat-list>
|
||||||
|
<mat-list-item *ngFor="let message of messages; let i = index" [ngClass]="{'projected': isProjected(message)}">
|
||||||
|
<button type="button" mat-icon-button (click)="project(message)">
|
||||||
|
<mat-icon>videocam</mat-icon>
|
||||||
|
</button>
|
||||||
|
<span translate>Message</span> {{ i + 1 }}
|
||||||
|
</mat-list-item>
|
||||||
|
</mat-list>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h4>Current list of speakers overlay</h4>
|
||||||
|
<mat-list>
|
||||||
|
<mat-list-item [ngClass]="{'projected': isClosProjected(true)}">
|
||||||
|
<button type="button" mat-icon-button (click)="toggleClos(true)">
|
||||||
|
<mat-icon>videocam</mat-icon>
|
||||||
|
</button>
|
||||||
|
<span translate>Current list of speakers overlay</span>
|
||||||
|
</mat-list-item>
|
||||||
|
</mat-list>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="!isClosProjected(false)">
|
||||||
|
<h4>Current list of speakers slide</h4>
|
||||||
|
<mat-list>
|
||||||
|
<mat-list-item>
|
||||||
|
<button type="button" mat-icon-button (click)="toggleClos(false)">
|
||||||
|
<mat-icon>videocam</mat-icon>
|
||||||
|
</button>
|
||||||
|
<span translate>Current list of speakers slide</span>
|
||||||
|
</mat-list-item>
|
||||||
|
</mat-list>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="queue">
|
||||||
|
<h5 translate>Queue</h5>
|
||||||
|
<div cdkDropList class="drop-list" (cdkDropListDropped)="onSortingChange($event)">
|
||||||
|
<div class="list-entry" *ngFor="let element of projector.elements_preview; let i = index" cdkDrag>
|
||||||
|
<div class="drag-handle" cdkDragHandle>
|
||||||
|
<mat-icon>unfold_more</mat-icon>
|
||||||
|
</div>
|
||||||
|
<div class="name">
|
||||||
|
{{ i+1 }}. <span>{{ getElementDescription(element) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="button-right">
|
||||||
|
<div>
|
||||||
|
<button type="button" mat-button (click)="projectNow(i)">
|
||||||
|
<span translate>Project now</span>
|
||||||
|
</button>
|
||||||
|
<button type="button" mat-icon-button (click)="removePreviewElement(i)">
|
||||||
|
<mat-icon>close</mat-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -5,16 +5,91 @@
|
|||||||
.column-left {
|
.column-left {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding-top: 20px;
|
padding-top: 20px;
|
||||||
width: 70%;
|
width: 60%;
|
||||||
|
min-width: 200px;
|
||||||
padding-right: 25px;
|
padding-right: 25px;
|
||||||
|
|
||||||
|
/* Do not let the a tag ruin the projector */
|
||||||
|
a {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: inherit;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-right {
|
.column-right {
|
||||||
padding-top: 20px;
|
padding-top: 20px;
|
||||||
min-width: calc(30% - 25px);
|
width: calc(40% - 30px);
|
||||||
float: right;
|
float: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.control-group {
|
.control-group {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
color: rgba(0, 0, 0, 0.54);
|
||||||
|
|
||||||
|
.button-size {
|
||||||
|
width: 40px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
margin-top: 5px;
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h5 {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.queue {
|
||||||
|
margin-top: 15px;
|
||||||
|
|
||||||
|
.drop-list {
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.list-entry {
|
||||||
|
display: table;
|
||||||
|
min-height: 50px;
|
||||||
|
width: 100%;
|
||||||
|
border-bottom: solid 1px #ccc;
|
||||||
|
color: rgba(0, 0, 0, 0.87);
|
||||||
|
|
||||||
|
.drag-handle {
|
||||||
|
display: table-cell;
|
||||||
|
padding: 0 10px;
|
||||||
|
line-height: 0px;
|
||||||
|
vertical-align: middle;
|
||||||
|
width: 25px;
|
||||||
|
color: slategrey;
|
||||||
|
cursor: move;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
display: table-cell;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-right {
|
||||||
|
display: table-cell;
|
||||||
|
padding-right: 10px;
|
||||||
|
vertical-align: middle;
|
||||||
|
width: auto;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
div {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-entry:last-child {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,16 @@ import { TranslateService } from '@ngx-translate/core';
|
|||||||
import { ProjectorRepositoryService, ScrollScaleDirection } from '../../services/projector-repository.service';
|
import { ProjectorRepositoryService, ScrollScaleDirection } from '../../services/projector-repository.service';
|
||||||
import { ViewProjector } from '../../models/view-projector';
|
import { ViewProjector } from '../../models/view-projector';
|
||||||
import { BaseViewComponent } from 'app/site/base/base-view';
|
import { BaseViewComponent } from 'app/site/base/base-view';
|
||||||
|
import { ProjectorService } from 'app/core/services/projector.service';
|
||||||
|
import { moveItemInArray, CdkDragDrop } from '@angular/cdk/drag-drop';
|
||||||
|
import { ProjectorElement } from 'app/shared/models/core/projector';
|
||||||
|
import { SlideManager } from 'app/slides/services/slide-manager.service';
|
||||||
|
import { CountdownRepositoryService } from 'app/site/common/services/countdown-repository.service';
|
||||||
|
import { ProjectorMessageRepositoryService } from 'app/site/common/services/projectormessage-repository.service';
|
||||||
|
import { ViewProjectorMessage } from 'app/site/common/models/view-projectormessage';
|
||||||
|
import { ViewCountdown } from 'app/site/common/models/view-countdown';
|
||||||
|
import { Projectable } from 'app/site/base/projectable';
|
||||||
|
import { CurrentListOfSpeakersSlideService } from '../../services/current-list-of-of-speakers-slide.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The projector detail view.
|
* The projector detail view.
|
||||||
@ -25,6 +35,10 @@ export class ProjectorDetailComponent extends BaseViewComponent implements OnIni
|
|||||||
|
|
||||||
public scrollScaleDirection = ScrollScaleDirection;
|
public scrollScaleDirection = ScrollScaleDirection;
|
||||||
|
|
||||||
|
public countdowns: ViewCountdown[] = [];
|
||||||
|
|
||||||
|
public messages: ViewProjectorMessage[] = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param titleService
|
* @param titleService
|
||||||
* @param translate
|
* @param translate
|
||||||
@ -37,9 +51,17 @@ export class ProjectorDetailComponent extends BaseViewComponent implements OnIni
|
|||||||
translate: TranslateService,
|
translate: TranslateService,
|
||||||
matSnackBar: MatSnackBar,
|
matSnackBar: MatSnackBar,
|
||||||
private repo: ProjectorRepositoryService,
|
private repo: ProjectorRepositoryService,
|
||||||
private route: ActivatedRoute
|
private route: ActivatedRoute,
|
||||||
|
private projectorService: ProjectorService,
|
||||||
|
private slideManager: SlideManager,
|
||||||
|
private countdownRepo: CountdownRepositoryService,
|
||||||
|
private messageRepo: ProjectorMessageRepositoryService,
|
||||||
|
private currentListOfSpeakersSlideService: CurrentListOfSpeakersSlideService
|
||||||
) {
|
) {
|
||||||
super(titleService, translate, matSnackBar);
|
super(titleService, translate, matSnackBar);
|
||||||
|
|
||||||
|
this.countdownRepo.getViewModelListObservable().subscribe(countdowns => (this.countdowns = countdowns));
|
||||||
|
this.messageRepo.getViewModelListObservable().subscribe(messages => (this.messages = messages));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -68,4 +90,65 @@ export class ProjectorDetailComponent extends BaseViewComponent implements OnIni
|
|||||||
public scale(direction: ScrollScaleDirection): void {
|
public scale(direction: ScrollScaleDirection): void {
|
||||||
this.repo.scale(this.projector, direction).then(null, this.raiseError);
|
this.repo.scale(this.projector, direction).then(null, this.raiseError);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public projectNextSlide(): void {
|
||||||
|
this.projectorService.projectNextSlide(this.projector.projector).then(null, this.raiseError);
|
||||||
|
}
|
||||||
|
|
||||||
|
public projectPreviousSlide(): void {
|
||||||
|
this.projectorService.projectPreviousSlide(this.projector.projector).then(null, this.raiseError);
|
||||||
|
}
|
||||||
|
|
||||||
|
public onSortingChange(event: CdkDragDrop<ProjectorElement>): void {
|
||||||
|
moveItemInArray(this.projector.elements_preview, event.previousIndex, event.currentIndex);
|
||||||
|
this.projectorService.savePreview(this.projector.projector).then(null, this.raiseError);
|
||||||
|
}
|
||||||
|
|
||||||
|
public removePreviewElement(elementIndex: number): void {
|
||||||
|
this.projector.elements_preview.splice(elementIndex, 1);
|
||||||
|
this.projectorService.savePreview(this.projector.projector).then(null, this.raiseError);
|
||||||
|
}
|
||||||
|
|
||||||
|
public projectNow(elementIndex: number): void {
|
||||||
|
this.projectorService.projectPreviewSlide(this.projector.projector, elementIndex).then(null, this.raiseError);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getElementDescription(element: ProjectorElement): string {
|
||||||
|
if (!this.slideManager.canSlideBeMappedToModel(element.name)) {
|
||||||
|
return this.slideManager.getSlideVerboseName(element.name);
|
||||||
|
} else {
|
||||||
|
const idElement = this.slideManager.getIdentifialbeProjectorElement(element);
|
||||||
|
const model = this.projectorService.getModelFromProjectorElement(idElement);
|
||||||
|
return model.getTitle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public isProjected(obj: Projectable): boolean {
|
||||||
|
return this.projectorService.isProjectedOn(obj, this.projector.projector);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async project(obj: Projectable): Promise<void> {
|
||||||
|
try {
|
||||||
|
if (this.isProjected(obj)) {
|
||||||
|
await this.projectorService.removeFrom(this.projector.projector, obj);
|
||||||
|
} else {
|
||||||
|
await this.projectorService.projectOn(this.projector.projector, obj);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
this.raiseError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public unprojectCurrent(element: ProjectorElement): void {
|
||||||
|
const idElement = this.slideManager.getIdentifialbeProjectorElement(element);
|
||||||
|
this.projectorService.removeFrom(this.projector.projector, idElement).then(null, this.raiseError);
|
||||||
|
}
|
||||||
|
|
||||||
|
public isClosProjected(stable: boolean): boolean {
|
||||||
|
return this.currentListOfSpeakersSlideService.isProjectedOn(this.projector, stable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public toggleClos(stable: boolean): void {
|
||||||
|
this.currentListOfSpeakersSlideService.toggleOn(this.projector, stable);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,13 +39,12 @@
|
|||||||
</mat-card>
|
</mat-card>
|
||||||
|
|
||||||
<div id="card-wrapper">
|
<div id="card-wrapper">
|
||||||
<mat-card class="projector-card" *ngFor="let projector of projectors">
|
<div class="projector-card" *ngFor="let projector of projectors">
|
||||||
<mat-card-title>
|
<os-meta-text-block showActionRow="false">
|
||||||
<a [routerLink]="['/projector-site/detail', projector.id]">
|
<ng-container class="meta-text-block-title">
|
||||||
{{ projector.name }}
|
{{ projector.name | translate }}
|
||||||
</a>
|
</ng-container>
|
||||||
|
<ng-container class="meta-text-block-action-row">
|
||||||
<div class="card-actions">
|
|
||||||
<button mat-icon-button *ngIf="editId !== projector.id" (click)=onEditButton(projector)>
|
<button mat-icon-button *ngIf="editId !== projector.id" (click)=onEditButton(projector)>
|
||||||
<mat-icon>edit</mat-icon>
|
<mat-icon>edit</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
@ -55,47 +54,53 @@
|
|||||||
<button mat-icon-button *ngIf="editId === projector.id" (click)=onSaveButton(projector)>
|
<button mat-icon-button *ngIf="editId === projector.id" (click)=onSaveButton(projector)>
|
||||||
<mat-icon>save</mat-icon>
|
<mat-icon>save</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
<button mat-icon-button mat-button (click)=onDeleteButton(projector)>
|
<button mat-icon-button color="warn" (click)=onDeleteButton(projector)>
|
||||||
<mat-icon>delete</mat-icon>
|
<mat-icon>delete</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</ng-container>
|
||||||
</mat-card-title>
|
<ng-container class="meta-text-block-content">
|
||||||
<mat-card-content>
|
<a class="no-markup" [routerLink]="['/projectors/detail', projector.id]">
|
||||||
<p>TODO: projector</p>
|
<div class="projector">
|
||||||
<ng-container *ngIf="editId === projector.id">
|
<os-projector [projector]="projector"></os-projector>
|
||||||
<form [formGroup]="updateForm" (keydown)="keyDownFunction($event, projector)">
|
</div>
|
||||||
<p>
|
</a>
|
||||||
|
<ng-container *ngIf="editId === projector.id">
|
||||||
|
<form [formGroup]="updateForm" (keydown)="keyDownFunction($event, projector)">
|
||||||
<!-- Name field -->
|
<!-- Name field -->
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<input formControlName="name" matInput placeholder="{{'Name' | translate}}" required>
|
<input formControlName="name" matInput placeholder="{{'Name' | translate}}" required>
|
||||||
<mat-hint *ngIf="!createForm.controls.name.valid">
|
<mat-hint *ngIf="!updateForm.controls.name.valid">
|
||||||
<span translate>Required</span>
|
<span translate>Required</span>
|
||||||
</mat-hint>
|
</mat-hint>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</p><p>
|
<h3 translate>Resolution and size</h3>
|
||||||
<!-- Aspect ratio field -->
|
<!-- Aspect ratio field -->
|
||||||
<mat-radio-group formControlName="aspectRatio" [name]="projector.id">
|
<mat-radio-group formControlName="aspectRatio" [name]="projector.id">
|
||||||
<mat-radio-button *ngFor="let ratio of aspectRatiosKeys" [value]="ratio">
|
<mat-radio-button *ngFor="let ratio of aspectRatiosKeys" [value]="ratio">
|
||||||
{{ ratio }}
|
{{ ratio }}
|
||||||
</mat-radio-button>
|
</mat-radio-button>
|
||||||
</mat-radio-group>
|
</mat-radio-group>
|
||||||
</p><p>
|
|
||||||
<mat-slider [thumbLabel]="true" formControlName="width" min="800" max="3840" step="10"></mat-slider>
|
<mat-slider [thumbLabel]="true" formControlName="width" min="800" max="3840" step="10"></mat-slider>
|
||||||
{{ updateForm.value.width }}
|
{{ updateForm.value.width }}
|
||||||
</p>
|
<div>
|
||||||
</form>
|
<mat-checkbox formControlName="clock">
|
||||||
|
<span translate>Show clock</span>
|
||||||
|
</mat-checkbox>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</mat-card-content>
|
</os-meta-text-block>
|
||||||
</mat-card>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<mat-menu #ellipsisMenu="matMenu">
|
<mat-menu #ellipsisMenu="matMenu">
|
||||||
|
<button mat-menu-item routerLink="/countdowns">
|
||||||
|
<mat-icon>alarm</mat-icon>
|
||||||
|
<span translate>Countdowns</span>
|
||||||
|
</button>
|
||||||
<button mat-menu-item>
|
<button mat-menu-item>
|
||||||
<mat-icon>note</mat-icon>
|
<mat-icon>note</mat-icon>
|
||||||
<span translate>Projector messages</span>
|
<span translate>Projector messages</span>
|
||||||
</button>
|
</button>
|
||||||
<button mat-menu-item>
|
|
||||||
<mat-icon>alarm</mat-icon>
|
|
||||||
<span translate>Countdowns</span>
|
|
||||||
</button>
|
|
||||||
</mat-menu>
|
</mat-menu>
|
||||||
|
@ -1,13 +1,28 @@
|
|||||||
#card-wrapper {
|
#card-wrapper {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
|
margin-left: 10px;
|
||||||
|
|
||||||
.projector-card {
|
.projector-card {
|
||||||
margin: 20px;
|
width: 350px;
|
||||||
width: 300px;
|
margin: 10px;
|
||||||
display: inline-block;
|
float: left;
|
||||||
}
|
|
||||||
|
|
||||||
.card-actions {
|
.projector {
|
||||||
float: right;
|
width: 320px;
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::ng-deep mat-card {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-markup {
|
||||||
|
/* Do not let the a tag ruin the projector */
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: inherit;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
|
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
|
||||||
import { Title } from '@angular/platform-browser';
|
import { Title } from '@angular/platform-browser';
|
||||||
|
import { MatSnackBar } from '@angular/material';
|
||||||
|
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
@ -8,8 +9,8 @@ import { ProjectorRepositoryService } from '../../services/projector-repository.
|
|||||||
import { ViewProjector } from '../../models/view-projector';
|
import { ViewProjector } from '../../models/view-projector';
|
||||||
import { Projector } from 'app/shared/models/core/projector';
|
import { Projector } from 'app/shared/models/core/projector';
|
||||||
import { BaseViewComponent } from 'app/site/base/base-view';
|
import { BaseViewComponent } from 'app/site/base/base-view';
|
||||||
import { MatSnackBar } from '@angular/material';
|
|
||||||
import { PromptService } from 'app/core/services/prompt.service';
|
import { PromptService } from 'app/core/services/prompt.service';
|
||||||
|
import { ClockSlideService } from '../../services/clock-slide.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All supported aspect rations for projectors.
|
* All supported aspect rations for projectors.
|
||||||
@ -76,7 +77,8 @@ export class ProjectorListComponent extends BaseViewComponent implements OnInit
|
|||||||
matSnackBar: MatSnackBar,
|
matSnackBar: MatSnackBar,
|
||||||
private repo: ProjectorRepositoryService,
|
private repo: ProjectorRepositoryService,
|
||||||
private formBuilder: FormBuilder,
|
private formBuilder: FormBuilder,
|
||||||
private promptService: PromptService
|
private promptService: PromptService,
|
||||||
|
private clockSlideService: ClockSlideService
|
||||||
) {
|
) {
|
||||||
super(titleService, translate, matSnackBar);
|
super(titleService, translate, matSnackBar);
|
||||||
|
|
||||||
@ -88,7 +90,8 @@ export class ProjectorListComponent extends BaseViewComponent implements OnInit
|
|||||||
this.updateForm = this.formBuilder.group({
|
this.updateForm = this.formBuilder.group({
|
||||||
name: ['', Validators.required],
|
name: ['', Validators.required],
|
||||||
aspectRatio: ['', Validators.required],
|
aspectRatio: ['', Validators.required],
|
||||||
width: [0, Validators.required]
|
width: [0, Validators.required],
|
||||||
|
clock: [true]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,6 +119,12 @@ export class ProjectorListComponent extends BaseViewComponent implements OnInit
|
|||||||
public create(): void {
|
public create(): void {
|
||||||
if (this.createForm.valid && this.projectorToCreate) {
|
if (this.createForm.valid && this.projectorToCreate) {
|
||||||
this.projectorToCreate.patchValues(this.createForm.value as Projector);
|
this.projectorToCreate.patchValues(this.createForm.value as Projector);
|
||||||
|
// TODO: the server shouldn't want to have this data..
|
||||||
|
this.projectorToCreate.patchValues({
|
||||||
|
elements: [{ name: 'core/clock', stable: true }],
|
||||||
|
elements_preview: [],
|
||||||
|
elements_history: []
|
||||||
|
});
|
||||||
this.repo.create(this.projectorToCreate).then(() => (this.projectorToCreate = null), this.raiseError);
|
this.repo.create(this.projectorToCreate).then(() => (this.projectorToCreate = null), this.raiseError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -178,7 +187,8 @@ export class ProjectorListComponent extends BaseViewComponent implements OnInit
|
|||||||
this.updateForm.patchValue({
|
this.updateForm.patchValue({
|
||||||
name: projector.name,
|
name: projector.name,
|
||||||
aspectRatio: this.getAspectRatioKey(projector),
|
aspectRatio: this.getAspectRatioKey(projector),
|
||||||
width: projector.width
|
width: projector.width,
|
||||||
|
clock: this.clockSlideService.isProjectedOn(projector)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,7 +208,7 @@ export class ProjectorListComponent extends BaseViewComponent implements OnInit
|
|||||||
*
|
*
|
||||||
* @param projector The projector to save.
|
* @param projector The projector to save.
|
||||||
*/
|
*/
|
||||||
public onSaveButton(projector: ViewProjector): void {
|
public async onSaveButton(projector: ViewProjector): Promise<void> {
|
||||||
if (projector.id !== this.editId || !this.updateForm.valid) {
|
if (projector.id !== this.editId || !this.updateForm.valid) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -207,8 +217,13 @@ export class ProjectorListComponent extends BaseViewComponent implements OnInit
|
|||||||
width: this.updateForm.value.width,
|
width: this.updateForm.value.width,
|
||||||
height: Math.round(this.updateForm.value.width / aspectRatios[this.updateForm.value.aspectRatio])
|
height: Math.round(this.updateForm.value.width / aspectRatios[this.updateForm.value.aspectRatio])
|
||||||
};
|
};
|
||||||
this.repo.update(updateProjector, projector);
|
try {
|
||||||
this.editId = null;
|
await this.clockSlideService.setProjectedOn(projector, this.updateForm.value.clock);
|
||||||
|
await this.repo.update(updateProjector, projector);
|
||||||
|
this.editId = null;
|
||||||
|
} catch (e) {
|
||||||
|
this.raiseError(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
}
|
}
|
||||||
.content {
|
.content {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
@ -216,8 +216,10 @@ export class ProjectorComponent extends BaseComponent implements OnDestroy {
|
|||||||
.getProjectorObservable(to)
|
.getProjectorObservable(to)
|
||||||
.subscribe(data => (this.slides = data || []));
|
.subscribe(data => (this.slides = data || []));
|
||||||
this.projectorSubscription = this.projectorRepository.getViewModelObservable(to).subscribe(projector => {
|
this.projectorSubscription = this.projectorRepository.getViewModelObservable(to).subscribe(projector => {
|
||||||
this.scroll = projector.scroll;
|
if (projector) {
|
||||||
this.scale = projector.scale;
|
this.scroll = projector.scroll || 0;
|
||||||
|
this.scale = projector.scale || 0;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} else if (!to && from > 0) {
|
} else if (!to && from > 0) {
|
||||||
// no new projector
|
// no new projector
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
::ng-deep #slide {
|
::ng-deep #slide {
|
||||||
z-index: 5;
|
z-index: 5;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ import { TranslateService } from '@ngx-translate/core';
|
|||||||
|
|
||||||
import { BaseComponent } from 'app/base.component';
|
import { BaseComponent } from 'app/base.component';
|
||||||
import { SlideData } from '../../services/projector-data.service';
|
import { SlideData } from '../../services/projector-data.service';
|
||||||
import { DynamicSlideLoader } from 'app/slides/services/dynamic-slide-loader.service';
|
import { SlideManager } from 'app/slides/services/slide-manager.service';
|
||||||
import { BaseSlideComponent } from 'app/slides/base-slide-component';
|
import { BaseSlideComponent } from 'app/slides/base-slide-component';
|
||||||
import { SlideOptions } from 'app/slides/slide-manifest';
|
import { SlideOptions } from 'app/slides/slide-manifest';
|
||||||
import { ConfigService } from 'app/core/services/config.service';
|
import { ConfigService } from 'app/core/services/config.service';
|
||||||
@ -106,7 +106,7 @@ export class SlideContainerComponent extends BaseComponent {
|
|||||||
public constructor(
|
public constructor(
|
||||||
titleService: Title,
|
titleService: Title,
|
||||||
translate: TranslateService,
|
translate: TranslateService,
|
||||||
private dynamicSlideLoader: DynamicSlideLoader,
|
private slideManager: SlideManager,
|
||||||
private configService: ConfigService
|
private configService: ConfigService
|
||||||
) {
|
) {
|
||||||
super(titleService, translate);
|
super(titleService, translate);
|
||||||
@ -133,13 +133,13 @@ export class SlideContainerComponent extends BaseComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads the slides via the dynamicSlideLoader. Creates the slide components and provide the slide data to it.
|
* Loads the slides via the SlideManager. Creates the slide components and provide the slide data to it.
|
||||||
*
|
*
|
||||||
* @param slideName The slide to load.
|
* @param slideName The slide to load.
|
||||||
*/
|
*/
|
||||||
private slideChanged(slideName: string): void {
|
private slideChanged(slideName: string): void {
|
||||||
this.slideOptions = this.dynamicSlideLoader.getSlideOptions(slideName);
|
this.slideOptions = this.slideManager.getSlideOptions(slideName);
|
||||||
this.dynamicSlideLoader.getSlideFactory(slideName).then(slideFactory => {
|
this.slideManager.getSlideFactory(slideName).then(slideFactory => {
|
||||||
this.slide.clear();
|
this.slide.clear();
|
||||||
this.slideRef = this.slide.createComponent(slideFactory);
|
this.slideRef = this.slide.createComponent(slideFactory);
|
||||||
this.setDataForComponent();
|
this.setDataForComponent();
|
||||||
|
@ -20,6 +20,18 @@ export class ViewProjector extends BaseViewModel {
|
|||||||
return this.projector ? this.projector.elements : null;
|
return this.projector ? this.projector.elements : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get non_stable_elements(): ProjectorElements {
|
||||||
|
return this.projector ? this.projector.elements.filter(element => !element.stable) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get elements_preview(): ProjectorElements {
|
||||||
|
return this.projector ? this.projector.elements_preview : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get elements_history(): ProjectorElements[] {
|
||||||
|
return this.projector ? this.projector.elements_history : null;
|
||||||
|
}
|
||||||
|
|
||||||
public get height(): number {
|
public get height(): number {
|
||||||
return this.projector ? this.projector.height : null;
|
return this.projector ? this.projector.height : null;
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ import { ProjectorDetailComponent } from './components/projector-detail/projecto
|
|||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
path: 'list',
|
path: '',
|
||||||
component: ProjectorListComponent
|
component: ProjectorListComponent
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -12,7 +12,7 @@ export const ProjectorAppConfig: AppConfig = {
|
|||||||
],
|
],
|
||||||
mainMenuEntries: [
|
mainMenuEntries: [
|
||||||
{
|
{
|
||||||
route: '/projector-site/list',
|
route: '/projectors',
|
||||||
displayName: 'Projector',
|
displayName: 'Projector',
|
||||||
icon: 'videocam',
|
icon: 'videocam',
|
||||||
weight: 700,
|
weight: 700,
|
||||||
|
@ -8,8 +8,12 @@ import { ProjectorListComponent } from './components/projector-list/projector-li
|
|||||||
import { ProjectorDetailComponent } from './components/projector-detail/projector-detail.component';
|
import { ProjectorDetailComponent } from './components/projector-detail/projector-detail.component';
|
||||||
import { SlideContainerComponent } from './components/slide-container/slide-container.component';
|
import { SlideContainerComponent } from './components/slide-container/slide-container.component';
|
||||||
import { FullscreenProjectorComponent } from './components/fullscreen-projector/fullscreen-projector.component';
|
import { FullscreenProjectorComponent } from './components/fullscreen-projector/fullscreen-projector.component';
|
||||||
|
import { ClockSlideService } from './services/clock-slide.service';
|
||||||
|
import { ProjectorRepositoryService } from './services/projector-repository.service';
|
||||||
|
import { ProjectorDataService } from './services/projector-data.service';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
providers: [ClockSlideService, ProjectorDataService, ProjectorRepositoryService],
|
||||||
imports: [CommonModule, ProjectorRoutingModule, SharedModule],
|
imports: [CommonModule, ProjectorRoutingModule, SharedModule],
|
||||||
declarations: [
|
declarations: [
|
||||||
ProjectorComponent,
|
ProjectorComponent,
|
||||||
|
@ -0,0 +1,35 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
|
import { ProjectorService } from 'app/core/services/projector.service';
|
||||||
|
import { ViewProjector } from '../models/view-projector';
|
||||||
|
import { IdentifiableProjectorElement } from 'app/shared/models/core/projector';
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class ClockSlideService {
|
||||||
|
public constructor(private projectorService: ProjectorService) {}
|
||||||
|
|
||||||
|
private getClockProjectorElement(): IdentifiableProjectorElement {
|
||||||
|
return {
|
||||||
|
name: 'core/clock',
|
||||||
|
stable: true,
|
||||||
|
getIdentifiers: () => ['name']
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public isProjectedOn(projector: ViewProjector): boolean {
|
||||||
|
return this.projectorService.isProjectedOn(this.getClockProjectorElement(), projector.projector);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async setProjectedOn(projector: ViewProjector, show: boolean): Promise<void> {
|
||||||
|
const isClockProjected = this.isProjectedOn(projector);
|
||||||
|
if (show && !isClockProjected) {
|
||||||
|
await this.projectorService.projectOn(projector.projector, this.getClockProjectorElement());
|
||||||
|
} else if (!show && isClockProjected) {
|
||||||
|
await this.projectorService.removeFrom(projector.projector, this.getClockProjectorElement());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { ProjectorService } from 'app/core/services/projector.service';
|
||||||
|
import { ViewProjector } from '../models/view-projector';
|
||||||
|
import { IdentifiableProjectorElement } from 'app/shared/models/core/projector';
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class CurrentListOfSpeakersSlideService {
|
||||||
|
public constructor(private projectorService: ProjectorService) {}
|
||||||
|
|
||||||
|
private getCurrentListOfSpeakersProjectorElement(overlay: boolean): IdentifiableProjectorElement {
|
||||||
|
return {
|
||||||
|
name: overlay ? 'agenda/current-list-of-speakers-overlay' : 'agenda/current-list-of-speakers',
|
||||||
|
stable: overlay,
|
||||||
|
getIdentifiers: () => ['name', 'stable']
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public isProjectedOn(projector: ViewProjector, overlay: boolean): boolean {
|
||||||
|
return this.projectorService.isProjectedOn(
|
||||||
|
this.getCurrentListOfSpeakersProjectorElement(overlay),
|
||||||
|
projector.projector
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async toggleOn(projector: ViewProjector, overlay: boolean): Promise<void> {
|
||||||
|
const isClosProjected = this.isProjectedOn(projector, overlay);
|
||||||
|
if (isClosProjected) {
|
||||||
|
await this.projectorService.removeFrom(
|
||||||
|
projector.projector,
|
||||||
|
this.getCurrentListOfSpeakersProjectorElement(overlay)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await this.projectorService.projectOn(
|
||||||
|
projector.projector,
|
||||||
|
this.getCurrentListOfSpeakersProjectorElement(overlay)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -12,7 +12,7 @@ export interface SlideData<T = object> {
|
|||||||
export type ProjectorData = SlideData[];
|
export type ProjectorData = SlideData[];
|
||||||
|
|
||||||
interface AllProjectorData {
|
interface AllProjectorData {
|
||||||
[id: number]: ProjectorData;
|
[id: number]: ProjectorData | { error: string };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -45,8 +45,12 @@ export class ProjectorDataService {
|
|||||||
this.websocketService.getOberservable('projector').subscribe((update: AllProjectorData) => {
|
this.websocketService.getOberservable('projector').subscribe((update: AllProjectorData) => {
|
||||||
Object.keys(update).forEach(_id => {
|
Object.keys(update).forEach(_id => {
|
||||||
const id = parseInt(_id, 10);
|
const id = parseInt(_id, 10);
|
||||||
if (this.currentProjectorData[id]) {
|
if ((<{ error: string }>update[id]).error !== undefined) {
|
||||||
this.currentProjectorData[id].next(update[id]);
|
console.log('TODO: Why does the server sends errors on autpupdates?');
|
||||||
|
} else {
|
||||||
|
if (this.currentProjectorData[id]) {
|
||||||
|
this.currentProjectorData[id].next(update[id] as ProjectorData);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -82,7 +82,7 @@ export class ProjectorRepositoryService extends BaseRepository<ViewProjector, Pr
|
|||||||
* @param direction The direction.
|
* @param direction The direction.
|
||||||
*/
|
*/
|
||||||
public async scroll(projector: ViewProjector, direction: ScrollScaleDirection): Promise<void> {
|
public async scroll(projector: ViewProjector, direction: ScrollScaleDirection): Promise<void> {
|
||||||
this.controlView(projector, direction, 'scroll');
|
await this.controlView(projector, direction, 'scroll');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -92,7 +92,7 @@ export class ProjectorRepositoryService extends BaseRepository<ViewProjector, Pr
|
|||||||
* @param direction The direction.
|
* @param direction The direction.
|
||||||
*/
|
*/
|
||||||
public async scale(projector: ViewProjector, direction: ScrollScaleDirection): Promise<void> {
|
public async scale(projector: ViewProjector, direction: ScrollScaleDirection): Promise<void> {
|
||||||
this.controlView(projector, direction, 'scale');
|
await this.controlView(projector, direction, 'scale');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -107,7 +107,7 @@ export class ProjectorRepositoryService extends BaseRepository<ViewProjector, Pr
|
|||||||
direction: ScrollScaleDirection,
|
direction: ScrollScaleDirection,
|
||||||
action: 'scale' | 'scroll'
|
action: 'scale' | 'scroll'
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
this.http.post(`/rest/core/projector/${projector.id}/control_view`, {
|
await this.http.post<void>(`/rest/core/projector/${projector.id}/control_view/`, {
|
||||||
action: action,
|
action: action,
|
||||||
direction: direction
|
direction: direction
|
||||||
});
|
});
|
||||||
|
@ -52,7 +52,7 @@ const routes: Routes = [
|
|||||||
loadChildren: './history/history.module#HistoryModule'
|
loadChildren: './history/history.module#HistoryModule'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'projector-site',
|
path: 'projectors',
|
||||||
loadChildren: './projector/projector.module#ProjectorModule'
|
loadChildren: './projector/projector.module#ProjectorModule'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -2,6 +2,7 @@ import { User } from '../../../shared/models/users/user';
|
|||||||
import { Group } from '../../../shared/models/users/group';
|
import { Group } from '../../../shared/models/users/group';
|
||||||
import { BaseModel } from '../../../shared/models/base/base-model';
|
import { BaseModel } from '../../../shared/models/base/base-model';
|
||||||
import { BaseProjectableModel } from 'app/site/base/base-projectable-model';
|
import { BaseProjectableModel } from 'app/site/base/base-projectable-model';
|
||||||
|
import { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable';
|
||||||
|
|
||||||
export class ViewUser extends BaseProjectableModel {
|
export class ViewUser extends BaseProjectableModel {
|
||||||
private _user: User;
|
private _user: User;
|
||||||
@ -109,16 +110,17 @@ export class ViewUser extends BaseProjectableModel {
|
|||||||
this._groups = groups;
|
this._groups = groups;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getProjectionDefaultName(): string {
|
public getSlide(): ProjectorElementBuildDeskriptor {
|
||||||
return 'users';
|
return {
|
||||||
}
|
getBasicProjectorElement: () => ({
|
||||||
|
name: User.COLLECTIONSTRING,
|
||||||
public getNameForSlide(): string {
|
id: this.id,
|
||||||
return User.COLLECTIONSTRING;
|
getIdentifiers: () => ['name', 'id']
|
||||||
}
|
}),
|
||||||
|
slideOptions: [],
|
||||||
public isStableSlide(): boolean {
|
projectionDefaultName: 'users',
|
||||||
return true;
|
getTitle: () => this.getTitle()
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
export interface AgendaCurrentListOfSpeakersSlideData {
|
||||||
|
error: string;
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
<div id="overlay">
|
||||||
|
Current list of speakers overlay
|
||||||
|
</div>
|
@ -0,0 +1,9 @@
|
|||||||
|
#overlay {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: green;
|
||||||
|
height: 30px;
|
||||||
|
margin: 10px;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { AgendaCurrentListOfSpeakersOverlaySlideComponent } from './agenda-current-list-of-speakers-overlay-slide.component';
|
||||||
|
import { E2EImportsModule } from '../../../../e2e-imports.module';
|
||||||
|
|
||||||
|
describe('AgendaCurrentListOfSpeakersOverlaySlideComponent', () => {
|
||||||
|
let component: AgendaCurrentListOfSpeakersOverlaySlideComponent;
|
||||||
|
let fixture: ComponentFixture<AgendaCurrentListOfSpeakersOverlaySlideComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [E2EImportsModule],
|
||||||
|
declarations: [AgendaCurrentListOfSpeakersOverlaySlideComponent]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(AgendaCurrentListOfSpeakersOverlaySlideComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,23 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
|
||||||
|
import { BaseSlideComponent } from 'app/slides/base-slide-component';
|
||||||
|
import { HttpService } from 'app/core/services/http.service';
|
||||||
|
import { AgendaCurrentListOfSpeakersSlideData } from '../base/agenda-current-list-of-speakers-slide-data';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'os-agenda-current-list-of-speakers-overlay-slide',
|
||||||
|
templateUrl: './agenda-current-list-of-speakers-overlay-slide.component.html',
|
||||||
|
styleUrls: ['./agenda-current-list-of-speakers-overlay-slide.component.scss']
|
||||||
|
})
|
||||||
|
export class AgendaCurrentListOfSpeakersOverlaySlideComponent
|
||||||
|
extends BaseSlideComponent<AgendaCurrentListOfSpeakersSlideData>
|
||||||
|
implements OnInit {
|
||||||
|
public constructor(private http: HttpService) {
|
||||||
|
super();
|
||||||
|
console.log(this.http);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ngOnInit(): void {
|
||||||
|
console.log('Hello from current list of speakers overlay');
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
import { AgendaCurrentListOfSpeakersOverlaySlideModule } from './agenda-current-list-of-speakers-overlay-slide.module';
|
||||||
|
|
||||||
|
describe('AgendaCurrentListOfSpeakersOverlaySlideModule', () => {
|
||||||
|
let agendaCurrentListOfSpeakersOverlaySlideModule: AgendaCurrentListOfSpeakersOverlaySlideModule;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
agendaCurrentListOfSpeakersOverlaySlideModule = new AgendaCurrentListOfSpeakersOverlaySlideModule();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create an instance', () => {
|
||||||
|
expect(agendaCurrentListOfSpeakersOverlaySlideModule).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,7 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
|
||||||
|
import { makeSlideModule } from 'app/slides/base-slide-module';
|
||||||
|
import { AgendaCurrentListOfSpeakersOverlaySlideComponent } from './agenda-current-list-of-speakers-overlay-slide.component';
|
||||||
|
|
||||||
|
@NgModule(makeSlideModule(AgendaCurrentListOfSpeakersOverlaySlideComponent))
|
||||||
|
export class AgendaCurrentListOfSpeakersOverlaySlideModule {}
|
@ -0,0 +1,3 @@
|
|||||||
|
<div>
|
||||||
|
Current list of speakers slide
|
||||||
|
</div>
|
@ -0,0 +1,26 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { AgendaCurrentListOfSpeakersSlideComponent } from './agenda-current-list-of-speakers-slide.component';
|
||||||
|
import { E2EImportsModule } from '../../../../e2e-imports.module';
|
||||||
|
|
||||||
|
describe('CoreCountdownSlideComponent', () => {
|
||||||
|
let component: AgendaCurrentListOfSpeakersSlideComponent;
|
||||||
|
let fixture: ComponentFixture<AgendaCurrentListOfSpeakersSlideComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [E2EImportsModule],
|
||||||
|
declarations: [AgendaCurrentListOfSpeakersSlideComponent]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(AgendaCurrentListOfSpeakersSlideComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,37 @@
|
|||||||
|
import { Component, OnInit, Input } from '@angular/core';
|
||||||
|
|
||||||
|
import { BaseSlideComponent } from 'app/slides/base-slide-component';
|
||||||
|
import { HttpService } from 'app/core/services/http.service';
|
||||||
|
import { AgendaCurrentListOfSpeakersSlideData } from '../base/agenda-current-list-of-speakers-slide-data';
|
||||||
|
import { SlideData } from 'app/site/projector/services/projector-data.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'os-agenda-current-list-of-speakers-slide',
|
||||||
|
templateUrl: './agenda-current-list-of-speakers-slide.component.html',
|
||||||
|
styleUrls: ['./agenda-current-list-of-speakers-slide.component.scss']
|
||||||
|
})
|
||||||
|
export class AgendaCurrentListOfSpeakersSlideComponent extends BaseSlideComponent<AgendaCurrentListOfSpeakersSlideData>
|
||||||
|
implements OnInit {
|
||||||
|
private _data: SlideData<AgendaCurrentListOfSpeakersSlideData>;
|
||||||
|
|
||||||
|
public isOverlay: boolean;
|
||||||
|
|
||||||
|
public get data(): SlideData<AgendaCurrentListOfSpeakersSlideData> {
|
||||||
|
return this._data;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
public set data(value: SlideData<AgendaCurrentListOfSpeakersSlideData>) {
|
||||||
|
this.isOverlay = !value || value.element.stable;
|
||||||
|
this._data = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public constructor(private http: HttpService) {
|
||||||
|
super();
|
||||||
|
console.log(this.http);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ngOnInit(): void {
|
||||||
|
console.log('Hello from current list of speakers slide');
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
import { AgendaCurrentListOfSpeakersSlideModule } from './agenda-current-list-of-speakers-slide.module';
|
||||||
|
|
||||||
|
describe('AgendaCurrentListOfSpeakersModule', () => {
|
||||||
|
let agendaCurrentListOfSpeakersSlideModule: AgendaCurrentListOfSpeakersSlideModule;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
agendaCurrentListOfSpeakersSlideModule = new AgendaCurrentListOfSpeakersSlideModule();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create an instance', () => {
|
||||||
|
expect(agendaCurrentListOfSpeakersSlideModule).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,7 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
|
||||||
|
import { makeSlideModule } from 'app/slides/base-slide-module';
|
||||||
|
import { AgendaCurrentListOfSpeakersSlideComponent } from './agenda-current-list-of-speakers-slide.component';
|
||||||
|
|
||||||
|
@NgModule(makeSlideModule(AgendaCurrentListOfSpeakersSlideComponent))
|
||||||
|
export class AgendaCurrentListOfSpeakersSlideModule {}
|
@ -2,20 +2,72 @@ import { SlideManifest } from './slide-manifest';
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Here, all slides has to be registered.
|
* Here, all slides has to be registered.
|
||||||
|
*
|
||||||
|
* Note: When adding or removing slides here, you may need to restart yarn/npm, because
|
||||||
|
* the angular CLI scans this file just at it's start time and creates the modules then. There
|
||||||
|
* is no such thing as "dynamic update" in this case..
|
||||||
*/
|
*/
|
||||||
export const allSlides: SlideManifest[] = [
|
export const allSlides: SlideManifest[] = [
|
||||||
{
|
{
|
||||||
slideName: 'motions/motion',
|
slide: 'motions/motion',
|
||||||
path: 'motions/motion',
|
path: 'motions/motion',
|
||||||
loadChildren: './slides/motions/motion/motions-motion-slide.module#MotionsMotionSlideModule',
|
loadChildren: './slides/motions/motion/motions-motion-slide.module#MotionsMotionSlideModule',
|
||||||
scaleable: true,
|
scaleable: true,
|
||||||
scrollable: true
|
scrollable: true,
|
||||||
|
verboseName: 'Motion',
|
||||||
|
elementIdentifiers: ['name', 'id'],
|
||||||
|
canBeMappedToModel: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
slideName: 'users/user',
|
slide: 'users/user',
|
||||||
path: 'users/user',
|
path: 'users/user',
|
||||||
loadChildren: './slides/users/user/users-user-slide.module#UsersUserSlideModule',
|
loadChildren: './slides/users/user/users-user-slide.module#UsersUserSlideModule',
|
||||||
|
scaleable: true,
|
||||||
|
scrollable: true,
|
||||||
|
verboseName: 'Participant',
|
||||||
|
elementIdentifiers: ['name', 'id'],
|
||||||
|
canBeMappedToModel: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slide: 'core/clock',
|
||||||
|
path: 'core/clock',
|
||||||
|
loadChildren: './slides/core/clock/core-clock-slide.module#CoreClockSlideModule',
|
||||||
scaleable: false,
|
scaleable: false,
|
||||||
scrollable: false
|
scrollable: false,
|
||||||
|
verboseName: 'Clock',
|
||||||
|
elementIdentifiers: ['name'],
|
||||||
|
canBeMappedToModel: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slide: 'core/countdown',
|
||||||
|
path: 'core/countdown',
|
||||||
|
loadChildren: './slides/core/countdown/core-countdown-slide.module#CoreCountdownSlideModule',
|
||||||
|
scaleable: false,
|
||||||
|
scrollable: false,
|
||||||
|
verboseName: 'Countdown',
|
||||||
|
elementIdentifiers: ['name', 'id'],
|
||||||
|
canBeMappedToModel: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slide: 'agenda/current-list-of-speakers',
|
||||||
|
path: 'agenda/current-list-of-speakers',
|
||||||
|
loadChildren:
|
||||||
|
'./slides/agenda/current-list-of-speakers/agenda-current-list-of-speakers-slide.module#AgendaCurrentListOfSpeakersSlideModule',
|
||||||
|
scaleable: true,
|
||||||
|
scrollable: true,
|
||||||
|
verboseName: 'Current list of speakers',
|
||||||
|
elementIdentifiers: ['name', 'id'],
|
||||||
|
canBeMappedToModel: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slide: 'agenda/current-list-of-speakers-overlay',
|
||||||
|
path: 'agenda/current-list-of-speakers-overlay',
|
||||||
|
loadChildren:
|
||||||
|
'./slides/agenda/current-list-of-speakers-overlay/agenda-current-list-of-speakers-overlay-slide.module#AgendaCurrentListOfSpeakersOverlaySlideModule',
|
||||||
|
scaleable: false,
|
||||||
|
scrollable: false,
|
||||||
|
verboseName: 'Current list of speakers overlay',
|
||||||
|
elementIdentifiers: ['name', 'id'],
|
||||||
|
canBeMappedToModel: false
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
<div id="clock" *ngIf="time">
|
||||||
|
<mat-icon>schedule</mat-icon>
|
||||||
|
<span>{{ time }}</span>
|
||||||
|
</div>
|
@ -0,0 +1,15 @@
|
|||||||
|
#clock {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
color: white;
|
||||||
|
height: 30px;
|
||||||
|
margin: 12px;
|
||||||
|
z-index: 2;
|
||||||
|
|
||||||
|
span {
|
||||||
|
padding-left: 5px;
|
||||||
|
font-size: 16px;
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { CoreClockSlideComponent } from './core-clock-slide.component';
|
||||||
|
import { E2EImportsModule } from '../../../../e2e-imports.module';
|
||||||
|
|
||||||
|
describe('CoreClockSlideComponent', () => {
|
||||||
|
let component: CoreClockSlideComponent;
|
||||||
|
let fixture: ComponentFixture<CoreClockSlideComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [E2EImportsModule],
|
||||||
|
declarations: [CoreClockSlideComponent]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(CoreClockSlideComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,44 @@
|
|||||||
|
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||||
|
import { BaseSlideComponent } from 'app/slides/base-slide-component';
|
||||||
|
import { ServertimeService } from 'app/core/services/servertime.service';
|
||||||
|
import { Subscription } from 'rxjs';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'os-core-clock-slide',
|
||||||
|
templateUrl: './core-clock-slide.component.html',
|
||||||
|
styleUrls: ['./core-clock-slide.component.scss']
|
||||||
|
})
|
||||||
|
export class CoreClockSlideComponent extends BaseSlideComponent<{}> implements OnInit, OnDestroy {
|
||||||
|
public time: string;
|
||||||
|
|
||||||
|
private servertimeSubscription: Subscription | null = null;
|
||||||
|
|
||||||
|
public constructor(private servertimeService: ServertimeService) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ngOnInit(): void {
|
||||||
|
// Update clock, when the server offset changes.
|
||||||
|
this.servertimeSubscription = this.servertimeService
|
||||||
|
.getServerOffsetObservable()
|
||||||
|
.subscribe(() => this.updateClock());
|
||||||
|
|
||||||
|
// Update clock every 10 seconds.
|
||||||
|
setInterval(() => this.updateClock(), 10 * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateClock(): void {
|
||||||
|
const time = new Date(this.servertimeService.getServertime());
|
||||||
|
const hours = '0' + time.getHours();
|
||||||
|
const minutes = '0' + time.getMinutes();
|
||||||
|
|
||||||
|
// Will display time in 10:30:23 format
|
||||||
|
this.time = hours.slice(-2) + ':' + minutes.slice(-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ngOnDestroy(): void {
|
||||||
|
if (this.servertimeSubscription) {
|
||||||
|
this.servertimeSubscription.unsubscribe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
import { CoreClockSlideModule } from './core-clock-slide.module';
|
||||||
|
|
||||||
|
describe('CoreClockSlideModule', () => {
|
||||||
|
let coreClockSlideModule: CoreClockSlideModule;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
coreClockSlideModule = new CoreClockSlideModule();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create an instance', () => {
|
||||||
|
expect(coreClockSlideModule).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,7 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
|
||||||
|
import { makeSlideModule } from 'app/slides/base-slide-module';
|
||||||
|
import { CoreClockSlideComponent } from './core-clock-slide.component';
|
||||||
|
|
||||||
|
@NgModule(makeSlideModule(CoreClockSlideComponent))
|
||||||
|
export class CoreClockSlideModule {}
|
@ -0,0 +1,3 @@
|
|||||||
|
export interface CoreCountdownSlideData {
|
||||||
|
error: string;
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
<div id="outer">
|
||||||
|
COUNTDOWN
|
||||||
|
</div>
|
@ -0,0 +1,10 @@
|
|||||||
|
#outer {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
background-color: green;
|
||||||
|
height: 30px;
|
||||||
|
margin: 10px;
|
||||||
|
margin-top: 100px;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { CoreCountdownSlideComponent } from './core-countdown-slide.component';
|
||||||
|
import { E2EImportsModule } from '../../../../e2e-imports.module';
|
||||||
|
|
||||||
|
describe('CoreCountdownSlideComponent', () => {
|
||||||
|
let component: CoreCountdownSlideComponent;
|
||||||
|
let fixture: ComponentFixture<CoreCountdownSlideComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [E2EImportsModule],
|
||||||
|
declarations: [CoreCountdownSlideComponent]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(CoreCountdownSlideComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,20 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { BaseSlideComponent } from 'app/slides/base-slide-component';
|
||||||
|
import { CoreCountdownSlideData } from './core-countdown-slide-data';
|
||||||
|
import { HttpService } from 'app/core/services/http.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'os-core-countdown-slide',
|
||||||
|
templateUrl: './core-countdown-slide.component.html',
|
||||||
|
styleUrls: ['./core-countdown-slide.component.scss']
|
||||||
|
})
|
||||||
|
export class CoreCountdownSlideComponent extends BaseSlideComponent<CoreCountdownSlideData> implements OnInit {
|
||||||
|
public constructor(private http: HttpService) {
|
||||||
|
super();
|
||||||
|
console.log(this.http);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ngOnInit(): void {
|
||||||
|
console.log('Hello from countdown slide');
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
import { CoreCountdownSlideModule } from './core-countdown-slide.module';
|
||||||
|
|
||||||
|
describe('CoreCountdownSlideModule', () => {
|
||||||
|
let coreCountdownSlideModule: CoreCountdownSlideModule;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
coreCountdownSlideModule = new CoreCountdownSlideModule();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create an instance', () => {
|
||||||
|
expect(CoreCountdownSlideModule).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,7 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
|
||||||
|
import { makeSlideModule } from 'app/slides/base-slide-module';
|
||||||
|
import { CoreCountdownSlideComponent } from './core-countdown-slide.component';
|
||||||
|
|
||||||
|
@NgModule(makeSlideModule(CoreCountdownSlideComponent))
|
||||||
|
export class CoreCountdownSlideModule {}
|
@ -1,3 +1,3 @@
|
|||||||
export interface MotionsMotionSlideData {
|
export interface MotionsMotionSlideData {
|
||||||
test: string;
|
title: string;
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
<div>
|
<div *ngIf="data">
|
||||||
Motion Slide
|
Motion Slide
|
||||||
<h1>TEST</h1>
|
<h1>{{ data.data.title }}</h1>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { BaseSlideComponent } from 'app/slides/base-slide-component';
|
import { BaseSlideComponent } from 'app/slides/base-slide-component';
|
||||||
import { MotionsMotionSlideData } from './motions-motion-slide-model';
|
import { MotionsMotionSlideData } from './motions-motion-slide-data';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'os-motions-motion-slide',
|
selector: 'os-motions-motion-slide',
|
||||||
|
@ -4,34 +4,36 @@ import { SlideManifest, SlideOptions } from '../slide-manifest';
|
|||||||
import { SLIDE } from '../slide-token';
|
import { SLIDE } from '../slide-token';
|
||||||
import { SLIDE_MANIFESTS } from '../slide-manifest';
|
import { SLIDE_MANIFESTS } from '../slide-manifest';
|
||||||
import { BaseSlideComponent } from '../base-slide-component';
|
import { BaseSlideComponent } from '../base-slide-component';
|
||||||
|
import { ProjectorElement, IdentifiableProjectorElement } from 'app/shared/models/core/projector';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cares about loading slides dynamically.
|
* Cares about loading slides dynamically.
|
||||||
*/
|
*/
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DynamicSlideLoader {
|
export class SlideManager {
|
||||||
|
private loadedSlides: { [name: string]: SlideManifest } = {};
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
@Inject(SLIDE_MANIFESTS) private manifests: SlideManifest[],
|
@Inject(SLIDE_MANIFESTS) private manifests: SlideManifest[],
|
||||||
private loader: NgModuleFactoryLoader,
|
private loader: NgModuleFactoryLoader,
|
||||||
private injector: Injector
|
private injector: Injector
|
||||||
) {}
|
) {
|
||||||
|
this.manifests.forEach(slideManifest => {
|
||||||
|
this.loadedSlides[slideManifest.slide] = slideManifest;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Searches the manifest for the given slide name.
|
* Searches the manifest for the given slide name.
|
||||||
*
|
*
|
||||||
* TODO: Improve by loading all manifests in an object with the
|
|
||||||
* slide name as keys in the constructor. It's just a lookup here, then.
|
|
||||||
*
|
|
||||||
* @param slideName The slide to look up.
|
* @param slideName The slide to look up.
|
||||||
* @returns the slide's manifest.
|
* @returns the slide's manifest.
|
||||||
*/
|
*/
|
||||||
private getManifest(slideName: string): SlideManifest {
|
private getManifest(slideName: string): SlideManifest {
|
||||||
const manifest = this.manifests.find(m => m.slideName === slideName);
|
if (!this.loadedSlides[slideName]) {
|
||||||
|
|
||||||
if (!manifest) {
|
|
||||||
throw new Error(`Could not find slide for "${slideName}"`);
|
throw new Error(`Could not find slide for "${slideName}"`);
|
||||||
}
|
}
|
||||||
return manifest;
|
return this.loadedSlides[slideName];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -44,6 +46,29 @@ export class DynamicSlideLoader {
|
|||||||
return this.getManifest(slideName);
|
return this.getManifest(slideName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
public getIdentifialbeProjectorElement(element: ProjectorElement): IdentifiableProjectorElement {
|
||||||
|
const identifiableElement: IdentifiableProjectorElement = element as IdentifiableProjectorElement;
|
||||||
|
const identifiers = this.getManifest(element.name).elementIdentifiers.map(x => x); // map to copy.
|
||||||
|
identifiableElement.getIdentifiers = () => identifiers;
|
||||||
|
return identifiableElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get slide verbose name for a given slide.
|
||||||
|
*
|
||||||
|
* @param slideName The slide
|
||||||
|
* @returns the verbose slide name for the requested slide.
|
||||||
|
*/
|
||||||
|
public getSlideVerboseName(slideName: string): string {
|
||||||
|
return this.getManifest(slideName).verboseName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public canSlideBeMappedToModel(slideName: string): boolean {
|
||||||
|
return this.getManifest(slideName).canBeMappedToModel;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asynchronically load the slide's component factory, which is used to create
|
* Asynchronically load the slide's component factory, which is used to create
|
||||||
* the slide component.
|
* the slide component.
|
@ -1,4 +1,5 @@
|
|||||||
import { InjectionToken } from '@angular/core';
|
import { InjectionToken } from '@angular/core';
|
||||||
|
import { IdentifiableProjectorElement } from 'app/shared/models/core/projector';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Slides can have these options.
|
* Slides can have these options.
|
||||||
@ -20,9 +21,12 @@ export interface SlideOptions {
|
|||||||
* path in sync.
|
* path in sync.
|
||||||
*/
|
*/
|
||||||
export interface SlideManifest extends SlideOptions {
|
export interface SlideManifest extends SlideOptions {
|
||||||
slideName: string;
|
slide: string;
|
||||||
path: string;
|
path: string;
|
||||||
loadChildren: string;
|
loadChildren: string;
|
||||||
|
verboseName: string;
|
||||||
|
elementIdentifiers: (keyof IdentifiableProjectorElement)[];
|
||||||
|
canBeMappedToModel: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SLIDE_MANIFESTS = new InjectionToken<SlideManifest[]>('SLIDE_MANIFEST');
|
export const SLIDE_MANIFESTS = new InjectionToken<SlideManifest[]>('SLIDE_MANIFEST');
|
||||||
|
@ -2,7 +2,7 @@ import { NgModule, NgModuleFactoryLoader, SystemJsNgModuleLoader } from '@angula
|
|||||||
import { ModuleWithProviders } from '@angular/compiler/src/core';
|
import { ModuleWithProviders } from '@angular/compiler/src/core';
|
||||||
import { ROUTES } from '@angular/router';
|
import { ROUTES } from '@angular/router';
|
||||||
|
|
||||||
import { DynamicSlideLoader } from './services/dynamic-slide-loader.service';
|
import { SlideManager } from './services/slide-manager.service';
|
||||||
import { SLIDE_MANIFESTS } from './slide-manifest';
|
import { SLIDE_MANIFESTS } from './slide-manifest';
|
||||||
import { allSlides } from './all-slides';
|
import { allSlides } from './all-slides';
|
||||||
|
|
||||||
@ -15,7 +15,7 @@ import { allSlides } from './all-slides';
|
|||||||
* found and put in sepearte chunks.
|
* found and put in sepearte chunks.
|
||||||
*/
|
*/
|
||||||
@NgModule({
|
@NgModule({
|
||||||
providers: [DynamicSlideLoader, { provide: NgModuleFactoryLoader, useClass: SystemJsNgModuleLoader }]
|
providers: [SlideManager, { provide: NgModuleFactoryLoader, useClass: SystemJsNgModuleLoader }]
|
||||||
})
|
})
|
||||||
export class SlidesModule {
|
export class SlidesModule {
|
||||||
public static forRoot(): ModuleWithProviders {
|
public static forRoot(): ModuleWithProviders {
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user