Merge pull request #5392 from tsiegleauq/vote-await-server-answer
Wait for server while voting
This commit is contained in:
commit
0275df6ab2
@ -1,5 +1,5 @@
|
|||||||
<ng-container *ngIf="poll">
|
<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 -->
|
<!-- Poll hint -->
|
||||||
<p *ngIf="pollHint">
|
<p *ngIf="pollHint">
|
||||||
<i>{{ pollHint }}</i>
|
<i>{{ pollHint }}</i>
|
||||||
@ -9,9 +9,7 @@
|
|||||||
<h4 *ngIf="poll.pollmethod === AssignmentPollMethod.Votes && poll.votes_amount > 1">
|
<h4 *ngIf="poll.pollmethod === AssignmentPollMethod.Votes && poll.votes_amount > 1">
|
||||||
{{ 'Available votes' | translate }}:
|
{{ 'Available votes' | translate }}:
|
||||||
|
|
||||||
<b>
|
<b> {{ getVotesAvailable() }}/{{ poll.votes_amount }} </b>
|
||||||
{{ getVotesAvailable() }}/{{ poll.votes_amount }}
|
|
||||||
</b>
|
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
<!-- Options and Actions -->
|
<!-- Options and Actions -->
|
||||||
@ -39,6 +37,7 @@
|
|||||||
class="vote-button"
|
class="vote-button"
|
||||||
mat-raised-button
|
mat-raised-button
|
||||||
(click)="saveSingleVote(option.id, action.vote)"
|
(click)="saveSingleVote(option.id, action.vote)"
|
||||||
|
[disabled]="deliveringVote"
|
||||||
[ngClass]="
|
[ngClass]="
|
||||||
voteRequestData.votes[option.id] === action.vote ||
|
voteRequestData.votes[option.id] === action.vote ||
|
||||||
voteRequestData.votes[option.id] === 1
|
voteRequestData.votes[option.id] === 1
|
||||||
@ -67,6 +66,7 @@
|
|||||||
mat-raised-button
|
mat-raised-button
|
||||||
(click)="saveGlobalVote('N')"
|
(click)="saveGlobalVote('N')"
|
||||||
[ngClass]="voteRequestData.global === 'N' ? 'voted-no' : ''"
|
[ngClass]="voteRequestData.global === 'N' ? 'voted-no' : ''"
|
||||||
|
[disabled]="deliveringVote"
|
||||||
>
|
>
|
||||||
<mat-icon> thumb_down </mat-icon>
|
<mat-icon> thumb_down </mat-icon>
|
||||||
</button>
|
</button>
|
||||||
@ -103,13 +103,19 @@
|
|||||||
|
|
||||||
<ng-template #cannotVote>
|
<ng-template #cannotVote>
|
||||||
<div class="centered-button-wrapper">
|
<div class="centered-button-wrapper">
|
||||||
<div>
|
<div *ngIf="!deliveringVote">
|
||||||
<mat-icon class="vote-submitted">
|
<mat-icon class="vote-submitted">
|
||||||
check_circle
|
check_circle
|
||||||
</mat-icon>
|
</mat-icon>
|
||||||
<br />
|
<br />
|
||||||
<span>{{ 'Voting successful.' | translate }}</span>
|
<span>{{ 'Voting successful.' | 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>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
|
@ -67,3 +67,10 @@
|
|||||||
.mat-divider-horizontal {
|
.mat-divider-horizontal {
|
||||||
position: initial;
|
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 { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
import { Title } from '@angular/platform-browser';
|
import { Title } from '@angular/platform-browser';
|
||||||
|
|
||||||
@ -29,7 +29,8 @@ interface VoteActions {
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'os-assignment-poll-vote',
|
selector: 'os-assignment-poll-vote',
|
||||||
templateUrl: './assignment-poll-vote.component.html',
|
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 {
|
export class AssignmentPollVoteComponent extends BasePollVoteComponent<ViewAssignmentPoll> implements OnInit {
|
||||||
public AssignmentPollMethod = AssignmentPollMethod;
|
public AssignmentPollMethod = AssignmentPollMethod;
|
||||||
@ -47,7 +48,8 @@ export class AssignmentPollVoteComponent extends BasePollVoteComponent<ViewAssig
|
|||||||
operator: OperatorService,
|
operator: OperatorService,
|
||||||
public vmanager: VotingService,
|
public vmanager: VotingService,
|
||||||
private pollRepo: AssignmentPollRepositoryService,
|
private pollRepo: AssignmentPollRepositoryService,
|
||||||
private promptService: PromptService
|
private promptService: PromptService,
|
||||||
|
private cd: ChangeDetectorRef
|
||||||
) {
|
) {
|
||||||
super(title, translate, matSnackbar, operator);
|
super(title, translate, matSnackbar, operator);
|
||||||
}
|
}
|
||||||
@ -58,6 +60,7 @@ export class AssignmentPollVoteComponent extends BasePollVoteComponent<ViewAssig
|
|||||||
this.defineVoteOptions();
|
this.defineVoteOptions();
|
||||||
} else {
|
} else {
|
||||||
this.alreadyVoted = true;
|
this.alreadyVoted = true;
|
||||||
|
this.cd.markForCheck();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,19 +107,23 @@ export class AssignmentPollVoteComponent extends BasePollVoteComponent<ViewAssig
|
|||||||
return !!this.voteRequestData.global;
|
return !!this.voteRequestData.global;
|
||||||
}
|
}
|
||||||
|
|
||||||
public submitVote(): void {
|
public async submitVote(): Promise<void> {
|
||||||
const title = this.translate.instant('Submit selection now?');
|
const title = this.translate.instant('Submit selection now?');
|
||||||
const content = this.translate.instant('Your decision cannot be changed afterwards.');
|
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) {
|
if (confirmed) {
|
||||||
this.pollRepo
|
this.deliveringVote = true;
|
||||||
.vote(this.voteRequestData, this.poll.id)
|
this.cd.markForCheck();
|
||||||
.then(() => {
|
this.pollRepo
|
||||||
this.alreadyVoted = true;
|
.vote(this.voteRequestData, this.poll.id)
|
||||||
})
|
.then(() => {
|
||||||
.catch(this.raiseError);
|
this.alreadyVoted = true;
|
||||||
}
|
})
|
||||||
});
|
.catch(this.raiseError)
|
||||||
|
.finally(() => {
|
||||||
|
this.deliveringVote = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public saveSingleVote(optionId: number, vote: VoteValue): void {
|
public saveSingleVote(optionId: number, vote: VoteValue): void {
|
||||||
|
@ -42,10 +42,16 @@
|
|||||||
mat-stroked-button
|
mat-stroked-button
|
||||||
[ngClass]="pollStateActions[poll.state].css"
|
[ngClass]="pollStateActions[poll.state].css"
|
||||||
(click)="changeState(poll.nextState)"
|
(click)="changeState(poll.nextState)"
|
||||||
|
[disabled]="stateChangePending"
|
||||||
>
|
>
|
||||||
<mat-icon> {{ pollStateActions[poll.state].icon }}</mat-icon>
|
<mat-icon> {{ pollStateActions[poll.state].icon }}</mat-icon>
|
||||||
<span class="next-state-label">
|
<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>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,19 +2,26 @@
|
|||||||
<os-poll-progress [poll]="poll"></os-poll-progress>
|
<os-poll-progress [poll]="poll"></os-poll-progress>
|
||||||
</div>
|
</div>
|
||||||
<ng-container *ngIf="poll && !poll.user_has_voted; else userHasVotes">
|
<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 -->
|
<!-- Voting -->
|
||||||
<div class="vote-button" *ngFor="let option of voteOptions">
|
<div class="vote-button" *ngFor="let option of voteOptions">
|
||||||
<button
|
<button
|
||||||
mat-raised-button
|
mat-raised-button
|
||||||
(click)="saveVote(option.vote)"
|
(click)="saveVote(option.vote)"
|
||||||
[ngClass]="currentVote && currentVote.vote === option.vote ? option.css : ''"
|
[ngClass]="currentVote && currentVote.vote === option.vote ? option.css : ''"
|
||||||
|
[disabled]="deliveringVote"
|
||||||
>
|
>
|
||||||
<mat-icon> {{ option.icon }}</mat-icon>
|
<mat-icon> {{ option.icon }}</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
<span class="vote-label"> {{ option.label | translate }} </span>
|
<span class="vote-label"> {{ option.label | translate }} </span>
|
||||||
</div>
|
</div>
|
||||||
</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-container>
|
||||||
|
|
||||||
<ng-template #userHasVotes>
|
<ng-template #userHasVotes>
|
||||||
|
@ -8,6 +8,14 @@
|
|||||||
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.submit-vote-indicator {
|
||||||
|
margin-top: 1em;
|
||||||
|
text-align: center;
|
||||||
|
.mat-spinner {
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.vote-button {
|
.vote-button {
|
||||||
display: inline-grid;
|
display: inline-grid;
|
||||||
grid-gap: 1em;
|
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 { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
import { Title } from '@angular/platform-browser';
|
import { Title } from '@angular/platform-browser';
|
||||||
|
|
||||||
@ -22,7 +22,8 @@ interface VoteOption {
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'os-motion-poll-vote',
|
selector: 'os-motion-poll-vote',
|
||||||
templateUrl: './motion-poll-vote.component.html',
|
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> {
|
export class MotionPollVoteComponent extends BasePollVoteComponent<ViewMotionPoll> {
|
||||||
public currentVote: VoteOption = {};
|
public currentVote: VoteOption = {};
|
||||||
@ -54,19 +55,28 @@ export class MotionPollVoteComponent extends BasePollVoteComponent<ViewMotionPol
|
|||||||
operator: OperatorService,
|
operator: OperatorService,
|
||||||
public vmanager: VotingService,
|
public vmanager: VotingService,
|
||||||
private pollRepo: MotionPollRepositoryService,
|
private pollRepo: MotionPollRepositoryService,
|
||||||
private promptService: PromptService
|
private promptService: PromptService,
|
||||||
|
private cd: ChangeDetectorRef
|
||||||
) {
|
) {
|
||||||
super(title, translate, matSnackbar, operator);
|
super(title, translate, matSnackbar, operator);
|
||||||
}
|
}
|
||||||
|
|
||||||
public saveVote(vote: VoteValue): void {
|
public async saveVote(vote: VoteValue): Promise<void> {
|
||||||
this.currentVote.vote = vote;
|
this.currentVote.vote = vote;
|
||||||
const title = this.translate.instant('Submit selection now?');
|
const title = this.translate.instant('Submit selection now?');
|
||||||
const content = this.translate.instant('Your decision cannot be changed afterwards.');
|
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);
|
if (confirmed) {
|
||||||
}
|
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 -->
|
<!-- Change state button -->
|
||||||
<div *osPerms="'motions.can_manage_polls'; and: !hideChangeState">
|
<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>
|
<mat-icon> {{ pollStateActions[poll.state].icon }}</mat-icon>
|
||||||
<span class="next-state-label">
|
<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>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -16,6 +16,8 @@ export abstract class BasePollVoteComponent<V extends ViewBasePoll> extends Base
|
|||||||
|
|
||||||
public votingErrors = VotingError;
|
public votingErrors = VotingError;
|
||||||
|
|
||||||
|
public deliveringVote = false;
|
||||||
|
|
||||||
protected user: ViewUser;
|
protected user: ViewUser;
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
|
@ -15,6 +15,8 @@ import { PollService } from '../services/poll.service';
|
|||||||
import { ViewBasePoll } from '../models/view-base-poll';
|
import { ViewBasePoll } from '../models/view-base-poll';
|
||||||
|
|
||||||
export abstract class BasePollComponent<V extends ViewBasePoll, S extends PollService> extends BaseViewComponent {
|
export abstract class BasePollComponent<V extends ViewBasePoll, S extends PollService> extends BaseViewComponent {
|
||||||
|
public stateChangePending = false;
|
||||||
|
|
||||||
public chartDataSubject: BehaviorSubject<ChartData> = new BehaviorSubject([]);
|
public chartDataSubject: BehaviorSubject<ChartData> = new BehaviorSubject([]);
|
||||||
|
|
||||||
protected _poll: V;
|
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 title = this.translate.instant('Are you sure you want to reset this vote?');
|
||||||
const content = this.translate.instant('All votes will be lost.');
|
const content = this.translate.instant('All votes will be lost.');
|
||||||
if (await this.promptService.open(title, content)) {
|
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 {
|
} 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 */
|
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 {
|
.import-table {
|
||||||
.table-container {
|
.table-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
Loading…
Reference in New Issue
Block a user