Merge branch 'features/clean_refacto' into 'develop'

clean refacto

See merge request framasoft/framadate/funky-framadate-front!40
archived-develop
seraph_ino 3 years ago
commit b6e703b6ca

@ -1,4 +1,5 @@
image: weboaks/node-karma-protractor-chrome
# image: weboaks/node-karma-protractor-chrome
image: node:latest
stages:
- pages
@ -13,7 +14,7 @@ pages:
stage: pages
script:
- yarn install --pure-lockfile
- yarn build
- yarn build:prod:gitlabpage
- mv dist/framadate/ public/
artifacts:
paths:

@ -40,7 +40,8 @@ app.swagger GET ANY ANY /api/doc.json
api_get_poll_slug GET ANY ANY /polls/slug/{id}/{token}
api_clean_expired_polls GET ANY ANY /admin/clean-polls/{token}
api_test-mail-polls GET ANY ANY /polls/mail/test-mail-polls/{emailChoice}
api_update_vote_stack PATCH ANY ANY /votes-stack/{id}/token/{modifierToken}
api_test-mail-polls GET ANY ANY /polls/mail/test-mail-polls/{emailChoice}
api_update_vote_stack PATCH ANY ANY /polls/{slug}/answers/{pseudo}/token/{modifierToken}
-------------------------- -------- -------- ------ ------------------------------------------------
*/

@ -9,6 +9,7 @@
"id": 1,
"slug": "picnic",
"configuration": {
"id": 1,
"isAboutDate": true,
"isProtectedByPassword": false,
"isOwnerNotifiedByEmailOnNewVote": false,
@ -20,51 +21,13 @@
},
"ownerId": 1,
"question": "Quelle date pour le picnic ?",
"description": "Gros badass picnic en plein air ! Come on !",
"answersByChoiceByParticipant": {
"TOTO": { "samedi": "YES", "dimanche": "NO" },
"TATA": { "samedi": "NO", "dimanche": null },
"TITI": { "samedi": "MAYBE", "dimanche": "NO" }
},
"answers": [
{
"pseudo": "TOTO",
"token": "TOTO-TOKEN",
"responsesByChoices": [
{ "choice": { "label": "samedi", "imagePath": null }, "responseType": "YES" },
{ "choice": { "label": "dimanche", "imagePath": null }, "responseType": "NO" }
]
},
{
"pseudo": "TATA",
"token": "TATA-TOKEN",
"responsesByChoices": [
{ "choice": { "label": "samedi", "imagePath": null }, "responseType": "NO" },
{ "choice": { "label": "dimanche", "imagePath": null }, "responseType": "NO" }
]
},
{
"pseudo": "TITI",
"token": "TITI-TOKEN",
"responsesByChoices": [
{ "choice": { "label": "samedi", "imagePath": null }, "responseType": "NO" },
{ "choice": { "label": "dimanche", "imagePath": null }, "responseType": "YES" }
]
},
{
"pseudo": "TETE",
"token": "TETE-TOKEN",
"responsesByChoices": [
{ "choice": { "label": "samedi", "imagePath": null }, "responseType": "YES" },
{ "choice": { "label": "dimanche", "imagePath": null }, "responseType": "NO" }
]
}
]
"description": "Gros badass picnic en plein air ! Come on !"
},
{
"id": 2,
"slug": "vacances",
"configuration": {
"id": 2,
"isAboutDate": true,
"isProtectedByPassword": false,
"isOwnerNotifiedByEmailOnNewVote": false,
@ -76,50 +39,7 @@
},
"ownerId": 2,
"question": "On fait quoi pendant les vacances ?",
"description": "Vacances en famille",
"answersByChoiceByParticipant": {
"TOTO": { "bateau": "YES", "montagne": "NO", "quad": "MAYBE" },
"TATA": { "bateau": "NO", "montagne": null, "quad": "YES" },
"TITI": { "bateau": "MAYBE", "montagne": "NO", "quad": null }
},
"answers": [
{
"pseudo": "TOTO",
"token": "TOTO-TOKEN",
"responsesByChoices": [
{ "choice": { "label": "bateau", "imagePath": null }, "responseType": "YES" },
{ "choice": { "label": "montagne", "imagePath": null }, "responseType": "NO" },
{ "choice": { "label": "quad", "imagePath": null }, "responseType": "NO" }
]
},
{
"pseudo": "TATA",
"token": "TATA-TOKEN",
"responsesByChoices": [
{ "choice": { "label": "bateau", "imagePath": null }, "responseType": "NO" },
{ "choice": { "label": "montagne", "imagePath": null }, "responseType": "NO" },
{ "choice": { "label": "quad", "imagePath": null }, "responseType": "NO" }
]
},
{
"pseudo": "TITI",
"token": "TITI-TOKEN",
"responsesByChoices": [
{ "choice": { "label": "bateau", "imagePath": null }, "responseType": "NO" },
{ "choice": { "label": "montagne", "imagePath": null }, "responseType": "YES" },
{ "choice": { "label": "quad", "imagePath": null }, "responseType": "NO" }
]
},
{
"pseudo": "TETE",
"token": "TETE-TOKEN",
"responsesByChoices": [
{ "choice": { "label": "bateau", "imagePath": null }, "responseType": "YES" },
{ "choice": { "label": "montagne", "imagePath": null }, "responseType": "NO" },
{ "choice": { "label": "quad", "imagePath": null }, "responseType": "NO" }
]
}
]
"description": "Vacances en famille"
}
],
"choices": [
@ -129,9 +49,15 @@
"pollSlug": "picnic",
"label": "samedi",
"participants": [
["YES", ["TOTO", "TITI"]],
["NO", ["TETE"]],
["MAYBE", ["TATA"]]
[
"YES",
[
{ "pseudo": "TOTO", "token": "TOTO-TOKEN" },
{ "pseudo": "TITI", "token": "TITI-TOKEN" }
]
],
["NO", []],
["MAYBE", [{ "pseudo": "TATA", "token": "TATA-TOKEN" }]]
]
},
{
@ -140,9 +66,15 @@
"pollSlug": "picnic",
"label": "dimanche",
"participants": [
["YES", ["TOTO", "TITI"]],
["NO", ["TETE"]],
["MAYBE", ["TATA"]]
[
"YES",
[
{ "pseudo": "TATA", "token": "TATA-TOKEN" },
{ "pseudo": "TETE", "token": "TETE-TOKEN" }
]
],
["NO", [{ "pseudo": "TOTO", "token": "TOTO-TOKEN" }]],
["MAYBE", [{ "pseudo": "TITI", "token": "TITI-TOKEN" }]]
]
},
{
@ -151,9 +83,15 @@
"pollSlug": "vacances",
"label": "bateau",
"participants": [
["YES", ["TOTO", "TITI"]],
["NO", ["TETE"]],
["MAYBE", ["TATA"]]
[
"YES",
[
{ "pseudo": "TOTO", "token": "TOTO-TOKEN" },
{ "pseudo": "TITI", "token": "TITI-TOKEN" }
]
],
["NO", [{ "pseudo": "TETE", "token": "TETE-TOKEN" }]],
["MAYBE", [{ "pseudo": "TATA", "token": "TATA-TOKEN" }]]
]
},
{
@ -162,9 +100,15 @@
"pollSlug": "vacances",
"label": "montagne",
"participants": [
["YES", ["TOTO", "TITI"]],
["NO", ["TETE"]],
["MAYBE", ["TATA"]]
[
"YES",
[
{ "pseudo": "TOTO", "token": "TOTO-TOKEN" },
{ "pseudo": "TITI", "token": "TITI-TOKEN" }
]
],
["NO", [{ "pseudo": "TETE", "token": "TETE-TOKEN" }]],
["MAYBE", [{ "pseudo": "TATA", "token": "TATA-TOKEN" }]]
]
},
{
@ -173,30 +117,18 @@
"pollSlug": "vacances",
"label": "quad",
"participants": [
["YES", ["TOTO", "TITI"]],
["NO", ["TETE"]],
["MAYBE", ["TATA"]]
[
"YES",
[
{ "pseudo": "TOTO", "token": "TOTO-TOKEN" },
{ "pseudo": "TITI", "token": "TITI-TOKEN" }
]
],
["NO", [{ "pseudo": "TETE", "token": "TETE-TOKEN" }]],
["MAYBE", [{ "pseudo": "TATA", "token": "TATA-TOKEN" }]]
]
}
],
"answers": [
{ "choiceId": 1, "pseudo": "TOTO", "response": "YES", "token": "TOTO-TOKEN" },
{ "choiceId": 1, "pseudo": "TATA", "response": "NO", "token": "TATA-TOKEN" },
{ "choiceId": 1, "pseudo": "TITI", "response": "MAYBE", "token": "TITI-TOKEN" },
{ "choiceId": 2, "pseudo": "TOTO", "response": "YES", "token": "TOTO-TOKEN" },
{ "choiceId": 2, "pseudo": "TATA", "response": "NO", "token": "TATA-TOKEN" },
{ "choiceId": 2, "pseudo": "TITI", "response": "MAYBE", "token": "TITI-TOKEN" },
{ "choiceId": 2, "pseudo": "EVA", "response": null, "token": "EVA-TOKEN" },
{ "choiceId": 3, "pseudo": "TOTO", "response": "YES", "token": "TOTO-TOKEN" },
{ "choiceId": 3, "pseudo": "TATA", "response": "NO", "token": "TATA-TOKEN" },
{ "choiceId": 3, "pseudo": "TITI", "response": "MAYBE", "token": "TITI-TOKEN" },
{ "choiceId": 4, "pseudo": "TOTO", "response": "YES", "token": "TOTO-TOKEN" },
{ "choiceId": 4, "pseudo": "TATA", "response": "NO", "token": "TATA-TOKEN" },
{ "choiceId": 4, "pseudo": "TITI", "response": "MAYBE", "token": "TITI-TOKEN" },
{ "choiceId": 5, "pseudo": "TOTO", "response": "YES", "token": "TOTO-TOKEN" },
{ "choiceId": 5, "pseudo": "TATA", "response": "NO", "token": "TATA-TOKEN" },
{ "choiceId": 5, "pseudo": "TITI", "response": "MAYBE", "token": "TITI-TOKEN" }
],
"comments": [
{
"id": 1,

@ -1,8 +1,7 @@
{
"/api/v1/*": "/$1",
"/owners/:email/": "/owners?email=:email",
"/choices": "/choices?_embed=answers",
"/polls/:slug": "/polls?slug=:slug&_expand=owner&_embed=choices&_embed=comments",
"/polls/:slug/choices": "/choices?pollSlug=:slug&_embed=answers",
"/polls/:slug/choices": "/choices?pollSlug=:slug",
"/polls/:slug/comments": "/comments?pollSlug=:slug"
}

@ -5,13 +5,13 @@
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build --prod --progress=true",
"build-prod-stats": "ng build --prod --stats-json",
"build:demo": "ng build --crossOrigin=anonymous --extractCss=true --progress=true --prod && npm run package",
"build:demobliss": "ng build --crossOrigin=anonymous --extractCss=true --baseHref=https://framadate-api.cipherbliss.com --progress=true --prod && npm run package",
"build:prod": "ng build --prod",
"build:prod:stats": "ng build --prod --stats-json",
"build:prod:gitlabpage": "ng build --prod --baseHref=/framadate/funky-framadate-front/",
"build:prod:demobliss": "ng build --prod --baseHref=https://framadate-api.cipherbliss.com",
"test": "jest",
"test:watch": "jest --watch",
"test:ci": "jest --runInBand",
"test:ci": "jest --ci",
"lint": "prettier --write \"src/**/*.{js,jsx,ts,tsx,md,html,css,scss}\"",
"e2e": "ng e2e",
"format:check": "prettier --list-different \"src/{app,environments,assets}/**/*{.ts,.js,.json,.css,.scss}\"",
@ -65,7 +65,7 @@
"@babel/preset-env": "^7.9.5",
"@babel/preset-typescript": "^7.9.0",
"@compodoc/compodoc": "^1.1.11",
"@types/jest": "^25.2.1",
"@types/jest": "^26.0.0",
"@types/node": "^14.0.1",
"@types/uuid": "^8.0.0",
"@typescript-eslint/eslint-plugin": "^3.0.0",
@ -86,7 +86,7 @@
"ts-jest": "^26.0.0",
"ts-mockito": "^2.5.0",
"ts-node": "^8.10.1",
"typescript": "~3.8.3"
"typescript": "<3.9.0"
},
"husky": {
"hooks": {

@ -32,7 +32,7 @@ export class AppComponent implements OnInit, OnDestroy {
if (!environment.production) {
this.appTitle += ' [DEV]';
// TODO: to be removed
this.mockingService.loadUser(new User('TOTO', 'toto@gafam.com', UserRole.REGISTERED));
this.mockingService.loadUser(new User('TOTO', 'toto@gafam.com', [], UserRole.REGISTERED));
}
this.titleService.setTitle(this.appTitle);
this.languageService.configureAndInitTranslations();

@ -1,4 +1,4 @@
import { APP_BASE_HREF, CommonModule, registerLocaleData } from '@angular/common';
import { CommonModule, registerLocaleData } from '@angular/common';
import { HttpClient, HttpClientModule } from '@angular/common/http';
import localeEn from '@angular/common/locales/en';
import localeFr from '@angular/common/locales/fr';
@ -64,7 +64,7 @@ export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader {
useDefaultLang: false,
}),
],
providers: [{ provide: APP_BASE_HREF, useValue: environment.baseHref }, Title, TranslateService],
providers: [Title, TranslateService],
bootstrap: [AppComponent],
})
export class AppModule {}

@ -1,4 +1,4 @@
export enum ResponseType {
export enum Answer {
YES = 'YES',
NO = 'NO',
MAYBE = 'MAYBE',

@ -1,37 +1,38 @@
import { ResponseType } from '../enums/response-type.enum';
import { Answer } from '../enums/answer.enum';
import { User } from './user.model';
export class Choice {
constructor(
public label: string,
public imageUrl?: string,
public participants: Map<ResponseType, Set<string>> = new Map<ResponseType, Set<string>>([
[ResponseType.YES, new Set<string>()],
[ResponseType.NO, new Set<string>()],
[ResponseType.MAYBE, new Set<string>()],
public participants: Map<Answer, Set<User>> = new Map<Answer, Set<User>>([
[Answer.YES, new Set<User>()],
[Answer.NO, new Set<User>()],
[Answer.MAYBE, new Set<User>()],
]),
public counts: Map<ResponseType, number> = new Map<ResponseType, number>([
[ResponseType.YES, 0],
[ResponseType.NO, 0],
[ResponseType.MAYBE, 0],
public counts: Map<Answer, number> = new Map<Answer, number>([
[Answer.YES, 0],
[Answer.NO, 0],
[Answer.MAYBE, 0],
])
) {}
public updateParticipation(pseudo: string, responseType: ResponseType): void {
this.removeParticipant(pseudo);
this.participants.get(responseType).add(pseudo);
public updateParticipation(user: User, responseType: Answer): void {
this.removeParticipant(user);
this.participants.get(responseType).add(user);
this.updateCounts();
}
public removeParticipant(pseudo: string): void {
for (const responseType of Object.values(ResponseType)) {
if (this.participants.get(responseType).has(pseudo)) {
this.participants.get(responseType).delete(pseudo);
public removeParticipant(user: User): void {
for (const responseType of Object.values(Answer)) {
if (this.participants.get(responseType).has(user)) {
this.participants.get(responseType).delete(user);
}
}
}
public updateCounts(): void {
for (const responseType of Object.values(ResponseType)) {
for (const responseType of Object.values(Answer)) {
this.counts.set(responseType, this.participants.get(responseType).size);
}
}

@ -1,6 +0,0 @@
import { ResponseType } from '../enums/response-type.enum';
import { Choice } from './choice.model';
export class PollUserAnswers {
constructor(public pseudo: string, public token: string, public responsesByChoices: Map<Choice, ResponseType>) {}
}

@ -1,8 +1,6 @@
import { environment } from 'src/environments/environment';
import { v4 as uuidv4 } from 'uuid';
import { ResponseType } from '../enums/response-type.enum';
import { PollUserAnswers } from './poll-user-answers.model';
import { Choice } from './choice.model';
import { Comment } from './comment.model';
import { Configuration } from './configuration.model';
@ -16,12 +14,7 @@ export class Poll {
public slug: string = uuidv4(),
public configuration: Configuration = new Configuration(),
public comments: Comment[] = [],
public choices: Choice[] = [],
public answersByChoiceByParticipant: Map<string, Map<string, ResponseType>> = new Map<
string,
Map<string, ResponseType>
>(),
public answers: PollUserAnswers[] = []
public choices: Choice[] = []
) {}
public getAdministrationUrl(): string {
@ -33,18 +26,7 @@ export class Poll {
}
public static adaptFromLocalJsonServer(
item: Pick<
Poll,
| 'owner'
| 'question'
| 'description'
| 'slug'
| 'configuration'
| 'comments'
| 'choices'
| 'answersByChoiceByParticipant'
| 'answers'
>
item: Pick<Poll, 'owner' | 'question' | 'description' | 'slug' | 'configuration' | 'comments' | 'choices'>
): Poll {
const poll = new Poll(
new User(item.owner.pseudo, item.owner.email, undefined),
@ -58,32 +40,17 @@ export class Poll {
new Comment(c.author, c.content, new Date(c.dateCreated))
)
.sort(Comment.sortChronologically),
item.choices.map((c: Pick<Choice, 'label' | 'imageUrl' | 'participants' | 'imageUrl'>) => {
item.choices.map((c: Pick<Choice, 'label' | 'imageUrl' | 'participants' | 'counts'>) => {
const choice = new Choice(c.label, c.imageUrl, new Map(c.participants));
choice.participants.forEach((value, key) => {
choice.participants.set(key, new Set(value));
});
choice.updateCounts();
console.log({ choice });
return choice;
})
);
// handle answersByChoiceByParticipant
for (const [pseudo, answersByChoice] of Object.entries(item.answersByChoiceByParticipant)) {
if (!poll.answersByChoiceByParticipant.has(pseudo)) {
poll.answersByChoiceByParticipant.set(pseudo, new Map<string, ResponseType>());
}
for (const [choiceLabel, answer] of Object.entries(answersByChoice)) {
poll.answersByChoiceByParticipant.get(pseudo).set(choiceLabel, answer as ResponseType);
}
}
// handle answers
poll.answers = item.answers.map(
(pollUserAnswers: Pick<PollUserAnswers, 'pseudo' | 'token' | 'responsesByChoices'>) =>
new PollUserAnswers(pollUserAnswers.pseudo, pollUserAnswers.token, pollUserAnswers.responsesByChoices)
);
return poll;
}
}

@ -1,3 +0,0 @@
export class Question {
constructor(public label: string, public description: string) {}
}

@ -2,5 +2,11 @@ import { UserRole } from '../enums/user-role.enum';
import { Poll } from './poll.model';
export class User {
constructor(public pseudo: string, public email: string, public role?: UserRole, public polls?: Poll[]) {}
constructor(
public pseudo: string,
public email: string,
public polls: Poll[] = [],
public role?: UserRole,
public token?: string
) {}
}

@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
import axios, { AxiosInstance, AxiosResponse } from 'axios';
import { environment } from 'src/environments/environment';
import { ResponseType } from '../enums/response-type.enum';
import { Answer } from '../enums/answer.enum';
import { Poll } from '../models/poll.model';
@Injectable({
@ -40,7 +40,7 @@ export class ApiService {
pollId: string,
choiceLabel: string,
pseudo: string,
response: ResponseType
response: Answer
): Promise<string> {
try {
return await this.axiosInstance.post(`${this.pollsEndpoint}/${pollId}${this.answersEndpoint}`, {
@ -151,17 +151,12 @@ export class ApiService {
////////////
// UPDATE //
////////////
public async updateAnswer(
slug: string,
choiceLabel: string,
pseudo: string,
response: ResponseType
): Promise<string> {
public async updateAnswer(slug: string, choiceLabel: string, pseudo: string, answer: Answer): Promise<string> {
try {
return await this.axiosInstance.patch(`${this.pollsEndpoint}/${slug}${this.answersEndpoint}`, {
choiceLabel,
pseudo,
response,
answer,
});
} catch (error) {
this.handleError(error);

@ -2,7 +2,6 @@ import { Injectable } from '@angular/core';
import { Choice } from '../models/choice.model';
import { Poll } from '../models/poll.model';
import { Question } from '../models/question.model';
import { User } from '../models/user.model';
import { PollService } from './poll.service';
import { UserService } from './user.service';

@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { MessageSeverity } from '../enums/message-severity.enum';
import { ResponseType } from '../enums/response-type.enum';
import { Answer } from '../enums/answer.enum';
import { Choice } from '../models/choice.model';
import { Poll } from '../models/poll.model';
import { User } from '../models/user.model';
@ -40,9 +40,9 @@ export class PollService {
}
}
public saveParticipation(choice: Choice, user: User, response: ResponseType): void {
public saveParticipation(choice: Choice, user: User, response: Answer): void {
const currentPoll = this._poll.getValue();
currentPoll.choices.find((c) => c.label === choice.label)?.updateParticipation(user.pseudo, response);
currentPoll.choices.find((c) => c.label === choice.label)?.updateParticipation(user, response);
this.updateCurrentPoll(currentPoll);
this.apiService.createParticipation(currentPoll.slug, choice.label, user.pseudo, response);
this.messageDisplayerService.display(
@ -71,4 +71,37 @@ export class PollService {
'Les commentaires de ce sondage ont รฉtรฉ supprimรฉs.'
);
}
public buildAnswersByChoiceLabelByPseudo(poll: Poll): Map<string, Map<string, Answer>> {
const pseudos: Set<string> = new Set();
poll.choices.forEach((choice: Choice) => {
choice.participants.forEach((users: Set<User>) => {
users.forEach((user: User) => {
pseudos.add(user.pseudo);
});
});
});
const list = new Map<string, Map<string, Answer>>();
pseudos.forEach((pseudo: string) => {
list.set(
pseudo,
new Map<string, Answer>(
poll.choices.map((choice: Choice) => {
return [choice.label, undefined];
})
)
);
});
poll.choices.forEach((choice: Choice) => {
choice.participants.forEach((users: Set<User>, answer: Answer) => {
users.forEach((user: User) => {
list.get(user.pseudo).set(choice.label, answer);
});
});
});
return list;
}
}

@ -10,7 +10,7 @@ import { ApiService } from './api.service';
providedIn: 'root',
})
export class UserService {
public anonymous: User = new User('', '', UserRole.ANONYMOUS);
public anonymous: User = new User('', '', [], UserRole.ANONYMOUS);
private _user: BehaviorSubject<User> = new BehaviorSubject<User>(this.anonymous);
public readonly user: Observable<User> = this._user.asObservable();

@ -7,11 +7,11 @@
<div class="buttons has-addons is-right" (click)="openModal(choice)">
<button class="button is-white">
<img class="image is-24x24" src="../../../assets/img/icon_voter_YES.svg" />
{{ choice.counts.get(responseTypeEnum.YES) }}
{{ choice.counts.get(answerEnum.YES) }}
</button>
<button class="button is-white" *ngIf="poll.configuration.isMaybeAnswerAvailable">
<img class="image is-24x24" src="../../../assets/img/icon_voter_MAYBE.svg" />
{{ choice.counts.get(responseTypeEnum.MAYBE) }}
{{ choice.counts.get(answerEnum.MAYBE) }}
</button>
</div>
</div>

@ -1,6 +1,6 @@
import { Component, Input, OnInit } from '@angular/core';
import { ResponseType } from '../../../core/enums/response-type.enum';
import { Answer } from '../../../core/enums/answer.enum';
import { Choice } from '../../../core/models/choice.model';
import { Poll } from '../../../core/models/poll.model';
import { ModalService } from '../../../core/services/modal.service';
@ -14,7 +14,7 @@ export class PollResultsCompactComponent implements OnInit {
@Input() public poll: Poll;
public isModalOpened = false;
public choiceInModal: Choice;
public responseTypeEnum = ResponseType;
public answerEnum = Answer;
constructor(private modalService: ModalService) {}

@ -6,7 +6,7 @@
</tr>
</thead>
<tbody>
<ng-container *ngFor="let item of poll.answersByChoiceByParticipant | keyvalue">
<ng-container *ngFor="let item of buildAnswersByChoiceLabelByPseudo() | keyvalue">
<tr>
<td>{{ item.key }}</td>
<td *ngFor="let subItem of item.value | keyvalue">{{ subItem.value }}</td>
@ -14,22 +14,3 @@
</ng-container>
</tbody>
</table>
<hr />
<table>
<thead>
<tr>
<th></th>
<th *ngFor="let choice of poll.choices">{{ choice.label }}</th>
</tr>
</thead>
<tbody>
<ng-container *ngFor="let answer of poll.answers">
<tr>
<td>{{ answer.pseudo }}</td>
<td *ngFor="let response of answer.responsesByChoices | keyvalue">
{{ response.value }}
</td>
</tr>
</ng-container>
</tbody>
</table>

@ -1,6 +1,8 @@
import { Component, Input, OnInit } from '@angular/core';
import { Answer } from '../../../core/enums/answer.enum';
import { Poll } from '../../../core/models/poll.model';
import { PollService } from '../../../core/services/poll.service';
@Component({
selector: 'app-poll-results-detailed',
@ -10,7 +12,11 @@ import { Poll } from '../../../core/models/poll.model';
export class PollResultsDetailedComponent implements OnInit {
@Input() public poll: Poll;
constructor() {}
constructor(private pollService: PollService) {}
ngOnInit(): void {}
public buildAnswersByChoiceLabelByPseudo(): Map<string, Map<string, Answer>> {
return this.pollService.buildAnswersByChoiceLabelByPseudo(this.poll);
}
}

@ -28,11 +28,11 @@
<div class="column">
<div class="buttons has-addons is-right">
<button class="button is-white">
{{ choice.counts.get(responseTypeEnum.YES) }}
{{ choice.counts.get(answerEnum.YES) }}
<img class="image is-24x24" src="../../../assets/img/icon_voter_YES.svg" />
</button>
<button class="button is-white" *ngIf="poll.configuration.isMaybeAnswerAvailable">
{{ choice.counts.get(responseTypeEnum.MAYBE) }}
{{ choice.counts.get(answerEnum.MAYBE) }}
<img class="image is-24x24" src="../../../assets/img/icon_voter_MAYBE.svg" />
</button>
</div>

@ -1,5 +1,5 @@
import { Component, Input, OnInit } from '@angular/core';
import { ResponseType } from 'src/app/core/enums/response-type.enum';
import { Answer } from 'src/app/core/enums/answer.enum';
import { Choice } from '../../../core/models/choice.model';
import { Poll } from '../../../core/models/poll.model';
@ -15,15 +15,15 @@ export class AddAnswerComponent implements OnInit {
@Input() user: User;
@Input() poll: Poll;
@Input() choice: Choice;
public responseTypeEnum = ResponseType;
public response: ResponseType;
public answerEnum = Answer;
public response: Answer;
constructor(private pollService: PollService) {}
ngOnInit(): void {}
public vote(response: string): void {
this.response = response as ResponseType;
this.response = response as Answer;
console.log(this.response);
this.pollService.saveParticipation(this.choice, this.user, this.response);
}

@ -5,13 +5,13 @@
<tr>
<th class="is-flex">
<img class="image is-24x24" src="../../../assets/img/icon_voter_YES.svg" />
{{ choice.counts.get(responseTypeEnum.YES) }}
{{ choice.counts.get(answerEnum.YES) }}
</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let pseudo of choice.participants.get(responseTypeEnum.YES)">
<td>{{ pseudo }}</td>
<tr *ngFor="let user of choice.participants.get(answerEnum.YES)">
<td>{{ user.pseudo }}</td>
</tr>
</tbody>
</table>
@ -22,13 +22,13 @@
<tr>
<th class="is-flex">
<img class="image is-24x24" src="../../../assets/img/icon_voter_MAYBE.svg" />
{{ choice.counts.get(responseTypeEnum.MAYBE) }}
{{ choice.counts.get(answerEnum.MAYBE) }}
</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let pseudo of choice.participants.get(responseTypeEnum.MAYBE)">
<td>{{ pseudo }}</td>
<tr *ngFor="let user of choice.participants.get(answerEnum.MAYBE)">
<td>{{ user.pseudo }}</td>
</tr>
</tbody>
</table>

@ -1,7 +1,7 @@
import { Component, OnInit } from '@angular/core';
import { DynamicDialogConfig, DynamicDialogRef } from 'primeng';
import { ResponseType } from '../../../core/enums/response-type.enum';
import { Answer } from '../../../core/enums/answer.enum';
import { Choice } from '../../../core/models/choice.model';
@Component({
@ -11,7 +11,7 @@ import { Choice } from '../../../core/models/choice.model';
})
export class ChoiceDetailsComponent implements OnInit {
public choice: Choice;
public responseTypeEnum = ResponseType;
public answerEnum = Answer;
constructor(public ref: DynamicDialogRef, public config: DynamicDialogConfig) {}

@ -5,7 +5,6 @@ const backendApiUrlsInDev = {
export const environment = {
production: true,
baseHref: '/framadate/funky-framadate-front/',
appTitle: 'FramaSondage',
api: {
baseHref: backendApiUrlsInDev.remote,
@ -21,9 +20,6 @@ export const environment = {
slugs: {
name: '/slugs',
},
votesStacks: {
name: '/votes-stacks',
},
answers: {
name: '/answers',
},

@ -9,7 +9,6 @@ const backendApiUrlsInDev = {
export const environment = {
production: false,
baseHref: '/',
appTitle: 'FramaSondage',
api: {
baseHref: backendApiUrlsInDev.local,
@ -25,9 +24,6 @@ export const environment = {
slugs: {
name: '/slugs',
},
votesStacks: {
name: '/votes-stacks',
},
answers: {
name: '/answers',
},

@ -21,7 +21,7 @@
// most general dom elements
@import './styles/partials/global';
@import './styles/partials/logo';
@import './styles/partials/navigation';
@import './styles/partials/navigation';
// main content elements
@import './styles/partials/main';

@ -1,10 +1,9 @@
@charset "UTF-8";
label{
label {
line-height: 2.5em;
}
.ui-inputswitch{
.ui-inputswitch {
margin-left: 1ch;
margin-right: 1ch;
}
@ -69,6 +68,6 @@ li {
width: 100%;
}
button[type='submit']{
margin-top : 2em;
button[type='submit'] {
margin-top: 2em;
}

@ -18,7 +18,8 @@ nav {
display: none;
}
&.active, &.is-active {
&.active,
&.is-active {
color: $white;
background: $primary_color;
}
@ -82,9 +83,9 @@ a {
}
}
// material override
.navbar{
margin-bottom: 0
.navbar {
margin-bottom: 0;
}
a span.ui-steps-number{
a span.ui-steps-number {
padding: 0;
}

@ -1,7 +1,7 @@
#big_container {
background: $primary;
main{
main {
margin-bottom: 2em;
padding-bottom: 5em;
padding-top: 1em;

@ -6,16 +6,19 @@ $theme-color-tertiary: #ccc;
background: #222;
color: $theme-color-tertiary;
main, .big-header, .navbar , footer{
main,
.big-header,
.navbar,
footer {
background: #444;
}
.big-header{
.big-header {
color: $white;
a{
a {
color: $white;
}
}
input{
input {
background: #222;
color: $white;
}
@ -60,30 +63,44 @@ $theme-color-tertiary: #ccc;
.big-header {
margin-bottom: 22px;
}
label{
label {
color: $theme-color-tertiary;
}
// bulma override
.button.is-primary, .button.btn--primary, button.is-primary, button.btn--primary, .is-primary.btn, .btn.btn--primary, .is-primary.back, .back.btn--primary{
.button.is-primary,
.button.btn--primary,
button.is-primary,
button.btn--primary,
.is-primary.btn,
.btn.btn--primary,
.is-primary.back,
.back.btn--primary {
background-color: $theme-color-primary !important;
color: $theme-color-tertiary !important;
}
.navbar-dropdown a.navbar-item{
.navbar-dropdown a.navbar-item {
color: #444;
}
// material override
.navbar-dropdown a.navbar-item.is-active,
body .ui-steps .ui-steps-item.ui-state-highlight .ui-steps-number,
a.navbar-item:focus, a.navbar-item:focus-within, a.navbar-item:hover, a.navbar-item.is-active, .navbar-link:focus, .navbar-link:focus-within, .navbar-link:hover, .navbar-link.is-active{
a.navbar-item:focus,
a.navbar-item:focus-within,
a.navbar-item:hover,
a.navbar-item.is-active,
.navbar-link:focus,
.navbar-link:focus-within,
.navbar-link:hover,
.navbar-link.is-active {
color: $theme-color-primary !important;
}
.navbar-link:not(.is-arrowless)::after{
.navbar-link:not(.is-arrowless)::after {
border-color: $theme-color-primary;
}
body .ui-inputswitch.ui-inputswitch-checked:not(.ui-state-disabled) .ui-inputswitch-slider{
body .ui-inputswitch.ui-inputswitch-checked:not(.ui-state-disabled) .ui-inputswitch-slider {
background-color: $theme-color-primary;
}
}

@ -19,14 +19,13 @@
}
// material override
body .ui-steps .ui-steps-item.ui-state-highlight .ui-steps-number{
body .ui-steps .ui-steps-item.ui-state-highlight .ui-steps-number {
color: $primary_color;
}
body .ui-inputswitch.ui-inputswitch-checked:not(.ui-state-disabled) .ui-inputswitch-slider{
body .ui-inputswitch.ui-inputswitch-checked:not(.ui-state-disabled) .ui-inputswitch-slider {
background-color: $primary_color;
}
.navbar-link:not(.is-arrowless)::after{
.navbar-link:not(.is-arrowless)::after {
border-color: $primary_color;
}
}

@ -7,7 +7,7 @@ $theme-color-tertiary: #ffbca4;
background: $theme-color-tertiary;
color: $theme-color-secondary;
main{
main {
background: $white;
}
h1::after {
@ -22,15 +22,28 @@ $theme-color-tertiary: #ffbca4;
}
// bulma override
.button.is-primary, .button.btn--primary, button.is-primary, button.btn--primary, .is-primary.btn, .btn.btn--primary, .is-primary.back, .back.btn--primary{
.button.is-primary,
.button.btn--primary,
button.is-primary,
button.btn--primary,
.is-primary.btn,
.btn.btn--primary,
.is-primary.back,
.back.btn--primary {
background-color: $theme-color-primary;
color: $theme-color-tertiary;
}
// material override
.navbar-dropdown a.navbar-item.is-active,
a.navbar-item:focus, a.navbar-item:focus-within, a.navbar-item:hover, a.navbar-item.is-active, .navbar-link:focus, .navbar-link:focus-within, .navbar-link:hover, .navbar-link.is-active{
a.navbar-item:focus,
a.navbar-item:focus-within,
a.navbar-item:hover,
a.navbar-item.is-active,
.navbar-link:focus,
.navbar-link:focus-within,
.navbar-link:hover,
.navbar-link.is-active {
color: $theme-color-primary;
}
}
}

@ -12,29 +12,29 @@
jest-preset-angular "^8.1.2"
lodash "^4.17.10"
"@angular-devkit/architect@0.901.7", "@angular-devkit/architect@>=0.900.0 < 0.1000.0":
version "0.901.7"
resolved "https://registry.yarnpkg.com/@angular-devkit/architect/-/architect-0.901.7.tgz#6a09cb076ca92b3202053fca757a456d1f8e4395"
integrity sha512-yW/PUEqle55QihOFbmeNXaVTodhfeXkteoFDUpz+YpX3xiQDXDtNbIJSzKOQTojtBKdSMKMvZkQLr+RAa7/1EA==
"@angular-devkit/architect@0.901.8", "@angular-devkit/architect@>=0.900.0 < 0.1000.0":
version "0.901.8"
resolved "https://registry.yarnpkg.com/@angular-devkit/architect/-/architect-0.901.8.tgz#d2f5f4c16fba3ed61ee27c7fc72118421ea2b45d"
integrity sha512-tK9ZQlubH6n+q+c2J9Wvfcxg3RFuRiTfJriNoodo6GHvtF2KLdPY67w3Gen0Sp172A5Q8Y927NseddNI8RZ/0A==
dependencies:
"@angular-devkit/core" "9.1.7"
"@angular-devkit/core" "9.1.8"
rxjs "6.5.4"
"@angular-devkit/build-angular@^0.901.2":
version "0.901.7"
resolved "https://registry.yarnpkg.com/@angular-devkit/build-angular/-/build-angular-0.901.7.tgz#10d55e3c73213971ba7d733f15d66494dfe9918a"
integrity sha512-NiBwapx/XJqYGzSmENff78i6Yif9PjYDJ9BB+59t2eDofkCZUcPFrhQmRgliO7rt6RATvT81lDP89+LBXCTQPw==
dependencies:
"@angular-devkit/architect" "0.901.7"
"@angular-devkit/build-optimizer" "0.901.7"
"@angular-devkit/build-webpack" "0.901.7"
"@angular-devkit/core" "9.1.7"
version "0.901.8"
resolved "https://registry.yarnpkg.com/@angular-devkit/build-angular/-/build-angular-0.901.8.tgz#6450be4743dacf564af143c85d2a03b7bdd81551"
integrity sha512-W2RTjtPPJRbke6K7Qt9eZOPRGfFBFsYzskxsuxXwkW2RPopj6k1wUWh9Be8CtAMAUlhyPvlzviOtv3F7leYr3w==
dependencies:
"@angular-devkit/architect" "0.901.8"
"@angular-devkit/build-optimizer" "0.901.8"
"@angular-devkit/build-webpack" "0.901.8"
"@angular-devkit/core" "9.1.8"
"@babel/core" "7.9.0"
"@babel/generator" "7.9.3"
"@babel/preset-env" "7.9.0"
"@babel/template" "7.8.6"
"@jsdevtools/coverage-istanbul-loader" "3.0.3"
"@ngtools/webpack" "9.1.7"
"@ngtools/webpack" "9.1.8"
ajv "6.12.0"
autoprefixer "9.7.4"
babel-loader "8.0.6"
@ -42,7 +42,7 @@
cacache "15.0.0"
caniuse-lite "^1.0.30001032"
circular-dependency-plugin "5.2.0"
copy-webpack-plugin "5.1.1"
copy-webpack-plugin "6.0.2"
core-js "3.6.4"
css-loader "3.5.1"
cssnano "4.1.10"
@ -51,7 +51,7 @@
glob "7.1.6"
jest-worker "25.1.0"
karma-source-map-support "1.4.0"
less "3.11.1"
less "3.11.3"
less-loader "5.0.0"
license-webpack-plugin "2.1.4"
loader-utils "2.0.0"
@ -87,10 +87,10 @@
webpack-subresource-integrity "1.4.0"
worker-plugin "4.0.3"
"@angular-devkit/build-optimizer@0.901.7":
version "0.901.7"
resolved "https://registry.yarnpkg.com/@angular-devkit/build-optimizer/-/build-optimizer-0.901.7.tgz#e72fc3031207a78aee175a76d3317cdf226984e9"
integrity sha512-Xuce3StdxhcgLYb0BAaFGr3Bzj5EM2OsAqIT15PkikWY1k5cK50vPxoC/BkX4QDL9eXSHtqAfMBfA6h5N422vw==
"@angular-devkit/build-optimizer@0.901.8":
version "0.901.8"
resolved "https://registry.yarnpkg.com/@angular-devkit/build-optimizer/-/build-optimizer-0.901.8.tgz#55a6cecf9b963bac15f84b5db8ec211c82119954"
integrity sha512-k9DynuWKMsJk5xg+LthdsqmOlGVMVP/TEu2odiVty9gnTVlIjs1bUzs+HNAF/w11juIBcVKa690K+FkSCalo9w==
dependencies:
loader-utils "2.0.0"
source-map "0.7.3"
@ -98,19 +98,19 @@
typescript "3.6.5"
webpack-sources "1.4.3"
"@angular-devkit/build-webpack@0.901.7":
version "0.901.7"
resolved "https://registry.yarnpkg.com/@angular-devkit/build-webpack/-/build-webpack-0.901.7.tgz#6d93c38756540a02e67d2c3ccfac4220c62962de"
integrity sha512-pTLW5Eqy9cHgv78LKiH0e30lxqKzUPjh1djvNtFsEemOHsfKQdAfjLjikoaQvqMoBKVaUU7r2vmyyS17cH+1yw==
"@angular-devkit/build-webpack@0.901.8":
version "0.901.8"
resolved "https://registry.yarnpkg.com/@angular-devkit/build-webpack/-/build-webpack-0.901.8.tgz#19fbac49c3f60c16d6814d61e518431503ab746a"
integrity sha512-OyLfPI0yo1Qg4I1QP8ZxEYVxrf3IDjGfpxlKXqSChpEy5m/uZmBIRDZ/n/G3+32xFc6MWEdU4EHfRrfn17ae/w==
dependencies:
"@angular-devkit/architect" "0.901.7"
"@angular-devkit/core" "9.1.7"
"@angular-devkit/architect" "0.901.8"
"@angular-devkit/core" "9.1.8"
rxjs "6.5.4"
"@angular-devkit/core@9.1.7", "@angular-devkit/core@^9.0.0":
version "9.1.7"
resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-9.1.7.tgz#f193ccbae4c80b34188bc9cc401c16b3ced50339"
integrity sha512-guvolu9Cl+qYMTtedLZD9wCqustJjdqzJ2psD2C1Sr1LrX9T0mprmDldR/YnhsitThveJEb6sM/0EvqWxoSvKw==
"@angular-devkit/core@9.1.8", "@angular-devkit/core@^9.0.0":
version "9.1.8"
resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-9.1.8.tgz#7c517a14e3ddfd180858972d9f1836aa90a1248e"
integrity sha512-4k1pZwje2oh5c/ULg7pnCBzTstx3l3uF7O5tQq/KXomDDsam97IhLm6cKUqQpaoyC1NUsBV6xJARJ0PyUP5TPQ==
dependencies:
ajv "6.12.0"
fast-json-stable-stringify "2.1.0"
@ -118,19 +118,19 @@
rxjs "6.5.4"
source-map "0.7.3"
"@angular-devkit/schematics@9.1.7":
version "9.1.7"
resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-9.1.7.tgz#45394a1c928db449b412dacf205c3ec78fb5ef0c"
integrity sha512-oeHPJePBcPp/bd94jHQeFUnft93PGF5iJiKV9szxqS8WWC5OMZ5eK7icRY0PwvLyfenspAZxdZcNaqJqPMul5A==
"@angular-devkit/schematics@9.1.8":
version "9.1.8"
resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-9.1.8.tgz#8eeea0b6f9702a5b065f909cdcaf1d35cc8e2fa3"
integrity sha512-/8L5J4X6SkcFMRmrSQHvJWOPilrMWTNlv1lD+1z06D3xGJEktVxXM3gCUXhDrbMvpoi+lYtR2Fuia0E6zvyjCQ==
dependencies:
"@angular-devkit/core" "9.1.7"
"@angular-devkit/core" "9.1.8"
ora "4.0.3"
rxjs "6.5.4"
"@angular/animations@^9.1.1":
version "9.1.9"
resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-9.1.9.tgz#de54334ea195189402487855c9a98f5618605da4"
integrity sha512-qWVi0TxmU6HeXAgEsfpQvFFygh+a0kH2kGe6bWij4XvG6dWfV3xZjlaFwSIYGk+yK4yL0+9+PAXH+ENfxNw+Cw==
version "9.1.11"
resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-9.1.11.tgz#2c7b6e584df0ba0884d05f01fa7ab86c1fdd1c5e"
integrity sha512-VKAExUnEJfo1PDQKagpx2pn+QMZCsPLRiADzTdl4U0VPylK3ALbn4ZNY9UbdwyE2plitz++LkH7sEGGfh+PNrQ==
"@angular/cdk@^9.2.2":
version "9.2.4"
@ -140,15 +140,15 @@
parse5 "^5.0.0"
"@angular/cli@^9.1.2":
version "9.1.7"
resolved "https://registry.yarnpkg.com/@angular/cli/-/cli-9.1.7.tgz#0532b9c55d267cd6ee3edb79fec8b19c4e64e607"
integrity sha512-NhsIa725S/U/n7nDxp6ForusdYHEXF4aSIvsFRdoK6vbQ889c5e1Rdj+T5EWXLmpQZxeprSKhLI2alNX0nVhhQ==
dependencies:
"@angular-devkit/architect" "0.901.7"
"@angular-devkit/core" "9.1.7"
"@angular-devkit/schematics" "9.1.7"
"@schematics/angular" "9.1.7"
"@schematics/update" "0.901.7"
version "9.1.8"
resolved "https://registry.yarnpkg.com/@angular/cli/-/cli-9.1.8.tgz#fd143e26c913ccea5b8ac1716e1c168432ac96d3"
integrity sha512-yfF7glPo3Xm7fTJVln1bFZVXqHu8wkIGZRZGb6lsJa+QH4ePxHgn+dNYXho0MYpGUnhY7xOBW4MJzjS7E+1y5Q==
dependencies:
"@angular-devkit/architect" "0.901.8"
"@angular-devkit/core" "9.1.8"
"@angular-devkit/schematics" "9.1.8"
"@schematics/angular" "9.1.8"
"@schematics/update" "0.901.8"
"@yarnpkg/lockfile" "1.1.0"
ansi-colors "4.1.1"
debug "4.1.1"
@ -166,14 +166,14 @@
uuid "7.0.2"
"@angular/common@^9.0.7":
version "9.1.9"
resolved "https://registry.yarnpkg.com/@angular/common/-/common-9.1.9.tgz#16e77b2db675b80e32f1788a20c538150fd09294"
integrity sha512-y/tJtkuOJhV2kcaXZyrLZH84i4uQ1r+vaaEHvXj+JZYfYfcMMd/TDqMiPcIkUb3RxqghtZ+q0ZNW5D1Nlru3Pw==
version "9.1.11"
resolved "https://registry.yarnpkg.com/@angular/common/-/common-9.1.11.tgz#1323f7b043410791bd2d0d71b0bbb1f862319c04"
integrity sha512-Vh5lF7zWwDK9RedmYXUc8vUXyrecR3j1mAWlTlnmcHYxxFThPzN/dr0slQcPi6nyJn0EmyRKUGvAoZx4rIb7wg==
"@angular/compiler-cli@^9.1.1":
version "9.1.9"
resolved "https://registry.yarnpkg.com/@angular/compiler-cli/-/compiler-cli-9.1.9.tgz#e3c234d888074002fa5f6b7eab4f63f4ddbdb7bd"
integrity sha512-aLr2eaDlREN8XybgTbelvjtSZ8eAkxBPilnkddc700BgiC6ImyUSKaItOwa8bnjQwq4Wlz5eVG0ibsrX+5MXwg==
version "9.1.11"
resolved "https://registry.yarnpkg.com/@angular/compiler-cli/-/compiler-cli-9.1.11.tgz#39da68ddadb52008fe5231141707bddd3aa790b2"
integrity sha512-9qIxbtpRXOQnRm6inxCa5HuH87MSuMzuceD0YBVzl8v+vLtewon9KXYMmF4kTBhWa/LEa8FrajljLh0azf3VLg==
dependencies:
canonical-path "1.0.0"
chokidar "^3.0.0"
@ -189,48 +189,48 @@
yargs "15.3.0"
"@angular/compiler@^9.0.7":
version "9.1.9"
resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-9.1.9.tgz#cbf678ee28a0811a8ef3ee7be565d4911ff28ec7"
integrity sha512-kjFgaTB2ckr9lgmkS1dOGRT7kmzpQueydxsxXSHWgICNVE6F/u1PHyeSOyJRpxW0GnrkLq3QM2EUFnQGGga5bg==
version "9.1.11"
resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-9.1.11.tgz#4c7100f53c87f47e793e149427b8bdee44302381"
integrity sha512-MbVheCG0U8gt6xtiipau20N26mD2sXjLChVmRKgO6rbDruxboNMZfEd94q9NP9JRaUsVnjXvY7GMDldoymdXig==
"@angular/core@^9.0.7":
version "9.1.9"
resolved "https://registry.yarnpkg.com/@angular/core/-/core-9.1.9.tgz#db4241f867d6e14b81ed6e7c50334813c6ebfc10"
integrity sha512-q/DERgVU6vK2LtTcdVCGGBcoO424WsEfImh3Vcuy+P/ZVmthlDUC/+q+tSKt8MMf4hLpxFDQJE8vUSkktj7QEw==
version "9.1.11"
resolved "https://registry.yarnpkg.com/@angular/core/-/core-9.1.11.tgz#7a92d27292212ed381be15f9000d4019867f1c7c"
integrity sha512-KAlEedBo761O1aeoTJVziOSHi8Fttk9ipvbDZXYT/o0W/KdVwubxP34g9t5aD8LCcF8+L0z4VLw++HjdJAUpwg==
"@angular/forms@^9.0.7":
version "9.1.9"
resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-9.1.9.tgz#20c9a79d1dcb2cace45df9e2f304b658e02c1687"
integrity sha512-r675yImnb/0pY7K5W3V2ITa7YETu1I2AS+bRfII6UQ6gthyeFFOHb5noa7YneC2yqQiM6E4DQmF5ig3daPuFNg