import { Inject, Injectable } from '@angular/core'; import { ActivatedRoute, ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router'; import { BehaviorSubject, Observable, Subscription } from 'rxjs'; import { Answer } from '../enums/answer.enum'; import { Choice } from '../models/choice.model'; import { Poll } from '../models/poll.model'; import { ApiService } from './api.service'; import { ToastService } from './toast.service'; import { UserService } from './user.service'; import { UuidService } from './uuid.service'; import { HttpClient } from '@angular/common/http'; import { environment } from '../../../environments/environment'; import { StorageService } from './storage.service'; import { Title } from '@angular/platform-browser'; import { UntypedFormArray, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; import { DOCUMENT } from '@angular/common'; import { DateChoice, TimeSlices } from '../models/dateChoice.model'; import { DateUtilitiesService } from './date.utilities.service'; import { Owner } from '../models/owner.model'; import { Stack } from '../models/stack.model'; import { Vote } from '../models/vote.model'; import { ClipboardService } from 'ngx-clipboard'; import { TranslateService } from '@ngx-translate/core'; import { CommentDTO } from '../models/comment.model'; @Injectable({ providedIn: 'root', }) export class PollService implements Resolve { public _poll: BehaviorSubject = new BehaviorSubject(undefined); public readonly poll: Observable = this._poll.asObservable(); public form: UntypedFormGroup; public startDateInterval: string; public endDateInterval: string; public intervalDays: number = 1; public intervalDaysDefault = 7; public dateChoiceList: DateChoice[] = []; // sets of days as strings, config to set identical time for days in a special days poll public timeList: TimeSlices[] = [{ literal: '' }]; // ranges of time expressed as strings public previousRouteName: string = '/administration'; public nextRouteName: string = '/administration/step/2'; public step_current: number = 1; public step_max: number = 7; public round: Function; public pass_hash: string; public admin_key: string; public urlPrefix: string = window.location.origin; public advancedDisplayEnabled = false; public showDateInterval = false; public allowSeveralHours = false; public richTextMode = false; public mode_calendar = true; // default input to select dates in creation step public calendar: Date[] = []; public disabled_dates: Date[] = []; constructor( private http: HttpClient, private router: Router, private apiService: ApiService, private storageService: StorageService, private userService: UserService, private uuidService: UuidService, private toastService: ToastService, private titleService: Title, public DateUtilitiesService: DateUtilitiesService, public route: ActivatedRoute, private _clipboardService: ClipboardService, private translate: TranslateService, @Inject(DOCUMENT) private document: any, private fb: UntypedFormBuilder ) { this.createFormGroup(); // disable days before today for (let i = 1; i < 31; i++) { this.disabled_dates.push(this.DateUtilitiesService.addDaysToDate(-i, new Date())); } if (environment.autofill_creation) { this.setDemoValues(); // fill in the next 3 days of the calendar date picker this.calendar = this.DateUtilitiesService.makeDefaultCalendarDateChoices(); this.dateChoiceList = this.DateUtilitiesService.makeDefaultDateChoices(); } if (environment.autoSendNewPoll) { this.createPoll(); } } updateTitle() { let suppl = environment.production ? ' [DEV]' : ''; let apptitle = environment.appTitle + suppl; let step_current; if (this.step_current) { // in creation tunnel let stepsTitle = { date: [ 'creation.title', 'creation.want', 'dates.title', 'hours.title', 'advanced.title', 'owner.title', 'resume.title', ], text: [ 'creation.title', 'creation.want', 'dates.title', 'hours.title', 'advanced.title', 'owner.title', 'resume.title', ], }; let kind = 'date'; step_current = this.step_current | 1; if (this.form.value.isAboutDate) { kind = 'text'; } let keyToTranslate = stepsTitle[kind][(step_current - 1) | 0]; this.translate.getTranslation(this.translate.getBrowserLang()).subscribe((response) => { let step = this.translate.getParsedResult(response, 'nav.step'); let on = this.translate.getParsedResult(response, 'nav.on'); let no_title = this.translate.getParsedResult(response, 'nav.no_title'); let titleText = this.form.value.title ? ' - ' + this.form.value.title : no_title + ' -'; let stepTitle = this.translate.getParsedResult(response, keyToTranslate); if (this.step_current == 1) { titleText = stepTitle; } console.log('a', step, this.translate.getBrowserCultureLang()); let fullTitle = `${titleText} ${step} ${this.step_current} ${on} ${this.step_max} - ${environment.appTitle} `; console.log('fullTitle', fullTitle); // ex: Nom de mon sondage - étape 3 sur 7 - Framadate this.titleService.setTitle(fullTitle); }); } else { this.titleService.setTitle(apptitle); } } /** * add example values to the form for demo env */ setDemoValues(): void { this.form.patchValue({ title: 'Mon titre de sondage du ' + this.DateUtilitiesService.formateDateToInputStringNg(new Date()), description: 'répondez SVP <3 ! *-* ', custom_url: this.uuidService.getUUID(), creatorPseudo: 'Chuck Norris', creatorEmail: 'chucknorris@example.com', isAboutDate: false, whoModifiesAnswers: 'everybody', whoCanChangeAnswers: 'everybody', isProtectedByPassword: false, richTextMode: false, areResultsPublic: true, expiresDaysDelay: environment.expiresDaysDelay, }); this.automaticSlug(); } /** * set the poll slug from other data of the poll */ automaticSlug() { this.form.patchValue({ custom_url: this.makeSlug(this.form) }); } public createFormGroup() { let minlengthValidation = environment.production ? 12 : 0; let form = this.fb.group({ title: ['', [Validators.required, Validators.minLength(minlengthValidation)]], creatorPseudo: ['', []], created_at: [new Date(), [Validators.required]], creatorEmail: ['', [environment.creation_email_is_required ? Validators.required : null]], custom_url: [this.uuidService.getUUID(), [Validators.required]], description: ['', []], password: ['', []], password_repeat: ['', []], choices: new UntypedFormArray([]), whoModifiesAnswers: ['self', [Validators.required]], whoCanChangeAnswers: ['self', [Validators.required]], isAboutDate: [false, [Validators.required]], expiresDaysDelay: [environment.expiresDaysDelay, []], expiracy_date: [this.DateUtilitiesService.addDaysToDate(environment.expiresDaysDelay, new Date()), []], isZeroKnoledge: [false, [Validators.required]], isProtectedByPassword: [false, [Validators.required]], isOwnerNotifiedByEmailOnNewVote: [true, [Validators.required]], isOwnerNotifiedByEmailOnNewComment: [true, [Validators.required]], areResultsPublic: [true, [Validators.required]], richTextMode: [false, [Validators.required]], isYesAnswerAvailable: [true, [Validators.required]], isMaybeAnswerAvailable: [true, [Validators.required]], isNoAnswerAvailable: [true, [Validators.required]], allowComments: [true, [Validators.required]], maxCountOfAnswers: ['', []], hasMaxCountOfAnswers: ['', [Validators.required]], useVoterUniqueLink: [false, [Validators.required]], voterEmailList: ['', []], hasSeveralHours: [true, []], hideResults: [false, []], allowNewDateTime: [true, [Validators.required]], }); this.form = form; return form; } /** * set default configs to the form */ public patchFormDefaultValues() { this.form.patchValue({ title: 'Mon titre de sondage', description: '', custom_url: this.uuidService.getUUID(), creatorPseudo: '', creatorEmail: '', whoModifiesAnswers: 'everybody', whoCanChangeAnswers: 'everybody', isProtectedByPassword: false, richTextMode: false, areResultsPublic: true, expiresDaysDelay: environment.expiresDaysDelay, maxCountOfAnswers: 1000, voterEmailList: '', password: '', }); this.setDefaultDatesForInterval(); } /** * get a new slug from form title and creation date */ public updateSlug(): void { this.form.patchValue({ custom_url: this.makeSlug(this.form) }); } /** * auto fetch a poll when route is looking for one in the administration pattern * DO NOT USE - needs refacto * @param route * @param state */ public async resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise { console.log('resolve route,state', route, state); const segments: string[] = state.url.split('/'); const wantedcustom_url: string = segments.includes('poll') ? segments[segments.indexOf('poll') + 1] : ''; if ( !this._poll.getValue() || !this._poll.getValue().custom_url || this._poll.getValue().custom_url !== wantedcustom_url ) { if (this.pass_hash) { this.storageService.vote_stack.pass_hash = this.pass_hash; await this.loadPollByCustomUrlWithPasswordHash(wantedcustom_url, this.pass_hash); } else { await this.loadPollByCustomUrl(wantedcustom_url); } } const loadedPoll = this._poll.getValue(); if (loadedPoll) { this.storageService.vote_stack.custom_url = loadedPoll.custom_url; return loadedPoll; } else { this.router.navigate(['page-not-found']); return; } } /** * get all polls */ getAllAvailablePolls(): void { const baseHref = environment.api.version.apiV1.baseHref; console.log('getAllAvailablePolls baseHref', baseHref); const headers = ApiService.makeHeaders(); console.log('getAllAvailablePolls headers', headers); try { this.http.get(`${baseHref}/poll`, headers).subscribe((res: Observable) => { console.log('getAllAvailablePolls res', res); }); } catch (e) { console.log('getAllAvailablePolls e', e); } } /** * load a poll data and update the current poll of PollService * @param custom_url */ public async loadPollByCustomUrl(custom_url: string): Promise { if (custom_url) { const poll: Poll | undefined = await this.apiService.getPollByCustomUrl(custom_url); if (poll) { this.updateCurrentPoll(poll); this.titleService.setTitle(`☑️ ${poll.title} - ${environment.appTitle}`); } else { this.toastService.display(`sondage ${custom_url} non trouvé`); this.router.navigate(['page-not-found']); } } else { this.toastService.display(`sondage sans custom url : ${custom_url}`); } } public async loadPollByCustomUrlWithPasswordHash(custom_url: string, hash: string): Promise { if (custom_url) { const poll: Poll | undefined = await this.apiService.getPollByCustomUrlWithHash(custom_url, hash); if (poll) { this.updateCurrentPoll(poll); this.titleService.setTitle(`☑️ ${poll.title} - ${environment.appTitle}`); } else { this.toastService.display(`sondage ${custom_url} non trouvé`); this.router.navigate(['page-not-found']); } } else { this.toastService.display(`sondage sans custom url : ${custom_url}`); } } /** * update poll and parse its fields * @param poll */ public updateCurrentPoll(poll: Poll, displayToast = false): Poll { console.log('this.storageService.vote_stack.id', this.storageService.vote_stack.id); this._poll.next(poll); console.log('next poll', poll); this.storageService.setChoicesForVoteStack(poll.choices); if (displayToast) { this.toastService.display(`sondage ${poll.title} bien mis à jour`, 'success'); } return poll; } /** * add all the dates between the start and end dates in the interval section */ addIntervalOfDates(): void { const newIntervalArray = this.DateUtilitiesService.getDatesInRange( this.DateUtilitiesService.parseInputDateToDateObject(new Date(this.startDateInterval)), this.DateUtilitiesService.parseInputDateToDateObject(new Date(this.endDateInterval)), 1 ); const converted = []; newIntervalArray.forEach((element) => { converted.push({ literal: element.literal, date_object: element.date_object, timeList: [ { literal: 'matin', }, ], }); }); this.dateChoiceList = [...new Set(converted)]; // add only dates that are not already present with a Set of unique items console.log('this.dateChoiceList', this.dateChoiceList); this.showDateInterval = false; this.form.patchValue({ choices: this.dateChoiceList }); this.toastService.display(`les dates ont été ajoutées aux réponses possibles.`); } /** * handle keyboard shortcuts * @param $event * @param choice_number */ keyOnChoice($event: KeyboardEvent, choice_number: number): void { $event.preventDefault(); const lastChoice = this.choices.length - 1 === choice_number; // reset field with Ctrl + D // add a field with Ctrl + N // go to previous timeSlice with arrow up // go to next timeSlice with arrow down if ($event.key == 'ArrowUp' && choice_number > 0) { this.focusOnChoice(choice_number - 1); } if ($event.key == 'ArrowDown') { // add a field if we are on the last timeSlice if (lastChoice) { this.addChoice(); this.toastService.display('choix ajouté par raccourci "flèche bas"'); } else { this.focusOnChoice(choice_number + 1); } } if ($event.ctrlKey && $event.key == 'Backspace') { this.deleteChoiceField(choice_number); this.toastService.display('choix supprimé par raccourci "Ctrl + retour"'); this.focusOnChoice(Math.min(choice_number - 1, 0)); } if ($event.ctrlKey && $event.key == 'Enter') { // go to other fields const elem = this.document.querySelector('#creatorEmail'); if (elem) { elem.focus(); } } } /** * change time spans */ addTime() { this.timeList.push({ literal: '', }); } removeAllTimes() { this.timeList = []; } resetTimes() { this.timeList = []; } /** * add a time period to a specific date timeSlice, * focus on the new input * @param config * @param id */ addTimeToDate(config: any, id: number) { this.timeList.push({ literal: '', }); const selector = '[ng-reflect-choice_label="dateTime_' + id + '_Choices_' + (this.timeList.length - 1) + '"]'; const elem = this.document.querySelector(selector); if (elem) { elem.focus(); } } /** * convert form data to DTO to create a new poll, and store the admin key */ public createPoll(): Promise { this.toastService.display('sending...'); const newpoll = this.newPollFromForm(); return this.apiService.createPoll(newpoll).then( (resp: any) => { console.log('poll created resp', resp); this.admin_key = resp.data.poll.admin_key; this.storageService.userPolls.push(resp.data.poll); }, (error) => { this.toastService.display('BOOM, the createPoll went wrong'); this.apiService.ousideHandleError(error); } ); } /** * default interval of dates proposed is from today to 7 days more */ setDefaultDatesForInterval(): void { const dateCurrent = new Date(); const dateJson = dateCurrent.toISOString(); this.startDateInterval = dateJson.substring(0, 10); this.endDateInterval = this.DateUtilitiesService.addDaysToDate(this.intervalDaysDefault, dateCurrent) .toISOString() .substring(0, 10); this.form.patchValue({ startDateInterval: this.startDateInterval, endDateInterval: this.endDateInterval, }); this.countDays(); } askInitFormDefault(): void { this.initFormDefault(environment.autofill_creation); // this.toastService.display('formulaire réinitialisé'); } countDays(): void { this.intervalDays = this.DateUtilitiesService.countDays( this.DateUtilitiesService.parseInputDateToDateObject(new Date(this.startDateInterval)), this.DateUtilitiesService.parseInputDateToDateObject(new Date(this.endDateInterval)) ); } focusOnChoice(index): void { const selector = '#choice_label_' + index; const elem = this.document.querySelector(selector); if (elem) { elem.focus(); } } deleteChoiceField(index: number): void { if (this.choices.length !== 1) { this.choices.removeAt(index); } } initFormDefault(showDemoValues = true): void { this.form = this.createFormGroup(); this.patchFormDefaultValues(); this.setDefaultDatesForInterval(); if (showDemoValues) { this.setDemoValues(); } } get choices(): UntypedFormArray { return this.form.get('choices') as UntypedFormArray; } reinitChoices(): void { this.choices.setValue([]); } addChoice(optionalLabel = ''): void { const newControlGroup = this.fb.group({ label: this.fb.control('', [Validators.required]), imageUrl: ['', [Validators.required]], }); if (optionalLabel) { newControlGroup.patchValue({ label: optionalLabel, imageUrl: 'mon url', }); } this.choices.push(newControlGroup); this.focusOnChoice(this.choices.length - 1); } /** * make a uniq slug for the current poll creation * @param form */ makeSlug(form: UntypedFormGroup): string { let str = ''; str = form.value.created_at.getFullYear() + '_' + (form.value.created_at.getMonth() + 1) + '_' + form.value.created_at.getDate() + '_' + form.value.creatorPseudo + '_' + form.value.title; str = str.replace(/^\s+|\s+$/g, ''); // trim str = str.toLowerCase(); // remove accents, swap ñ for n, etc const from = 'àáäâèéëêìíïîòóöôùúüûñç·/_,:;'; const to = 'aaaaeeeeiiiioooouuuunc------'; for (let i = 0, l = from.length; i < l; i++) { str = str.replace(new RegExp(from.charAt(i), 'g'), to.charAt(i)); } str = str .replace(/[^a-z0-9 -]/g, '') // remove invalid chars .replace(/\s+/g, '-') // collapse whitespace and replace by - .replace(/-+/g, '-'); // collapse dashes return str + '-' + this.uuidService.getUUID(); } public async saveCurrentPoll(): Promise { const pollUrl: Subscription = await this.apiService.createPoll(this._poll.getValue()); // TODO: Maybe handle the url to update currentPoll according to backend response if (pollUrl) { this.toastService.display('Le sondage a été enregistré.'); } else { this.toastService.display('Le sondage n’a été correctement enregistré, veuillez ré-essayer.'); } } public saveParticipation(choice: Choice, user: Owner, response: Answer): void { const currentPoll = this._poll.getValue(); currentPoll.choices.find((c) => c.name === choice.name)?.updateParticipation(user, response); this.updateCurrentPoll(currentPoll); this.apiService.createParticipation(currentPoll.custom_url, choice.name, user.pseudo, response); this.toastService.display('Votre participation au sondage a été enregistrée.'); } public async deleteAllAnswers(): Promise { await this.apiService.deletePollAnswers(this._poll.getValue().custom_url); this.toastService.display('Les participations des votants à ce sondage ont été supprimées.'); } public async addComment(comment: CommentDTO): Promise { await this.apiService.createComment(this._poll.getValue().custom_url, comment).then( (resp) => { console.log('resp', resp); this.loadPollByCustomUrl(this._poll.getValue().custom_url); console.log('resp', resp); this.toastService.display('Votre commentaire a été enregistré.'); }, (err) => this.apiService.ousideHandleError(err) ); } public async deleteComments(): Promise { await this.apiService.deletePollComments(this._poll.getValue().custom_url); this.toastService.display('Les commentaires de ce sondage ont été supprimés.'); } public buildAnswersByChoiceLabelByPseudo(poll: Poll): Map> { const pseudos: Set = new Set(); poll.choices.forEach((choice: Choice) => { choice.participants.forEach((users: Set) => { users.forEach((user: Owner) => { pseudos.add(user.pseudo); }); }); }); const list = new Map>(); pseudos.forEach((pseudo: string) => { list.set( pseudo, new Map( poll.choices.map((choice: Choice) => { return [choice.name, undefined]; }) ) ); }); poll.choices.forEach((choice: Choice) => { choice.participants.forEach((users: Set, answer: Answer) => { users.forEach((user: Owner) => { list.get(user.pseudo).set(choice.name, answer); }); }); }); return list; } public getParticipationUrlFromForm(): string { return `${environment.frontDomain}#/poll/${this.form.value.custom_url}/consultation`; } public getAdministrationUrlFromForm(): string { // admin_key is filled after creation // example http://localhost:4200/#/administration/key/8Ubcg2YI99f69xz946cn4O64bQAeb return `${environment.frontDomain}#/administration/key/${this.admin_key}`; } public getParticipationUrl(): string { // http://localhost:4200/#/poll/dessin-anime/consultation // handle secure access // http://localhost:4200/#/poll/citron/consultation/secure/1c01ed9c94fc640a1be864f197ff808c // http://localhost:4200/#/poll/citron/consultation/prompt pour entrer le pass à double hasher en md5 let url = ''; let suffix_password = ''; const currentPoll = this._poll.getValue(); if (this._poll && currentPoll) { if (currentPoll.password) { // handle pass access suffix_password = '/prompt'; } if (currentPoll) { url = `${environment.frontDomain}/#/poll/${currentPoll.custom_url}/consultation${suffix_password}`; } else { url = `${environment.frontDomain}/#/poll/${this.form.value.custom_url}/consultation${suffix_password}`; } } else { url = `${environment.frontDomain}/#/poll/${this.form.value.custom_url}/consultation${suffix_password}`; } return url; } public getAdministrationUrl(): string { // http://localhost:4200/#/admin/9S75b70ECXI5J5xDc058d3H40H9r2CHfO0Kj8T02EK2U8rY8fYTn-eS659j2Dhp794Oa6R1b9V70e3WGaE30iD9h45zwdm76C85SWB4LcUCrc7e0Ncc0 let url = ''; if (this._poll && this._poll.getValue) { const polltemp = this._poll.getValue(); if (polltemp) { url = `${environment.frontDomain}/#/admin/${polltemp.admin_key}`; } } else { url = `${environment.frontDomain}/#/admin/${this.form.value.admin_key}`; } return url; } /** * enrich vote stack with missing default votes * @param vote_stack */ enrichVoteStackWithCurrentPollChoicesDefaultVotes(vote_stack: Stack) { if (this._poll && this._poll.getValue) { const polltemp = this._poll.getValue(); polltemp.choices.map((choice) => { // for each vote, if it has the choice_id, do nothing, else, add a default vote if (!this.findExistingVoteFromChoiceId(choice.id, vote_stack.votes)) { vote_stack.votes.push(new Vote(choice.id)); } }); } } /** * find an existing vote in vote_stack from its choice_id * @param choice_id * @param votes */ findExistingVoteFromChoiceId(choice_id: number, votes: Vote[]) { return votes.find((vote: Vote) => { if (vote.choice_id === choice_id) { return vote; } }); } /** * convertir les dates de la propriété Calendar en objets de saisie de texte */ convertCalendarToText() { console.log('this.dateChoiceList', this.dateChoiceList); if (this.calendar && this.calendar.length) { let converted = []; for (let someDate of this.calendar) { converted.push(this.DateUtilitiesService.convertDateToDateChoiceObject(someDate)); } this.dateChoiceList = converted.sort((first: any, second: any) => { return first.date_object - second.date_object; }); return converted; } else { this.dateChoiceList = []; } return this.dateChoiceList; } /** * convert the DateChoices to an arrray of Dates for calendar picker */ convertTextToCalendar(): Date[] { console.log('convert text to calendar', this.dateChoiceList); let converted = []; for (let someDateChoice of this.dateChoiceList) { let dateObj = new Date(someDateChoice.date_input); console.log('dateObj', dateObj); // check that date is not part of the disabled dates if (this.disabled_dates.indexOf(dateObj) === -1) { converted.push(dateObj); } } console.log('converted', converted); this.calendar = converted; return converted; } patchFormWithPoll(poll: Poll) { this.form.patchValue({ ...poll, isAboutDate: poll.kind == 'date', }); } /** * @description convert to API version 1 data transition object */ newPollFromForm(): Poll { let form = this.form; const newOwner = this.storageService.vote_stack.owner; newOwner.pseudo = form.value.creatorPseudo; newOwner.email = form.value.creatorEmail; const newpoll = new Poll(newOwner, form.value.custom_url, form.value.title); const pollKeys = Object.keys(newpoll); const formFields = Object.keys(form.value); newpoll.allowed_answers = []; // comparer les champs de formulaire avec le DTO de création de sondage for (const pk of pollKeys) { if (formFields.indexOf(pk) !== -1) { const field = form.value[pk]; newpoll[pk] = field; } else { console.log('newPollFromForm : manque pollKey', pk); } } if (form.value.isYesAnswerAvailable) { newpoll.allowed_answers.push('yes'); } if (form.value.isMaybeAnswerAvailable) { newpoll.allowed_answers.push('maybe'); } if (form.value.isNoAnswerAvailable) { newpoll.allowed_answers.push('no'); } newpoll.description = form.value.description; newpoll.has_several_hours = form.value.hasSeveralHours; newpoll.max_count_of_answers = form.value.maxCountOfAnswers | (1024 * 10); newpoll.maxCountOfAnswers = form.value.maxCountOfAnswers | (1024 * 10); newpoll.password = form.value.password; newpoll.kind = form.value.isAboutDate ? 'date' : 'classic'; newpoll.allow_comments = form.value.allowComments; // merge choices from storage if (form.value.isAboutDate) { // first we convert calendar picker dates. // we want a list of date object, and we want the kind of dates who was lastly edited by the user // depending on the manual or datepicker mode, we need to get a converted list of dates let convertedDates = []; if (this.mode_calendar) { // mode calendar date picker, we take the list of date objects in calendar property convertedDates = this.calendar; } else { // mode text, we convert to calendar list, and take that list convertedDates = this.convertTextToCalendar(); } console.log('this.calendar', this.calendar); for (let elem of convertedDates) { console.log('elem', elem); let converted_day = this.DateUtilitiesService.convertDateToDateChoiceObject(elem); newpoll.dateChoices.push(converted_day); } console.log('newpoll.dateChoices', newpoll.dateChoices); } else { // text choices newpoll.choicesText = Object.assign([], this.storageService.choicesText); } newpoll.choices = Object.assign([], this.storageService.choices); // newpoll.dateChoices = Object.assign([], this.storageService.dateChoices); newpoll.timeSlices = Object.assign([], this.storageService.timeSlices); console.log('newpoll', newpoll); return newpoll; } /** * copy public url of new poll * @param textToCopy */ copyText(textToCopy: string) { this._clipboardService.copyFromContent(textToCopy); this.translate.get('success.copy_message').subscribe((resp) => { console.log('resp', resp); this.toastService.display(`${resp} ${textToCopy}`); }); } }