Clean projector detail interface

Alters the projector detail interface to provide better usability.
Alters the dragging slightly to have a nice preview and smoother
transitions.
This commit is contained in:
Sean Engelhardt 2019-02-28 17:26:28 +01:00 committed by Emanuel Schütze
parent d77abf5934
commit 587a0fe443
6 changed files with 263 additions and 193 deletions

View File

@ -1,5 +1,5 @@
<div cdkDropList class="os-card" (cdkDropListDropped)="drop($event)"> <div cdkDropList class="os-card" (cdkDropListDropped)="drop($event)">
<div class= "box line" *ngIf="!array.length"> <div class="box line" *ngIf="!array.length">
<span translate>No data</span> <span translate>No data</span>
</div> </div>
<div class="box line" *ngFor="let item of array; let i = index" cdkDrag> <div class="box line" *ngFor="let item of array; let i = index" cdkDrag>

View File

@ -1,3 +1,5 @@
@import '../../../../assets/styles/drag.scss';
.box { .box {
width: 100%; width: 100%;
border-bottom: solid 1px #ccc; border-bottom: solid 1px #ccc;
@ -5,21 +7,6 @@
font-size: 14px; font-size: 14px;
} }
.cdk-drag-preview {
box-sizing: border-box;
border-radius: 4px;
box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2), 0 8px 10px 1px rgba(0, 0, 0, 0.14),
0 3px 14px 2px rgba(0, 0, 0, 0.12);
}
.cdk-drag-placeholder {
opacity: 0;
}
.cdk-drag-animating {
transition: transform 125ms ease-in-out;
}
.box:last-child { .box:last-child {
border: none; border: none;
} }

View File

@ -12,56 +12,65 @@
<os-projector [projector]="projector"></os-projector> <os-projector [projector]="projector"></os-projector>
</div> </div>
</a> </a>
</div> <!-- Controls under the projector preview -->
<div class="column-right" *osPerms="'core.can_manage_projector'"> <div class="control-group projector-controls">
<div class="control-group"> <!-- scaling indicator -->
<div class="button-size">{{ projector.scroll }}</div> <div class="button-size">{{ projector.scale }}</div>
<button type="button" mat-icon-button (click)="scroll(scrollScaleDirection.Up)">
<mat-icon>arrow_upward</mat-icon> <!-- scale up -->
<button type="button" mat-icon-button (click)="scale(scrollScaleDirection.Up)">
<mat-icon>zoom_in</mat-icon>
</button> </button>
<!-- scale down -->
<button type="button" mat-icon-button (click)="scale(scrollScaleDirection.Down)">
<mat-icon>zoom_out</mat-icon>
</button>
<!-- scroll indicator -->
<div class="button-size">{{ projector.scroll }}</div>
<!-- scroll down -->
<button type="button" mat-icon-button (click)="scroll(scrollScaleDirection.Down)"> <button type="button" mat-icon-button (click)="scroll(scrollScaleDirection.Down)">
<mat-icon>arrow_downward</mat-icon> <mat-icon>arrow_downward</mat-icon>
</button> </button>
<!-- scroll up -->
<button type="button" mat-icon-button (click)="scroll(scrollScaleDirection.Up)">
<mat-icon>arrow_upward</mat-icon>
</button>
<!-- refresh button -->
<button type="button" mat-icon-button (click)="scroll(scrollScaleDirection.Reset)"> <button type="button" mat-icon-button (click)="scroll(scrollScaleDirection.Reset)">
<mat-icon>refresh</mat-icon> <mat-icon>refresh</mat-icon>
</button> </button>
</div> </div>
<div class="control-group"> </div>
<div class="button-size">{{ projector.scale }}</div> <div class="column-right" *osPerms="'core.can_manage_projector'">
<button type="button" mat-icon-button (click)="scale(scrollScaleDirection.Up)"> <div class="control-group slide-controls">
<mat-icon>zoom_in</mat-icon> <button
</button> type="button"
<button type="button" mat-icon-button (click)="scale(scrollScaleDirection.Down)"> mat-button
<mat-icon>zoom_out</mat-icon> (click)="projectPreviousSlide()"
</button> [disabled]="projector?.elements_history.length === 0"
<button type="button" mat-icon-button (click)="scale(scrollScaleDirection.Reset)"> >
<mat-icon>refresh</mat-icon>
</button>
</div>
<hr>
<div class="control-group">
<button type="button" mat-button (click)="projectPreviousSlide()" [disabled]="projector?.elements_history.length === 0">
<mat-icon>arrow_back</mat-icon> <mat-icon>arrow_back</mat-icon>
<span translate>Previous</span> <span translate>Previous</span>
</button> </button>
<button type="button" mat-button (click)="projectNextSlide()" [disabled]="projector?.elements_preview.length === 0"> <button
type="button"
mat-button
(click)="projectNextSlide()"
[disabled]="projector?.elements_preview.length === 0"
>
<span translate>Next</span> <span translate>Next</span>
<mat-icon>arrow_forward</mat-icon> <mat-icon>arrow_forward</mat-icon>
</button> </button>
</div> <hr />
<div class="queue" *ngIf="projector.elements_history.length">
<h5 translate>History</h5>
<p *ngFor="let elements of projector.elements_history">
{{ getSlideTitle(elements[0]) }}
</p>
</div> </div>
<div> <div>
<h5 translate>Current</h5>
<div *ngIf="projector.non_stable_elements.length"> <div *ngIf="projector.non_stable_elements.length">
<h4 translate>Slides</h4>
<mat-list> <mat-list>
<mat-list-item *ngFor="let element of projector.non_stable_elements" class="projected"> <mat-list-item *ngFor="let element of projector.non_stable_elements" class="projected">
<button type="button" mat-icon-button (click)="unprojectCurrent(element)"> <button type="button" mat-icon-button (click)="unprojectCurrent(element)">
@ -72,99 +81,142 @@
</mat-list> </mat-list>
</div> </div>
<div *ngIf="countdowns.length"> <!-- Expandable elements -->
<h4> <mat-accordion multi="true">
<span translate>Countdowns</span> <!-- Queue -->
<button type="button" mat-icon-button disableRipple routerLink="/projectors/countdowns"> <mat-expansion-panel *ngIf="projector.elements_preview.length" [expanded]="true">
<mat-icon>edit</mat-icon> <mat-expansion-panel-header>
</button> <span translate>Queue</span>
</h4> </mat-expansion-panel-header>
<mat-list>
<mat-list-item *ngFor="let countdown of countdowns" [ngClass]="{'projected': isProjected(countdown)}">
<button type="button" mat-icon-button (click)="project(countdown)">
<mat-icon>videocam</mat-icon>
</button>
{{ countdown.description }}
</mat-list-item>
</mat-list>
</div>
<div *ngIf="messages.length"> <div
<h4> cdkDropList
<span translate>Messages</span> class="drop-list"
<button type="button" mat-icon-button disableRipple routerLink="/projectors/messages"> [cdkDropListDisabled]="!editQueue"
<mat-icon>edit</mat-icon> (cdkDropListDropped)="onSortingChange($event)"
</button> >
</h4> <div
<mat-list> class="drop-list-entry"
<mat-list-item *ngFor="let message of messages" [ngClass]="{'projected': isProjected(message)}"> *ngFor="let element of projector.elements_preview; let i = index"
<button type="button" mat-icon-button (click)="project(message)"> cdkDrag
<mat-icon>videocam</mat-icon> >
</button> <div class="drag-handle" cdkDragHandle *ngIf="editQueue">
<span>{{ message.getPreview(40) }}</span> <mat-icon>drag_indicator</mat-icon>
</mat-list-item> </div>
</mat-list> <div class="drag-handle" *ngIf="!editQueue">
</div> <button type="button" mat-mini-fab (click)="projectNow(i)">
<mat-icon>videocam</mat-icon>
<div> </button>
<h4>Current list of speakers overlay</h4> </div>
<mat-list> <div class="name">
<mat-list-item [ngClass]="{'projected': isClosProjected(true)}"> {{ i + 1 }}.&nbsp;<span>{{ getSlideTitle(element) }}</span>
<button type="button" mat-icon-button (click)="toggleClos(true)"> </div>
<mat-icon>videocam</mat-icon> <div class="button-right" *ngIf="editQueue">
</button> <div>
<span translate>Current list of speakers overlay</span> <button type="button" mat-icon-button (click)="removePreviewElement(i)">
</mat-list-item> <mat-icon>close</mat-icon>
</mat-list> </button>
</div> </div>
</div>
<div *ngIf="!isClosProjected(false)">
<h4>Current list of speakers slide</h4>
<mat-list>
<mat-list-item>
<button type="button" mat-icon-button (click)="toggleClos(false)">
<mat-icon>videocam</mat-icon>
</button>
<span translate>Current list of speakers slide</span>
</mat-list-item>
</mat-list>
</div>
</div>
<div>
<h4>Current speaker chyron</h4>
<mat-list>
<mat-list-item [ngClass]="{'projected': isChyronProjected()}">
<button type="button" mat-icon-button (click)="toggleChyron()">
<mat-icon>videocam</mat-icon>
</button>
<span translate>Current speaker chyron</span>
</mat-list-item>
</mat-list>
</div>
<div class="queue" *ngIf="projector.elements_preview.length">
<h5 translate>Queue</h5>
<div cdkDropList class="drop-list" (cdkDropListDropped)="onSortingChange($event)">
<div class="list-entry" *ngFor="let element of projector.elements_preview; let i = index" cdkDrag>
<div class="drag-handle" cdkDragHandle>
<mat-icon>drag_indicator</mat-icon>
</div>
<div class="name">
{{ i+1 }}.&nbsp;<span>{{ getSlideTitle(element) }}</span>
</div>
<div class="button-right">
<div>
<button type="button" mat-button (click)="projectNow(i)">
<span translate>Project now</span>
</button>
<button type="button" mat-icon-button (click)="removePreviewElement(i)">
<mat-icon>close</mat-icon>
</button>
</div> </div>
</div> </div>
</div> <mat-action-row>
</div> <button type="button" mat-icon-button (click)="editQueue = !editQueue">
<mat-icon>edit</mat-icon>
</button>
</mat-action-row>
</mat-expansion-panel>
<!-- Previous Slides -->
<mat-expansion-panel *ngIf="projector.elements_history.length">
<mat-expansion-panel-header>
<span translate>History</span>
</mat-expansion-panel-header>
<p *ngFor="let elements of projector.elements_history; let i = index">
{{ i + 1 }}. {{ getSlideTitle(elements[0]) }}
</p>
</mat-expansion-panel>
<!-- countdowns -->
<mat-expansion-panel *ngIf="countdowns.length">
<mat-expansion-panel-header>
<span translate>Countdowns</span>
</mat-expansion-panel-header>
<mat-list>
<mat-list-item
*ngFor="let countdown of countdowns"
[ngClass]="{ projected: isProjected(countdown) }"
>
<button type="button" mat-icon-button (click)="project(countdown)">
<mat-icon>videocam</mat-icon>
</button>
{{ countdown.description }}
</mat-list-item>
</mat-list>
<mat-action-row>
<button type="button" mat-icon-button routerLink="/projectors/countdowns">
<mat-icon>edit</mat-icon>
</button>
</mat-action-row>
</mat-expansion-panel>
<!-- messages -->
<mat-expansion-panel *ngIf="messages.length">
<mat-expansion-panel-header>
<span translate>Messages</span>
</mat-expansion-panel-header>
<mat-list>
<mat-list-item *ngFor="let message of messages" [ngClass]="{ projected: isProjected(message) }">
<button type="button" mat-icon-button (click)="project(message)">
<mat-icon>videocam</mat-icon>
</button>
<span>{{ message.getPreview(40) }}</span>
</mat-list-item>
</mat-list>
<mat-action-row>
<button type="button" mat-icon-button routerLink="/projectors/messages">
<mat-icon>edit</mat-icon>
</button>
</mat-action-row>
</mat-expansion-panel>
<!-- Current List of Speakers -->
<mat-expansion-panel>
<mat-expansion-panel-header>
<span translate>Current list of speakers</span>
</mat-expansion-panel-header>
<!-- Overlay -->
<mat-list>
<mat-list-item [ngClass]="{ projected: isClosProjected(true) }">
<button type="button" mat-icon-button (click)="toggleClos(true)">
<mat-icon>videocam</mat-icon>
</button>
<span translate>Current list of speakers overlay</span>
</mat-list-item>
</mat-list>
<!-- Current Speaker -->
<mat-list *ngIf="!isClosProjected(false)">
<mat-list-item>
<button type="button" mat-icon-button (click)="toggleClos(false)">
<mat-icon>videocam</mat-icon>
</button>
<span translate>Current list of speakers slide</span>
</mat-list-item>
</mat-list>
<!-- Chyron -->
<mat-list>
<mat-list-item [ngClass]="{ projected: isChyronProjected() }">
<button type="button" mat-icon-button (click)="toggleChyron()">
<mat-icon>videocam</mat-icon>
</button>
<span translate>Current speaker chyron</span>
</mat-list-item>
</mat-list>
</mat-expansion-panel>
</mat-accordion>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,3 +1,5 @@
@import '../../../../../assets/styles/drag.scss';
#projector { #projector {
width: 100%; /*1000px;*/ width: 100%; /*1000px;*/
border: 1px solid lightgrey; border: 1px solid lightgrey;
@ -25,8 +27,7 @@
} }
.control-group { .control-group {
text-align: center; color: rgba(0, 0, 0, 0.5);
color: rgba(0, 0, 0, 0.54);
.button-size { .button-size {
width: 40px; width: 40px;
@ -34,64 +35,60 @@
} }
} }
h4 { .slide-controls {
margin-top: 5px; text-align: center;
}
button { .projector-controls {
width: 20px; text-align: right;
height: 20px; }
.drop-list {
width: 100%;
display: block;
overflow: hidden;
}
.drop-list-entry {
display: table;
min-height: 50px;
width: 100%;
border-bottom: solid 1px #ccc;
color: rgba(0, 0, 0, 0.87);
.drag-handle {
display: table-cell;
padding: 0 10px;
line-height: 0px;
vertical-align: middle;
width: 25px;
color: slategrey;
cursor: move;
} }
}
h5 { .name {
margin-bottom: 0; display: table-cell;
} vertical-align: middle;
}
.queue { .button-right {
margin-top: 15px; display: table-cell;
padding-right: 10px;
vertical-align: middle;
width: auto;
white-space: nowrap;
.drop-list { div {
width: 100%; float: right;
display: block;
overflow: hidden;
.list-entry {
display: table;
min-height: 50px;
width: 100%;
border-bottom: solid 1px #ccc;
color: rgba(0, 0, 0, 0.87);
.drag-handle {
display: table-cell;
padding: 0 10px;
line-height: 0px;
vertical-align: middle;
width: 25px;
color: slategrey;
cursor: move;
}
.name {
display: table-cell;
vertical-align: middle;
}
.button-right {
display: table-cell;
padding-right: 10px;
vertical-align: middle;
width: auto;
white-space: nowrap;
div {
float: right;
}
}
}
.list-entry:last-child {
border: none;
} }
} }
} }
.drop-list-entry:last-child {
border: none;
}
// move away from preview.
.drop-list.cdk-drop-list-dragging .drop-list-entry:not(.cdk-drag-placeholder) {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}

View File

@ -43,6 +43,11 @@ export class ProjectorDetailComponent extends BaseViewComponent implements OnIni
public messages: ViewProjectorMessage[] = []; public messages: ViewProjectorMessage[] = [];
/**
* true if the queue might be altered
*/
public editQueue = false;
/** /**
* @param titleService * @param titleService
* @param translate * @param translate

View File

@ -0,0 +1,29 @@
//shared CSS rules that are required for all components that implement drag and drop
/**
* Moving list entries away from preview cannot be stored in this scss file, cause the naming
* of the css classes is relevant. Use it like the following.
*
* transform can somehow not be stored as css variable
*
* @example
* ```css
* .drop-list.cdk-drop-list-dragging .list-entry:not(.cdk-drag-placeholder) {
* transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
* }
*/
.cdk-drag-preview {
box-sizing: border-box;
border-radius: 4px;
background-color: white;
box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2), 0 8px 10px 1px rgba(0, 0, 0, 0.14),
0 3px 14px 2px rgba(0, 0, 0, 0.12);
}
.cdk-drag-placeholder {
opacity: 0;
}
.cdk-drag-animating {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}