Merge pull request #5392 from tsiegleauq/vote-await-server-answer

Wait for server while voting
This commit is contained in:
Emanuel Schütze 2020-06-03 17:35:19 +02:00 committed by GitHub
commit 0275df6ab2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 117 additions and 34 deletions

View File

@ -1,5 +1,5 @@
<ng-container *ngIf="poll">
<ng-container *ngIf="vmanager.canVote(poll) && !alreadyVoted; else cannotVote">
<ng-container *ngIf="vmanager.canVote(poll) && !alreadyVoted && !deliveringVote; else cannotVote">
<!-- Poll hint -->
<p *ngIf="pollHint">
<i>{{ pollHint }}</i>
@ -9,9 +9,7 @@
<h4 *ngIf="poll.pollmethod === AssignmentPollMethod.Votes && poll.votes_amount > 1">
{{ 'Available votes' | translate }}:
<b>
{{ getVotesAvailable() }}/{{ poll.votes_amount }}
</b>
<b> {{ getVotesAvailable() }}/{{ poll.votes_amount }} </b>
</h4>
<!-- Options and Actions -->
@ -39,6 +37,7 @@
class="vote-button"
mat-raised-button
(click)="saveSingleVote(option.id, action.vote)"
[disabled]="deliveringVote"
[ngClass]="
voteRequestData.votes[option.id] === action.vote ||
voteRequestData.votes[option.id] === 1
@ -67,6 +66,7 @@
mat-raised-button
(click)="saveGlobalVote('N')"
[ngClass]="voteRequestData.global === 'N' ? 'voted-no' : ''"
[disabled]="deliveringVote"
>
<mat-icon> thumb_down </mat-icon>
</button>
@ -103,13 +103,19 @@
<ng-template #cannotVote>
<div class="centered-button-wrapper">
<div>
<div *ngIf="!deliveringVote">
<mat-icon class="vote-submitted">
check_circle
</mat-icon>
<br />
<span>{{ 'Voting successful.' | translate }}</span>
</div>
<div *ngIf="deliveringVote" class="submit-vote-indicator">
<mat-spinner class="small-spinner"></mat-spinner>
<br />
<span>{{ 'Delivering vote... Please wait!' | translate }}</span>
</div>
</div>
</ng-template>

View File

@ -67,3 +67,10 @@
.mat-divider-horizontal {
position: initial;
}
.submit-vote-indicator {
text-align: center;
.mat-spinner {
margin: auto;
}
}

View File

@ -1,4 +1,4 @@
import { Component, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import { Title } from '@angular/platform-browser';
@ -29,7 +29,8 @@ interface VoteActions {
@Component({
selector: 'os-assignment-poll-vote',
templateUrl: './assignment-poll-vote.component.html',
styleUrls: ['./assignment-poll-vote.component.scss']
styleUrls: ['./assignment-poll-vote.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AssignmentPollVoteComponent extends BasePollVoteComponent<ViewAssignmentPoll> implements OnInit {
public AssignmentPollMethod = AssignmentPollMethod;
@ -47,7 +48,8 @@ export class AssignmentPollVoteComponent extends BasePollVoteComponent<ViewAssig
operator: OperatorService,
public vmanager: VotingService,
private pollRepo: AssignmentPollRepositoryService,
private promptService: PromptService
private promptService: PromptService,
private cd: ChangeDetectorRef
) {
super(title, translate, matSnackbar, operator);
}
@ -58,6 +60,7 @@ export class AssignmentPollVoteComponent extends BasePollVoteComponent<ViewAssig
this.defineVoteOptions();
} else {
this.alreadyVoted = true;
this.cd.markForCheck();
}
}
@ -104,19 +107,23 @@ export class AssignmentPollVoteComponent extends BasePollVoteComponent<ViewAssig
return !!this.voteRequestData.global;
}
public submitVote(): void {
public async submitVote(): Promise<void> {
const title = this.translate.instant('Submit selection now?');
const content = this.translate.instant('Your decision cannot be changed afterwards.');
this.promptService.open(title, content).then(confirmed => {
if (confirmed) {
this.pollRepo
.vote(this.voteRequestData, this.poll.id)
.then(() => {
this.alreadyVoted = true;
})
.catch(this.raiseError);
}
});
const confirmed = await this.promptService.open(title, content);
if (confirmed) {
this.deliveringVote = true;
this.cd.markForCheck();
this.pollRepo
.vote(this.voteRequestData, this.poll.id)
.then(() => {
this.alreadyVoted = true;
})
.catch(this.raiseError)
.finally(() => {
this.deliveringVote = false;
});
}
}
public saveSingleVote(optionId: number, vote: VoteValue): void {

View File

@ -42,10 +42,16 @@
mat-stroked-button
[ngClass]="pollStateActions[poll.state].css"
(click)="changeState(poll.nextState)"
[disabled]="stateChangePending"
>
<mat-icon> {{ pollStateActions[poll.state].icon }}</mat-icon>
<span class="next-state-label">
{{ poll.nextStateActionVerbose | translate }}
<ng-container *ngIf="!stateChangePending">
{{ poll.nextStateActionVerbose | translate }}
</ng-container>
<ng-container *ngIf="stateChangePending">
{{ 'In progress, please wait...' | translate }}
</ng-container>
</span>
</button>
</div>

View File

@ -2,19 +2,26 @@
<os-poll-progress [poll]="poll"></os-poll-progress>
</div>
<ng-container *ngIf="poll && !poll.user_has_voted; else userHasVotes">
<div *ngIf="vmanager.canVote(poll)" class="vote-button-grid">
<div *ngIf="vmanager.canVote(poll) && !deliveringVote" class="vote-button-grid">
<!-- Voting -->
<div class="vote-button" *ngFor="let option of voteOptions">
<button
mat-raised-button
(click)="saveVote(option.vote)"
[ngClass]="currentVote && currentVote.vote === option.vote ? option.css : ''"
[disabled]="deliveringVote"
>
<mat-icon> {{ option.icon }}</mat-icon>
</button>
<span class="vote-label"> {{ option.label | translate }} </span>
</div>
</div>
<div *ngIf="deliveringVote" class="submit-vote-indicator">
<mat-spinner class="small-spinner"></mat-spinner>
<br />
<span>{{ 'Delivering vote... Please wait!' | translate }}</span>
</div>
</ng-container>
<ng-template #userHasVotes>

View File

@ -8,6 +8,14 @@
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
}
.submit-vote-indicator {
margin-top: 1em;
text-align: center;
.mat-spinner {
margin: auto;
}
}
.vote-button {
display: inline-grid;
grid-gap: 1em;

View File

@ -1,4 +1,4 @@
import { Component } from '@angular/core';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import { Title } from '@angular/platform-browser';
@ -22,7 +22,8 @@ interface VoteOption {
@Component({
selector: 'os-motion-poll-vote',
templateUrl: './motion-poll-vote.component.html',
styleUrls: ['./motion-poll-vote.component.scss']
styleUrls: ['./motion-poll-vote.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class MotionPollVoteComponent extends BasePollVoteComponent<ViewMotionPoll> {
public currentVote: VoteOption = {};
@ -54,19 +55,28 @@ export class MotionPollVoteComponent extends BasePollVoteComponent<ViewMotionPol
operator: OperatorService,
public vmanager: VotingService,
private pollRepo: MotionPollRepositoryService,
private promptService: PromptService
private promptService: PromptService,
private cd: ChangeDetectorRef
) {
super(title, translate, matSnackbar, operator);
}
public saveVote(vote: VoteValue): void {
public async saveVote(vote: VoteValue): Promise<void> {
this.currentVote.vote = vote;
const title = this.translate.instant('Submit selection now?');
const content = this.translate.instant('Your decision cannot be changed afterwards.');
this.promptService.open(title, content).then(confirmed => {
if (confirmed) {
this.pollRepo.vote(vote, this.poll.id).catch(this.raiseError);
}
});
const confirmed = await this.promptService.open(title, content);
if (confirmed) {
this.deliveringVote = true;
this.cd.markForCheck();
this.pollRepo
.vote(vote, this.poll.id)
.catch(this.raiseError)
.finally(() => {
this.deliveringVote = false;
});
}
}
}

View File

@ -42,10 +42,15 @@
<!-- Change state button -->
<div *osPerms="'motions.can_manage_polls'; and: !hideChangeState">
<button mat-stroked-button [ngClass]="pollStateActions[poll.state].css" (click)="changeState(poll.nextState)">
<button mat-stroked-button [ngClass]="pollStateActions[poll.state].css" (click)="changeState(poll.nextState)" [disabled]="stateChangePending">
<mat-icon> {{ pollStateActions[poll.state].icon }}</mat-icon>
<span class="next-state-label">
{{ poll.nextStateActionVerbose | translate }}
<ng-container *ngIf="!stateChangePending">
{{ poll.nextStateActionVerbose | translate }}
</ng-container>
<ng-container *ngIf="stateChangePending">
{{ 'In progress, please wait...' | translate }}
</ng-container>
</span>
</button>
</div>

View File

@ -16,6 +16,8 @@ export abstract class BasePollVoteComponent<V extends ViewBasePoll> extends Base
public votingErrors = VotingError;
public deliveringVote = false;
protected user: ViewUser;
public constructor(

View File

@ -15,6 +15,8 @@ import { PollService } from '../services/poll.service';
import { ViewBasePoll } from '../models/view-base-poll';
export abstract class BasePollComponent<V extends ViewBasePoll, S extends PollService> extends BaseViewComponent {
public stateChangePending = false;
public chartDataSubject: BehaviorSubject<ChartData> = new BehaviorSubject([]);
protected _poll: V;
@ -55,10 +57,22 @@ export abstract class BasePollComponent<V extends ViewBasePoll, S extends PollSe
const title = this.translate.instant('Are you sure you want to reset this vote?');
const content = this.translate.instant('All votes will be lost.');
if (await this.promptService.open(title, content)) {
this.repo.resetPoll(this._poll).catch(this.raiseError);
this.stateChangePending = true;
this.repo
.resetPoll(this._poll)
.catch(this.raiseError)
.finally(() => {
this.stateChangePending = false;
});
}
} else {
this.repo.changePollState(this._poll).catch(this.raiseError);
this.stateChangePending = true;
this.repo
.changePollState(this._poll)
.catch(this.raiseError)
.finally(() => {
this.stateChangePending = false;
});
}
}

View File

@ -778,6 +778,17 @@ button.mat-menu-item.selected {
display: none !important; /* hide scrollbars in webkit browsers */
}
.small-spinner {
// 24px is the size of a normal icon
$spinner-size: 24px;
height: $spinner-size !important;
height: $spinner-size !important;
svg {
height: $spinner-size !important;
height: $spinner-size !important;
}
}
.import-table {
.table-container {
width: 100%;