diff --git a/client/src/app/shared/components/projector-button/projector-button.component.ts b/client/src/app/shared/components/projector-button/projector-button.component.ts index 1fa528d3c..c7648318b 100644 --- a/client/src/app/shared/components/projector-button/projector-button.component.ts +++ b/client/src/app/shared/components/projector-button/projector-button.component.ts @@ -15,6 +15,8 @@ import { ProjectionDialogService } from 'app/core/ui-services/projection-dialog. * * Use the input [object] to specify the object to project. It can either be * a Projectable or a ProjectorElementBuildDeskriptor + * + * For useage in menues set `menuItem=true`. */ @Component({ selector: 'os-projector-button', diff --git a/client/src/app/site/assignments/components/assignment-list/assignment-list.component.html b/client/src/app/site/assignments/components/assignment-list/assignment-list.component.html index 2179f4fab..549422ef5 100644 --- a/client/src/app/site/assignments/components/assignment-list/assignment-list.component.html +++ b/client/src/app/site/assignments/components/assignment-list/assignment-list.component.html @@ -28,18 +28,28 @@ > - + {{ isSelected(assignment) ? 'check_circle' : '' }} + + + + Projector + + + + + Title {{ assignment.getListTitle() }} + Phase @@ -57,6 +67,7 @@ Deselect all + Candidates diff --git a/client/src/app/site/assignments/components/assignment-list/assignment-list.component.ts b/client/src/app/site/assignments/components/assignment-list/assignment-list.component.ts index a3b618cf9..10c32e9ca 100644 --- a/client/src/app/site/assignments/components/assignment-list/assignment-list.component.ts +++ b/client/src/app/site/assignments/components/assignment-list/assignment-list.component.ts @@ -116,9 +116,12 @@ export class AssignmentListComponent extends ListViewBaseComponent
- +
diff --git a/client/src/app/site/assignments/models/view-assignment-poll.ts b/client/src/app/site/assignments/models/view-assignment-poll.ts index 063757473..a6b0fcc20 100644 --- a/client/src/app/site/assignments/models/view-assignment-poll.ts +++ b/client/src/app/site/assignments/models/view-assignment-poll.ts @@ -5,8 +5,9 @@ import { AssignmentPoll } from 'app/shared/models/assignments/assignment-poll'; import { AssignmentPollMethod } from '../services/assignment-poll.service'; import { ViewAssignmentPollOption } from './view-assignment-poll-option'; import { AssignmentPollOption } from 'app/shared/models/assignments/assignment-poll-option'; +import { Projectable, ProjectorElementBuildDeskriptor } from 'app/site/base/projectable'; -export class ViewAssignmentPoll implements Identifiable, Updateable { +export class ViewAssignmentPoll implements Identifiable, Updateable, Projectable { private _assignmentPoll: AssignmentPoll; private _assignmentPollOptions: ViewAssignmentPollOption[]; @@ -81,6 +82,14 @@ export class ViewAssignmentPoll implements Identifiable, Updateable { this.options.forEach(option => option.updateDependencies(update)); } + public getTitle(): string { + return 'TODO'; + } + + public getListTitle(): string { + return this.getTitle(); + } + /** * Creates a copy with deep-copy on all changing numerical values, * but intact uncopied references to the users @@ -98,4 +107,18 @@ export class ViewAssignmentPoll implements Identifiable, Updateable { }) ); } + + public getSlide(): ProjectorElementBuildDeskriptor { + return { + getBasicProjectorElement: options => ({ + name: 'assignments/poll', + assignment_id: this.assignment_id, + poll_id: this.id, + getIdentifiers: () => ['name', 'assignment_id', 'poll_id'] + }), + slideOptions: [], + projectionDefaultName: 'assignments', + getDialogTitle: () => 'TODO' + }; + } } diff --git a/client/src/app/slides/all-slide-configurations.ts b/client/src/app/slides/all-slide-configurations.ts index 862eb0c76..327f52ac9 100644 --- a/client/src/app/slides/all-slide-configurations.ts +++ b/client/src/app/slides/all-slide-configurations.ts @@ -82,6 +82,11 @@ export const allSlidesDynamicConfiguration: (SlideDynamicConfiguration & Slide)[ scaleable: true, scrollable: true }, + { + slide: 'assignments/poll', + scaleable: true, + scrollable: true + }, { slide: 'mediafiles/mediafile', scaleable: true, diff --git a/client/src/app/slides/all-slides.ts b/client/src/app/slides/all-slides.ts index 8bd28e026..ccd1aba76 100644 --- a/client/src/app/slides/all-slides.ts +++ b/client/src/app/slides/all-slides.ts @@ -115,6 +115,14 @@ export const allSlides: SlideManifest[] = [ elementIdentifiers: ['name', 'id'], canBeMappedToModel: true }, + { + slide: 'assignments/poll', + path: 'assignments/poll', + loadChildren: './slides/assignments/poll/poll-slide.module#PollSlideModule', + verboseName: 'Poll', + elementIdentifiers: ['name', 'assignment_id', 'poll_id'], + canBeMappedToModel: false + }, { slide: 'mediafiles/mediafile', path: 'mediafiles/mediafile', diff --git a/client/src/app/slides/assignments/assignment/assignment-slide-data.ts b/client/src/app/slides/assignments/assignment/assignment-slide-data.ts index f7ac4994e..4b9f9a2b2 100644 --- a/client/src/app/slides/assignments/assignment/assignment-slide-data.ts +++ b/client/src/app/slides/assignments/assignment/assignment-slide-data.ts @@ -1,3 +1,10 @@ export interface AssignmentSlideData { - user: string; + title: string; + description: string; + phase: number; + open_posts: number; + assignment_related_users: { + user: string; + elected: boolean; + }[]; } diff --git a/client/src/app/slides/assignments/assignment/assignment-slide.component.ts b/client/src/app/slides/assignments/assignment/assignment-slide.component.ts index bdb728c38..1a4d84fd1 100644 --- a/client/src/app/slides/assignments/assignment/assignment-slide.component.ts +++ b/client/src/app/slides/assignments/assignment/assignment-slide.component.ts @@ -1,7 +1,8 @@ -import { Component } from '@angular/core'; +import { Component, Input } from '@angular/core'; import { BaseSlideComponent } from 'app/slides/base-slide-component'; import { AssignmentSlideData } from './assignment-slide-data'; +import { SlideData } from 'app/core/core-services/projector-data.service'; @Component({ selector: 'os-assignment-slide', @@ -9,6 +10,20 @@ import { AssignmentSlideData } from './assignment-slide-data'; styleUrls: ['./assignment-slide.component.scss'] }) export class AssignmentSlideComponent extends BaseSlideComponent { + // TODO: Remove the following block, if not needed. + // This is just for debugging to get a console statement with all recieved + // data from the server + private _data: SlideData; + @Input() + public set data(data: SlideData) { + this._data = data; + console.log('Data: ', data); + } + public get data(): SlideData { + return this._data; + } + // UNTIL HERE + public constructor() { super(); } diff --git a/client/src/app/slides/assignments/assignment/assignment-slide.module.spec.ts b/client/src/app/slides/assignments/assignment/assignment-slide.module.spec.ts index fed92d424..92cc309f0 100644 --- a/client/src/app/slides/assignments/assignment/assignment-slide.module.spec.ts +++ b/client/src/app/slides/assignments/assignment/assignment-slide.module.spec.ts @@ -1,13 +1,13 @@ import { AssignmentSlideModule } from './assignment-slide.module'; -describe('UsersUserSlideModule', () => { - let usersUserSlideModule: AssignmentSlideModule; +describe('AssignmentSlideModule', () => { + let assignmentSlideModule: AssignmentSlideModule; beforeEach(() => { - usersUserSlideModule = new AssignmentSlideModule(); + assignmentSlideModule = new AssignmentSlideModule(); }); it('should create an instance', () => { - expect(usersUserSlideModule).toBeTruthy(); + expect(assignmentSlideModule).toBeTruthy(); }); }); diff --git a/client/src/app/slides/assignments/poll/poll-slide-data.ts b/client/src/app/slides/assignments/poll/poll-slide-data.ts new file mode 100644 index 000000000..e72af43b1 --- /dev/null +++ b/client/src/app/slides/assignments/poll/poll-slide-data.ts @@ -0,0 +1,17 @@ +import { AssignmentPercentBase, AssignmentPollMethod } from 'app/site/assignments/services/assignment-poll.service'; + +export interface PollSlideData { + title: string; + assignments_poll_100_percent_base: AssignmentPercentBase; + poll: { + published: boolean; + description?: string; + has_votes?: boolean; + pollmethod?: AssignmentPollMethod; + votesno?: string; // TODO: same conversion needed as for the PollModel + votesabstain?: string; + votesvalid?: string; + votesinvalid?: string; + votescast?: string; + }; +} diff --git a/client/src/app/slides/assignments/poll/poll-slide.component.html b/client/src/app/slides/assignments/poll/poll-slide.component.html new file mode 100644 index 000000000..edc11e288 --- /dev/null +++ b/client/src/app/slides/assignments/poll/poll-slide.component.html @@ -0,0 +1,3 @@ +
+

TODO

+
diff --git a/client/src/app/slides/assignments/poll/poll-slide.component.scss b/client/src/app/slides/assignments/poll/poll-slide.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/client/src/app/slides/assignments/poll/poll-slide.component.spec.ts b/client/src/app/slides/assignments/poll/poll-slide.component.spec.ts new file mode 100644 index 000000000..ee19e7570 --- /dev/null +++ b/client/src/app/slides/assignments/poll/poll-slide.component.spec.ts @@ -0,0 +1,26 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PollSlideComponent } from './poll-slide.component'; +import { E2EImportsModule } from '../../../../e2e-imports.module'; + +describe('PollSlideComponent', () => { + let component: PollSlideComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [E2EImportsModule], + declarations: [PollSlideComponent] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(PollSlideComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/client/src/app/slides/assignments/poll/poll-slide.component.ts b/client/src/app/slides/assignments/poll/poll-slide.component.ts new file mode 100644 index 000000000..f1bbaaf9e --- /dev/null +++ b/client/src/app/slides/assignments/poll/poll-slide.component.ts @@ -0,0 +1,30 @@ +import { Component, Input } from '@angular/core'; + +import { BaseSlideComponent } from 'app/slides/base-slide-component'; +import { PollSlideData } from './poll-slide-data'; +import { SlideData } from 'app/core/core-services/projector-data.service'; + +@Component({ + selector: 'os-poll-slide', + templateUrl: './poll-slide.component.html', + styleUrls: ['./poll-slide.component.scss'] +}) +export class PollSlideComponent extends BaseSlideComponent { + // TODO: Remove the following block, if not needed. + // This is just for debugging to get a console statement with all recieved + // data from the server + private _data: SlideData; + @Input() + public set data(data: SlideData) { + this._data = data; + console.log('Data: ', data); + } + public get data(): SlideData { + return this._data; + } + // UNTIL HERE + + public constructor() { + super(); + } +} diff --git a/client/src/app/slides/assignments/poll/poll-slide.module.spec.ts b/client/src/app/slides/assignments/poll/poll-slide.module.spec.ts new file mode 100644 index 000000000..da322455d --- /dev/null +++ b/client/src/app/slides/assignments/poll/poll-slide.module.spec.ts @@ -0,0 +1,13 @@ +import { PollSlideModule } from './poll-slide.module'; + +describe('PollSlideModule', () => { + let pollSlideModule: PollSlideModule; + + beforeEach(() => { + pollSlideModule = new PollSlideModule(); + }); + + it('should create an instance', () => { + expect(pollSlideModule).toBeTruthy(); + }); +}); diff --git a/client/src/app/slides/assignments/poll/poll-slide.module.ts b/client/src/app/slides/assignments/poll/poll-slide.module.ts new file mode 100644 index 000000000..0d538cde8 --- /dev/null +++ b/client/src/app/slides/assignments/poll/poll-slide.module.ts @@ -0,0 +1,7 @@ +import { NgModule } from '@angular/core'; + +import { makeSlideModule } from 'app/slides/base-slide-module'; +import { PollSlideComponent } from './poll-slide.component'; + +@NgModule(makeSlideModule(PollSlideComponent)) +export class PollSlideModule {} diff --git a/openslides/assignments/projector.py b/openslides/assignments/projector.py index 04ec336b2..1fb2e4d06 100644 --- a/openslides/assignments/projector.py +++ b/openslides/assignments/projector.py @@ -1,6 +1,12 @@ -from typing import Any, Dict +from typing import Any, Dict, List -from ..utils.projector import AllData, register_projector_slide +from ..users.projector import get_user_name +from ..utils.projector import ( + AllData, + ProjectorElementException, + get_config, + register_projector_slide, +) # Important: All functions have to be prune. This means, that thay can only @@ -8,15 +14,95 @@ from ..utils.projector import AllData, register_projector_slide # side effects. +def get_assignment(all_data: AllData, id: Any) -> Dict[str, Any]: + if id is None: + raise ProjectorElementException("id is required for assignment slide") + + try: + assignment = all_data["assignments/assignment"][id] + except KeyError: + raise ProjectorElementException(f"assignment with id {id} does not exist") + return assignment + + async def assignment_slide( all_data: AllData, element: Dict[str, Any], projector_id: int ) -> Dict[str, Any]: """ Assignment slide. """ - poll_id = element.get("tree") # noqa - return {"error": "TODO"} + assignment = get_assignment(all_data, element.get("id")) + + assignment_related_users: List[Dict[str, Any]] = [ + { + "user": await get_user_name(all_data, aru["user_id"]), + "elected": aru["elected"], + } + for aru in sorted( + assignment["assignment_related_users"], key=lambda aru: aru["weight"] + ) + ] + + return { + "title": assignment["title"], + "phase": assignment["phase"], + "open_posts": assignment["open_posts"], + "description": assignment["description"], + "assignment_related_users": assignment_related_users, + } + + +async def poll_slide( + all_data: AllData, element: Dict[str, Any], projector_id: int +) -> Dict[str, Any]: + """ + Poll slide. + """ + assignment = get_assignment(all_data, element.get("assignment_id")) + + # get poll + poll_id = element.get("poll_id") + if poll_id is None: + raise ProjectorElementException("id is required for poll slide") + + poll = None + for p in assignment["polls"]: + if p["id"] == poll_id: + poll = p + break + if poll is None: + raise ProjectorElementException(f"poll with id {poll_id} does not exist") + + poll_data = {"published": poll["published"]} + + if poll["published"]: + poll_data["description"] = poll["description"] + poll_data["has_votes"] = poll["has_votes"] + poll_data["pollmethod"] = poll["pollmethod"] + poll_data["votesno"] = poll["votesno"] + poll_data["votesabstain"] = poll["votesabstain"] + poll_data["votesvalid"] = poll["votesvalid"] + poll_data["votesinvalid"] = poll["votesinvalid"] + poll_data["votescast"] = poll["votescast"] + + poll_data["options"] = [ + { + "user": await get_user_name(all_data, option["user_id"]), + "is_elected": option["is_elected"], + "votes": option["votes"], + } + for option in sorted(poll["options"], key=lambda option: option["weight"]) + ] + + return { + "title": assignment["title"], + "assignments_poll_100_percent_base": await get_config( + all_data, "assignments_poll_100_percent_base" + ), + "poll": poll_data, + } def register_projector_slides() -> None: register_projector_slide("assignments/assignment", assignment_slide) + register_projector_slide("assignments/poll", poll_slide)