Merge pull request #4380 from FinnStutzenstein/motionBlockSlide

Motion block slide
This commit is contained in:
Emanuel Schütze 2019-02-21 15:23:42 +01:00 committed by GitHub
commit c0cd3bc252
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 267 additions and 88 deletions

View File

@ -3,7 +3,7 @@
[ngClass]="isProjected() ? 'projector-active' : 'projector-inactive'">
<mat-icon>videocam</mat-icon>
</button>
<button type="button" *ngIf="menuItem" mat-menu-item (click)="onClick($event)"
<button type="button" *ngIf="menuItem" mat-menu-item (click)="onClick()"
[ngClass]="isProjected() ? 'projector-active' : 'projector-inactive'">
<mat-icon>videocam</mat-icon>
<span translate>Project</span>

View File

@ -63,8 +63,10 @@ export class ProjectorButtonComponent implements OnInit {
*
* @param event the click event
*/
public onClick(event: Event): void {
public onClick(event?: Event): void {
if (event) {
event.stopPropagation();
}
if (this.object) {
this.projectionDialogService.openProjectDialogFor(this.object);
}

View File

@ -21,7 +21,7 @@
background-repeat: no-repeat;
background-size: 100% 100%;
margin-bottom: 20px;
z-index: 1;
z-index: 9;
.projector-logo-main {
height: 50px;
@ -59,7 +59,7 @@
width: 100%;
height: 35px;
bottom: 0;
z-index: 1;
z-index: 9;
.footertext {
font-size: 16px;

View File

@ -104,10 +104,8 @@
<span translate>List of speakers</span>
</button>
<button mat-menu-item *osPerms="'core.can_manage_projector'">
<mat-icon>videocam</mat-icon>
<span translate>Project</span>
</button>
<os-projector-button *ngIf="block" [object]="block" [menuItem]="true"></os-projector-button>
<div *osPerms="['motions.can_manage', 'motions.can_manage_metadata']">
<button mat-menu-item (click)="toggleEditMode()">
<mat-icon>edit</mat-icon>

View File

@ -52,6 +52,14 @@
<!-- Table -->
<mat-card class="os-card">
<table class="os-headed-listview-table on-transition-fade" mat-table [dataSource]="dataSource" matSort>
<!-- Projector column -->
<ng-container matColumnDef="projector">
<mat-header-cell *matHeaderCellDef mat-sort-header>Projector</mat-header-cell>
<mat-cell *matCellDef="let block">
<os-projector-button [object]="block"></os-projector-button>
</mat-cell>
</ng-container>
<!-- title column -->
<ng-container matColumnDef="title">
<mat-header-cell *matHeaderCellDef mat-sort-header> <span translate>Title</span> </mat-header-cell>

View File

@ -122,7 +122,11 @@ export class MotionBlockListComponent extends ListViewBaseComponent<ViewMotionBl
* @returns an array of strings building the column definition
*/
public getColumnDefinition(): string[] {
return ['title', 'amount', 'menu'];
let columns = ['title', 'amount', 'menu'];
if (this.operator.hasPerms('core.can_manage_projector')) {
columns = ['projector'].concat(columns);
}
return columns;
}
/**

View File

@ -41,7 +41,7 @@ import { LinenumberingService } from 'app/core/ui-services/linenumbering.service
import { Tag } from 'app/shared/models/core/tag';
import { UserRepositoryService } from 'app/core/repositories/users/user-repository.service';
import { ViewMotionBlock } from '../../models/view-motion-block';
import { ViewWorkflow } from '../../models/view-workflow';
import { ViewWorkflow, StateCssClassMapping } from '../../models/view-workflow';
import { ViewUser } from 'app/site/users/models/view-user';
import { ViewCategory } from '../../models/view-category';
import { ViewMediafile } from 'app/site/mediafiles/models/view-mediafile';
@ -1280,17 +1280,6 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit {
return '';
}
switch (this.motion.state.css_class) {
case 'success':
return 'green';
case 'danger':
return 'red';
case 'default':
return 'grey';
case 'primary':
return 'lightblue';
default:
return '';
}
return StateCssClassMapping[this.motion.state.css_class] || '';
}
}

View File

@ -41,7 +41,7 @@ export class MotionListComponent extends ListViewBaseComponent<ViewMotion, Motio
/**
* Columns to display in table when desktop view is available
*/
public displayedColumnsDesktop: string[] = ['identifier', 'title', 'state', 'speakers'];
public displayedColumnsDesktop: string[] = ['identifier', 'title', 'state'];
/**
* Columns to display in table when mobile view is available
@ -237,6 +237,9 @@ export class MotionListComponent extends ListViewBaseComponent<ViewMotion, Motio
if (this.isMultiSelect) {
columns = ['selector'].concat(columns);
}
if (this.operator.hasPerms('agenda.can_see')) {
columns = columns.concat(['speakers']);
}
return columns;
}

View File

@ -82,6 +82,15 @@ export class ViewMotionBlock extends BaseAgendaViewModel implements Searchable {
};
public getSlide(): ProjectorElementBuildDeskriptor {
throw new Error('todo');
return {
getBasicProjectorElement: options => ({
name: MotionBlock.COLLECTIONSTRING,
id: this.id,
getIdentifiers: () => ['name', 'id']
}),
slideOptions: [],
projectionDefaultName: 'motionBlocks',
getDialogTitle: () => this.getTitle()
};
}
}

View File

@ -2,6 +2,13 @@ import { Workflow } from 'app/shared/models/motions/workflow';
import { WorkflowState } from 'app/shared/models/motions/workflow-state';
import { BaseViewModel } from '../../base/base-view-model';
export const StateCssClassMapping = {
success: 'green',
danger: 'red',
default: 'grey',
primary: 'lightblue'
};
/**
* class for the ViewWorkflow.
* @ignore

View File

@ -15,6 +15,11 @@ export const allSlidesDynamicConfiguration: (SlideDynamicConfiguration & Slide)[
scaleable: true,
scrollable: true
},
{
slide: 'motions/motion-block',
scaleable: true,
scrollable: true
},
{
slide: 'users/user',
scaleable: true,

View File

@ -19,11 +19,19 @@ export const allSlides: SlideManifest[] = [
{
slide: 'motions/motion',
path: 'motions/motion',
loadChildren: './slides/motions/motion/motions-motion-slide.module#MotionsMotionSlideModule',
loadChildren: './slides/motions/motion/motion-slide.module#MotionSlideModule',
verboseName: 'Motion',
elementIdentifiers: ['name', 'id'],
canBeMappedToModel: true
},
{
slide: 'motions/motion-block',
path: 'motions/motion-block',
loadChildren: './slides/motions/motion-block/motion-block-slide.module#MotionBlockSlideModule',
verboseName: 'Motion block',
elementIdentifiers: ['name', 'id'],
canBeMappedToModel: true
},
{
slide: 'users/user',
path: 'users/user',

View File

@ -0,0 +1,14 @@
export interface MotionBlockSlideMotionRepresentation {
title: string;
identifier?: string;
recommendation?: {
name: string;
css_class: string;
};
recommendation_extension?: string;
}
export interface MotionBlockSlideData {
title: string;
motions: MotionBlockSlideMotionRepresentation[];
}

View File

@ -0,0 +1,17 @@
<div *ngIf="data">
<div class="slidetitle">
<h1>{{ data.data.title }}</h1>
<h2><span translate>Motion block</span> {{ data.data.motions.length }} <span translate>motions</span></h2>
</div>
<div *ngFor="let motion of data.data.motions">
<div class="ellipsis-overflow">
{{ motion.identifier }}: {{ motion.title }}
</div>
<div class="white ellipsis-overflow">
<mat-basic-chip *ngIf="motion.recommendation" disableRipple [ngClass]="getStateCssColor(motion)">
{{ getRecommendationLabel(motion) }}
</mat-basic-chip>
</div>
</div>
</div>

View File

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

View File

@ -0,0 +1,35 @@
import { Component } from '@angular/core';
import { BaseSlideComponent } from 'app/slides/base-slide-component';
import { MotionBlockSlideData, MotionBlockSlideMotionRepresentation } from './motion-block-slide-data';
import { Motion } from 'app/shared/models/motions/motion';
import { MotionRepositoryService } from 'app/core/repositories/motions/motion-repository.service';
import { StateCssClassMapping } from 'app/site/motions/models/view-workflow';
import { TranslateService } from '@ngx-translate/core';
@Component({
selector: 'os-motion-block-slide',
templateUrl: './motion-block-slide.component.html',
styleUrls: ['./motion-block-slide.component.scss']
})
export class MotionBlockSlideComponent extends BaseSlideComponent<MotionBlockSlideData> {
public constructor(private motionRepo: MotionRepositoryService, private translate: TranslateService) {
super();
}
public getMotionTitle(motion: Partial<Motion>): string {
return this.motionRepo.getAgendaTitle(motion);
}
public getStateCssColor(motion: MotionBlockSlideMotionRepresentation): string {
return StateCssClassMapping[motion.recommendation.css_class] || '';
}
public getRecommendationLabel(motion: MotionBlockSlideMotionRepresentation): string {
let recommendation = this.translate.instant(motion.recommendation.name);
if (motion.recommendation_extension) {
recommendation += ' ' + this.motionRepo.solveExtensionPlaceHolder(motion.recommendation_extension);
}
return recommendation;
}
}

View File

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

View File

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

View File

@ -5,7 +5,7 @@ import { MergeAmendment } from '../../../shared/models/motions/workflow-state';
* This interface describes the data returned by the server about an amendment.
* This object is used if actually the motion is shown and the amendment is shown in the context of the motion.
*/
export interface MotionsMotionSlideDataAmendment {
export interface MotionSlideDataAmendment {
id: number;
title: string;
amendment_paragraphs: string[];
@ -16,7 +16,7 @@ export interface MotionsMotionSlideDataAmendment {
* This interface describes the data returned by the server about a motion that is changed by an amendment.
* It only contains the data necessary for rendering the amendment's diff.
*/
export interface MotionsMotionSlideDataBaseMotion {
export interface MotionSlideDataBaseMotion {
identifier: string;
title: string;
text: string;
@ -26,7 +26,7 @@ export interface MotionsMotionSlideDataBaseMotion {
* This interface describes the data returned by the server about a statute paragraph that is changed by an amendment.
* It only contains the data necessary for rendering the amendment's diff.
*/
export interface MotionsMotionSlideDataBaseStatute {
export interface MotionSlideDataBaseStatute {
title: string;
text: string;
}
@ -34,7 +34,7 @@ export interface MotionsMotionSlideDataBaseStatute {
/**
* This interface describes the data returned by the server about a change recommendation.
*/
export interface MotionsMotionSlideDataChangeReco {
export interface MotionSlideDataChangeReco {
creation_time: string;
id: number;
internal: boolean;
@ -53,7 +53,7 @@ export interface MotionsMotionSlideDataChangeReco {
* This interface describes either an motion (with all amendments and change recommendations enbedded)
* or an amendment (with the bas motion embedded).
*/
export interface MotionsMotionSlideData {
export interface MotionSlideData {
identifier: string;
title: string;
preamble: string;
@ -65,11 +65,11 @@ export interface MotionsMotionSlideData {
recommender?: string;
recommendation?: string;
recommendation_extension?: string;
base_motion?: MotionsMotionSlideDataBaseMotion;
base_statute?: MotionsMotionSlideDataBaseStatute;
base_motion?: MotionSlideDataBaseMotion;
base_statute?: MotionSlideDataBaseStatute;
amendment_paragraphs: string[];
change_recommendations: MotionsMotionSlideDataChangeReco[];
amendments: MotionsMotionSlideDataAmendment[];
change_recommendations: MotionSlideDataChangeReco[];
amendments: MotionSlideDataAmendment[];
modified_final_version?: string;
line_length: number;
line_numbering_mode: LineNumberingMode;

View File

@ -1,5 +1,5 @@
import { ViewUnifiedChange, ViewUnifiedChangeType } from '../../../shared/models/motions/view-unified-change';
import { MotionsMotionSlideDataAmendment } from './motions-motion-slide-data';
import { MotionSlideDataAmendment } from './motion-slide-data';
import { MergeAmendment } from '../../../shared/models/motions/workflow-state';
import { LineRange } from '../../../core/ui-services/diff.service';
@ -7,13 +7,13 @@ import { LineRange } from '../../../core/ui-services/diff.service';
* This class adds methods to the MotionsMotionSlideDataChangeReco data object
* necessary for use it as a UnifiedChange in the Diff-Functions
*/
export class MotionsMotionSlideObjAmendmentParagraph implements ViewUnifiedChange {
export class MotionSlideObjAmendmentParagraph implements ViewUnifiedChange {
public id: number;
public type: number;
public merge_amendment_into_final: MergeAmendment;
public constructor(
data: MotionsMotionSlideDataAmendment,
data: MotionSlideDataAmendment,
private paragraphNo: number,
private newText: string,
private lineRange: LineRange

View File

@ -1,11 +1,11 @@
import { ViewUnifiedChange, ViewUnifiedChangeType } from '../../../shared/models/motions/view-unified-change';
import { MotionsMotionSlideDataChangeReco } from './motions-motion-slide-data';
import { MotionSlideDataChangeReco } from './motion-slide-data';
/**
* This class adds methods to the MotionsMotionSlideDataChangeReco data object
* necessary for use it as a UnifiedChange in the Diff-Functions
*/
export class MotionsMotionSlideObjChangeReco implements MotionsMotionSlideDataChangeReco, ViewUnifiedChange {
export class MotionSlideObjChangeReco implements MotionSlideDataChangeReco, ViewUnifiedChange {
public creation_time: string;
public id: number;
public internal: boolean;
@ -17,7 +17,7 @@ export class MotionsMotionSlideObjChangeReco implements MotionsMotionSlideDataCh
public text: string;
public type: number;
public constructor(data: MotionsMotionSlideDataChangeReco) {
public constructor(data: MotionSlideDataChangeReco) {
Object.assign(this, data);
}

View File

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

View File

@ -2,21 +2,21 @@ import { Component, Input } from '@angular/core';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
import { BaseSlideComponent } from 'app/slides/base-slide-component';
import { MotionsMotionSlideData, MotionsMotionSlideDataAmendment } from './motions-motion-slide-data';
import { MotionSlideData, MotionSlideDataAmendment } from './motion-slide-data';
import { ChangeRecoMode, LineNumberingMode } from '../../../site/motions/models/view-motion';
import { DiffLinesInParagraph, DiffService, LineRange } from '../../../core/ui-services/diff.service';
import { LinenumberingService } from '../../../core/ui-services/linenumbering.service';
import { ViewUnifiedChange } from '../../../shared/models/motions/view-unified-change';
import { MotionsMotionSlideObjChangeReco } from './motions-motion-slide-obj-change-reco';
import { MotionSlideObjChangeReco } from './motion-slide-obj-change-reco';
import { SlideData } from '../../../site/projector/services/projector-data.service';
import { MotionsMotionSlideObjAmendmentParagraph } from './motions-motion-slide-obj-amendment-paragraph';
import { MotionSlideObjAmendmentParagraph } from './motion-slide-obj-amendment-paragraph';
@Component({
selector: 'os-motions-motion-slide',
templateUrl: './motions-motion-slide.component.html',
styleUrls: ['./motions-motion-slide.component.scss']
selector: 'os-motion-slide',
templateUrl: './motion-slide.component.html',
styleUrls: ['./motion-slide.component.scss']
})
export class MotionsMotionSlideComponent extends BaseSlideComponent<MotionsMotionSlideData> {
export class MotionSlideComponent extends BaseSlideComponent<MotionSlideData> {
/**
* Indicates the LineNumberingMode Mode.
*/
@ -48,10 +48,10 @@ export class MotionsMotionSlideComponent extends BaseSlideComponent<MotionsMotio
*/
public allChangingObjects: ViewUnifiedChange[];
private _data: SlideData<MotionsMotionSlideData>;
private _data: SlideData<MotionSlideData>;
@Input()
public set data(value: SlideData<MotionsMotionSlideData>) {
public set data(value: SlideData<MotionSlideData>) {
this._data = value;
this.lnMode = value.data.line_numbering_mode;
this.lineLength = value.data.line_length;
@ -62,7 +62,7 @@ export class MotionsMotionSlideComponent extends BaseSlideComponent<MotionsMotio
this.recalcUnifiedChanges();
}
public get data(): SlideData<MotionsMotionSlideData> {
public get data(): SlideData<MotionSlideData> {
return this._data;
}
@ -77,19 +77,17 @@ export class MotionsMotionSlideComponent extends BaseSlideComponent<MotionsMotio
/**
* Returns all paragraphs that are affected by the given amendment as unified change objects.
*
* @param {MotionsMotionSlideDataAmendment} amendment
* @returns {MotionsMotionSlideObjAmendmentParagraph[]}
* @param {MotionSlideDataAmendment} amendment
* @returns {MotionSlideObjAmendmentParagraph[]}
*/
public getAmendmentAmendedParagraphs(
amendment: MotionsMotionSlideDataAmendment
): MotionsMotionSlideObjAmendmentParagraph[] {
public getAmendmentAmendedParagraphs(amendment: MotionSlideDataAmendment): MotionSlideObjAmendmentParagraph[] {
let baseHtml = this.data.data.text;
baseHtml = this.lineNumbering.insertLineNumbers(baseHtml, this.lineLength);
const baseParagraphs = this.lineNumbering.splitToParagraphs(baseHtml);
return amendment.amendment_paragraphs
.map(
(newText: string, paraNo: number): MotionsMotionSlideObjAmendmentParagraph => {
(newText: string, paraNo: number): MotionSlideObjAmendmentParagraph => {
if (newText === null) {
return null;
}
@ -114,10 +112,10 @@ export class MotionsMotionSlideComponent extends BaseSlideComponent<MotionsMotio
this.diff.extractRangeByLineNumbers(newTextLines, affectedLines.from, affectedLines.to)
);
return new MotionsMotionSlideObjAmendmentParagraph(amendment, paraNo, newTextLines, affectedLines);
return new MotionSlideObjAmendmentParagraph(amendment, paraNo, newTextLines, affectedLines);
}
)
.filter((para: MotionsMotionSlideObjAmendmentParagraph) => para !== null);
.filter((para: MotionSlideObjAmendmentParagraph) => para !== null);
}
/**
@ -129,7 +127,7 @@ export class MotionsMotionSlideComponent extends BaseSlideComponent<MotionsMotio
if (this.data.data.change_recommendations) {
this.data.data.change_recommendations.forEach(change => {
this.allChangingObjects.push(new MotionsMotionSlideObjChangeReco(change));
this.allChangingObjects.push(new MotionSlideObjChangeReco(change));
});
}
if (this.data.data.amendments) {

View File

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

View File

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

View File

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

View File

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

View File

@ -617,7 +617,7 @@ button.mat-menu-item.selected {
margin-bottom: 40px;
h1 {
font-size: 2em;
font-size: 1.8em;
line-height: 1.1em;
margin-bottom: 0;
padding-bottom: 0;

View File

@ -206,9 +206,45 @@ def motion_slide(all_data: AllData, element: Dict[str, Any]) -> Dict[str, Any]:
def motion_block_slide(all_data: AllData, element: Dict[str, Any]) -> Dict[str, Any]:
"""
Motion slide.
Motion block slide.
"""
return {"error": "TODO"}
motion_block_id = element.get("id")
if motion_block_id is None:
raise ProjectorElementException("id is required for motion block slide")
try:
motion_block = all_data["motions/motion-block"][motion_block_id]
except KeyError:
raise ProjectorElementException(
f"motion block with id {motion_block_id} does not exist"
)
motions = []
for motion in all_data["motions/motion"].values():
if motion["motion_block_id"] == motion_block_id:
motion_object = {
"title": motion["title"],
"identifier": motion["identifier"],
}
recommendation_id = motion["recommendation_id"]
if recommendation_id is not None:
recommendation = get_state(
all_data, motion, motion["recommendation_id"]
)
motion_object["recommendation"] = {
"name": recommendation["recommendation_label"],
"css_class": recommendation["css_class"],
}
if recommendation["show_recommendation_extension_field"]:
motion_object["recommendation_extension"] = motion[
"recommendation_extension"
]
motions.append(motion_object)
return {"title": motion_block["title"], "motions": motions}
def register_projector_slides() -> None: