Merge pull request #4923 from tsiegleauq/motion-block-detail-layout
Unify motion block detail
This commit is contained in:
commit
bfccf4cd2b
@ -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>
|
<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>
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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();
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@ -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();
|
||||||
|
});
|
||||||
|
});
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user