List of speakers slide
This commit is contained in:
parent
0975de7432
commit
7ef36e93c6
@ -18,6 +18,7 @@ import { SlideManager } from 'app/slides/services/slide-manager.service';
|
||||
import { BaseModel } from 'app/shared/models/base/base-model';
|
||||
import { ViewModelStoreService } from './view-model-store.service';
|
||||
import { BaseProjectableViewModel } from 'app/site/base/base-projectable-view-model';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
/**
|
||||
* This service cares about Projectables being projected and manage all projection-related
|
||||
@ -39,7 +40,8 @@ export class ProjectorService {
|
||||
private DS: DataStoreService,
|
||||
private http: HttpService,
|
||||
private slideManager: SlideManager,
|
||||
private viewModelStore: ViewModelStoreService
|
||||
private viewModelStore: ViewModelStoreService,
|
||||
private translate: TranslateService
|
||||
) {}
|
||||
|
||||
/**
|
||||
@ -273,6 +275,24 @@ export class ProjectorService {
|
||||
return viewModel;
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
public getSlideTitle(element: ProjectorElement): string {
|
||||
if (this.slideManager.canSlideBeMappedToModel(element.name)) {
|
||||
const idElement = this.slideManager.getIdentifialbeProjectorElement(element);
|
||||
const viewModel = this.getViewModelFromProjectorElement(idElement);
|
||||
if (viewModel) {
|
||||
return viewModel.getProjectorTitle();
|
||||
}
|
||||
}
|
||||
const configuration = this.slideManager.getSlideConfiguration(element.name);
|
||||
if (configuration.getSlideTitle) {
|
||||
return configuration.getSlideTitle(element, this.translate, this.viewModelStore);
|
||||
}
|
||||
|
||||
return this.translate.instant(this.slideManager.getSlideVerboseName(element.name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Projects the next slide in the queue. Moves all currently projected
|
||||
* non-stable slides to the history.
|
||||
|
@ -20,6 +20,7 @@ import { ViewModelStoreService } from 'app/core/core-services/view-model-store.s
|
||||
import { BaseViewModel } from 'app/site/base/base-view-model';
|
||||
import { ViewUser } from 'app/site/users/models/view-user';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { BaseAgendaContentObjectRepository } from '../base-agenda-content-object-repository';
|
||||
|
||||
/**
|
||||
* Repository service for users
|
||||
@ -77,6 +78,28 @@ export class ItemRepositoryService extends BaseRepository<ViewItem, Item> {
|
||||
viewItem.getVerboseName = (plural: boolean = false) => {
|
||||
return this.translate.instant(plural ? 'Items' : 'Item');
|
||||
};
|
||||
viewItem.getTitle = () => {
|
||||
if (viewItem.contentObject) {
|
||||
return viewItem.contentObject.getAgendaTitle();
|
||||
} else {
|
||||
const repo = this.collectionStringMapperService.getRepository(
|
||||
viewItem.item.content_object.collection
|
||||
) as BaseAgendaContentObjectRepository<any, any>;
|
||||
return repo.getAgendaTitle(viewItem);
|
||||
}
|
||||
};
|
||||
viewItem.getListTitle = () => {
|
||||
const numberPrefix = viewItem.itemNumber ? `${viewItem.itemNumber} · ` : '';
|
||||
|
||||
if (viewItem.contentObject) {
|
||||
return numberPrefix + viewItem.contentObject.getAgendaTitleWithType();
|
||||
} else {
|
||||
const repo = this.collectionStringMapperService.getRepository(
|
||||
viewItem.item.content_object.collection
|
||||
) as BaseAgendaContentObjectRepository<any, any>;
|
||||
return numberPrefix + repo.getAgendaTitleWithType(viewItem);
|
||||
}
|
||||
};
|
||||
return viewItem;
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { Topic } from 'app/shared/models/topics/topic';
|
||||
import { BaseRepository } from 'app/core/repositories/base-repository';
|
||||
import { Mediafile } from 'app/shared/models/mediafiles/mediafile';
|
||||
import { Item } from 'app/shared/models/agenda/item';
|
||||
import { DataStoreService } from 'app/core/core-services/data-store.service';
|
||||
@ -13,7 +14,7 @@ import { CreateTopic } from 'app/site/agenda/models/create-topic';
|
||||
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
|
||||
import { ViewMediafile } from 'app/site/mediafiles/models/view-mediafile';
|
||||
import { ViewItem } from 'app/site/agenda/models/view-item';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { BaseAgendaContentObjectRepository } from '../base-agenda-content-object-repository';
|
||||
|
||||
/**
|
||||
* Repository for topics
|
||||
@ -21,7 +22,7 @@ import { TranslateService } from '@ngx-translate/core';
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class TopicRepositoryService extends BaseRepository<ViewTopic, Topic> {
|
||||
export class TopicRepositoryService extends BaseAgendaContentObjectRepository<ViewTopic, Topic> {
|
||||
/**
|
||||
* Constructor calls the parent constructor
|
||||
*
|
||||
@ -39,6 +40,19 @@ export class TopicRepositoryService extends BaseRepository<ViewTopic, Topic> {
|
||||
super(DS, mapperService, viewModelStoreService, Topic, [Mediafile, Item]);
|
||||
}
|
||||
|
||||
public getAgendaTitle = (topic: Partial<Topic> | Partial<ViewTopic>) => {
|
||||
return topic.title;
|
||||
};
|
||||
|
||||
public getAgendaTitleWithType = (topic: Partial<Topic> | Partial<ViewTopic>) => {
|
||||
// Do not append ' (Topic)' to the title.
|
||||
return topic.title;
|
||||
};
|
||||
|
||||
public getVerboseName = (plural: boolean = false) => {
|
||||
return this.translate.instant(plural ? 'Topics' : 'Topic');
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a new viewModel out of the given model
|
||||
*
|
||||
@ -49,9 +63,9 @@ export class TopicRepositoryService extends BaseRepository<ViewTopic, Topic> {
|
||||
const attachments = this.viewModelStoreService.getMany(ViewMediafile, topic.attachments_id);
|
||||
const item = this.viewModelStoreService.get(ViewItem, topic.agenda_item_id);
|
||||
const viewTopic = new ViewTopic(topic, attachments, item);
|
||||
viewTopic.getVerboseName = (plural: boolean = false) => {
|
||||
return this.translate.instant(plural ? 'Topics' : 'Topic');
|
||||
};
|
||||
viewTopic.getVerboseName = this.getVerboseName;
|
||||
viewTopic.getAgendaTitle = () => this.getAgendaTitle(viewTopic);
|
||||
viewTopic.getAgendaTitleWithType = () => this.getAgendaTitle(viewTopic);
|
||||
return viewTopic;
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,12 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { ViewAssignment } from 'app/site/assignments/models/view-assignment';
|
||||
import { Assignment } from 'app/shared/models/assignments/assignment';
|
||||
import { User } from 'app/shared/models/users/user';
|
||||
import { Tag } from 'app/shared/models/core/tag';
|
||||
import { Item } from 'app/shared/models/agenda/item';
|
||||
import { BaseRepository } from '../base-repository';
|
||||
import { DataStoreService } from '../../core-services/data-store.service';
|
||||
import { Identifiable } from 'app/shared/models/base/identifiable';
|
||||
import { CollectionStringMapperService } from '../../core-services/collectionStringMapper.service';
|
||||
@ -12,7 +14,7 @@ import { ViewModelStoreService } from 'app/core/core-services/view-model-store.s
|
||||
import { ViewItem } from 'app/site/agenda/models/view-item';
|
||||
import { ViewUser } from 'app/site/users/models/view-user';
|
||||
import { ViewTag } from 'app/site/tags/models/view-tag';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { BaseAgendaContentObjectRepository } from '../base-agenda-content-object-repository';
|
||||
|
||||
/**
|
||||
* Repository Service for Assignments.
|
||||
@ -22,7 +24,7 @@ import { TranslateService } from '@ngx-translate/core';
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class AssignmentRepositoryService extends BaseRepository<ViewAssignment, Assignment> {
|
||||
export class AssignmentRepositoryService extends BaseAgendaContentObjectRepository<ViewAssignment, Assignment> {
|
||||
/**
|
||||
* Constructor for the Assignment Repository.
|
||||
*
|
||||
@ -38,15 +40,27 @@ export class AssignmentRepositoryService extends BaseRepository<ViewAssignment,
|
||||
super(DS, mapperService, viewModelStoreService, Assignment, [User, Item, Tag]);
|
||||
}
|
||||
|
||||
public getAgendaTitle = (assignment: Partial<Assignment> | Partial<ViewAssignment>) => {
|
||||
return assignment.title;
|
||||
};
|
||||
|
||||
public getAgendaTitleWithType = (assignment: Partial<Assignment> | Partial<ViewAssignment>) => {
|
||||
return assignment.title + ' (' + this.getVerboseName() + ')';
|
||||
};
|
||||
|
||||
public getVerboseName = (plural: boolean = false) => {
|
||||
return this.translate.instant(plural ? 'Elections' : 'Election');
|
||||
};
|
||||
|
||||
public createViewModel(assignment: Assignment): ViewAssignment {
|
||||
const relatedUser = this.viewModelStoreService.getMany(ViewUser, assignment.candidates_id);
|
||||
const agendaItem = this.viewModelStoreService.get(ViewItem, assignment.agenda_item_id);
|
||||
const tags = this.viewModelStoreService.getMany(ViewTag, assignment.tags_id);
|
||||
|
||||
const viewAssignment = new ViewAssignment(assignment, relatedUser, agendaItem, tags);
|
||||
viewAssignment.getVerboseName = (plural: boolean = false) => {
|
||||
return this.translate.instant(plural ? 'Elections' : 'Election');
|
||||
};
|
||||
viewAssignment.getVerboseName = this.getVerboseName;
|
||||
viewAssignment.getAgendaTitle = () => this.getAgendaTitle(viewAssignment);
|
||||
viewAssignment.getAgendaTitleWithType = () => this.getAgendaTitleWithType(viewAssignment);
|
||||
return viewAssignment;
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,37 @@
|
||||
import { BaseViewModel } from '../../site/base/base-view-model';
|
||||
import { BaseModel, ModelConstructor } from '../../shared/models/base/base-model';
|
||||
import { CollectionStringMapperService } from '../core-services/collectionStringMapper.service';
|
||||
import { DataStoreService } from '../core-services/data-store.service';
|
||||
import { ViewModelStoreService } from '../core-services/view-model-store.service';
|
||||
import { BaseRepository } from './base-repository';
|
||||
|
||||
export function isBaseAgendaContentObjectRepository(obj: any): obj is BaseAgendaContentObjectRepository<any, any> {
|
||||
const repo = obj as BaseAgendaContentObjectRepository<any, any>;
|
||||
return (
|
||||
!!obj &&
|
||||
repo.getVerboseName !== undefined &&
|
||||
repo.getAgendaTitle !== undefined &&
|
||||
repo.getAgendaTitleWithType !== undefined
|
||||
);
|
||||
}
|
||||
|
||||
export abstract class BaseAgendaContentObjectRepository<
|
||||
V extends BaseViewModel,
|
||||
M extends BaseModel
|
||||
> extends BaseRepository<V, M> {
|
||||
public abstract getAgendaTitle: (model: Partial<M> | Partial<V>) => string;
|
||||
public abstract getAgendaTitleWithType: (model: Partial<M> | Partial<V>) => string;
|
||||
public abstract getVerboseName: (plural?: boolean) => string;
|
||||
|
||||
/**
|
||||
*/
|
||||
public constructor(
|
||||
DS: DataStoreService,
|
||||
collectionStringMapperService: CollectionStringMapperService,
|
||||
viewModelStoreService: ViewModelStoreService,
|
||||
baseModelCtor: ModelConstructor<M>,
|
||||
depsModelCtors?: ModelConstructor<BaseModel>[]
|
||||
) {
|
||||
super(DS, collectionStringMapperService, viewModelStoreService, baseModelCtor, depsModelCtors);
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ import { Injectable } from '@angular/core';
|
||||
|
||||
import { Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { DataSendService } from 'app/core/core-services/data-send.service';
|
||||
import { User } from 'app/shared/models/users/user';
|
||||
@ -14,7 +15,6 @@ import { ViewMotionChangeRecommendation } from 'app/site/motions/models/view-cha
|
||||
import { Identifiable } from 'app/shared/models/base/identifiable';
|
||||
import { CollectionStringMapperService } from 'app/core/core-services/collectionStringMapper.service';
|
||||
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
/**
|
||||
* Repository Services for change recommendations
|
||||
|
@ -2,8 +2,8 @@ import { Injectable } from '@angular/core';
|
||||
|
||||
import { Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { BaseRepository } from 'app/core/repositories/base-repository';
|
||||
import { CollectionStringMapperService } from 'app/core/core-services/collectionStringMapper.service';
|
||||
import { DataSendService } from 'app/core/core-services/data-send.service';
|
||||
import { DataStoreService } from 'app/core/core-services/data-store.service';
|
||||
@ -17,7 +17,7 @@ import { ViewMotionBlock } from 'app/site/motions/models/view-motion-block';
|
||||
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
|
||||
import { Item } from 'app/shared/models/agenda/item';
|
||||
import { ViewItem } from 'app/site/agenda/models/view-item';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { BaseAgendaContentObjectRepository } from '../base-agenda-content-object-repository';
|
||||
|
||||
/**
|
||||
* Repository service for motion blocks
|
||||
@ -25,7 +25,7 @@ import { TranslateService } from '@ngx-translate/core';
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class MotionBlockRepositoryService extends BaseRepository<ViewMotionBlock, MotionBlock> {
|
||||
export class MotionBlockRepositoryService extends BaseAgendaContentObjectRepository<ViewMotionBlock, MotionBlock> {
|
||||
/**
|
||||
* Constructor for the motion block repository
|
||||
*
|
||||
@ -47,6 +47,18 @@ export class MotionBlockRepositoryService extends BaseRepository<ViewMotionBlock
|
||||
super(DS, mapperService, viewModelStoreService, MotionBlock, [Item]);
|
||||
}
|
||||
|
||||
public getAgendaTitle = (motionBlock: Partial<MotionBlock> | Partial<ViewMotionBlock>) => {
|
||||
return motionBlock.title;
|
||||
};
|
||||
|
||||
public getAgendaTitleWithType = (motionBlock: Partial<MotionBlock> | Partial<ViewMotionBlock>) => {
|
||||
return motionBlock.title + ' (' + this.getVerboseName() + ')';
|
||||
};
|
||||
|
||||
public getVerboseName = (plural: boolean = false) => {
|
||||
return this.translate.instant(plural ? 'Motion blocks' : 'Motion block');
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts a given motion block into a ViewModel
|
||||
*
|
||||
@ -56,9 +68,9 @@ export class MotionBlockRepositoryService extends BaseRepository<ViewMotionBlock
|
||||
protected createViewModel(block: MotionBlock): ViewMotionBlock {
|
||||
const item = this.viewModelStoreService.get(ViewItem, block.agenda_item_id);
|
||||
const viewMotionBlock = new ViewMotionBlock(block, item);
|
||||
viewMotionBlock.getVerboseName = (plural: boolean = false) => {
|
||||
return this.translate.instant(plural ? 'Motion blocks' : 'Motion block');
|
||||
};
|
||||
viewMotionBlock.getVerboseName = this.getVerboseName;
|
||||
viewMotionBlock.getAgendaTitle = () => this.getAgendaTitle(viewMotionBlock);
|
||||
viewMotionBlock.getAgendaTitleWithType = () => this.getAgendaTitleWithType(viewMotionBlock);
|
||||
return viewMotionBlock;
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,6 @@ import { TranslateService } from '@ngx-translate/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { tap, map } from 'rxjs/operators';
|
||||
|
||||
import { BaseRepository } from '../base-repository';
|
||||
import { Category } from 'app/shared/models/motions/category';
|
||||
import { ChangeRecoMode, ViewMotion } from 'app/site/motions/models/view-motion';
|
||||
import { CollectionStringMapperService } from '../../core-services/collectionStringMapper.service';
|
||||
@ -40,6 +39,7 @@ import { ViewItem } from 'app/site/agenda/models/view-item';
|
||||
import { ViewMotionBlock } from 'app/site/motions/models/view-motion-block';
|
||||
import { ViewMediafile } from 'app/site/mediafiles/models/view-mediafile';
|
||||
import { ViewTag } from 'app/site/tags/models/view-tag';
|
||||
import { BaseAgendaContentObjectRepository } from '../base-agenda-content-object-repository';
|
||||
|
||||
/**
|
||||
* Repository Services for motions (and potentially categories)
|
||||
@ -54,7 +54,7 @@ import { ViewTag } from 'app/site/tags/models/view-tag';
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class MotionRepositoryService extends BaseRepository<ViewMotion, Motion> {
|
||||
export class MotionRepositoryService extends BaseAgendaContentObjectRepository<ViewMotion, Motion> {
|
||||
/**
|
||||
* Creates a MotionRepository
|
||||
*
|
||||
@ -92,6 +92,28 @@ export class MotionRepositoryService extends BaseRepository<ViewMotion, Motion>
|
||||
]);
|
||||
}
|
||||
|
||||
public getAgendaTitle = (motion: Partial<Motion> | Partial<ViewMotion>) => {
|
||||
// if the identifier is set, the title will be 'Motion <identifier>'.
|
||||
if (motion.identifier) {
|
||||
return this.translate.instant('Motion') + ' ' + motion.identifier;
|
||||
} else {
|
||||
return motion.title;
|
||||
}
|
||||
};
|
||||
|
||||
public getAgendaTitleWithType = (motion: Partial<Motion> | Partial<ViewMotion>) => {
|
||||
// Append the verbose name only, if not the special format 'Motion <identifier>' is used.
|
||||
if (motion.identifier) {
|
||||
return this.translate.instant('Motion') + ' ' + motion.identifier;
|
||||
} else {
|
||||
return motion.title + ' (' + this.getVerboseName() + ')';
|
||||
}
|
||||
};
|
||||
|
||||
public getVerboseName = (plural: boolean = false) => {
|
||||
return this.translate.instant(plural ? 'Motions' : 'Motion');
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts a motion to a ViewMotion and adds it to the store.
|
||||
*
|
||||
@ -127,26 +149,10 @@ export class MotionRepositoryService extends BaseRepository<ViewMotion, Motion>
|
||||
tags,
|
||||
parent
|
||||
);
|
||||
viewMotion.getVerboseName = (plural: boolean = false) => {
|
||||
return this.translate.instant(plural ? 'Motions' : 'Motion');
|
||||
};
|
||||
viewMotion.getAgendaTitle = () => {
|
||||
// if the identifier is set, the title will be 'Motion <identifier>'.
|
||||
if (viewMotion.identifier) {
|
||||
return this.translate.instant('Motion') + ' ' + viewMotion.identifier;
|
||||
} else {
|
||||
return viewMotion.getTitle();
|
||||
}
|
||||
};
|
||||
viewMotion.getVerboseName = this.getVerboseName;
|
||||
viewMotion.getAgendaTitle = () => this.getAgendaTitle(viewMotion);
|
||||
viewMotion.getProjectorTitle = viewMotion.getAgendaTitle;
|
||||
viewMotion.getAgendaTitleWithType = () => {
|
||||
// Append the verbose name only, if not the special format 'Motion <identifier>' is used.
|
||||
if (viewMotion.identifier) {
|
||||
return this.translate.instant('Motion') + ' ' + viewMotion.identifier;
|
||||
} else {
|
||||
return viewMotion.getTitle() + ' (' + viewMotion.getVerboseName() + ')';
|
||||
}
|
||||
};
|
||||
viewMotion.getAgendaTitleWithType = () => this.getAgendaTitleWithType(viewMotion);
|
||||
return viewMotion;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
<h2 mat-dialog-title>
|
||||
<span translate>Project</span> {{ projectorElementBuildDescriptor.getTitle() }}?
|
||||
<span translate>Project</span> {{ projectorElementBuildDescriptor.getDialogTitle() }}?
|
||||
</h2>
|
||||
<mat-dialog-content>
|
||||
<div class="projectors"
|
||||
|
@ -1,4 +1,16 @@
|
||||
<button type="button" mat-mini-fab (click)="onClick($event)"
|
||||
[ngClass]="isProjected() ? 'projectorbutton-active' : 'projectorbutton-inactive'">
|
||||
<mat-icon>videocam</mat-icon>
|
||||
</button>
|
||||
<ng-container *osPerms="'core.can_manage_projector'">
|
||||
<button type="button" *ngIf="!text && !menuItem" mat-mini-fab (click)="onClick($event)"
|
||||
[ngClass]="isProjected() ? 'projector-active' : 'projector-inactive'">
|
||||
<mat-icon>videocam</mat-icon>
|
||||
</button>
|
||||
<button type="button" *ngIf="menuItem" mat-menu-item (click)="onClick($event)"
|
||||
[ngClass]="isProjected() ? 'projector-active' : 'projector-inactive'">
|
||||
<mat-icon>videocam</mat-icon>
|
||||
<span translate>Project</span>
|
||||
</button>
|
||||
<button type="button" *ngIf="text && !menuItem" mat-button (click)="onClick($event)"
|
||||
[ngClass]="isProjected() ? 'projector-active' : 'projector-inactive'">
|
||||
<mat-icon>videocam</mat-icon>
|
||||
{{ text | translate }}
|
||||
</button>
|
||||
</ng-container>
|
||||
|
@ -1,4 +1,18 @@
|
||||
.projectorbutton-inactive {
|
||||
.projector-inactive {
|
||||
background-color: white !important;
|
||||
color: grey !important;
|
||||
|
||||
mat-icon {
|
||||
color: grey !important;
|
||||
}
|
||||
}
|
||||
|
||||
/** TODO: Take this from the accent color. Make the hovering visible */
|
||||
|
||||
.projector-active {
|
||||
background-color: #03a9f4;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
button.mat-menu-item.projector-active:hover {
|
||||
background-color: #03a9f4;
|
||||
}
|
||||
|
@ -39,6 +39,12 @@ export class ProjectorButtonComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
@Input()
|
||||
public text: string | null;
|
||||
|
||||
@Input()
|
||||
public menuItem = false;
|
||||
|
||||
/**
|
||||
* The constructor
|
||||
*/
|
||||
|
@ -29,8 +29,7 @@ export class Item extends BaseModel<Item> {
|
||||
|
||||
public id: number;
|
||||
public item_number: string;
|
||||
public title: string;
|
||||
public title_with_type: string;
|
||||
public title_information: object;
|
||||
public comment: string;
|
||||
public closed: boolean;
|
||||
public type: number;
|
||||
|
@ -6,7 +6,7 @@
|
||||
<span *ngIf="currentListOfSpeakers">Current list of speakers</span>
|
||||
</h2>
|
||||
</div>
|
||||
<div class="menu-slot" *osPerms="'agenda.can_manage_list_of_speakers'">
|
||||
<div class="menu-slot" *osPerms="['agenda.can_manage_list_of_speakers', 'core.can_manage_projector']">
|
||||
<button type="button" mat-icon-button [matMenuTriggerFor]="speakerMenu"><mat-icon>more_vert</mat-icon></button>
|
||||
</div>
|
||||
</os-head-bar>
|
||||
@ -131,6 +131,8 @@
|
||||
</mat-card>
|
||||
|
||||
<mat-menu #speakerMenu="matMenu">
|
||||
<os-projector-button *ngIf="viewItem" [object]="viewItem.listOfSpeakersSlide" [menuItem]="true"></os-projector-button>
|
||||
|
||||
<button mat-menu-item *ngIf="closedList" (click)="openSpeakerList()">
|
||||
<mat-icon>mic</mat-icon>
|
||||
<span translate>Open list of speakers</span>
|
||||
|
@ -2,6 +2,7 @@ import { BaseViewModel } from '../../base/base-view-model';
|
||||
import { Item, itemVisibilityChoices } from 'app/shared/models/agenda/item';
|
||||
import { Speaker, SpeakerState } from 'app/shared/models/agenda/speaker';
|
||||
import { BaseAgendaViewModel, isAgendaBaseModel } from 'app/site/base/base-agenda-view-model';
|
||||
import { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable';
|
||||
|
||||
export class ViewItem extends BaseViewModel {
|
||||
public static COLLECTIONSTRING = Item.COLLECTIONSTRING;
|
||||
@ -111,6 +112,19 @@ export class ViewItem extends BaseViewModel {
|
||||
* This is set by the repository
|
||||
*/
|
||||
public getVerboseName;
|
||||
public getTitle;
|
||||
public getListTitle;
|
||||
|
||||
public listOfSpeakersSlide: ProjectorElementBuildDeskriptor = {
|
||||
getBasicProjectorElement: options => ({
|
||||
name: 'agenda/list-of-speakers',
|
||||
id: this.id,
|
||||
getIdentifiers: () => ['name', 'id']
|
||||
}),
|
||||
slideOptions: [],
|
||||
projectionDefaultName: 'agenda_list_of_speakers',
|
||||
getDialogTitle: () => this.getTitle()
|
||||
};
|
||||
|
||||
public constructor(item: Item, contentObject: BaseAgendaViewModel) {
|
||||
super(Item.COLLECTIONSTRING);
|
||||
@ -118,30 +132,6 @@ export class ViewItem extends BaseViewModel {
|
||||
this._contentObject = contentObject;
|
||||
}
|
||||
|
||||
public getTitle = () => {
|
||||
if (this.contentObject) {
|
||||
return this.contentObject.getAgendaTitle();
|
||||
} else {
|
||||
return this.item ? this.item.title : null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create the list view title.
|
||||
* If a number was given, 'whitespac-dot-whitespace' will be added to the prefix number
|
||||
*
|
||||
* @returns the agenda list title as string
|
||||
*/
|
||||
public getListTitle = () => {
|
||||
const numberPrefix = this.itemNumber ? `${this.itemNumber} · ` : '';
|
||||
|
||||
if (this.contentObject) {
|
||||
return numberPrefix + this.contentObject.getAgendaTitleWithType();
|
||||
} else {
|
||||
return numberPrefix + this.item.title_with_type;
|
||||
}
|
||||
};
|
||||
|
||||
public updateDependencies(update: BaseViewModel): boolean {
|
||||
if (
|
||||
update &&
|
||||
|
@ -53,6 +53,8 @@ export class ViewTopic extends BaseAgendaViewModel {
|
||||
* This is set by the repository
|
||||
*/
|
||||
public getVerboseName;
|
||||
public getAgendaTitle;
|
||||
public getAgendaTitleWithType;
|
||||
|
||||
public constructor(topic: Topic, attachments?: ViewMediafile[], item?: ViewItem) {
|
||||
super(Topic.COLLECTIONSTRING);
|
||||
@ -69,11 +71,6 @@ export class ViewTopic extends BaseAgendaViewModel {
|
||||
return this.agendaItem;
|
||||
}
|
||||
|
||||
public getAgendaTitleWithType = () => {
|
||||
// Do not append ' (Topic)' to the title.
|
||||
return this.getAgendaTitle();
|
||||
};
|
||||
|
||||
/**
|
||||
* Formats the category for search
|
||||
*
|
||||
@ -104,7 +101,7 @@ export class ViewTopic extends BaseAgendaViewModel {
|
||||
}),
|
||||
slideOptions: [],
|
||||
projectionDefaultName: 'topics',
|
||||
getTitle: () => this.getTitle()
|
||||
getDialogTitle: () => this.getTitle()
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -23,6 +23,10 @@ export class ViewAssignment extends BaseAgendaViewModel {
|
||||
return this._assignment;
|
||||
}
|
||||
|
||||
public get title(): string {
|
||||
return this.assignment.title;
|
||||
}
|
||||
|
||||
public get candidates(): ViewUser[] {
|
||||
return this._relatedUser;
|
||||
}
|
||||
@ -50,6 +54,8 @@ export class ViewAssignment extends BaseAgendaViewModel {
|
||||
* This is set by the repository
|
||||
*/
|
||||
public getVerboseName;
|
||||
public getAgendaTitle;
|
||||
public getAgendaTitleWithType;
|
||||
|
||||
public constructor(assignment: Assignment, relatedUser?: ViewUser[], agendaItem?: ViewItem, tags?: ViewTag[]) {
|
||||
super(Assignment.COLLECTIONSTRING);
|
||||
@ -68,7 +74,7 @@ export class ViewAssignment extends BaseAgendaViewModel {
|
||||
}
|
||||
|
||||
public getTitle = () => {
|
||||
return this.assignment.title;
|
||||
return this.title;
|
||||
};
|
||||
|
||||
public formatForSearch(): SearchRepresentation {
|
||||
@ -88,7 +94,7 @@ export class ViewAssignment extends BaseAgendaViewModel {
|
||||
}),
|
||||
slideOptions: [],
|
||||
projectionDefaultName: 'assignments',
|
||||
getTitle: () => this.getTitle()
|
||||
getDialogTitle: () => this.getTitle()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ export function isProjectorElementBuildDeskriptor(obj: any): obj is ProjectorEle
|
||||
!!deskriptor &&
|
||||
deskriptor.slideOptions !== undefined &&
|
||||
deskriptor.getBasicProjectorElement !== undefined &&
|
||||
deskriptor.getTitle !== undefined
|
||||
deskriptor.getDialogTitle !== undefined
|
||||
);
|
||||
}
|
||||
|
||||
@ -20,7 +20,7 @@ export interface ProjectorElementBuildDeskriptor {
|
||||
/**
|
||||
* The title to show in the projection dialog
|
||||
*/
|
||||
getTitle(): string;
|
||||
getDialogTitle(): string;
|
||||
}
|
||||
|
||||
export function isProjectable(obj: any): obj is Projectable {
|
||||
|
@ -97,7 +97,7 @@ export class ViewMediafile extends BaseProjectableViewModel implements Searchabl
|
||||
}),
|
||||
slideOptions: [],
|
||||
projectionDefaultName: 'mediafiles',
|
||||
getTitle: () => this.getTitle()
|
||||
getDialogTitle: () => this.getTitle()
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -40,6 +40,8 @@ export class ViewMotionBlock extends BaseAgendaViewModel implements Searchable {
|
||||
* This is set by the repository
|
||||
*/
|
||||
public getVerboseName;
|
||||
public getAgendaTitle;
|
||||
public getAgendaTitleWithType;
|
||||
|
||||
public constructor(motionBlock: MotionBlock, agendaItem?: ViewItem) {
|
||||
super(MotionBlock.COLLECTIONSTRING);
|
||||
|
@ -580,7 +580,7 @@ export class ViewMotion extends BaseAgendaViewModel implements Searchable {
|
||||
}
|
||||
],
|
||||
projectionDefaultName: 'motions',
|
||||
getTitle: this.getAgendaTitle
|
||||
getDialogTitle: this.getAgendaTitle
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -56,7 +56,7 @@
|
||||
<div class="queue">
|
||||
<h5 translate>History</h5>
|
||||
<p *ngFor="let elements of projector?.elements_history">
|
||||
{{ getElementDescription(elements[0]) }}
|
||||
{{ getSlideTitle(elements[0]) }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@ -70,7 +70,7 @@
|
||||
<button type="button" mat-icon-button (click)="unprojectCurrent(element)">
|
||||
<mat-icon>videocam</mat-icon>
|
||||
</button>
|
||||
{{ getElementDescription(element) }}
|
||||
{{ getSlideTitle(element) }}
|
||||
</mat-list-item>
|
||||
</mat-list>
|
||||
</div>
|
||||
@ -142,7 +142,7 @@
|
||||
<mat-icon>drag_indicator</mat-icon>
|
||||
</div>
|
||||
<div class="name">
|
||||
{{ i+1 }}. <span>{{ getElementDescription(element) }}</span>
|
||||
{{ i+1 }}. <span>{{ getSlideTitle(element) }}</span>
|
||||
</div>
|
||||
<div class="button-right">
|
||||
<div>
|
||||
|
@ -116,16 +116,8 @@ export class ProjectorDetailComponent extends BaseViewComponent implements OnIni
|
||||
this.projectorService.projectPreviewSlide(this.projector.projector, elementIndex).then(null, this.raiseError);
|
||||
}
|
||||
|
||||
public getElementDescription(element: ProjectorElement): string {
|
||||
if (this.slideManager.canSlideBeMappedToModel(element.name)) {
|
||||
const idElement = this.slideManager.getIdentifialbeProjectorElement(element);
|
||||
const viewModel = this.projectorService.getViewModelFromProjectorElement(idElement);
|
||||
if (viewModel) {
|
||||
return viewModel.getProjectorTitle();
|
||||
}
|
||||
}
|
||||
|
||||
return this.slideManager.getSlideVerboseName(element.name);
|
||||
public getSlideTitle(element: ProjectorElement): string {
|
||||
return this.projectorService.getSlideTitle(element);
|
||||
}
|
||||
|
||||
public isProjected(obj: Projectable): boolean {
|
||||
|
@ -64,7 +64,7 @@ export class ViewCountdown extends BaseProjectableViewModel {
|
||||
}
|
||||
],
|
||||
projectionDefaultName: 'countdowns',
|
||||
getTitle: () => this.getTitle()
|
||||
getDialogTitle: () => this.getTitle()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ export class ViewProjectorMessage extends BaseProjectableViewModel {
|
||||
}),
|
||||
slideOptions: [],
|
||||
projectionDefaultName: 'messages',
|
||||
getTitle: () => this.getTitle()
|
||||
getDialogTitle: () => this.getTitle()
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -195,7 +195,7 @@ export class ViewUser extends BaseProjectableViewModel implements Searchable {
|
||||
}),
|
||||
slideOptions: [],
|
||||
projectionDefaultName: 'users',
|
||||
getTitle: () => this.getTitle()
|
||||
getDialogTitle: () => this.getTitle()
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1,3 +0,0 @@
|
||||
export interface AgendaCurrentListOfSpeakersSlideData {
|
||||
error: string;
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
export interface CurrentListOfSpeakersSlideData {
|
||||
error: string;
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
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();
|
||||
});
|
||||
});
|
@ -1,17 +0,0 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { BaseSlideComponent } from 'app/slides/base-slide-component';
|
||||
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
|
||||
> {
|
||||
public constructor() {
|
||||
super();
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
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();
|
||||
});
|
||||
});
|
@ -1,7 +0,0 @@
|
||||
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,26 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { CurrentListOfSpeakersOverlaySlideComponent } from './current-list-of-speakers-overlay-slide.component';
|
||||
import { E2EImportsModule } from '../../../../e2e-imports.module';
|
||||
|
||||
describe('CurrentListOfSpeakersOverlaySlideComponent', () => {
|
||||
let component: CurrentListOfSpeakersOverlaySlideComponent;
|
||||
let fixture: ComponentFixture<CurrentListOfSpeakersOverlaySlideComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [E2EImportsModule],
|
||||
declarations: [CurrentListOfSpeakersOverlaySlideComponent]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(CurrentListOfSpeakersOverlaySlideComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,15 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { BaseSlideComponent } from 'app/slides/base-slide-component';
|
||||
import { CurrentListOfSpeakersSlideData } from '../base/current-list-of-speakers-slide-data';
|
||||
|
||||
@Component({
|
||||
selector: 'os-current-list-of-speakers-overlay-slide',
|
||||
templateUrl: './current-list-of-speakers-overlay-slide.component.html',
|
||||
styleUrls: ['./current-list-of-speakers-overlay-slide.component.scss']
|
||||
})
|
||||
export class CurrentListOfSpeakersOverlaySlideComponent extends BaseSlideComponent<CurrentListOfSpeakersSlideData> {
|
||||
public constructor() {
|
||||
super();
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
import { CurrentListOfSpeakersOverlaySlideModule } from './current-list-of-speakers-overlay-slide.module';
|
||||
|
||||
describe('CurrentListOfSpeakersOverlaySlideModule', () => {
|
||||
let currentListOfSpeakersOverlaySlideModule: CurrentListOfSpeakersOverlaySlideModule;
|
||||
|
||||
beforeEach(() => {
|
||||
currentListOfSpeakersOverlaySlideModule = new CurrentListOfSpeakersOverlaySlideModule();
|
||||
});
|
||||
|
||||
it('should create an instance', () => {
|
||||
expect(currentListOfSpeakersOverlaySlideModule).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,7 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
import { makeSlideModule } from 'app/slides/base-slide-module';
|
||||
import { CurrentListOfSpeakersOverlaySlideComponent } from './current-list-of-speakers-overlay-slide.component';
|
||||
|
||||
@NgModule(makeSlideModule(CurrentListOfSpeakersOverlaySlideComponent))
|
||||
export class CurrentListOfSpeakersOverlaySlideModule {}
|
@ -1,18 +0,0 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
import { BaseSlideComponent } from 'app/slides/base-slide-component';
|
||||
import { AgendaCurrentListOfSpeakersSlideData } from '../base/agenda-current-list-of-speakers-slide-data';
|
||||
|
||||
@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 {
|
||||
public constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public ngOnInit(): void {}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
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();
|
||||
});
|
||||
});
|
@ -1,7 +0,0 @@
|
||||
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 {}
|
@ -1,21 +1,21 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { AgendaCurrentListOfSpeakersSlideComponent } from './agenda-current-list-of-speakers-slide.component';
|
||||
import { CurrentListOfSpeakersSlideComponent } from './current-list-of-speakers-slide.component';
|
||||
import { E2EImportsModule } from '../../../../e2e-imports.module';
|
||||
|
||||
describe('CoreCountdownSlideComponent', () => {
|
||||
let component: AgendaCurrentListOfSpeakersSlideComponent;
|
||||
let fixture: ComponentFixture<AgendaCurrentListOfSpeakersSlideComponent>;
|
||||
describe('CurrentListOfSpeakersSlideComponent', () => {
|
||||
let component: CurrentListOfSpeakersSlideComponent;
|
||||
let fixture: ComponentFixture<CurrentListOfSpeakersSlideComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [E2EImportsModule],
|
||||
declarations: [AgendaCurrentListOfSpeakersSlideComponent]
|
||||
declarations: [CurrentListOfSpeakersSlideComponent]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AgendaCurrentListOfSpeakersSlideComponent);
|
||||
fixture = TestBed.createComponent(CurrentListOfSpeakersSlideComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
@ -0,0 +1,18 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
import { BaseSlideComponent } from 'app/slides/base-slide-component';
|
||||
import { CurrentListOfSpeakersSlideData } from '../base/current-list-of-speakers-slide-data';
|
||||
|
||||
@Component({
|
||||
selector: 'os-current-list-of-speakers-slide',
|
||||
templateUrl: './current-list-of-speakers-slide.component.html',
|
||||
styleUrls: ['./current-list-of-speakers-slide.component.scss']
|
||||
})
|
||||
export class CurrentListOfSpeakersSlideComponent extends BaseSlideComponent<CurrentListOfSpeakersSlideData>
|
||||
implements OnInit {
|
||||
public constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public ngOnInit(): void {}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
import { CurrentListOfSpeakersSlideModule } from './current-list-of-speakers-slide.module';
|
||||
|
||||
describe('CurrentListOfSpeakersSlideModule', () => {
|
||||
let currentListOfSpeakersSlideModule: CurrentListOfSpeakersSlideModule;
|
||||
|
||||
beforeEach(() => {
|
||||
currentListOfSpeakersSlideModule = new CurrentListOfSpeakersSlideModule();
|
||||
});
|
||||
|
||||
it('should create an instance', () => {
|
||||
expect(currentListOfSpeakersSlideModule).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,7 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
import { makeSlideModule } from 'app/slides/base-slide-module';
|
||||
import { CurrentListOfSpeakersSlideComponent } from './current-list-of-speakers-slide.component';
|
||||
|
||||
@NgModule(makeSlideModule(CurrentListOfSpeakersSlideComponent))
|
||||
export class CurrentListOfSpeakersSlideModule {}
|
@ -0,0 +1,13 @@
|
||||
interface SlideSpeaker {
|
||||
user: string;
|
||||
marked: boolean;
|
||||
}
|
||||
|
||||
export interface ListOfSpeakersSlideData {
|
||||
waiting: SlideSpeaker[];
|
||||
current: SlideSpeaker;
|
||||
finished: SlideSpeaker[];
|
||||
title_information: object;
|
||||
content_object_collection: string;
|
||||
item_number: string;
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
<div *ngIf="data">
|
||||
<h1 translate>List of speakers</h1>
|
||||
<h3>{{ getTitle() }}</h3>
|
||||
|
||||
<div *ngIf="data.data.finished.length">
|
||||
<span translate>Finished</span>:
|
||||
<div *ngFor="let speaker of data.data.finished">
|
||||
{{ speaker.user }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="data.data.current">
|
||||
<span translate>Current</span>: {{ data.data.current.user }}
|
||||
</div>
|
||||
|
||||
<div *ngIf="data.data.finished.length">
|
||||
<span translate>Waiting</span>:
|
||||
<div *ngFor="let speaker of data.data.waiting">
|
||||
{{ speaker.user }} {{ speaker.marked ? 'marked' : '' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,26 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ListOfSpeakersSlideComponent } from './list-of-speakers-slide.component';
|
||||
import { E2EImportsModule } from '../../../../e2e-imports.module';
|
||||
|
||||
describe('ListOfSpeakersSlideComponent', () => {
|
||||
let component: ListOfSpeakersSlideComponent;
|
||||
let fixture: ComponentFixture<ListOfSpeakersSlideComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [E2EImportsModule],
|
||||
declarations: [ListOfSpeakersSlideComponent]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ListOfSpeakersSlideComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,28 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { BaseSlideComponent } from 'app/slides/base-slide-component';
|
||||
import { ListOfSpeakersSlideData } from './list-of-speakers-slide-data';
|
||||
import { CollectionStringMapperService } from 'app/core/core-services/collectionStringMapper.service';
|
||||
import { isBaseAgendaContentObjectRepository } from 'app/core/repositories/base-agenda-content-object-repository';
|
||||
|
||||
@Component({
|
||||
selector: 'os-list-of-speakers-slide',
|
||||
templateUrl: './list-of-speakers-slide.component.html',
|
||||
styleUrls: ['./list-of-speakers-slide.component.scss']
|
||||
})
|
||||
export class ListOfSpeakersSlideComponent extends BaseSlideComponent<ListOfSpeakersSlideData> {
|
||||
public constructor(private collectionStringMapperService: CollectionStringMapperService) {
|
||||
super();
|
||||
}
|
||||
|
||||
public getTitle(): string {
|
||||
const numberPrefix = this.data.data.item_number ? `${this.data.data.item_number} · ` : '';
|
||||
const repo = this.collectionStringMapperService.getRepository(this.data.data.content_object_collection);
|
||||
|
||||
if (isBaseAgendaContentObjectRepository(repo)) {
|
||||
return numberPrefix + repo.getAgendaTitle(this.data.data.title_information);
|
||||
} else {
|
||||
throw new Error('The content object has no agenda based repository!');
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
import { ListOfSpeakersSlideModule } from './list-of-speakers-slide.module';
|
||||
|
||||
describe('ListOfSpeakersSlideModule', () => {
|
||||
let listOfSpeakersSlideModule: ListOfSpeakersSlideModule;
|
||||
|
||||
beforeEach(() => {
|
||||
listOfSpeakersSlideModule = new ListOfSpeakersSlideModule();
|
||||
});
|
||||
|
||||
it('should create an instance', () => {
|
||||
expect(listOfSpeakersSlideModule).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,7 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
import { makeSlideModule } from 'app/slides/base-slide-module';
|
||||
import { ListOfSpeakersSlideComponent } from './list-of-speakers-slide.component';
|
||||
|
||||
@NgModule(makeSlideModule(ListOfSpeakersSlideComponent))
|
||||
export class ListOfSpeakersSlideModule {}
|
4
client/src/app/slides/agenda/topic/topic-slide-data.ts
Normal file
4
client/src/app/slides/agenda/topic/topic-slide-data.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export interface TopicSlideData {
|
||||
title: string;
|
||||
text: string;
|
||||
}
|
@ -1,21 +1,21 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { TopicsTopicSlideComponent } from './topics-topic-slide.component';
|
||||
import { TopicSlideComponent } from './topic-slide.component';
|
||||
import { E2EImportsModule } from 'e2e-imports.module';
|
||||
|
||||
describe('TopicsTopicSlideComponent', () => {
|
||||
let component: TopicsTopicSlideComponent;
|
||||
let fixture: ComponentFixture<TopicsTopicSlideComponent>;
|
||||
describe('TopicSlideComponent', () => {
|
||||
let component: TopicSlideComponent;
|
||||
let fixture: ComponentFixture<TopicSlideComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [E2EImportsModule],
|
||||
declarations: [TopicsTopicSlideComponent]
|
||||
declarations: [TopicSlideComponent]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(TopicsTopicSlideComponent);
|
||||
fixture = TestBed.createComponent(TopicSlideComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
14
client/src/app/slides/agenda/topic/topic-slide.component.ts
Normal file
14
client/src/app/slides/agenda/topic/topic-slide.component.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { BaseSlideComponent } from 'app/slides/base-slide-component';
|
||||
import { TopicSlideData } from './topic-slide-data';
|
||||
|
||||
@Component({
|
||||
selector: 'os-topic-slide',
|
||||
templateUrl: './topic-slide.component.html',
|
||||
styleUrls: ['./topic-slide.component.scss']
|
||||
})
|
||||
export class TopicSlideComponent extends BaseSlideComponent<TopicSlideData> {
|
||||
public constructor() {
|
||||
super();
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
import { TopicSlideModule } from './topic-slide.module';
|
||||
|
||||
describe('TopicSlideModule', () => {
|
||||
let topicsTopicSlideModule: TopicSlideModule;
|
||||
|
||||
beforeEach(() => {
|
||||
topicsTopicSlideModule = new TopicSlideModule();
|
||||
});
|
||||
|
||||
it('should create an instance', () => {
|
||||
expect(topicsTopicSlideModule).toBeTruthy();
|
||||
});
|
||||
});
|
7
client/src/app/slides/agenda/topic/topic-slide.module.ts
Normal file
7
client/src/app/slides/agenda/topic/topic-slide.module.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
import { makeSlideModule } from 'app/slides/base-slide-module';
|
||||
import { TopicSlideComponent } from './topic-slide.component';
|
||||
|
||||
@NgModule(makeSlideModule(TopicSlideComponent))
|
||||
export class TopicSlideModule {}
|
@ -1,4 +0,0 @@
|
||||
export interface TopicsTopicSlideData {
|
||||
title: string;
|
||||
text: string;
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { BaseSlideComponent } from 'app/slides/base-slide-component';
|
||||
import { TopicsTopicSlideData } from './topics-topic-slide-data';
|
||||
|
||||
@Component({
|
||||
selector: 'os-topic-slide',
|
||||
templateUrl: './topics-topic-slide.component.html',
|
||||
styleUrls: ['./topics-topic-slide.component.scss']
|
||||
})
|
||||
export class TopicsTopicSlideComponent extends BaseSlideComponent<TopicsTopicSlideData> {
|
||||
public constructor() {
|
||||
super();
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
import { TopicsTopicSlideModule } from './topics-topic-slide.module';
|
||||
|
||||
describe('TopicsTopicSlideModule', () => {
|
||||
let topicsTopicSlideModule: TopicsTopicSlideModule;
|
||||
|
||||
beforeEach(() => {
|
||||
topicsTopicSlideModule = new TopicsTopicSlideModule();
|
||||
});
|
||||
|
||||
it('should create an instance', () => {
|
||||
expect(topicsTopicSlideModule).toBeTruthy();
|
||||
});
|
||||
});
|
@ -1,7 +0,0 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
import { makeSlideModule } from 'app/slides/base-slide-module';
|
||||
import { TopicsTopicSlideComponent } from './topics-topic-slide.component';
|
||||
|
||||
@NgModule(makeSlideModule(TopicsTopicSlideComponent))
|
||||
export class TopicsTopicSlideModule {}
|
@ -1,4 +1,8 @@
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { SlideDynamicConfiguration, Slide } from './slide-manifest';
|
||||
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
|
||||
import { ProjectorElement } from 'app/shared/models/core/projector';
|
||||
|
||||
export const allSlidesDynamicConfiguration: (SlideDynamicConfiguration & Slide)[] = [
|
||||
{
|
||||
@ -31,6 +35,23 @@ export const allSlidesDynamicConfiguration: (SlideDynamicConfiguration & Slide)[
|
||||
scaleable: false,
|
||||
scrollable: false
|
||||
},
|
||||
{
|
||||
slide: 'agenda/list-of-speakers',
|
||||
scaleable: true,
|
||||
scrollable: true,
|
||||
getSlideTitle: (
|
||||
element: ProjectorElement,
|
||||
translate: TranslateService,
|
||||
viewModelStore: ViewModelStoreService
|
||||
) => {
|
||||
const item = viewModelStore.get('agenda/item', element.id);
|
||||
if (item) {
|
||||
const title = translate.instant('List of speakers for');
|
||||
return title + ' ' + item.getTitle();
|
||||
}
|
||||
return translate.instant('List of speakers');
|
||||
}
|
||||
},
|
||||
{
|
||||
slide: 'agenda/current-list-of-speakers',
|
||||
scaleable: true,
|
||||
|
@ -11,7 +11,7 @@ export const allSlides: SlideManifest[] = [
|
||||
{
|
||||
slide: 'topics/topic',
|
||||
path: 'topics/topic',
|
||||
loadChildren: './slides/agenda/topic/topics-topic-slide.module#TopicsTopicSlideModule',
|
||||
loadChildren: './slides/agenda/topic/topic-slide.module#TopicSlideModule',
|
||||
verboseName: 'Topic',
|
||||
elementIdentifiers: ['name', 'id'],
|
||||
canBeMappedToModel: true
|
||||
@ -60,7 +60,7 @@ export const allSlides: SlideManifest[] = [
|
||||
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',
|
||||
'./slides/agenda/current-list-of-speakers/current-list-of-speakers-slide.module#CurrentListOfSpeakersSlideModule',
|
||||
verboseName: 'Current list of speakers',
|
||||
elementIdentifiers: ['name', 'id'],
|
||||
canBeMappedToModel: false
|
||||
@ -69,11 +69,19 @@ export const allSlides: SlideManifest[] = [
|
||||
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',
|
||||
'./slides/agenda/current-list-of-speakers-overlay/current-list-of-speakers-overlay-slide.module#CurrentListOfSpeakersOverlaySlideModule',
|
||||
verboseName: 'Current list of speakers overlay',
|
||||
elementIdentifiers: ['name', 'id'],
|
||||
canBeMappedToModel: false
|
||||
},
|
||||
{
|
||||
slide: 'agenda/list-of-speakers',
|
||||
path: 'agenda/list-of-speakers',
|
||||
loadChildren: './slides/agenda/list-of-speakers/list-of-speakers-slide.module#ListOfSpeakersSlideModule',
|
||||
verboseName: 'List of speakers',
|
||||
elementIdentifiers: ['name', 'id'],
|
||||
canBeMappedToModel: false
|
||||
},
|
||||
{
|
||||
slide: 'assignments/assignment',
|
||||
path: 'assignments/assignment',
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
|
||||
|
||||
import { BaseSlideComponent } from 'app/slides/base-slide-component';
|
||||
import { MotionsMotionSlideData, MotionsMotionSlideDataAmendment } from './motions-motion-slide-data';
|
||||
import { ChangeRecoMode, LineNumberingMode } from '../../../site/motions/models/view-motion';
|
||||
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
|
||||
import { DiffLinesInParagraph, DiffService, LineRange } from '../../../core/ui-services/diff.service';
|
||||
import { LinenumberingService } from '../../../core/ui-services/linenumbering.service';
|
||||
import { ViewUnifiedChange } from '../../../shared/models/motions/view-unified-change';
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { InjectionToken } from '@angular/core';
|
||||
import { IdentifiableProjectorElement, ProjectorElement } from 'app/shared/models/core/projector';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
|
||||
|
||||
type BooleanOrFunction = boolean | ((element: ProjectorElement) => boolean);
|
||||
|
||||
@ -20,6 +22,12 @@ export interface SlideDynamicConfiguration {
|
||||
* Should this slide be scaleable?
|
||||
*/
|
||||
scaleable: BooleanOrFunction;
|
||||
|
||||
getSlideTitle?: (
|
||||
element: ProjectorElement,
|
||||
translate: TranslateService,
|
||||
viewModelStore: ViewModelStoreService
|
||||
) => string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -57,7 +57,13 @@ class ItemAccessPermissions(BaseAccessPermissions):
|
||||
# so that list of speakers is provided regardless. Hidden items can only be seen by managers.
|
||||
# We know that full_data has at least one entry which can be used to parse the keys.
|
||||
blocked_keys_internal_hidden_case = set(full_data[0].keys()) - set(
|
||||
("id", "title", "speakers", "speaker_list_closed", "content_object")
|
||||
(
|
||||
"id",
|
||||
"title_information",
|
||||
"speakers",
|
||||
"speaker_list_closed",
|
||||
"content_object",
|
||||
)
|
||||
)
|
||||
|
||||
# In non internal case managers see everything and non managers see
|
||||
|
@ -283,32 +283,16 @@ class Item(RESTModelMixin, models.Model):
|
||||
)
|
||||
unique_together = ("content_type", "object_id")
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
@property
|
||||
def title(self):
|
||||
def title_information(self):
|
||||
"""
|
||||
Return get_agenda_title() from the content_object.
|
||||
Return get_agenda_title_information() from the content_object.
|
||||
"""
|
||||
try:
|
||||
return self.content_object.get_agenda_title()
|
||||
return self.content_object.get_agenda_title_information()
|
||||
except AttributeError:
|
||||
raise NotImplementedError(
|
||||
"You have to provide a get_agenda_title "
|
||||
"method on your related model."
|
||||
)
|
||||
|
||||
@property
|
||||
def title_with_type(self):
|
||||
"""
|
||||
Return get_agenda_title_with_type() from the content_object.
|
||||
"""
|
||||
try:
|
||||
return self.content_object.get_agenda_title_with_type()
|
||||
except AttributeError:
|
||||
raise NotImplementedError(
|
||||
"You have to provide a get_agenda_title_with_type "
|
||||
"You have to provide a get_agenda_title_information "
|
||||
"method on your related model."
|
||||
)
|
||||
|
||||
|
@ -1,9 +1,11 @@
|
||||
from collections import defaultdict
|
||||
from typing import Any, Dict, List, Tuple
|
||||
|
||||
from ..users.projector import get_user_name
|
||||
from ..utils.projector import (
|
||||
AllData,
|
||||
ProjectorElementException,
|
||||
get_config,
|
||||
register_projector_slide,
|
||||
)
|
||||
|
||||
@ -75,19 +77,56 @@ def list_of_speakers_slide(
|
||||
|
||||
Returns all usernames, that are on the list of speaker of a slide.
|
||||
"""
|
||||
item_id = element.get("id") or 0 # item_id 0 means current_list_of_speakers
|
||||
item_id = element.get("id")
|
||||
|
||||
# TODO: handle item_id == 0
|
||||
if item_id is None:
|
||||
raise ProjectorElementException("id is required for list of speakers slide")
|
||||
|
||||
try:
|
||||
item = all_data["agenda/item"][item_id]
|
||||
except KeyError:
|
||||
raise ProjectorElementException(f"Item {item_id} does not exist")
|
||||
|
||||
user_ids = []
|
||||
# Partition speaker objects to waiting, current and finished
|
||||
speakers_waiting = []
|
||||
speakers_finished = []
|
||||
current_speaker = None
|
||||
for speaker in item["speakers"]:
|
||||
user_ids.append(speaker["user"])
|
||||
return {"user_ids": user_ids}
|
||||
user = get_user_name(all_data, speaker["user_id"])
|
||||
formatted_speaker = {
|
||||
"user": user,
|
||||
"marked": speaker["marked"],
|
||||
"weight": speaker["weight"],
|
||||
"end_time": speaker["end_time"],
|
||||
}
|
||||
|
||||
if speaker["begin_time"] is None and speaker["end_time"] is None:
|
||||
speakers_waiting.append(formatted_speaker)
|
||||
elif speaker["begin_time"] is not None and speaker["end_time"] is None:
|
||||
current_speaker = formatted_speaker
|
||||
else:
|
||||
speakers_finished.append(formatted_speaker)
|
||||
|
||||
# sort speakers
|
||||
speakers_waiting = sorted(speakers_waiting, key=lambda s: s["weight"])
|
||||
speakers_finished = sorted(speakers_finished, key=lambda s: s["end_time"])
|
||||
|
||||
number_of_last_speakers = get_config(all_data, "agenda_show_last_speakers")
|
||||
if number_of_last_speakers == 0:
|
||||
speakers_finished = []
|
||||
else:
|
||||
speakers_finished = speakers_finished[
|
||||
-number_of_last_speakers:
|
||||
] # Take the last speakers
|
||||
|
||||
return {
|
||||
"waiting": speakers_waiting,
|
||||
"current": current_speaker,
|
||||
"finished": speakers_finished,
|
||||
"content_object_collection": item["content_object"]["collection"],
|
||||
"title_information": item["title_information"],
|
||||
"item_number": item["item_number"],
|
||||
}
|
||||
|
||||
|
||||
def current_list_of_speakers_slide(
|
||||
|
@ -1,4 +1,4 @@
|
||||
from openslides.utils.rest_api import ModelSerializer, RelatedField
|
||||
from openslides.utils.rest_api import JSONField, ModelSerializer, RelatedField
|
||||
|
||||
from .models import Item, Speaker
|
||||
|
||||
@ -42,13 +42,14 @@ class ItemSerializer(ModelSerializer):
|
||||
content_object = RelatedItemRelatedField(read_only=True)
|
||||
speakers = SpeakerSerializer(many=True, read_only=True)
|
||||
|
||||
title_information = JSONField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Item
|
||||
fields = (
|
||||
"id",
|
||||
"item_number",
|
||||
"title",
|
||||
"title_with_type",
|
||||
"title_information",
|
||||
"comment",
|
||||
"closed",
|
||||
"type",
|
||||
|
@ -16,7 +16,7 @@ def listen_to_related_object_post_save(sender, instance, created, **kwargs):
|
||||
Do not run caching and autoupdate if the instance has a key
|
||||
skip_autoupdate in the agenda_item_update_information container.
|
||||
"""
|
||||
if hasattr(instance, "get_agenda_title"):
|
||||
if hasattr(instance, "get_agenda_title_information"):
|
||||
if created:
|
||||
attrs = {}
|
||||
for attr in ("type", "parent_id", "comment", "duration", "weight"):
|
||||
|
@ -318,18 +318,8 @@ class Assignment(RESTModelMixin, models.Model):
|
||||
"""
|
||||
agenda_item_update_information: Dict[str, Any] = {}
|
||||
|
||||
def get_agenda_title(self):
|
||||
"""
|
||||
Returns the title for the agenda.
|
||||
"""
|
||||
return str(self)
|
||||
|
||||
def get_agenda_title_with_type(self):
|
||||
"""
|
||||
Return a title for the agenda with the appended assignment verbose name.
|
||||
Note: It has to be the same return value like in JavaScript.
|
||||
"""
|
||||
return f"{self.get_agenda_title()} (self._meta.verbose_name)"
|
||||
def get_agenda_title_information(self):
|
||||
return {"title": self.title}
|
||||
|
||||
@property
|
||||
def agenda_item(self):
|
||||
|
@ -520,32 +520,8 @@ class Motion(RESTModelMixin, models.Model):
|
||||
"""
|
||||
agenda_item_update_information: Dict[str, Any] = {}
|
||||
|
||||
def get_agenda_title(self):
|
||||
"""
|
||||
Return the title string for the agenda.
|
||||
|
||||
If the identifier is given, the title consists of the motion verbose name
|
||||
and the identifier.
|
||||
Note: It has to be the same return value like in JavaScript.
|
||||
"""
|
||||
if self.identifier:
|
||||
title = f"{self._meta.verbose_name} {self.identifier}"
|
||||
else:
|
||||
title = self.title
|
||||
return title
|
||||
|
||||
def get_agenda_title_with_type(self):
|
||||
"""
|
||||
Return a title for the agenda with the type or the modified title if the
|
||||
identifier is set..
|
||||
|
||||
Note: It has to be the same return value like in JavaScript.
|
||||
"""
|
||||
if self.identifier:
|
||||
title = f"{self._meta.verbose_name} {self.identifier}"
|
||||
else:
|
||||
title = f"{self.title} ({self._meta.verbose_name})"
|
||||
return title
|
||||
def get_agenda_title_information(self):
|
||||
return {"title": self.title, "identifier": self.identifier}
|
||||
|
||||
@property
|
||||
def agenda_item(self):
|
||||
@ -908,11 +884,8 @@ class MotionBlock(RESTModelMixin, models.Model):
|
||||
"""
|
||||
return self.agenda_item.pk
|
||||
|
||||
def get_agenda_title(self):
|
||||
return self.title
|
||||
|
||||
def get_agenda_title_with_type(self):
|
||||
return f"{self.get_agenda_title()} ({self._meta.verbose_name})"
|
||||
def get_agenda_title_information(self):
|
||||
return {"title": self.title}
|
||||
|
||||
|
||||
class MotionLog(RESTModelMixin, models.Model):
|
||||
|
@ -67,14 +67,5 @@ class Topic(RESTModelMixin, models.Model):
|
||||
"""
|
||||
return self.agenda_item.pk
|
||||
|
||||
def get_agenda_title(self):
|
||||
"""
|
||||
Returns the title for the agenda.
|
||||
"""
|
||||
return self.title
|
||||
|
||||
def get_agenda_title_with_type(self):
|
||||
"""
|
||||
Returns the agenda title. Topicy should not get a type postfix.
|
||||
"""
|
||||
return self.get_agenda_title()
|
||||
def get_agenda_title_information(self):
|
||||
return {"title": self.title}
|
||||
|
@ -25,19 +25,18 @@ def user_slide(all_data: AllData, element: Dict[str, Any]) -> Dict[str, Any]:
|
||||
if user_id is None:
|
||||
raise ProjectorElementException("id is required for user slide")
|
||||
|
||||
try:
|
||||
user = all_data["users/user"][user_id]
|
||||
except KeyError:
|
||||
raise ProjectorElementException(f"user with id {user_id} does not exist")
|
||||
|
||||
return {"user": get_user_name(all_data, user["id"])}
|
||||
return {"user": get_user_name(all_data, user_id)}
|
||||
|
||||
|
||||
def get_user_name(all_data: AllData, user_id: int) -> str:
|
||||
"""
|
||||
Returns the short name for an user_id.
|
||||
"""
|
||||
user = all_data["users/user"][user_id]
|
||||
try:
|
||||
user = all_data["users/user"][user_id]
|
||||
except KeyError:
|
||||
raise ProjectorElementException(f"user with id {user_id} does not exist")
|
||||
|
||||
name_parts: List[str] = []
|
||||
for name_part in ("title", "first_name", "last_name"):
|
||||
if user[name_part]:
|
||||
|
@ -77,7 +77,13 @@ class RetrieveItem(TestCase):
|
||||
self.assertEqual(
|
||||
sorted(response.data.keys()),
|
||||
sorted(
|
||||
("id", "title", "speakers", "speaker_list_closed", "content_object")
|
||||
(
|
||||
"id",
|
||||
"title_information",
|
||||
"speakers",
|
||||
"speaker_list_closed",
|
||||
"content_object",
|
||||
)
|
||||
),
|
||||
)
|
||||
forbidden_keys = (
|
||||
|
@ -8,15 +8,17 @@ class TestItemTitle(TestCase):
|
||||
@patch("openslides.agenda.models.Item.content_object")
|
||||
def test_title_from_content_object(self, content_object):
|
||||
item = Item()
|
||||
content_object.get_agenda_title.return_value = "related_title"
|
||||
content_object.get_agenda_title_information.return_value = {
|
||||
"attr": "related_title"
|
||||
}
|
||||
|
||||
self.assertEqual(item.title, "related_title")
|
||||
self.assertEqual(item.title_information, {"attr": "related_title"})
|
||||
|
||||
@patch("openslides.agenda.models.Item.content_object")
|
||||
def test_title_invalid_related(self, content_object):
|
||||
item = Item()
|
||||
content_object.get_agenda_title.return_value = "related_title"
|
||||
del content_object.get_agenda_title
|
||||
content_object.get_agenda_title_information.return_value = "related_title"
|
||||
del content_object.get_agenda_title_information
|
||||
|
||||
with self.assertRaises(NotImplementedError):
|
||||
item.title
|
||||
item.title_information
|
||||
|
Loading…
Reference in New Issue
Block a user