From 84a39ccb621fe966373ae09928c0fd1e9e4abf2b Mon Sep 17 00:00:00 2001 From: Sean Engelhardt Date: Wed, 22 Jan 2020 17:39:10 +0100 Subject: [PATCH] More voting UI improvements For Motion poll: - Overworked how motion poll chart displays the legend - Added the vote counter to the motion detail - Added a progress bar to the vote counter - Fixed some perm errors with the chart - Show a "Singe Votes" link as button for published named polls - Replace the edit-button with a dot-menu - Having project, Edit, PDF and Delete For Motion Poll detail: - enhance search panel - Remove the breadcrumbs - Remove the vote counter - Enhanced the single-vote grid, table and filter bar - Enhance how the poll state enum was checkend For the Motion Poll Create/Update Form: - Remove the selection of poll-methode (whenever possible) - only show "publish imediately" during creation --- client/src/app/app.component.ts | 14 ++- .../pdf-services/base-poll-pdf-service.ts | 1 + .../src/app/shared/models/poll/base-poll.ts | 16 ++++ .../src/app/shared/models/poll/base-vote.ts | 6 +- .../pipes/parse-poll-number.pipe.spec.ts | 20 +++++ .../shared/pipes/parse-poll-number.pipe.ts | 22 +++++ .../src/app/shared/pipes/reverse.pipe.spec.ts | 8 ++ client/src/app/shared/pipes/reverse.pipe.ts | 20 +++++ client/src/app/shared/shared.module.ts | 14 ++- .../assignment-detail.component.html | 22 ++--- .../assignment-poll-detail.component.html | 10 ++- .../assignment-poll-dialog.component.html | 6 +- .../assignment-poll.component.html | 60 ++++++------- .../assignment-poll.component.scss | 60 +------------ .../services/assignment-poll.service.ts | 2 +- .../config-overview.component.html | 1 + .../motion-detail.component.html | 2 +- .../motion-poll-detail.component.html | 46 +++++----- .../motion-poll-detail.component.scss | 4 + .../motion-poll-dialog.component.html | 16 ++-- .../motion-poll-dialog.component.ts | 7 +- .../motion-poll-vote.component.html | 4 +- .../motion-poll-vote.component.scss | 11 ++- .../motion-poll/motion-poll.component.html | 85 +++++++++++++------ .../motion-poll/motion-poll.component.scss | 40 +++++++-- .../motion-poll/motion-poll.component.ts | 76 +++++++++++------ .../site/motions/motions-routing.module.ts | 2 +- .../services/motion-poll-pdf.service.ts | 2 +- .../components/base-poll-detail.component.ts | 12 +-- .../poll-form/poll-form.component.html | 4 +- .../poll-progress.component.html | 4 +- .../poll-progress/poll-progress.component.ts | 4 + .../app/site/polls/models/view-base-poll.ts | 15 ++-- client/src/assets/styles/poll-colors.scss | 6 ++ openslides/assignments/config_variables.py | 9 +- .../assignments/migrations/0009_voting_2.py | 2 +- ...tor_size_1.py => 0026_projector_size_1.py} | 2 +- ...tor_size_2.py => 0027_projector_size_2.py} | 2 +- ...tor_size_3.py => 0028_projector_size_3.py} | 2 +- ...d.py => 0029_remove_history_restricted.py} | 2 +- openslides/motions/config_variables.py | 11 +-- 41 files changed, 400 insertions(+), 252 deletions(-) create mode 100644 client/src/app/shared/pipes/parse-poll-number.pipe.spec.ts create mode 100644 client/src/app/shared/pipes/parse-poll-number.pipe.ts create mode 100644 client/src/app/shared/pipes/reverse.pipe.spec.ts create mode 100644 client/src/app/shared/pipes/reverse.pipe.ts create mode 100644 client/src/assets/styles/poll-colors.scss rename openslides/core/migrations/{0027_projector_size_1.py => 0026_projector_size_1.py} (91%) rename openslides/core/migrations/{0028_projector_size_2.py => 0027_projector_size_2.py} (96%) rename openslides/core/migrations/{0029_projector_size_3.py => 0028_projector_size_3.py} (85%) rename openslides/core/migrations/{0026_remove_history_restricted.py => 0029_remove_history_restricted.py} (79%) diff --git a/client/src/app/app.component.ts b/client/src/app/app.component.ts index e24171a4f..2730ea888 100644 --- a/client/src/app/app.component.ts +++ b/client/src/app/app.component.ts @@ -95,7 +95,6 @@ export class AppComponent { translate.use(translate.getLangs().includes(browserLang) ? browserLang : 'en'); // change default JS functions - this.overloadArrayToString(); this.overloadArrayFunctions(); this.overloadModulo(); @@ -118,7 +117,7 @@ export class AppComponent { * * TODO: Should be renamed */ - private overloadArrayToString(): void { + private overloadArrayFunctions(): void { Object.defineProperty(Array.prototype, 'toString', { value: function(): string { let string = ''; @@ -139,12 +138,7 @@ export class AppComponent { }, enumerable: false }); - } - /** - * Adds an implementation of flatMap and intersect. - */ - private overloadFlatMap(): void { Object.defineProperty(Array.prototype, 'flatMap', { value: function(o: any): any[] { const concatFunction = (x: any, y: any[]) => x.concat(y); @@ -154,10 +148,11 @@ export class AppComponent { enumerable: false }); + // intersect Object.defineProperty(Array.prototype, 'intersect', { value: function(other: T[]): T[] { - let a = this, - b = other; + let a = this; + let b = other; // indexOf to loop over shorter if (b.length > a.length) { [a, b] = [b, a]; @@ -167,6 +162,7 @@ export class AppComponent { enumerable: false }); + // mapToObject Object.defineProperty(Array.prototype, 'mapToObject', { value: function(f: (item: T) => { [key: string]: any }): { [key: string]: any } { return this.reduce((aggr, item) => { diff --git a/client/src/app/core/pdf-services/base-poll-pdf-service.ts b/client/src/app/core/pdf-services/base-poll-pdf-service.ts index 5e2e5de9e..d59a8c7e2 100644 --- a/client/src/app/core/pdf-services/base-poll-pdf-service.ts +++ b/client/src/app/core/pdf-services/base-poll-pdf-service.ts @@ -71,6 +71,7 @@ export abstract class PollPdfService { * @returns the amount of ballots, depending on the config settings */ protected getBallotCount(): number { + // TODO: seems to be broken switch (this.ballotCountSelection) { case 'NUMBER_OF_ALL_PARTICIPANTS': return this.userRepo.getViewModelList().length; diff --git a/client/src/app/shared/models/poll/base-poll.ts b/client/src/app/shared/models/poll/base-poll.ts index be98afe08..9aec1d218 100644 --- a/client/src/app/shared/models/poll/base-poll.ts +++ b/client/src/app/shared/models/poll/base-poll.ts @@ -52,6 +52,22 @@ export abstract class BasePoll = any> extends public onehundred_percent_base: PercentBase; public user_has_voted: boolean; + public get isStateCreated(): boolean { + return this.state === PollState.Created; + } + + public get isStateStarted(): boolean { + return this.state === PollState.Started; + } + + public get isStateFinished(): boolean { + return this.state === PollState.Finished; + } + + public get isStatePublished(): boolean { + return this.state === PollState.Published; + } + /** * Determine if the state is finished or published */ diff --git a/client/src/app/shared/models/poll/base-vote.ts b/client/src/app/shared/models/poll/base-vote.ts index 91325f4bc..f1aee007b 100644 --- a/client/src/app/shared/models/poll/base-vote.ts +++ b/client/src/app/shared/models/poll/base-vote.ts @@ -9,9 +9,9 @@ export const VoteValueVerbose = { }; export const GeneralValueVerbose = { - votesvalid: 'Votes valid', - votesinvalid: 'Votes invalid', - votescast: 'Votes cast', + votesvalid: 'Valid votes', + votesinvalid: 'Invalid votes', + votescast: 'Total votes cast', votesno: 'Votes No', votesabstain: 'Votes abstain' }; diff --git a/client/src/app/shared/pipes/parse-poll-number.pipe.spec.ts b/client/src/app/shared/pipes/parse-poll-number.pipe.spec.ts new file mode 100644 index 000000000..5e2523140 --- /dev/null +++ b/client/src/app/shared/pipes/parse-poll-number.pipe.spec.ts @@ -0,0 +1,20 @@ +import { inject, TestBed } from '@angular/core/testing'; + +import { TranslateModule, TranslateService } from '@ngx-translate/core'; + +import { ParsePollNumberPipe } from './parse-poll-number.pipe'; + +describe('ParsePollNumberPipe', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot()], + declarations: [ParsePollNumberPipe] + }); + TestBed.compileComponents(); + }); + + it('create an instance', inject([TranslateService], (translate: TranslateService) => { + const pipe = new ParsePollNumberPipe(translate); + expect(pipe).toBeTruthy(); + })); +}); diff --git a/client/src/app/shared/pipes/parse-poll-number.pipe.ts b/client/src/app/shared/pipes/parse-poll-number.pipe.ts new file mode 100644 index 000000000..c8c558a7d --- /dev/null +++ b/client/src/app/shared/pipes/parse-poll-number.pipe.ts @@ -0,0 +1,22 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +import { TranslateService } from '@ngx-translate/core'; + +@Pipe({ + name: 'parsePollNumber' +}) +export class ParsePollNumberPipe implements PipeTransform { + public constructor(private translate: TranslateService) {} + + public transform(value: number): number | string { + const input = Math.trunc(value); + switch (input) { + case -1: + return this.translate.instant('majority'); + case -2: + return this.translate.instant('undocumented'); + default: + return input; + } + } +} diff --git a/client/src/app/shared/pipes/reverse.pipe.spec.ts b/client/src/app/shared/pipes/reverse.pipe.spec.ts new file mode 100644 index 000000000..fc22c38d6 --- /dev/null +++ b/client/src/app/shared/pipes/reverse.pipe.spec.ts @@ -0,0 +1,8 @@ +import { ReversePipe } from './reverse.pipe'; + +describe('ReversePipe', () => { + it('create an instance', () => { + const pipe = new ReversePipe(); + expect(pipe).toBeTruthy(); + }); +}); diff --git a/client/src/app/shared/pipes/reverse.pipe.ts b/client/src/app/shared/pipes/reverse.pipe.ts new file mode 100644 index 000000000..9e52edc6c --- /dev/null +++ b/client/src/app/shared/pipes/reverse.pipe.ts @@ -0,0 +1,20 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +/** + * Invert the order of arrays in templates + * + * @example + * ```html + *
  • + * {{ user.name }} has the id: {{ user.id }} + *
  • + * ``` + */ +@Pipe({ + name: 'reverse' +}) +export class ReversePipe implements PipeTransform { + public transform(value: any[]): any[] { + return value.slice().reverse(); + } +} diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts index 696293c3c..319640470 100644 --- a/client/src/app/shared/shared.module.ts +++ b/client/src/app/shared/shared.module.ts @@ -119,6 +119,8 @@ import { BasePollDialogComponent } from 'app/site/polls/components/base-poll-dia import { PollFormComponent } from 'app/site/polls/components/poll-form/poll-form.component'; import { MotionPollDialogComponent } from 'app/site/motions/modules/motion-poll/motion-poll-dialog/motion-poll-dialog.component'; import { AssignmentPollDialogComponent } from 'app/site/assignments/components/assignment-poll-dialog/assignment-poll-dialog.component'; +import { ParsePollNumberPipe } from './pipes/parse-poll-number.pipe'; +import { ReversePipe } from './pipes/reverse.pipe'; /** * Share Module for all "dumb" components and pipes. @@ -279,7 +281,9 @@ import { AssignmentPollDialogComponent } from 'app/site/assignments/components/a BannerComponent, PollFormComponent, MotionPollDialogComponent, - AssignmentPollDialogComponent + AssignmentPollDialogComponent, + ParsePollNumberPipe, + ReversePipe ], declarations: [ PermsDirective, @@ -333,7 +337,9 @@ import { AssignmentPollDialogComponent } from 'app/site/assignments/components/a BannerComponent, PollFormComponent, MotionPollDialogComponent, - AssignmentPollDialogComponent + AssignmentPollDialogComponent, + ParsePollNumberPipe, + ReversePipe ], providers: [ { @@ -349,7 +355,9 @@ import { AssignmentPollDialogComponent } from 'app/site/assignments/components/a DecimalPipe, ProgressSnackBarComponent, TrustPipe, - LocalizedDatePipe + LocalizedDatePipe, + ParsePollNumberPipe, + ReversePipe ], entryComponents: [ SortBottomSheetComponent, diff --git a/client/src/app/site/assignments/components/assignment-detail/assignment-detail.component.html b/client/src/app/site/assignments/components/assignment-detail/assignment-detail.component.html index 96cb6589c..eb82c8f25 100644 --- a/client/src/app/site/assignments/components/assignment-detail/assignment-detail.component.html +++ b/client/src/app/site/assignments/components/assignment-detail/assignment-detail.component.html @@ -65,20 +65,12 @@
    - - - - - - - - - - + + @@ -135,13 +127,17 @@ - - + +

    Candidates

    {{ group.getTitle() | translate }}
    -
    {{ 'Poll type' | translate }}: {{ poll.typeVerbose | translate }}
    -
    {{ 'Poll method' | translate }}: {{ poll.pollmethodVerbose | translate }}
    -
    {{ 'Majority method' | translate }}: {{ poll.majorityMethodVerbose | translate }}
    +
    {{ 'Voting type' | translate }}: {{ poll.typeVerbose | translate }}
    +
    {{ 'Election method' | translate }}: {{ poll.pollmethodVerbose | translate }}
    +
    {{ 'Required majority' | translate }}: {{ poll.majorityMethodVerbose | translate }}
    {{ '100% base' | translate }}: {{ poll.percentBaseVerbose | translate }}
    + +
    -
    +

    Result

    diff --git a/client/src/app/site/assignments/components/assignment-poll-dialog/assignment-poll-dialog.component.html b/client/src/app/site/assignments/components/assignment-poll-dialog/assignment-poll-dialog.component.html index e984aca1f..f67ea8518 100644 --- a/client/src/app/site/assignments/components/assignment-poll-dialog/assignment-poll-dialog.component.html +++ b/client/src/app/site/assignments/components/assignment-poll-dialog/assignment-poll-dialog.component.html @@ -8,14 +8,14 @@ {{ option.user.getFullName() }} No user {{ option.candidate_id }}
    - +
    @@ -28,7 +28,7 @@ [placeholder]="generalValueVerbose[value] | translate" [checkboxValue]="-1" inputType="number" - [checkboxLabel]="'Majority' | translate" + [checkboxLabel]="'majority' | translate" [formControlName]="value" >
    diff --git a/client/src/app/site/assignments/components/assignment-poll/assignment-poll.component.html b/client/src/app/site/assignments/components/assignment-poll/assignment-poll.component.html index f1543a869..db66adf8f 100644 --- a/client/src/app/site/assignments/components/assignment-poll/assignment-poll.component.html +++ b/client/src/app/site/assignments/components/assignment-poll/assignment-poll.component.html @@ -1,4 +1,10 @@
    + +
    + +
    + +
    - -
    - -
    -
    - -
    -
    - - -
    -
    - - + + +
    + +
    +
    + +
    +
    + + +
    +
    diff --git a/client/src/app/site/assignments/components/assignment-poll/assignment-poll.component.scss b/client/src/app/site/assignments/components/assignment-poll/assignment-poll.component.scss index 7c8b40b80..bcc96c384 100644 --- a/client/src/app/site/assignments/components/assignment-poll/assignment-poll.component.scss +++ b/client/src/app/site/assignments/components/assignment-poll/assignment-poll.component.scss @@ -3,33 +3,10 @@ position: relative; padding: 0 15px; - .poll-main-content { - padding-top: 10px; - } - - .poll-grid { - display: grid; - grid-gap: 5px; - padding: 5px; - grid-template-columns: 30px auto 250px 150px; - .candidate-name { - word-wrap: break-word; - } - } - - .right-align { - text-align: right; - } - - .vote-input { - .mat-form-field-wrapper { - // padding-bottom: 0; - - .mat-form-field-infix { - width: 60px; - border-top: 0; - } - } + .poll-menu { + position: absolute; + top: 0; + right: 0; } .poll-properties { @@ -62,33 +39,4 @@ } } } - - .poll-menu { - position: absolute; - top: 0; - right: 0; - } - - .poll-quorum { - text-align: right; - margin-right: 10px; - mat-icon { - vertical-align: middle; - font-size: 100%; - } - } - - .top-aligned { - position: absolute; - top: 0; - left: 0; - } - - .wide { - width: 90%; - } - - .hint-form { - margin-top: 20px; - } } diff --git a/client/src/app/site/assignments/services/assignment-poll.service.ts b/client/src/app/site/assignments/services/assignment-poll.service.ts index d3ad17b52..13d315b1a 100644 --- a/client/src/app/site/assignments/services/assignment-poll.service.ts +++ b/client/src/app/site/assignments/services/assignment-poll.service.ts @@ -49,7 +49,7 @@ export class AssignmentPollService extends PollService { const length = this.pollRepo.getViewModelList().filter(item => item.assignment_id === poll.assignment_id) .length; - poll.title = !length ? this.translate.instant('Vote') : `${this.translate.instant('Vote')} (${length + 1})`; + poll.title = !length ? this.translate.instant('Ballot') : `${this.translate.instant('Ballot')} (${length + 1})`; poll.pollmethod = AssignmentPollMethods.YN; poll.assignment_id = poll.assignment_id; } diff --git a/client/src/app/site/config/components/config-overview/config-overview.component.html b/client/src/app/site/config/components/config-overview/config-overview.component.html index 140deffee..30b0f6261 100644 --- a/client/src/app/site/config/components/config-overview/config-overview.component.html +++ b/client/src/app/site/config/components/config-overview/config-overview.component.html @@ -25,6 +25,7 @@ home today assignment + pie_chart how_to_vote groups language diff --git a/client/src/app/site/motions/modules/motion-detail/components/motion-detail/motion-detail.component.html b/client/src/app/site/motions/modules/motion-detail/components/motion-detail/motion-detail.component.html index 433dcee23..41cf9a52b 100644 --- a/client/src/app/site/motions/modules/motion-detail/components/motion-detail/motion-detail.component.html +++ b/client/src/app/site/motions/modules/motion-detail/components/motion-detail/motion-detail.component.html @@ -463,7 +463,7 @@
    diff --git a/client/src/app/site/motions/modules/motion-poll/motion-poll-detail/motion-poll-detail.component.html b/client/src/app/site/motions/modules/motion-poll/motion-poll-detail/motion-poll-detail.component.html index de621c05b..fb4b65c77 100644 --- a/client/src/app/site/motions/modules/motion-poll/motion-poll-detail/motion-poll-detail.component.html +++ b/client/src/app/site/motions/modules/motion-poll/motion-poll-detail/motion-poll-detail.component.html @@ -1,9 +1,9 @@
    -

    {{ 'Motion' | translate }} {{ poll.motion.id }}

    +

    {{ 'Motion' | translate }} {{ poll.motion.identifierOrTitle }}

    - - -
    - -
    - - - diff --git a/client/src/app/site/motions/modules/motion-poll/motion-poll-detail/motion-poll-detail.component.scss b/client/src/app/site/motions/modules/motion-poll/motion-poll-detail/motion-poll-detail.component.scss index f09ea5838..355c9138a 100644 --- a/client/src/app/site/motions/modules/motion-poll/motion-poll-detail/motion-poll-detail.component.scss +++ b/client/src/app/site/motions/modules/motion-poll/motion-poll-detail/motion-poll-detail.component.scss @@ -35,4 +35,8 @@ .named-result-table { grid-area: names; + .mat-form-field { + font-size: 14px; + width: 100%; + } } diff --git a/client/src/app/site/motions/modules/motion-poll/motion-poll-dialog/motion-poll-dialog.component.html b/client/src/app/site/motions/modules/motion-poll/motion-poll-dialog/motion-poll-dialog.component.html index 0c04fa638..dbf278737 100644 --- a/client/src/app/site/motions/modules/motion-poll/motion-poll-dialog/motion-poll-dialog.component.html +++ b/client/src/app/site/motions/modules/motion-poll/motion-poll-dialog/motion-poll-dialog.component.html @@ -6,41 +6,43 @@ [placeholder]="'Yes' | translate" [checkboxValue]="-1" inputType="number" - [checkboxLabel]="'Majority' | translate" + [checkboxLabel]="'majority' | translate" formControlName="Y" >
    -
    + + +
    Publish immediately diff --git a/client/src/app/site/motions/modules/motion-poll/motion-poll-dialog/motion-poll-dialog.component.ts b/client/src/app/site/motions/modules/motion-poll/motion-poll-dialog/motion-poll-dialog.component.ts index 17954a2c5..652450db5 100644 --- a/client/src/app/site/motions/modules/motion-poll/motion-poll-dialog/motion-poll-dialog.component.ts +++ b/client/src/app/site/motions/modules/motion-poll/motion-poll-dialog/motion-poll-dialog.component.ts @@ -42,9 +42,6 @@ export class MotionPollDialogComponent extends BasePollDialogComponent { votesinvalid: data.votesinvalid, votescast: data.votescast }; - // if (data.pollmethod === 'YNA') { - // update.A = data.options[0].abstain; - // } if (this.dialogVoteForm) { const result = this.undoReplaceEmptyValues(update); @@ -64,9 +61,7 @@ export class MotionPollDialogComponent extends BasePollDialogComponent { votesinvalid: ['', [Validators.min(-2)]], votescast: ['', [Validators.min(-2)]] }); - // if (this.pollData.pollmethod === MotionPollMethods.YNA) { - // this.dialogVoteForm.addControl('A', this.fb.control('', [Validators.min(-2)])); - // } + if (this.pollData.poll) { this.updateDialogVoteForm(this.pollData); } diff --git a/client/src/app/site/motions/modules/motion-poll/motion-poll-vote/motion-poll-vote.component.html b/client/src/app/site/motions/modules/motion-poll/motion-poll-vote/motion-poll-vote.component.html index e3c602ec4..e0efc88ab 100644 --- a/client/src/app/site/motions/modules/motion-poll/motion-poll-vote/motion-poll-vote.component.html +++ b/client/src/app/site/motions/modules/motion-poll/motion-poll-vote/motion-poll-vote.component.html @@ -1,6 +1,8 @@ +
    + +
    -

    - - {{ 'Poll type' | translate }}: {{ poll.typeVerbose | translate }} - +
    + {{ poll.typeVerbose | translate }} +
    -
    - +
    +
    -
    - - {{ voteYes }} - + +
    +
    +
    -
    - -
    -
    - - {{ voteNo }} - +
    +
    + + {{ voteYes | parsePollNumber }} + +
    +
    + + {{ voteNo | parsePollNumber }} + +
    +
    + + {{ voteAbstain | parsePollNumber }} + +
    +
    + +
    -
    - An empty poll - you have to enter votes. +
    + {{ 'An empty poll - you have to enter votes.' | translate }}
    + + + + + +
    + + +
    +
    + +