Basic assignment and poll slide

This commit is contained in:
FinnStutzenstein 2019-04-23 15:04:38 +02:00
parent d0c6fd1dd1
commit 55dd2d1d6f
19 changed files with 272 additions and 20 deletions

View File

@ -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 * Use the input [object] to specify the object to project. It can either be
* a Projectable or a ProjectorElementBuildDeskriptor * a Projectable or a ProjectorElementBuildDeskriptor
*
* For useage in menues set `menuItem=true`.
*/ */
@Component({ @Component({
selector: 'os-projector-button', selector: 'os-projector-button',

View File

@ -28,18 +28,28 @@
> >
</os-sort-filter-bar> </os-sort-filter-bar>
<mat-table class="os-listview-table on-transition-fade" [dataSource]="dataSource" matSort> <mat-table class="os-listview-table on-transition-fade" [dataSource]="dataSource" matSort>
<!-- slector column --> <!-- selector column -->
<ng-container matColumnDef="selector"> <ng-container matColumnDef="selector">
<mat-header-cell *matHeaderCellDef mat-sort-header class="icon-cell"></mat-header-cell> <mat-header-cell *matHeaderCellDef mat-sort-header class="icon-cell"></mat-header-cell>
<mat-cell *matCellDef="let assignment" class="icon-cell"> <mat-cell *matCellDef="let assignment" class="icon-cell">
<mat-icon>{{ isSelected(assignment) ? 'check_circle' : '' }}</mat-icon> <mat-icon>{{ isSelected(assignment) ? 'check_circle' : '' }}</mat-icon>
</mat-cell> </mat-cell>
</ng-container> </ng-container>
<!-- projector column -->
<ng-container matColumnDef="projector">
<mat-header-cell *matHeaderCellDef mat-sort-header>Projector</mat-header-cell>
<mat-cell *matCellDef="let assignment">
<os-projector-button [object]="assignment"></os-projector-button>
</mat-cell>
</ng-container>
<!-- name column --> <!-- name column -->
<ng-container matColumnDef="title"> <ng-container matColumnDef="title">
<mat-header-cell *matHeaderCellDef mat-sort-header>Title</mat-header-cell> <mat-header-cell *matHeaderCellDef mat-sort-header>Title</mat-header-cell>
<mat-cell *matCellDef="let assignment">{{ assignment.getListTitle() }}</mat-cell> <mat-cell *matCellDef="let assignment">{{ assignment.getListTitle() }}</mat-cell>
</ng-container> </ng-container>
<!-- phase column--> <!-- phase column-->
<ng-container matColumnDef="phase"> <ng-container matColumnDef="phase">
<mat-header-cell *matHeaderCellDef mat-sort-header>Phase</mat-header-cell> <mat-header-cell *matHeaderCellDef mat-sort-header>Phase</mat-header-cell>
@ -57,6 +67,7 @@
<span translate>Deselect all</span> <span translate>Deselect all</span>
</button> </button>
</ng-container> </ng-container>
<!-- candidates column --> <!-- candidates column -->
<ng-container matColumnDef="candidates"> <ng-container matColumnDef="candidates">
<mat-header-cell *matHeaderCellDef mat-sort-header>Candidates</mat-header-cell> <mat-header-cell *matHeaderCellDef mat-sort-header>Candidates</mat-header-cell>

View File

@ -116,9 +116,12 @@ export class AssignmentListComponent extends ListViewBaseComponent<ViewAssignmen
* @returns a list of string matching the columns * @returns a list of string matching the columns
*/ */
public getColumnDefintion(): string[] { public getColumnDefintion(): string[] {
const list = ['title', 'phase', 'candidates']; let list = ['title', 'phase', 'candidates'];
if (this.operator.hasPerms('core.can_manage_projector')) {
list = ['projector'].concat(list);
}
if (this.isMultiSelect) { if (this.isMultiSelect) {
return ['selector'].concat(list); list = ['selector'].concat(list);
} }
return list; return list;
} }

View File

@ -28,11 +28,7 @@
</button> </button>
</div> </div>
<div *osPerms="'core.can_manage_projector'"> <div *osPerms="'core.can_manage_projector'">
<button mat-menu-item> <os-projector-button menuItem=true [object]="poll"></os-projector-button>
<mat-icon>videocam</mat-icon>
<span translate>Project</span>
<!-- os-projector-button ?-->
</button>
</div> </div>
<div *osPerms="'assignments.can_manage'"> <div *osPerms="'assignments.can_manage'">
<mat-divider></mat-divider> <mat-divider></mat-divider>

View File

@ -5,8 +5,9 @@ import { AssignmentPoll } from 'app/shared/models/assignments/assignment-poll';
import { AssignmentPollMethod } from '../services/assignment-poll.service'; import { AssignmentPollMethod } from '../services/assignment-poll.service';
import { ViewAssignmentPollOption } from './view-assignment-poll-option'; import { ViewAssignmentPollOption } from './view-assignment-poll-option';
import { AssignmentPollOption } from 'app/shared/models/assignments/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 _assignmentPoll: AssignmentPoll;
private _assignmentPollOptions: ViewAssignmentPollOption[]; private _assignmentPollOptions: ViewAssignmentPollOption[];
@ -81,6 +82,14 @@ export class ViewAssignmentPoll implements Identifiable, Updateable {
this.options.forEach(option => option.updateDependencies(update)); 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, * Creates a copy with deep-copy on all changing numerical values,
* but intact uncopied references to the users * 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'
};
}
} }

View File

@ -13,7 +13,7 @@ import { ViewAssignmentPoll } from '../models/view-assignment-poll';
type AssignmentPollValues = 'auto' | 'votes' | 'yesnoabstain' | 'yesno'; type AssignmentPollValues = 'auto' | 'votes' | 'yesnoabstain' | 'yesno';
export type AssignmentPollMethod = 'yn' | 'yna' | 'votes'; export type AssignmentPollMethod = 'yn' | 'yna' | 'votes';
type AssignmentPercentBase = 'YES_NO_ABSTAIN' | 'YES_NO' | 'VALID' | 'CAST' | 'DISABLED'; export type AssignmentPercentBase = 'YES_NO_ABSTAIN' | 'YES_NO' | 'VALID' | 'CAST' | 'DISABLED';
/** /**
* Service class for assignment polls. * Service class for assignment polls.

View File

@ -82,6 +82,11 @@ export const allSlidesDynamicConfiguration: (SlideDynamicConfiguration & Slide)[
scaleable: true, scaleable: true,
scrollable: true scrollable: true
}, },
{
slide: 'assignments/poll',
scaleable: true,
scrollable: true
},
{ {
slide: 'mediafiles/mediafile', slide: 'mediafiles/mediafile',
scaleable: true, scaleable: true,

View File

@ -115,6 +115,14 @@ export const allSlides: SlideManifest[] = [
elementIdentifiers: ['name', 'id'], elementIdentifiers: ['name', 'id'],
canBeMappedToModel: true 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', slide: 'mediafiles/mediafile',
path: 'mediafiles/mediafile', path: 'mediafiles/mediafile',

View File

@ -1,3 +1,10 @@
export interface AssignmentSlideData { export interface AssignmentSlideData {
user: string; title: string;
description: string;
phase: number;
open_posts: number;
assignment_related_users: {
user: string;
elected: boolean;
}[];
} }

View File

@ -1,7 +1,8 @@
import { Component } from '@angular/core'; import { Component, Input } from '@angular/core';
import { BaseSlideComponent } from 'app/slides/base-slide-component'; import { BaseSlideComponent } from 'app/slides/base-slide-component';
import { AssignmentSlideData } from './assignment-slide-data'; import { AssignmentSlideData } from './assignment-slide-data';
import { SlideData } from 'app/core/core-services/projector-data.service';
@Component({ @Component({
selector: 'os-assignment-slide', selector: 'os-assignment-slide',
@ -9,6 +10,20 @@ import { AssignmentSlideData } from './assignment-slide-data';
styleUrls: ['./assignment-slide.component.scss'] styleUrls: ['./assignment-slide.component.scss']
}) })
export class AssignmentSlideComponent extends BaseSlideComponent<AssignmentSlideData> { export class AssignmentSlideComponent extends BaseSlideComponent<AssignmentSlideData> {
// 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<AssignmentSlideData>;
@Input()
public set data(data: SlideData<AssignmentSlideData>) {
this._data = data;
console.log('Data: ', data);
}
public get data(): SlideData<AssignmentSlideData> {
return this._data;
}
// UNTIL HERE
public constructor() { public constructor() {
super(); super();
} }

View File

@ -1,13 +1,13 @@
import { AssignmentSlideModule } from './assignment-slide.module'; import { AssignmentSlideModule } from './assignment-slide.module';
describe('UsersUserSlideModule', () => { describe('AssignmentSlideModule', () => {
let usersUserSlideModule: AssignmentSlideModule; let assignmentSlideModule: AssignmentSlideModule;
beforeEach(() => { beforeEach(() => {
usersUserSlideModule = new AssignmentSlideModule(); assignmentSlideModule = new AssignmentSlideModule();
}); });
it('should create an instance', () => { it('should create an instance', () => {
expect(usersUserSlideModule).toBeTruthy(); expect(assignmentSlideModule).toBeTruthy();
}); });
}); });

View File

@ -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;
};
}

View File

@ -0,0 +1,3 @@
<div *ngIf="data">
<h1>TODO</h1>
</div>

View File

@ -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<PollSlideComponent>;
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();
});
});

View File

@ -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<PollSlideData> {
// 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<PollSlideData>;
@Input()
public set data(data: SlideData<PollSlideData>) {
this._data = data;
console.log('Data: ', data);
}
public get data(): SlideData<PollSlideData> {
return this._data;
}
// UNTIL HERE
public constructor() {
super();
}
}

View File

@ -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();
});
});

View File

@ -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 {}

View File

@ -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 # 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. # 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( async def assignment_slide(
all_data: AllData, element: Dict[str, Any], projector_id: int all_data: AllData, element: Dict[str, Any], projector_id: int
) -> Dict[str, Any]: ) -> Dict[str, Any]:
""" """
Assignment slide. Assignment slide.
""" """
poll_id = element.get("tree") # noqa assignment = get_assignment(all_data, element.get("id"))
return {"error": "TODO"}
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: def register_projector_slides() -> None:
register_projector_slide("assignments/assignment", assignment_slide) register_projector_slide("assignments/assignment", assignment_slide)
register_projector_slide("assignments/poll", poll_slide)