diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 00000000..cb2ae8de --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,16 @@ +build: + image: node:12 + before_script: + - npm ci + script: + - npm run build + artifacts: + paths: + - dist/ +test: + image: node:12 + type: test + before_script: + - npm ci + script: + - npm run test diff --git a/.storybook/main.js b/.storybook/main.js new file mode 100644 index 00000000..ec3f5802 --- /dev/null +++ b/.storybook/main.js @@ -0,0 +1,4 @@ +module.exports = { + stories: ['../src/**/*.stories.ts'], + addons: ['@storybook/addon-actions', '@storybook/addon-links', '@storybook/addon-notes'], +}; diff --git a/.storybook/tsconfig.json b/.storybook/tsconfig.json new file mode 100644 index 00000000..0bcfc758 --- /dev/null +++ b/.storybook/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "../tsconfig.app.json", + "compilerOptions": { + "types": [ + "node" + ] + }, + "exclude": [ + "../src/test.ts", + "../src/**/*.spec.ts", + "../projects/**/*.spec.ts" + ], + "include": [ + "../src/**/*", + "../projects/**/*" + ], + "files": [ + "./typings.d.ts" + ] +} diff --git a/.storybook/typings.d.ts b/.storybook/typings.d.ts new file mode 100644 index 00000000..f73d61b3 --- /dev/null +++ b/.storybook/typings.d.ts @@ -0,0 +1,4 @@ +declare module '*.md' { + const content: string; + export default content; +} diff --git a/angular.json b/angular.json index df9c8dac..8405a6e7 100644 --- a/angular.json +++ b/angular.json @@ -1,133 +1,137 @@ { - "$schema": "./node_modules/@angular/cli/lib/config/schema.json", - "version": 1, - "newProjectRoot": "projects", - "projects": { - "framadate": { - "projectType": "application", - "schematics": { - "@schematics/angular:component": { - "style": "scss" - } - }, - "root": "", - "sourceRoot": "src", - "prefix": "framadate", - "architect": { - "build": { - "builder": "@angular-devkit/build-angular:browser", - "options": { - "outputPath": "dist/framadate", - "index": "src/index.html", - "main": "src/main.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.app.json", - "aot": false, - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "src/styles.scss" - ], - "scripts": [ - "node_modules/marked/lib/marked.js", - "node_modules/prismjs/prism.js", - "node_modules/prismjs/components/prism-css.min.js" - ] - }, - "configurations": { - "production": { - "fileReplacements": [ - { - "replace": "src/environments/environment.ts", - "with": "src/environments/environment.prod.ts" + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "framadate": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "style": "scss" } - ], - "optimization": true, - "outputHashing": "all", - "sourceMap": false, - "extractCss": true, - "namedChunks": false, - "aot": true, - "extractLicenses": true, - "vendorChunk": false, - "buildOptimizer": true, - "budgets": [ - { - "type": "initial", - "maximumWarning": "2mb", - "maximumError": "5mb" + }, + "root": "", + "sourceRoot": "src", + "prefix": "framadate", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/framadate", + "index": "src/index.html", + "main": "src/main.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.app.json", + "aot": true, + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "node_modules/primeicons/primeicons.css", + "node_modules/font-awesome/css/font-awesome.css", + "node_modules/primeng/resources/themes/nova-light/theme.css", + "node_modules/primeng/resources/primeng.min.css", + "src/styles.scss" + ], + "scripts": [ + "node_modules/marked/lib/marked.js", + "node_modules/prismjs/prism.js", + "node_modules/prismjs/components/prism-css.min.js" + ] + }, + "configurations": { + "production": { + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + } + ], + "optimization": true, + "outputHashing": "all", + "sourceMap": false, + "extractCss": true, + "namedChunks": false, + "extractLicenses": true, + "vendorChunk": false, + "buildOptimizer": true, + "budgets": [ + { + "type": "initial", + "maximumWarning": "2mb", + "maximumError": "5mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "6kb", + "maximumError": "10kb" + } + ] + } + } }, - { - "type": "anyComponentStyle", - "maximumWarning": "6kb", - "maximumError": "10kb" + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "options": { + "browserTarget": "framadate:build" + }, + "configurations": { + "production": { + "browserTarget": "framadate:build:production" + } + } + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "framadate:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "src/test.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.spec.json", + "karmaConfig": "karma.conf.js", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "src/styles.scss" + ], + "scripts": [] + } + }, + "lint": { + "builder": "@angular-devkit/build-angular:tslint", + "options": { + "tsConfig": [ + "tsconfig.app.json", + "tsconfig.spec.json", + "e2e/tsconfig.json" + ], + "exclude": [ + "**/node_modules/**" + ] + } + }, + "e2e": { + "builder": "@angular-devkit/build-angular:protractor", + "options": { + "protractorConfig": "e2e/protractor.conf.js", + "devServerTarget": "framadate:serve" + }, + "configurations": { + "production": { + "devServerTarget": "framadate:serve:production" + } + } } - ] } - } - }, - "serve": { - "builder": "@angular-devkit/build-angular:dev-server", - "options": { - "browserTarget": "framadate:build" - }, - "configurations": { - "production": { - "browserTarget": "framadate:build:production" - } - } - }, - "extract-i18n": { - "builder": "@angular-devkit/build-angular:extract-i18n", - "options": { - "browserTarget": "framadate:build" - } - }, - "test": { - "builder": "@angular-devkit/build-angular:karma", - "options": { - "main": "src/test.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.spec.json", - "karmaConfig": "karma.conf.js", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "src/styles.scss" - ], - "scripts": [] - } - }, - "lint": { - "builder": "@angular-devkit/build-angular:tslint", - "options": { - "tsConfig": [ - "tsconfig.app.json", - "tsconfig.spec.json", - "e2e/tsconfig.json" - ], - "exclude": [ - "**/node_modules/**" - ] - } - }, - "e2e": { - "builder": "@angular-devkit/build-angular:protractor", - "options": { - "protractorConfig": "e2e/protractor.conf.js", - "devServerTarget": "framadate:serve" - }, - "configurations": { - "production": { - "devServerTarget": "framadate:serve:production" - } - } } - } - }}, - "defaultProject": "framadate" + }, + "defaultProject": "framadate" } diff --git a/documentation/classes/AppPage.html b/documentation/classes/AppPage.html new file mode 100644 index 00000000..6fb7c2db --- /dev/null +++ b/documentation/classes/AppPage.html @@ -0,0 +1,266 @@ + + +
+ + ++
+ e2e/src/app.po.ts
+
+ Methods+ |
+
+
|
+
+ + + + getTitleText + + + + | +
+getTitleText()
+ |
+
+ Defined in e2e/src/app.po.ts:8
+ |
+
+
+
+ Returns :
+ any
+
+ |
+
+ + + + navigateTo + + + + | +
+navigateTo()
+ |
+
+ Defined in e2e/src/app.po.ts:4
+ |
+
+
+
+ Returns :
+ any
+
+ |
+
import { browser, by, element } from 'protractor';
+
+export class AppPage {
+ navigateTo() {
+ return browser.get(browser.baseUrl) as Promise<any>;
+ }
+
+ getTitleText() {
+ return element(by.css('app-root h1')).getText() as Promise<string>;
+ }
+}
+
+ +
+ src/app/app.module.ts
+
+
+ MissingTranslationHandler
+
+ Methods+ |
+
+
|
+
+ + + + handle + + + + | +||||||
+handle(params: MissingTranslationHandlerParams)
+ |
+ ||||||
+ Defined in src/app/app.module.ts:62
+ |
+ ||||||
+
+
+ Parameters :
+
+
+
+
+ Returns :
+ string
+
+
+
+
+ |
+
import {BrowserModule} from '@angular/platform-browser';
+import {NgModule} from '@angular/core';
+import {AppRoutingModule} from './app-routing.module';
+import {AppComponent} from './app.component';
+import {BaseComponent} from './pages/base-page/base.component';
+import {KindComponent} from './pages/kind/kind.component';
+import {HeaderComponent} from './header/header.component';
+import {FormsModule} from '@angular/forms';
+import {NavigationComponent} from './ui/navigation/navigation.component';
+import {RouterModule} from '@angular/router';
+import {Routes} from './config/Routes';
+import {CommonModule, registerLocaleData} from '@angular/common';
+import {DatesComponent} from './pages/dates/dates.component';
+import {DebuggerComponent} from './debugger/debugger.component';
+import {VisibilityComponent} from './pages/visibility/visibility.component';
+import {ResumeComponent} from './pages/resume/resume.component';
+import {PicturesComponent} from './pages/pictures/pictures.component';
+import {AnswersComponent} from './pages/answers/answers.component';
+import {EndConfirmationComponent} from './pages/end-confirmation/end-confirmation.component';
+import {CreateOrRetrieveComponent} from './pages/create-or-retrieve/create-or-retrieve.component';
+import localeFr from '@angular/common/locales/fr';
+import localeEn from '@angular/common/locales/en';
+import {VotingSummaryComponent} from './pages/voting/voting-summary/voting-summary.component';
+import {VotingGraphComponent} from './pages/voting/voting-graph/voting-graph.component';
+import {VotingChoiceComponent} from './pages/voting/voting-choice/voting-choice.component';
+import {PasswordComponent} from './pages/password/password.component';
+import {HomeComponent} from './pages/home/home.component';
+import {HttpClient, HttpClientModule} from '@angular/common/http';
+import {MarkdownModule} from 'ngx-markdown';
+import {
+ MissingTranslationHandler,
+ MissingTranslationHandlerParams,
+ TranslateLoader,
+ TranslateModule,
+ TranslateService
+} from '@ngx-translate/core';
+import {TranslateHttpLoader} from '@ngx-translate/http-loader';
+import {PollGraphicComponent} from './poll-graphic/poll-graphic.component';
+
+import {AdminComponent} from './pages/admin/admin.component';
+import {SelectorComponent} from './ui/selector/selector.component';
+import {BrowserAnimationsModule} from "@angular/platform-browser/animations";
+import {ConfigService} from "./services/config.service";
+import {PollService} from "./services/poll.service";
+import {ToastModule} from 'primeng/toast';
+import {ConfirmationService, MessageModule, MessageService} from "primeng";
+import {PollDisplayComponent} from './pages/poll-display/poll-display.component';
+import {VotingComponent} from './pages/voting/voting.component';
+import {VotingCommentComponent} from './pages/voting/voting-comment/voting-comment.component';
+import {ResettableInputDirective} from './ui/directives/resettable-input.directive';
+import {ClipboardModule} from "ngx-clipboard";
+import {ErasableInputComponent} from './ui/erasable-input/erasable-input.component';
+import {ConfirmDialogModule} from 'primeng/confirmdialog';
+import {DialogModule} from 'primeng/dialog';
+import {DateValueAccessorModule} from "./custom-lib/date-value-accessor";
+import {CopyTextComponent} from './ui/copy-text/copy-text.component';
+import {CommentsListComponent} from './pages/voting/comments-list/comments-list.component';
+import {ChoicesListComponent} from './pages/voting/choices-list/choices-list.component';
+import {VotingNavigationComponent} from './pages/voting/voting-navigation/voting-navigation.component';
+
+export class MyMissingTranslationHandler implements MissingTranslationHandler {
+ handle(params: MissingTranslationHandlerParams) {
+ return 'some value';
+ }
+}
+
+registerLocaleData(localeFr, 'fr');
+registerLocaleData(localeEn, 'en');
+
+export function HttpLoaderFactory(http: HttpClient) {
+ return new TranslateHttpLoader(http);
+}
+
+@NgModule({
+ declarations: [
+ AppComponent,
+ BaseComponent,
+ KindComponent,
+ HeaderComponent,
+ NavigationComponent,
+ DatesComponent,
+ DebuggerComponent,
+ VisibilityComponent,
+ ResumeComponent,
+ PicturesComponent,
+ AnswersComponent,
+ EndConfirmationComponent,
+ CreateOrRetrieveComponent,
+ VotingSummaryComponent,
+ VotingGraphComponent,
+ VotingChoiceComponent,
+ PasswordComponent,
+ HomeComponent,
+ PollGraphicComponent,
+ AdminComponent,
+ SelectorComponent,
+ PollDisplayComponent,
+ VotingComponent,
+ VotingCommentComponent,
+ ResettableInputDirective,
+ ErasableInputComponent,
+ CopyTextComponent,
+ CommentsListComponent,
+ ChoicesListComponent,
+ VotingNavigationComponent,
+
+ ],
+ imports: [
+ ConfirmDialogModule,
+ ClipboardModule,
+ CommonModule,
+ BrowserModule,
+ DialogModule,
+ DateValueAccessorModule,
+ BrowserAnimationsModule,
+ AppRoutingModule,
+ ToastModule,
+ MessageModule,
+ MarkdownModule.forRoot(),
+ TranslateModule.forRoot({
+ missingTranslationHandler: {
+ provide: MissingTranslationHandler,
+ useClass: MyMissingTranslationHandler,
+ },
+ // useDefaultLang: false,
+ loader: {
+ provide: TranslateLoader,
+ useFactory: HttpLoaderFactory,
+ deps: [HttpClient]
+ }
+ }),
+ HttpClientModule,
+ FormsModule,
+ RouterModule.forRoot(Routes, {useHash: true})
+ ],
+ providers: [TranslateService, ConfigService, PollService, MessageService, ConfirmationService],
+ bootstrap: [AppComponent]
+})
+export class AppModule {
+}
+
+ +
+ src/app/config/PollConfig.ts
+
+
+
configuration of the poll, add new fields at will
+ + + + + + +
+ Properties+ |
+
+
|
+
+ Methods+ |
+
+
|
+
+ + + + adminKey + + + | +
+ Type : string
+
+ |
+
+ Default value : ''
+ |
+
+ Defined in src/app/config/PollConfig.ts:58
+ |
+
+ + + + allowSeveralHours + + + | +
+ Type : string
+
+ |
+
+ Default value : 'true'
+ |
+
+ Defined in src/app/config/PollConfig.ts:41
+ |
+
+ + + + answers + + + | +
+ Type : PollAnswer[]
+
+ |
+
+ Default value : defaultAnswers
+ |
+
+ Defined in src/app/config/PollConfig.ts:66
+ |
+
+ + + + canModifyAnswers + + + | +
+ Type : boolean
+
+ |
+
+ Default value : true
+ |
+
+ Defined in src/app/config/PollConfig.ts:60
+ |
+
+ + + + creationDate + + + | +
+ Default value : new Date()
+ |
+
+ Defined in src/app/config/PollConfig.ts:45
+ |
+
+ + + + currentPoll + + + | +
+ Defined in src/app/config/PollConfig.ts:50
+ |
+
+ + + + customUrl + + + | +
+ Type : string
+
+ |
+
+ Default value : ''
+ |
+
+ Defined in src/app/config/PollConfig.ts:53
+ |
+
+ + + + customUrlIsUnique + + + | +
+ Type : null
+
+ |
+
+ Default value : null
+ |
+
+ Defined in src/app/config/PollConfig.ts:54
+ |
+
+ + + + dateList + + + | +
+ Type : any
+
+ |
+
+ Default value : otherDefaultDates
+ |
+
+ Defined in src/app/config/PollConfig.ts:63
+ |
+
+ + + + deletionDateAfterLastModification + + + | +
+ Type : number
+
+ |
+
+ Default value : 180
+ |
+
+ Defined in src/app/config/PollConfig.ts:27
+ |
+
+ + + + description + + + | +
+ Type : string
+
+ |
+
+ Default value : 'ma description'
+ |
+
+ Defined in src/app/config/PollConfig.ts:32
+ |
+
+ + + + expiracyDateDefaultInDays + + + | +
+ Type : number
+
+ |
+
+ Default value : 60
+ |
+
+ Defined in src/app/config/PollConfig.ts:26
+ |
+
+ + + + expirationDate + + + | +
+ Type : string
+
+ |
+
+ Default value : ''
+ |
+
+ Defined in src/app/config/PollConfig.ts:46
+ |
+
+ + + + isAdmin + + + | +
+ Type : boolean
+
+ |
+
+ Default value : true
+ |
+
+ Defined in src/app/config/PollConfig.ts:35
+ |
+
+ + + + myComment + + + | +
+ Type : string
+
+ |
+
+ Default value : 'wouah trop bien framadate!'
+ |
+
+ Defined in src/app/config/PollConfig.ts:34
+ |
+
+ + + + myEmail + + + | +
+ Type : string
+
+ |
+
+ Default value : "tktest@tktest.com"
+ |
+
+ Defined in src/app/config/PollConfig.ts:38
+ |
+
+ + + + myName + + + | +
+ Type : string
+
+ |
+
+ Default value : 'mon pseudo'
+ |
+
+ Defined in src/app/config/PollConfig.ts:33
+ |
+
+ + + + myPolls + + + | +
+ Type : any
+
+ |
+
+ Default value : []
+ |
+
+ Defined in src/app/config/PollConfig.ts:39
+ |
+
+ + + + myTempVoteStack + + + | +
+ Type : number
+
+ |
+
+ Default value : 0
+ |
+
+ Defined in src/app/config/PollConfig.ts:37
+ |
+
+ + + + myVoteStack + + + | +
+ Type : any
+
+ |
+
+ Defined in src/app/config/PollConfig.ts:36
+ |
+
+ + + + owner_modifier_token + + + | +
+ Type : string
+
+ |
+
+ Default value : ''
+ |
+
+ Defined in src/app/config/PollConfig.ts:59
+ |
+
+ + + + password + + + | +
+ Type : string
+
+ |
+
+ Default value : ''
+ |
+
+ Defined in src/app/config/PollConfig.ts:52
+ |
+
+ + + + passwordAccess + + + | +
+ Type : number
+
+ |
+
+ Default value : 0
+ |
+
+ Defined in src/app/config/PollConfig.ts:51
+ |
+
+ + + + pollId + + + | +
+ Type : null
+
+ |
+
+ Default value : null
+ |
+
+ Defined in src/app/config/PollConfig.ts:48
+ |
+
+ + + + pollSlug + + + | +
+ Type : null
+
+ |
+
+ Default value : null
+ |
+
+ Defined in src/app/config/PollConfig.ts:49
+ |
+
+ + + + pollType + + + | +
+ Type : string
+
+ |
+
+ Default value : 'dates'
+ |
+
+ Defined in src/app/config/PollConfig.ts:30
+ |
+
+ + + + step + + + | +
+ Type : number
+
+ |
+
+ Default value : 0
+ |
+
+ Defined in src/app/config/PollConfig.ts:28
+ |
+
+ + + + stepMax + + + | +
+ Type : number
+
+ |
+
+ Default value : 3
+ |
+
+ Defined in src/app/config/PollConfig.ts:29
+ |
+
+ + + + timeList + + + | +
+ Type : DateChoice[]
+
+ |
+
+ Default value : otherDefaultDates
+ |
+
+ Defined in src/app/config/PollConfig.ts:64
+ |
+
+ + + + title + + + | +
+ Type : string
+
+ |
+
+ Default value : 'titre'
+ |
+
+ Defined in src/app/config/PollConfig.ts:31
+ |
+
+ + + + urlAdmin + + + | +
+ Default value : environment.baseHref + '/#/admin/d65es45fd45sdf45sd345f312sdf31sgfd345'
+ |
+
+ Defined in src/app/config/PollConfig.ts:57
+ |
+
+ + + + urlPublic + + + | +
+ Default value : environment.baseHref + '/#/poll/id/4'
+ |
+
+ Defined in src/app/config/PollConfig.ts:56
+ |
+
+ + + + urlSlugPublic + + + | +
+ Type : null
+
+ |
+
+ Default value : null
+ |
+
+ Defined in src/app/config/PollConfig.ts:55
+ |
+
+ + + + visibility + + + | +
+ Type : string
+
+ |
+
+ Default value : 'link_only'
+ |
+
+ Defined in src/app/config/PollConfig.ts:43
+ |
+
+ + + + voteChoices + + + | +
+ Type : string
+
+ |
+
+ Default value : 'only_yes'
+ |
+
+ Defined in src/app/config/PollConfig.ts:44
+ |
+
+ + + + voteStackId + + + | +
+ Type : null
+
+ |
+
+ Default value : null
+ |
+
+ Defined in src/app/config/PollConfig.ts:47
+ |
+
+ + + + whoCanChangeAnswers + + + | +
+ Type : string
+
+ |
+
+ Default value : 'everybody'
+ |
+
+ Defined in src/app/config/PollConfig.ts:62
+ |
+
+ + + + whoModifiesAnswers + + + | +
+ Type : string
+
+ |
+
+ Default value : "everybody"
+ |
+
+ Defined in src/app/config/PollConfig.ts:61
+ |
+
+ + + + resetConfig + + + + | +
+resetConfig()
+ |
+
+ Defined in src/app/config/PollConfig.ts:68
+ |
+
+
+
+ Returns :
+ void
+
+ |
+
import {environment} from "../../environments/environment";
+import {DateChoice, defaultAnswers, otherDefaultDates, PollAnswer} from "./defaultConfigs";
+
+export interface DateOption {
+ timeList: any;
+ literal: string;
+ date_object?: object;
+}
+
+const baseConfigValues = {
+ pollType: "classic",
+ title: "",
+ description: "",
+ myName: "",
+ myEmail: "",
+};
+
+
+/**
+ * configuration of the poll, add new fields at will
+ */
+export class PollConfig {
+ expiracyDateDefaultInDays = 60;
+ deletionDateAfterLastModification = 180;
+ step: number = 0; // step in the progress of creating a poll
+ stepMax: number = 3; // step max in the progress of creating a poll
+ pollType: string = 'dates';// classic or dates
+ title: string = 'titre';
+ description: string = 'ma description';
+ myName: string = 'mon pseudo';
+ myComment: string = 'wouah trop bien framadate!';
+ isAdmin: boolean = true;
+ myVoteStack: any;
+ myTempVoteStack = 0;
+ myEmail: string = "tktest@tktest.com";
+ myPolls: any = [];// list of retrieved polls from the backend api
+ // date specific poll, we have the choice to setup different hours (timeList) for all possible dates (dateList), or use the same hours for all dates
+ allowSeveralHours = 'true';
+ // access
+ visibility = 'link_only'; // visible to anyone with the link:
+ voteChoices = 'only_yes'; // possible answers to a vote choice: only "yes", "yes, maybe, no"
+ creationDate = new Date();
+ expirationDate = ''; // expiracy date
+ voteStackId = null; // id of the vote stack to update
+ pollId = null; // id of the current poll when created. data given by the backend api
+ pollSlug = null; // id of the current poll when created. data given by the backend api
+ currentPoll; // current poll selected with createPoll or getPoll of ConfigService
+ passwordAccess = 0;
+ password = '';
+ customUrl = ''; // custom slug in the url, must be unique
+ customUrlIsUnique = null; // given by the backend
+ urlSlugPublic = null;
+ urlPublic = environment.baseHref + '/#/poll/id/4';
+ urlAdmin = environment.baseHref + '/#/admin/d65es45fd45sdf45sd345f312sdf31sgfd345';
+ adminKey = ''; // key to change config of the poll
+ owner_modifier_token = ''; // key to change a vote stack
+ canModifyAnswers: boolean = true;// bool for the frontend selector
+ whoModifiesAnswers = "everybody";// everybody, self, nobody (= just admin)
+ whoCanChangeAnswers = 'everybody';// everybody, self, nobody (= just admin)
+ dateList: any = otherDefaultDates; // sets of days as strings, config to set identical time for days in a special days poll
+ timeList: DateChoice[] = otherDefaultDates; // ranges of time expressed as strings
+
+ answers: PollAnswer[] = defaultAnswers;
+
+ resetConfig() {
+ const self = this;
+ Object.keys(baseConfigValues).forEach((key) => {
+ self[key] = baseConfigValues[key];
+ })
+ }
+}
+
+ +
+ src/app/pages/admin/admin.component.ts
+
+
+ OnInit
+
selector | +framadate-admin |
+
styleUrls | +./admin.component.scss |
+
templateUrl | +./admin.component.html |
+
+ Properties+ |
+
+
|
+
+ Methods+ |
+
+
|
+
+constructor(config: ConfigService)
+ |
+ ||||||
+ Defined in src/app/pages/admin/admin.component.ts:9
+ |
+ ||||||
+
+ Parameters :
+
+
|
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ Defined in src/app/pages/admin/admin.component.ts:14
+ |
+
+
+
+ Returns :
+ void
+
+ |
+
+ + + + Public + config + + + | +
+ Type : ConfigService
+
+ |
+
+ Defined in src/app/pages/admin/admin.component.ts:11
+ |
+
import {Component, OnInit} from '@angular/core';
+import {ConfigService} from '../../services/config.service';
+
+@Component({
+ selector: 'framadate-admin',
+ templateUrl: './admin.component.html',
+ styleUrls: ['./admin.component.scss']
+})
+export class AdminComponent implements OnInit {
+
+ constructor(public config: ConfigService) {
+ }
+
+ ngOnInit() {
+ }
+
+}
+
+ <form>
+ <h1>Nom du sondage</h1>
+
+ <!--Infos-->
+ <h2>{{"admin.info_section_title"|translate}}</h2>
+
+ <label for="title">
+ {{"admin.choose_title"|translate}}
+ </label>
+ <input type="text" name="title">
+
+ <label for="desc">
+ {{"admin.description"|translate}}
+ </label>
+ <textarea name="desc"> </textarea>
+
+ <label for="name">
+ {{"creation.name"|translate}}
+ </label>
+ <input type="text" name="name">
+
+ <!--Params-->
+ <h2>{{"admin.settings_section_title"|translate}}</h2>
+ <h3>{{"visibility.title"|translate}}</h3>
+
+ <section class="row">
+ <label for="answer-visible">
+ {{"visibility.visibility_want"|translate}}
+ </label>
+ <select name="answer-visible">
+ <option value="all" selected>
+ {{"visibility.visibility_link"|translate}}
+ </option>
+ <option value="per">
+ {{"visibility.visibility_nobody"|translate}}
+ </option>
+ </select>
+ <label for="answer-visible">
+ {{"visibility.visibility_see"|translate}}
+ </label>
+ </section>
+
+ <h3>{{"visibility.votes"|translate}}</h3>
+ <section>
+ <label for="vote-date">
+ {{"visibility.archiving"|translate}}
+ </label>
+ <input type="date" name="vote-date">
+ </section>
+ <section>
+ <label for="alter-vote">
+ {{"visibility.archiving_start"|translate}}
+ </label>
+ <select name="alter-vote">
+ <option value="yes" selected>
+ {{"visibility.archiving_can"|translate}}
+ </option>
+ <option value="no">
+ {{"visibility.archiving_can_not"|translate}}
+ </option>
+ </select>
+ <label for="type-vote">
+ {{"visibility.archiving_end"|translate}}
+ </label>
+ <select name="type-vote">
+ <option value="solo" selected>
+ {{"visibility.modfiy_their"|translate}}
+ </option>
+ <option value="all">
+ {{"visibility.modfiy_everyone"|translate}}
+ </option>
+ </select>
+ </section>
+
+ <label for="del-vote">
+ {{"admin.votes_deletion_desc"|translate}}
+ </label>
+ <button class="btn btn--alert btn--outline"
+ (click)="config.deleteVotes()"
+ >
+ <i class="fa fa-trash"></i>
+ {{'admin.votes_deletion_btn'|translate}}
+ <i class="fa fa-object-group"></i></button>
+
+ <h3>{{"admin.comments_deletion_title"|translate}}</h3>
+ <label for="del-com">
+ {{"admin.comments_deletion_desc"|translate}}
+ </label>
+
+ <button class="btn btn--alert btn--outline"
+ (click)="config.deleteComments()"
+ >
+ <i class="fa fa-trash"></i>
+
+ {{'admin.comments_deletion_btn'|translate}}
+ <i class="fa fa-comments-o"></i></button>
+
+ <h3>{{"admin.archiving_title"|translate}}</h3>
+ <section class="row">
+ <label for="arch">
+ {{"admin.archiving_desc"|translate}}
+ </label>
+ <input type="date" name="arch">
+ </section>
+
+ <h3>{{"visibility.access"|translate}}</h3>
+ <section class="row">
+ <label for="password">Je</label>
+ <select name="password">
+ <option value="yes" selected>
+ {{"visibility.access_want_yes"|translate}}
+ </option>
+ <option value="no">
+ {{"visibility.access_want_no"|translate}}
+ </option>
+ </select>
+ <label for="alter-vote">
+ {{"visibility.access_protect"|translate}}
+ </label>
+ </section>
+
+ <h3>{{"admin.deletion"|translate}}</h3>
+ <label for="del"> {{"admin.deletion_desc"|translate}} </label>
+ <input class="btn btn--alert" type="submit"
+ name="del" value="{{'admin.deletion_btn'|translate}}">
+
+ <!-- Access link -->
+ <h2>{{"admin.link"|translate}}</h2>
+ <h3>{{"admin.link_admin"|translate}}</h3>
+
+ <label for="copy-link-admin">
+ {{"admin.link_admin_desc"|translate}}
+ https://framadate.org/urladmindusondage
+ </label>
+ <input class="btn btn--mini" type="submit" name="copy-link-admin"
+ value="{{'admin.copy_link'|translate}}">
+ <a href="#" class="next">{{"admin.link_admin_btn"|translate}}</a>
+
+ <h3>{{"admin.polled_people"|translate}}</h3>
+ <label for="copy-link">
+ {{"admin.polled_people_desc"|translate}}
+ {{config.urlPublic}}</label>
+ <input class="btn btn--mini" type="submit" name="copy-link"
+ value="{{'admin.copy_link'|translate}}">
+ <a href="#" class="next">{{"admin.polled_people_btn"|translate}}</a>
+
+ <h3>{{"admin.email_links"|translate}}</h3>
+ <label for="mail">{{"admin.email_links_desc"|translate}}</label>
+ <input type="email" name="mail">
+ <input class="btn btn--mini" type="submit" name="send-mail"
+ value="{{'admin.email_links_btn'|translate}}">
+
+</form>
+<p-confirmDialog header="Confirmation" icon="pi pi-exclamation-triangle"></p-confirmDialog>
+
+
+ ./admin.component.scss
+
form{
+ display:flex;
+ flex-direction: column;
+ width:340px;
+margin:auto;
+}
+.row{
+ flex-direction: row;
+ flex-wrap: wrap;
+ padding-bottom: 65px;
+}
+textarea{
+ height:115px;
+ margin-bottom: 50px;
+}
+label{
+ padding-bottom: 10px;
+ flex-wrap: wrap;
+}
+h2,h1{
+ margin-bottom: 40px;
+}
+h2{
+ margin-top: 40px;
+ padding-left:16px;
+}
+.btn--alert{
+ font-weight: 600;
+}
+h3{
+ padding-left: 28px;
+ padding-bottom:15px;
+}
+select,input[type=date]{
+ max-width: 130px;
+}
+.btn--outline{
+ margin-bottom: 70px;
+}
+
+ +
+ src/app/pages/answers/answers.component.ts
+
+
+ BaseComponent
+
+
+ OnInit
+ AfterViewInit
+ OnChanges
+
selector | +framadate-answers |
+
styleUrls | +./answers.component.scss |
+
templateUrl | +./answers.component.html |
+
+ Properties+ |
+
+
|
+
+ Methods+ |
+
+
|
+
+constructor(config: ConfigService, document: any, cd: ChangeDetectorRef)
+ |
+ ||||||||||||
+ + | +||||||||||||
+
+ Parameters :
+
+
|
+
+ + + + addAnswer + + + + | +
+addAnswer()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
+ + + + checkValidAnswers + + + + | +
+checkValidAnswers()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
+ + + + focusOnAnswer + + + + | +||||
+focusOnAnswer(i)
+ |
+ ||||
+ + | +||||
+
+
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + navigateOrDelete + + + + | +|||||||||
+navigateOrDelete(event: KeyboardEvent, i)
+ |
+ |||||||||
+ + | +|||||||||
+
+
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + ngAfterViewInit + + + + | +
+ngAfterViewInit()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
+ + + + ngOnChanges + + + + | +
+ngOnChanges()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
+ + + + showModalForPictureOfAnswer + + + + | +||||
+showModalForPictureOfAnswer(answer)
+ |
+ ||||
+ + | +||||
+
+
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + trackFunction + + + + | +|||||||||
+trackFunction(index: number, item: any)
+ |
+ |||||||||
+ + | +|||||||||
+
+
+ Parameters :
+
+
+
+
+ Returns :
+ number
+
+
+
+
+ |
+
+ + + + checkValidity + + + + | +
+checkValidity()
+ |
+
+ Inherited from
+ BaseComponent
+ |
+
+ Defined in
+ BaseComponent:21
+ |
+
+
+
+ Returns :
+ boolean
+
+ |
+
+ + + + displayErrorMessage + + + + | +
+displayErrorMessage()
+ |
+
+ Inherited from
+ BaseComponent
+ |
+
+ Defined in
+ BaseComponent:27
+ |
+
+
+
+ Returns :
+ boolean
+
+ |
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ Inherited from
+ BaseComponent
+ |
+
+ Defined in
+ BaseComponent:17
+ |
+
+
+
+ Returns :
+ void
+
+ |
+
+ + + + allAnswersAreValid + + + | +
+ Default value : false
+ |
+
+ + | +
+ + + + answerList + + + | +
+ Type : []
+
+ |
+
+ Default value : []
+ |
+
+ + | +
+ + + + Public + config + + + | +
+ Type : ConfigService
+
+ |
+
+ + | +
+ + + + currentHeader + + + | +
+ Type : any
+
+ |
+
+ Default value : ""
+ |
+
+ + | +
+ + + + display + + + | +
+ Type : boolean
+
+ |
+
+ + | +
+ + + + Public + config + + + | +
+ Type : ConfigService
+
+ |
+
+ Inherited from
+ BaseComponent
+ |
+
+ Defined in
+ BaseComponent:14
+ |
+
import {AfterViewInit, ChangeDetectorRef, Component, Inject, OnChanges, OnInit} from '@angular/core';
+import {BaseComponent} from '../base-page/base.component';
+import {ConfigService} from '../../services/config.service';
+
+import {DOCUMENT} from '@angular/common';
+
+@Component({
+ selector: 'framadate-answers',
+ templateUrl: './answers.component.html',
+ styleUrls: ['./answers.component.scss']
+})
+export class AnswersComponent extends BaseComponent implements OnInit, AfterViewInit, OnChanges {
+
+ allAnswersAreValid = false;
+
+ answerList = [];
+ currentHeader: any = "";
+ display: boolean;
+
+ constructor(public config: ConfigService,
+ @Inject(DOCUMENT) private document: any,
+ private cd: ChangeDetectorRef) {
+ super(config);
+ this.answerList = this.config.answers;
+ }
+
+ // todo, manage validation of each page in a common way
+ ngOnInit() {
+ }
+
+ ngOnChanges() {
+
+ this.checkValidAnswers();
+ }
+
+ checkValidAnswers() {
+ this.allAnswersAreValid = true;
+ this.config.answers.forEach(answer => {
+ if (!answer.text.length) {
+ this.allAnswersAreValid = false;
+ return;
+ }
+ });
+ }
+
+ ngAfterViewInit() {
+ this.focusOnAnswer(0);
+ this.checkValidAnswers();
+ }
+
+ trackFunction(index: number, item: any): number {
+ return item.id;
+ }
+
+ addAnswer() {
+ this.config.answers.push(
+ {
+ id: this.config.answers.length + 1,
+ text: '',
+ url: '',
+ file: '',
+ literal: '',
+ date_object: null,
+ timeList: []
+ });
+ this.cd.detectChanges(); // to refresh the view before focusing on the new input
+ this.focusOnAnswer(this.config.answers.length - 1)
+ }
+
+ focusOnAnswer(i) {
+ const AnswersDomToFocus = this.document.querySelectorAll('.answers .answer');
+ const dom = AnswersDomToFocus[i];
+ if (dom.focus) {
+ dom.focus();
+ }
+ if (dom.select) {
+ dom.select();
+ }
+ }
+
+ navigateOrDelete(event: KeyboardEvent, i) {
+ if (event.ctrlKey && event.key == "d") {
+ this.config.answers.splice(i, 1)
+ }
+ if (event.key == "ArrowUp" && i > 0) {
+ this.focusOnAnswer(i - 1);
+ }
+ if (event.key == "ArrowDown" && i < this.config.answers.length) {
+ this.focusOnAnswer(i + 1);
+ }
+ }
+
+ showModalForPictureOfAnswer(answer) {
+ // TODO
+ this.currentHeader = answer;
+ this.display = true;
+ // this.config.todo();
+ }
+}
+
+ <div class="answers" >
+ <h1 i18n >
+ Choisir les propositions
+ </h1 >
+
+ <p
+ class="subtitle"
+ i18n >
+ Vous pouvez utiliser la syntaxe markdown, et naviguer entre les inputs avec les flèches du clavier.
+ </p >
+
+ <ol >
+ <li
+ #answers
+ *ngFor="let answer of config.answers; index as i;trackBy trackFunction"
+ class="answer-item" >
+ <button
+ class='btn btn--default'
+ title='ajouter une image'
+ (click)='showModalForPictureOfAnswer(answer)' >
+ <i class='fa fa-image' ></i >
+ </button >
+ <label
+ for='answer_{{answer.id}}_url'
+ (click)='showModalForPictureOfAnswer(answer)' >
+
+ <img
+ class='img-thumbnail'
+ src='{{answer.url}}'
+ alt='image {{answer.url}}' >
+ </label >
+ <p-dialog
+ class='url-dialog'
+ [(visible)]="display"
+ [modal]='true' >
+ <p-header >
+ {{answer.text}}
+ </p-header >
+
+ <form
+ action='#'
+ (submit)='display=false' >
+ <label for='answer_{{answer.id}}_url' >
+ Choisissez une URL pour illustrer le choix de réponse
+ </label >
+ <i class='fa fa-image' ></i >
+ <br >
+ <input
+ class='input is-block'
+ id='answer_{{answer.id}}_url'
+ type='text'
+ autofocus='autofocus'
+ name='answer-url'
+ [(ngModel)]='answer.url' >
+
+ </form >
+
+ </p-dialog >
+ <input
+ type="name"
+ class="answer"
+ id='answer_{{answer.id}}'
+ [(ngModel)]="answer.text"
+ (keyup.enter)="addAnswer()"
+ (keyup)="navigateOrDelete($event,i)"
+ required='required'
+ placeholder="réponse"
+ >
+ <button
+ class="btn btn--alert"
+ (click)="config.answers.splice(i,1)" >X
+ </button >
+ </li >
+ </ol >
+
+ <button
+ class="btn btn--primary btn--outline"
+ (click)="addAnswer()"
+ [ngClass]="{'btn--primary': allAnswersAreValid}"
+ i18n
+ >
+ <i class='fa fa-plus' ></i >
+ Ajouter une proposition
+ </button >
+ <br >
+ <button
+ [routerLink]="'/step/resume'"
+ class="btn btn--full "
+ i18n
+ [ngClass]="{'btn--primary': allAnswersAreValid}"
+ [disabled]='!allAnswersAreValid'
+ >
+ Voyons ce que ça donne
+ </button >
+ <br >
+ <a
+ [routerLink]="'/home'"
+ class="prev"
+ i18n >
+ Retour
+ </a >
+</div >
+
+
+ ./answers.component.scss
+
+ +
+ src/app/app.component.ts
+
selector | +app-root |
+
styleUrls | +./app.component.scss |
+
templateUrl | +./app.component.html |
+
+ Properties+ |
+
+
|
+
+ Methods+ |
+
+
|
+
+constructor(translate: TranslateService, config: ConfigService, document, route: Router)
+ |
+ |||||||||||||||
+ Defined in src/app/app.component.ts:24
+ |
+ |||||||||||||||
+
+ Parameters :
+
+
|
+
+ + + + changeLanguage + + + + | +
+changeLanguage()
+ |
+
+ Defined in src/app/app.component.ts:58
+ |
+
+ set the next lang or loop to the first +this is to manage future languages available +
+ Returns :
+ void
+
+ |
+
+ + + + detectCurrentTabOnRouteChange + + + + | +
+detectCurrentTabOnRouteChange()
+ |
+
+ Defined in src/app/app.component.ts:35
+ |
+
+
+
+ Returns :
+ void
+
+ |
+
+ + + + scrollGoToTop + + + + | +
+scrollGoToTop()
+ |
+
+ Defined in src/app/app.component.ts:75
+ |
+
+
+
+ Returns :
+ void
+
+ |
+
+ + + + switchLanguage + + + + | +||||||
+switchLanguage(language: string)
+ |
+ ||||||
+ Defined in src/app/app.component.ts:49
+ |
+ ||||||
+
+
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + toggleMenu + + + + | +
+toggleMenu()
+ |
+
+ Defined in src/app/app.component.ts:71
+ |
+
+
+
+ Returns :
+ void
+
+ |
+
+ + + + updateCurrentTab + + + + | +||||
+updateCurrentTab(event)
+ |
+ ||||
+ Defined in src/app/app.component.ts:79
+ |
+ ||||
+
+
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + currentLang + + + | +
+ Type : string
+
+ |
+
+ Default value : 'fr'
+ |
+
+ Defined in src/app/app.component.ts:14
+ |
+
+ + + + langsAvailable + + + | +
+ Type : []
+
+ |
+
+ Default value : ['fr', 'en']
+ |
+
+ Defined in src/app/app.component.ts:15
+ |
+
+ + + + menuVisible + + + | +
+ Type : boolean
+
+ |
+
+ Default value : true
+ |
+
+ Defined in src/app/app.component.ts:23
+ |
+
+ + + + minutes + + + | +
+ Type : number
+
+ |
+
+ Default value : 12
+ |
+
+ Defined in src/app/app.component.ts:18
+ |
+
+ + + + step + + + | +
+ Type : string
+
+ |
+
+ Defined in src/app/app.component.ts:24
+ |
+
+ + + + title + + + | +
+ Type : string
+
+ |
+
+ Default value : 'framadate'
+ |
+
+ Defined in src/app/app.component.ts:16
+ |
+
+ + + + user + + + | +
+ Type : object
+
+ |
+
+ Default value : {
+ name: 'Arthur',
+ age: 42
+ }
+ |
+
+ Defined in src/app/app.component.ts:19
+ |
+
import {Component, Inject} from '@angular/core';
+import {TranslateService} from '@ngx-translate/core';
+import {NavigationStart, Router} from '@angular/router';
+import {DOCUMENT} from "@angular/common";
+import {filter} from "rxjs/operators";
+import {ConfigService} from "./services/config.service";
+
+@Component({
+ selector: 'app-root',
+ templateUrl: './app.component.html',
+ styleUrls: ['./app.component.scss']
+})
+export class AppComponent {
+ currentLang = 'fr';
+ langsAvailable = ['fr', 'en'];
+ title = 'framadate';
+ //translation demo:
+ minutes = 12;
+ user = {
+ name: 'Arthur',
+ age: 42
+ };
+ menuVisible: boolean = true;
+ step: string;
+
+ constructor(private translate: TranslateService,
+ private config : ConfigService,
+ @Inject(DOCUMENT) private document,
+ private route: Router) {
+ this.translate.setDefaultLang(this.currentLang);
+ this.detectCurrentTabOnRouteChange();
+
+ }
+
+ detectCurrentTabOnRouteChange() {
+ this.route.events.subscribe((event: any) => {
+
+
+ });
+ this.route.events.pipe(filter(event => event instanceof NavigationStart)).subscribe((event:NavigationStart) => {
+ this.scrollGoToTop();
+ this.updateCurrentTab(event);
+ // only if there is a poll ID
+ this.config.fetchPollFromRoute(event);
+ })
+
+ }
+
+ switchLanguage(language: string) {
+ this.translate.use(language);
+ this.currentLang = language;
+ }
+
+ /**
+ * set the next lang or loop to the first
+ * this is to manage future languages available
+ */
+ changeLanguage() {
+ let langs = this.langsAvailable;
+ let indexofCurrent = langs.indexOf(this.currentLang);
+ if (indexofCurrent > -1) {
+ let newIndex = indexofCurrent + 1;
+ if (newIndex > (langs.length - 1)) {
+ newIndex = 0;
+ }
+ this.currentLang = this.langsAvailable[newIndex];
+ }
+ this.translate.use(this.currentLang);
+ }
+
+ toggleMenu() {
+ this.menuVisible = !this.menuVisible;
+ }
+
+ scrollGoToTop() {
+ this.document.documentElement.scrollTop = 0;
+ }
+
+ updateCurrentTab(event){
+ if (event.url) {
+ const tab = event.url.split('/');
+ if (tab && tab[2]) {
+ this.step = tab[2];
+ } else {
+ this.step = 'home';
+ }
+ }
+ }
+}
+
+ <header style="text-align:center" >
+ <a
+ [routerLink]="'home'"
+ class="home_link"
+ aria-roledescription="home"
+ >
+ <h1 >
+ <span class="logo_first" >Frama</span >
+ <span class="logo_second" >date</span > (démo)
+ </h1 >
+ <div class="legend" >proposé par
+ <span class="legend_first" >Frama</span >
+ <span class="legend_second" >soft</span >
+ </div >
+ </a >
+ <div id="translate_example" >
+ <div class="wrapper" >
+ <img
+ src="assets/img/icone-langue.svg"
+ alt="location icon"
+ (click)="changeLanguage()"
+ class="lang_icon clickable"
+ >
+ <img
+ (click)="toggleMenu()"
+ alt="menu icon"
+ class="menu_icon clickable"
+ src="assets/img/icone-menu.svg"
+ >
+ <select
+ name="language"
+ class="language-selector"
+ (change)="switchLanguage(currentLang)"
+ [(ngModel)]="currentLang"
+ >
+ <option
+ value="d"
+ default
+ class="select_language"
+ >{{"Language" | translate}}</option >
+ <option value="en" >English</option >
+ <option value="fr" >Français</option >
+ </select >
+ <span
+ (click)="toggleMenu()"
+ class="menu_label" >Menu</span >
+ </div >
+
+ </div >
+
+</header >
+
+<main >
+ <router-outlet ></router-outlet >
+</main >
+<framadate-debugger ></framadate-debugger >
+<framadate-navigation
+ *ngIf="menuVisible"
+ [step]="step" ></framadate-navigation >
+<p-toast position="top-right" ></p-toast >
+
+
+
+ ./app.component.scss
+
@charset "UTF-8";
+
+header {
+ h1,
+ a {
+ &::before,
+ &::after {
+ display: none;
+ }
+ }
+}
+
+i {
+ display: block;
+}
+
+.language-selector {
+ width: auto;
+}
+
+ +
+ src/app/pages/base-page/base.component.ts
+
+
+ OnInit
+
selector | +framadate-base-page |
+
styleUrls | +./base.component.scss |
+
templateUrl | +./base.component.html |
+
+ Properties+ |
+
+
|
+
+ Methods+ |
+
+
|
+
+constructor(config: ConfigService)
+ |
+ ||||||
+ Defined in src/app/pages/base-page/base.component.ts:12
+ |
+ ||||||
+
+ Parameters :
+
+
|
+
+ + + + checkValidity + + + + | +
+checkValidity()
+ |
+
+ Defined in src/app/pages/base-page/base.component.ts:21
+ |
+
+
+
+ Returns :
+ boolean
+
+ |
+
+ + + + displayErrorMessage + + + + | +
+displayErrorMessage()
+ |
+
+ Defined in src/app/pages/base-page/base.component.ts:27
+ |
+
+
+
+ Returns :
+ boolean
+
+ |
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ Defined in src/app/pages/base-page/base.component.ts:17
+ |
+
+
+
+ Returns :
+ void
+
+ |
+
+ + + + Public + config + + + | +
+ Type : ConfigService
+
+ |
+
+ Defined in src/app/pages/base-page/base.component.ts:14
+ |
+
import {Component, OnInit} from '@angular/core';
+import {ConfigService} from '../../services/config.service';
+
+@Component({
+ selector: 'framadate-base-page',
+ templateUrl: './base.component.html',
+ styleUrls: ['./base.component.scss']
+})
+/**
+ * base page is aware of the state of the filling
+ */
+export class BaseComponent implements OnInit {
+
+ constructor(public config: ConfigService) {
+ }
+
+ ngOnInit() {
+ }
+
+
+ checkValidity() {
+ // TODO with form controls
+ this.config.todo();
+ return true;
+ }
+
+ displayErrorMessage() {
+ // TODO
+ this.config.todo();
+ return true;
+ }
+}
+
+ <h1>Ce composant est celui de base pour les pages</h1>
+<a [routerLink]="'/step/end'" class="btn btn-block" i18n="@@confirm">
+ {{"config.perfect"|translate}}
+</a>
+
+
+ ./base.component.scss
+
+ +
+ src/app/pages/voting/choices-list/choices-list.component.ts
+
selector | +framadate-choices-list |
+
styleUrls | +./choices-list.component.scss |
+
templateUrl | +./choices-list.component.html |
+
+ Properties+ |
+
+
|
+
+constructor(config: ConfigService)
+ |
+ ||||||
+ + | +||||||
+
+ Parameters :
+
+
|
+
+ + + + Public + config + + + | +
+ Type : ConfigService
+
+ |
+
+ + | +
import {Component} from '@angular/core';
+import {ConfigService} from "../../../services/config.service";
+
+@Component({
+ selector: 'framadate-choices-list',
+ templateUrl: './choices-list.component.html',
+ styleUrls: ['./choices-list.component.scss']
+})
+export class ChoicesListComponent {
+
+ constructor(public config: ConfigService) {
+
+ }
+
+}
+
+
+<div
+ *ngIf='config.currentPoll'
+ class="list-of-choices" >
+ <div *ngFor="let choice of config.currentPoll.choices" >
+
+ <framadate-voting-choice
+ [choice]="choice"
+ [choices_count]="config.currentPoll.choices_count"
+ [pollIsSpecialDate]="config.currentPoll.poll.kind == 'date'"
+ [poll]='config.currentPoll' ></framadate-voting-choice >
+ </div >
+</div >
+<button
+ class='btn btn-block submit-votestack'
+ (click)='config.addVote()'
+ [disabled]='!config.myTempVoteStack'
+ [ngClass]='{"btn--primary" : config.myTempVoteStack } '
+ *ngIf='!config.myVoteStack || !config.myVoteStack.id' >
+ <i class='fa fa-paper-plane' ></i > Envoyer
+
+</button >
+<button
+ class='btn btn--primary btn-block submit-votestack update'
+ (click)='config.updateVote(config.myVoteStack)'
+ *ngIf='config.myVoteStack && config.myVoteStack.id' >
+ <i class='fa fa-pencil' ></i > Mettre à jour
+</button >
+
+
+ ./choices-list.component.scss
+
.submit-votestack {
+ position: fixed;
+ bottom: 5px;
+ right: 5px;
+}
+
+ +
+ src/app/pages/voting/comments-list/comments-list.component.ts
+
selector | +framadate-comments-list |
+
styleUrls | +./comments-list.component.scss |
+
templateUrl | +./comments-list.component.html |
+
+ Properties+ |
+
+
|
+
+constructor(config: ConfigService)
+ |
+ ||||||
+ + | +||||||
+
+ Parameters :
+
+
|
+
+ + + + Public + config + + + | +
+ Type : ConfigService
+
+ |
+
+ + | +
import {Component} from '@angular/core';
+import {ConfigService} from "../../../services/config.service";
+
+@Component({
+ selector: 'framadate-comments-list',
+ templateUrl: './comments-list.component.html',
+ styleUrls: ['./comments-list.component.scss']
+})
+export class CommentsListComponent {
+
+ constructor(public config: ConfigService) {
+
+ }
+
+}
+
+ <section class="name" >
+ <label for="name" >
+ <i class='fa fa-user' ></i >
+ Votre nom :</label >
+ <input
+ type="text"
+ name="name"
+ id="name"
+ [(ngModel)]="config.myName" >
+ <input
+ type="text"
+ name="name"
+ id="email"
+ [(ngModel)]="config.myEmail" >
+ <i class='fa fa-envelope' ></i >
+</section >
+<div
+ class="comments"
+ id='comments' >
+ <h2 class="margin-top-x7" >Laisser un commentaire</h2 >
+ <label for="crname" >Votre nom :</label >
+ <input
+ type="text"
+ class="margin-btm-x3"
+ name="crname"
+ [(ngModel)]='config.myName'
+ id="crname" >
+ <input
+ type="text"
+ name="cremail"
+ id="email_comment"
+ [(ngModel)]="config.myEmail" >
+ <label for='email_comment' >
+ <i class='fa fa-envelope' ></i >
+ </label >
+ <div >
+ <label for="comment" >Votre commentaire :</label >
+ <br >
+ <textarea
+ name="comment"
+ id="comment"
+ [(ngModel)]='config.myComment'
+ >
+ </textarea >
+ </div >
+ <input
+ type="submit"
+ name="add-comment"
+ class="btn btn--primary btn--outline"
+ value="Ajouter mon commentaire"
+ (click)='config.addComment()' >
+ <div
+ class='comments-part'
+ *ngIf='config.currentPoll' >
+
+ <framadate-voting-comment
+ [comment]="c"
+ *ngFor="let c of config.currentPoll.comments " >
+ </framadate-voting-comment >
+ </div >
+</div >
+
+
+ ./comments-list.component.scss
+
+ +
+ src/app/ui/copy-text/copy-text.component.ts
+
+
+ OnInit
+
selector | +framadate-copy-text |
+
styleUrls | +./copy-text.component.scss |
+
templateUrl | +./copy-text.component.html |
+
+ Methods+ |
+
+
|
+
+ Inputs+ |
+
+
|
+
+constructor(messageService: MessageService)
+ |
+ ||||||
+ + | +||||||
+
+ Parameters :
+
+
|
+
+ + textToCopy + | +|
+ Type : any
+
+ |
+ |
+ + | +
+ + + + handleClick + + + + | +
+handleClick()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
import {Component, Input, OnInit} from '@angular/core';
+import {MessageService} from "primeng/api";
+
+@Component({
+ selector: 'framadate-copy-text',
+ templateUrl: './copy-text.component.html',
+ styleUrls: ['./copy-text.component.scss']
+})
+export class CopyTextComponent implements OnInit {
+ @Input() public textToCopy: any;
+
+ constructor(private messageService: MessageService,) {
+ }
+
+ ngOnInit() {
+ }
+
+ handleClick() {
+ this.messageService.add({
+ severity: 'success',
+ summary: 'Texte copié',
+ detail: this.textToCopy
+ })
+ }
+
+}
+
+ <button
+ (click)='handleClick()'
+ [cbContent]="textToCopy"
+ [ngxClipboard]
+ class=" btn btn--primary btn--outline"
+ id="copyLink" >
+ <i class='fa fa-copy' ></i >
+ {{"admin.copy_link" |translate}} " {{ textToCopy}}"
+</button >
+
+
+ ./copy-text.component.scss
+
+ +
+ src/app/pages/create-or-retrieve/create-or-retrieve.component.ts
+
+
+ BaseComponent
+
+
+ OnInit
+
selector | +framadate-create-or-retrieve |
+
styleUrls | +./create-or-retrieve.component.scss |
+
templateUrl | +./create-or-retrieve.component.html |
+
+ Properties+ |
+
+
|
+
+ Methods+ |
+
+
|
+
+constructor(config: ConfigService)
+ |
+ ||||||
+ + | +||||||
+
+ Parameters :
+
+
|
+
+ + + + findMyPollsByEmail + + + + | +||||||
+findMyPollsByEmail(email: string)
+ |
+ ||||||
+ + | +||||||
+
+
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
+ + + + trackFunction + + + + | +|||||||||
+trackFunction(index: number, item: any)
+ |
+ |||||||||
+ + | +|||||||||
+
+
+ Parameters :
+
+
+
+
+ Returns :
+ number
+
+
+
+
+ |
+
+ + + + checkValidity + + + + | +
+checkValidity()
+ |
+
+ Inherited from
+ BaseComponent
+ |
+
+ Defined in
+ BaseComponent:21
+ |
+
+
+
+ Returns :
+ boolean
+
+ |
+
+ + + + displayErrorMessage + + + + | +
+displayErrorMessage()
+ |
+
+ Inherited from
+ BaseComponent
+ |
+
+ Defined in
+ BaseComponent:27
+ |
+
+
+
+ Returns :
+ boolean
+
+ |
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ Inherited from
+ BaseComponent
+ |
+
+ Defined in
+ BaseComponent:17
+ |
+
+
+
+ Returns :
+ void
+
+ |
+
+ + + + Public + config + + + | +
+ Type : ConfigService
+
+ |
+
+ + | +
+ + + + loadedMyPolls + + + | +
+ Type : boolean
+
+ |
+
+ Default value : false
+ |
+
+ + | +
+ + + + Public + config + + + | +
+ Type : ConfigService
+
+ |
+
+ Inherited from
+ BaseComponent
+ |
+
+ Defined in
+ BaseComponent:14
+ |
+
import {Component, OnInit} from '@angular/core';
+import {BaseComponent} from "../base-page/base.component";
+import {ConfigService} from "../../services/config.service";
+
+@Component({
+ selector: 'framadate-create-or-retrieve',
+ templateUrl: './create-or-retrieve.component.html',
+ styleUrls: ['./create-or-retrieve.component.scss']
+})
+export class CreateOrRetrieveComponent extends BaseComponent implements OnInit {
+
+ loadedMyPolls: boolean = false;
+
+
+ constructor(public config: ConfigService) {
+ super(config);
+ }
+
+ ngOnInit() {
+ // if (!environment.production) {
+ // this.findMyPollsByEmail('tktest@tktest.com')
+ // }
+
+ }
+
+ findMyPollsByEmail(email: string) {
+ if (!email) {
+ return
+ }
+ this.config.findPollsByEmail(email);
+ this.loadedMyPolls = true;
+ }
+
+ trackFunction(index: number, item: any): number {
+ return item.id;
+ };
+}
+
+ <div class="container" >
+ <section class="creation" >
+ <h1 >
+ {{"creation.title"|translate}}
+ </h1 >
+ <p
+ class="description margin-btm-x5"
+ i18n >
+ {{"config.description"|translate}}
+ </p >
+ <div class="btn-next" >
+
+ <button
+ [routerLink]="'step/date'"
+ class="btn btn--full btn--primary" >
+ {{"config.letsgo"|translate}}
+ </button >
+
+ </div >
+ </section >
+
+ <section class="recuperation" >
+ <h1 class="margin-top-x8" >
+ {{"config.find_my_polls"|translate}}
+ </h1 >
+ <form
+ (ngSubmit)="findMyPollsByEmail(config.myEmail)"
+ >
+ <label
+ class="description"
+ for="sendemail"
+ i18n
+ >
+ <i class='fa fa-envelope' ></i >
+ {{"config.find_helper"|translate}} :
+ </label >
+ <input
+ [(ngModel)]="config.myEmail"
+ class='input'
+ autofocus="autofocus"
+ id="sendemail"
+ name="mail"
+ required="required"
+ type="email"
+ />
+ <input
+ [disabled]="!config.myEmail || !config.myEmail.length"
+ [ngClass]="{'btn--primary': config.myEmail}"
+ class="btn btn--full"
+ id='sendemailbutton'
+ i18n-value="'config.find_button'|translate"
+ type="submit"
+ />
+ </form >
+ </section >
+ <section
+ class="list-my-polls"
+ *ngIf="!config.loading" >
+ <h2 >
+ Mes Sondages trouvés:
+ </h2 >
+ <ul
+ class="poll-list"
+ *ngFor="let poll of config.myPolls ; index as i;trackBy trackFunction" >
+ <li >
+ <a href='{{poll.url}}' >
+ {{poll.title}}
+ <sub >
+ {{poll.description}}
+ </sub >
+ </a >
+ </li >
+ </ul >
+
+ <div
+ class="no-polls"
+ *ngIf="! config.myPolls || !config.myPolls.length" >
+ Aucun sondage.
+ </div >
+ </section >
+ <div
+ class="loading"
+ *ngIf="config.loading" >
+ <i class="fa fa-refresh fa-spin fa-3x fa-fw" ></i >
+ </div >
+
+</div >
+
+
+ ./create-or-retrieve.component.scss
+
.container{
+ display:flex;
+ flex-direction: column;
+ margin:auto;
+}
+h1 {
+ display: inline-block;
+ margin-bottom: 3rem;
+ font-size: 2.6rem;
+
+ &::after {
+ content: "";
+ display: block;
+ width: 27.4%;
+ height: 2px;
+ margin-top: 5px;
+ margin-right: auto;
+ margin-left: auto;
+ // background-color: $primary_color;
+ }
+ }
+label{
+ float:left;
+}
+input[type=email]{
+ display:block;
+}
+
+ +
+ src/app/pages/dates/dates.component.ts
+
+
+ BaseComponent
+
+
+ OnInit
+
selector | +framadate-dates |
+
styleUrls | +./dates.component.scss |
+
templateUrl | +./dates.component.html |
+
+ Properties+ |
+
+
|
+
+ Methods+ |
+
+
|
+
+constructor(config: ConfigService, cd: ChangeDetectorRef, messageService: MessageService, dateUtilities: DateUtilities, document: any)
+ |
+ ||||||||||||||||||
+ Defined in src/app/pages/dates/dates.component.ts:19
+ |
+ ||||||||||||||||||
+
+ Parameters :
+
+
|
+
+ + + + addDate + + + + | +
+addDate()
+ |
+
+ Defined in src/app/pages/dates/dates.component.ts:47
+ |
+
+
+
+ Returns :
+ void
+
+ |
+
+ + + + addIntervalOfDates + + + + | +
+addIntervalOfDates()
+ |
+
+ Defined in src/app/pages/dates/dates.component.ts:117
+ |
+
+ add all the dates between the start and end dates in the interval section +
+ Returns :
+ void
+
+ |
+
+ + + + addTime + + + + | +
+addTime()
+ |
+
+ Defined in src/app/pages/dates/dates.component.ts:64
+ |
+
+ change time spans +
+ Returns :
+ void
+
+ |
+
+ + + + addTimeToDate + + + + | +|||||||||
+addTimeToDate(config: any, id: number)
+ |
+ |||||||||
+ Defined in src/app/pages/dates/dates.component.ts:89
+ |
+ |||||||||
+ add a time period to a specific date choice, +focus on the new input +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + countDays + + + + | +
+countDays()
+ |
+
+ Defined in src/app/pages/dates/dates.component.ts:30
+ |
+
+
+
+ Returns :
+ void
+
+ |
+
+ + + + emptyAll + + + + | +
+emptyAll()
+ |
+
+ Defined in src/app/pages/dates/dates.component.ts:102
+ |
+
+ remove all input contents, does not reset to default +
+ Returns :
+ void
+
+ |
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ Defined in src/app/pages/dates/dates.component.ts:40
+ |
+
+ set the interval options +
+ Returns :
+ void
+
+ |
+
+ + + + removeAllTimes + + + + | +
+removeAllTimes()
+ |
+
+ Defined in src/app/pages/dates/dates.component.ts:74
+ |
+
+
+
+ Returns :
+ void
+
+ |
+
+ + + + resetTimes + + + + | +
+resetTimes()
+ |
+
+ Defined in src/app/pages/dates/dates.component.ts:78
+ |
+
+
+
+ Returns :
+ void
+
+ |
+
+ + + + checkValidity + + + + | +
+checkValidity()
+ |
+
+ Inherited from
+ BaseComponent
+ |
+
+ Defined in
+ BaseComponent:21
+ |
+
+
+
+ Returns :
+ boolean
+
+ |
+
+ + + + displayErrorMessage + + + + | +
+displayErrorMessage()
+ |
+
+ Inherited from
+ BaseComponent
+ |
+
+ Defined in
+ BaseComponent:27
+ |
+
+
+
+ Returns :
+ boolean
+
+ |
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ Inherited from
+ BaseComponent
+ |
+
+ Defined in
+ BaseComponent:17
+ |
+
+
+
+ Returns :
+ void
+
+ |
+
+ + + + Public + config + + + | +
+ Type : ConfigService
+
+ |
+
+ Defined in src/app/pages/dates/dates.component.ts:21
+ |
+
+ + + + endDateInterval + + + | +
+ Type : any
+
+ |
+
+ Defined in src/app/pages/dates/dates.component.ts:19
+ |
+
+ + + + intervalDays + + + | +
+ Type : any
+
+ |
+
+ Defined in src/app/pages/dates/dates.component.ts:17
+ |
+
+ + + + intervalDaysDefault + + + | +
+ Type : number
+
+ |
+
+ Default value : 7
+ |
+
+ Defined in src/app/pages/dates/dates.component.ts:18
+ |
+
+ + + + showDateInterval + + + | +
+ Type : boolean
+
+ |
+
+ Default value : true
+ |
+
+ Defined in src/app/pages/dates/dates.component.ts:15
+ |
+
+ + + + startDateInterval + + + | +
+ Type : any
+
+ |
+
+ Defined in src/app/pages/dates/dates.component.ts:16
+ |
+
+ + + + Public + config + + + | +
+ Type : ConfigService
+
+ |
+
+ Inherited from
+ BaseComponent
+ |
+
+ Defined in
+ BaseComponent:14
+ |
+
import {ChangeDetectorRef, Component, Inject, OnInit} from '@angular/core';
+import {ConfigService} from '../../services/config.service';
+import {BaseComponent} from '../base-page/base.component';
+import {DOCUMENT} from '@angular/common';
+import {MessageService} from "primeng/api";
+import {otherDefaultDates} from "../../config/defaultConfigs";
+import {DateUtilities} from "../../config/DateUtilities";
+
+@Component({
+ selector: 'framadate-dates',
+ templateUrl: './dates.component.html',
+ styleUrls: ['./dates.component.scss']
+})
+export class DatesComponent extends BaseComponent implements OnInit {
+ showDateInterval: boolean = true;
+ startDateInterval: any;
+ intervalDays: any;
+ intervalDaysDefault: number = 7;
+ endDateInterval: any;
+
+ constructor(public config: ConfigService,
+ private cd: ChangeDetectorRef,
+ private messageService: MessageService,
+ private dateUtilities: DateUtilities,
+ @Inject(DOCUMENT) private document: any
+ ) {
+ super(config);
+ }
+
+ countDays() {
+ // compute the number of days in the date interval
+ if (this.endDateInterval && this.startDateInterval) {
+ this.intervalDays = (this.dateUtilities.dayDiff(this.endDateInterval, this.startDateInterval)).toFixed(0)
+ }
+ }
+
+ /**
+ * set the interval options
+ */
+ ngOnInit() {
+ let dateCurrent = new Date();
+ const dateJson = dateCurrent.toISOString();
+ this.startDateInterval = dateJson.substring(0, 10);
+ this.endDateInterval = this.dateUtilities.addDaysToDate(this.intervalDaysDefault, dateCurrent).toISOString().substring(0, 10);
+ }
+
+ addDate() {
+ this.config.dateList.push({
+ literal: '',
+ date_object: new Date(),
+ timeList: []
+ });
+ let selector = '[ng-reflect-name="dateChoices_' + (this.config.dateList.length - 1) + '"]';
+ this.cd.detectChanges();
+ const elem = this.document.querySelector(selector);
+ if (elem) {
+ elem.focus();
+ }
+ }
+
+ /**
+ * change time spans
+ */
+ addTime() {
+ this.config.timeList.push(
+ {
+ literal: '',
+ timeList: [],
+ date_object: new Date()
+ }
+ );
+ }
+
+ removeAllTimes() {
+ this.config.timeList = [];
+ }
+
+ resetTimes() {
+ this.config.timeList = otherDefaultDates;
+ }
+
+
+ /**
+ * add a time period to a specific date choice,
+ * focus on the new input
+ * @param config
+ * @param id
+ */
+ addTimeToDate(config: any, id: number) {
+ config.timeList.push({literal: ''});
+ let selector = '[ng-reflect-name="dateTime_' + id + '_Choices_' + (config.timeList.length - 1) + '"]';
+ this.cd.detectChanges();
+ const elem = this.document.querySelector(selector);
+ if (elem) {
+ elem.focus();
+ }
+ }
+
+ /**
+ * remove all input contents, does not reset to default
+ */
+ emptyAll() {
+ this.config.dateList.forEach(element => {
+ element.literal = '';
+ element.date_object = new Date();
+ element.timeList = ['', '', ''];
+ });
+ this.config.timeList.forEach(element => {
+ element.literal = '';
+ });
+ }
+
+
+ /**
+ * add all the dates between the start and end dates in the interval section
+ */
+ addIntervalOfDates() {
+ let newIntervalArray = this.dateUtilities.getDatesInRange(this.startDateInterval, this.endDateInterval, 1);
+
+ const converted = [];
+ newIntervalArray.forEach(element => {
+ converted.push({
+ literal: element.literal,
+ date_object: element.date_object,
+ timeList: []
+ });
+ });
+ this.config.dateList = [...new Set(converted)]; // add only dates that are not already present with a Set of unique items
+ this.showDateInterval = false;
+
+ this.messageService.add({
+ severity: 'success',
+ summary: 'Dates ajoutées',
+ detail: `les dates ont été ajoutées aux réponses possibles`
+ });
+
+ }
+
+
+}
+
+ <div i18n >
+ {{"dates.title"|translate}}
+</div >
+
+<div >
+ <label for="multi_hours" >
+ <span >
+ {{"dates.hours_different"|translate}}
+ </span >
+ <select
+ [(ngModel)]="config.allowSeveralHours"
+ id="multi_hours"
+ name="multi_hours"
+ >
+ <option value=true >{{"dates.multiple.different"|translate}}</option >
+ <option value=false >{{"dates.multiple.identical"|translate}}</option >
+ </select >
+ <span i18n >
+ {{"dates.hours_each_day"|translate}}
+ </span >
+ </label >
+</div >
+
+<button
+ (click)="addDate()"
+ class="btn btn--primary"
+ id="add_date_button"
+>
+ <i class='fa fa-plus' ></i >
+ {{"dates.add"|translate}}
+</button >
+<button
+ (click)="showDateInterval = !showDateInterval "
+ [ngClass]="{active: showDateInterval}"
+ class="btn btn--primary"
+ id="toggle_interval_button"
+>
+ <i class='fa fa-clock-o' ></i >
+ {{"dates.add_interval"|translate}}
+</button >
+
+<button
+ (click)="emptyAll()"
+ class="btn btn--warning"
+ id="empty_button"
+>
+ <i class='fa fa-trash' ></i >
+ {{"dates.empty"|translate}}
+</button >
+<section
+ *ngIf="showDateInterval"
+ class="date-interval " >
+ <!-- TODO à mettre en popup-->
+ <hr >
+ <h2 > {{"dates.add_interval"|translate}}</h2 >
+ <p >
+ {{"dates.interval_propose"|translate}}
+ <input
+ (change)="countDays()"
+ [(ngModel)]="startDateInterval"
+ type="date" >
+ {{"dates.interval_span"|translate}}
+ <input
+ (change)="countDays()"
+ [(ngModel)]="endDateInterval"
+ type="date" >
+ <br >
+
+ </p >
+ <button
+ (click)="addIntervalOfDates()"
+ class="btn btn-block btn--primary" >
+ <i class='fa fa-plus' ></i >
+ {{"dates.interval_button"|translate}}
+ {{intervalDays}}
+ {{"dates.interval_button_dates"|translate}}
+ </button >
+ <hr >
+</section >
+<div class='columns' >
+ <div class='column' >
+ <div class="dates-list " >
+ <div class='title' >
+ <span class="count-dates" >
+ {{config.timeList.length}}
+ </span >
+ <span class="count-dates-txt " >
+ {{"dates.count_time"|translate}}
+ (pour chaque jour)
+ </span >
+
+ </div >
+ <div class='actions' >
+ <button
+ (click)="addTime()"
+ *ngIf=" 'false' === config.allowSeveralHours "
+ class="btn btn--primary"
+ id="add_time_button"
+ >
+ <i class='fa fa-plus' ></i >
+ {{"dates.add_time"|translate}}
+ </button >
+ <button
+ (click)="removeAllTimes()"
+ *ngIf=" 'false' === config.allowSeveralHours "
+ class="btn btn--warning"
+ id="remove_time_button"
+ >
+ <i class='fa fa-trash' ></i >
+ Aucune plage horaire
+ </button >
+ <button
+ (click)="resetTimes()"
+ *ngIf=" 'false' === config.allowSeveralHours"
+ class="btn btn--warning"
+ id="reset_time_button"
+ >
+ <i class='fa fa-refresh' ></i >
+ réinitialiser
+ </button >
+ </div >
+
+ <div
+ *ngIf=" 'false' === config.allowSeveralHours"
+ class="identical-dates"
+ >
+ <div
+ *ngFor="let time of config.timeList; index as id"
+ class="time-choice"
+ >
+ <label for='timeChoices_{{id}}' >
+ <i class='fa fa-clock-o' ></i >
+ </label >
+ <input
+ [(ngModel)]="time.literal"
+ name="timeChoices_{{id}}"
+ type="text"
+ id='timeChoices_{{id}}'
+ >
+ <button
+ (click)="time.timeList.splice(id, 1)"
+ class="btn btn-warning" ><i class="fa fa-times" ></i >
+ </button >
+ </div >
+ </div >
+ <hr >
+ <span class="count-dates title" >
+ {{config.dateList.length}}
+ </span >
+ <span >
+ {{"dates.count_dates"|translate}}
+ </span >
+ <button
+ class='btn btn--primary'
+ (click)='addDate()' >
+ {{"dates.add"|translate}}
+ </button >
+ <div
+ *ngFor="let choice of config.dateList; index as id"
+ class="date-choice"
+ >
+ {{id}})
+ <input
+ [(ngModel)]="choice.date_object"
+ name="dateChoices_{{id}}"
+ id="dateChoices_{{id}}"
+ useValueAsDate
+ type="date"
+ >
+ <button
+ (click)="config.dateList.splice(id, 1)"
+ class="btn btn-warning"
+ ><i class="fa fa-times" ></i >
+ </button >
+ <button
+ (click)="addTimeToDate(choice, id)"
+ *ngIf=" 'true' === config.allowSeveralHours"
+ class="btn btn--primary"
+ >
+ {{"dates.add_time"|translate}}
+ </button >
+ <div
+ *ngIf=" 'true' === config.allowSeveralHours"
+ class="several-times"
+ >
+ <pre class='debug padded warning' >
+ choice.timeList :
+ {{choice.timeList|json}}
+ </pre >
+ <h2 >Several hours</h2 >
+ <div
+ *ngFor="let timeItem of choice.timeList; index as idTime"
+ class="time-choice"
+ >
+ <input
+ [(ngModel)]="timeItem.literal"
+ name="dateTime_{{id}}_Choices_{{idTime}}"
+ id="dateTime_{{id}}_Choices_{{idTime}}"
+
+ type="text"
+ >
+ <button
+ (click)="choice.timeList.splice(idTime, 1)"
+ class="btn btn-warning"
+ ><i class="fa fa-times" ></i >
+ </button >
+ </div >
+ </div >
+ </div >
+ </div >
+ </div >
+ <div class='column' >
+ <framadate-resume ></framadate-resume >
+ </div >
+</div >
+
+<a
+ [routerLink]="'/step/resume'"
+ class="btn btn--full btn--primary"
+>
+ C'est parfait!
+</a >
+<a
+ [routerLink]="'/step/home'"
+ class="prev"
+>
+ Retour
+</a >
+
+
+ ./dates.component.scss
+
.several-times {
+ padding-left: 1em;
+}
+
+.date-interval {
+ padding: 1em;
+ margin-bottom: 1em;
+}
+
+.title {
+ font-size: 1.5rem;
+}
+
+:host {
+ input, button {
+ + button {
+ margin-left: 1em;
+ }
+ }
+
+}
+
+ +
+ src/app/debugger/debugger.component.ts
+
+
+ OnInit
+
selector | +framadate-debugger |
+
styleUrls | +./debugger.component.scss |
+
templateUrl | +./debugger.component.html |
+
+ Properties+ |
+
+
|
+
+ Methods+ |
+
+
|
+
+constructor(config: ConfigService)
+ |
+ ||||||
+ Defined in src/app/debugger/debugger.component.ts:10
+ |
+ ||||||
+
+ Parameters :
+
+
|
+
+ + + + launchToast + + + + | +
+launchToast()
+ |
+
+ Defined in src/app/debugger/debugger.component.ts:27
+ |
+
+
+
+ Returns :
+ void
+
+ |
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ Defined in src/app/debugger/debugger.component.ts:15
+ |
+
+
+
+ Returns :
+ void
+
+ |
+
+ + + + selectOption + + + + | +|||||||||
+selectOption(key: string, val: any)
+ |
+ |||||||||
+ Defined in src/app/debugger/debugger.component.ts:18
+ |
+ |||||||||
+
+
+ Parameters :
+
+
+
+
+ Returns :
+ boolean
+
+
+
+
+ |
+
+ + + + Public + config + + + | +
+ Type : ConfigService
+
+ |
+
+ Defined in src/app/debugger/debugger.component.ts:12
+ |
+
+ + + + formIsValid + + + | +
+ Default value : true
+ |
+
+ Defined in src/app/debugger/debugger.component.ts:10
+ |
+
import {Component, OnInit} from '@angular/core';
+import {ConfigService} from '../services/config.service';
+
+@Component({
+ selector: 'framadate-debugger',
+ templateUrl: './debugger.component.html',
+ styleUrls: ['./debugger.component.scss']
+})
+export class DebuggerComponent implements OnInit {
+ formIsValid = true;
+
+ constructor(public config: ConfigService) {
+ }
+
+ ngOnInit() {
+ }
+
+ selectOption(key: string, val: any) {
+ if (!this.config[key]) {
+ return false;
+ }
+ this.config[key] = val;
+
+ return true;
+ }
+
+ launchToast() {
+ this.config.handleError({message: "hop"})
+ }
+}
+
+ <div class="well debug" >
+ <strong >
+ <h2 i18n >
+ infos de debug
+ </h2 >
+ <span class="demo" >
+ {{"config.demo"|translate}}
+ </span >
+ </strong >
+ <ul >
+ <li >
+ étape actuelle {{config.step}} / {{config.stepMax}}
+ </li >
+ <li >
+ formulaire valide : {{formIsValid}}
+ </li >
+ <li >
+ type de formulaire: {{config.pollType}}
+ </li >
+ </ul >
+ <button
+ class="btn btn--primary"
+ i18n
+ (click)="config.createPoll()"
+ >
+ Envoyer le formulaire
+ </button >
+ <button
+ class="btn btn--primary"
+ i18n
+ (click)="config.getPollById( '1', 'example password')"
+ >
+ get poll 1
+ </button >
+ <button
+ class="btn btn--primary"
+ i18n
+ (click)="config.getMyPolls( 'tktest@tktest.com')"
+ >
+ get my polls
+ </button >
+ <button
+ class="btn btn--success"
+ (click)="launchToast()" >
+ launch success toast
+ </button >
+ <a
+ [routerLink]="'/vote/poll/id/3'"
+ class="btn btn--success" >
+ See example of vote page
+ </a >
+</div >
+
+
+ ./debugger.component.scss
+
+ +
+ src/app/pages/end-confirmation/end-confirmation.component.ts
+
+
+ BaseComponent
+
+
+ OnInit
+
selector | +framadate-end-confirmation |
+
styleUrls | +./end-confirmation.component.scss |
+
templateUrl | +./end-confirmation.component.html |
+
+ Properties+ |
+
+
|
+
+ Methods+ |
+
+
|
+
+constructor(config: ConfigService, http: HttpClient, messageService: MessageService)
+ |
+ ||||||||||||
+ + | +||||||||||||
+
+ Parameters :
+
+
|
+
+ + + + copyLink + + + + | +||||||
+copyLink(str: any)
+ |
+ ||||||
+ + | +||||||
+
+
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
+ + + + sendToEmail + + + + | +
+sendToEmail()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
+ + + + checkValidity + + + + | +
+checkValidity()
+ |
+
+ Inherited from
+ BaseComponent
+ |
+
+ Defined in
+ BaseComponent:21
+ |
+
+
+
+ Returns :
+ boolean
+
+ |
+
+ + + + displayErrorMessage + + + + | +
+displayErrorMessage()
+ |
+
+ Inherited from
+ BaseComponent
+ |
+
+ Defined in
+ BaseComponent:27
+ |
+
+
+
+ Returns :
+ boolean
+
+ |
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ Inherited from
+ BaseComponent
+ |
+
+ Defined in
+ BaseComponent:17
+ |
+
+
+
+ Returns :
+ void
+
+ |
+
+ + + + Public + config + + + | +
+ Type : ConfigService
+
+ |
+
+ + | +
+ + + + Public + http + + + | +
+ Type : HttpClient
+
+ |
+
+ + | +
+ + + + mailToRecieve + + + | +
+ Type : string
+
+ |
+
+ Default value : ''
+ |
+
+ + | +
+ + + + Public + config + + + | +
+ Type : ConfigService
+
+ |
+
+ Inherited from
+ BaseComponent
+ |
+
+ Defined in
+ BaseComponent:14
+ |
+
import {Component, OnInit} from '@angular/core';
+import {BaseComponent} from '../base-page/base.component';
+import {HttpClient} from '@angular/common/http';
+import {ConfigService} from "../../services/config.service";
+import {MessageService} from 'primeng/api';
+
+@Component({
+ selector: 'framadate-end-confirmation',
+ templateUrl: './end-confirmation.component.html',
+ styleUrls: ['./end-confirmation.component.scss']
+})
+export class EndConfirmationComponent extends BaseComponent implements OnInit {
+ mailToRecieve = '';
+
+ constructor(public config: ConfigService,
+ public http: HttpClient,
+ private messageService: MessageService
+ ) {
+ super(config);
+ this.mailToRecieve = this.config.myEmail;
+ }
+
+ ngOnInit() {
+ }
+
+ copyLink(str: any) {
+ this.messageService.add({
+ severity: 'success',
+ summary: 'Lien copié',
+ detail: str
+ });
+ }
+
+ sendToEmail() {
+
+ }
+}
+
+ <h1 i18n >
+ {{"resume.title"|translate}}
+</h1 >
+<section class="admin" >
+ <h2 i18n >{{"resume.admins"|translate}}</h2 >
+ <p >
+
+ Votre sondage «
+ <span class="poll-title" >
+ {{config.title}}
+ </span >
+ » a bien été créé !
+ </p >
+ <p >
+ Voici les liens d’accès au sondage, conservez-les soigneusement !
+ (Si vous les perdez vous pourrez toujours les recevoir par email)
+ </p >
+
+ <p >
+ Pour accéder au sondage et à tous ses paramètres :
+ <a href="{{config.urlAdmin}}" >{{config.urlAdmin}}
+ </a >
+ </p >
+ <framadate-copy-text [textToCopy]='config.urlAdmin' ></framadate-copy-text >
+ <a href="{{config.urlAdmin}}" >
+ Voir le sondage coté administrateur·ice
+ </a >
+ <p class="note" >
+ Note : Le sondage sera supprimé {{config.deletionDateAfterLastModification}} jours après la date de sa dernière
+ modification.
+ </p >
+
+</section >
+<section class="public" >
+
+ <h2 i18n >{{"resume.users"|translate}}</h2 >
+ <p >
+ Pour accéder au sondage :
+ <a href="{{config.urlPublic}}" >{{config.urlPublic}}
+ </a >
+
+ </p >
+ <framadate-copy-text [textToCopy]='config.urlPublic' ></framadate-copy-text >
+ <a href="{{config.urlPublic}}" >
+ Voir le sondage
+ </a >
+</section >
+<section class="mail" >
+
+ <h2 i18n >{{"resume.links_mail"|translate}}</h2 >
+ <p >
+ Pour être sur de retrouver ces liens, nous pouvons vous les envoyer sur votre mail
+
+ mail :
+ <input
+ type="email"
+ [(ngModel)]="mailToRecieve"
+ paceholder="email"
+ >
+
+ </p >
+
+ <button
+ class="btn btn--primary"
+ (click)="sendToEmail()"
+ ><i class="fa fa-paper-plane" ></i >
+ Envoyer les liens du sondage
+ </button >
+ <a href="{{config.urlPublic}}" >
+ Voir le sondage
+ </a >
+
+</section >
+
+
+
+ ./end-confirmation.component.scss
+
:host {
+ button {
+ margin-right: 1em;
+ }
+}
+
+ +
+ src/app/ui/erasable-input/erasable-input.component.ts
+
+
+ OnInit
+
selector | +framadate-erasable-input |
+
styleUrls | +./erasable-input.component.scss |
+
templateUrl | +./erasable-input.component.html |
+
+ Methods+ |
+
+
|
+
+ Inputs+ |
+
+
|
+
+ Outputs+ |
+
+
|
+
+constructor()
+ |
+
+ + | +
+ + inputModel + | +|
+ + | +
+ + inputModelChange + | +|
+ Type : EventEmitter
+
+ |
+ |
+ + | +
+ + + + eraseInput + + + + | +
+eraseInput()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
+
+@Component({
+ selector: 'framadate-erasable-input',
+ templateUrl: './erasable-input.component.html',
+ styleUrls: ['./erasable-input.component.scss']
+})
+export class ErasableInputComponent implements OnInit {
+ @Output() inputModelChange = new EventEmitter();
+
+ @Input() inputModel;
+
+ constructor() {
+ }
+
+ ngOnInit() {
+ }
+
+ eraseInput() {
+ //
+ this.inputModelChange.emit('');
+ // TODO focus on other element
+ }
+
+}
+
+ <button
+ class="erase btn btn--warning"
+ *ngIf='inputModel.length'
+ (click)="eraseInput()" >
+ <i class="fa fa-times" ></i >
+</button >
+
+
+ ./erasable-input.component.scss
+
+ +
+ src/app/header/header.component.ts
+
+
+ OnInit
+
selector | +framadate-header |
+
styleUrls | +./header.component.scss |
+
templateUrl | +./header.component.html |
+
+ Methods+ |
+
+
|
+
+constructor()
+ |
+
+ Defined in src/app/header/header.component.ts:8
+ |
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ Defined in src/app/header/header.component.ts:12
+ |
+
+
+
+ Returns :
+ void
+
+ |
+
import { Component, OnInit } from '@angular/core';
+
+@Component({
+ selector: 'framadate-header',
+ templateUrl: './header.component.html',
+ styleUrls: ['./header.component.scss']
+})
+export class HeaderComponent implements OnInit {
+
+ constructor() { }
+
+ ngOnInit() {
+ }
+
+}
+
+ <p>(progression)</p>
+<!--TODO-->
+
+
+ ./header.component.scss
+
+ +
+ src/app/pages/home/home.component.ts
+
+
+ BaseComponent
+
+
+ OnInit
+
selector | +framadate-home |
+
styleUrls | +./home.component.scss |
+
templateUrl | +./home.component.html |
+
+ Properties+ |
+
+ + | +
+ Methods+ |
+
+
|
+
+constructor(config: ConfigService)
+ |
+ ||||||
+ Defined in src/app/pages/home/home.component.ts:12
+ |
+ ||||||
+
+ Parameters :
+
+
|
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ Defined in src/app/pages/home/home.component.ts:17
+ |
+
+
+
+ Returns :
+ void
+
+ |
+
+ + + + checkValidity + + + + | +
+checkValidity()
+ |
+
+ Inherited from
+ BaseComponent
+ |
+
+ Defined in
+ BaseComponent:21
+ |
+
+
+
+ Returns :
+ boolean
+
+ |
+
+ + + + displayErrorMessage + + + + | +
+displayErrorMessage()
+ |
+
+ Inherited from
+ BaseComponent
+ |
+
+ Defined in
+ BaseComponent:27
+ |
+
+
+
+ Returns :
+ boolean
+
+ |
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ Inherited from
+ BaseComponent
+ |
+
+ Defined in
+ BaseComponent:17
+ |
+
+
+
+ Returns :
+ void
+
+ |
+
+ + + + Public + config + + + | +
+ Type : ConfigService
+
+ |
+
+ Defined in src/app/pages/home/home.component.ts:13
+ |
+
+ + + + nextStep + + + | +
+ Type : string
+
+ |
+
+ Default value : '/step/answers'
+ |
+
+ Defined in src/app/pages/home/home.component.ts:12
+ |
+
+ + + + Public + config + + + | +
+ Type : ConfigService
+
+ |
+
+ Inherited from
+ BaseComponent
+ |
+
+ Defined in
+ BaseComponent:14
+ |
+
import {Component, OnInit} from '@angular/core';
+import {ConfigService} from "../../services/config.service";
+import {BaseComponent} from "../base-page/base.component";
+
+@Component({
+ selector: 'framadate-home',
+ templateUrl: './home.component.html',
+ styleUrls: ['./home.component.scss']
+})
+export class HomeComponent extends BaseComponent implements OnInit {
+
+ nextStep = '/step/answers';
+ constructor(public config: ConfigService) {
+ super(config);
+ }
+
+ ngOnInit() {
+ }
+
+}
+
+ <div class="description" >
+ <router-outlet ></router-outlet >
+ <h1 i18n >
+ {{"creation.title"|translate}}
+ </h1 >
+ <span
+ class="pre-selector"
+ i18n
+ >
+ {{"creation.want"|translate}}
+ </span >
+ <button
+ (click)='config.resetConfig()'
+ class='btn btn--warning' >
+ Reset all
+ </button >
+ <select
+ [(ngModel)]="config.pollType"
+ autofocus="autofocus"
+ id="selector"
+ name='polltype'
+ >
+ <option
+ value="dates"
+ name='polltype_date' >
+ {{"creation.kind.date"|translate}}
+ </option >
+ <option
+ value="classic"
+ name='polltype_classic' >
+ {{"creation.kind.classic"|translate}}
+ </option >
+ </select >
+
+ <span class="post-selector" >
+ </span >
+
+ <div >
+
+ <label
+ class="title-label"
+ for="poll_title"
+ i18n
+ >
+ {{"creation.choose_title"|translate}}
+ </label >
+ <input
+ [(ngModel)]="config.title"
+ id="poll_title"
+ name="poll_title"
+ placeholder="{{'creation.choose_title_placeholder'|translate}}"
+ type="name"
+ >
+ <framadate-erasable-input [(inputModel)]="config.title" ></framadate-erasable-input >
+ </div >
+
+ <div >
+ <label for="my_name" >
+ {{"creation.name"|translate}} :
+ </label >
+ <input
+ [(ngModel)]="config.myName"
+ id="my_name"
+ name="my_name"
+ placeholder="{{'creation.name_placeholder'|translate}}"
+ type="name"
+ >
+ <framadate-erasable-input [(inputModel)]="config.myName" ></framadate-erasable-input >
+ </div >
+
+ <div >
+ <label
+ class="title-label"
+ for="poll_description"
+ i18n
+ >
+ {{"creation.description"|translate}}:
+ <framadate-erasable-input [(inputModel)]="config.description" ></framadate-erasable-input >
+ </label >
+ <br >
+ <textarea
+ [(ngModel)]="config.description"
+ cols="50"
+ id="poll_description"
+ lines="5"
+ name="poll_description"
+ placeholder="description"
+ ></textarea >
+ </div >
+ <div >
+ <label for="email" >
+ Mon email pour administrer le sondage est
+ </label >
+ <input
+ [(ngModel)]="config.myEmail"
+ autofocus="autofocus"
+ id="email"
+ name="mail"
+ required="required"
+ type="email"
+ />
+ <framadate-erasable-input [(inputModel)]="config.myEmail" ></framadate-erasable-input >
+ </div >
+
+ <button
+ [routerLink]="'/step/answers'"
+ class="btn btn--primary btn--full"
+ *ngIf="config.pollType == 'classic'"
+ i18n
+ >
+ Continuer
+ <i class='fa fa-file-text' ></i >
+ </button >
+ <button
+ [routerLink]="'/step/date'"
+ class="btn btn--primary btn--full"
+ *ngIf="config.pollType == 'dates'"
+ i18n
+ >
+ Continuer
+ <i class='fa fa-calendar-check-o' ></i >
+ </button >
+ <a
+ [routerLink]="'/step/creation'"
+ class="prev"
+ i18n
+ >
+ Retour
+ </a >
+ <hr >
+</div >
+
+
+ ./home.component.scss
+
+ +
+ src/app/pages/kind/kind.component.ts
+
+
+ BaseComponent
+
+
+ OnInit
+
selector | +framadate-page-kind |
+
styleUrls | +./kind.component.scss |
+
templateUrl | +./kind.component.html |
+
+ Properties+ |
+
+ + | +
+ Methods+ |
+
+
|
+
+constructor(config: ConfigService)
+ |
+ ||||||
+ Defined in src/app/pages/kind/kind.component.ts:10
+ |
+ ||||||
+
+ Parameters :
+
+
|
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ Defined in src/app/pages/kind/kind.component.ts:16
+ |
+
+
+
+ Returns :
+ void
+
+ |
+
+ + + + checkValidity + + + + | +
+checkValidity()
+ |
+
+ Inherited from
+ BaseComponent
+ |
+
+ Defined in
+ BaseComponent:21
+ |
+
+
+
+ Returns :
+ boolean
+
+ |
+
+ + + + displayErrorMessage + + + + | +
+displayErrorMessage()
+ |
+
+ Inherited from
+ BaseComponent
+ |
+
+ Defined in
+ BaseComponent:27
+ |
+
+
+
+ Returns :
+ boolean
+
+ |
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ Inherited from
+ BaseComponent
+ |
+
+ Defined in
+ BaseComponent:17
+ |
+
+
+
+ Returns :
+ void
+
+ |
+
+ + + + Public + config + + + | +
+ Type : ConfigService
+
+ |
+
+ Defined in src/app/pages/kind/kind.component.ts:12
+ |
+
+ + + + Public + config + + + | +
+ Type : ConfigService
+
+ |
+
+ Inherited from
+ BaseComponent
+ |
+
+ Defined in
+ BaseComponent:14
+ |
+
import {Component, OnInit} from '@angular/core';
+import {BaseComponent} from '../base-page/base.component';
+import {ConfigService} from '../../services/config.service';
+
+@Component({
+ selector: 'framadate-page-kind',
+ templateUrl: './kind.component.html',
+ styleUrls: ['./kind.component.scss']
+})
+export class KindComponent extends BaseComponent implements OnInit {
+
+ constructor(public config: ConfigService) {
+ super(config);
+ }
+
+ ngOnInit() {
+ }
+
+}
+
+ <h1 >page de démo</h1 >
+<p >cette étape est en cours de développement. <br > S'inspirer de la page de Home pour réaliser d'autres pages
+</p >
+<a
+ [routerLink]="'/step/end'"
+ class="btn btn--primary"
+ i18n="@@confirm" >C'est parfait!</a >
+<h1 >Atoms</h1 >
+<section >
+ <article >
+ <h2 >Headings</h2 >
+
+ <h1 >Ceci est un h1</h1 >
+ <h2 >Ceci est un h2</h2 >
+ <h3 >Ceci est un h3</h3 >
+ </article >
+
+ <article >
+ <h2 >Links</h2 >
+ <div >
+ <a
+ [routerLink]="'/home'"
+ class="next" >
+ <span
+ class="text"
+ i18n >
+ C'est parti !
+ </span >
+ </a >
+ </div >
+ <div >
+ <a
+ [routerLink]="'/home'"
+ class="prev" >
+ <span
+ class="text"
+ i18n >
+ C'est parti !
+ </span >
+ </a >
+ </div >
+ </article >
+
+ <article >
+ <h2 >Buttons</h2 >
+
+ <button
+ type="submit"
+ class="btn btn--primary" >
+ primary - default
+ </button >
+
+ <br >
+
+ <button
+ type="submit"
+ class="btn btn--primary btn--small" >
+ primary - small
+ </button >
+
+ <br >
+
+ <a
+ href="#"
+ class="btn btn--primary btn--outline" >
+ primary - outline - default
+ </a >
+
+ <br >
+
+ <button
+ type="submit"
+ class="btn btn--primary btn--outline btn--small" >
+ primary - outline - small
+ </button >
+
+ <br >
+
+ <input
+ type="submit"
+ class="btn btn--alert"
+ value="alert - default" >
+
+ <br >
+
+ <button
+ type="submit"
+ class="btn btn--alert btn--small" >
+ alert - small
+ </button >
+
+ <br >
+
+ <button
+ type="submit"
+ class="btn btn--alert btn--outline" >
+ alert - outline - default
+ </button >
+
+ <br >
+
+ <button
+ type="submit"
+ class="btn btn--alert btn--outline btn--small" >
+ alert - outline - small
+ </button >
+
+ <br ><br >
+
+ <button
+ type="submit"
+ class="btn btn--primary btn--full" >
+ primary - full
+ </button >
+
+ <button
+ type="submit"
+ class="btn btn--primary btn--outline btn--full" >
+ primary - outline - full
+ </button >
+
+ <button
+ type="submit"
+ class="btn btn--alert btn--full" >
+ alert - full
+ </button >
+
+ <button
+ type="submit"
+ class="btn btn--alert btn--outline btn--full" >
+ alert - outline - full
+ </button >
+
+ </article >
+
+ <article >
+ <h2 >Forms element</h2 >
+
+ <h3 >Labels</h3 >
+
+ <label for="" >Un label pour les labelliser tous</label >
+
+ <h3 >Input name</h3 >
+ <input
+ type="name"
+ name=""
+ id="" ><br >
+ <input
+ type="name"
+ name=""
+ id=""
+ value="texte" >
+
+ <h3 >Input email</h3 >
+ <input
+ type="email"
+ name=""
+ id="" ><br >
+ <input
+ type="email"
+ name=""
+ id=""
+ value="adresse@email.com" >
+
+ <h3 >Input password</h3 >
+ <input
+ type="password"
+ name=""
+ id="" ><br >
+ <input
+ type="password"
+ name=""
+ id=""
+ value="password" >
+
+ <h3 >Input date</h3 >
+ <input
+ type="date"
+ name=""
+ id="" ><br >
+ <input
+ type="date"
+ name=""
+ id=""
+ value="1985-11-23" >
+
+ <h3 >Select</h3 >
+ <select
+ name=""
+ id="" >
+ <option value="" >
+ 1
+ </option >
+ <option value="" >
+ 2
+ </option >
+ <option value="" >
+ 3
+ </option >
+ </select >
+
+ <h3 >Textarea</h3 >
+ <textarea
+ name=""
+ id=""
+ cols="30"
+ rows="10" ></textarea >
+ </article >
+
+ <article >
+ <h2 >Paragraphs</h2 >
+
+ <p >Lorem, ipsum dolor sit amet consectetur adipisicing elit. Magnam perspiciatis minus libero error dolores.
+ Corrupti repellat vero repellendus reiciendis assumenda minus. Nobis, quaerat ut nihil minima sed animi
+ delectus beatae!</p >
+ </article >
+
+ <article >
+ <h2 >Lists</h2 >
+
+ <h3 >Unordered list</h3 >
+ <ul >
+ <li >
+ plop
+ </li >
+ <li >
+ plop
+ </li >
+ <li >
+ plop
+ </li >
+ </ul >
+
+ <h3 >Ordered list</h3 >
+ <ol >
+ <li >
+ plop
+ </li >
+ <li >
+ plop
+ </li >
+ <li >
+ plop
+ </li >
+ </ol >
+ </article >
+
+ <article >
+ <h2 >Images</h2 >
+ <img
+ src="http://placekitten.com/200/300"
+ alt="" >
+ </article >
+</section >
+
+<section >
+ <h1 >Molecules</h1 >
+ <framadate-voting-choice ></framadate-voting-choice >
+ <article >
+ <h2 >Useful classes</h2 >
+
+ <h3 >Align right</h3 >
+ <div class="align-right" >
+ <a
+ [routerLink]="'/home'"
+ class="next" >
+ <span
+ class="text"
+ i18n >
+ C'est parti !
+ </span >
+ </a >
+ </div >
+ </article >
+
+ <article >
+ <h2 >Label + input name</h2 >
+ <label for="test-text" >Ceci est un label un peu long mais pas trop</label >
+ <input
+ type="name"
+ name="test-text"
+ id="test-text" >
+ </article >
+
+ <article >
+ <h2 >Label + select</h2 >
+ <label for="test-select" >Ceci est un label un peu long mais pas trop</label >
+ <select
+ name="test-select"
+ id="test-select" >
+ <option value="" >
+ 1
+ </option >
+ <option value="" >
+ 2
+ </option >
+ <option value="" >
+ 3
+ </option >
+ </select >
+ </article >
+
+ <article >
+ <h2 >Input name with info</h2 >
+ <a href="https://sketch.cloud/s/00A80/a/MAl5q7" >like here</a >
+ </article >
+
+ <article >
+ <h2 >Commentaries</h2 >
+ </article >
+</section >
+
+<section >
+ <h1 >Components</h1 >
+
+ <article >
+ <h2 >Images list</h2 >
+ <a href="https://sketch.cloud/s/00A80/a/bQA9wj" >that</a >
+ </article >
+
+ <article >
+ <h2 >Calendar</h2 >
+ </article >
+
+ <article >
+ <h2 >Modale</h2 >
+ </article >
+
+ <article >
+ <h2 >Way to vote</h2 >
+ <a href="https://sketch.cloud/s/00A80/a/Ol0598" >that</a >
+ </article >
+
+ <article >
+ <h2 >Voted</h2 >
+ <a href="https://sketch.cloud/s/00A80/a/OlJZo2" >that</a >
+ </article >
+
+ <article >
+ <h2 >Graphics</h2 >
+ <a href="https://sketch.cloud/s/00A80/a/megprw" >that</a >
+ </article >
+</section >
+
+
+ ./kind.component.scss
+
@charset "UTF-8";
+
+section {
+ &:not(:last-of-type) {
+ border-bottom: 6px solid #000;
+ }
+
+ + section {
+ margin: 0 !important;
+ }
+
+}
+
+article {
+ padding: 2rem 0;
+ border-top: 3px solid #ffb92c;
+
+ h3 {
+ &:not(:first-of-type) {
+ margin-top: 2rem;
+ }
+ }
+}
+
+ +
+ src/app/ui/navigation/navigation.component.ts
+
+
+ BaseComponent
+
+
+ OnInit
+
selector | +framadate-navigation |
+
styleUrls | +./navigation.component.scss |
+
templateUrl | +./navigation.component.html |
+
+ Properties+ |
+
+ + | +
+ Methods+ |
+
+
|
+
+ Inputs+ |
+
+
|
+
+constructor(config: ConfigService, route: Router)
+ |
+ |||||||||
+ + | +|||||||||
+
+ Parameters :
+
+
|
+
+ + step + | +|
+ Type : string
+
+ |
+ |
+ Default value : 'home'
+ |
+ |
+ + | +
+ + + + nextPage + + + + | +
+nextPage()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
+ + + + checkValidity + + + + | +
+checkValidity()
+ |
+
+ Inherited from
+ BaseComponent
+ |
+
+ Defined in
+ BaseComponent:21
+ |
+
+
+
+ Returns :
+ boolean
+
+ |
+
+ + + + displayErrorMessage + + + + | +
+displayErrorMessage()
+ |
+
+ Inherited from
+ BaseComponent
+ |
+
+ Defined in
+ BaseComponent:27
+ |
+
+
+
+ Returns :
+ boolean
+
+ |
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ Inherited from
+ BaseComponent
+ |
+
+ Defined in
+ BaseComponent:17
+ |
+
+
+
+ Returns :
+ void
+
+ |
+
+ + + + Public + config + + + | +
+ Type : ConfigService
+
+ |
+
+ + | +
+ + + + Public + route + + + | +
+ Type : Router
+
+ |
+
+ + | +
+ + + + Public + config + + + | +
+ Type : ConfigService
+
+ |
+
+ Inherited from
+ BaseComponent
+ |
+
+ Defined in
+ BaseComponent:14
+ |
+
import {Component, Input, OnInit} from '@angular/core';
+import {BaseComponent} from '../../pages/base-page/base.component';
+
+import {Router} from '@angular/router';
+import {ConfigService} from '../../services/config.service';
+
+@Component({
+ selector: 'framadate-navigation',
+ templateUrl: './navigation.component.html',
+ styleUrls: ['./navigation.component.scss']
+})
+export class NavigationComponent extends BaseComponent implements OnInit {
+
+ @Input() public step: string = 'home';
+
+ constructor(public config: ConfigService,
+ public route: Router) {
+ super(config);
+
+ }
+
+ ngOnInit() {
+
+ }
+
+ nextPage() {
+ if (this.checkValidity()) {
+ if (this.config.step < this.config.stepMax) {
+ this.config.step++;
+ }
+
+ } else {
+ this.displayErrorMessage();
+ }
+ }
+}
+
+ <nav class="choices" >
+ <a
+ [routerLink]="'home'"
+ [ngClass]="{'active': (step === 'home')}"
+ i18n
+ >
+ <i class="fa fa-home" ></i >
+ </a >
+ <a
+ [routerLink]="'/step/creation'"
+ [ngClass]="{'active': step == 'creation'}"
+ i18n
+ >
+ Création
+ </a >
+ <a
+ [routerLink]="'/step/date'"
+ [ngClass]="{'active': step == 'date'}"
+ i18n
+ >
+ Les Dates
+ </a >
+ <a
+ [routerLink]="'/step/answers'"
+ [ngClass]="{'active': step == 'answers'}"
+ i18
+ >
+ Réponses
+ </a >
+ <a
+ [routerLink]="'/step/visibility'"
+ [ngClass]="{'active': step == 'visibility'}"
+ i18n
+ >
+ Visibilité
+ </a >
+ <a
+ [routerLink]="'/step/base'"
+ [ngClass]="{'active': step == 'base'}"
+ i18n
+ >
+ Base
+ </a >
+ <a
+ [routerLink]="'/step/pictures'"
+ [ngClass]="{'active': step == 'pictures'}"
+ i18n
+ >
+ Images
+ </a >
+ <a
+ [routerLink]="'/step/resume'"
+ [ngClass]="{'active': step == 'resume'}"
+ i18n
+ >
+ Résumé
+ </a >
+ <a
+ [routerLink]="'/step/end'"
+ [ngClass]="{'active': step == 'end'}"
+ i18n
+ >
+ Confirmation
+ </a >
+ <a
+ [routerLink]="'/step/kind'"
+ [ngClass]="{'active': step == 'kind'}"
+ i18n
+ >
+ Page démo
+ </a >
+ <a
+ [routerLink]="['vote/poll/id/',3]"
+ i18n >
+ Sondage dessins animés
+ </a >
+ <a
+ [routerLink]="['vote/poll/id/',4]"
+ i18n >
+ Sondage 4
+ </a >
+ <a
+ [routerLink]="'/vote/poll/id/5'"
+ i18n >
+ Sondage 5
+ </a >
+ <a
+ [routerLink]="'/graphic/toto'"
+ [ngClass]="{'active': step == 'graphic'}"
+ i18n
+ >
+ Graphique
+ </a >
+ <a
+ [routerLink]="'/step/admin'"
+ i18n
+ >
+ Administration
+ </a >
+ <a
+ [routerLink]="'/home'"
+ i18n
+ >
+ Accueil
+ </a >
+</nav >
+
+
+ ./navigation.component.scss
+
@charset "UTF-8";
+
+nav {
+ text-align: center;
+ margin-bottom: 3.2rem;
+ padding-top: 1.6rem;
+ padding-bottom: 1.6rem;
+ border-top: 2px solid #ffd52c;
+ border-bottom: 2px solid #ffd52c;
+
+ a {
+ &::before {
+ display: inline-block;
+ position: relative;
+ width: auto;
+ background: none;
+ }
+
+ &::after {
+ display: none;
+ }
+
+ &:not(:first-of-type)::before {
+ content: ' | ';
+
+ }
+
+ &.active {
+ color: white;
+ background: black;
+ }
+ }
+}
+
+ +
+ src/app/pages/password/password.component.ts
+
+
+ BaseComponent
+
+
+ OnInit
+
selector | +framadate-password |
+
styleUrls | +./password.component.scss |
+
templateUrl | +./password.component.html |
+
+ Properties+ |
+
+ + | +
+ Methods+ |
+
+
|
+
+constructor(config: ConfigService)
+ |
+ ||||||
+ + | +||||||
+
+ Parameters :
+
+
|
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
+ + + + checkValidity + + + + | +
+checkValidity()
+ |
+
+ Inherited from
+ BaseComponent
+ |
+
+ Defined in
+ BaseComponent:21
+ |
+
+
+
+ Returns :
+ boolean
+
+ |
+
+ + + + displayErrorMessage + + + + | +
+displayErrorMessage()
+ |
+
+ Inherited from
+ BaseComponent
+ |
+
+ Defined in
+ BaseComponent:27
+ |
+
+
+
+ Returns :
+ boolean
+
+ |
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ Inherited from
+ BaseComponent
+ |
+
+ Defined in
+ BaseComponent:17
+ |
+
+
+
+ Returns :
+ void
+
+ |
+
+ + + + Public + config + + + | +
+ Type : ConfigService
+
+ |
+
+ + | +
+ + + + Public + config + + + | +
+ Type : ConfigService
+
+ |
+
+ Inherited from
+ BaseComponent
+ |
+
+ Defined in
+ BaseComponent:14
+ |
+
import { Component, OnInit } from '@angular/core';
+import {BaseComponent} from "../base-page/base.component";
+import {ConfigService} from "../../services/config.service";
+
+@Component({
+ selector: 'framadate-password',
+ templateUrl: './password.component.html',
+ styleUrls: ['./password.component.scss']
+})
+export class PasswordComponent extends BaseComponent implements OnInit {
+
+ constructor(public config: ConfigService) {
+ super(config);
+ }
+
+ ngOnInit() {
+ }
+
+}
+
+ <h1 class="margin-btm-x3">Nom du sondage</h1>
+<p class="margin-btm-x6">Ce sondage est protégé par un mot de passe</p>
+
+<form>
+ <label for="password">Mot de passe :</label>
+ <section class="row">
+ <input class="margin-btm-x5" type="password" name="password">
+ <input type="submit" name="view" value="Voir" class="btn btn--small btn--purple">
+ </section>
+
+<input type="submit" name="go" value="Accéder au sondage" class="btn btn--primary btn--full btn--semi-bold">
+</form>
+
+
+ ./password.component.scss
+
form{
+ display:flex;
+ flex-direction: column;
+}
+label{
+ font-weight: 600;
+ font-size: 18px;
+}
+h1{
+ display:flex;
+ align-self: center;
+}
+
+ +
+ src/app/pages/pictures/pictures.component.ts
+
+
+ BaseComponent
+
+
+ OnInit
+
selector | +framadate-pictures |
+
styleUrls | +./pictures.component.scss |
+
templateUrl | +./pictures.component.html |
+
+ Properties+ |
+
+ + | +
+ Methods+ |
+
+
|
+
+constructor(config: ConfigService)
+ |
+ ||||||
+ + | +||||||
+
+ Parameters :
+
+
|
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
+ + + + checkValidity + + + + | +
+checkValidity()
+ |
+
+ Inherited from
+ BaseComponent
+ |
+
+ Defined in
+ BaseComponent:21
+ |
+
+
+
+ Returns :
+ boolean
+
+ |
+
+ + + + displayErrorMessage + + + + | +
+displayErrorMessage()
+ |
+
+ Inherited from
+ BaseComponent
+ |
+
+ Defined in
+ BaseComponent:27
+ |
+
+
+
+ Returns :
+ boolean
+
+ |
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ Inherited from
+ BaseComponent
+ |
+
+ Defined in
+ BaseComponent:17
+ |
+
+
+
+ Returns :
+ void
+
+ |
+
+ + + + Public + config + + + | +
+ Type : ConfigService
+
+ |
+
+ + | +
+ + + + Public + config + + + | +
+ Type : ConfigService
+
+ |
+
+ Inherited from
+ BaseComponent
+ |
+
+ Defined in
+ BaseComponent:14
+ |
+
import {Component, OnInit} from '@angular/core';
+import {ConfigService} from '../../services/config.service';
+import {BaseComponent} from '../base-page/base.component';
+
+@Component({
+ selector: 'framadate-pictures',
+ templateUrl: './pictures.component.html',
+ styleUrls: ['./pictures.component.scss']
+})
+export class PicturesComponent extends BaseComponent implements OnInit {
+
+ constructor(public config: ConfigService) {
+ super(config);
+ }
+
+ ngOnInit() {
+ }
+
+}
+
+ <!--ceci est la popup pour ajouter une image-->
+<h1 i18n>
+ Images
+</h1>
+<a [routerLink]="'/step/visibility'" class="btn btn--primary">ok</a>
+
+
+
+ ./pictures.component.scss
+
+ +
+ src/app/pages/poll-display/poll-display.component.ts
+
+
+ BaseComponent
+
+
+ OnInit
+
selector | +framadate-poll-display |
+
styleUrls | +./poll-display.component.scss |
+
templateUrl | +./poll-display.component.html |
+
+ Properties+ |
+
+
|
+
+ Methods+ |
+
+
|
+
+constructor(config: ConfigService, router: Router, activeRoute: ActivatedRoute)
+ |
+ ||||||||||||
+ + | +||||||||||||
+
+ Parameters :
+
+
|
+
+ + + + fetchPoll + + + + | +
+fetchPoll()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
+ + + + checkValidity + + + + | +
+checkValidity()
+ |
+
+ Inherited from
+ BaseComponent
+ |
+
+ Defined in
+ BaseComponent:21
+ |
+
+
+
+ Returns :
+ boolean
+
+ |
+
+ + + + displayErrorMessage + + + + | +
+displayErrorMessage()
+ |
+
+ Inherited from
+ BaseComponent
+ |
+
+ Defined in
+ BaseComponent:27
+ |
+
+
+
+ Returns :
+ boolean
+
+ |
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ Inherited from
+ BaseComponent
+ |
+
+ Defined in
+ BaseComponent:17
+ |
+
+
+
+ Returns :
+ void
+
+ |
+
+ + + + Public + activeRoute + + + | +
+ Type : ActivatedRoute
+
+ |
+
+ + | +
+ + + + comments + + + | +
+ Default value : mockComments
+ |
+
+ + | +
+ + + + Public + config + + + | +
+ Type : ConfigService
+
+ |
+
+ + | +
+ + + + Public + config + + + | +
+ Type : ConfigService
+
+ |
+
+ Inherited from
+ BaseComponent
+ |
+
+ Defined in
+ BaseComponent:14
+ |
+
import {Component, OnInit} from '@angular/core';
+import {BaseComponent} from "../base-page/base.component";
+import {ConfigService} from "../../services/config.service";
+import {mockComments} from "../../config/mocks/mock-comments";
+import {ActivatedRoute, Router} from "@angular/router";
+import {environment} from "../../../environments/environment";
+import {mockPoll3} from "../../config/mocks/mock-poll3";
+
+@Component({
+ selector: 'framadate-poll-display',
+ templateUrl: './poll-display.component.html',
+ styleUrls: ['./poll-display.component.scss']
+})
+export class PollDisplayComponent extends BaseComponent implements OnInit {
+
+ comments = mockComments;
+
+ constructor(public config: ConfigService,
+ private router: Router,
+ public activeRoute: ActivatedRoute) {
+ super(config);
+ this.activeRoute.paramMap.subscribe(params => {
+ console.log('params', params);
+ this.config.pollId = params.get('poll');
+ this.config.pollSlug = params.get('pollSlug');
+ if (!this.config.loading) {
+ this.fetchPoll();
+ }
+ });
+ }
+
+ ngOnInit() {
+
+ }
+
+
+ // fetch poll with its ID or slug.
+ fetchPoll() {
+ const id = this.activeRoute.snapshot.params.poll;
+ const pollSlug = this.activeRoute.snapshot.params.pollSlug;
+
+ if (!environment.production) {
+ console.log('mockPoll3', mockPoll3);
+ this.config.currentPoll = mockPoll3;
+ return;
+ }
+ if (id) {
+ this.config.loading = true;
+ // store it in the poll property here
+ this.config.getPollById(id).subscribe(
+ (res: any) => {
+ console.log('res', res);
+ this.config.updateCurrentPollFromResponse(res);
+ this.config.loading = false;
+ }, (e) => {
+ // handle need for a password
+ console.log('e', e);
+ this.config.handleError(e)
+ }
+ );
+ } else if (pollSlug) {
+ this.config.loading = true;
+ this.config.getPollByURL(pollSlug).subscribe(
+ (res: any) => {
+ this.config.loading = false;
+ this.config.updateCurrentPollFromResponse(res);
+ }, (e) => {
+ // handle need for a password
+ this.config.handleError(e)
+ }
+ );
+ }
+
+ }
+
+}
+
+ <div
+ class="poll"
+>
+ <div
+ class='loading'
+ *ngIf='config.loading' >
+ <i class='fa fa-refresh fa-spin' ></i >
+ </div >
+ <div
+ class='loaded-poll'
+ *ngIf='!config.loading && config.currentPoll' >
+
+ <div id='choices' >
+
+ <framadate-choices-list ></framadate-choices-list >
+ </div >
+ <div id='table' >
+ <!-- <framadate-voting-navigation ></framadate-voting-navigation >-->
+ <framadate-voting-summary ></framadate-voting-summary >
+ </div >
+ <div id='poll_comments' >
+ <framadate-comments-list ></framadate-comments-list >
+ </div >
+ <div id='graph' >
+ <!--<framadate-voting-graph ></framadate-voting-graph >-->
+ </div >
+ <div id='export_and_share' >
+
+ <div
+ class="sharing"
+ *ngIf='config.currentPoll' >
+ <h3 class="margin-top-x8" >Partager le sondage
+
+ <i class='fa fa-share' ></i ></h3 >
+ <p
+ class="nobold text-14"
+ for="copyLink" >Pour partager le sondage, vous pouvez diffuser ce lien :
+ <a href='{{config.currentPoll.urlPublic}}' >
+ {{config.currentPoll.urlPublic}}
+ </a >
+ </p >
+
+ <framadate-copy-text [textToCopy]='config.currentPoll.urlPublic' ></framadate-copy-text >
+ <h3 class="margin-top-x6 margin-btm-x3" >
+ Exporter/Imprimer
+ </h3 >
+ <input
+ type="submit"
+ name="export"
+ class="margin-btm-x3 btn btn--primary btn--outline"
+ value="Exporter en .csv"
+ (click)="config.exportCSV()" >
+ <input
+ type="submit"
+ name="copy-link"
+ class="btn btn--primary btn--outline"
+ value="Imprimer le sondage"
+ (click)="config.print()" >
+ </div >
+
+ </div >
+ </div >
+ <div
+ class='badly-loaded'
+ *ngIf='config.loading && !config.currentPoll' >
+ <div class='well is-warning' >
+
+ No current poll available
+ </div >
+ </div >
+
+</div >
+
+
+ ./poll-display.component.scss
+
@import "../../../assets/scss/variables";
+
+
+
+ +
+ src/app/poll-graphic/poll-graphic.component.ts
+
+
+ OnInit
+
selector | +framadate-poll-graphic |
+
styleUrls | +./poll-graphic.component.scss |
+
templateUrl | +./poll-graphic.component.html |
+
+ Properties+ |
+
+
|
+
+ Methods+ |
+
+
|
+
+constructor(document: any, config: ConfigService)
+ |
+ |||||||||
+ + | +|||||||||
+
+ Parameters :
+
+
|
+
+ + + + formatDataAnswers + + + + | +||||
+formatDataAnswers(poll)
+ |
+ ||||
+ + | +||||
+
+
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + initPollCounter + + + + | +
+initPollCounter()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
+ + + + toggleColorblind + + + + | +
+toggleColorblind()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
+ + + + dateList + + + | +
+ Type : string[]
+
+ |
+
+ Default value : []
+ |
+
+ + | +
+ + + + graphicConfig + + + | +
+ Type : any
+
+ |
+
+ Default value : mockGraphConfig
+ |
+
+ + | +
+ + + + isColorblind + + + | +
+ Type : boolean
+
+ |
+
+ Default value : false
+ |
+
+ + | +
+ + + + maybeList + + + | +
+ Type : number[]
+
+ |
+
+ Default value : []
+ |
+
+ + | +
+ + + + nbPoll + + + | +
+ Type : number
+
+ |
+
+ Default value : 0
+ |
+
+ + | +
+ + + + noList + + + | +
+ Type : number[]
+
+ |
+
+ Default value : []
+ |
+
+ + | +
+ + + + pollConfigRetrieved + + + | +
+ Type : any
+
+ |
+
+ Default value : mockPoll3
+ |
+
+ + | +
+ + + + preferred + + + | +
+ Type : any
+
+ |
+
+ Default value : "rien"
+ |
+
+ + | +
+ + + + yesList + + + | +
+ Type : number[]
+
+ |
+
+ Default value : []
+ |
+
+ + | +
import {Component, Inject, OnInit} from "@angular/core";
+import {Chart} from "chart.js";
+import {DOCUMENT} from '@angular/common';
+import {mockGraphConfig} from "../config/mocks/mock-graph";
+import {graphOptions} from "../config/graph-canevas-options";
+import {ConfigService} from "../services/config.service";
+import {mockPoll3} from "../config/mocks/mock-poll3";
+
+@Component({
+ selector: "framadate-poll-graphic",
+ templateUrl: "./poll-graphic.component.html",
+ styleUrls: ["./poll-graphic.component.scss"]
+})
+export class PollGraphicComponent implements OnInit {
+ isColorblind: boolean = false;
+ pollConfigRetrieved: any = mockPoll3;
+ graphicConfig: any = mockGraphConfig;
+ preferred: any = "rien";
+ yesList: number[] = [];
+ maybeList: number[] = [];
+ noList: number[] = [];
+ nbPoll: number = 0;
+ dateList: string[] = [];
+
+ constructor(@Inject(DOCUMENT) private document: any,
+ private config: ConfigService) {
+ }
+
+ ngOnInit() {
+ this.formatDataAnswers(this.graphicConfig);
+ this.isColorblind = false;
+ this.pollConfigRetrieved = new Chart(this.document.getElementById("graph"), {
+ type: "horizontalBar",
+ data: {
+ labels: this.pollConfigRetrieved.choices.map(choice => choice.name),
+ datasets: [
+ {
+ type: "horizontalBar",
+ stack: "Yes",
+ backgroundColor: "#429a00",
+ data: this.yesList
+ },
+ {
+ type: "horizontalBar",
+ stack: "Yes",
+ backgroundColor: "#f5a623",
+ data: this.maybeList
+ },
+ {
+ type: "horizontalBar",
+ stack: "No",
+ backgroundColor: "#cd0000",
+ data: this.noList
+ }
+ ]
+ },
+ options: graphOptions
+ });
+ }
+
+ toggleColorblind() {
+ this.isColorblind = !this.isColorblind;
+ }
+
+ formatDataAnswers(poll) {
+ // if (poll && poll.pollType === "date") {
+ this.initPollCounter();
+ poll.answers.forEach(response => {
+ switch (response.text) {
+ case "yes":
+ this.yesList[this.nbPoll - 1]++;
+ break;
+ case "maybe":
+ this.maybeList[this.nbPoll - 1]++;
+ break;
+ case "no":
+ this.noList[this.nbPoll - 1]++;
+ break;
+ }
+ });
+ // }
+ }
+
+ initPollCounter() {
+ this.nbPoll++;
+ this.dateList[this.nbPoll - 1] = "jeudi";
+ this.maybeList[this.nbPoll - 1] = 0;
+ this.yesList[this.nbPoll - 1] = 0;
+ this.noList[this.nbPoll - 1] = 0;
+ }
+}
+
+ <select
+ id="selectColorblind"
+ name="selector"
+ autofocus="autofocus"
+ [(ngModel)]="isColorblind"
+ (change)="toggleColorblind()"
+ class="input"
+>
+ <option value="true" >
+ {{ "pollGraphic.choiceColorblind" | translate }}
+ </option >
+ <option value="false" >
+ {{ "pollGraphic.choiceNotColorblind" | translate }}
+ </option >
+</select >
+<span class="colorblind" >
+{{ "pollGraphic.colorblindText" | translate }}
+</span >
+<div class='well' >
+ work in progress to link data with poll config
+</div >
+<div >
+ <canvas
+ id="graph"
+ width="100%"
+ height="15em" ></canvas >
+</div >
+
+
+
+
+ ./poll-graphic.component.scss
+
#selectColorblind {
+ direction: rtl;
+}
+
+ +
+ src/app/pages/resume/resume.component.ts
+
+
+ BaseComponent
+
selector | +framadate-resume |
+
styleUrls | +./resume.component.scss |
+
templateUrl | +./resume.component.html |
+
+ Properties+ |
+
+ + | +
+ Methods+ |
+
+
|
+
+constructor(config: ConfigService, router: Router)
+ |
+ |||||||||
+ Defined in src/app/pages/resume/resume.component.ts:11
+ |
+ |||||||||
+
+ Parameters :
+
+
|
+
+ + + + checkValidity + + + + | +
+checkValidity()
+ |
+
+ Inherited from
+ BaseComponent
+ |
+
+ Defined in
+ BaseComponent:21
+ |
+
+
+
+ Returns :
+ boolean
+
+ |
+
+ + + + displayErrorMessage + + + + | +
+displayErrorMessage()
+ |
+
+ Inherited from
+ BaseComponent
+ |
+
+ Defined in
+ BaseComponent:27
+ |
+
+
+
+ Returns :
+ boolean
+
+ |
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ Inherited from
+ BaseComponent
+ |
+
+ Defined in
+ BaseComponent:17
+ |
+
+
+
+ Returns :
+ void
+
+ |
+
+ + + + Public + config + + + | +
+ Type : ConfigService
+
+ |
+
+ Defined in src/app/pages/resume/resume.component.ts:13
+ |
+
+ + + + Public + config + + + | +
+ Type : ConfigService
+
+ |
+
+ Inherited from
+ BaseComponent
+ |
+
+ Defined in
+ BaseComponent:14
+ |
+
import {Component} from '@angular/core';
+import {BaseComponent} from '../base-page/base.component';
+import {ConfigService} from '../../services/config.service';
+import {Router} from "@angular/router";
+
+@Component({
+ selector: 'framadate-resume',
+ templateUrl: './resume.component.html',
+ styleUrls: ['./resume.component.scss']
+})
+export class ResumeComponent extends BaseComponent {
+
+ constructor(public config: ConfigService,
+ private router: Router) {
+ super(config);
+ }
+
+}
+
+ <h1 >
+ Récapitulatif
+</h1 >
+<div class='card content' >
+
+ <h2 class="hero-title title" >
+ {{config.title}}
+ </h2 >
+ <div class="creation" >
+ créé par
+ <i class='pseudo' >
+ {{config.myName}}
+ </i >
+ </div >
+ <div class="description" >
+ <cite >
+ {{config.description}}
+ </cite >
+ </div >
+ <section
+ class='preview type-classic'
+ *ngIf='config.pollType == "classic"' >
+ <ul >
+ <li
+ markdown
+ *ngFor="let questions of config.answers" >
+ <img
+ class='img-thumbnail'
+ src='{{questions.url}}'
+ alt='image {{questions.url}}' >
+ {{questions.id + 1}}. {{questions.text}}
+ </li >
+ </ul >
+ </section >
+ <section
+ class='type-date'
+ *ngIf='config.pollType !== "classic"' >
+ <i class='fa fa-clock-o' ></i >
+ <span
+ class='well'
+ *ngIf="'true' === config.allowSeveralHours" >
+ {{"dates.multiple.different"|translate}}
+ </span >
+ <span
+ class='well'
+ *ngIf="'false' === config.allowSeveralHours" >
+ {{"dates.multiple.identical"|translate}}
+ </span >
+ <div
+ *ngFor="let choice of config.dateList; index as id"
+ class="date-choice"
+ >
+ <div
+ class='only-one-slice'
+ *ngIf='!choice.timeList.length' >
+ <i class='fa fa-square-o' ></i >
+ </div >
+ {{choice.literal}}
+
+ <!-- CASE different slices of the day-->
+ <div
+ *ngIf="'true' === config.allowSeveralHours"
+ class="several-times"
+ >
+
+ <div
+ *ngFor="let time of choice.timeList ; index as idTime"
+ class="time-choice"
+ >
+ {{idTime}})
+ <i class='fa fa-square-o' ></i >
+ {{time.literal}}
+ </div >
+
+ </div >
+ <!-- CASE all dates having the same slices of the day-->
+ <div
+ *ngIf="'false' === config.allowSeveralHours"
+ class="same-times"
+ >
+ <div
+ *ngFor="let time of config.timeList "
+ class="time-choice"
+ >
+ <i class='fa fa-square-o' ></i >
+ {{time.literal}}
+ </div >
+ </div >
+ </div >
+ </section >
+
+</div >
+
+<button
+ [routerLink]="'/step/visibility'"
+ class="btn btn--primary btn--full" >
+ C'est parfait!
+</button >
+
+<div class="back" >
+ <a
+ *ngIf='config.pollType == "classic"'
+ [routerLink]="'/step/answers'"
+ class="prev" >
+ Retour
+ </a >
+ <a
+ *ngIf='config.pollType == "dates"'
+ [routerLink]="'/step/date'"
+ class="prev" >
+ Retour
+ </a >
+</div >
+
+
+ ./resume.component.scss
+
.card {
+ box-shadow: 0px 0px 0.5em #ccc;
+ padding: 2em;
+ margin: 1em 0;
+}
+
+.time-choice {
+ margin-left: 3em;
+}
+
+ +
+ src/app/ui/selector/selector.component.ts
+
+
+ OnInit
+
selector | +framadate-selector |
+
styleUrls | +./selector.component.scss |
+
templateUrl | +./selector.component.html |
+
+ Methods+ |
+
+
|
+
+ Inputs+ |
+
+
|
+
+constructor()
+ |
+
+ Defined in src/app/ui/selector/selector.component.ts:22
+ |
+
+ + selectorConfig + | +|
+ Type : SelectorConfig
+
+ |
+ |
+ Defined in src/app/ui/selector/selector.component.ts:22
+ |
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ Defined in src/app/ui/selector/selector.component.ts:27
+ |
+
+
+
+ Returns :
+ void
+
+ |
+
import {Component, Input, OnInit} from '@angular/core';
+
+interface Item {
+ label: string,
+ value: string
+}
+
+interface SelectorConfig {
+ label: string,
+ name: string,
+ id: string,
+ selectorNgModel: any,
+ listItems: Item[]
+}
+
+@Component({
+ selector: 'framadate-selector',
+ templateUrl: './selector.component.html',
+ styleUrls: ['./selector.component.scss']
+})
+export class SelectorComponent implements OnInit {
+ @Input() selectorConfig: SelectorConfig;
+
+ constructor() {
+ }
+
+ ngOnInit() {
+ }
+
+}
+
+ <select
+ class="custom-selector"
+ name="{{selectorConfig.name}}"
+ id="{{selectorConfig.id}}"
+ [(ngModel)]="selectorConfig.selectorNgModel">
+ <option value="{{item.value}}" selected *ngFor="let item of selectorConfig.listItems">
+ {{item.label}}
+ </option>
+</select>
+
+
+ ./selector.component.scss
+
+ +
+ src/app/pages/visibility/visibility.component.ts
+
+
+ BaseComponent
+
+
+ OnInit
+
selector | +framadate-visibility |
+
styleUrls | +./visibility.component.scss |
+
templateUrl | +./visibility.component.html |
+
+ Properties+ |
+
+
|
+
+ Methods+ |
+
+
|
+
+constructor(config: ConfigService, utils: PollUtilities)
+ |
+ |||||||||
+ + | +|||||||||
+
+ Parameters :
+
+
|
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
+ + + + submitCreationAndGoToEnd + + + + | +
+submitCreationAndGoToEnd()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
+ + + + checkValidity + + + + | +
+checkValidity()
+ |
+
+ Inherited from
+ BaseComponent
+ |
+
+ Defined in
+ BaseComponent:21
+ |
+
+
+
+ Returns :
+ boolean
+
+ |
+
+ + + + displayErrorMessage + + + + | +
+displayErrorMessage()
+ |
+
+ Inherited from
+ BaseComponent
+ |
+
+ Defined in
+ BaseComponent:27
+ |
+
+
+
+ Returns :
+ boolean
+
+ |
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ Inherited from
+ BaseComponent
+ |
+
+ Defined in
+ BaseComponent:17
+ |
+
+
+
+ Returns :
+ void
+
+ |
+
+ + + + baseUrl + + + | +
+ Default value : environment.baseApiHref
+ |
+
+ + | +
+ + + + Public + config + + + | +
+ Type : ConfigService
+
+ |
+
+ + | +
+ + + + environment + + + | +
+ Default value : environment
+ |
+
+ + | +
+ + + + showCustomPassword + + + | +
+ Default value : false
+ |
+
+ + | +
+ + + + Public + utils + + + | +
+ Type : PollUtilities
+
+ |
+
+ + | +
+ + + + Public + config + + + | +
+ Type : ConfigService
+
+ |
+
+ Inherited from
+ BaseComponent
+ |
+
+ Defined in
+ BaseComponent:14
+ |
+
import {Component, OnInit} from '@angular/core';
+import {BaseComponent} from '../base-page/base.component';
+import {ConfigService} from '../../services/config.service';
+import {environment} from "../../../environments/environment";
+import {PollUtilities} from "../../config/PollUtilities";
+
+@Component({
+ selector: 'framadate-visibility',
+ templateUrl: './visibility.component.html',
+ styleUrls: ['./visibility.component.scss']
+})
+export class VisibilityComponent extends BaseComponent implements OnInit {
+ showCustomPassword = false;
+ baseUrl = environment.baseApiHref;
+ environment = environment;
+
+ constructor(public config: ConfigService,
+ public utils: PollUtilities) {
+ super(config);
+ }
+
+ ngOnInit() {
+ this.config.customUrl = this.utils.makeSlug(this.config);
+ this.config.expirationDate = (this.config.addDaysToDate(this.config.expiracyDateDefaultInDays, new Date())).toISOString().substring(0, 10);
+ }
+
+ submitCreationAndGoToEnd() {
+ this.config.createPoll();
+ }
+
+}
+
+ <h1 i18n >
+ {{"visibility.top_txt"|translate}}
+</h1 >
+<section class="answers" >
+ <h2 >
+ {{"visibility.title"|translate}}
+ </h2 >
+ <span >
+ {{"visibility.visibility_want"|translate}}
+ </span >
+ <select
+ name="visible_people"
+ id="visible_people"
+ [(ngModel)]="config.visibility"
+ >
+ <option value="link_only" >
+ {{"visibility.visibility_link"|translate}}
+ </option >
+ <option value="only_me" >
+ {{"visibility.visibility_nobody"|translate}}
+ </option >
+ </select >
+ <span >
+ {{"visibility.visibility_see"|translate}}
+ </span >
+
+</section >
+
+<section class="possible_votes" >
+ <h2 >
+ {{"visibility.votes"|translate}}
+ </h2 >
+ <label for="votes" >
+ {{"visibility.votes_possible"|translate}}
+ </label >
+ <select
+ name="votes"
+ id="votes"
+ [(ngModel)]="config.voteChoices"
+ >
+ <option value="only_yes" >
+ {{"visibility.votes_possible_single"|translate}}
+ </option >
+ <option value="normal" >
+ {{"visibility.votes_possible_normal"|translate}}
+ </option >
+ <option value="full" >
+ {{"visibility.votes_possible_full"|translate}}
+ </option >
+ </select >
+</section >
+<section class="expiracy" >
+ <label for="expirationDate" >
+ {{"visibility.archiving"|translate}}
+ </label >
+
+ <input
+ type="date"
+ id="expirationDate"
+ [(ngModel)]="config.expirationDate" >
+ <div class="modification" >
+ <label for="modificationAbility" >
+ {{"visibility.archiving_start"|translate}}
+ </label >
+ <select
+ name="modificationAbility"
+ id="modificationAbility"
+ [(ngModel)]="config.canModifyAnswers" >
+ <option value=true >
+ {{"visibility.archiving_can"|translate}}
+ </option >
+ <option value=false >
+ {{"visibility.archiving_can_not"|translate}}
+ </option >
+ </select >
+ <label for="modificationScope" >
+ {{"visibility.archiving_end"|translate}}
+ </label >
+
+ <span *ngIf="false == !!config.canModifyAnswers" >
+ {{"visibility.modfiy_their"|translate}}
+ </span >
+ <select
+ name="modificationScope"
+ id="modificationScope"
+ *ngIf="true == !!config.canModifyAnswers"
+ [(ngModel)]="config.whoModifiesAnswers"
+ [disabled]="false == !!config.canModifyAnswers" >
+ <option value="self" >
+ {{"visibility.modfiy_their"|translate}}
+ </option >
+ <option value="everybody" >
+ {{"visibility.modfiy_everyone"|translate}}
+ </option >
+ </select >
+
+ </div >
+
+</section >
+<section class="access" >
+ <h2 class='title' >
+ {{"visibility.access"|translate}}
+ </h2 >
+ <label for="url" >
+ {{"visibility.access_url"|translate}}
+ <br >
+ {{"visibility.access_url_key"|translate}}
+ </label >
+ <br >
+ <input
+ type="name"
+ class="input-lg"
+ name="url"
+ id="url"
+ [(ngModel)]="config.customUrl" >
+
+ <sub class="instructions" >
+ {{"visibility.access_instructions"|translate}}
+ </sub >
+ <div class="preview-url" >
+ <a [href]="'/#/vote/poll/slug/' + config.customUrl" >
+ {{environment.baseHref + '#/vote/poll/slug/' + config.customUrl}}
+ </a >
+ <framadate-copy-text [textToCopy]='config.urlPublic' ></framadate-copy-text >
+ </div >
+ <br >
+ <label for="passwordAccess" >
+ {{"visibility.access_want"|translate}}
+ </label >
+ <select
+ name="passwordAccess"
+ id="passwordAccess"
+ [(ngModel)]="config.passwordAccess" >
+ <option value="0" > {{"visibility.access_want_no"|translate}}</option >
+ <option value="1" > {{"visibility.access_want_yes"|translate}}</option >
+ </select >
+
+ <label for="password" >
+ {{"visibility.access_protect"|translate}}
+ </label >
+
+ <div
+ class="enablepassword"
+ *ngIf="config.passwordAccess == 1" >
+ <input
+ type="password"
+ name="password"
+ id="password"
+ min="8"
+ *ngIf="!showCustomPassword"
+ [(ngModel)]="config.password" >
+ <input
+ type="name"
+ name="password_visible"
+ id="password_visible"
+ min="8"
+ *ngIf="showCustomPassword"
+ [(ngModel)]="config.password" >
+ <button
+ (click)="showCustomPassword = !showCustomPassword"
+ [disabled]="!config.password"
+ class="btn btn--default" >
+ <i class="fa fa-eye" ></i >
+ {{"visibility.see_pass"|translate}}
+ </button >
+ </div >
+</section >
+
+<button
+ (click)='submitCreationAndGoToEnd()'
+ class="btn btn--primary btn--full"
+ i18n="@@confirm" >
+ {{"visibility.validate_btn"|translate}}
+ <i
+ class='fa fa-paper-plane'
+ *ngIf="!config.loading" ></i >
+ <span
+ class="loading"
+ *ngIf="config.loading" >
+ <i class="fa fa-refresh fa-spin fa-fw" ></i >
+ </span >
+</button >
+
+<div class="back" >
+
+ <a
+ [routerLink]="'/step/answers'"
+ class="prev" >
+ Retour
+ </a >
+</div >
+
+
+ ./visibility.component.scss
+
:host {
+ h2 {
+ margin-top: 1em;
+ }
+}
+
+ +
+ src/app/pages/voting/voting-choice/voting-choice.component.ts
+
+
+
each vote choice takes a configuration from the container of all choices. +this component is used to select a date choice, or a name answer
+ + + + + + +selector | +framadate-voting-choice |
+
styleUrls | +./voting-choice.component.scss |
+
templateUrl | +./voting-choice.component.html |
+
+ Properties+ |
+
+
|
+
+ Methods+ |
+
+
|
+
+ Inputs+ |
+
+
|
+
+constructor(el: ElementRef, config: ConfigService)
+ |
+ |||||||||
+ + | +|||||||||
+
+ Parameters :
+
+
|
+
+ + choice + | +|
+ Type : any
+
+ |
+ |
+ + | +
+ + choice_id + | +|
+ Type : any
+
+ |
+ |
+ + | +
+ + choices_count + | +|
+ Type : any
+
+ |
+ |
+ + | +
+ + poll + | +|
+ Type : any
+
+ |
+ |
+ + | +
+ + pollIsSpecialDate + | +|
+ Type : boolean
+
+ |
+ |
+ Default value : false
+ |
+ |
+ + | +
+ + simpleAnswer + | +|
+ Type : boolean
+
+ |
+ |
+ Default value : true
+ |
+ |
+ + | +
+ + + + setAnswserTo + + + + | +||||||
+setAnswserTo(newAnswer: "yes" | "no" | "maybe" | null)
+ |
+ ||||||
+ + | +||||||
+
+
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + Public + showChangeChoicebutton + + + | +
+ Default value : false
+ |
+
+ + | +
import {Component, ElementRef, Input} from '@angular/core';
+import {ConfigService} from "../../../services/config.service";
+
+interface VoteChoice {
+ votes?: {
+ yes: number
+ no: number
+ maybe: number
+ notAnswered: number
+ };
+ name?: string;
+ date?: Date;
+ answer: 'yes' | 'no' | 'maybe' | null;
+ simpleAnswer?: boolean
+ false; // enable if we display only a togglable "yes"
+}
+
+/**
+ * each vote choice takes a configuration from the container of all choices.
+ * this component is used to select a date choice, or a name answer
+ */
+@Component({
+ selector: 'framadate-voting-choice',
+ templateUrl: './voting-choice.component.html',
+ styleUrls: ['./voting-choice.component.scss']
+})
+export class VotingChoiceComponent {
+
+ public showChangeChoicebutton = false;
+ @Input() public choice: any;
+ @Input() public choices_count: any;
+ @Input() public choice_id: any;
+ @Input() public poll: any;
+ @Input() public simpleAnswer: boolean = true;
+ @Input() public pollIsSpecialDate: boolean = false;
+
+ constructor(private el: ElementRef,
+ private config: ConfigService) {
+
+ if (this.poll && this.poll.allowedAnswers) {
+ this.simpleAnswer = this.poll.allowedAnswers.length == 1
+ }
+ }
+
+ setAnswserTo(newAnswer: 'yes' | 'no' | 'maybe' | null) {
+ if (this.simpleAnswer) {
+ // only toggle yes to no
+ if (this.choice.answer && this.choice.answer === 'yes') {
+ this.choice.answer = 'no';
+ this.config.myTempVoteStack--;
+ } else {
+ this.choice.answer = newAnswer;
+ this.config.myTempVoteStack++;
+ }
+
+ } else {
+ this.choice.answer = newAnswer;
+ if (this.choice.answer !== newAnswer) {
+ if (newAnswer == 'maybe' || newAnswer == 'yes') {
+ this.config.myTempVoteStack++;
+ }
+ } else {
+ console.info('same answer as before')
+ }
+
+ }
+ this.el.nativeElement.blur();
+ }
+
+}
+
+ <div class="choicebox selection-{{choice.answer}}" >
+ <!-- add .choicebox--active to most voted -->
+ <button
+ *ngIf='showChangeChoicebutton'
+ class='btn btn--primary'
+ (click)=' choice.simpleAnswer = !choice.simpleAnswer' >
+ <i class='fa fa-gears' ></i >
+ </button >
+
+ <div class="choicebox__subject" >
+ <div class='columns' >
+ <div class='column' >
+ <div
+ class="text title clickable"
+ (click)="setAnswserTo('yes')" >
+ {{choice.text}}
+ </div > <!-- TEXT CASE --><!--
+ <p class="choicebox__txt">
+ Lorem ipsum dolor sit amet consectetur, adipisicing elit. Nulla nobis nam culpa !
+ </p>
+ --><!-- TEXT CASE -->
+
+ <!-- IMG CASE -->
+ <img
+ *ngIf='choice.url'
+ class="choicebox__img"
+ [src]="choice.url"
+ alt="{{choice.url}}"
+ >
+ <!-- IMG CASE -->
+
+ <!-- DATE CASE -->
+ <div
+ class="dates"
+ *ngIf="pollIsSpecialDate" >
+ <div class="choicebox__date" >
+ {{choice.date.date | date:'EEE'}} <span
+ class="choicebox__day" >{{choice.date.date | date:'dd'}}</span > {{choice.date.date | date:'LLL'}}
+ </div >
+ <div class="choicebox__hour" >
+ {{choice.date.date | date:'H:m'}}
+ </div >
+ </div >
+ <!-- DATE CASE -->
+ </div >
+ <div class='column' >
+ <div class="choicebox__actions" >
+ <!-- show only the yes check if the config is set to simpleAnswer -->
+ <!-- add .choicebox__btn--active to selected <button> -->
+ <span class="simple-answer" >
+
+ <button
+ class="choicebox__btn choicebox__btn--yes"
+ type="button"
+ [ngClass]="{'choicebox__btn--active': choice.answer === 'yes'}"
+ (click)="setAnswserTo('yes')" >
+ <img
+ src="../../../assets/img/check.svg"
+ alt="" >
+ </button >
+ </span >
+ <span
+ class="complex-answers"
+ *ngIf="!simpleAnswer" >
+ <button
+ class="choicebox__btn choicebox__btn--maybe"
+ type="button"
+ [ngClass]="{'choicebox__btn--active': choice.answer === 'maybe'}"
+ (click)="setAnswserTo('maybe')" >
+ <img
+ src="../../../assets/img/check-2.svg"
+ alt="" >
+ </button >
+ <button
+ class="choicebox__btn choicebox__btn--no"
+ type="button"
+ [ngClass]="{'choicebox__btn--active': choice.answer === 'no'}"
+ (click)="setAnswserTo('no')"
+ >
+ <img
+ src="../../../assets/img/croix.svg"
+ alt="" >
+ </button >
+ </span >
+
+ </div >
+ </div >
+ <div class='column' >
+ <div class="choicebox__count" >
+
+ <div
+ class='no-votes'
+ *ngIf='! poll.choices_count.counts[choice.id] ' >
+ aucun vote
+ </div >
+ <button
+ type="button"
+ aria-describedby="choicebox-tooltip"
+ class="choicebox__votes"
+ *ngIf="poll.choices_count && choice && poll.choices_count.counts[choice.id]" >
+ <div class="choicebox__vote" >
+ {{poll.choices_count.counts[choice.id].yes.count}}
+ <img
+ width="20px"
+ height="21px"
+ src="../../../assets/img/votant-sur.svg"
+ alt="" >
+ </div >
+ <div class="choicebox__vote" >
+ {{poll.choices_count.counts[choice.id].maybe.count}}
+ <img
+ width="22px"
+ height="24px"
+ src="../../../assets/img/votant-pas-sur.svg"
+ alt="" >
+ </div >
+ <div
+ class="choicebox__tooltip"
+ id="choicebox-tooltip" >
+ <div class="choicebox__tooltiplist" >
+ <div class="choicebox__tooltipttl" >
+ <img
+ width="20px"
+ height="21px"
+ src="../../../assets/img/votant-sur.svg"
+ alt="" >
+ {{poll.choices_count.counts[choice.id].yes.count}} "Oui"
+ </div >
+ <!-- liste des gens qui ont répondu oui-->
+ <ul >
+ <li *ngFor='let pseudo of choices_count.counts[choice.id].yes.people ' >{{pseudo}}</li >
+ </ul >
+ </div >
+ <div
+ class="choicebox__tooltiplist"
+ *ngIf='!simpleAnswer' >
+ <div class="choicebox__tooltipttl" >
+ <img
+ width="22px"
+ height="24px"
+ src="../../../assets/img/votant-pas-sur.svg"
+ alt="" >
+ {{poll.choices_count.counts[choice.id].maybe.count}} "Peut-être"
+ </div >
+ <ul >
+ <li *ngFor='let pseudo of choices_count.counts[choice.id].maybe.people ' >{{pseudo}}</li >
+ </ul >
+ </div >
+ <div
+ class="choicebox__tooltiplist"
+ *ngIf='!simpleAnswer' >
+ <div class="choicebox__tooltipttl" >
+ <i class='fa fa-times' ></i >
+ {{poll.choices_count.counts[choice.id].no.count}} "Non"
+ </div >
+ <ul >
+ <li *ngFor='let pseudo of choices_count.counts[choice.id].no.people ' >{{pseudo}}</li >
+ </ul >
+ </div >
+ </div >
+ </button >
+ <div class="choicebox__countxt" >
+ Choix ayant reçu le plus de votes
+ </div >
+ </div >
+ </div >
+ </div >
+
+ </div >
+
+</div >
+
+
+ ./voting-choice.component.scss
+
// ---------------------------------------------------------
+// -- VOTE CHOICE COMPONENT
+// ---------------------------------------------------------
+
+// -- IMPORTS
+// ----------------------------
+
+@import "../../../../assets/scss/variables";
+
+
+// -- VARIABLES
+// ----------------------------
+
+$box-padding: 2rem;
+$box-border-width: .6rem;
+$btn-size: 5rem;
+$btn-margin-x: 1rem;
+$btn-margin-y: 1.5rem;
+$btn-wrap-size: calc(2 * #{$btn-size} + 4 * #{$btn-margin-x});
+$img-maxheight: 12rem;
+$breakpoint-responsive: 640px; // à définir
+
+
+// -- GLOBAL
+// ----------------------------
+
+.choicebox {
+ position: relative;
+ min-width: 32rem;
+ min-height: 16rem;
+ display: block;
+ padding: $box-padding $box-padding $box-padding calc(#{$box-padding} - #{$box-border-width});
+ border-left: $box-border-width solid transparent;
+ background-color: $white;
+ box-shadow: 0 0 .6rem 0 rgba($black, .2);
+
+ &--active {
+ padding-left: $box-padding;
+ border-left-color: $primary_color;
+ }
+
+ &.selection-yes {
+ font-weight: 700;
+ background: #e9bdeb;
+ }
+}
+
+.choicebox__subject {
+ margin-bottom: 3rem;
+ padding-right: $btn-wrap-size;
+ @media (min-width: $breakpoint-responsive) {
+ margin-bottom: 0;
+ padding-right: 0;
+ }
+}
+
+
+// -- DATE
+// ----------------------------
+
+.choicebox__date {
+ font-size: 1.8rem;
+ margin-bottom: .5rem;
+ white-space: nowrap;
+ text-transform: capitalize;
+
+ @media (min-width: $breakpoint-responsive) {
+ margin-bottom: 0;
+ }
+}
+
+.choicebox__day {
+ font-size: 2.4rem;
+ font-weight: bold;
+}
+
+
+// -- IMG
+// ----------------------------
+
+.choicebox__img {
+ max-width: 100%;
+ max-height: $img-maxheight;
+}
+
+
+// -- TXT
+// ----------------------------
+
+.choicebox__txt {
+ margin: 0;
+ font-size: 1.8rem;
+ min-width: 10em;
+}
+
+
+// -- VOTE BTNS
+// ----------------------------
+
+.choicebox__actions {
+ position: absolute;
+ z-index: 1;
+
+ max-width: $btn-wrap-size;
+ top: 50%;
+ right: $box-padding;
+
+ @media (min-width: $breakpoint-responsive) {
+ position: static;
+ max-width: none;
+ transform: none;
+ margin: 0 1.5rem;
+ }
+}
+
+.choicebox__btn {
+ width: $btn-size;
+ height: $btn-size;
+ align-items: center;
+ justify-content: center;
+ margin: $btn-margin-y $btn-margin-x;
+ border: .3rem solid #ccc9c9;
+ background-color: transparent;
+ border-radius: 50%;
+ cursor: pointer;
+ float: left;
+
+ &--maybe {
+ position: relative;
+ top: calc((#{$btn-size} + 2 * #{$btn-margin-y}) / 2);
+ @media (min-width: $breakpoint-responsive) {
+ top: auto;
+ left: auto;
+ }
+ }
+
+ &--active {
+ border-color: #bf83c2;
+ }
+
+ @media (min-width: $breakpoint-responsive) {
+ margin-top: 0;
+ margin-bottom: 0;
+ }
+
+}
+
+
+// -- VOTE COUNT
+// ----------------------------
+
+.choicebox__count {
+ position: relative;
+ padding-right: $btn-wrap-size;
+ @media (min-width: $breakpoint-responsive) {
+ text-align: right;
+ padding-right: 0;
+ }
+}
+
+.choicebox__votes {
+ border: 0;
+ padding: 0;
+ line-height: normal;
+ background-color: transparent;
+ @media (min-width: $breakpoint-responsive) {
+ padding: 1.5rem;
+ }
+}
+
+.choicebox__vote {
+ display: inline-block;
+ vertical-align: middle;
+
+ & + .choicebox__vote {
+ margin-left: 1.5rem;
+ }
+}
+
+.choicebox__countxt {
+ display: none;
+ margin-top: .5rem;
+
+ .choicebox--active & {
+ display: block;
+ @media (min-width: $breakpoint-responsive) {
+ display: none;
+ }
+ }
+}
+
+
+// -- TOOLTIP
+// ----------------------------
+
+.choicebox__tooltip {
+ display: none;
+ @media (min-width: $breakpoint-responsive) {
+ position: absolute;
+ min-width: 18rem;
+ font-weight: normal;
+ top: 5rem;
+ left: 50%;
+ z-index: 1;
+ padding: 2rem;
+ border: .1rem solid rgba($black, .1);
+ background-color: $white;
+ text-align: left;
+ transform: translateX(-50%);
+ &::after,
+ &::before {
+ position: absolute;
+ width: 0;
+ height: 0;
+ bottom: 100%;
+ left: 50%;
+ content: " ";
+ pointer-events: none;
+ border: solid transparent;
+ }
+ &::after {
+ margin-left: -1.5rem;
+ border-width: 1.5rem;
+ border-color: rgba($white, 0);
+ border-bottom-color: #fff;
+ }
+ &::before {
+ margin-left: -1.6rem;
+ border-width: 1.6rem;
+ border-color: rgba($black, 0);
+ border-bottom-color: rgba($black, .1);
+ }
+ ul {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ }
+ }
+}
+
+.choicebox__tooltiplist {
+ & + .choicebox__tooltiplist {
+ padding-left: 3rem;
+ }
+
+ ul {
+ max-height: 11rem;
+ overflow: auto;
+ }
+}
+
+.choicebox__tooltipttl {
+ @media (min-width: $breakpoint-responsive) {
+ margin-bottom: 1rem;
+ font-size: 1.6rem;
+ font-weight: bold;
+ white-space: nowrap;
+ img {
+ margin-right: .5rem;
+ vertical-align: sub;
+ }
+ & ~ .choicebox__tooltipttl {
+ margin-top: 3rem;
+ }
+ }
+}
+
+ +
+ src/app/pages/voting/voting-comment/voting-comment.component.ts
+
+
+ OnInit
+
selector | +framadate-voting-comment |
+
styleUrls | +./voting-comment.component.scss |
+
templateUrl | +./voting-comment.component.html |
+
+ Methods+ |
+
+
|
+
+ Inputs+ |
+
+
|
+
+constructor()
+ |
+
+ + | +
+ + comment + | +|
+ Default value : mockComments[0]
+ |
+ |
+ + | +
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
import {Component, Input, OnInit} from '@angular/core';
+import {mockComments} from "../../../config/mocks/mock-comments";
+
+@Component({
+ selector: 'framadate-voting-comment',
+ templateUrl: './voting-comment.component.html',
+ styleUrls: ['./voting-comment.component.scss']
+})
+export class VotingCommentComponent implements OnInit {
+
+ @Input() comment = mockComments[0];
+
+ constructor() {
+ }
+
+ ngOnInit() {
+ }
+
+}
+
+ <div class="comment" >
+ <span class="cname" >
+ {{comment.pseudo}}
+ </span >, le
+ <span class="date padding-btm-x1" >
+ {{comment.date.date }}
+ </span >
+ <blockquote >
+ <p class="text" >
+ {{comment.text}}
+ </p >
+ </blockquote >
+</div >
+
+
+ ./voting-comment.component.scss
+
+ +
+ src/app/pages/voting/voting.component.ts
+
+
+ OnInit
+
selector | +framadate-voting |
+
styleUrls | +./voting.component.scss |
+
templateUrl | +./voting.component.html |
+
+ Properties+ |
+
+
|
+
+ Methods+ |
+
+
|
+
+constructor()
+ |
+
+ Defined in src/app/pages/voting/voting.component.ts:9
+ |
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ Defined in src/app/pages/voting/voting.component.ts:13
+ |
+
+
+
+ Returns :
+ void
+
+ |
+
+ + + + show_mask + + + | +
+ Default value : true
+ |
+
+ Defined in src/app/pages/voting/voting.component.ts:9
+ |
+
import { Component, OnInit } from '@angular/core';
+
+@Component({
+ selector: 'framadate-voting',
+ templateUrl: './voting.component.html',
+ styleUrls: ['./voting.component.scss']
+})
+export class VotingComponent implements OnInit {
+ show_mask=true;
+ constructor() {
+ }
+
+ ngOnInit() {
+ }
+
+}
+
+ <form>
+
+
+ <a class="next">Voir le graphique</a>
+
+ <h2 class="margin-top-x4">Les commentaires</h2>
+ <p class="comment">
+ <span class="cname">Pikachu </span>
+ <span class="date date padding-btm-x1"> le 26 novembre 2019</span>
+ Rock Luxio Surskit. Glacier Badge
+ </p>
+
+
+
+
+</form>
+<div ng-show="show_popup" class="popup">
+ <h4 class="margin-btm-x2">Participation validée !</h4>
+ <p class="margin-btm-x1">Votre vote a bien été pris en compte, mais faites attention : ce sondage n'autorise
+ l'édition de votre vote qu'avec le lien personnalisé suivant :</p>
+ <p>https://framadate.org/urladmindusondage</p>
+ <button class="btn btn--primary btn--small btn--purple btn--black-text">Copier le lien</button>
+ <p class="margin-btm-x6 margin-top-x2">Conservez-le précieusement !</p>
+ <button class="btn btn--primary btn--default btn--purple btn--black-text">Revenir au sondage</button>
+
+
+</div>
+<div class="rgba-black" [hidden]="show_mask">
+
+</div>
+
+
+ ./voting.component.scss
+
+ +
+ src/app/pages/voting/voting-graph/voting-graph.component.ts
+
+
+ BaseComponent
+
+
+ OnInit
+
selector | +framadate-voting-graph |
+
styleUrls | +./voting-graph.component.scss |
+
templateUrl | +./voting-graph.component.html |
+
+ Properties+ |
+
+ + | +
+ Methods+ |
+
+
|
+
+constructor(config: ConfigService)
+ |
+ ||||||
+ + | +||||||
+
+ Parameters :
+
+
|
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
+ + + + checkValidity + + + + | +
+checkValidity()
+ |
+
+ Inherited from
+ BaseComponent
+ |
+
+ Defined in
+ BaseComponent:21
+ |
+
+
+
+ Returns :
+ boolean
+
+ |
+
+ + + + displayErrorMessage + + + + | +
+displayErrorMessage()
+ |
+
+ Inherited from
+ BaseComponent
+ |
+
+ Defined in
+ BaseComponent:27
+ |
+
+
+
+ Returns :
+ boolean
+
+ |
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ Inherited from
+ BaseComponent
+ |
+
+ Defined in
+ BaseComponent:17
+ |
+
+
+
+ Returns :
+ void
+
+ |
+
+ + + + Public + config + + + | +
+ Type : ConfigService
+
+ |
+
+ + | +
+ + + + Public + config + + + | +
+ Type : ConfigService
+
+ |
+
+ Inherited from
+ BaseComponent
+ |
+
+ Defined in
+ BaseComponent:14
+ |
+
import { Component, OnInit } from '@angular/core';
+import {BaseComponent} from "../../base-page/base.component";
+import {ConfigService} from "../../../services/config.service";
+
+@Component({
+ selector: 'framadate-voting-graph',
+ templateUrl: './voting-graph.component.html',
+ styleUrls: ['./voting-graph.component.scss']
+})
+export class VotingGraphComponent extends BaseComponent implements OnInit {
+
+ constructor(public config: ConfigService) {
+ super(config);
+ }
+
+ ngOnInit() {
+ }
+
+}
+
+ <h1>Graphique</h1>
+<framadate-poll-graphic></framadate-poll-graphic>
+
+
+ ./voting-graph.component.scss
+
+ +
+ src/app/pages/voting/voting-navigation/voting-navigation.component.ts
+
selector | +framadate-voting-navigation |
+
styleUrls | +./voting-navigation.component.scss |
+
templateUrl | +./voting-navigation.component.html |
+
+ Properties+ |
+
+
|
+
+constructor(config: ConfigService)
+ |
+ ||||||
+ + | +||||||
+
+ Parameters :
+
+
|
+
+ + + + Public + config + + + | +
+ Type : ConfigService
+
+ |
+
+ + | +
import {Component} from '@angular/core';
+import {ConfigService} from "../../../services/config.service";
+
+@Component({
+ selector: 'framadate-voting-navigation',
+ templateUrl: './voting-navigation.component.html',
+ styleUrls: ['./voting-navigation.component.scss']
+})
+export class VotingNavigationComponent {
+
+ constructor(public config: ConfigService) {
+ }
+
+}
+
+ <nav id='navigation'>
+
+ <nav class='sections-nav' >
+ <ul >
+ <li >
+ <a href='#title' >
+ {{config.currentPoll.poll.title}}
+ </a >
+ </li >
+ <li >
+ <a href='#table' >
+ {{config.currentPoll.stacks.length}}
+ votes
+ </a >
+ </li >
+ <li >
+ <a href='#graph' >
+ Graphique
+ </a >
+ </li >
+ <li >
+ <a href='#comments' >
+ <i class='fa fa-comments' ></i >
+ <span
+ *ngIf='config.currentPoll && config.currentPoll.comments'
+ class='comments-count' >
+ {{config.currentPoll.comments.length}}
+ </span >
+ commentaires
+ </a >
+ </li >
+ </ul >
+ </nav >
+</nav>
+
+
+ ./voting-navigation.component.scss
+
+ +
+ src/app/pages/voting/voting-summary/voting-summary.component.ts
+
+
+ OnInit
+
selector | +framadate-voting-summary |
+
styleUrls | +./voting-summary.component.scss |
+
templateUrl | +./voting-summary.component.html |
+
+ Properties+ |
+
+
|
+
+ Methods+ |
+
+
|
+
+ Inputs+ |
+
+
|
+
+constructor(config: ConfigService)
+ |
+ ||||||
+ + | +||||||
+
+ Parameters :
+
+
|
+
+ + pollconfig + | +|
+ Default value : mockPoll3
+ |
+ |
+ + | +
+ + + + computePreferred + + + + | +
+computePreferred()
+ |
+
+ + | +
+ find the most "yes" +
+ Returns :
+ void
+
+ |
+
+ + + + getKeys + + + + | +||||
+getKeys(obj)
+ |
+ ||||
+ + | +||||
+
+
+ Parameters :
+
+
+
+
+ Returns :
+ any
+
+
+
+
+ |
+
+ + + + ngOnInit + + + + | +
+ngOnInit()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
+ + + + Public + config + + + | +
+ Type : ConfigService
+
+ |
+
+ + | +
+ + + + preferred + + + | +
+ Type : string
+
+ |
+
+ Default value : 'rien'
+ |
+
+ + | +
+ + + + severalPreferred + + + | +
+ Type : boolean
+
+ |
+
+ Default value : false
+ |
+
+ + | +
import {Component, Input, OnInit} from '@angular/core';
+import {ConfigService} from "../../../services/config.service";
+import {mockPoll3} from "../../../config/mocks/mock-poll3";
+
+@Component({
+ selector: 'framadate-voting-summary',
+ templateUrl: './voting-summary.component.html',
+ styleUrls: ['./voting-summary.component.scss']
+})
+export class VotingSummaryComponent implements OnInit {
+
+ preferred: string = 'rien';
+ severalPreferred: boolean = false;
+
+ @Input() pollconfig = mockPoll3;
+
+ constructor(public config: ConfigService) {
+
+ }
+
+ ngOnInit() {
+ this.computePreferred();
+ console.log(' this.pollconfig.choices', this.pollconfig.choices)
+ console.log(' this.pollconfig.choices_count', this.pollconfig.choices_count.counts)
+ console.log('this.pollconfig.choices_count.counts[10]', this.pollconfig.choices_count.counts[10])
+ console.log('this.pollconfig.choices[2].id', this.pollconfig.choices[2].id)
+ console.log('this.pollconfig.choices_count.counts[]', this.pollconfig.choices_count.counts[this.pollconfig.choices[2].id].score)
+ }
+
+ getKeys(obj) {
+ return Object.keys(obj)
+ }
+
+ /**
+ * find the most "yes"
+ */
+ computePreferred() {
+ let keys = Object.keys(this.pollconfig.choices_count.counts);
+ this.preferred = '';
+ this.severalPreferred = false;
+ let maxScore = this.pollconfig.choices_count.maxScore;
+
+ keys.forEach(item => {
+ if (maxScore === this.pollconfig.choices_count.counts[item].score) {
+ if (this.preferred.length) {
+ this.preferred += ', '
+ this.severalPreferred = true;
+ }
+ // find the favourite
+ this.preferred += this.pollconfig.choices_count.counts[item].choice_text;
+ }
+
+ });
+
+ }
+
+
+}
+
+ <h2 >Résumé</h2 >
+<div class='heading' >
+ <div class='col-xs-6' >
+ <h1 id='title' >{{config.currentPoll.poll.title}}</h1 >
+ <p >{{config.currentPoll.poll.description}}</p >
+ <span class="creationDate" >
+ Créé le {{config.currentPoll.poll.creationDate.date}}
+ </span >
+ <span class="expiracyDate" >
+ Expire le {{config.currentPoll.poll.expiracyDate.date}}
+ </span >
+ <div class="votants" >
+ <i class='fa fa-users' ></i >
+ {{config.currentPoll.stacks.length}} votants,
+ {{config.currentPoll.choices.length}} choix,
+ </div >
+ </div >
+</div >
+<div class="preferred" >
+ <i class='fa fa-star' ></i >
+ Pour l'instant,
+ <span *ngIf='severalPreferred' >
+ les
+ </span > <span *ngIf='!severalPreferred' >
+ le
+ </span >
+ choix ayant reçu le plus grand nombre de votes ( {{config.currentPoll.choices_count.maxScore}} points )
+ <span *ngIf='severalPreferred' >
+ sont à égalité
+ </span > <span *ngIf='!severalPreferred' >
+ est
+ </span >
+ :
+ <span class='preferred-result' >
+ {{preferred}}
+ </span >
+</div >
+<table class='table is-striped is-bordered is-hoverable' >
+ <thead >
+ <tr *ngIf='config.currentPoll.choices && config.currentPoll.choices_count' >
+ <td >
+ Pseudo
+ </td >
+ <td
+ *ngFor='let choice of config.currentPoll.choices'
+
+ >
+ {{choice.text}}
+ </td >
+ </tr >
+ <!-- somme des points, dont un demi point pour les "peut être" -->
+
+ </thead >
+ <tbody >
+ <tr
+
+ title='somme des points, dont un demi point pour les "peut être"' >
+ <td >
+ <i class='fa fa-plus-circle' ></i > points
+ </td >
+ <!-- <td-->
+ <!-- *ngFor='let choice of config.currentPoll.choices'-->
+ <!-- [ngClass]='{"has-max-score" : config.currentPoll.choices_count.maxScore === config.currentPoll.choices_count.counts[choice.id].score}' >-->
+ <!-- {{config.currentPoll.choices[choice.id].score}}-->
+ <!-- </td >-->
+ <td
+ *ngFor='let choice of config.currentPoll.choices'
+ >
+ {{pollconfig.choices_count.counts[this.pollconfig.choices[2].id].score}}
+ </td >
+ </tr >
+ <tr class='details' >
+ <td >
+ <i class='fa fa-eye' ></i >
+ </td >
+ <td *ngFor='let choice of config.currentPoll.choices' >
+ id: {{choice.id}}
+ <br >
+ <!-- yes {{config.currentPoll.choices_count.counts[choice.id].yes.count}}-->
+ <!-- <br >-->
+ <!-- maybe-->
+ <!-- {{(config.currentPoll.choices_count.counts[k].yes.maybe ? config.currentPoll.choices_count.counts[k].yes.count * 0.5 : 0)}}-->
+
+ <!-- <br >-->
+ <!-- no {{(config.currentPoll.choices_count.counts[k].yes.maybe ? config.currentPoll.choices_count.counts[k].maybe.count * 0.5 : 0)}}-->
+ <br >
+ <!-- score :-->
+ <!-- {{(config.currentPoll.choices_count.counts[choice.id].score)}}-->
+
+ </td >
+ </tr >
+ <tr
+ class='votes-of-the-person'
+ *ngFor='let voteStack of config.currentPoll.stacks'
+ >
+ <td >
+ <!-- // TODO show modify if this is our own vote-->
+
+ <button
+ (click)='config.loadVoteStack(voteStack)'
+ *ngIf="config.currentPoll.poll.modificationPolicy === 'everybody'"
+ class='btn btn--primary pull-left btn--small'
+ >
+ <i class='fa fa-pencil' ></i >
+ </button >
+
+ {{voteStack.pseudo}}
+ </td >
+ <td *ngFor='let v of getKeys(voteStack.votes)' >
+ <span *ngIf='voteStack.votes[v].value' >
+
+ <img
+ *ngIf="voteStack.votes[v].value == 'yes'"
+ src='../../../../assets/img/votant-sur.svg'
+ alt='yes' >
+ <img
+ *ngIf="voteStack.votes[v].value == 'maybe'"
+ src='../../../../assets/img/votant-pas-sur.svg'
+ alt='yes' >
+ </span >
+ </td >
+ </tr >
+ <!-- bottom line shows each answer details-->
+
+ </tbody >
+</table >
+
+
+ ./voting-summary.component.scss
+
@import "../../../../assets/scss/variables";
+
+.person {
+ font-weight: 700;
+}
+
+.preferred-result {
+ font-weight: 700;
+ font-size: 1.5em;
+}
+
+thead {
+ font-size: 1.25em;
+ font-weight: 700;
+}
+
+tbody {
+ text-align: right;
+}
+
+td {
+ &.has-max-score {
+ background: $primary_color;
+ font-weight: 800;
+ }
+}
+
+ File | +Type | +Identifier | +Statements | +
---|---|---|---|
+ + e2e/src/app.po.ts + | +class | +AppPage | ++ 0 % + (0/3) + | +
+ + src/app/app.component.ts + | +component | +AppComponent | ++ 6 % + (1/15) + | +
+ + src/app/app.module.ts + | +class | +MyMissingTranslationHandler | ++ 0 % + (0/2) + | +
+ + src/app/app.module.ts + | +function | +HttpLoaderFactory | ++ 0 % + (0/1) + | +
+ + src/app/config/DateUtilities.ts + | +injectable | +DateUtilities | ++ 50 % + (3/6) + | +
+ + src/app/config/PollConfig.ts + | +class | +PollConfig | ++ 2 % + (1/40) + | +
+ + src/app/config/PollConfig.ts + | +interface | +DateOption | ++ 0 % + (0/4) + | +
+ + src/app/config/PollConfig.ts + | +variable | +baseConfigValues | ++ 0 % + (0/1) + | +
+ + src/app/config/PollUtilities.ts + | +injectable | +PollUtilities | ++ 75 % + (3/4) + | +
+ + src/app/config/Routes.ts + | +variable | +Routes | ++ 100 % + (1/1) + | +
+ + src/app/config/defaultConfigs.ts + | +interface | +DateChoice | ++ 0 % + (0/4) + | +
+ + src/app/config/defaultConfigs.ts + | +interface | +PollAnswer | ++ 0 % + (0/8) + | +
+ + src/app/config/defaultConfigs.ts + | +interface | +TimeSlices | ++ 0 % + (0/2) + | +
+ + src/app/config/defaultConfigs.ts + | +variable | +basicSlicesOfDay | ++ 0 % + (0/1) + | +
+ + src/app/config/defaultConfigs.ts + | +variable | +currentDay | ++ 0 % + (0/1) + | +
+ + src/app/config/defaultConfigs.ts + | +variable | +currentMonth | ++ 0 % + (0/1) + | +
+ + src/app/config/defaultConfigs.ts + | +variable | +currentYear | ++ 0 % + (0/1) + | +
+ + src/app/config/defaultConfigs.ts + | +variable | +defaultAnswers | ++ 0 % + (0/1) + | +
+ + src/app/config/defaultConfigs.ts + | +variable | +defaultDates | ++ 0 % + (0/1) + | +
+ + src/app/config/defaultConfigs.ts + | +variable | +defaultTimeOfDay | ++ 0 % + (0/1) + | +
+ + src/app/config/defaultConfigs.ts + | +variable | +otherDefaultDates | ++ 0 % + (0/1) + | +
+ + src/app/config/defaultConfigs.ts + | +variable | +otherSlicesOfDay | ++ 0 % + (0/1) + | +
+ + src/app/config/defaultConfigs.ts + | +variable | +otherTimeOfDay | ++ 0 % + (0/1) + | +
+ + src/app/config/graph-canevas-options.ts + | +variable | +graphOptions | ++ 0 % + (0/1) + | +
+ + src/app/config/mocks/choice.ts + | +variable | +mockChoice | ++ 0 % + (0/1) + | +
+ + src/app/config/mocks/mock-comments.ts + | +variable | +mockComments | ++ 0 % + (0/1) + | +
+ + src/app/config/mocks/mock-graph.ts + | +variable | +mockGraphConfig | ++ 0 % + (0/1) + | +
+ + src/app/config/mocks/mock-poll3.ts + | +variable | +mockPoll3 | ++ 0 % + (0/1) + | +
+ + src/app/config/mocks/mock-success-vote.ts + | +variable | +mockSuccessVote | ++ 0 % + (0/1) + | +
+ + src/app/config/mocks/mockmypolls.ts + | +variable | +mockMyPolls | ++ 0 % + (0/1) + | +
+ + src/app/custom-lib/date-value-accessor/date-value-accessor.ts + | +directive | +DateValueAccessor | ++ 12 % + (1/8) + | +
+ + src/app/custom-lib/date-value-accessor/date-value-accessor.ts + | +variable | +DATE_VALUE_ACCESSOR | ++ 0 % + (0/1) + | +
+ + src/app/debugger/debugger.component.ts + | +component | +DebuggerComponent | ++ 0 % + (0/7) + | +
+ + src/app/header/header.component.ts + | +component | +HeaderComponent | ++ 0 % + (0/3) + | +
+ + src/app/pages/admin/admin.component.ts + | +component | +AdminComponent | ++ 0 % + (0/4) + | +
+ + src/app/pages/answers/answers.component.ts + | +component | +AnswersComponent | ++ 0 % + (0/20) + | +
+ + src/app/pages/base-page/base.component.ts + | +component | +BaseComponent | ++ 0 % + (0/6) + | +
+ + src/app/pages/create-or-retrieve/create-or-retrieve.component.ts + | +component | +CreateOrRetrieveComponent | ++ 0 % + (0/11) + | +
+ + src/app/pages/dates/dates.component.ts + | +component | +DatesComponent | ++ 23 % + (5/21) + | +
+ + src/app/pages/end-confirmation/end-confirmation.component.ts + | +component | +EndConfirmationComponent | ++ 0 % + (0/12) + | +
+ + src/app/pages/home/home.component.ts + | +component | +HomeComponent | ++ 0 % + (0/9) + | +
+ + src/app/pages/kind/kind.component.ts + | +component | +KindComponent | ++ 0 % + (0/8) + | +
+ + src/app/pages/password/password.component.ts + | +component | +PasswordComponent | ++ 0 % + (0/8) + | +
+ + src/app/pages/pictures/pictures.component.ts + | +component | +PicturesComponent | ++ 0 % + (0/8) + | +
+ + src/app/pages/poll-display/poll-display.component.ts + | +component | +PollDisplayComponent | ++ 0 % + (0/11) + | +
+ + src/app/pages/resume/resume.component.ts + | +component | +ResumeComponent | ++ 0 % + (0/7) + | +
+ + src/app/pages/visibility/visibility.component.ts + | +component | +VisibilityComponent | ++ 0 % + (0/13) + | +
+ + src/app/pages/voting/choices-list/choices-list.component.ts + | +component | +ChoicesListComponent | ++ 0 % + (0/3) + | +
+ + src/app/pages/voting/comments-list/comments-list.component.ts + | +component | +CommentsListComponent | ++ 0 % + (0/3) + | +
+ + src/app/pages/voting/voting-choice/voting-choice.component.ts + | +component | +VotingChoiceComponent | ++ 10 % + (1/10) + | +
+ + src/app/pages/voting/voting-choice/voting-choice.component.ts + | +interface | +VoteChoice | ++ 0 % + (0/7) + | +
+ + src/app/pages/voting/voting-comment/voting-comment.component.ts + | +component | +VotingCommentComponent | ++ 0 % + (0/4) + | +
+ + src/app/pages/voting/voting-graph/voting-graph.component.ts + | +component | +VotingGraphComponent | ++ 0 % + (0/8) + | +
+ + src/app/pages/voting/voting-navigation/voting-navigation.component.ts + | +component | +VotingNavigationComponent | ++ 0 % + (0/3) + | +
+ + src/app/pages/voting/voting-summary/voting-summary.component.ts + | +component | +VotingSummaryComponent | ++ 11 % + (1/9) + | +
+ + src/app/pages/voting/voting.component.ts + | +component | +VotingComponent | ++ 0 % + (0/4) + | +
+ + src/app/poll-graphic/poll-graphic.component.ts + | +component | +PollGraphicComponent | ++ 0 % + (0/15) + | +
+ + src/app/services/config.service.ts + | +injectable | +ConfigService | ++ 25 % + (18/72) + | +
+ + src/app/services/poll.service.ts + | +injectable | +PollService | ++ 0 % + (0/3) + | +
+ + src/app/ui/copy-text/copy-text.component.ts + | +component | +CopyTextComponent | ++ 0 % + (0/5) + | +
+ + src/app/ui/directives/resettable-input.directive.ts + | +directive | +ResettableInputDirective | ++ 50 % + (1/2) + | +
+ + src/app/ui/erasable-input/erasable-input.component.ts + | +component | +ErasableInputComponent | ++ 0 % + (0/6) + | +
+ + src/app/ui/navigation/navigation.component.ts + | +component | +NavigationComponent | ++ 0 % + (0/11) + | +
+ + src/app/ui/selector/selector.component.ts + | +component | +SelectorComponent | ++ 0 % + (0/4) + | +
+ + src/app/ui/selector/selector.component.ts + | +interface | +Item | ++ 0 % + (0/3) + | +
+ + src/app/ui/selector/selector.component.ts + | +interface | +SelectorConfig | ++ 0 % + (0/6) + | +
+ + src/environments/environment.prod.ts + | +variable | +environment | ++ 0 % + (0/1) + | +
+ + src/environments/environment.ts + | +variable | +apiVersion | ++ 0 % + (0/1) + | +
+ + src/environments/environment.ts + | +variable | +baseURL | ++ 0 % + (0/1) + | +
+ + src/environments/environment.ts + | +variable | +baseURLDemo | ++ 0 % + (0/1) + | +
+ + src/environments/environment.ts + | +variable | +baseURLProd | ++ 0 % + (0/1) + | +
+ + src/environments/environment.ts + | +variable | +environment | ++ 0 % + (0/1) + | +
+ + src/environments/environment.ts + | +variable | +testOnDemo | ++ 0 % + (0/1) + | +
+ + src/stories/0-Welcome.stories.ts + | +variable | +ToStorybook | ++ 0 % + (0/1) + | +
+ + src/stories/1-Button.stories.ts + | +variable | +ButtonWithLinkToAnotherStory | ++ 0 % + (0/1) + | +
+ + src/stories/1-Button.stories.ts + | +variable | +Emoji | ++ 0 % + (0/1) + | +
+ + src/stories/1-Button.stories.ts + | +variable | +Text | ++ 0 % + (0/1) + | +
+ + src/stories/1-Button.stories.ts + | +variable | +WithSomeEmojiAndAction | ++ 0 % + (0/1) + | +
+ + src/test.ts + | +variable | +context | ++ 0 % + (0/1) + | +
+ + src/test.ts + | +variable | +require | ++ 0 % + (0/1) + | +
+
+ src/app/custom-lib/date-value-accessor/date-value-accessor.ts
+
+
+
The accessor for writing a value and listening to changes on a date input element
+ <input type="date" name="myBirthday" ngModel useValueAsDate>
+
Providers | +
+
+ DATE_VALUE_ACCESSOR
+
+ |
+
Selector | +[useValueAsDate] |
+
+ Methods+ |
+
+
|
+
+ HostListeners+ |
+
+ + | +
+constructor(_renderer: Renderer2, _elementRef: ElementRef)
+ |
+ |||||||||
+ + | +|||||||||
+
+ Parameters :
+
+
|
+
+ + + + blur + + + + | +
+blur()
+ |
+
+ + | +
+ + + + input + + + + | +
+ Arguments : '$event.target.valueAsDate'
+ |
+
+input()
+ |
+
+ + | +
+ + + + registerOnChange + + + + | +||||||
+registerOnChange(fn: (_: any) => void)
+ |
+ ||||||
+ + | +||||||
+
+
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + registerOnTouched + + + + | +||||||
+registerOnTouched(fn: () => void)
+ |
+ ||||||
+ + | +||||||
+
+
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + setDisabledState + + + + | +||||||
+setDisabledState(isDisabled: boolean)
+ |
+ ||||||
+ + | +||||||
+
+
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + writeValue + + + + | +||||||
+writeValue(value: Date)
+ |
+ ||||||
+ + | +||||||
+
+
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
import {Directive, ElementRef, forwardRef, HostListener, Renderer2} from '@angular/core';
+import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
+
+export const DATE_VALUE_ACCESSOR: any = {
+ provide: NG_VALUE_ACCESSOR,
+ useExisting: forwardRef(() => DateValueAccessor),
+ multi: true
+};
+
+/**
+ * The accessor for writing a value and listening to changes on a date input element
+ *
+ * ### Example
+ * `<input type="date" name="myBirthday" ngModel useValueAsDate>`
+ */
+@Directive({
+ // this selector changes the previous behavior silently and might break existing code
+ // selector: 'input[type=date][formControlName],input[type=date][formControl],input[type=date][ngModel]',
+
+ // this selector is an opt-in version
+ selector: '[useValueAsDate]',
+ providers: [DATE_VALUE_ACCESSOR]
+})
+export class DateValueAccessor implements ControlValueAccessor {
+
+ @HostListener('input', ['$event.target.valueAsDate']) onChange = (_: any) => {
+ };
+ @HostListener('blur', []) onTouched = () => {
+ };
+
+ constructor(private _renderer: Renderer2, private _elementRef: ElementRef) {
+ }
+
+ writeValue(value: Date): void {
+ if (!value) {
+ this._renderer.setProperty(this._elementRef.nativeElement, 'value', null);
+ return;
+ }
+ this._renderer.setProperty(this._elementRef.nativeElement, 'valueAsDate', value);
+ }
+
+ registerOnChange(fn: (_: any) => void): void {
+ this.onChange = fn;
+ }
+
+ registerOnTouched(fn: () => void): void {
+ this.onTouched = fn;
+ }
+
+ setDisabledState(isDisabled: boolean): void {
+ this._renderer.setProperty(this._elementRef.nativeElement, 'disabled', isDisabled);
+ }
+}
+
+ +
+ src/app/ui/directives/resettable-input.directive.ts
+
+
+
add a button on an input which deletes its value
+ + + + + +Selector | +[framadateResettableInput] |
+
+constructor()
+ |
+
+ + | +
import {Directive} from '@angular/core';
+
+/**
+ * add a button on an input which deletes its value
+ */
+@Directive({
+ selector: '[framadateResettableInput]'
+})
+export class ResettableInputDirective {
+
+ constructor() {
+ }
+
+}
+
+ This project was generated with Angular CLI version 8.2.1.
+Run ng serve
for a dev server. Navigate to http://localhost:4200/
. The app will automatically reload if you change any of the source files.
Run ng generate component component-name
to generate a new component. You can also use ng generate directive|pipe|service|class|guard|interface|enum|module
.
Run ng build
to build the project. The build artifacts will be stored in the dist/
directory. Use the --prod
flag for a production build.
Run ng test
to execute the unit tests via Karma.
Run ng e2e
to execute the end-to-end tests via Protractor.
Before using ng for the first time in this project, use npm i
to install needed npm modules.
To get more help on the Angular CLI use ng help
or go check out the Angular CLI README.
+
+ src/app/services/config.service.ts
+
+
+
le service transverse à chaque page qui permet de syncroniser la configuration de sondage souhaitée
+ + + ++
+ PollConfig
+
+constructor(http: HttpClient, messageService: MessageService, router: Router, utils: PollUtilities, confirmationService: ConfirmationService)
+ |
+ ||||||||||||||||||
+ Defined in src/app/services/config.service.ts:21
+ |
+ ||||||||||||||||||
+
+ Parameters :
+
+
|
+
+ + + + addComment + + + + | +||||||
+addComment(comment?: any)
+ |
+ ||||||
+ Defined in src/app/services/config.service.ts:398
+ |
+ ||||||
+ POST +/api/v1/poll/{id}/comment +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + addDaysToDate + + + + | +|||||||||
+addDaysToDate(days: number, date: Date)
+ |
+ |||||||||
+ Defined in src/app/services/config.service.ts:53
+ |
+ |||||||||
+ add some days to a date, to compute intervals +
+ Parameters :
+
+
+
+
+ Returns :
+ any
+
+
+
+
+ |
+
+ + + + addVote + + + + | +||||||
+addVote(voteStack?: any)
+ |
+ ||||||
+ Defined in src/app/services/config.service.ts:333
+ |
+ ||||||
+ POST +/api/v1/poll/{id}/vote +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + checkIfSlugIsUniqueInDatabase + + + + | +||||||||
+checkIfSlugIsUniqueInDatabase(slug: string)
+ |
+ ||||||||
+ Defined in src/app/services/config.service.ts:95
+ |
+ ||||||||
+
+
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + convertChoicesAnsweredToSend + + + + | +||||
+convertChoicesAnsweredToSend(choiceList)
+ |
+ ||||
+ Defined in src/app/services/config.service.ts:314
+ |
+ ||||
+ conversion to send to back +
+ Parameters :
+
+
+
+
+ Returns :
+ any
+
+
+
+
+ |
+
+ + + + createPoll + + + + | +
+createPoll()
+ |
+
+ Defined in src/app/services/config.service.ts:218
+ |
+
+ launch creation call to the api +
+ Returns :
+ void
+
+ |
+
+ + + + createPollFromConfig + + + + | +||||||
+createPollFromConfig(config: any)
+ |
+ ||||||
+ Defined in src/app/services/config.service.ts:283
+ |
+ ||||||
+ POST +/api/v1/poll/{id}/poll +
+ Parameters :
+
+
+
+
+ Returns :
+ any
+
+
+
+
+ |
+
+ + + + deleteComments + + + + | +
+deleteComments()
+ |
+
+ Defined in src/app/services/config.service.ts:436
+ |
+
+ administrator calls +
+ Returns :
+ void
+
+ |
+
+ + + + deletePoll + + + + | +
+deletePoll()
+ |
+
+ Defined in src/app/services/config.service.ts:482
+ |
+
+
+
+ Returns :
+ void
+
+ |
+
+ + + + deleteVotes + + + + | +
+deleteVotes()
+ |
+
+ Defined in src/app/services/config.service.ts:459
+ |
+
+
+
+ Returns :
+ void
+
+ |
+
+ + + + exportCSV + + + + | +
+exportCSV()
+ |
+
+ Defined in src/app/services/config.service.ts:542
+ |
+
+ TODO +export all the poll data available to the public as a CSV single file +
+ Returns :
+ void
+
+ |
+
+ + + + fetchPollFromRoute + + + + | +||||
+fetchPollFromRoute(event)
+ |
+ ||||
+ Defined in src/app/services/config.service.ts:191
+ |
+ ||||
+
+
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + fillValuesOnDevEnv + + + + | +
+fillValuesOnDevEnv()
+ |
+
+ Defined in src/app/services/config.service.ts:39
+ |
+
+
+
+ Returns :
+ void
+
+ |
+
+ + + + findLocalStorageData + + + + | +
+findLocalStorageData()
+ |
+
+ Defined in src/app/services/config.service.ts:160
+ |
+
+
+
+ Returns :
+ void
+
+ |
+
+ + + + findPollsByEmail + + + + | +||||||
+findPollsByEmail(email: string)
+ |
+ ||||||
+ Defined in src/app/services/config.service.ts:120
+ |
+ ||||||
+ search in localstorage, fallback asking the backend to send an email to the owner if it exists +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + getMyPolls + + + + | +||||||
+getMyPolls(ownerEmail: string)
+ |
+ ||||||
+ Defined in src/app/services/config.service.ts:200
+ |
+ ||||||
+ GET +api/v1/my-polls +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + getPollById + + + + | +|||||||||
+getPollById(id: string, password?: string)
+ |
+ |||||||||
+ Defined in src/app/services/config.service.ts:184
+ |
+ |||||||||
+ GET +api/v1/poll/{id} +
+ Parameters :
+
+
+
+
+ Returns :
+ any
+
+
+
+
+ |
+
+ + + + getPollByURL + + + + | +||||||
+getPollByURL(url: string)
+ |
+ ||||||
+ Defined in src/app/services/config.service.ts:173
+ |
+ ||||||
+ get one poll by its slug name +
+ Parameters :
+
+
+
+
+ Returns :
+ any
+
+
+
+
+ |
+
+ + + + getPollConfig + + + + | +
+getPollConfig()
+ |
+
+ Defined in src/app/services/config.service.ts:67
+ |
+
+ ================================== +poll public calls to get non authenticated info +==================================/ +/** +convert current poll config to a payload to send to the backend API +
+ Returns :
+ { owner: { email: any; pseudo: any; }; title: any; description: any; pollType: any; visibility: a...
+
+ |
+
+ + + + handleError + + + + | +||||||
+handleError(err: any)
+ |
+ ||||||
+ Defined in src/app/services/config.service.ts:153
+ |
+ ||||||
+ display error message depending on the response of the backend +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + handleVoteAdded + + + + | +||||
+handleVoteAdded(res)
+ |
+ ||||
+ Defined in src/app/services/config.service.ts:360
+ |
+ ||||
+
+
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + loadVoteStack + + + + | +||||||
+loadVoteStack(voteStack: any)
+ |
+ ||||||
+ Defined in src/app/services/config.service.ts:250
+ |
+ ||||||
+ update current answers with a previous vote +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + print + + + + | +
+print()
+ |
+
+ Defined in src/app/services/config.service.ts:612
+ |
+
+
+
+ Returns :
+ void
+
+ |
+
+ + + + resetCurrentChoicesAnswers + + + + | +
+resetCurrentChoicesAnswers()
+ |
+
+ Defined in src/app/services/config.service.ts:240
+ |
+
+
+
+ Returns :
+ void
+
+ |
+
+ + + + set + + + + | +||||||
+set(key, val)
+ |
+ ||||||
+ Defined in src/app/services/config.service.ts:34
+ |
+ ||||||
+
+
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + todo + + + + | +||||||||
+todo(message: string)
+ |
+ ||||||||
+ Defined in src/app/services/config.service.ts:616
+ |
+ ||||||||
+
+
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + updateCurrentPollFromResponse + + + + | +||||||
+updateCurrentPollFromResponse(res: any)
+ |
+ ||||||
+ Defined in src/app/services/config.service.ts:223
+ |
+ ||||||
+
+
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + updatePoll + + + + | +||||||||
+updatePoll(voteStack: any)
+ |
+ ||||||||
+ Defined in src/app/services/config.service.ts:520
+ |
+ ||||||||
+ UPDATE +/api/v1/poll/{id}/vote +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + updateVote + + + + | +||||||
+updateVote(voteStack?: any)
+ |
+ ||||||
+ Defined in src/app/services/config.service.ts:373
+ |
+ ||||||
+ UPDATE +/api/v1/poll/{id}/vote +
+ Parameters :
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + + resetConfig + + + + | +
+resetConfig()
+ |
+
+ Inherited from
+ PollConfig
+ |
+
+ Defined in
+ PollConfig:68
+ |
+
+
+
+ Returns :
+ void
+
+ |
+
+ + + + baseHref + + + | +
+ Type : any
+
+ |
+
+ Default value : environment.baseApiHref
+ |
+
+ Defined in src/app/services/config.service.ts:21
+ |
+
+ + + + loading + + + | +
+ Type : boolean
+
+ |
+
+ Default value : false
+ |
+
+ Defined in src/app/services/config.service.ts:20
+ |
+
+ + + + adminKey + + + | +
+ Type : string
+
+ |
+
+ Default value : ''
+ |
+
+ Inherited from
+ PollConfig
+ |
+
+ Defined in
+ PollConfig:58
+ |
+
+ + + + allowSeveralHours + + + | +
+ Type : string
+
+ |
+
+ Default value : 'true'
+ |
+
+ Inherited from
+ PollConfig
+ |
+
+ Defined in
+ PollConfig:41
+ |
+
+ + + + answers + + + | +
+ Type : PollAnswer[]
+
+ |
+
+ Default value : defaultAnswers
+ |
+
+ Inherited from
+ PollConfig
+ |
+
+ Defined in
+ PollConfig:66
+ |
+
+ + + + canModifyAnswers + + + | +
+ Type : boolean
+
+ |
+
+ Default value : true
+ |
+
+ Inherited from
+ PollConfig
+ |
+
+ Defined in
+ PollConfig:60
+ |
+
+ + + + creationDate + + + | +
+ Default value : new Date()
+ |
+
+ Inherited from
+ PollConfig
+ |
+
+ Defined in
+ PollConfig:45
+ |
+
+ + + + currentPoll + + + | +
+ Inherited from
+ PollConfig
+ |
+
+ Defined in
+ PollConfig:50
+ |
+
+ + + + customUrl + + + | +
+ Type : string
+
+ |
+
+ Default value : ''
+ |
+
+ Inherited from
+ PollConfig
+ |
+
+ Defined in
+ PollConfig:53
+ |
+
+ + + + customUrlIsUnique + + + | +
+ Type : null
+
+ |
+
+ Default value : null
+ |
+
+ Inherited from
+ PollConfig
+ |
+
+ Defined in
+ PollConfig:54
+ |
+
+ + + + dateList + + + | +
+ Type : any
+
+ |
+
+ Default value : otherDefaultDates
+ |
+
+ Inherited from
+ PollConfig
+ |
+
+ Defined in
+ PollConfig:63
+ |
+
+ + + + deletionDateAfterLastModification + + + | +
+ Type : number
+
+ |
+
+ Default value : 180
+ |
+
+ Inherited from
+ PollConfig
+ |
+
+ Defined in
+ PollConfig:27
+ |
+
+ + + + description + + + | +
+ Type : string
+
+ |
+
+ Default value : 'ma description'
+ |
+
+ Inherited from
+ PollConfig
+ |
+
+ Defined in
+ PollConfig:32
+ |
+
+ + + + expiracyDateDefaultInDays + + + | +
+ Type : number
+
+ |
+
+ Default value : 60
+ |
+
+ Inherited from
+ PollConfig
+ |
+
+ Defined in
+ PollConfig:26
+ |
+
+ + + + expirationDate + + + | +
+ Type : string
+
+ |
+
+ Default value : ''
+ |
+
+ Inherited from
+ PollConfig
+ |
+
+ Defined in
+ PollConfig:46
+ |
+
+ + + + isAdmin + + + | +
+ Type : boolean
+
+ |
+
+ Default value : true
+ |
+
+ Inherited from
+ PollConfig
+ |
+
+ Defined in
+ PollConfig:35
+ |
+
+ + + + myComment + + + | +
+ Type : string
+
+ |
+
+ Default value : 'wouah trop bien framadate!'
+ |
+
+ Inherited from
+ PollConfig
+ |
+
+ Defined in
+ PollConfig:34
+ |
+
+ + + + myEmail + + + | +
+ Type : string
+
+ |
+
+ Default value : "tktest@tktest.com"
+ |
+
+ Inherited from
+ PollConfig
+ |
+
+ Defined in
+ PollConfig:38
+ |
+
+ + + + myName + + + | +
+ Type : string
+
+ |
+
+ Default value : 'mon pseudo'
+ |
+
+ Inherited from
+ PollConfig
+ |
+
+ Defined in
+ PollConfig:33
+ |
+
+ + + + myPolls + + + | +
+ Type : any
+
+ |
+
+ Default value : []
+ |
+
+ Inherited from
+ PollConfig
+ |
+
+ Defined in
+ PollConfig:39
+ |
+
+ + + + myTempVoteStack + + + | +
+ Type : number
+
+ |
+
+ Default value : 0
+ |
+
+ Inherited from
+ PollConfig
+ |
+
+ Defined in
+ PollConfig:37
+ |
+
+ + + + myVoteStack + + + | +
+ Type : any
+
+ |
+
+ Inherited from
+ PollConfig
+ |
+
+ Defined in
+ PollConfig:36
+ |
+
+ + + + owner_modifier_token + + + | +
+ Type : string
+
+ |
+
+ Default value : ''
+ |
+
+ Inherited from
+ PollConfig
+ |
+
+ Defined in
+ PollConfig:59
+ |
+
+ + + + password + + + | +
+ Type : string
+
+ |
+
+ Default value : ''
+ |
+
+ Inherited from
+ PollConfig
+ |
+
+ Defined in
+ PollConfig:52
+ |
+
+ + + + passwordAccess + + + | +
+ Type : number
+
+ |
+
+ Default value : 0
+ |
+
+ Inherited from
+ PollConfig
+ |
+
+ Defined in
+ PollConfig:51
+ |
+
+ + + + pollId + + + | +
+ Type : null
+
+ |
+
+ Default value : null
+ |
+
+ Inherited from
+ PollConfig
+ |
+
+ Defined in
+ PollConfig:48
+ |
+
+ + + + pollSlug + + + | +
+ Type : null
+
+ |
+
+ Default value : null
+ |
+
+ Inherited from
+ PollConfig
+ |
+
+ Defined in
+ PollConfig:49
+ |
+
+ + + + pollType + + + | +
+ Type : string
+
+ |
+
+ Default value : 'dates'
+ |
+
+ Inherited from
+ PollConfig
+ |
+
+ Defined in
+ PollConfig:30
+ |
+
+ + + + step + + + | +
+ Type : number
+
+ |
+
+ Default value : 0
+ |
+
+ Inherited from
+ PollConfig
+ |
+
+ Defined in
+ PollConfig:28
+ |
+
+ + + + stepMax + + + | +
+ Type : number
+
+ |
+
+ Default value : 3
+ |
+
+ Inherited from
+ PollConfig
+ |
+
+ Defined in
+ PollConfig:29
+ |
+
+ + + + timeList + + + | +
+ Type : DateChoice[]
+
+ |
+
+ Default value : otherDefaultDates
+ |
+
+ Inherited from
+ PollConfig
+ |
+
+ Defined in
+ PollConfig:64
+ |
+
+ + + + title + + + | +
+ Type : string
+
+ |
+
+ Default value : 'titre'
+ |
+
+ Inherited from
+ PollConfig
+ |
+
+ Defined in
+ PollConfig:31
+ |
+
+ + + + urlAdmin + + + | +
+ Default value : environment.baseHref + '/#/admin/d65es45fd45sdf45sd345f312sdf31sgfd345'
+ |
+
+ Inherited from
+ PollConfig
+ |
+
+ Defined in
+ PollConfig:57
+ |
+
+ + + + urlPublic + + + | +
+ Default value : environment.baseHref + '/#/poll/id/4'
+ |
+
+ Inherited from
+ PollConfig
+ |
+
+ Defined in
+ PollConfig:56
+ |
+
+ + + + urlSlugPublic + + + | +
+ Type : null
+
+ |
+
+ Default value : null
+ |
+
+ Inherited from
+ PollConfig
+ |
+
+ Defined in
+ PollConfig:55
+ |
+
+ + + + visibility + + + | +
+ Type : string
+
+ |
+
+ Default value : 'link_only'
+ |
+
+ Inherited from
+ PollConfig
+ |
+
+ Defined in
+ PollConfig:43
+ |
+
+ + + + voteChoices + + + | +
+ Type : string
+
+ |
+
+ Default value : 'only_yes'
+ |
+
+ Inherited from
+ PollConfig
+ |
+
+ Defined in
+ PollConfig:44
+ |
+
+ + + + voteStackId + + + | +
+ Type : null
+
+ |
+
+ Default value : null
+ |
+
+ Inherited from
+ PollConfig
+ |
+
+ Defined in
+ PollConfig:47
+ |
+
+ + + + whoCanChangeAnswers + + + | +
+ Type : string
+
+ |
+
+ Default value : 'everybody'
+ |
+
+ Inherited from
+ PollConfig
+ |
+
+ Defined in
+ PollConfig:62
+ |
+
+ + + + whoModifiesAnswers + + + | +
+ Type : string
+
+ |
+
+ Default value : "everybody"
+ |
+
+ Inherited from
+ PollConfig
+ |
+
+ Defined in
+ PollConfig:61
+ |
+
import {Injectable} from '@angular/core';
+import {PollConfig} from '../config/PollConfig';
+import {HttpClient} from "@angular/common/http";
+import {environment} from "../../environments/environment";
+import {ConfirmationService, MessageService} from 'primeng/api';
+import {Router} from "@angular/router";
+import {mockMyPolls} from "../config/mocks/mockmypolls";
+import {mockPoll3} from "../config/mocks/mock-poll3";
+import {mockSuccessVote} from "../config/mocks/mock-success-vote";
+import {PollUtilities} from "../config/PollUtilities";
+
+/**
+ * le service transverse à chaque page qui permet de syncroniser la configuration de sondage souhaitée
+ */
+@Injectable({
+ providedIn: 'root'
+})
+export class ConfigService extends PollConfig {
+
+ loading: boolean = false;
+ baseHref: any = environment.baseApiHref;
+
+
+ constructor(private http: HttpClient,
+ private messageService: MessageService,
+ private router: Router,
+ private utils: PollUtilities,
+ private confirmationService: ConfirmationService,
+ ) {
+ super();
+ this.fillValuesOnDevEnv();
+ }
+
+ set(key, val) {
+ this[key] = val;
+ }
+
+ // fill in mock values if we are not in production environment
+ fillValuesOnDevEnv() {
+
+ if (!environment.production) {
+ console.info(' ######### framadate ######### we are not in production env, filling with mock values');
+ this.currentPoll = mockPoll3;
+ this.myPolls = mockMyPolls;
+ }
+ }
+
+ /**
+ * add some days to a date, to compute intervals
+ * @param days
+ * @param date
+ */
+ addDaysToDate(days: number, date: Date) {
+ date = new Date(date.valueOf());
+ date.setDate(date.getDate() + days);
+ return date;
+ };
+
+ /** ==================================
+ *
+ * poll public calls to get non authenticated info
+ *
+ * ==================================/
+ /**
+ * convert current poll config to a payload to send to the backend API
+ */
+ getPollConfig() {
+ const jsonConfig = {
+ owner: {
+ email: this.myEmail,
+ pseudo: this.myName,
+ },
+ title: this.title,
+ description: this.description,
+ pollType: this.pollType,
+ visibility: this.visibility,
+ voteChoices: this.voteChoices,
+ allowSeveralHours: this.allowSeveralHours,
+ expirationDate: this.expirationDate,
+ passwordAccess: this.passwordAccess,
+ password: this.password,
+ customUrl: this.customUrl,
+ canModifyAnswers: this.canModifyAnswers,
+ whoModifiesAnswers: this.whoModifiesAnswers,
+ dateList: this.dateList,
+ timeList: this.timeList,
+ answers: this.answers,
+ expiracyDateDefaultInDays: this.expiracyDateDefaultInDays,
+ deletionDateAfterLastModification: this.deletionDateAfterLastModification,
+ };
+ return jsonConfig
+ }
+
+
+ checkIfSlugIsUniqueInDatabase(slug: string = '') {
+ this.customUrlIsUnique = null;
+ if (!slug) {
+ slug = this.utils.makeSlug(this);
+ }
+
+ this.loading = true;
+ // TODO
+ this.todo('check slug is unique');
+ this.http.get(`${this.baseHref}/check-slug-is-uniq/${slug}`,
+ this.utils.makeHeaders({slug: this.customUrl}),
+ )
+ .subscribe((res: any) => {
+
+ this.customUrlIsUnique = res.poll.isUnique;
+ this.loading = false;
+ },
+ (e) => this.handleError(e))
+ ;
+ }
+
+ /**
+ * search in localstorage, fallback asking the backend to send an email to the owner if it exists
+ * @param email
+ */
+ findPollsByEmail(email: string) {
+
+ this.findLocalStorageData();
+ // If no key is found in the localstorage, ask the backend to send an email to the user
+
+ this.myEmail = email;
+
+
+ this.todo('send email for real : TODO');
+ this.loading = true;
+ this.http.get(`${this.baseHref}/send-polls-to-user/${this.myEmail}`,
+ this.utils.makeHeaders(),
+ )
+ .subscribe(res => {
+ // message: 'Trouvé! Allez voir votre boite email',
+ this.myPolls = res;
+ this.loading = false;
+ this.messageService.add({
+ severity: 'success',
+ summary: 'Succès',
+ detail: `Vos infos de sondages vous ont été transmises. Allez voir votre boite email ${this.myEmail}`
+ });
+ }, (e) => {
+ this.handleError(e)
+ }
+ )
+ }
+
+
+ /**
+ * display error message depending on the response of the backend
+ * @param err
+ */
+ handleError(err: any) {
+ console.error('err', err);
+ this.loading = false;
+ this.messageService.add({severity: 'warning', summary: "Erreur lors de l'appel ", detail: err.message});
+ }
+
+
+ findLocalStorageData() {
+ // TODO check if the person has a key to retrieve her polls
+ console.log('localStorage', localStorage);
+ if (localStorage) {
+ console.log('localStorage', localStorage)
+ }
+ }
+
+
+ /**
+ * get one poll by its slug name
+ * @param url
+ */
+ getPollByURL(url: string) {
+
+ this.todo();
+ return this.http.get(`${this.baseHref}/poll/slug/${url}`, this.utils.makeHeaders())
+ }
+
+ /**
+ * GET
+ * api/v1/poll/{id}
+ * @param id
+ */
+ getPollById(id: string, password?: string) {
+
+ return this.http
+ .get(`${this.baseHref}/poll/${id}`,
+ this.utils.makeHeaders({body: password}))
+ }
+
+ fetchPollFromRoute(event) {
+ console.log('time to fetch poll', event)
+ }
+
+ /**
+ * GET
+ * api/v1/my-polls
+ * @param ownerEmail
+ */
+ getMyPolls(ownerEmail: string) {
+ this.http
+ .get(`${this.baseHref}/my-polls`,
+ this.utils.makeHeaders({ownerEmail: ownerEmail})
+ )
+ .subscribe(
+ (res: any) => {
+ // this.myPolls = res.poll;
+ }, (e) => {
+ this.handleError(e)
+ }
+ );
+ }
+
+
+ /**
+ * launch creation call to the api
+ */
+ createPoll() {
+ this.loading = true;
+ this.createPollFromConfig(this.getPollConfig())
+ }
+
+ updateCurrentPollFromResponse(res: any) {
+ console.log('update res', res);
+ this.currentPoll = res;
+ this.pollId = res.poll.id;
+ this.owner_modifier_token = res.owner_modifier_token;
+ this.urlPublic = this.baseHref + '#/vote/poll/id/' + res.poll.id;
+ this.urlSlugPublic = this.baseHref + '#/vote/poll/slug/' + res.poll.id;
+ if (res.poll.customUrl) {
+ this.urlSlugPublic = this.baseHref + '#/vote/poll/id/' + res.poll.customUrl;
+ }
+ if (res.voteStack) {
+ this.loadVoteStack(res.voteStack);
+ }
+ this.adminKey = res.admin_key;
+ this.urlAdmin = this.baseHref + '#/admin/' + res.admin_key;
+ }
+
+ resetCurrentChoicesAnswers() {
+ this.currentPoll.choices.map(choice => {
+ choice.answer = null;
+ })
+ }
+
+ /**
+ * update current answers with a previous vote
+ * @param voteStack
+ */
+ loadVoteStack(voteStack: any) {
+
+ // load the pseudo and email
+ this.myName = voteStack.pseudo;
+ this.myEmail = voteStack.email;
+ this.voteStackId = voteStack.id;
+ this.myVoteStack = voteStack;
+ let keys = Object.keys(voteStack.votes);
+ console.log('voteStack', voteStack);
+ this.resetCurrentChoicesAnswers();
+ keys.forEach((id: any) => {
+ let voteItem = voteStack.votes[id];
+
+ /**
+ * the display of the poll uses the choices data, so we update the choices answers of the current poll to reflect the vote stack we have taken
+ */
+ if (voteItem.choice_id && voteItem.value) {
+ let foundChoiceToModify = this.currentPoll.choices.find(choicesItem => {
+ return voteItem.choice_id == choicesItem.id
+ });
+ console.log('foundChoiceToModify', foundChoiceToModify);
+ if (foundChoiceToModify) {
+ foundChoiceToModify.answer = voteItem.value;
+ }
+ }
+ })
+ }
+
+ /**
+ * POST
+ * /api/v1/poll/{id}/poll
+ * @param config
+ */
+ createPollFromConfig(config: any) {
+ this.loading = true;
+ console.log('config', config);
+ return this.http.post(`${this.baseHref}/poll`,
+ config,
+ this.utils.makeHeaders())
+ .subscribe((res: any) => {
+ // redirect to the page to administrate the new poll
+ this.messageService.add({severity: 'success', summary: 'Sondage Créé',});
+
+ this.updateCurrentPollFromResponse(res);
+
+ this.loading = false;
+ if (!this.myPolls) {
+ this.myPolls = [];
+ }
+ this.myPolls.push(res);
+ this.router.navigate(['step/end']);
+ // TODO save new poll to localstorage
+ // reset all fields in current config
+ this.resetConfig();
+ }, (e) => {
+ this.handleError(e)
+ }
+ );
+ }
+
+ /**
+ * conversion to send to back
+ * @param choiceList
+ */
+ convertChoicesAnsweredToSend(choiceList) {
+ choiceList = choiceList.filter(c => c.answer ? c : null); // remove choices where we did not answer
+ const converted = choiceList.map(elem => {
+ if (elem.answer) {
+ return {
+ choice_id: elem.id,
+ value: elem.answer
+ }
+ }
+ });
+ console.log('converted', converted);
+ return converted;
+ }
+
+ /**
+ * POST
+ * /api/v1/poll/{id}/vote
+ * @param voteStack
+ */
+ addVote(voteStack?: any) {
+ if (!voteStack) {
+ voteStack = {
+ pseudo: this.myName,
+ email: this.myEmail,
+ votes: this.convertChoicesAnsweredToSend(this.currentPoll.choices),
+ }
+ }
+ this.myVoteStack = voteStack;
+
+ if (!environment.production) {
+ this.handleVoteAdded(mockSuccessVote);
+ return;
+ }
+ this.http.post(
+ `${this.baseHref}/poll/${this.pollId}/vote`,
+ voteStack,
+ this.utils.makeHeaders())
+ .subscribe((res: any) => {
+
+ this.handleVoteAdded(res);
+ }, (e) => {
+ this.handleError(e)
+ }
+ );
+ }
+
+ handleVoteAdded(res) {
+ this.messageService.add({severity: 'success', summary: 'Vote ajouté'});
+ // save modifier token
+ this.myVoteStack['modifier_token'] = res.modifier_token;
+ this.myVoteStack['id'] = res.vote_stack.id;
+ this.updateCurrentPollFromResponse(res);
+ }
+
+ /**
+ * UPDATE
+ * /api/v1/poll/{id}/vote
+ * @param voteStack
+ */
+ updateVote(voteStack?: any) {
+ if (!this.myVoteStack) {
+ return;
+
+ } else {
+ voteStack = this.myVoteStack;
+ }
+ this.http.patch(
+ `${this.baseHref}/vote-stack/${voteStack.id}/token/${this.owner_modifier_token}`,
+ voteStack,
+ this.utils.makeHeaders())
+ .subscribe((res: any) => {
+ this.messageService.add({severity: 'success', summary: 'Vote mis à jour'});
+ this.updateCurrentPollFromResponse(res);
+ }, (e) => {
+ this.handleError(e)
+ }
+ );
+ }
+
+ /**
+ * POST
+ * /api/v1/poll/{id}/comment
+ * @param comment
+ */
+ addComment(comment?: any) {
+ if (!comment && this.myComment) {
+ comment = {
+ name: this.myName,
+ pseudo: this.myName,
+ email: this.myEmail,
+ date: new Date(),
+ text: this.myComment,
+ }
+ }
+ console.log('comment', comment);
+ this.http.post(
+ `${this.baseHref}/poll/${this.pollId}/comment`,
+ comment,
+ this.utils.makeHeaders())
+ .subscribe((res: any) => {
+ this.messageService.add({
+ severity: 'success',
+ summary: 'Commentaire Créé',
+ detail: comment.text
+ });
+ // empty comment after success
+ this.myComment = '';
+ comment.date = {
+ date: comment.date
+ };
+ this.currentPoll.comments.push(comment);
+ }, (e) => {
+ this.handleError(e);
+ }
+ );
+ }
+
+
+ /**
+ * administrator calls
+ */
+
+ deleteComments() {
+ // prompt for confirmation
+ this.confirmationService.confirm({
+ message: 'Are you sure that you want to completely delete the comments of this poll (' + this.title + ') permanentely?',
+ accept: () => {
+ this.http.delete(
+ `${this.baseHref}/poll/${this.pollId}/comments`,
+ this.utils.makeHeaders())
+ .subscribe((res: any) => {
+ this.messageService.add({
+ severity: 'success',
+ summary: 'Commentaires bien supprimés',
+ detail: 'Commentaires du sondage "' + this.title + '" supprimé'
+ });
+
+ }, (e) => {
+ this.handleError(e)
+ }
+ );
+ }
+ });
+ }
+
+ deleteVotes() {
+ // prompt for confirmation
+ this.confirmationService.confirm({
+ message: 'Are you sure that you want to completely delete the votes of this poll (' + this.title + ') permanentely?',
+ accept: () => {
+ this.http.delete(
+ `${this.baseHref}/poll/${this.pollId}/votes`,
+ this.utils.makeHeaders())
+ .subscribe((res: any) => {
+ this.messageService.add({
+ severity: 'success',
+ summary: 'Votes bien supprimés',
+ detail: 'Votes du sondage "' + this.title + '" supprimé'
+ });
+
+ }, (e) => {
+ this.handleError(e)
+ }
+ );
+ }
+ });
+ }
+
+ deletePoll() {
+ if (!this.pollId) {
+ this.messageService.add({
+ summary: 'this poll is not administrable, it has no ID',
+ severity: 'warning'
+ });
+ return;
+ }
+ let self = this;
+ // prompt for confirmation
+ this.confirmationService.confirm({
+ message: 'Are you sure that you want to completely delete this poll (' + self.title + ') and all is data permanentely?',
+ accept: () => {
+ this.http.delete(
+ `${this.baseHref}/poll/${this.pollId}`,
+ this.utils.makeHeaders())
+ .subscribe((res: any) => {
+ this.messageService.add({
+ severity: 'success',
+ summary: 'Sondage bien supprimé',
+ detail: 'sondage "' + this.title + '" supprimé'
+ });
+
+ this.router.navigate(['home']);
+ }, (e) => {
+ this.handleError(e)
+ }
+ );
+ }
+ });
+ }
+
+ /**
+ * UPDATE
+ * /api/v1/poll/{id}/vote
+ * @param voteStack
+ * TODO
+ */
+ updatePoll(voteStack: any) {
+ this.http.put(
+ `${this.baseHref}/poll/${this.pollId}`,
+ voteStack,
+ this.utils.makeHeaders()
+ )
+ .subscribe((res: any) => {
+ this.messageService.add({
+ severity: 'success',
+ summary: 'Sondage mis à jour',
+ });
+ this.updateCurrentPollFromResponse(res);
+ }, (e) => {
+ this.handleError(e)
+ }
+ );
+ }
+
+ /**
+ * TODO
+ * export all the poll data available to the public as a CSV single file
+ */
+ exportCSV() {
+
+
+ let rows = [];
+ let now = new Date();
+ const headers = [
+ ['export de sondage Framadate ', this.customUrl],
+ ['le', now.toISOString()],
+ [this.currentPoll.pollId, this.currentPoll.title, this.customUrl, this.creationDate],
+ ['pseudo']];
+
+
+ let listOfChoices = ['choices : '];
+ this.currentPoll.choices.map(choice => {
+ listOfChoices.push(choice.text)
+ });
+ listOfChoices.push('pseudo');
+
+ this.currentPoll.stacks.map(voteStack => {
+ let voteStackInArray = [voteStack.pseudo];
+ let keysVotes = Object.keys(voteStack.votes);
+
+ keysVotes.map(id => {
+ voteStackInArray.push(voteStack.votes[id].value ? voteStack.votes[id].value : "")
+ });
+ rows.push(
+ voteStackInArray
+ );
+ });
+ const headersComments = [
+ ['comments'],
+ ['pseudo', 'text', 'creation_date'],
+ ];
+ const comments = [];
+ this.currentPoll.comments.map(item => {
+ comments.push(
+ [item.pseudo,
+ item.text,
+ item.date.date,
+ '\n']
+ )
+ });
+ headers.push(listOfChoices);
+ rows = [headers, listOfChoices, rows, headersComments, comments];
+
+ let convertedCsv = rows.map(elem => {
+ console.log('elem', elem);
+ return elem.map(item => {
+ console.log('item', item);
+ if (typeof item === typeof Array) {
+ return item.join('\n')
+ }
+ return item;
+ }).join('\n')
+ }).join('\n');
+ console.log('rows', rows);
+ console.log('convertedCsv', convertedCsv);
+
+ let csvContent = "data:text/csv;charset=utf-8,"
+ + convertedCsv;
+ console.log('csvContent', csvContent);
+ var encodedUri = encodeURI(csvContent);
+ var link = document.createElement("a");
+ link.setAttribute("href", encodedUri);
+ let exportFileName = (this.urlPublic ? this.urlPublic : this.utils.makeSlug(this)) + "_export_" + new Date() + ".csv";
+ link.setAttribute("download", exportFileName);
+ document.body.appendChild(link); // Required for FF
+ link.click(); // This will download the data file named "my_data.csv".
+ }
+
+ print() {
+ alert('TODO');
+ }
+
+ todo(message = '') {
+ this.messageService.add({
+ severity: 'info' + message,
+ detail: "cette fonctionnalité n'est pas encore disponible. Venez en discuter sur framateam.org / Ux et design libre / Framasoft",
+ summary: "Work in progress",
+ });
+ }
+}
+
+ +
+ src/app/config/DateUtilities.ts
+
+ Methods+ |
+
+
|
+
+ + + + addDaysToDate + + + + | +|||||||||
+addDaysToDate(days: number, date: Date)
+ |
+ |||||||||
+ Defined in src/app/config/DateUtilities.ts:13
+ |
+ |||||||||
+ add some days to a date, to compute intervals +
+ Parameters :
+
+
+
+
+ Returns :
+ any
+
+
+
+
+ |
+
+ + + + dayDiff + + + + | +|||||||||
+dayDiff(d1: Date, d2: Date)
+ |
+ |||||||||
+ Defined in src/app/config/DateUtilities.ts:44
+ |
+ |||||||||
+ get the number of days between two dates +
+ Parameters :
+
+
+
+
+ Returns :
+ Number
+
+
+
+
+ |
+
+ + + + formateDate + + + + | +||||
+formateDate(date)
+ |
+ ||||
+ Defined in src/app/config/DateUtilities.ts:53
+ |
+ ||||
+ format a date object to the date format used by the inputs of type date +YYYY-MM-DD +
+ Parameters :
+
+
+
+
+ Returns :
+ any
+
+
+
+
+ |
+
+ + + + getDatesInRange + + + + | +||||||||||||
+getDatesInRange(d1: Date, d2: Date, interval: number)
+ |
+ ||||||||||||
+ Defined in src/app/config/DateUtilities.ts:25
+ |
+ ||||||||||||
+
+
+ Parameters :
+
+
+
+
+ Returns :
+ any
+
+
+
+
+ |
+
+ + + + getDoubleDigits + + + + | +||||
+getDoubleDigits(str)
+ |
+ ||||
+ Defined in src/app/config/DateUtilities.ts:61
+ |
+ ||||
+
+
+ Parameters :
+
+
+
+
+ Returns :
+ any
+
+
+
+
+ |
+
import {Injectable} from "@angular/core";
+
+@Injectable({
+ providedIn: 'root'
+})
+export class DateUtilities {
+
+ /**
+ * add some days to a date, to compute intervals
+ * @param days
+ * @param date
+ */
+ addDaysToDate(days: number, date: Date) {
+ date = new Date(date.valueOf());
+ date.setDate(date.getDate() + days);
+ return date;
+ };
+
+ /**
+ *
+ * @param d1
+ * @param d2
+ * @param interval
+ */
+ getDatesInRange(d1: Date, d2: Date, interval: number) {
+ d1 = new Date(d1);
+ d2 = new Date(d2);
+ const dates = [];
+ while (+d1 < +d2) {
+ dates.push({
+ literal: this.formateDate(d1),
+ date_object: d1
+ });
+ d1.setDate(d1.getDate() + interval)
+ }
+ return dates.slice(0);
+ }
+
+ /**
+ * get the number of days between two dates
+ * @param d1
+ * @param d2
+ */
+ dayDiff(d1: Date, d2: Date): Number {
+ return Number(((d2.getTime()) - (d1.getTime()) / 31536000000));
+ }
+
+ /**
+ * format a date object to the date format used by the inputs of type date
+ * YYYY-MM-DD
+ * @param date
+ */
+ formateDate(date) {
+ return [
+ date.getFullYear(),
+ this.getDoubleDigits(date.getMonth() + 1),
+ this.getDoubleDigits(date.getDate()),
+ ].join('-')
+ }
+
+ getDoubleDigits(str) {
+ return ("00" + str).slice(-2);
+ }
+}
+
+ +
+ src/app/services/poll.service.ts
+
+ Properties+ |
+
+
|
+
+constructor()
+ |
+
+ Defined in src/app/services/poll.service.ts:9
+ |
+
+ + + + Private + baseHref + + + | +
+ Type : string
+
+ |
+
+ Default value : environment.baseApiHref
+ |
+
+ Defined in src/app/services/poll.service.ts:9
+ |
+
import {Injectable} from '@angular/core';
+import {environment} from "../../environments/environment";
+
+@Injectable({
+ providedIn: 'root'
+})
+export class PollService {
+
+ private baseHref: string = environment.baseApiHref;
+
+ constructor() {
+ }
+
+
+}
+
+ +
+ src/app/config/PollUtilities.ts
+
+ Methods+ |
+
+
|
+
+ + + + makeHeaders + + + + | +||||||
+makeHeaders(bodyContent?: any)
+ |
+ ||||||
+ Defined in src/app/config/PollUtilities.ts:48
+ |
+ ||||||
+ prepare headers like the charset and json type for any call to the backend +
+ Parameters :
+
+
+
+
+ Returns :
+ { headers: any; body: any; }
+
+
+
+
+ |
+
+ + + + makeSlug + + + + | +||||||
+makeSlug(config: PollConfig)
+ |
+ ||||||
+ Defined in src/app/config/PollUtilities.ts:24
+ |
+ ||||||
+ make a uniq slug for the current poll creation +
+ Parameters :
+
+
+
+
+ Returns :
+ string
+
+
+
+
+ |
+
+ + + + makeUuid + + + + | +
+makeUuid()
+ |
+
+ Defined in src/app/config/PollUtilities.ts:13
+ |
+
+ generate unique id to have a default url for future poll +
+ Returns :
+ any
+
+ |
+
import {HttpHeaders} from "@angular/common/http";
+import {PollConfig} from "./PollConfig";
+import {Injectable} from "@angular/core";
+
+@Injectable({
+ providedIn: 'root'
+})
+export class PollUtilities {
+ // utils functions
+ /**
+ * generate unique id to have a default url for future poll
+ */
+ makeUuid() {
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
+ var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
+ return v.toString(16);
+ });
+ }
+
+ /**
+ * make a uniq slug for the current poll creation
+ * @param str
+ */
+ makeSlug(config: PollConfig) {
+ let str = '';
+ str = config.creationDate.getFullYear() + '_' + (config.creationDate.getMonth() + 1) + '_' + config.creationDate.getDate() + '_' + config.myName + '_' + config.title;
+ str = str.replace(/^\s+|\s+$/g, ''); // trim
+ str = str.toLowerCase();
+
+ // remove accents, swap ñ for n, etc
+ var from = "àáäâèéëêìíïîòóöôùúüûñç·/_,:;";
+ var to = "aaaaeeeeiiiioooouuuunc------";
+ for (var 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;
+ }
+
+ /**
+ * prepare headers like the charset and json type for any call to the backend
+ * @param bodyContent
+ */
+ makeHeaders(bodyContent?: any) {
+
+ const headerDict = {
+ 'Charset': 'UTF-8',
+ 'Content-Type': 'application/json',
+ 'Accept': 'application/json',
+ 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
+ 'Access-Control-Allow-Origin': '*'
+ };
+
+ const requestOptions = {
+ headers: new HttpHeaders(headerDict),
+ body: bodyContent
+ };
+
+ return requestOptions;
+ }
+}
+
+ +
+ src/app/config/defaultConfigs.ts
+
+ Properties+ |
+
+
|
+
+ + date_object + | +
+ date_object:
+ |
+
+ Type : Date
+
+ |
+
+ + literal + | +
+ literal:
+ |
+
+ Type : string
+
+ |
+
+ + timeList + | +
+ timeList:
+ |
+
+ Type : TimeSlices[]
+
+ |
+
export interface DateChoice {
+ literal: string,
+ timeList: TimeSlices[],
+ date_object: Date
+}
+
+export interface TimeSlices {
+ literal: string
+}
+
+export interface PollAnswer {
+ id: number,
+ text: string,
+ url: string,
+ file: string,
+ literal: string,
+ date_object: Date,
+ timeList: TimeSlices[]
+}
+
+const currentYear = new Date().getFullYear();
+const currentMonth = new Date().getMonth();
+const currentDay = new Date().getDate();
+
+export const basicSlicesOfDay: TimeSlices[] = [
+ {literal: 'matin'},
+ {literal: 'midi'},
+ {literal: 'soir'}
+];
+export const otherSlicesOfDay: TimeSlices[] = [
+ {literal: 'aux aurores'},
+ {literal: 'au petit dej'},
+ {literal: 'au deuxième petit dej des hobbits'}
+];
+export const defaultTimeOfDay: TimeSlices[] = (() => {
+ return [...basicSlicesOfDay]
+})();
+
+export const otherTimeOfDay: TimeSlices[] = (() => {
+ return [...otherSlicesOfDay]
+})();
+export const defaultDates: DateChoice[] = [
+ {
+ literal: `${currentYear}-${currentMonth}-${currentDay}`,
+ date_object: new Date(),
+ timeList: defaultTimeOfDay
+ },
+ {
+ literal: `${currentYear}-${currentMonth}-${currentDay + 1}`,
+ date_object: new Date(),
+ timeList: defaultTimeOfDay
+ },
+ {
+ literal: `${currentYear}-${currentMonth}-${currentDay + 2}`,
+ date_object: new Date(),
+ timeList: defaultTimeOfDay
+ }
+];
+
+export const otherDefaultDates: DateChoice[] = [
+ {
+ literal: `${currentYear}-${currentMonth}-${currentDay}`,
+ date_object: new Date(),
+ timeList: otherTimeOfDay
+ },
+ {
+ literal: `${currentYear}-${currentMonth}-${currentDay + 1}`,
+ date_object: new Date(currentYear, currentMonth, currentDay + 1),
+ timeList: defaultTimeOfDay
+ },
+ {
+ literal: `${currentYear}-${currentMonth}-${currentDay + 2}`,
+ date_object: new Date(),
+ timeList: otherTimeOfDay
+ }
+];
+export const defaultAnswers: PollAnswer[] = [{
+ id: 0,
+ text: 'réponse de démo 1',
+ file: '',
+ url: 'https://mastodon.cipherbliss.com/system/media_attachments/files/001/439/118/original/6fcf149bd902841b.png?1579471574',
+ literal: `${currentYear}-${currentMonth}-${currentDay}`,
+ date_object: new Date(),
+ timeList: otherSlicesOfDay
+},
+ {
+ id: 1,
+ text: 'réponse 2',
+ file: '',
+ url: 'https://mastodon.cipherbliss.com/system/media_attachments/files/001/439/118/original/6fcf149bd902841b.png?1579471574',
+ literal: `${currentYear}-${currentMonth}-${currentDay + 1}`,
+ date_object: new Date(),
+ timeList: basicSlicesOfDay
+ },
+ {
+ id: 2,
+ text: 'la réponse D',
+ file: '',
+ url: 'https://mastodon.cipherbliss.com/system/media_attachments/files/001/439/118/original/6fcf149bd902841b.png?1579471574',
+ literal: `${currentYear}-${currentMonth}-${currentDay + 2}`,
+ date_object: new Date(),
+ timeList: otherSlicesOfDay
+ }];
+
+ +
+ src/app/config/PollConfig.ts
+
+ Properties+ |
+
+
|
+
+ + date_object + | +
+ date_object:
+ |
+
+ Type : object
+
+ |
+
+ Optional + | +
+ + literal + | +
+ literal:
+ |
+
+ Type : string
+
+ |
+
+ + timeList + | +
+ timeList:
+ |
+
+ Type : any
+
+ |
+
import {environment} from "../../environments/environment";
+import {DateChoice, defaultAnswers, otherDefaultDates, PollAnswer} from "./defaultConfigs";
+
+export interface DateOption {
+ timeList: any;
+ literal: string;
+ date_object?: object;
+}
+
+const baseConfigValues = {
+ pollType: "classic",
+ title: "",
+ description: "",
+ myName: "",
+ myEmail: "",
+};
+
+
+/**
+ * configuration of the poll, add new fields at will
+ */
+export class PollConfig {
+ expiracyDateDefaultInDays = 60;
+ deletionDateAfterLastModification = 180;
+ step: number = 0; // step in the progress of creating a poll
+ stepMax: number = 3; // step max in the progress of creating a poll
+ pollType: string = 'dates';// classic or dates
+ title: string = 'titre';
+ description: string = 'ma description';
+ myName: string = 'mon pseudo';
+ myComment: string = 'wouah trop bien framadate!';
+ isAdmin: boolean = true;
+ myVoteStack: any;
+ myTempVoteStack = 0;
+ myEmail: string = "tktest@tktest.com";
+ myPolls: any = [];// list of retrieved polls from the backend api
+ // date specific poll, we have the choice to setup different hours (timeList) for all possible dates (dateList), or use the same hours for all dates
+ allowSeveralHours = 'true';
+ // access
+ visibility = 'link_only'; // visible to anyone with the link:
+ voteChoices = 'only_yes'; // possible answers to a vote choice: only "yes", "yes, maybe, no"
+ creationDate = new Date();
+ expirationDate = ''; // expiracy date
+ voteStackId = null; // id of the vote stack to update
+ pollId = null; // id of the current poll when created. data given by the backend api
+ pollSlug = null; // id of the current poll when created. data given by the backend api
+ currentPoll; // current poll selected with createPoll or getPoll of ConfigService
+ passwordAccess = 0;
+ password = '';
+ customUrl = ''; // custom slug in the url, must be unique
+ customUrlIsUnique = null; // given by the backend
+ urlSlugPublic = null;
+ urlPublic = environment.baseHref + '/#/poll/id/4';
+ urlAdmin = environment.baseHref + '/#/admin/d65es45fd45sdf45sd345f312sdf31sgfd345';
+ adminKey = ''; // key to change config of the poll
+ owner_modifier_token = ''; // key to change a vote stack
+ canModifyAnswers: boolean = true;// bool for the frontend selector
+ whoModifiesAnswers = "everybody";// everybody, self, nobody (= just admin)
+ whoCanChangeAnswers = 'everybody';// everybody, self, nobody (= just admin)
+ dateList: any = otherDefaultDates; // sets of days as strings, config to set identical time for days in a special days poll
+ timeList: DateChoice[] = otherDefaultDates; // ranges of time expressed as strings
+
+ answers: PollAnswer[] = defaultAnswers;
+
+ resetConfig() {
+ const self = this;
+ Object.keys(baseConfigValues).forEach((key) => {
+ self[key] = baseConfigValues[key];
+ })
+ }
+}
+
+ +
+ src/app/ui/selector/selector.component.ts
+
+ Properties+ |
+
+ + | +
+ + label + | +
+ label:
+ |
+
+ Type : string
+
+ |
+
+ + value + | +
+ value:
+ |
+
+ Type : string
+
+ |
+
import {Component, Input, OnInit} from '@angular/core';
+
+interface Item {
+ label: string,
+ value: string
+}
+
+interface SelectorConfig {
+ label: string,
+ name: string,
+ id: string,
+ selectorNgModel: any,
+ listItems: Item[]
+}
+
+@Component({
+ selector: 'framadate-selector',
+ templateUrl: './selector.component.html',
+ styleUrls: ['./selector.component.scss']
+})
+export class SelectorComponent implements OnInit {
+ @Input() selectorConfig: SelectorConfig;
+
+ constructor() {
+ }
+
+ ngOnInit() {
+ }
+
+}
+
+ +
+ src/app/config/defaultConfigs.ts
+
+ Properties+ |
+
+ + | +
+ + date_object + | +
+ date_object:
+ |
+
+ Type : Date
+
+ |
+
+ + file + | +
+ file:
+ |
+
+ Type : string
+
+ |
+
+ + id + | +
+ id:
+ |
+
+ Type : number
+
+ |
+
+ + literal + | +
+ literal:
+ |
+
+ Type : string
+
+ |
+
+ + text + | +
+ text:
+ |
+
+ Type : string
+
+ |
+
+ + timeList + | +
+ timeList:
+ |
+
+ Type : TimeSlices[]
+
+ |
+
+ + url + | +
+ url:
+ |
+
+ Type : string
+
+ |
+
export interface DateChoice {
+ literal: string,
+ timeList: TimeSlices[],
+ date_object: Date
+}
+
+export interface TimeSlices {
+ literal: string
+}
+
+export interface PollAnswer {
+ id: number,
+ text: string,
+ url: string,
+ file: string,
+ literal: string,
+ date_object: Date,
+ timeList: TimeSlices[]
+}
+
+const currentYear = new Date().getFullYear();
+const currentMonth = new Date().getMonth();
+const currentDay = new Date().getDate();
+
+export const basicSlicesOfDay: TimeSlices[] = [
+ {literal: 'matin'},
+ {literal: 'midi'},
+ {literal: 'soir'}
+];
+export const otherSlicesOfDay: TimeSlices[] = [
+ {literal: 'aux aurores'},
+ {literal: 'au petit dej'},
+ {literal: 'au deuxième petit dej des hobbits'}
+];
+export const defaultTimeOfDay: TimeSlices[] = (() => {
+ return [...basicSlicesOfDay]
+})();
+
+export const otherTimeOfDay: TimeSlices[] = (() => {
+ return [...otherSlicesOfDay]
+})();
+export const defaultDates: DateChoice[] = [
+ {
+ literal: `${currentYear}-${currentMonth}-${currentDay}`,
+ date_object: new Date(),
+ timeList: defaultTimeOfDay
+ },
+ {
+ literal: `${currentYear}-${currentMonth}-${currentDay + 1}`,
+ date_object: new Date(),
+ timeList: defaultTimeOfDay
+ },
+ {
+ literal: `${currentYear}-${currentMonth}-${currentDay + 2}`,
+ date_object: new Date(),
+ timeList: defaultTimeOfDay
+ }
+];
+
+export const otherDefaultDates: DateChoice[] = [
+ {
+ literal: `${currentYear}-${currentMonth}-${currentDay}`,
+ date_object: new Date(),
+ timeList: otherTimeOfDay
+ },
+ {
+ literal: `${currentYear}-${currentMonth}-${currentDay + 1}`,
+ date_object: new Date(currentYear, currentMonth, currentDay + 1),
+ timeList: defaultTimeOfDay
+ },
+ {
+ literal: `${currentYear}-${currentMonth}-${currentDay + 2}`,
+ date_object: new Date(),
+ timeList: otherTimeOfDay
+ }
+];
+export const defaultAnswers: PollAnswer[] = [{
+ id: 0,
+ text: 'réponse de démo 1',
+ file: '',
+ url: 'https://mastodon.cipherbliss.com/system/media_attachments/files/001/439/118/original/6fcf149bd902841b.png?1579471574',
+ literal: `${currentYear}-${currentMonth}-${currentDay}`,
+ date_object: new Date(),
+ timeList: otherSlicesOfDay
+},
+ {
+ id: 1,
+ text: 'réponse 2',
+ file: '',
+ url: 'https://mastodon.cipherbliss.com/system/media_attachments/files/001/439/118/original/6fcf149bd902841b.png?1579471574',
+ literal: `${currentYear}-${currentMonth}-${currentDay + 1}`,
+ date_object: new Date(),
+ timeList: basicSlicesOfDay
+ },
+ {
+ id: 2,
+ text: 'la réponse D',
+ file: '',
+ url: 'https://mastodon.cipherbliss.com/system/media_attachments/files/001/439/118/original/6fcf149bd902841b.png?1579471574',
+ literal: `${currentYear}-${currentMonth}-${currentDay + 2}`,
+ date_object: new Date(),
+ timeList: otherSlicesOfDay
+ }];
+
+ +
+ src/app/ui/selector/selector.component.ts
+
+ Properties+ |
+
+
|
+
+ + id + | +
+ id:
+ |
+
+ Type : string
+
+ |
+
+ + label + | +
+ label:
+ |
+
+ Type : string
+
+ |
+
+ + listItems + | +
+ listItems:
+ |
+
+ Type : Item[]
+
+ |
+
+ + name + | +
+ name:
+ |
+
+ Type : string
+
+ |
+
+ + selectorNgModel + | +
+ selectorNgModel:
+ |
+
+ Type : any
+
+ |
+
import {Component, Input, OnInit} from '@angular/core';
+
+interface Item {
+ label: string,
+ value: string
+}
+
+interface SelectorConfig {
+ label: string,
+ name: string,
+ id: string,
+ selectorNgModel: any,
+ listItems: Item[]
+}
+
+@Component({
+ selector: 'framadate-selector',
+ templateUrl: './selector.component.html',
+ styleUrls: ['./selector.component.scss']
+})
+export class SelectorComponent implements OnInit {
+ @Input() selectorConfig: SelectorConfig;
+
+ constructor() {
+ }
+
+ ngOnInit() {
+ }
+
+}
+
+ +
+ src/app/config/defaultConfigs.ts
+
+ Properties+ |
+
+
|
+
+ + literal + | +
+ literal:
+ |
+
+ Type : string
+
+ |
+
export interface DateChoice {
+ literal: string,
+ timeList: TimeSlices[],
+ date_object: Date
+}
+
+export interface TimeSlices {
+ literal: string
+}
+
+export interface PollAnswer {
+ id: number,
+ text: string,
+ url: string,
+ file: string,
+ literal: string,
+ date_object: Date,
+ timeList: TimeSlices[]
+}
+
+const currentYear = new Date().getFullYear();
+const currentMonth = new Date().getMonth();
+const currentDay = new Date().getDate();
+
+export const basicSlicesOfDay: TimeSlices[] = [
+ {literal: 'matin'},
+ {literal: 'midi'},
+ {literal: 'soir'}
+];
+export const otherSlicesOfDay: TimeSlices[] = [
+ {literal: 'aux aurores'},
+ {literal: 'au petit dej'},
+ {literal: 'au deuxième petit dej des hobbits'}
+];
+export const defaultTimeOfDay: TimeSlices[] = (() => {
+ return [...basicSlicesOfDay]
+})();
+
+export const otherTimeOfDay: TimeSlices[] = (() => {
+ return [...otherSlicesOfDay]
+})();
+export const defaultDates: DateChoice[] = [
+ {
+ literal: `${currentYear}-${currentMonth}-${currentDay}`,
+ date_object: new Date(),
+ timeList: defaultTimeOfDay
+ },
+ {
+ literal: `${currentYear}-${currentMonth}-${currentDay + 1}`,
+ date_object: new Date(),
+ timeList: defaultTimeOfDay
+ },
+ {
+ literal: `${currentYear}-${currentMonth}-${currentDay + 2}`,
+ date_object: new Date(),
+ timeList: defaultTimeOfDay
+ }
+];
+
+export const otherDefaultDates: DateChoice[] = [
+ {
+ literal: `${currentYear}-${currentMonth}-${currentDay}`,
+ date_object: new Date(),
+ timeList: otherTimeOfDay
+ },
+ {
+ literal: `${currentYear}-${currentMonth}-${currentDay + 1}`,
+ date_object: new Date(currentYear, currentMonth, currentDay + 1),
+ timeList: defaultTimeOfDay
+ },
+ {
+ literal: `${currentYear}-${currentMonth}-${currentDay + 2}`,
+ date_object: new Date(),
+ timeList: otherTimeOfDay
+ }
+];
+export const defaultAnswers: PollAnswer[] = [{
+ id: 0,
+ text: 'réponse de démo 1',
+ file: '',
+ url: 'https://mastodon.cipherbliss.com/system/media_attachments/files/001/439/118/original/6fcf149bd902841b.png?1579471574',
+ literal: `${currentYear}-${currentMonth}-${currentDay}`,
+ date_object: new Date(),
+ timeList: otherSlicesOfDay
+},
+ {
+ id: 1,
+ text: 'réponse 2',
+ file: '',
+ url: 'https://mastodon.cipherbliss.com/system/media_attachments/files/001/439/118/original/6fcf149bd902841b.png?1579471574',
+ literal: `${currentYear}-${currentMonth}-${currentDay + 1}`,
+ date_object: new Date(),
+ timeList: basicSlicesOfDay
+ },
+ {
+ id: 2,
+ text: 'la réponse D',
+ file: '',
+ url: 'https://mastodon.cipherbliss.com/system/media_attachments/files/001/439/118/original/6fcf149bd902841b.png?1579471574',
+ literal: `${currentYear}-${currentMonth}-${currentDay + 2}`,
+ date_object: new Date(),
+ timeList: otherSlicesOfDay
+ }];
+
+ +
+ src/app/pages/voting/voting-choice/voting-choice.component.ts
+
+ Properties+ |
+
+ + | +
+ + answer + | +
+ answer:
+ |
+
+ Type : "yes" | "no" | "maybe" | null
+
+ |
+
+ + date + | +
+ date:
+ |
+
+ Type : Date
+
+ |
+
+ Optional + | +
+ + false + | +
+ false:
+ |
+
+ + name + | +
+ name:
+ |
+
+ Type : string
+
+ |
+
+ Optional + | +
+ + simpleAnswer + | +
+ simpleAnswer:
+ |
+
+ Type : boolean
+
+ |
+
+ Optional + | +
+ + votes + | +
+ votes:
+ |
+
+ Type : literal type
+
+ |
+
+ Optional + | +
import {Component, ElementRef, Input} from '@angular/core';
+import {ConfigService} from "../../../services/config.service";
+
+interface VoteChoice {
+ votes?: {
+ yes: number
+ no: number
+ maybe: number
+ notAnswered: number
+ };
+ name?: string;
+ date?: Date;
+ answer: 'yes' | 'no' | 'maybe' | null;
+ simpleAnswer?: boolean
+ false; // enable if we display only a togglable "yes"
+}
+
+/**
+ * each vote choice takes a configuration from the container of all choices.
+ * this component is used to select a date choice, or a name answer
+ */
+@Component({
+ selector: 'framadate-voting-choice',
+ templateUrl: './voting-choice.component.html',
+ styleUrls: ['./voting-choice.component.scss']
+})
+export class VotingChoiceComponent {
+
+ public showChangeChoicebutton = false;
+ @Input() public choice: any;
+ @Input() public choices_count: any;
+ @Input() public choice_id: any;
+ @Input() public poll: any;
+ @Input() public simpleAnswer: boolean = true;
+ @Input() public pollIsSpecialDate: boolean = false;
+
+ constructor(private el: ElementRef,
+ private config: ConfigService) {
+
+ if (this.poll && this.poll.allowedAnswers) {
+ this.simpleAnswer = this.poll.allowedAnswers.length == 1
+ }
+ }
+
+ setAnswserTo(newAnswer: 'yes' | 'no' | 'maybe' | null) {
+ if (this.simpleAnswer) {
+ // only toggle yes to no
+ if (this.choice.answer && this.choice.answer === 'yes') {
+ this.choice.answer = 'no';
+ this.config.myTempVoteStack--;
+ } else {
+ this.choice.answer = newAnswer;
+ this.config.myTempVoteStack++;
+ }
+
+ } else {
+ this.choice.answer = newAnswer;
+ if (this.choice.answer !== newAnswer) {
+ if (newAnswer == 'maybe' || newAnswer == 'yes') {
+ this.config.myTempVoteStack++;
+ }
+ } else {
+ console.info('same answer as before')
+ }
+
+ }
+ this.el.nativeElement.blur();
+ }
+
+}
+
+