Merge pull request #5321 from tsiegleauq/projector-indicator-on-agenda
Projector indicator list view tables
This commit is contained in:
commit
77cf3e2785
@ -22,6 +22,8 @@
|
|||||||
[columns]="columnSet"
|
[columns]="columnSet"
|
||||||
[hideColumns]="hiddenColumns"
|
[hideColumns]="hiddenColumns"
|
||||||
(rowClick)="onSelectRow($event)"
|
(rowClick)="onSelectRow($event)"
|
||||||
|
[rowClassUpdate]="isElementProjected"
|
||||||
|
rowClassUpdateFreq="ngDoCheck"
|
||||||
>
|
>
|
||||||
<!-- "row" has the view model -->
|
<!-- "row" has the view model -->
|
||||||
<!-- "value" has the property, that was defined in the columnDefinition -->
|
<!-- "value" has the property, that was defined in the columnDefinition -->
|
||||||
@ -30,10 +32,21 @@
|
|||||||
<!-- Projector column -->
|
<!-- Projector column -->
|
||||||
<div *pblNgridCellDef="'projector'; row as viewModel" class="fill ngrid-lg">
|
<div *pblNgridCellDef="'projector'; row as viewModel" class="fill ngrid-lg">
|
||||||
<os-projector-button
|
<os-projector-button
|
||||||
|
*osPerms="'core.can_manage_projector'"
|
||||||
class="projector-button"
|
class="projector-button"
|
||||||
[object]="getProjectable(viewModel)"
|
[object]="getProjectable(viewModel)"
|
||||||
(changeEvent)="viewUpdateEvent()"
|
(changeEvent)="viewUpdateEvent()"
|
||||||
></os-projector-button>
|
></os-projector-button>
|
||||||
|
<!-- Projector indicator -->
|
||||||
|
<div class="projector-button" *osPerms="'core.can_manage_projector'; complement: true">
|
||||||
|
<mat-icon
|
||||||
|
color="accent"
|
||||||
|
*ngIf="projectorService.isProjected(getProjectable(viewModel))"
|
||||||
|
matTooltip="{{ 'Currently projected' | translate }}"
|
||||||
|
>
|
||||||
|
videocam
|
||||||
|
</mat-icon>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- No Results -->
|
<!-- No Results -->
|
||||||
|
@ -4,6 +4,7 @@ $pbl-height: var(--pbl-height);
|
|||||||
|
|
||||||
.projector-button {
|
.projector-button {
|
||||||
margin: auto;
|
margin: auto;
|
||||||
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pbl-ngrid-row {
|
.pbl-ngrid-row {
|
||||||
|
@ -0,0 +1,13 @@
|
|||||||
|
@import '~@angular/material/theming';
|
||||||
|
|
||||||
|
@mixin os-list-view-table-theme($theme) {
|
||||||
|
$primary: map-get($theme, primary);
|
||||||
|
$accent: map-get($theme, accent);
|
||||||
|
$warn: map-get($theme, accent);
|
||||||
|
$foreground: map-get($theme, foreground);
|
||||||
|
$background: map-get($theme, background);
|
||||||
|
|
||||||
|
.projected {
|
||||||
|
background-color: mat-color($background, hover) !important;
|
||||||
|
}
|
||||||
|
}
|
@ -5,8 +5,8 @@ import { E2EImportsModule } from 'e2e-imports.module';
|
|||||||
import { ListViewTableComponent } from './list-view-table.component';
|
import { ListViewTableComponent } from './list-view-table.component';
|
||||||
|
|
||||||
describe('ListViewTableComponent', () => {
|
describe('ListViewTableComponent', () => {
|
||||||
let component: ListViewTableComponent<any, any>;
|
let component: ListViewTableComponent<any>;
|
||||||
let fixture: ComponentFixture<ListViewTableComponent<any, any>>;
|
let fixture: ComponentFixture<ListViewTableComponent<any>>;
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
|
@ -13,18 +13,18 @@ import {
|
|||||||
import { NavigationStart, Router } from '@angular/router';
|
import { NavigationStart, Router } from '@angular/router';
|
||||||
|
|
||||||
import { columnFactory, createDS, DataSourcePredicate, PblDataSource, PblNgridComponent } from '@pebula/ngrid';
|
import { columnFactory, createDS, DataSourcePredicate, PblDataSource, PblNgridComponent } from '@pebula/ngrid';
|
||||||
import { PblColumnDefinition, PblColumnFactory, PblNgridColumnSet } from '@pebula/ngrid/lib/grid';
|
import { PblColumnDefinition, PblColumnFactory, PblNgridColumnSet, PblNgridRowContext } from '@pebula/ngrid/lib/grid';
|
||||||
import { PblNgridDataMatrixRow } from '@pebula/ngrid/target-events';
|
import { PblNgridDataMatrixRow } from '@pebula/ngrid/target-events';
|
||||||
import { Observable, Subscription } from 'rxjs';
|
import { Observable, Subscription } from 'rxjs';
|
||||||
import { distinctUntilChanged, filter } from 'rxjs/operators';
|
import { distinctUntilChanged, filter } from 'rxjs/operators';
|
||||||
|
|
||||||
import { OperatorService, Permission } from 'app/core/core-services/operator.service';
|
import { OperatorService, Permission } from 'app/core/core-services/operator.service';
|
||||||
|
import { ProjectorService } from 'app/core/core-services/projector.service';
|
||||||
import { StorageService } from 'app/core/core-services/storage.service';
|
import { StorageService } from 'app/core/core-services/storage.service';
|
||||||
import { HasViewModelListObservable } from 'app/core/definitions/has-view-model-list-observable';
|
import { HasViewModelListObservable } from 'app/core/definitions/has-view-model-list-observable';
|
||||||
import { BaseFilterListService } from 'app/core/ui-services/base-filter-list.service';
|
import { BaseFilterListService } from 'app/core/ui-services/base-filter-list.service';
|
||||||
import { BaseSortListService } from 'app/core/ui-services/base-sort-list.service';
|
import { BaseSortListService } from 'app/core/ui-services/base-sort-list.service';
|
||||||
import { ViewportService } from 'app/core/ui-services/viewport.service';
|
import { ViewportService } from 'app/core/ui-services/viewport.service';
|
||||||
import { BaseModel } from 'app/shared/models/base/base-model';
|
|
||||||
import { BaseProjectableViewModel } from 'app/site/base/base-projectable-view-model';
|
import { BaseProjectableViewModel } from 'app/site/base/base-projectable-view-model';
|
||||||
import { BaseViewModel } from 'app/site/base/base-view-model';
|
import { BaseViewModel } from 'app/site/base/base-view-model';
|
||||||
import { BaseViewModelWithContentObject } from 'app/site/base/base-view-model-with-content-object';
|
import { BaseViewModelWithContentObject } from 'app/site/base/base-view-model-with-content-object';
|
||||||
@ -89,7 +89,8 @@ export interface ColumnRestriction {
|
|||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
encapsulation: ViewEncapsulation.None
|
encapsulation: ViewEncapsulation.None
|
||||||
})
|
})
|
||||||
export class ListViewTableComponent<V extends BaseViewModel, M extends BaseModel> implements OnInit, OnDestroy {
|
export class ListViewTableComponent<V extends BaseViewModel | BaseViewModelWithContentObject>
|
||||||
|
implements OnInit, OnDestroy {
|
||||||
/**
|
/**
|
||||||
* Declare the table
|
* Declare the table
|
||||||
*/
|
*/
|
||||||
@ -254,11 +255,6 @@ export class ListViewTableComponent<V extends BaseViewModel, M extends BaseModel
|
|||||||
*/
|
*/
|
||||||
private dataListObservable: Observable<V[]>;
|
private dataListObservable: Observable<V[]>;
|
||||||
|
|
||||||
/**
|
|
||||||
* Minimal column width
|
|
||||||
*/
|
|
||||||
private columnMinWidth = '60px';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The column set to display in the table
|
* The column set to display in the table
|
||||||
*/
|
*/
|
||||||
@ -289,6 +285,14 @@ export class ListViewTableComponent<V extends BaseViewModel, M extends BaseModel
|
|||||||
*/
|
*/
|
||||||
private subs: Subscription[] = [];
|
private subs: Subscription[] = [];
|
||||||
|
|
||||||
|
private get projectorColumnWidth(): number {
|
||||||
|
if (this.operator.hasPerms('core.can_manage_projector')) {
|
||||||
|
return 60;
|
||||||
|
} else {
|
||||||
|
return 24;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Most, of not all list views require these
|
* Most, of not all list views require these
|
||||||
*/
|
*/
|
||||||
@ -302,7 +306,7 @@ export class ListViewTableComponent<V extends BaseViewModel, M extends BaseModel
|
|||||||
{
|
{
|
||||||
prop: 'projector',
|
prop: 'projector',
|
||||||
label: '',
|
label: '',
|
||||||
width: this.columnMinWidth
|
width: `${this.projectorColumnWidth}px`
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
return columns;
|
return columns;
|
||||||
@ -358,12 +362,7 @@ export class ListViewTableComponent<V extends BaseViewModel, M extends BaseModel
|
|||||||
}
|
}
|
||||||
|
|
||||||
// hide the projector columns
|
// hide the projector columns
|
||||||
if (
|
if (this.multiSelect || this.isMobile || !this.allowProjector) {
|
||||||
this.multiSelect ||
|
|
||||||
this.isMobile ||
|
|
||||||
!this.allowProjector ||
|
|
||||||
!this.operator.hasPerms('core.can_manage_projector')
|
|
||||||
) {
|
|
||||||
hidden.push('projector');
|
hidden.push('projector');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -398,7 +397,8 @@ export class ListViewTableComponent<V extends BaseViewModel, M extends BaseModel
|
|||||||
vp: ViewportService,
|
vp: ViewportService,
|
||||||
router: Router,
|
router: Router,
|
||||||
private store: StorageService,
|
private store: StorageService,
|
||||||
private cd: ChangeDetectorRef
|
private cd: ChangeDetectorRef,
|
||||||
|
public projectorService: ProjectorService
|
||||||
) {
|
) {
|
||||||
vp.isMobileSubject.subscribe(mobile => {
|
vp.isMobileSubject.subscribe(mobile => {
|
||||||
if (mobile !== this.isMobile) {
|
if (mobile !== this.isMobile) {
|
||||||
@ -458,7 +458,7 @@ export class ListViewTableComponent<V extends BaseViewModel, M extends BaseModel
|
|||||||
// Define the columns. Has to be in the OnInit cause "columns" is slower than
|
// Define the columns. Has to be in the OnInit cause "columns" is slower than
|
||||||
// the constructor of this class
|
// the constructor of this class
|
||||||
this.columnSet = columnFactory()
|
this.columnSet = columnFactory()
|
||||||
.default({ width: this.columnMinWidth })
|
.default({ width: '60px' })
|
||||||
.table(...this.defaultStartColumns, ...this.columns, ...this.defaultEndColumns)
|
.table(...this.defaultStartColumns, ...this.columns, ...this.defaultEndColumns)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
@ -483,6 +483,12 @@ export class ListViewTableComponent<V extends BaseViewModel, M extends BaseModel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public isElementProjected = (context: PblNgridRowContext<V>) => {
|
||||||
|
if (this.projectorService.isProjected(this.getProjectable(context.$implicit as V))) {
|
||||||
|
return 'projected';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines and sets the raw data as observable lists according
|
* Determines and sets the raw data as observable lists according
|
||||||
* to the used search and filter services
|
* to the used search and filter services
|
||||||
@ -597,11 +603,8 @@ export class ListViewTableComponent<V extends BaseViewModel, M extends BaseModel
|
|||||||
* @param viewModel The model of the table
|
* @param viewModel The model of the table
|
||||||
* @returns a view model that can be projected
|
* @returns a view model that can be projected
|
||||||
*/
|
*/
|
||||||
public getProjectable(
|
public getProjectable(viewModel: V): BaseProjectableViewModel {
|
||||||
viewModel: BaseViewModelWithContentObject | BaseProjectableViewModel
|
return (viewModel as BaseViewModelWithContentObject)?.contentObject ?? viewModel;
|
||||||
): BaseProjectableViewModel {
|
|
||||||
const withContent = viewModel as BaseViewModelWithContentObject;
|
|
||||||
return !!withContent.contentObject ? withContent.contentObject : viewModel;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -33,19 +33,31 @@
|
|||||||
<div *pblNgridCellDef="'title'; row as item; rowContext as rowContext" class="cell-slot fill">
|
<div *pblNgridCellDef="'title'; row as item; rowContext as rowContext" class="cell-slot fill">
|
||||||
<a class="detail-link" [routerLink]="getDetailUrl(item)" *ngIf="!isMultiSelect"></a>
|
<a class="detail-link" [routerLink]="getDetailUrl(item)" *ngIf="!isMultiSelect"></a>
|
||||||
<div [ngStyle]="{ 'margin-left': item.level * 25 + 'px' }" class="innerTable">
|
<div [ngStyle]="{ 'margin-left': item.level * 25 + 'px' }" class="innerTable">
|
||||||
<os-icon-container [noWrap]="true" [icon]="item.closed ? 'check' : null" size="large">
|
|
||||||
<div class="ellipsis-overflow">
|
<!-- Title line -->
|
||||||
|
<div class="title-line ellipsis-overflow">
|
||||||
|
<!-- Is Closed -->
|
||||||
|
<span class="icon-prefix" *ngIf="item.closed">
|
||||||
|
<mat-icon>check</mat-icon>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<!-- Title -->
|
||||||
|
<span>
|
||||||
{{ item.getListTitle() }}
|
{{ item.getListTitle() }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="showSubtitle" class="subtitle ellipsis-overflow">
|
|
||||||
|
<!-- Subtitle line -->
|
||||||
|
<div class="subtitle ellipsis-overflow">
|
||||||
{{ item.getSubtitle() }}
|
{{ item.getSubtitle() }}
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="item.comment" class="subtitle ellipsis-overflow">
|
|
||||||
|
<!-- Comment line -->
|
||||||
|
<div class="subtitle ellipsis-overflow" *ngIf="item.comment">
|
||||||
<os-icon-container size="small" icon="comment" [noWrap]="true">
|
<os-icon-container size="small" icon="comment" [noWrap]="true">
|
||||||
{{ item.comment }}
|
{{ item.comment }}
|
||||||
</os-icon-container>
|
</os-icon-container>
|
||||||
</div>
|
</div>
|
||||||
</os-icon-container>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -121,7 +133,12 @@
|
|||||||
<span>{{ 'Import' | translate }}</span>
|
<span>{{ 'Import' | translate }}</span>
|
||||||
</button>
|
</button>
|
||||||
<mat-divider></mat-divider>
|
<mat-divider></mat-divider>
|
||||||
<button mat-menu-item *osPerms="'agenda.can_manage'" class="red-warning-text" (click)="deleteAllSpeakersOfAllListsOfSpeakers()">
|
<button
|
||||||
|
mat-menu-item
|
||||||
|
*osPerms="'agenda.can_manage'"
|
||||||
|
class="red-warning-text"
|
||||||
|
(click)="deleteAllSpeakersOfAllListsOfSpeakers()"
|
||||||
|
>
|
||||||
<mat-icon>delete</mat-icon>
|
<mat-icon>delete</mat-icon>
|
||||||
<span>{{ 'Clear all list of speakers' | translate }}</span>
|
<span>{{ 'Clear all list of speakers' | translate }}</span>
|
||||||
</button>
|
</button>
|
||||||
@ -229,7 +246,12 @@
|
|||||||
<span>{{ 'Remove from agenda' | translate }}</span>
|
<span>{{ 'Remove from agenda' | translate }}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button mat-menu-item class="red-warning-text" (click)="deleteTopic(item)" *ngIf="isTopic(item.contentObject)">
|
<button
|
||||||
|
mat-menu-item
|
||||||
|
class="red-warning-text"
|
||||||
|
(click)="deleteTopic(item)"
|
||||||
|
*ngIf="isTopic(item.contentObject)"
|
||||||
|
>
|
||||||
<mat-icon>delete</mat-icon>
|
<mat-icon>delete</mat-icon>
|
||||||
<span>{{ 'Delete' | translate }}</span>
|
<span>{{ 'Delete' | translate }}</span>
|
||||||
</button>
|
</button>
|
||||||
|
@ -16,10 +16,3 @@
|
|||||||
.align-right {
|
.align-right {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Where is this used?
|
|
||||||
*/
|
|
||||||
.done-check {
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
|
@ -68,7 +68,7 @@
|
|||||||
</span>
|
</span>
|
||||||
|
|
||||||
<!-- Has File -->
|
<!-- Has File -->
|
||||||
<span class="attached-files" *ngIf="motion.hasAttachments()">
|
<span class="icon-prefix" *ngIf="motion.hasAttachments()">
|
||||||
<mat-icon>attach_file</mat-icon>
|
<mat-icon>attach_file</mat-icon>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
@ -74,7 +74,7 @@
|
|||||||
</span>
|
</span>
|
||||||
|
|
||||||
<!-- Has File -->
|
<!-- Has File -->
|
||||||
<span class="attached-files" *ngIf="motion.hasAttachments()">
|
<span class="icon-prefix" *ngIf="motion.hasAttachments()">
|
||||||
<mat-icon>attach_file</mat-icon>
|
<mat-icon>attach_file</mat-icon>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
|
||||||
.attached-files {
|
.icon-prefix {
|
||||||
.mat-icon {
|
.mat-icon {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
@ -33,6 +33,7 @@
|
|||||||
@import './app/site/assignments/components/assignment-poll-detail/assignment-poll-detail-component.scss-theme.scss';
|
@import './app/site/assignments/components/assignment-poll-detail/assignment-poll-detail-component.scss-theme.scss';
|
||||||
@import './app/shared/components/progress-snack-bar/progress-snack-bar.component.scss-theme.scss';
|
@import './app/shared/components/progress-snack-bar/progress-snack-bar.component.scss-theme.scss';
|
||||||
@import './app/shared/components/jitsi/jitsi.component.scss-theme.scss';
|
@import './app/shared/components/jitsi/jitsi.component.scss-theme.scss';
|
||||||
|
@import './app/shared/components/list-view-table/list-view-table.component.scss-theme.scss';
|
||||||
|
|
||||||
/** fonts */
|
/** fonts */
|
||||||
@import './assets/styles/fonts.scss';
|
@import './assets/styles/fonts.scss';
|
||||||
@ -66,6 +67,7 @@ $narrow-spacing: (
|
|||||||
@include os-assignment-poll-detail-style($theme);
|
@include os-assignment-poll-detail-style($theme);
|
||||||
@include os-progress-snack-bar-style($theme);
|
@include os-progress-snack-bar-style($theme);
|
||||||
@include os-jitsi-theme($theme);
|
@include os-jitsi-theme($theme);
|
||||||
|
@include os-list-view-table-theme($theme);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Load projector specific SCSS values */
|
/** Load projector specific SCSS values */
|
||||||
|
Loading…
Reference in New Issue
Block a user