Unify motion block detail

Changes motion block list to look and feel like all the other lists
This commit is contained in:
Sean Engelhardt 2019-08-15 14:40:10 +02:00
parent a1b7b1c69d
commit 1fc0ec02a9
5 changed files with 211 additions and 132 deletions

View File

@ -13,70 +13,79 @@
<mat-icon>more_vert</mat-icon>
</button>
</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>
<mat-card>
<button
*osPerms="['motions.can_manage', 'motions.can_manage_metadata']"
mat-raised-button
color="primary"
(click)="onFollowRecButton()"
[disabled]="isFollowingProhibited()"
>
<mat-icon>done_all</mat-icon>&nbsp;
<span translate>Follow recommendations for all motions</span>
</button>
<os-list-view-table
[repo]="motionRepo"
[filterService]="filterService"
[columns]="tableColumnDefinition"
[filterProps]="filterProps"
(dataSourceChange)="onDataSourceChange($event)"
>
<!-- 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>
<pbl-ngrid
class="block-detail-table"
cellTooltip
showHeader="true"
vScrollFixed="80"
[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) }}
<!-- 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>
<!-- Remove from block column -->
<div *pblNgridCellDef="'remove'; row as motion" class="cell-slot fill">
<button
type="button"
mat-icon-button
color="warn"
matTooltip="{{ 'Remove from motion block' | translate }}"
(click)="onRemoveMotionButton(motion)"
>
<mat-icon>close</mat-icon>
</button>
</div>
</pbl-ngrid>
</mat-card>
<!-- 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>
</div>
<!-- Remove from block column -->
<div *pblNgridCellDef="'remove'; row as motion" class="cell-slot fill">
<button
type="button"
mat-icon-button
color="warn"
matTooltip="{{ 'Remove from motion block' | translate }}"
(click)="onRemoveMotionButton(motion)"
>
<mat-icon>close</mat-icon>
</button>
</div>
</os-list-view-table>
<!-- The menu content -->
<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-projector-button *ngIf="block" [object]="block" [menuItem]="true"></os-projector-button>

View File

@ -1,30 +1,5 @@
@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 {
overflow: hidden;
}

View File

@ -6,17 +6,20 @@ import { Title } from '@angular/platform-browser';
import { ActivatedRoute, Router } from '@angular/router';
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 { MotionBlockRepositoryService } from 'app/core/repositories/motions/motion-block-repository.service';
import { MotionRepositoryService } from 'app/core/repositories/motions/motion-repository.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 { 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 { 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
@ -26,52 +29,48 @@ import { ViewMotionBlock } from 'app/site/motions/models/view-motion-block';
templateUrl: './motion-block-detail.component.html',
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
*/
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
*/
public tableColumnDefinition: PblColumnDefinition[] = [];
/**
* Define the columns to show
* TODO: The translation will not update when the
*/
public columnSet = columnFactory()
.table(
{
prop: 'title',
label: this.translate.instant('Title'),
width: 'auto'
},
{
prop: 'state',
label: this.translate.instant('State'),
width: '30%',
minWidth: 60
},
{
prop: 'recommendation',
label: this.translate.instant('Recommendation'),
width: '30%',
minWidth: 60
},
{
prop: 'remove',
label: '',
width: '40px'
}
)
.build();
public tableColumnDefinition: PblColumnDefinition[] = [
{
prop: 'title',
width: 'auto'
},
{
prop: 'state',
width: '30%',
minWidth: 60
},
{
prop: 'recommendation',
label: this.translate.instant('Recommendation'),
width: '30%',
minWidth: 60
},
{
prop: 'remove',
label: '',
width: '40px'
}
];
/**
* The form to edit blocks
@ -105,13 +104,18 @@ export class MotionBlockDetailComponent extends BaseViewComponent implements OnI
private route: ActivatedRoute,
private router: Router,
protected repo: MotionBlockRepositoryService,
protected motionRepo: MotionRepositoryService,
public motionRepo: MotionRepositoryService,
private promptService: PromptService,
private fb: FormBuilder,
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
*/
public ngOnInit(): void {
super.setTitle('Motion block');
const blockId = parseInt(this.route.snapshot.params.id, 10);
// pseudo filter
this.subscriptions.push(
this.repo.getViewModelObservable(blockId).subscribe(newBlock => {
this.repo.getViewModelObservable(this.blockId).subscribe(newBlock => {
if (newBlock) {
super.setTitle(newBlock.getTitle());
super.setTitle(`${this.translate.instant('Motion block')} - ${newBlock.getTitle()}`);
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);
}
}