Projector templates

- Projector base template
- Projection button
- projection dialog
- motion slide (template and motions/projector.py)
- user slide (template and users/projector.py)
- motion and user list view with projector column permission check.
This commit is contained in:
Emanuel Schütze 2019-01-26 20:37:49 +01:00
parent 965d23be50
commit 42f8b74d8e
31 changed files with 347 additions and 111 deletions

View File

@ -45,7 +45,6 @@ export class ProjectionDialogService extends OpenSlidesComponent {
ProjectorElementBuildDeskriptor,
ProjectionDialogReturnType
>(ProjectionDialogComponent, {
minWidth: '500px',
maxHeight: '90vh',
data: descriptor
});

View File

@ -1,23 +1,19 @@
<h2 mat-dialog-title>{{ projectorElementBuildDescriptor.getTitle() }}</h2>
<h2 mat-dialog-title translate>Project
<span *ngIf="projectorElementBuildDescriptor.projectionDefaultName === 'motions'" translate>Motion</span>
{{ projectorElementBuildDescriptor.getTitle() }}?</h2>
<mat-dialog-content>
<mat-card>
<mat-card-title> <span translate>Projectors</span> </mat-card-title>
<mat-card-content>
<div class="projectors" *ngFor="let projector of projectors" [ngClass]="isProjectedOn(projector) ? 'projected' : ''">
<mat-checkbox [checked]="isProjectorSelected(projector)" (change)="toggleProjector(projector)">
{{ projector.name | translate }}
</mat-checkbox>
<span *ngIf="isProjectedOn(projector)" class="right">
<mat-icon>videocam</mat-icon>
</span>
</div>
</mat-card-content>
</mat-card>
<mat-card *ngIf="options.length > 0">
<mat-card-title>
<span translate>Slide options</span>
</mat-card-title>
<mat-card-content>
<div class="projectors" *ngFor="let projector of projectors" [ngClass]="isProjectedOn(projector) ? 'projected' : ''">
<mat-checkbox [checked]="isProjectorSelected(projector)" (change)="toggleProjector(projector)">
{{ projector.name | translate }}
</mat-checkbox>
<span *ngIf="isProjectedOn(projector)" class="right">
<mat-icon>videocam</mat-icon>
</span>
</div>
<mat-divider></mat-divider>
<div *ngIf="options.length > 0">
<div *ngFor="let option of options">
<div *ngIf="isDecisionOption(option)">
<mat-checkbox [checked]="projectorElement[option.key]" (change)="projectorElement[option.key] = !projectorElement[option.key]">
@ -33,8 +29,7 @@
</mat-radio-group>
</div>
</div>
</mat-card-content>
</mat-card>
</div>
</mat-dialog-content>
<mat-dialog-actions>
<button mat-button (click)="onOk()" color="primary" translate>OK</button>

View File

@ -1,15 +1,12 @@
mat-dialog-content {
overflow: inherit;
min-width: auto;
div.projectors {
padding: 15px;
padding: 15px 0;
.right {
float: right;
}
}
mat-card {
margin-bottom: 10px;
}
}

View File

@ -1,3 +1,4 @@
<button type="button" mat-icon-button (click)="onClick($event)">
<button type="button" mat-mini-fab (click)="onClick($event)"
[ngClass]="isProjected() ? 'projectorbutton-active' : 'projectorbutton-inactive'">
<mat-icon>videocam</mat-icon>
</button>

View File

@ -0,0 +1,8 @@
.projectorbutton-active {
color: white !important;
}
.projectorbutton-inactive {
background-color: white;
color: grey;
}

View File

@ -1,6 +1,7 @@
import { Component, OnInit, Input } from '@angular/core';
import { Projectable, ProjectorElementBuildDeskriptor } from 'app/site/base/projectable';
import { ProjectionDialogService } from 'app/core/services/projection-dialog.service';
import { ProjectorService } from '../../../core/services/projector.service';
/**
*/
@ -16,7 +17,10 @@ export class ProjectorButtonComponent implements OnInit {
/**
* The consotructor
*/
public constructor(private projectionDialogService: ProjectionDialogService) {}
public constructor(
private projectionDialogService: ProjectionDialogService,
private projectorService: ProjectorService
) {}
/**
* Initialization function
@ -27,4 +31,17 @@ export class ProjectorButtonComponent implements OnInit {
event.stopPropagation();
this.projectionDialogService.openProjectDialogFor(this.object);
}
/**
*
*
* @returns true, if the object is projected on one projector.
*/
public isProjected(): boolean {
if (this.object) {
return this.projectorService.isProjected(this.object);
} else {
return false;
}
}
}

View File

@ -36,12 +36,12 @@ export class MediafileListComponent extends ListViewBaseComponent<ViewMediafile>
public fontActions: string[];
/**
* Columns to display in Mediafile table when fill width is available
* Columns to display in Mediafile table when desktop view is available
*/
public displayedColumnsDesktop: string[] = ['title', 'info', 'indicator', 'menu'];
/**
* Columns to display in Mediafile table when fill width is available
* Columns to display in Mediafile table when mobile view is available
*/
public displayedColumnsMobile: string[] = ['title', 'menu'];

View File

@ -27,16 +27,16 @@
<mat-table class="os-listview-table on-transition-fade" [dataSource]="dataSource" matSort>
<!-- Selector column -->
<ng-container matColumnDef="selector">
<mat-header-cell *matHeaderCellDef mat-sort-header class="checkbox-cell"></mat-header-cell>
<mat-cell *matCellDef="let motion" class="checkbox-cell">
<mat-header-cell *matHeaderCellDef mat-sort-header></mat-header-cell>
<mat-cell *matCellDef="let motion">
<mat-icon>{{ isSelected(motion) ? 'check_circle' : '' }}</mat-icon>
</mat-cell>
</ng-container>
<!-- Projector column -->
<ng-container matColumnDef="projector">
<mat-header-cell *matHeaderCellDef mat-sort-header class="icon-cell">Projector</mat-header-cell>
<mat-cell *matCellDef="let motion" class="icon-cell">
<mat-header-cell *matHeaderCellDef mat-sort-header>Projector</mat-header-cell>
<mat-cell *matCellDef="let motion">
<os-projector-button [object]="motion"></os-projector-button>
</mat-cell>
</ng-container>
@ -84,13 +84,14 @@
grey: motion.state.css_class === 'default',
lightblue: motion.state.css_class === 'primary'
}"
[disabled]="true"
>
{{ getStateLabel(motion) }}
</mat-basic-chip>
<!-- recommendation -->
<span *ngIf="motion.recommendation">
<mat-basic-chip class="bluegrey"> {{ getRecommendationLabel(motion) }} </mat-basic-chip>
<span *ngIf="motion.recommendation && motion.state.next_states_id.length > 0">
<mat-basic-chip class="bluegrey" [disabled]="true">{{ getRecommendationLabel(motion) }} </mat-basic-chip>
</span>
</div>
</mat-cell>

View File

@ -25,6 +25,8 @@ import { WorkflowState } from '../../../../shared/models/motions/workflow-state'
import { WorkflowRepositoryService } from '../../services/workflow-repository.service';
import { MotionPdfExportService } from '../../services/motion-pdf-export.service';
import { MotionExportDialogComponent } from '../motion-export-dialog/motion-export-dialog.component';
import { OperatorService } from '../../../../core/services/operator.service';
import { ViewportService } from '../../../../core/services/viewport.service';
/**
* Component that displays all the motions in a Table using DataSource.
@ -36,18 +38,14 @@ import { MotionExportDialogComponent } from '../motion-export-dialog/motion-expo
})
export class MotionListComponent extends ListViewBaseComponent<ViewMotion> implements OnInit {
/**
* Use for minimal width. Please note the 'selector' row for multiSelect mode,
* to be able to display an indicator for the state of selection
* TODO: Remove projector, if columnsToDisplayFullWidth is used..
* Columns to display in table when desktop view is available
*/
public columnsToDisplayMinWidth = ['projector', 'identifier', 'title', 'state', 'speakers'];
public displayedColumnsDesktop: string[] = ['identifier', 'title', 'state', 'speakers'];
/**
* Use for maximal width. Please note the 'selector' row for multiSelect mode,
* to be able to display an indicator for the state of selection
* TODO: Needs vp.desktop check
* Columns to display in table when mobile view is available
*/
public columnsToDisplayFullWidth = ['projector', 'identifier', 'title', 'state', 'speakers'];
public displayedColumnsMobile = ['identifier', 'title'];
/**
* Value of the configuration variable `motions_statutes_enabled` - are statutes enabled?
@ -82,6 +80,7 @@ export class MotionListComponent extends ListViewBaseComponent<ViewMotion> imple
* @param userRepo
* @param sortService
* @param filterService
* @param vp
* @param perms LocalPermissionService
*/
public constructor(
@ -97,8 +96,10 @@ export class MotionListComponent extends ListViewBaseComponent<ViewMotion> imple
private workflowRepo: WorkflowRepositoryService,
private motionRepo: MotionRepositoryService,
private motionCsvExport: MotionCsvExportService,
private operator: OperatorService,
private pdfExport: MotionPdfExportService,
private dialog: MatDialog,
private vp: ViewportService,
public multiselectService: MotionMultiselectService,
public sortService: MotionSortListService,
public filterService: MotionFilterListService,
@ -221,10 +222,14 @@ export class MotionListComponent extends ListViewBaseComponent<ViewMotion> imple
* Returns current definitions for the listView table
*/
public getColumnDefinition(): string[] {
if (this.isMultiSelect) {
return ['selector'].concat(this.columnsToDisplayMinWidth);
let columns = this.vp.isMobile ? this.displayedColumnsMobile : this.displayedColumnsDesktop;
if (this.operator.hasPerms('core.can_manage_projector')) {
columns = ['projector'].concat(columns);
}
return this.columnsToDisplayMinWidth;
if (this.isMultiSelect) {
columns = ['selector'].concat(columns);
}
return columns;
}
/**

View File

@ -318,7 +318,7 @@ export class ViewMotion extends BaseProjectableModel {
public getTitle(): string {
if (this.identifier) {
return this.identifier + ' - ' + this.title;
return 'Motion ' + this.identifier;
}
return this.title;
}
@ -480,7 +480,7 @@ export class ViewMotion extends BaseProjectableModel {
}
],
projectionDefaultName: 'motions',
getTitle: () => this.getTitle()
getTitle: () => this.identifier
};
}

View File

@ -1,8 +1,15 @@
<div id="container" [osResized]="resizeSubject" [ngStyle]="containerStyle" #container>
<div id="projector" [ngStyle]="projectorStyle">
<div class="header" [ngStyle]="headerFooterStyle" *ngIf="enableHeaderAndFooter">
<div *ngIf="enableTitle">
Header Title
<div id="header" [ngStyle]="headerFooterStyle" *ngIf="enableHeaderAndFooter">
<!-- TODO: Logo <img *ngIf="enableLogo" id="logo"> -->
<div *ngIf="enableTitle" id="eventdata">
<div
*ngIf="eventName"
class="event-name"
[ngClass]="!eventDescription ? 'titleonly' : ''"
[innerHTML]="eventName"
></div>
<div *ngIf="eventDescription" class="event-description" [innerHTML]="eventDescription"></div>
</div>
</div>
@ -10,8 +17,12 @@
<os-slide-container [slideData]="slide" [scroll]="scroll" [scale]="scale"></os-slide-container>
</div>
<div class="footer" [ngStyle]="headerFooterStyle" *ngIf="enableHeaderAndFooter">
Footer
<div id="footer" [ngStyle]="headerFooterStyle" *ngIf="enableHeaderAndFooter">
<div class="footertext">
<span *ngIf="eventDate"> {{ eventDate }} </span>
<span *ngIf="eventDate && eventLocation"> | </span>
<span *ngIf="eventLocation"> {{ eventLocation }} </span>
</div>
</div>
</div>
</div>

View File

@ -9,29 +9,76 @@
transform-origin: left top;
overflow: hidden;
.header {
#header {
position: absolute;
top: 0;
left: 0;
color: white;
width: 100%;
height: 50px;
height: 70px;
box-shadow: 0 0 7px rgba(0, 0, 0, 0.6);
background-repeat: no-repeat;
background-size: 100% 100%;
margin-bottom: 20px;
z-index: 1;
#logo {
padding-left: 50px;
padding-top: 10px;
height: 50px;
margin-right: 25px;
float: left;
}
#eventdata {
padding-left: 50px;
padding-top: 12px;
height: 50px;
overflow: hidden;
line-height: 1.1;
.event-name {
font-size: 26px;
font-weight: 400;
&.titleonly {
padding-top: 12px;
}
}
.event-description {
font-size: 18px;
opacity: 0.8;
}
}
}
.content {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
left: 50px;
right: 50px;
}
.footer {
position: absolute;
color: white;
#footer {
position: fixed;
width: 100%;
height: 50px;
height: 35px;
bottom: 0;
z-index: 1;
.footertext {
font-size: 16px;
padding-left: 50px;
padding-right: 50px;
padding-top: 5px;
overflow: hidden;
text-align: right;
}
span {
opacity: 0.8;
}
}
}
}

View File

@ -132,6 +132,10 @@ export class ProjectorComponent extends BaseComponent implements OnDestroy {
public enableHeaderAndFooter = true;
public enableTitle = true;
public enableLogo = true;
public eventName;
public eventDescription;
public eventDate;
public eventLocation;
/**
* Listen to all related config variables. Register the resizeSubject.
@ -166,6 +170,10 @@ export class ProjectorComponent extends BaseComponent implements OnDestroy {
this.configService
.get<string>('projector_background_color')
.subscribe(val => (this.projectorStyle['background-color'] = val));
this.configService.get<string>('general_event_name').subscribe(val => (this.eventName = val));
this.configService.get<string>('general_event_description').subscribe(val => (this.eventDescription = val));
this.configService.get<string>('general_event_date').subscribe(val => (this.eventDate = val));
this.configService.get<string>('general_event_location').subscribe(val => (this.eventLocation = val));
// Watches for resizing of the container.
this.resizeSubject.subscribe(() => {

View File

@ -1,3 +1,6 @@
#slide {
width: calc(100% - 100px);
}
::ng-deep #slide {
z-index: 5;
height: 100%;

View File

@ -95,7 +95,7 @@ export class SlideContainerComponent extends BaseComponent {
*/
public slideStyle: { 'font-size': string; 'margin-top': string } = {
'font-size': '100%',
'margin-top': '50px'
'margin-top': '100px'
};
/**
@ -124,7 +124,7 @@ export class SlideContainerComponent extends BaseComponent {
let value = this._scroll;
value *= -50;
if (this.headerEnabled) {
value += 50; // Default offset for the header
value += 100; // Default offset for the header
}
this.slideStyle['margin-top'] = `${value}px`;
} else {

View File

@ -25,16 +25,16 @@
<mat-table class="os-listview-table on-transition-fade" [dataSource]="dataSource" matSort>
<!-- Selector column -->
<ng-container matColumnDef="selector">
<mat-header-cell *matHeaderCellDef mat-sort-header class="icon-cell"></mat-header-cell>
<mat-cell *matCellDef="let user" (click)="selectItem(user, $event)" class="icon-cell">
<mat-header-cell *matHeaderCellDef mat-sort-header></mat-header-cell>
<mat-cell *matCellDef="let user" (click)="selectItem(user, $event)">
<mat-icon>{{ isSelected(user) ? 'check_circle' : '' }}</mat-icon>
</mat-cell>
</ng-container>
<!-- Projector column -->
<ng-container matColumnDef="projector">
<mat-header-cell *matHeaderCellDef mat-sort-header class="icon-cell">Projector</mat-header-cell>
<mat-cell *matCellDef="let user" class="icon-cell">
<mat-header-cell *matHeaderCellDef mat-sort-header>Projector</mat-header-cell>
<mat-cell *matCellDef="let user">
<os-projector-button [object]="user"></os-projector-button>
</mat-cell>
</ng-container>
@ -49,7 +49,7 @@
<ng-container matColumnDef="group">
<mat-header-cell *matHeaderCellDef mat-sort-header>Group</mat-header-cell>
<mat-cell *matCellDef="let user">
<div class="groupsCell">
<div class='groupsCell'>
<span *ngIf="user.groups && user.groups.length">
<mat-icon>people</mat-icon>
{{ user.groups }}

View File

@ -9,6 +9,10 @@
}
.os-listview-table {
.mat-column-projector {
padding-right: 15px;
}
.mat-column-name {
flex: 1 0 200px;
}

View File

@ -14,6 +14,8 @@ import { UserRepositoryService } from '../../services/user-repository.service';
import { ViewUser } from '../../models/view-user';
import { UserFilterListService } from '../../services/user-filter-list.service';
import { UserSortListService } from '../../services/user-sort-list.service';
import { ViewportService } from '../../../../core/services/viewport.service';
import { OperatorService } from '../../../../core/services/operator.service';
/**
* Component for the user list view.
@ -25,6 +27,16 @@ import { UserSortListService } from '../../services/user-sort-list.service';
styleUrls: ['./user-list.component.scss']
})
export class UserListComponent extends ListViewBaseComponent<ViewUser> implements OnInit {
/**
* Columns to display in table when desktop view is available
*/
public displayedColumnsDesktop: string[] = ['name', 'group'];
/**
* Columns to display in table when mobile view is available
*/
public displayedColumnsMobile = ['name'];
/**
* Stores the observed configuration if the presence view is available to administrators
*/
@ -48,6 +60,8 @@ export class UserListComponent extends ListViewBaseComponent<ViewUser> implement
* @param groupRepo: The user group repository
* @param router the router service
* @param route the local route
* @param operator
* @param vp
* @param csvExport CSV export Service,
* @param promptService
* @param groupRepo
@ -64,6 +78,8 @@ export class UserListComponent extends ListViewBaseComponent<ViewUser> implement
private choiceService: ChoiceService,
private router: Router,
private route: ActivatedRoute,
private operator: OperatorService,
private vp: ViewportService,
protected csvExport: CsvExportService,
private promptService: PromptService,
public filterService: UserFilterListService,
@ -243,10 +259,15 @@ export class UserListComponent extends ListViewBaseComponent<ViewUser> implement
* @returns column definition
*/
public getColumnDefinition(): string[] {
// TODO: no projector in mobile view.
const columns = ['projector', 'name', 'group', 'presence'];
let columns = this.vp.isMobile ? this.displayedColumnsMobile : this.displayedColumnsDesktop;
if (this.operator.hasPerms('core.can_manage_projector')) {
columns = ['projector'].concat(columns);
}
if (this.operator.hasPerms('users.can_manage')) {
columns = columns.concat(['presence']);
}
if (this.isMultiSelect) {
return ['selector'].concat(columns);
columns = ['selector'].concat(columns);
}
return columns;
}

View File

@ -6,10 +6,16 @@
height: 30px;
margin: 12px;
z-index: 2;
padding-right: 50px;
padding-top: 5px;
mat-icon {
padding-top: 5px;
}
span {
padding-left: 5px;
font-size: 16px;
font-size: 24px;
float: right;
}
}

View File

@ -1,3 +1,15 @@
export interface MotionsMotionSlideData {
identifier: string;
title: string;
text: string;
reason?: string;
is_child: boolean;
show_meta_box: boolean;
submitter?: string[];
recommender?: string;
recommendation?: string;
recommendation_extension?: string;
amendment_paragraphs: { paragraph: string }[];
change_recommendations: object[];
modified_final_version?: string;
}

View File

@ -1,4 +1,35 @@
<div *ngIf="data">
Motion Slide
<h1>{{ data.data.title }}</h1>
<div id="sidebox" *ngIf="data.data.show_meta_box">
<!-- Submitters -->
<h3 translate>Submitters</h3>
<span *ngFor="let submitter of data.data.submitter; let last = last">
{{ submitter }}<span *ngIf="!last">, </span>
</span>
<!-- Recommendation -->
<div *ngIf="data.data.recommendation && data.data.recommender">
<h3>{{ data.data.recommender }}</h3>
{{ data.data.recommendation | translate }} {{ data.data.recommendation_extension }}
</div>
</div>
<div [ngStyle]="{'width': data.data.show_meta_box ? 'calc(100% - 250px)' : '100%'}">
<!-- Title -->
<div class="slidetitle">
<h1>{{ data.data.title }}</h1>
<h2><span translate>Motion</span> {{ data.data.identifier }}</h2>
</div>
<!-- Text (original) -->
<div *ngIf="!data.data.is_child" [innerHTML]="data.data.text"></div>
<!-- Amendment text -->
<div *ngIf="data.data.is_child" [innerHTML]="data.data.amendment_paragraphs[0]"></div>
<!-- Reason -->
<div *ngIf="data.data.reason">
<h3 translate>Reason</h3>
<div [innerHTML]="data.data.reason"></div>
</div>
</div>
</div>

View File

@ -1,3 +1,15 @@
div {
background-color: red;
#sidebox {
width: 260px;
right: 0;
margin-top: 50px;
background: #d3d3d3;
border-radius: 7px 0 0 7px;
padding: 3px 7px 10px 10px;
position: fixed;
z-index: 5;
h3 {
margin-top: 10px;
margin-bottom: 0px;
}
}

View File

@ -1,3 +1,3 @@
export interface UsersUserSlideData {
test: string;
user: string;
}

View File

@ -1,3 +1,3 @@
<div id="outer">
User Slide
<div *ngIf="data">
<h1>{{ data.data.user }}</h1>
</div>

View File

@ -41,7 +41,9 @@ body {
h1,
h2,
h3,
.title-font {
.title-font,
.slidetitle h1,
.slidetitle h2 {
font-family: OSFont Condensed, Fira Sans Condensed, Roboto-condensed, Arial, Helvetica, sans-serif;
}
@ -232,6 +234,15 @@ mat-card {
.os-listview-table {
@extend %os-table;
/* multi select column */
.mat-column-selector {
flex: 0 0 60px;
}
/* projector button column */
.mat-column-projector {
flex: 0 0 40px;
overflow: visible;
}
/** hide mat header row */
.mat-header-row {
display: none;
@ -242,6 +253,7 @@ mat-card {
@extend %os-table;
}
/* TODO: find a better way to get more vertical space in (empty/small) tables for maximize filter dialog */
mat-paginator {
min-height: 800px;
@ -292,10 +304,6 @@ mat-expansion-panel {
display: none;
}
.icon-cell {
flex: 0 0 40px;
}
// ngx-file-drop requires the custom style in the global css file
.file-drop-style {
margin: auto;
@ -565,6 +573,7 @@ button.mat-menu-item.selected {
flex: 1;
min-width: 0px;
}
.filter-imports {
max-width: 50%;
}
@ -574,3 +583,42 @@ button.mat-menu-item.selected {
font-weight: 500;
font-size: 16px;
}
/*** Projector slides ***/
#slide {
h3 {
color: #222;
margin-bottom: 10px
}
.slidetitle {
border-bottom: 5px solid #d3d3d3;
margin-bottom: 40px;
h1 {
font-size: 2.25em;
line-height: 1.1em;
margin-bottom: 0;
padding-bottom: 0;
}
h2 {
color: #9a9898;
margin-top: 10px;
margin-bottom: 0px;
font-size: 28px;
font-weight: normal;
display: block;
}
}
ul, ol {
margin: 0 0 10px 0;
}
hr {
margin: 10px 0;
}
}

View File

@ -107,5 +107,9 @@ def current_list_of_speakers_slide(
def register_projector_slides() -> None:
register_projector_slide("agenda/item-list", items_slide)
register_projector_slide("agenda/list-of-speakers", list_of_speakers_slide)
register_projector_slide("agenda/current-list-of-speakers", current_list_of_speakers_slide)
register_projector_element("agenda/current-list-of-speakers-overlay", current_list_of_speakers_slide)
register_projector_slide(
"agenda/current-list-of-speakers", current_list_of_speakers_slide
)
register_projector_slide(
"agenda/current-list-of-speakers-overlay", current_list_of_speakers_slide
)

View File

@ -54,4 +54,4 @@ def clock_slide(all_data: AllData, element: Dict[str, Any]) -> Dict[str, Any]:
def register_projector_slides() -> None:
register_projector_slide("core/countdown", countdown_slide)
register_projector_slide("core/projector-message", message_slide)
register_projector_element("core/clock", clock_slide)
register_projector_slide("core/clock", clock_slide)

View File

@ -173,7 +173,9 @@ class ProjectorViewSet(ModelViewSet):
elements = request.data.get("elements")
preview = request.data.get("preview")
history_element = request.data.get("append_to_history")
delete_last_history_element = request.data.get("delete_last_history_element", False)
delete_last_history_element = request.data.get(
"delete_last_history_element", False
)
changed_data = {}
if elements is not None:

View File

@ -45,12 +45,11 @@ def motion_slide(all_data: AllData, element: Dict[str, Any]) -> Dict[str, Any]:
* show_meta_box
* reason
* modified_final_version
* state
* state_extension
* recommendation
* recommendation_extension
* recommender
* change_recommendations
* submitter
* poll
"""
mode = element.get("mode")
motion_id = element.get("id")
@ -81,11 +80,6 @@ def motion_slide(all_data: AllData, element: Dict[str, Any]) -> Dict[str, Any]:
return_value["modified_final_version"] = motion["modified_final_version"]
if show_meta_box:
state = get_state(all_data, motion, motion["state_id"])
return_value["state"] = state["name"]
if state["show_state_extension_field"]:
return_value["state_extension"] = motion["state_extension"]
if (
not get_config(all_data, "motions_disable_recommendation_on_projector")
and motion["recommendation_id"]
@ -101,6 +95,9 @@ def motion_slide(all_data: AllData, element: Dict[str, Any]) -> Dict[str, Any]:
"recommendation_extension"
]
return_value["recommender"] = get_config(
all_data, "motions_recommendations_by"
)
return_value["change_recommendations"] = motion["change_recommendations"]
return_value["submitter"] = [
@ -110,15 +107,6 @@ def motion_slide(all_data: AllData, element: Dict[str, Any]) -> Dict[str, Any]:
)
]
for poll in motion["polls"][::-1]:
if poll["has_votes"]:
return_value["poll"] = {
"yes": poll["yes"],
"no": poll["no"],
"abstain": poll["abstain"],
}
break
return return_value

View File

@ -1,6 +1,10 @@
from typing import Any, Dict, List
from ..utils.projector import AllData, register_projector_slide
from ..utils.projector import (
AllData,
ProjectorElementException,
register_projector_slide,
)
# Important: All functions have to be prune. This means, that thay can only
@ -12,8 +16,22 @@ from ..utils.projector import AllData, register_projector_slide
def user_slide(all_data: AllData, element: Dict[str, Any]) -> Dict[str, Any]:
"""
User slide.
The returned dict can contain the following fields:
* user
"""
return {"error": "TODO"}
user_id = element.get("id")
if user_id is None:
return {"error": "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_value = {"user": get_user_name(all_data, user["id"])}
return return_value
def get_user_name(all_data: AllData, user_id: int) -> str:

View File

@ -166,7 +166,5 @@ def test_motion_slide(all_data):
"is_child": False,
"show_meta_box": True,
"reason": "",
"state": "submitted",
"submitter": ["Administrator"],
"poll": {"yes": "10.000000", "no": "-1.000000", "abstain": "20.000000"},
}