Basic assignment and poll slide
This commit is contained in:
parent
d0c6fd1dd1
commit
55dd2d1d6f
@ -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',
|
||||||
|
@ -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>
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
@ -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'
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
|
@ -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,
|
||||||
|
@ -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',
|
||||||
|
@ -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;
|
||||||
|
}[];
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
17
client/src/app/slides/assignments/poll/poll-slide-data.ts
Normal file
17
client/src/app/slides/assignments/poll/poll-slide-data.ts
Normal 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;
|
||||||
|
};
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
<div *ngIf="data">
|
||||||
|
<h1>TODO</h1>
|
||||||
|
</div>
|
@ -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();
|
||||||
|
});
|
||||||
|
});
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
});
|
||||||
|
});
|
@ -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 {}
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user