Merge pull request #4923 from tsiegleauq/motion-block-detail-layout

Unify motion block detail
This commit is contained in:
Emanuel Schütze 2019-08-15 14:57:45 +02:00 committed by GitHub
commit bfccf4cd2b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 211 additions and 132 deletions

View File

@ -13,70 +13,79 @@
<mat-icon>more_vert</mat-icon> <mat-icon>more_vert</mat-icon>
</button> </button>
</div> </div>
<!-- Extra Controls -->
<div class="extra-controls-slot" *ngIf="!vp.isMobile">
<button
*osPerms="['motions.can_manage', 'motions.can_manage_metadata']"
mat-button
(click)="onFollowRecButton()"
[disabled]="isFollowingProhibited()"
>
<os-icon-container icon="done_all">
<span translate>Follow recommendations for all motions</span>
</os-icon-container>
</button>
</div>
</os-head-bar> </os-head-bar>
<mat-card> <os-list-view-table
<button [repo]="motionRepo"
*osPerms="['motions.can_manage', 'motions.can_manage_metadata']" [filterService]="filterService"
mat-raised-button [columns]="tableColumnDefinition"
color="primary" [filterProps]="filterProps"
(click)="onFollowRecButton()" (dataSourceChange)="onDataSourceChange($event)"
[disabled]="isFollowingProhibited()" >
> <!-- Title column -->
<mat-icon>done_all</mat-icon>&nbsp; <div *pblNgridCellDef="'title'; row as motion; rowContext as rowContext" class="cell-slot fill motion-block-title">
<span translate>Follow recommendations for all motions</span> <a class="detail-link" [routerLink]="motion.getDetailStateURL()" *ngIf="!isMultiSelect"></a>
</button> <span>{{ motion.getTitle() }}</span>
</div>
<pbl-ngrid <!-- State column -->
class="block-detail-table" <div *pblNgridCellDef="'state'; row as motion" class="cell-slot fill">
cellTooltip <div class="chip-container">
showHeader="true" <mat-basic-chip disableRipple [ngClass]="motion.stateCssColor">
vScrollFixed="80" {{ getStateLabel(motion) }}
[dataSource]="dataSource"
[columns]="columnSet"
>
<!-- Title column -->
<div
*pblNgridCellDef="'title'; row as motion; rowContext as rowContext"
class="cell-slot fill motion-block-title"
>
<a class="detail-link" [routerLink]="motion.getDetailStateURL()" *ngIf="!isMultiSelect"></a>
<span>{{ motion.getTitle() }}</span>
</div>
<!-- State column -->
<div *pblNgridCellDef="'state'; row as motion" class="cell-slot fill">
<div class="chip-container">
<mat-basic-chip disableRipple [ngClass]="motion.stateCssColor">
{{ getStateLabel(motion) }}
</mat-basic-chip>
</div>
</div>
<!-- Recommendation column -->
<div *pblNgridCellDef="'recommendation'; row as motion" class="cell-slot fill">
<mat-basic-chip *ngIf="!!motion.recommendation" disableRipple class="bluegrey">
{{ getRecommendationLabel(motion) }}
</mat-basic-chip> </mat-basic-chip>
</div> </div>
</div>
<!-- Remove from block column --> <!-- Recommendation column -->
<div *pblNgridCellDef="'remove'; row as motion" class="cell-slot fill"> <div *pblNgridCellDef="'recommendation'; row as motion" class="cell-slot fill">
<button <mat-basic-chip *ngIf="!!motion.recommendation" disableRipple class="bluegrey">
type="button" {{ getRecommendationLabel(motion) }}
mat-icon-button </mat-basic-chip>
color="warn" </div>
matTooltip="{{ 'Remove from motion block' | translate }}"
(click)="onRemoveMotionButton(motion)" <!-- Remove from block column -->
> <div *pblNgridCellDef="'remove'; row as motion" class="cell-slot fill">
<mat-icon>close</mat-icon> <button
</button> type="button"
</div> mat-icon-button
</pbl-ngrid> color="warn"
</mat-card> matTooltip="{{ 'Remove from motion block' | translate }}"
(click)="onRemoveMotionButton(motion)"
>
<mat-icon>close</mat-icon>
</button>
</div>
</os-list-view-table>
<!-- The menu content --> <!-- The menu content -->
<mat-menu #motionBlockMenu="matMenu"> <mat-menu #motionBlockMenu="matMenu">
<div *ngIf="vp.isMobile">
<button
*osPerms="['motions.can_manage', 'motions.can_manage_metadata']"
mat-menu-item
(click)="onFollowRecButton()"
[disabled]="isFollowingProhibited()"
>
<mat-icon>done_all</mat-icon>
<span translate>Follow recommendations for all motions</span>
</button>
</div>
<os-speaker-button [menuItem]="true" [object]="block"></os-speaker-button> <os-speaker-button [menuItem]="true" [object]="block"></os-speaker-button>
<os-projector-button *ngIf="block" [object]="block" [menuItem]="true"></os-projector-button> <os-projector-button *ngIf="block" [object]="block" [menuItem]="true"></os-projector-button>

View File

@ -1,30 +1,5 @@
@import '~assets/styles/tables.scss'; @import '~assets/styles/tables.scss';
.block-detail-table {
margin-top: 10px;
height: calc(100vh - 250px);
::ng-deep .pbl-ngrid-row {
height: 80px !important;
}
.pbl-ngrid-column-title {
height: 100%;
}
}
@media only screen and (max-width: 960px) {
.block-detail-table {
height: calc(100vh - 186px);
}
}
.motion-block-title {
&.pbl-ngrid-cell {
height: 100%;
}
}
.edit-form { .edit-form {
overflow: hidden; overflow: hidden;
} }

View File

@ -6,17 +6,20 @@ import { Title } from '@angular/platform-browser';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { columnFactory, createDS, PblColumnDefinition, PblDataSource } from '@pebula/ngrid'; import { PblColumnDefinition } from '@pebula/ngrid';
import { StorageService } from 'app/core/core-services/storage.service';
import { ItemRepositoryService } from 'app/core/repositories/agenda/item-repository.service'; import { ItemRepositoryService } from 'app/core/repositories/agenda/item-repository.service';
import { MotionBlockRepositoryService } from 'app/core/repositories/motions/motion-block-repository.service'; import { MotionBlockRepositoryService } from 'app/core/repositories/motions/motion-block-repository.service';
import { MotionRepositoryService } from 'app/core/repositories/motions/motion-repository.service'; import { MotionRepositoryService } from 'app/core/repositories/motions/motion-repository.service';
import { PromptService } from 'app/core/ui-services/prompt.service'; import { PromptService } from 'app/core/ui-services/prompt.service';
import { ViewportService } from 'app/core/ui-services/viewport.service';
import { MotionBlock } from 'app/shared/models/motions/motion-block'; import { MotionBlock } from 'app/shared/models/motions/motion-block';
import { infoDialogSettings } from 'app/shared/utils/dialog-settings'; import { infoDialogSettings } from 'app/shared/utils/dialog-settings';
import { BaseViewComponent } from 'app/site/base/base-view'; import { BaseListViewComponent } from 'app/site/base/base-list-view';
import { ViewMotion } from 'app/site/motions/models/view-motion'; import { ViewMotion } from 'app/site/motions/models/view-motion';
import { ViewMotionBlock } from 'app/site/motions/models/view-motion-block'; import { ViewMotionBlock } from 'app/site/motions/models/view-motion-block';
import { BlockDetailFilterListService } from 'app/site/motions/services/block-detail-filter-list.service';
/** /**
* Detail component to display one motion block * Detail component to display one motion block
@ -26,52 +29,48 @@ import { ViewMotionBlock } from 'app/site/motions/models/view-motion-block';
templateUrl: './motion-block-detail.component.html', templateUrl: './motion-block-detail.component.html',
styleUrls: ['./motion-block-detail.component.scss'] styleUrls: ['./motion-block-detail.component.scss']
}) })
export class MotionBlockDetailComponent extends BaseViewComponent implements OnInit { export class MotionBlockDetailComponent extends BaseListViewComponent<ViewMotion> implements OnInit {
/**
* Holds the block ID
*/
private blockId: number;
/** /**
* Determines the block id from the given URL * Determines the block id from the given URL
*/ */
public block: ViewMotionBlock; public block: ViewMotionBlock;
/** /**
* Data source for the motions in the block * To quick-filter the list
*/ */
public dataSource: PblDataSource<ViewMotion>; public filterProps = ['submitters', 'title', 'identifier'];
/** /**
* Columns to display in table when desktop view is available
* Define the columns to show * Define the columns to show
*/ */
public tableColumnDefinition: PblColumnDefinition[] = []; public tableColumnDefinition: PblColumnDefinition[] = [
{
/** prop: 'title',
* Define the columns to show width: 'auto'
* TODO: The translation will not update when the },
*/ {
public columnSet = columnFactory() prop: 'state',
.table( width: '30%',
{ minWidth: 60
prop: 'title', },
label: this.translate.instant('Title'), {
width: 'auto' prop: 'recommendation',
}, label: this.translate.instant('Recommendation'),
{ width: '30%',
prop: 'state', minWidth: 60
label: this.translate.instant('State'), },
width: '30%', {
minWidth: 60 prop: 'remove',
}, label: '',
{ width: '40px'
prop: 'recommendation', }
label: this.translate.instant('Recommendation'), ];
width: '30%',
minWidth: 60
},
{
prop: 'remove',
label: '',
width: '40px'
}
)
.build();
/** /**
* The form to edit blocks * The form to edit blocks
@ -105,13 +104,18 @@ export class MotionBlockDetailComponent extends BaseViewComponent implements OnI
private route: ActivatedRoute, private route: ActivatedRoute,
private router: Router, private router: Router,
protected repo: MotionBlockRepositoryService, protected repo: MotionBlockRepositoryService,
protected motionRepo: MotionRepositoryService, public motionRepo: MotionRepositoryService,
private promptService: PromptService, private promptService: PromptService,
private fb: FormBuilder, private fb: FormBuilder,
private dialog: MatDialog, private dialog: MatDialog,
private itemRepo: ItemRepositoryService private itemRepo: ItemRepositoryService,
storage: StorageService,
public filterService: BlockDetailFilterListService,
public vp: ViewportService
) { ) {
super(titleService, translate, matSnackBar); super(titleService, translate, matSnackBar, storage);
this.blockId = parseInt(this.route.snapshot.params.id, 10);
this.filterService.blockId = this.blockId;
} }
/** /**
@ -119,22 +123,12 @@ export class MotionBlockDetailComponent extends BaseViewComponent implements OnI
* Sets the title, observes the block and the motions belonging in this block * Sets the title, observes the block and the motions belonging in this block
*/ */
public ngOnInit(): void { public ngOnInit(): void {
super.setTitle('Motion block');
const blockId = parseInt(this.route.snapshot.params.id, 10);
// pseudo filter // pseudo filter
this.subscriptions.push( this.subscriptions.push(
this.repo.getViewModelObservable(blockId).subscribe(newBlock => { this.repo.getViewModelObservable(this.blockId).subscribe(newBlock => {
if (newBlock) { if (newBlock) {
super.setTitle(newBlock.getTitle()); super.setTitle(`${this.translate.instant('Motion block')} - ${newBlock.getTitle()}`);
this.block = newBlock; this.block = newBlock;
this.dataSource = createDS<ViewMotion>()
.onTrigger(() => {
return this.block.motions;
})
.create();
} }
}) })
); );

View File

@ -0,0 +1,18 @@
import { TestBed } from '@angular/core/testing';
import { E2EImportsModule } from 'e2e-imports.module';
import { BlockDetailFilterListService } from './block-detail-filter-list.service';
describe('BlockDetailFilterListService', () => {
beforeEach(() =>
TestBed.configureTestingModule({
imports: [E2EImportsModule]
})
);
it('should be created', () => {
const service: BlockDetailFilterListService = TestBed.get(BlockDetailFilterListService);
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,83 @@
import { Injectable } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { OpenSlidesStatusService } from 'app/core/core-services/openslides-status.service';
import { OperatorService } from 'app/core/core-services/operator.service';
import { StorageService } from 'app/core/core-services/storage.service';
import { CategoryRepositoryService } from 'app/core/repositories/motions/category-repository.service';
import { MotionBlockRepositoryService } from 'app/core/repositories/motions/motion-block-repository.service';
import { MotionCommentSectionRepositoryService } from 'app/core/repositories/motions/motion-comment-section-repository.service';
import { WorkflowRepositoryService } from 'app/core/repositories/motions/workflow-repository.service';
import { TagRepositoryService } from 'app/core/repositories/tags/tag-repository.service';
import { ConfigService } from 'app/core/ui-services/config.service';
import { MotionFilterListService } from './motion-filter-list.service';
import { ViewMotion } from '../models/view-motion';
/**
* Filter service for motion blocks
*/
@Injectable({
providedIn: 'root'
})
export class BlockDetailFilterListService extends MotionFilterListService {
/**
* Private acessor for the blockId
*/
private _blockId: number;
/**
* setter for the blockId
*/
public set blockId(id: number) {
this._blockId = id;
}
/**
*
* @param store
* @param OSStatus
* @param categoryRepo
* @param motionBlockRepo
* @param commentRepo
* @param tagRepo
* @param workflowRepo
* @param translate
* @param operator
* @param config
*/
public constructor(
store: StorageService,
OSStatus: OpenSlidesStatusService,
categoryRepo: CategoryRepositoryService,
motionBlockRepo: MotionBlockRepositoryService,
commentRepo: MotionCommentSectionRepositoryService,
tagRepo: TagRepositoryService,
workflowRepo: WorkflowRepositoryService,
translate: TranslateService,
operator: OperatorService,
config: ConfigService
) {
super(
store,
OSStatus,
categoryRepo,
motionBlockRepo,
commentRepo,
tagRepo,
workflowRepo,
translate,
operator,
config
);
}
/**
* @override from parent
* @param viewMotions
* @return
*/
protected preFilter(viewMotions: ViewMotion[]): ViewMotion[] {
return viewMotions.filter(motion => motion.motion_block_id === this._blockId);
}
}