Merge pull request #4382 from FinnStutzenstein/agendaSlide

Agenda item list slide
This commit is contained in:
Emanuel Schütze 2019-02-21 22:11:40 +01:00 committed by GitHub
commit deb80ddf37
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 285 additions and 111 deletions

View File

@ -67,7 +67,6 @@ import { SearchValueSelectorComponent } from './components/search-value-selector
import { OpenSlidesDateAdapter } from './date-adapter';
import { PromptDialogComponent } from './components/prompt-dialog/prompt-dialog.component';
import { SortingListComponent } from './components/sorting-list/sorting-list.component';
import { SpeakerListComponent } from 'app/site/agenda/components/speaker-list/speaker-list.component';
import { SortingTreeComponent } from './components/sorting-tree/sorting-tree.component';
import { ChoiceDialogComponent } from './components/choice-dialog/choice-dialog.component';
import { OsSortFilterBarComponent } from './components/os-sort-filter-bar/os-sort-filter-bar.component';
@ -215,7 +214,6 @@ import { CountdownTimeComponent } from './components/contdown-time/countdown-tim
SearchValueSelectorComponent,
PromptDialogComponent,
SortingListComponent,
SpeakerListComponent,
SortingTreeComponent,
ChoiceDialogComponent,
OsSortFilterBarComponent,

View File

@ -4,7 +4,7 @@ import { Routes, RouterModule } from '@angular/router';
import { AgendaImportListComponent } from './components/agenda-import-list/agenda-import-list.component';
import { AgendaListComponent } from './components/agenda-list/agenda-list.component';
import { AgendaSortComponent } from './components/agenda-sort/agenda-sort.component';
import { SpeakerListComponent } from './components/speaker-list/speaker-list.component';
import { ListOfSpeakersComponent } from './components/list-of-speakers/list-of-speakers.component';
import { TopicDetailComponent } from './components/topic-detail/topic-detail.component';
const routes: Routes = [
@ -12,9 +12,9 @@ const routes: Routes = [
{ path: 'import', component: AgendaImportListComponent },
{ path: 'topics/new', component: TopicDetailComponent },
{ path: 'sort-agenda', component: AgendaSortComponent },
{ path: 'speakers', component: SpeakerListComponent },
{ path: 'speakers', component: ListOfSpeakersComponent },
{ path: 'topics/:id', component: TopicDetailComponent },
{ path: ':id/speakers', component: SpeakerListComponent }
{ path: ':id/speakers', component: ListOfSpeakersComponent }
];
@NgModule({

View File

@ -8,6 +8,7 @@ import { AgendaRoutingModule } from './agenda-routing.module';
import { SharedModule } from '../../shared/shared.module';
import { TopicDetailComponent } from './components/topic-detail/topic-detail.component';
import { AgendaSortComponent } from './components/agenda-sort/agenda-sort.component';
import { ListOfSpeakersComponent } from './components/list-of-speakers/list-of-speakers.component';
/**
* AppModule for the agenda and it's children.
@ -20,7 +21,8 @@ import { AgendaSortComponent } from './components/agenda-sort/agenda-sort.compon
TopicDetailComponent,
ItemInfoDialogComponent,
AgendaImportListComponent,
AgendaSortComponent
AgendaSortComponent,
ListOfSpeakersComponent
]
})
export class AgendaModule {}

View File

@ -127,6 +127,10 @@
<span translate>Sort</span>
</button>
</div>
<!-- Project agenda -->
<os-projector-button [object]="itemListSlide" [menuItem]="true"></os-projector-button>
<!-- Current list of speakers -->
<button mat-menu-item routerLink="speakers">
<mat-icon>mic</mat-icon>

View File

@ -18,6 +18,7 @@ import { PromptService } from 'app/core/ui-services/prompt.service';
import { PdfDocumentService } from 'app/core/ui-services/pdf-document.service';
import { ViewportService } from 'app/core/ui-services/viewport.service';
import { ViewItem } from '../../models/view-item';
import { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable';
/**
* List view for the agenda.
@ -49,6 +50,22 @@ export class AgendaListComponent extends ListViewBaseComponent<ViewItem, Item> i
return this.operator.hasPerms('agenda.can_manage');
}
public itemListSlide: ProjectorElementBuildDeskriptor = {
getBasicProjectorElement: options => ({
name: 'agenda/item-list',
getIdentifiers: () => ['name']
}),
slideOptions: [
{
key: 'only_main_items',
displayName: 'Only main agenda items',
default: false
}
],
projectionDefaultName: 'agenda_all_items',
getDialogTitle: () => this.translate.instant('Item list')
};
/**
* The usual constructor for components
* @param titleService Setting the browser tab title

View File

@ -1,20 +1,21 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SpeakerListComponent } from './speaker-list.component';
import { ListOfSpeakersComponent } from './list-of-speakers.component';
import { E2EImportsModule } from '../../../../../e2e-imports.module';
describe('SpeakerListComponent', () => {
let component: SpeakerListComponent;
let fixture: ComponentFixture<SpeakerListComponent>;
describe('ListOfSpeakersComponent', () => {
let component: ListOfSpeakersComponent;
let fixture: ComponentFixture<ListOfSpeakersComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [E2EImportsModule]
imports: [E2EImportsModule],
declarations: [ListOfSpeakersComponent]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SpeakerListComponent);
fixture = TestBed.createComponent(ListOfSpeakersComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

View File

@ -25,11 +25,11 @@ import { DurationService } from 'app/core/ui-services/duration.service';
* The list of speakers for agenda items.
*/
@Component({
selector: 'os-speaker-list',
templateUrl: './speaker-list.component.html',
styleUrls: ['./speaker-list.component.scss']
selector: 'os-list-of-speakers',
templateUrl: './list-of-speakers.component.html',
styleUrls: ['./list-of-speakers.component.scss']
})
export class SpeakerListComponent extends BaseViewComponent implements OnInit {
export class ListOfSpeakersComponent extends BaseViewComponent implements OnInit {
/**
* Determine if the user is viewing the current list if speakers
*/

View File

@ -11,7 +11,6 @@ import { MotionCommentSectionListComponent } from './components/motion-comment-s
import { MotionDetailComponent } from './components/motion-detail/motion-detail.component';
import { MotionImportListComponent } from './components/motion-import-list/motion-import-list.component';
import { MotionListComponent } from './components/motion-list/motion-list.component';
import { SpeakerListComponent } from '../agenda/components/speaker-list/speaker-list.component';
import { StatuteImportListComponent } from './components/statute-paragraph-list/statute-import-list/statute-import-list.component';
import { StatuteParagraphListComponent } from './components/statute-paragraph-list/statute-paragraph-list.component';
import { WorkflowListComponent } from './components/workflow-list/workflow-list.component';
@ -32,7 +31,6 @@ const routes: Routes = [
{ path: 'workflow', component: WorkflowListComponent },
{ path: 'workflow/:id', component: WorkflowDetailComponent },
{ path: ':id', component: MotionDetailComponent },
{ path: ':id/speakers', component: SpeakerListComponent },
{ path: ':id/create-amendment', component: AmendmentCreateWizardComponent }
];

View File

@ -0,0 +1,10 @@
export interface SlideItem {
item_number: string;
title_information: object;
collection: string;
depth: number;
}
export interface ItemListSlideData {
items: SlideItem[];
}

View File

@ -0,0 +1,13 @@
<div *ngIf="data">
<h1 translate>Agenda</h1>
<div
*ngFor="let item of data.data.items"
[ngStyle]="getItemStyle(item)"
[ngClass]="item.depth === 0 ? 'mainitem' : 'subitem'"
class="item"
>
<span *ngIf="item.item_number">{{ item.item_number }} &middot;</span>
{{ getTitle(item) }}
</div>
</div>

View File

@ -0,0 +1,10 @@
.item {
line-height: 34px;
}
.mainitem {
font-size: 110%;
}
.subitem {
font-size: 90%;
}

View File

@ -1,21 +1,21 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { UsersUserSlideComponent } from './users-user-slide.component';
import { ItemListSlideComponent } from './item-list-slide.component';
import { E2EImportsModule } from '../../../../e2e-imports.module';
describe('UsersUserSlideComponent', () => {
let component: UsersUserSlideComponent;
let fixture: ComponentFixture<UsersUserSlideComponent>;
describe('ItemListSlideComponent', () => {
let component: ItemListSlideComponent;
let fixture: ComponentFixture<ItemListSlideComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [E2EImportsModule],
declarations: [UsersUserSlideComponent]
declarations: [ItemListSlideComponent]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(UsersUserSlideComponent);
fixture = TestBed.createComponent(ItemListSlideComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

View File

@ -0,0 +1,31 @@
import { Component } from '@angular/core';
import { BaseSlideComponent } from 'app/slides/base-slide-component';
import { ItemListSlideData, SlideItem } from './item-list-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-item-list-slide',
templateUrl: './item-list-slide.component.html',
styleUrls: ['./item-list-slide.component.scss']
})
export class ItemListSlideComponent extends BaseSlideComponent<ItemListSlideData> {
public constructor(private collectionStringMapperService: CollectionStringMapperService) {
super();
}
public getTitle(item: SlideItem): string {
const repo = this.collectionStringMapperService.getRepository(item.collection);
if (isBaseAgendaContentObjectRepository(repo)) {
return repo.getAgendaTitle(item.title_information);
} else {
throw new Error('The content object has no agenda based repository!');
}
}
public getItemStyle(item: SlideItem): object {
return {
'margin-left': 20 * item.depth + 'px'
};
}
}

View File

@ -0,0 +1,13 @@
import { ItemListSlideModule } from './item-list-slide.module';
describe('ItemListSlideModule', () => {
let itemListSlideModule: ItemListSlideModule;
beforeEach(() => {
itemListSlideModule = new ItemListSlideModule();
});
it('should create an instance', () => {
expect(itemListSlideModule).toBeTruthy();
});
});

View File

@ -0,0 +1,7 @@
import { NgModule } from '@angular/core';
import { makeSlideModule } from 'app/slides/base-slide-module';
import { ItemListSlideComponent } from './item-list-slide.component';
@NgModule(makeSlideModule(ItemListSlideComponent))
export class ItemListSlideModule {}

View File

@ -5,6 +5,11 @@ import { ViewModelStoreService } from 'app/core/core-services/view-model-store.s
import { ProjectorElement } from 'app/shared/models/core/projector';
export const allSlidesDynamicConfiguration: (SlideDynamicConfiguration & Slide)[] = [
{
slide: 'agenda/item-list',
scaleable: true,
scrollable: true
},
{
slide: 'topics/topic',
scaleable: true,

View File

@ -8,10 +8,18 @@ import { SlideManifest } from './slide-manifest';
* is no such thing as "dynamic update" in this case..
*/
export const allSlides: SlideManifest[] = [
{
slide: 'agenda/item-list',
path: 'agenda/item-list',
loadChildren: './slides/agenda/item-list/item-list-slide.module#ItemListSlideModule',
verboseName: 'Agenda',
elementIdentifiers: ['name'],
canBeMappedToModel: false
},
{
slide: 'topics/topic',
path: 'topics/topic',
loadChildren: './slides/agenda/topic/topic-slide.module#TopicSlideModule',
loadChildren: './slides/topics/topic/topic-slide.module#TopicSlideModule',
verboseName: 'Topic',
elementIdentifiers: ['name', 'id'],
canBeMappedToModel: true
@ -35,7 +43,7 @@ export const allSlides: SlideManifest[] = [
{
slide: 'users/user',
path: 'users/user',
loadChildren: './slides/users/user/users-user-slide.module#UsersUserSlideModule',
loadChildren: './slides/users/user/user-slide.module#UserSlideModule',
verboseName: 'Participant',
elementIdentifiers: ['name', 'id'],
canBeMappedToModel: true

View File

@ -0,0 +1,3 @@
export interface UserSlideData {
user: string;
}

View File

@ -0,0 +1,26 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { UserSlideComponent } from './user-slide.component';
import { E2EImportsModule } from '../../../../e2e-imports.module';
describe('UserSlideComponent', () => {
let component: UserSlideComponent;
let fixture: ComponentFixture<UserSlideComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [E2EImportsModule],
declarations: [UserSlideComponent]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(UserSlideComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,13 @@
import { Component } from '@angular/core';
import { BaseSlideComponent } from 'app/slides/base-slide-component';
import { UserSlideData } from './user-slide-data';
@Component({
selector: 'os-user-slide',
templateUrl: './user-slide.component.html'
})
export class UserSlideComponent extends BaseSlideComponent<UserSlideData> {
public constructor() {
super();
}
}

View File

@ -0,0 +1,13 @@
import { UserSlideModule } from './user-slide.module';
describe('UserSlideModule', () => {
let userSlideModule: UserSlideModule;
beforeEach(() => {
userSlideModule = new UserSlideModule();
});
it('should create an instance', () => {
expect(userSlideModule).toBeTruthy();
});
});

View File

@ -0,0 +1,7 @@
import { NgModule } from '@angular/core';
import { makeSlideModule } from 'app/slides/base-slide-module';
import { UserSlideComponent } from './user-slide.component';
@NgModule(makeSlideModule(UserSlideComponent))
export class UserSlideModule {}

View File

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

View File

@ -1,14 +0,0 @@
import { Component } from '@angular/core';
import { BaseSlideComponent } from 'app/slides/base-slide-component';
import { UsersUserSlideData } from './users-user-slide-data';
@Component({
selector: 'os-users-user-slide',
templateUrl: './users-user-slide.component.html',
styleUrls: ['./users-user-slide.component.scss']
})
export class UsersUserSlideComponent extends BaseSlideComponent<UsersUserSlideData> {
public constructor() {
super();
}
}

View File

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

View File

@ -1,7 +0,0 @@
import { NgModule } from '@angular/core';
import { makeSlideModule } from 'app/slides/base-slide-module';
import { UsersUserSlideComponent } from './users-user-slide.component';
@NgModule(makeSlideModule(UsersUserSlideComponent))
export class UsersUserSlideModule {}

View File

@ -1,5 +1,5 @@
from collections import defaultdict
from typing import Any, Dict, List, Tuple
from typing import Any, Dict, List
from ..users.projector import get_user_name
from ..utils.projector import (
@ -16,9 +16,7 @@ from ..utils.projector import (
# to be fast!
def get_tree(
all_data: AllData, parent_id: int = 0
) -> List[Tuple[int, List[Tuple[int, List[Any]]]]]:
def get_flat_tree(all_data: AllData, parent_id: int = 0) -> List[Dict[str, Any]]:
"""
Build the item tree from all_data.
@ -36,35 +34,51 @@ def get_tree(
if item["type"] == 1: # only normal items
children[item["parent_id"] or 0].append(item["id"])
def get_children(
item_ids: List[int]
) -> List[Tuple[int, List[Tuple[int, List[Any]]]]]:
return [
(all_data["agenda/item"][item_id]["title"], get_children(children[item_id]))
for item_id in item_ids
]
tree = []
return get_children(children[parent_id])
def get_children(item_ids: List[int], depth: int) -> None:
for item_id in item_ids:
tree.append(
{
"item_number": all_data["agenda/item"][item_id]["item_number"],
"title_information": all_data["agenda/item"][item_id][
"title_information"
],
"collection": all_data["agenda/item"][item_id]["content_object"][
"collection"
],
"depth": depth,
}
)
get_children(children[item_id], depth + 1)
get_children(children[parent_id], 0)
return tree
def items_slide(all_data: AllData, element: Dict[str, Any]) -> Dict[str, Any]:
def item_list_slide(all_data: AllData, element: Dict[str, Any]) -> Dict[str, Any]:
"""
Item list slide.
Returns all root items or all children of an item.
"""
root_item_id = element.get("id") or None
show_tree = element.get("tree") or False
only_main_items = element.get("only_main_items", True)
if show_tree:
agenda_items = get_tree(all_data, root_item_id or 0)
else:
if only_main_items:
agenda_items = []
for item in sorted(
all_data["agenda/item"].values(), key=lambda item: item["weight"]
):
if item["parent_id"] == root_item_id and item["type"] == 1:
agenda_items.append(item["title"])
if item["parent_id"] is None and item["type"] == 1:
agenda_items.append(
{
"item_number": item["item_number"],
"title_information": item["title_information"],
"collection": item["content_object"]["collection"],
}
)
else:
agenda_items = get_flat_tree(all_data)
return {"items": agenda_items}
@ -144,7 +158,7 @@ def current_list_of_speakers_slide(
def register_projector_slides() -> None:
register_projector_slide("agenda/item-list", items_slide)
register_projector_slide("agenda/item-list", item_list_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

View File

@ -12,8 +12,7 @@ def all_data():
1: {
"id": 1,
"item_number": "",
"title": "Item1",
"title_with_type": "Item1",
"title_information": {"title": "item1"},
"comment": None,
"closed": False,
"type": 1,
@ -31,6 +30,7 @@ def all_data():
"item_number": "",
"title": "item2",
"title_with_type": "item2",
"title_information": {"title": "item2"},
"comment": None,
"closed": False,
"type": 1,
@ -49,6 +49,7 @@ def all_data():
"item_number": "",
"title": "item3",
"title_with_type": "item3",
"title_information": {"title": "item3"},
"comment": None,
"closed": True,
"type": 2,
@ -65,8 +66,7 @@ def all_data():
4: {
"id": 4,
"item_number": "",
"title": "item4",
"title_with_type": "item4",
"title_information": {"title": "item4"},
"comment": None,
"closed": True,
"type": 1,
@ -85,33 +85,51 @@ def all_data():
return all_data
def test_items(all_data):
def test_main_items(all_data):
element: Dict[str, Any] = {}
data = projector.items_slide(all_data, element)
data = projector.item_list_slide(all_data, element)
assert data == {"items": ["Item1", "item2"]}
assert data == {
"items": [
{
"collection": "topics/topic",
"item_number": "",
"title_information": {"title": "item1"},
},
{
"collection": "topics/topic",
"item_number": "",
"title_information": {"title": "item2"},
},
]
}
def test_items_parent(all_data):
element: Dict[str, Any] = {"id": 1}
def test_all_items(all_data):
element: Dict[str, Any] = {"only_main_items": False}
data = projector.items_slide(all_data, element)
data = projector.item_list_slide(all_data, element)
assert data == {"items": ["item4"]}
def test_items_tree(all_data):
element: Dict[str, Any] = {"tree": True}
data = projector.items_slide(all_data, element)
assert data == {"items": [("Item1", [("item4", [])]), ("item2", [])]}
def test_items_tree_parent(all_data):
element: Dict[str, Any] = {"tree": True, "id": 1}
data = projector.items_slide(all_data, element)
assert data == {"items": [("item4", [])]}
assert data == {
"items": [
{
"collection": "topics/topic",
"depth": 0,
"item_number": "",
"title_information": {"title": "item1"},
},
{
"collection": "topics/topic",
"depth": 1,
"item_number": "",
"title_information": {"title": "item4"},
},
{
"collection": "topics/topic",
"depth": 0,
"item_number": "",
"title_information": {"title": "item2"},
},
]
}