Wait for server while voting
Blocks voting state changes and prevents the user from sending multiple vote values while the server is not responding during voting
This commit is contained in:
parent
7665634d42
commit
dced8fbcc7
@ -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>
|
||||
|
||||
|
@ -67,3 +67,10 @@
|
||||
.mat-divider-horizontal {
|
||||
position: initial;
|
||||
}
|
||||
|
||||
.submit-vote-indicator {
|
||||
text-align: center;
|
||||
.mat-spinner {
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
|
@ -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,20 +107,24 @@ 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 => {
|
||||
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);
|
||||
}
|
||||
.catch(this.raiseError)
|
||||
.finally(() => {
|
||||
this.deliveringVote = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public saveSingleVote(optionId: number, vote: VoteValue): void {
|
||||
if (this.isGlobalOptionSelected()) {
|
||||
|
@ -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">
|
||||
<ng-container *ngIf="!stateChangePending">
|
||||
{{ poll.nextStateActionVerbose | translate }}
|
||||
</ng-container>
|
||||
<ng-container *ngIf="stateChangePending">
|
||||
{{ 'In progress, please wait...' | translate }}
|
||||
</ng-container>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
@ -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 => {
|
||||
const confirmed = await this.promptService.open(title, content);
|
||||
|
||||
if (confirmed) {
|
||||
this.pollRepo.vote(vote, this.poll.id).catch(this.raiseError);
|
||||
}
|
||||
this.deliveringVote = true;
|
||||
this.cd.markForCheck();
|
||||
|
||||
this.pollRepo
|
||||
.vote(vote, this.poll.id)
|
||||
.catch(this.raiseError)
|
||||
.finally(() => {
|
||||
this.deliveringVote = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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">
|
||||
<ng-container *ngIf="!stateChangePending">
|
||||
{{ poll.nextStateActionVerbose | translate }}
|
||||
</ng-container>
|
||||
<ng-container *ngIf="stateChangePending">
|
||||
{{ 'In progress, please wait...' | translate }}
|
||||
</ng-container>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
@ -16,6 +16,8 @@ export abstract class BasePollVoteComponent<V extends ViewBasePoll> extends Base
|
||||
|
||||
public votingErrors = VotingError;
|
||||
|
||||
public deliveringVote = false;
|
||||
|
||||
protected user: ViewUser;
|
||||
|
||||
public constructor(
|
||||
|
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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%;
|
||||
|
Loading…
Reference in New Issue
Block a user