diff --git a/.angular-cli.json b/.angular-cli.json new file mode 100644 index 0000000..7aacfe0 --- /dev/null +++ b/.angular-cli.json @@ -0,0 +1,68 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "project": { + "name": "study-cave" + }, + "apps": [ + { + "root": "src", + "outDir": "dist", + "assets": [ + "assets", + "./favicon.ico" + ], + "index": "index.html", + "main": "main.ts", + "polyfills": "polyfills.ts", + "test": "test.ts", + "tsconfig": "tsconfig.app.json", + "testTsconfig": "tsconfig.spec.json", + "prefix": "app", + "styles": [ + "../node_modules/bootstrap/dist/css/bootstrap.min.css", + "../node_modules/primeicons/primeicons.css", + "../node_modules/primeng/resources/primeng.min.css", + "../node_modules/primeng/resources/themes/luna-amber/theme.css", + "../node_modules/primeng/resources/components/dialog/dialog.css", + "styles.css" + ], + "scripts": [ + "../node_modules/jquery/dist/jquery.min.js", + "../node_modules/bootstrap/dist/js/bootstrap.bundle.min.js" + ], + "environmentSource": "environments/environment.ts", + "environments": { + "dev": "environments/environment.ts", + "prod": "environments/environment.prod.ts" + } + } + ], + "e2e": { + "protractor": { + "config": "./protractor.conf.js" + } + }, + "lint": [ + { + "project": "src/tsconfig.app.json", + "exclude": "**/node_modules/**" + }, + { + "project": "src/tsconfig.spec.json", + "exclude": "**/node_modules/**" + }, + { + "project": "e2e/tsconfig.e2e.json", + "exclude": "**/node_modules/**" + } + ], + "test": { + "karma": { + "config": "./karma.conf.js" + } + }, + "defaults": { + "styleExt": "css", + "component": {} + } +} \ No newline at end of file diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..6e87a00 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +# Editor configuration, see http://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +max_line_length = off +trim_trailing_whitespace = false diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..70ab5ea --- /dev/null +++ b/.gitignore @@ -0,0 +1,43 @@ +# See http://help.github.com/ignore-files/ for more about ignoring files. + +# compiled output +/dist +/tmp +/out-tsc + +# dependencies +/node_modules +package-lock.json + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +# misc +/.sass-cache +/connect.lock +/coverage +/libpeerconnection.log +npm-debug.log +testem.log +/typings + +# e2e +/e2e/*.js +/e2e/*.map + +# System Files +.DS_Store +Thumbs.db diff --git a/.htaccess b/.htaccess new file mode 100644 index 0000000..7643899 --- /dev/null +++ b/.htaccess @@ -0,0 +1,7 @@ +Options +FollowSymLinks + +RewriteEngine On +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d +RewriteCond %{REQUEST_URI} !index +RewriteRule (.*) index.html [L] diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..6ab9a22 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,18 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Launch Program", + "program": "${workspaceFolder}\\serve", + "preLaunchTask": "tsc: build - tsconfig.json", + "outFiles": [ + "${workspaceFolder}/dist/out-tsc/**/*.js" + ] + } + ] +} \ No newline at end of file diff --git a/e2e/app.e2e-spec.ts b/e2e/app.e2e-spec.ts new file mode 100644 index 0000000..2138f07 --- /dev/null +++ b/e2e/app.e2e-spec.ts @@ -0,0 +1,14 @@ +import { AppPage } from './app.po'; + +describe('study-cave App', () => { + let page: AppPage; + + beforeEach(() => { + page = new AppPage(); + }); + + it('should display welcome message', () => { + page.navigateTo(); + expect(page.getParagraphText()).toEqual('Welcome to app!'); + }); +}); diff --git a/e2e/app.po.ts b/e2e/app.po.ts new file mode 100644 index 0000000..82ea75b --- /dev/null +++ b/e2e/app.po.ts @@ -0,0 +1,11 @@ +import { browser, by, element } from 'protractor'; + +export class AppPage { + navigateTo() { + return browser.get('/'); + } + + getParagraphText() { + return element(by.css('app-root h1')).getText(); + } +} diff --git a/e2e/tsconfig.e2e.json b/e2e/tsconfig.e2e.json new file mode 100644 index 0000000..1d9e5ed --- /dev/null +++ b/e2e/tsconfig.e2e.json @@ -0,0 +1,14 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/e2e", + "baseUrl": "./", + "module": "commonjs", + "target": "es5", + "types": [ + "jasmine", + "jasminewd2", + "node" + ] + } +} diff --git a/karma.conf.js b/karma.conf.js new file mode 100644 index 0000000..af139fa --- /dev/null +++ b/karma.conf.js @@ -0,0 +1,33 @@ +// Karma configuration file, see link for more information +// https://karma-runner.github.io/1.0/config/configuration-file.html + +module.exports = function (config) { + config.set({ + basePath: '', + frameworks: ['jasmine', '@angular/cli'], + plugins: [ + require('karma-jasmine'), + require('karma-chrome-launcher'), + require('karma-jasmine-html-reporter'), + require('karma-coverage-istanbul-reporter'), + require('@angular/cli/plugins/karma') + ], + client:{ + clearContext: false // leave Jasmine Spec Runner output visible in browser + }, + coverageIstanbulReporter: { + reports: [ 'html', 'lcovonly' ], + fixWebpackSourcePaths: true + }, + angularCli: { + environment: 'dev' + }, + reporters: ['progress', 'kjhtml'], + port: 9876, + colors: true, + logLevel: config.LOG_INFO, + autoWatch: true, + browsers: ['Chrome'], + singleRun: false + }); +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..c4f81d8 --- /dev/null +++ b/package.json @@ -0,0 +1,68 @@ +{ + "name": "study-cave", + "version": "0.0.0", + "license": "MIT", + "scripts": { + "ng": "ng", + "start": "ng serve", + "build": "ng build", + "test": "ng test", + "lint": "ng lint", + "e2e": "ng e2e" + }, + "private": true, + "dependencies": { + "@angular/animations": "^5.0.0", + "@angular/cdk": "^5.2.5", + "@angular/common": "^5.0.0", + "@angular/compiler": "^5.0.0", + "@angular/core": "^5.0.0", + "@angular/forms": "^5.0.0", + "@angular/http": "^5.0.0", + "@angular/material": "^5.2.5", + "@angular/platform-browser": "^5.0.0", + "@angular/platform-browser-dynamic": "^5.0.0", + "@angular/router": "^5.0.0", + "@auth0/angular-jwt": "^2.0.0", + "@fortawesome/fontawesome-svg-core": "^1.2.10", + "@fortawesome/free-solid-svg-icons": "^5.6.1", + "ag-grid-angular": "^19.1.2", + "ag-grid-community": "^19.1.4", + "ajv": "^6.0.0", + "angular-font-awesome": "^3.1.2", + "angular2-jwt": "^0.2.3", + "bootstrap": "^4.1.2", + "core-js": "^2.4.1", + "font-awesome": "^4.7.0", + "jquery": "^3.3.1", + "jquery-ui": "^1.12.1", + "picasso.js": "^0.18.2", + "popper.js": "^1.12.9", + "primeicons": "^1.0.0", + "primeng": "^6.1.7", + "rxjs": "^5.5.2", + "zone.js": "^0.8.14" + }, + "devDependencies": { + "@angular-devkit/core": "^7.1.2", + "@angular/cli": "^1.7.4", + "@angular/compiler-cli": "^5.0.0", + "@angular/language-service": "^5.0.0", + "@types/jasmine": "~2.5.53", + "@types/jasminewd2": "~2.0.2", + "@types/node": "~6.0.60", + "codelyzer": "^4.0.1", + "jasmine-core": "~2.5.2", + "jasmine-spec-reporter": "~4.1.0", + "karma": "~1.7.0", + "karma-chrome-launcher": "~2.1.1", + "karma-cli": "~1.0.1", + "karma-coverage-istanbul-reporter": "^1.2.1", + "karma-jasmine": "~1.1.0", + "karma-jasmine-html-reporter": "^0.2.2", + "protractor": "~5.1.2", + "ts-node": "~3.2.0", + "tslint": "~5.7.0", + "typescript": "~2.4.2" + } +} diff --git a/protractor.conf.js b/protractor.conf.js new file mode 100644 index 0000000..7ee3b5e --- /dev/null +++ b/protractor.conf.js @@ -0,0 +1,28 @@ +// Protractor configuration file, see link for more information +// https://github.com/angular/protractor/blob/master/lib/config.ts + +const { SpecReporter } = require('jasmine-spec-reporter'); + +exports.config = { + allScriptsTimeout: 11000, + specs: [ + './e2e/**/*.e2e-spec.ts' + ], + capabilities: { + 'browserName': 'chrome' + }, + directConnect: true, + baseUrl: 'http://localhost:4200/', + framework: 'jasmine', + jasmineNodeOpts: { + showColors: true, + defaultTimeoutInterval: 30000, + print: function() {} + }, + onPrepare() { + require('ts-node').register({ + project: 'e2e/tsconfig.e2e.json' + }); + jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); + } +}; diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts new file mode 100644 index 0000000..fcbe078 --- /dev/null +++ b/src/app/app-routing.module.ts @@ -0,0 +1,108 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +import { FlashcardsModule } from './flashcards/flashcards.module'; +import { TestsModule } from './tests/tests.module'; +import { UserModule } from './user/user.module'; +import { GroupsModule } from './groups/groups.module'; + +import { AuthGuard } from './auth-guard.service'; + +import { FlashcardsComponent } from './flashcards/flashcards/flashcards.component'; +import { FlashcardsSetsListComponent } from './flashcards/flashcards-sets-list/flashcards-sets-list.component'; +import { FlashcardsAddComponent } from './flashcards/flashcards-add/flashcards-add.component'; +import { FlashcardsAddCsvComponent } from './flashcards/flashcards-add-csv/flashcards-add-csv.component'; +import { FlashcardsAddTableComponent } from './flashcards/flashcards-add-table/flashcards-add-table.component'; +import { FlashcardsSetDetailComponent } from './flashcards/flashcards-set-detail/flashcards-set-detail.component'; +import { FlashcardsPairsTestComponent } from './flashcards/flashcards-pairs-test/flashcards-pairs-test.component'; +import { FlashcardsMemoryTestComponent } from './flashcards/flashcards-memory-test/flashcards-memory-test.component'; +import { FlashcardsEditTableComponent } from './flashcards/flashcards-edit-table/flashcards-edit-table.component'; +import { FlashcardsFillingInTestComponent } from './flashcards/flashcards-filling-in-test/flashcards-filling-in-test.component'; +import { FlashcardsTyperaceTestComponent } from './flashcards/flashcards-typerace-test/flashcards-typerace-test.component'; +import { HomePageComponent } from './home-page/home-page.component'; +import { LoginComponent } from './login/login.component'; +import { TestMakerComponent } from './tests/test-maker/test-maker.component'; +import { TestEditComponent } from './tests/test-edit/test-edit.component'; +import { RegisterComponent } from './user/register/register.component'; +import { UserComponent } from './user/user/user.component'; +import { EditUserComponent } from './user/edit-user/edit-user.component'; +import { MaterialsMenuComponent } from './materials/materials-menu/materials-menu.component'; +import { MaterialsListComponent } from './materials/materials-list/materials-list.component'; +import { MaterialsAddComponent } from './materials/materials-add/materials-add.component'; +import { TestsListComponent } from './tests/tests-list/tests-list.component'; +import { WorkInProgressComponent } from './work-in-progress/work-in-progress.component'; +import { TestDetailsComponent } from './tests/test-details/test-details.component'; +import { MaterialsDetailsComponent } from './materials/materials-details/materials-details.component'; +import { MyGroupsComponent } from './groups/my-groups/my-groups.component'; +import { GroupCreatorComponent } from './groups/group-creator/group-creator.component'; +import { JoinToGroupComponent } from './groups/join-to-group/join-to-group.component'; +import { GroupDetailsComponent } from './groups/group-details/group-details.component'; +import { ManageGroupComponent } from './groups/manage-group/manage-group.component'; +import { SharingResourcesInGroupsComponent } from './groups/sharing-resources-in-groups/sharing-resources-in-groups.component'; +import { CommentsComponent } from './shared/comments/comments.component'; +import { SharedModule } from './shared/shared.module'; +import { WaitingResourcesComponent } from './groups/waiting-resources/waiting-resources.component'; +import { BagdesComponent } from './user/bagdes/bagdes.component'; +import { RankingComponent } from './groups/ranking/ranking.component'; +import { HistoryOfActivityInGroupComponent } from './groups/history-of-activity-in-group/history-of-activity-in-group.component'; + + +const routes: Routes = [ + { path: 'login', component: LoginComponent }, + { path: '', component: HomePageComponent }, + { path: 'home', component: HomePageComponent }, + { path: 'flashcards', component: FlashcardsComponent }, + { path: 'flashcards/sets', component: FlashcardsSetsListComponent }, + { path: 'flashcards/add', component: FlashcardsAddComponent, canActivate: [AuthGuard] }, + { path: 'flashcards/add/table', component: FlashcardsAddTableComponent , canActivate: [AuthGuard]}, + { path: 'flashcards/add/csv', component: FlashcardsAddCsvComponent, canActivate: [AuthGuard] }, + { path: 'flashcards/sets/:id', component: FlashcardsSetDetailComponent }, + { path: 'flashcards/test-gen/flashcards-pairs/:id', component: FlashcardsPairsTestComponent }, + { path: 'flashcards/test-gen/flashcards-memory/:id', component: FlashcardsMemoryTestComponent }, + { path: 'flashcards/test-gen/flashcards-typerace/:id', component: FlashcardsTyperaceTestComponent }, + { path: 'flashcards/test-gen/flashcards-filling-in/:id', component: FlashcardsFillingInTestComponent }, + { path: 'flashcards/sets/edit/:id', component: FlashcardsEditTableComponent, canActivate: [AuthGuard] }, + { path: 'tests', component: TestsListComponent}, + { path: 'tests/:id', component: TestDetailsComponent}, + { path: 'tests/edit/:id', component: TestEditComponent, canActivate: [AuthGuard]}, + { path: 'test-maker', component: TestMakerComponent, canActivate: [AuthGuard] }, + { path: 'sign-up', component: RegisterComponent }, + { path: 'profile/:id', component: UserComponent, canActivate: [AuthGuard] }, + { path: 'edit-profile', component: EditUserComponent, canActivate: [AuthGuard] }, + { path: 'materials', component: MaterialsMenuComponent }, + { path: 'materials/list', component: MaterialsListComponent }, + { path: 'materials/add-materials', component: MaterialsAddComponent, canActivate: [AuthGuard] }, + { path: 'work-in-progress', component: WorkInProgressComponent }, + { path: 'materials/:id', component: MaterialsDetailsComponent}, + { path: 'my-groups', component: MyGroupsComponent, canActivate: [AuthGuard] }, + { path: 'create-group', component: GroupCreatorComponent, canActivate: [AuthGuard] }, + { path: 'join-to-group', component: JoinToGroupComponent, canActivate: [AuthGuard] }, + { path: 'groups/:id', component: GroupDetailsComponent, canActivate: [AuthGuard] }, + { path: 'groups/manage/:id', component: ManageGroupComponent, canActivate: [AuthGuard] }, + { path: 'groups/add-resources/:id', component: SharingResourcesInGroupsComponent , canActivate: [AuthGuard] }, + { path: 'groups/waiting-resources/:id', component: WaitingResourcesComponent, canActivate: [AuthGuard] }, + { path: 'badges', component: BagdesComponent, canActivate: [AuthGuard] }, + { path: 'groups/ranking/:id', component: RankingComponent , canActivate: [AuthGuard] }, + { path: 'groups/waiting-resources/:id', component: WaitingResourcesComponent, canActivate: [AuthGuard] }, + { path: 'groups/waiting-resources/:id', component: WaitingResourcesComponent, canActivate: [AuthGuard] }, + { path: 'groups/history/:id', component: HistoryOfActivityInGroupComponent, canActivate: [AuthGuard] } +]; + +@NgModule({ + imports: [ + RouterModule.forRoot(routes, {onSameUrlNavigation: 'reload', useHash: false}), + FlashcardsModule, + TestsModule, + UserModule, + GroupsModule, + SharedModule + ], + exports: [ + RouterModule, + FlashcardsModule, + TestsModule, + UserModule, + SharedModule + ] +}) +export class AppRoutingModule { } diff --git a/src/app/app.component.css b/src/app/app.component.css new file mode 100644 index 0000000..7653b75 --- /dev/null +++ b/src/app/app.component.css @@ -0,0 +1,30 @@ +.content{ + width: 100%; + position: fixed; + margin-top: 100px; + max-width: 100%; + overflow: auto; + height: calc(100vh - 154px); + min-height: calc(100vh - 154px); + +} + +.wrapper{ + overflow: auto; +} + +@media screen and (max-width: 800px) { + .content{ + margin-top: 170px; + height: calc(100vh - 200px); + min-height: calc(100vh - 200px); + } +} + +@media screen and (max-width: 352px) { + .content{ + margin-top: 170px; + height: calc(100vh - 250px); + min-height: calc(100vh - 250px); + } +} \ No newline at end of file diff --git a/src/app/app.component.html b/src/app/app.component.html new file mode 100644 index 0000000..1e83454 --- /dev/null +++ b/src/app/app.component.html @@ -0,0 +1,8 @@ +
+ +
+ +



+
+ +
\ No newline at end of file diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts new file mode 100644 index 0000000..2ac9564 --- /dev/null +++ b/src/app/app.component.spec.ts @@ -0,0 +1,31 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { AppComponent } from './app.component'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { RoutingStateService } from './routing-state.service'; +import { RouterTestingModule } from '@angular/router/testing'; + +describe('AppComponent', () => { + let component: AppComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ + AppComponent + ], + providers: [RoutingStateService], + imports: [RouterTestingModule], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(AppComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create the app', async(() => { + expect(component).toBeTruthy(); + })); +}); diff --git a/src/app/app.component.ts b/src/app/app.component.ts new file mode 100644 index 0000000..42be132 --- /dev/null +++ b/src/app/app.component.ts @@ -0,0 +1,17 @@ +import { Component, OnInit, Input } from '@angular/core'; +import { RoutingStateService } from './routing-state.service'; + +@Component({ + selector: 'app-root', + templateUrl: './app.component.html', + styleUrls: ['./app.component.css'] +}) +export class AppComponent implements OnInit { + title = 'app'; + + constructor(routingState: RoutingStateService) { + routingState.loadRouting(); + } + + ngOnInit() {} +} diff --git a/src/app/app.module.ts b/src/app/app.module.ts new file mode 100644 index 0000000..335c200 --- /dev/null +++ b/src/app/app.module.ts @@ -0,0 +1,60 @@ +import { BrowserModule } from '@angular/platform-browser'; +import { NgModule } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { HttpModule } from '@angular/http'; +import { HttpClientModule } from '@angular/common/http'; + +import { AppRoutingModule } from './app-routing.module'; +import { MaterialsModule } from './materials/materials.module'; +import { FlashcardsModule } from './flashcards/flashcards.module'; +import { TestsModule } from './tests/tests.module'; +import { GroupsModule } from './groups/groups.module'; +import { UserModule } from './user/user.module'; + +import { httpInterceptorProviders } from './http-interceptors/index'; +import { LocationStrategy, HashLocationStrategy, PathLocationStrategy } from '@angular/common'; + +import { LoginComponent } from './login/login.component'; +import { AppComponent } from './app.component'; +import { MainNavigationComponent } from './main-navigation/main-navigation.component'; +import { FooterComponent } from './footer/footer.component'; +import { HomePageComponent } from './home-page/home-page.component'; +import { WorkInProgressComponent } from './work-in-progress/work-in-progress.component'; + +import { AuthGuard } from './auth-guard.service'; +import { AuthenticationService } from './authentication.service'; +import { SharedModule } from './shared/shared.module'; +import { RoutingStateService } from './routing-state.service'; + +import { AutofocusDirective } from './autofocus.directive'; + + +@NgModule({ + declarations: [ + LoginComponent, + AppComponent, + MainNavigationComponent, + FooterComponent, + HomePageComponent, + WorkInProgressComponent, + AutofocusDirective + ], + imports: [ + BrowserModule, + FormsModule, + HttpModule, + HttpClientModule, + AppRoutingModule, + FlashcardsModule, + MaterialsModule, + TestsModule, + GroupsModule, + UserModule, + SharedModule + ], + providers: [AuthGuard, httpInterceptorProviders, AuthenticationService, RoutingStateService, { + provide: LocationStrategy, useClass: PathLocationStrategy + }], + bootstrap: [AppComponent] +}) +export class AppModule { } diff --git a/src/app/auth-guard.service.spec.ts b/src/app/auth-guard.service.spec.ts new file mode 100644 index 0000000..0ddcfcf --- /dev/null +++ b/src/app/auth-guard.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed, inject } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { AuthGuard } from './auth-guard.service'; + +describe('AuthGuardService', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [AuthGuard], + imports: [RouterTestingModule] + }); + }); + + it('should be created', inject([AuthGuard], (service: AuthGuard) => { + expect(service).toBeTruthy(); + })); +}); diff --git a/src/app/auth-guard.service.ts b/src/app/auth-guard.service.ts new file mode 100644 index 0000000..d9b7fb8 --- /dev/null +++ b/src/app/auth-guard.service.ts @@ -0,0 +1,19 @@ +import {Injectable} from '@angular/core'; +import {Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot} from '@angular/router'; + +@Injectable() +export class AuthGuard implements CanActivate { + constructor(private router: Router) { + } + canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { + if (localStorage.getItem('currentUser')) { + // logged in so return true + return true; + } + // tslint:disable-next-line:one-line + else { + this.router.navigate(['/login']); + return false; + } + } +} diff --git a/src/app/authentication.service.spec.ts b/src/app/authentication.service.spec.ts new file mode 100644 index 0000000..16f8317 --- /dev/null +++ b/src/app/authentication.service.spec.ts @@ -0,0 +1,19 @@ +import { TestBed, inject } from '@angular/core/testing'; +import { AuthenticationService } from './authentication.service'; +import { HttpClientModule } from '@angular/common/http'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; + +describe('AuthenticationService', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [AuthenticationService], + imports: [HttpClientModule, MatSnackBarModule], + schemas: [NO_ERRORS_SCHEMA] + }); + }); + + it('should be created', inject([AuthenticationService], (service: AuthenticationService) => { + expect(service).toBeTruthy(); + })); +}); diff --git a/src/app/authentication.service.ts b/src/app/authentication.service.ts new file mode 100644 index 0000000..d363e7a --- /dev/null +++ b/src/app/authentication.service.ts @@ -0,0 +1,71 @@ +import { Injectable, Output, EventEmitter } from '@angular/core'; +import { HttpClient, HttpHeaders, HttpRequest, HttpEvent } from '@angular/common/http'; +import { Observable } from 'rxjs/Observable'; +import 'rxjs/add/operator/map'; +import 'rxjs/add/operator/catch'; +import 'rxjs/add/observable/throw'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { Subject } from 'rxjs/Subject'; + +@Injectable() +export class AuthenticationService { + @Output() getLoggedInName: EventEmitter = new EventEmitter(); + private headers = new HttpHeaders({ 'Content-Type': 'application/json' }); + private storageSub = new Subject(); + public token: string; + constructor(private http: HttpClient, public snackBar: MatSnackBar) { + } + + watchStorage(): Observable { + return this.storageSub.asObservable(); + } + + watchStorageChanges(): void { + this.storageSub.next(true); + } + + + login(username: string, password: string): Observable { + return this.http.post('login', { password: password, username: username }, { headers: this.headers, observe: 'response' }) + .map((response) => { + // czy login ok jeśli w response jest token + // const token = response.json() && response.json().token; + const token = response.headers.get('authorization'); + if (token) { + // store username and jwt token w local storage aby nie wylogowało przy zmianie stron + localStorage.setItem('currentUser', JSON.stringify({ username: username, authorization: token })); + this.getLoggedInName.emit('logged'); + this.storageSub.next(true); + this.snackBar.open('Zalogowano pomyślnie!', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-success'] }); + // return true jeśli ok + return true; + } else { + // return false jeśli nie + this.getLoggedInName.emit('notLogged'); + return false; + } + }).catch((error: any) => Observable.throw(error.json().error || 'Server error')); + } + + getToken(): String { + const currentUser = JSON.parse(localStorage.getItem('currentUser')); + if (currentUser.authorization == null) { + return ' '; + } else { + return currentUser.authorization; + } + } + + isLoggedIn(): boolean { + const token: String = this.getToken(); + return token && token.length > 0; + } + + logout(): void { + // clear token remove user from local storage to log user out + this.snackBar.open('Wylogowano pomyślnie!', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-success'] }); + localStorage.removeItem('currentUser'); + } +} diff --git a/src/app/autofocus.directive.spec.ts b/src/app/autofocus.directive.spec.ts new file mode 100644 index 0000000..174c215 --- /dev/null +++ b/src/app/autofocus.directive.spec.ts @@ -0,0 +1,7 @@ +import { AutofocusDirective } from './autofocus.directive'; + +describe('AutofocusDirective', () => { + it('should create an instance', () => { + + }); +}); diff --git a/src/app/autofocus.directive.ts b/src/app/autofocus.directive.ts new file mode 100644 index 0000000..de8db2d --- /dev/null +++ b/src/app/autofocus.directive.ts @@ -0,0 +1,15 @@ +import { Directive, AfterViewInit, ElementRef } from '@angular/core'; + +@Directive({ + selector: '[appAutofocus]' +}) +export class AutofocusDirective implements AfterViewInit { + + constructor(private el: ElementRef) { + } + + ngAfterViewInit() { + this.el.nativeElement.focus(); + } + +} diff --git a/src/app/filter-user.pipe.spec.ts b/src/app/filter-user.pipe.spec.ts new file mode 100644 index 0000000..0752df4 --- /dev/null +++ b/src/app/filter-user.pipe.spec.ts @@ -0,0 +1,8 @@ +import { FilterUserPipe } from './filter-user.pipe'; + +describe('FilterUserPipe', () => { + it('create an instance', () => { + const pipe = new FilterUserPipe(); + expect(pipe).toBeTruthy(); + }); +}); diff --git a/src/app/filter-user.pipe.ts b/src/app/filter-user.pipe.ts new file mode 100644 index 0000000..b13c389 --- /dev/null +++ b/src/app/filter-user.pipe.ts @@ -0,0 +1,20 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'filterUser' +}) +export class FilterUserPipe implements PipeTransform { + + transform(sets: any, searchOwner: any ): any { + if (searchOwner === undefined || sets === undefined) { + return sets; + } else { + return sets.filter(function(set){ + return set.owner.toString() === (searchOwner.toString()); + }); + } + + } + +} + diff --git a/src/app/filter.pipe.spec.ts b/src/app/filter.pipe.spec.ts new file mode 100644 index 0000000..1427de3 --- /dev/null +++ b/src/app/filter.pipe.spec.ts @@ -0,0 +1,8 @@ +import { FilterPipe } from './filter.pipe'; + +describe('FilterPipe', () => { + it('create an instance', () => { + const pipe = new FilterPipe(); + expect(pipe).toBeTruthy(); + }); +}); diff --git a/src/app/filter.pipe.ts b/src/app/filter.pipe.ts new file mode 100644 index 0000000..d89f307 --- /dev/null +++ b/src/app/filter.pipe.ts @@ -0,0 +1,20 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'filter' +}) +export class FilterPipe implements PipeTransform { + + transform(sets: any, search: any ): any { + if (search === undefined || sets === undefined) { + return sets; + } else { + return sets.filter(function(set){ + return set.permission === (search); + }); + } + + } + +} + diff --git a/src/app/flashcards/flashcards-add-csv/flashcards-add-csv.component.css b/src/app/flashcards/flashcards-add-csv/flashcards-add-csv.component.css new file mode 100644 index 0000000..f8e46bb --- /dev/null +++ b/src/app/flashcards/flashcards-add-csv/flashcards-add-csv.component.css @@ -0,0 +1,55 @@ +.description { + background-color: #22272a; + padding: 1rem; +margin-top: 1rem; + margin-bottom: 2rem; + text-align: left; + +} + +.container { + background-color: #181616; + padding: 30px; + margin-top: 2rem; + margin-bottom: 2rem; + text-align: center; +} + +.wrapper-description { + display: flex; + justify-content: center; + align-content: center; +} + +.wrapper-add { + width: 100%; + display: flex; + justify-content: center; + align-content: center; + margin-bottom: 4rem; +} + +.wrapper{ + width: 100%; + padding: 30px; + min-height: 100%; + margin-bottom: 40px; +} + +.content{ + background-color: #181616; + padding: 30px; + margin-top: 2rem; + margin-bottom: 2rem; + text-align: center; + margin-left: auto; + margin-right: auto; +} + +ag-grid-angular{ + margin: 10px; +} + +.buttons-container > div{ + margin: 10px; +} \ No newline at end of file diff --git a/src/app/flashcards/flashcards-add-csv/flashcards-add-csv.component.html b/src/app/flashcards/flashcards-add-csv/flashcards-add-csv.component.html new file mode 100644 index 0000000..20361ca --- /dev/null +++ b/src/app/flashcards/flashcards-add-csv/flashcards-add-csv.component.html @@ -0,0 +1,53 @@ +
+
+
+ + +
+
+
+
+

Importowanie zestawu z pliku CSV

+
+

Twój plik CSV powinien mieć poniższy format:

+
+
+

+ nazwa zestawu;kategoria
+ lewa strona fiszki;prawa strona fiszki
+ lewa strona fiszki;prawa strona fiszki
+ lewa strona fiszki;prawa strona fiszki
+ ..... +

+

+ Przykład:
+ zwierzęta;angielski
+ pies;dog
+ kot;cat
+ królik;rabbit
+ ryba;fish
+

+
+ +
+
+
+ {{progress.percentage}}%
+
+ +
+
+ Udostępnij publicznie +

+ +

+ +
+
+
diff --git a/src/app/flashcards/flashcards-add-csv/flashcards-add-csv.component.spec.ts b/src/app/flashcards/flashcards-add-csv/flashcards-add-csv.component.spec.ts new file mode 100644 index 0000000..ea50fa9 --- /dev/null +++ b/src/app/flashcards/flashcards-add-csv/flashcards-add-csv.component.spec.ts @@ -0,0 +1,34 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FlashcardsAddCsvComponent } from './flashcards-add-csv.component'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { FlashcardsService } from '../flashcards.service'; +import { HttpClientModule } from '@angular/common/http'; +import { RouterTestingModule } from '@angular/router/testing'; +import { AuthenticationService } from '../../authentication.service'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; + +describe('FlashcardsAddCsvComponent', () => { + let component: FlashcardsAddCsvComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ FlashcardsAddCsvComponent ], + schemas: [NO_ERRORS_SCHEMA], + providers: [FlashcardsService, AuthenticationService], + imports: [HttpClientModule, RouterTestingModule, MatSnackBarModule] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(FlashcardsAddCsvComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/flashcards/flashcards-add-csv/flashcards-add-csv.component.ts b/src/app/flashcards/flashcards-add-csv/flashcards-add-csv.component.ts new file mode 100644 index 0000000..0633fb3 --- /dev/null +++ b/src/app/flashcards/flashcards-add-csv/flashcards-add-csv.component.ts @@ -0,0 +1,75 @@ +import { Component, OnInit } from '@angular/core'; +import { HttpClient, HttpResponse, HttpEventType } from '@angular/common/http'; +import { FlashcardsService } from '../flashcards.service'; +import { Router } from '@angular/router'; +import { MatSnackBar } from '@angular/material/snack-bar'; + +@Component({ + selector: 'app-flashcards-add-csv', + templateUrl: './flashcards-add-csv.component.html', + styleUrls: ['./flashcards-add-csv.component.css'] +}) +export class FlashcardsAddCsvComponent implements OnInit { + + selectedFiles: FileList; + currentFileUpload: File; + progress: { percentage: number } = { percentage: 0 }; + currentUser = JSON.parse(localStorage.getItem('currentUser')); + user: string; + permission: Boolean = true; + constructor(private uploadService: FlashcardsService, private router: Router, public snackBar: MatSnackBar) { } + + ngOnInit() { this.isLoggedIn(); } + + isLoggedIn() { + if (localStorage.getItem('currentUser') === null) { + this.user = 'Anonim'; + } else { + this.user = this.currentUser.username; + } + } + + changePermission(): void { + this.permission = !this.permission; + } + + selectFile(event) { + this.selectedFiles = event.target.files; + } + + upload() { + this.progress.percentage = 0; + const url = 'file/upload'; + let p = 'Private'; + if (this.permission) { + p = 'Public'; + } + this.currentFileUpload = this.selectedFiles.item(0); + if (this.currentFileUpload.type === 'application/vnd.ms-excel') { + this.uploadService.pushFileToStorage(this.currentFileUpload, this.user, p, url).subscribe( + event => { + if (event.type === HttpEventType.UploadProgress) { + this.progress.percentage = Math.round(100 * event.loaded / event.total); + } else if (event instanceof HttpResponse) { + this.currentFileUpload = undefined; + this.snackBar.open(`Plik został zaimportowany. + Swoje fiszki możesz podejrzeć na liście zestawów fiszek + i tam je edytować jeśli zajdzie taka potrzeba :)`, null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-success'] }); + this.router.navigate(['flashcards/sets']); + } + }, + error => { + this.snackBar.open('Coś poszło nie tak. Spróbuj ponownie później.', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + this.currentFileUpload = undefined; + } + ); + } else { + this.currentFileUpload = undefined; + this.snackBar.open('Wybierz plik CSV!', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } + this.selectedFiles = undefined; + } +} diff --git a/src/app/flashcards/flashcards-add-table/flashcards-add-table.component.css b/src/app/flashcards/flashcards-add-table/flashcards-add-table.component.css new file mode 100644 index 0000000..8507619 --- /dev/null +++ b/src/app/flashcards/flashcards-add-table/flashcards-add-table.component.css @@ -0,0 +1,49 @@ +label { + display: block; +} + +table, th, td, tr { + border-width: 0; + background-color: transparent; +} + +.container { + background-color: #181616; + padding: 30px; + margin-top: 2rem; + margin-bottom: 2rem; + text-align: center; +} + +.wrapper-add { + width: 100%; + display: flex; + justify-content: center; + align-content: center; + margin-bottom: 4rem; +} + +.wrapper{ + width: 100%; + padding: 30px; + min-height: 100%; + margin-bottom: 40px; +} + +.content{ + background-color: #181616; + padding: 30px; + margin-top: 2rem; + margin-bottom: 2rem; + text-align: center; + margin-left: auto; + margin-right: auto; +} + +ag-grid-angular{ + margin: 10px; +} + +.buttons-container > div{ + margin: 10px; +} \ No newline at end of file diff --git a/src/app/flashcards/flashcards-add-table/flashcards-add-table.component.html b/src/app/flashcards/flashcards-add-table/flashcards-add-table.component.html new file mode 100644 index 0000000..1eae5dc --- /dev/null +++ b/src/app/flashcards/flashcards-add-table/flashcards-add-table.component.html @@ -0,0 +1,68 @@ +
+
+
+ + +
+
+
+
+

Tworzenie zestawu

+
+
+ + +
+ +
+
+ + Udostępnij publicznie +
+

+ + +
+
+
+
\ No newline at end of file diff --git a/src/app/flashcards/flashcards-add-table/flashcards-add-table.component.spec.ts b/src/app/flashcards/flashcards-add-table/flashcards-add-table.component.spec.ts new file mode 100644 index 0000000..fdfaeee --- /dev/null +++ b/src/app/flashcards/flashcards-add-table/flashcards-add-table.component.spec.ts @@ -0,0 +1,44 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FlashcardsAddTableComponent } from './flashcards-add-table.component'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { FlashcardsService } from '../flashcards.service'; +import { HttpClientModule } from '@angular/common/http'; +import { RouterTestingModule } from '@angular/router/testing'; +import { AuthenticationService } from '../../authentication.service'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { FormsModule } from '@angular/forms'; + +describe('FlashcardsAddTableComponent', () => { + let component: FlashcardsAddTableComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ FlashcardsAddTableComponent ], + schemas: [NO_ERRORS_SCHEMA], + providers: [FlashcardsService, AuthenticationService], + imports: [HttpClientModule, RouterTestingModule, MatSnackBarModule, FormsModule] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(FlashcardsAddTableComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + // Testowanie funkcji w klasie + it('should change permissions', () => { + expect(component.permission).toBe(true); + component.changePermission(); + expect(component.permission).toBe(false); + component.changePermission(); + expect(component.permission).toBe(true); + }); +}); diff --git a/src/app/flashcards/flashcards-add-table/flashcards-add-table.component.ts b/src/app/flashcards/flashcards-add-table/flashcards-add-table.component.ts new file mode 100644 index 0000000..c3221b1 --- /dev/null +++ b/src/app/flashcards/flashcards-add-table/flashcards-add-table.component.ts @@ -0,0 +1,84 @@ +import { Component, OnInit } from '@angular/core'; +import { FlashcardsService } from '../flashcards.service'; +import { MatSnackBar } from '@angular/material/snack-bar'; + +@Component({ + selector: 'app-flashcards-add-table', + templateUrl: './flashcards-add-table.component.html', + styleUrls: ['./flashcards-add-table.component.css'] +}) +export class FlashcardsAddTableComponent implements OnInit { + + table: Boolean = false; + tableToSend: any = {}; + public fieldArray: Array = []; + newAttribute: any = {}; + currentUser; + permission: Boolean = true; + + constructor(private flashcardsService: FlashcardsService, public snackBar: MatSnackBar) { } + + ngOnInit() { this.isLoggedIn(); } + + changePermission(): void { + this.permission = !this.permission; + } + + isLoggedIn() { + if (localStorage.getItem('currentUser') === null) { + this.currentUser = 'Anonim'; + } else { + this.currentUser = JSON.parse(localStorage.getItem('currentUser')); + } + } + + addFieldValue() { + const undefinedAttr = ((this.newAttribute['left_side'] === undefined) || (this.newAttribute['right_side'] === undefined)); + if (undefinedAttr) { + this.snackBar.open('Nie można dodać fiszki z pustym polem!', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } else { + const length = ((this.newAttribute['left_side'].trim().length === 0) || (this.newAttribute['right_side'].trim().length === 0)); + if (length) { + this.snackBar.open('Nie można dodać fiszki z pustym polem!', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } else { + this.fieldArray.push(this.newAttribute); + this.newAttribute = {}; + } + } + } + + deleteFieldValue(index) { + this.fieldArray.splice(index, 1); + } + + setOwner(): string { + if (localStorage.getItem('currentUser')) { + return JSON.parse(localStorage.getItem('currentUser')).username; + } else { + return 'anonim'; + } + } + + addTable(value: any) { + // obsługa formularza dodawania fiszek do tabeli + if (this.fieldArray.length === 0) { + this.snackBar.open('Zestaw fiszek nie może być pusty!', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } else { + let p = 'Private'; + if (this.permission) { + p = 'Public'; + } + this.tableToSend = { + name: value.title, + category: value.category, + owner: this.setOwner(), + flashcards: this.fieldArray, + permission: p + }; + this.flashcardsService.add(this.tableToSend); + } + } +} diff --git a/src/app/flashcards/flashcards-add/flashcards-add.component.css b/src/app/flashcards/flashcards-add/flashcards-add.component.css new file mode 100644 index 0000000..a57c9dd --- /dev/null +++ b/src/app/flashcards/flashcards-add/flashcards-add.component.css @@ -0,0 +1,36 @@ + + +.wrapper{ + width: 100%; + padding: 30px; + min-height: 100%; + margin-bottom: 40px; +} + +@media screen and (max-width: 800px) { + .mobile{ + display: none; + } +} + +.empty{ + text-align: center; +} + +.content{ + background-color: #181616; + padding: 30px; + margin-top: 2rem; + margin-bottom: 2rem; + text-align: center; + margin-left: auto; + margin-right: auto; +} + +ag-grid-angular{ + margin: 10px; +} + +.buttons-container > div{ + margin: 10px; +} \ No newline at end of file diff --git a/src/app/flashcards/flashcards-add/flashcards-add.component.html b/src/app/flashcards/flashcards-add/flashcards-add.component.html new file mode 100644 index 0000000..0d9ede8 --- /dev/null +++ b/src/app/flashcards/flashcards-add/flashcards-add.component.html @@ -0,0 +1,9 @@ +
+
+
+ + + +
+
+
\ No newline at end of file diff --git a/src/app/flashcards/flashcards-add/flashcards-add.component.spec.ts b/src/app/flashcards/flashcards-add/flashcards-add.component.spec.ts new file mode 100644 index 0000000..2ea9f1b --- /dev/null +++ b/src/app/flashcards/flashcards-add/flashcards-add.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FlashcardsAddComponent } from './flashcards-add.component'; + +describe('FlashcardsAddComponent', () => { + let component: FlashcardsAddComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ FlashcardsAddComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(FlashcardsAddComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/flashcards/flashcards-add/flashcards-add.component.ts b/src/app/flashcards/flashcards-add/flashcards-add.component.ts new file mode 100644 index 0000000..2f48966 --- /dev/null +++ b/src/app/flashcards/flashcards-add/flashcards-add.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-flashcards-add', + templateUrl: './flashcards-add.component.html', + styleUrls: ['./flashcards-add.component.css'] +}) +export class FlashcardsAddComponent implements OnInit { + + constructor() { } + + ngOnInit() { + } + +} diff --git a/src/app/flashcards/flashcards-edit-table/flashcards-edit-table.component.css b/src/app/flashcards/flashcards-edit-table/flashcards-edit-table.component.css new file mode 100644 index 0000000..8507619 --- /dev/null +++ b/src/app/flashcards/flashcards-edit-table/flashcards-edit-table.component.css @@ -0,0 +1,49 @@ +label { + display: block; +} + +table, th, td, tr { + border-width: 0; + background-color: transparent; +} + +.container { + background-color: #181616; + padding: 30px; + margin-top: 2rem; + margin-bottom: 2rem; + text-align: center; +} + +.wrapper-add { + width: 100%; + display: flex; + justify-content: center; + align-content: center; + margin-bottom: 4rem; +} + +.wrapper{ + width: 100%; + padding: 30px; + min-height: 100%; + margin-bottom: 40px; +} + +.content{ + background-color: #181616; + padding: 30px; + margin-top: 2rem; + margin-bottom: 2rem; + text-align: center; + margin-left: auto; + margin-right: auto; +} + +ag-grid-angular{ + margin: 10px; +} + +.buttons-container > div{ + margin: 10px; +} \ No newline at end of file diff --git a/src/app/flashcards/flashcards-edit-table/flashcards-edit-table.component.html b/src/app/flashcards/flashcards-edit-table/flashcards-edit-table.component.html new file mode 100644 index 0000000..4e4346c --- /dev/null +++ b/src/app/flashcards/flashcards-edit-table/flashcards-edit-table.component.html @@ -0,0 +1,73 @@ +
+
+
+ +
+
+
+
+

Edycja zestawu

+
+
+
+
+ + +
+ +
+
+ +
+
+ + +
+
+
+
+
\ No newline at end of file diff --git a/src/app/flashcards/flashcards-edit-table/flashcards-edit-table.component.spec.ts b/src/app/flashcards/flashcards-edit-table/flashcards-edit-table.component.spec.ts new file mode 100644 index 0000000..2d66067 --- /dev/null +++ b/src/app/flashcards/flashcards-edit-table/flashcards-edit-table.component.spec.ts @@ -0,0 +1,35 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FlashcardsEditTableComponent } from './flashcards-edit-table.component'; +import { FormsModule } from '@angular/forms'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { FlashcardsService } from '../flashcards.service'; +import { HttpClientModule } from '@angular/common/http'; +import { AuthenticationService } from '../../authentication.service'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; + +describe('FlashcardsEditTableComponent', () => { + let component: FlashcardsEditTableComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ FlashcardsEditTableComponent ], + schemas: [NO_ERRORS_SCHEMA], + providers: [FlashcardsService, AuthenticationService], + imports: [ RouterTestingModule, FormsModule, HttpClientModule, MatSnackBarModule] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(FlashcardsEditTableComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/flashcards/flashcards-edit-table/flashcards-edit-table.component.ts b/src/app/flashcards/flashcards-edit-table/flashcards-edit-table.component.ts new file mode 100644 index 0000000..10e74fc --- /dev/null +++ b/src/app/flashcards/flashcards-edit-table/flashcards-edit-table.component.ts @@ -0,0 +1,106 @@ +import { Component, OnInit, OnDestroy, Input } from '@angular/core'; +import { FlashcardsService } from '../flashcards.service'; +import { Subscription } from 'rxjs/Subscription'; +import { ActivatedRoute } from '@angular/router'; +import { Router } from '@angular/router'; +import { MatSnackBar } from '@angular/material/snack-bar'; + +@Component({ + selector: 'app-flashcards-edit-table', + templateUrl: './flashcards-edit-table.component.html', + styleUrls: ['./flashcards-edit-table.component.css'] +}) +export class FlashcardsEditTableComponent implements OnInit, OnDestroy { + + ident: number; + permission: Boolean = false; + table: Boolean = false; + tableToSend: any = {}; + fieldArray: Array = []; + newAttribute: any = {}; + set: Object = {}; + flashcardSubscribtion: Subscription; + + constructor(private flashcardsService: FlashcardsService, private route: ActivatedRoute, private router: Router, + public snackBar: MatSnackBar) { } + + ngOnInit() { + this.ident = this.route.snapshot.params.id; + this.flashcardSubscribtion = this.flashcardsService.getSet(this.ident).subscribe(data => { + this.set = data; + if (data['permission'] === 'Private') { + this.permission = false; + } else { + this.permission = true; + } + const flashcards = data['flashcards']; + for (let i = 0; i < flashcards.length; i++) { + const id = flashcards[i]['id']; + this.fieldArray.push({ + id: id, + left_side: flashcards[i]['left_side'], + right_side: flashcards[i]['right_side'] + }); + } + }); + } + returnToSet() { + this.router.navigate(['/flashcards/sets/', this.ident]); + } + addFieldValue() { + const undefinedAttr = ((this.newAttribute['left_side'] === undefined) || (this.newAttribute['right_side'] === undefined)); + if (undefinedAttr) { + this.snackBar.open('Nie można dodać fiszki z pustym polem!', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } else { + const length = ((this.newAttribute['left_side'].trim().length === 0) || (this.newAttribute['right_side'].trim().length === 0)); + if (length) { + this.snackBar.open('Nie można dodać fiszki z pustym polem!', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } else { + const insert = { + id: null, + left_side: this.newAttribute['left_side'], + right_side: this.newAttribute['right_side'], + }; + this.fieldArray.push(insert); + this.newAttribute = {}; + } + } + } + + deleteFieldValue(index) { + this.fieldArray.splice(index, 1); + } + + addTable(value: any) { + const currentUser = JSON.parse(localStorage.getItem('currentUser')); + if (this.fieldArray.length === 0) { + this.snackBar.open('Zestaw fiszek nie może być pusty!', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } else { + let perm = 'Private'; + if (this.permission) { + perm = 'Public'; + } + this.tableToSend = { + id: this.ident, + name: value.title, + permission: perm, + category: value.category, + owner: currentUser.username, + flashcards: this.fieldArray + }; + this.flashcardsService.edit(this.tableToSend); + } + } + + changePermission(): void { + this.permission = !this.permission; + } + + ngOnDestroy() { + this.flashcardSubscribtion.unsubscribe(); + } + +} diff --git a/src/app/flashcards/flashcards-filling-in-test/flashcards-filling-in-test.component.css b/src/app/flashcards/flashcards-filling-in-test/flashcards-filling-in-test.component.css new file mode 100644 index 0000000..f5d8eae --- /dev/null +++ b/src/app/flashcards/flashcards-filling-in-test/flashcards-filling-in-test.component.css @@ -0,0 +1,43 @@ +#container-small { + background-color: #181616; + padding: 30px; + width: 32%; + height: 400px; + box-sizing: border-box; + position: relative; + text-align: center; +} + +.content { + display: flex; + justify-content: space-around; + align-content: center; + margin-top: 2rem; +} + +#container-large { + background-color: #181616; + padding: 30px; + width: 62%; + min-height: 400px; + text-align: center; +} + +.container { + margin-bottom: 4rem; +} + +@media screen and (max-width: 800px) { + .content { + flex-wrap: wrap; + } + + #container-small { + height: 250px; + } + + #container-small, #container-large { + width: 90%; + margin-bottom: 2.5rem; + } +} \ No newline at end of file diff --git a/src/app/flashcards/flashcards-filling-in-test/flashcards-filling-in-test.component.html b/src/app/flashcards/flashcards-filling-in-test/flashcards-filling-in-test.component.html new file mode 100644 index 0000000..a480f7f --- /dev/null +++ b/src/app/flashcards/flashcards-filling-in-test/flashcards-filling-in-test.component.html @@ -0,0 +1,59 @@ +
+
+

Uzupełnianie fiszek

+
+
+ Nazwa zestawu: + {{ name }} +
+ Kategoria: + {{ category }} +
+
+
+
+ Uzupełniono: + {{ filled }} z {{ length_test }} fiszek +
+
+ Poprawnych odpowiedzi: + {{ good }} na {{ length_test }}
+ Złych odpowiedzi: + {{ bad }} na {{ length_test }} +
+
+
+
+
+
+
+ + Poprzednia odpowiedź:
+ {{flashcards[index-1].content}} {{allAnswers[index-1].answer}} +
+
+ Podaj odpowiedź:
+ {{flashcards[index].content}} + + +
+
ODPOWIEDŹ POPRAWNA
+
ODPOWIEDŹ NIEPOPRAWNA
+
+ Następne pytanie:
+ {{flashcards[index+1].content}} +
+
+ + +
+ +
+
+ +
+ + +
+ +
diff --git a/src/app/flashcards/flashcards-filling-in-test/flashcards-filling-in-test.component.spec.ts b/src/app/flashcards/flashcards-filling-in-test/flashcards-filling-in-test.component.spec.ts new file mode 100644 index 0000000..c0b94fd --- /dev/null +++ b/src/app/flashcards/flashcards-filling-in-test/flashcards-filling-in-test.component.spec.ts @@ -0,0 +1,34 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FlashcardsFillingInTestComponent } from './flashcards-filling-in-test.component'; +import { FormsModule } from '@angular/forms'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { FlashcardsService } from '../flashcards.service'; +import { HttpClientModule } from '@angular/common/http'; +import { AuthenticationService } from '../../authentication.service'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; + +describe('FlashcardsFillingInTestComponent', () => { + let component: FlashcardsFillingInTestComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ FlashcardsFillingInTestComponent ], + schemas: [NO_ERRORS_SCHEMA], + providers: [FlashcardsService, AuthenticationService], + imports: [ RouterTestingModule, FormsModule, HttpClientModule, MatSnackBarModule] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(FlashcardsFillingInTestComponent); + component = fixture.componentInstance; + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/flashcards/flashcards-filling-in-test/flashcards-filling-in-test.component.ts b/src/app/flashcards/flashcards-filling-in-test/flashcards-filling-in-test.component.ts new file mode 100644 index 0000000..a12dfdd --- /dev/null +++ b/src/app/flashcards/flashcards-filling-in-test/flashcards-filling-in-test.component.ts @@ -0,0 +1,96 @@ +import { Component, OnInit, Input, OnDestroy } from '@angular/core'; +import { FlashcardsService } from '../flashcards.service'; +import { Subscription } from 'rxjs/Subscription'; +import { ActivatedRoute } from '@angular/router'; + +@Component({ + selector: 'app-flashcards-filling-in-test', + templateUrl: './flashcards-filling-in-test.component.html', + styleUrls: ['./flashcards-filling-in-test.component.css'] +}) +export class FlashcardsFillingInTestComponent implements OnInit, OnDestroy { + + id: number; + flashcardSubscribtionMeta: Subscription; + flashcardSubscribtion: Subscription; + flashcardSubscribtionCheck: Subscription; + name: String; + category: String; + length_test: number; + goodNow: number; + started: Boolean = false; + finish: Boolean = false; + flashcards: Array = []; + filled = 0; + good = 0; + bad = 0; + answer: String; + index = 0; + not_last: Boolean = true; + is_correct: Boolean; + allAnswers = []; + + + constructor(private flashcardsService: FlashcardsService, private route: ActivatedRoute) { } + + ngOnInit() { + this.id = this.route.snapshot.params.id; + this.flashcardSubscribtionMeta = this.flashcardsService.getSet(this.id).subscribe(data => { + this.name = data['name']; + this.category = data['category']; + }); + this.flashcardSubscribtion = this.flashcardsService.getTestFilling(this.id).subscribe(data => { + this.length_test = data.length; + this.flashcards = data; + }); + + } + + start() { + this.started = true; + } + finished() { + this.finish = true; + } + verifyAnswer() { + const body = []; + const n = this.length_test; + if (this.answer === '') { + this.answer = ' '; + } + this.allAnswers.push({ + id: this.index, + answer: this.answer, + }); + body.push({ + id: this.flashcards[this.index]['id'], + content: this.answer, + side: this.flashcards[this.index]['side'], + }); + this.flashcardSubscribtionCheck = this.flashcardsService.testCheck(this.id, body[0]) + .subscribe(data => { + this.is_correct = data.result; + if (this.is_correct === true) { + this.good = this.good + 1; + } else { + this.bad = this.bad + 1; + } + }); + if (this.index < this.length_test) { + this.index = this.index + 1; + this.filled = this.filled + 1; + this.answer = ''; + } + if (this.index === this.length_test) { + this.not_last = false; + } + } + + ngOnDestroy() { + this.flashcardSubscribtionMeta.unsubscribe(); + this.flashcardSubscribtion.unsubscribe(); + if (this.flashcardSubscribtionCheck) { + this.flashcardSubscribtionCheck.unsubscribe(); + } + } +} diff --git a/src/app/flashcards/flashcards-memory-test-set/flashcards-memory-test-set.component.css b/src/app/flashcards/flashcards-memory-test-set/flashcards-memory-test-set.component.css new file mode 100644 index 0000000..1421026 --- /dev/null +++ b/src/app/flashcards/flashcards-memory-test-set/flashcards-memory-test-set.component.css @@ -0,0 +1,26 @@ +h2 { + text-align: center; + font-weight: 900; +} + +.clickable { + cursor: pointer; +} + +.card { + color: white; + background-color: gray; + text-align: center; + min-height: 4rem; + display: flex; + justify-content: center; + transition: color 1000ms; +} + +.card-invisible { + font-size: 2.5rem; +} + +.card-visible { + font-size: 1.5rem; +} \ No newline at end of file diff --git a/src/app/flashcards/flashcards-memory-test-set/flashcards-memory-test-set.component.html b/src/app/flashcards/flashcards-memory-test-set/flashcards-memory-test-set.component.html new file mode 100644 index 0000000..4a1283c --- /dev/null +++ b/src/app/flashcards/flashcards-memory-test-set/flashcards-memory-test-set.component.html @@ -0,0 +1,14 @@ +
+
+

 

+

Dobrze :)

+

Źle :(

+
+
+
+
+

???

+

{{ card }}

+
+
+
diff --git a/src/app/flashcards/flashcards-memory-test-set/flashcards-memory-test-set.component.spec.ts b/src/app/flashcards/flashcards-memory-test-set/flashcards-memory-test-set.component.spec.ts new file mode 100644 index 0000000..ced1253 --- /dev/null +++ b/src/app/flashcards/flashcards-memory-test-set/flashcards-memory-test-set.component.spec.ts @@ -0,0 +1,34 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FlashcardsMemoryTestSetComponent } from './flashcards-memory-test-set.component'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { FlashcardsService } from '../flashcards.service'; +import { AuthenticationService } from '../../authentication.service'; +import { RouterTestingModule } from '@angular/router/testing'; +import { FormsModule } from '@angular/forms'; +import { HttpClientModule } from '@angular/common/http'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; + +describe('FlashcardsMemoryTestSetComponent', () => { + let component: FlashcardsMemoryTestSetComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ FlashcardsMemoryTestSetComponent ], + schemas: [NO_ERRORS_SCHEMA], + providers: [FlashcardsService, AuthenticationService], + imports: [ RouterTestingModule, FormsModule, HttpClientModule, MatSnackBarModule] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(FlashcardsMemoryTestSetComponent); + component = fixture.componentInstance; + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/flashcards/flashcards-memory-test-set/flashcards-memory-test-set.component.ts b/src/app/flashcards/flashcards-memory-test-set/flashcards-memory-test-set.component.ts new file mode 100644 index 0000000..7b4a8d2 --- /dev/null +++ b/src/app/flashcards/flashcards-memory-test-set/flashcards-memory-test-set.component.ts @@ -0,0 +1,121 @@ +import { Component, OnInit, OnChanges, SimpleChanges, Input, Output, EventEmitter, OnDestroy } from '@angular/core'; +import { FlashcardsService } from '../flashcards.service'; +import { Subscription } from 'rxjs/Subscription'; +import { MatSnackBar } from '@angular/material/snack-bar'; + +@Component({ + selector: 'app-flashcards-memory-test-set', + templateUrl: './flashcards-memory-test-set.component.html', + styleUrls: ['./flashcards-memory-test-set.component.css'] +}) +export class FlashcardsMemoryTestSetComponent implements OnInit, OnChanges, OnDestroy { + + @Input() package: Array; + @Input() id: number; + @Input() package_id: number; + + @Output() goodEvent = new EventEmitter(); + @Output() isChecked = new EventEmitter(); + + flashcardSubscribtion: Array = []; + answer: Array = []; + set: Array = []; + visible: Array = []; + toCheck: Array = []; + isBad: Boolean = false; + isOK: Boolean = false; + good = 0; + clicks = 0; + + constructor(private uploadService: FlashcardsService, public snackBar: MatSnackBar) { } + + ngOnInit() { + this.isChecked.emit(false); + this.flashcardSubscribtion = []; + this.visible = []; + this.set = this.package[this.package_id]['set']; + for (let i = 0; i < this.set.length; i++) { + this.visible.push(false); + } + } + + ngOnChanges(changes: SimpleChanges) { + this.ngOnDestroy(); + const package_idChanges = changes['package_id']; + if (package_idChanges) { + this.ngOnInit(); + } + } + + check(event) { + if (this.clicks <= 2) { + this.clicks += 1; + this.toCheck.push(event.target.id); + this.visible[this.toCheck[0]] = true; + if (this.clicks === 2) { + this.visible[this.toCheck[1]] = true; + const toSend = { + x: this.set[this.toCheck[0]], + y: this.set[this.toCheck[1]], + }; + setTimeout(() => { + this.flashcardSubscribtion[this.flashcardSubscribtion.length] = + this.uploadService.testMemory(this.id, toSend).subscribe(data => { + this.showWrong(data); + this.clicks = 0; + }, + error => { + this.snackBar.open('Coś poszło nie tak :( Spróbuj ponownie później.', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + this.clicks = 0; + } + ); + }, 1000); + } + } + } + + showWrong(answer: Boolean) { + this.isGood(answer); + this.goodEvent.emit(this.good); + } + + isGood(answer: Boolean) { + if (!answer) { + this.isBad = true; + this.visible[this.toCheck[0]] = false; + this.visible[this.toCheck[1]] = false; + setTimeout(() => { + this.isBad = false; + }, 1000); + } else { + this.isOK = true; + this.visible[this.toCheck[0]] = true; + this.visible[this.toCheck[1]] = true; + document.getElementById(this.toCheck[0].toString()).style.color = 'rgb(94, 249, 37)'; + document.getElementById(this.toCheck[1].toString()).style.color = 'rgb(94, 249, 37)'; + setTimeout(() => { + this.isOK = false; + }, 1000); + this.good += 1; + let i = 0; + while (this.visible[i]) { + if (i === (this.visible.length - 1)) { + this.isChecked.emit(true); + } + i++; + if (i === this.visible.length) { + break; + } + } + } + this.toCheck = []; + } + + ngOnDestroy() { + for (let i = 0; i < this.flashcardSubscribtion.length; i++) { + this.flashcardSubscribtion[i].unsubscribe(); + } + } + +} diff --git a/src/app/flashcards/flashcards-memory-test/flashcards-memory-test.component.css b/src/app/flashcards/flashcards-memory-test/flashcards-memory-test.component.css new file mode 100644 index 0000000..f5d8eae --- /dev/null +++ b/src/app/flashcards/flashcards-memory-test/flashcards-memory-test.component.css @@ -0,0 +1,43 @@ +#container-small { + background-color: #181616; + padding: 30px; + width: 32%; + height: 400px; + box-sizing: border-box; + position: relative; + text-align: center; +} + +.content { + display: flex; + justify-content: space-around; + align-content: center; + margin-top: 2rem; +} + +#container-large { + background-color: #181616; + padding: 30px; + width: 62%; + min-height: 400px; + text-align: center; +} + +.container { + margin-bottom: 4rem; +} + +@media screen and (max-width: 800px) { + .content { + flex-wrap: wrap; + } + + #container-small { + height: 250px; + } + + #container-small, #container-large { + width: 90%; + margin-bottom: 2.5rem; + } +} \ No newline at end of file diff --git a/src/app/flashcards/flashcards-memory-test/flashcards-memory-test.component.html b/src/app/flashcards/flashcards-memory-test/flashcards-memory-test.component.html new file mode 100644 index 0000000..ebdfc27 --- /dev/null +++ b/src/app/flashcards/flashcards-memory-test/flashcards-memory-test.component.html @@ -0,0 +1,49 @@ +
+
+
+

Memory

+
+
+ Nazwa zestawu: + {{ name }} +
+ Kategoria: + {{ category }} +
+
+
+
+ Uzupełniono: + {{ filled }} z {{ flashcards }} fiszek +
+
+ Poprawnych odpowiedzi: + {{ good }} na {{ flashcards }} +
+
+
+
+
+
+ +
+
+ Ekran: + {{ package_id + 1 }} z {{ length_packages }} +
+
+
+ +
+
+
+ + + +
+
+ +
+
+
diff --git a/src/app/flashcards/flashcards-memory-test/flashcards-memory-test.component.spec.ts b/src/app/flashcards/flashcards-memory-test/flashcards-memory-test.component.spec.ts new file mode 100644 index 0000000..ba9a6c2 --- /dev/null +++ b/src/app/flashcards/flashcards-memory-test/flashcards-memory-test.component.spec.ts @@ -0,0 +1,34 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FlashcardsMemoryTestComponent } from './flashcards-memory-test.component'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { FlashcardsService } from '../flashcards.service'; +import { AuthenticationService } from '../../authentication.service'; +import { RouterTestingModule } from '@angular/router/testing'; +import { FormsModule } from '@angular/forms'; +import { HttpClientModule } from '@angular/common/http'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; + +describe('FlashcardsMemoryTestComponent', () => { + let component: FlashcardsMemoryTestComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ FlashcardsMemoryTestComponent ], + schemas: [NO_ERRORS_SCHEMA], + providers: [FlashcardsService, AuthenticationService], + imports: [ RouterTestingModule, FormsModule, HttpClientModule, MatSnackBarModule] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(FlashcardsMemoryTestComponent); + component = fixture.componentInstance; + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/flashcards/flashcards-memory-test/flashcards-memory-test.component.ts b/src/app/flashcards/flashcards-memory-test/flashcards-memory-test.component.ts new file mode 100644 index 0000000..9be83db --- /dev/null +++ b/src/app/flashcards/flashcards-memory-test/flashcards-memory-test.component.ts @@ -0,0 +1,102 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { FlashcardsService } from '../flashcards.service'; +import { Subscription } from 'rxjs/Subscription'; +import { ActivatedRoute } from '@angular/router'; + +@Component({ + selector: 'app-flashcards-memory-test', + templateUrl: './flashcards-memory-test.component.html', + styleUrls: ['./flashcards-memory-test.component.css'] +}) +export class FlashcardsMemoryTestComponent implements OnInit, OnDestroy { + + id: number; + flashcardSubscribtionMeta: Subscription; + flashcardSubscribtion: Subscription; + name: String; + category: String; + length_test: number; + flashcards: number; + length_packages: number; + goodNow: number; + started: Boolean = false; + finish: Boolean = false; + checked: Boolean = false; + not_last: Boolean = true; + packages: Array = []; + filled = 0; + good = 0; + package_id = 0; + + constructor(private flashcardsService: FlashcardsService, private route: ActivatedRoute) {} + + ngOnInit() { + this.id = this.route.snapshot.params.id; + this.flashcardSubscribtionMeta = this.flashcardsService.getSet(this.id).subscribe(data => { + this.name = data['name']; + this.category = data['category']; + }); + this.flashcardSubscribtion = this.flashcardsService.getTestMemory(this.id).subscribe(data => { + this.length_test = data.length; + this.flashcards = data.length / 2; + this.createPackages(data); + }); + } + + createPackages(data) { + let attribute = '0'; + let set = []; + const n = this.length_test; + for (let i = 0; i < n; i++) { + if (i % 10 === 0) { + this.packages[attribute] = { + set: set, + }; + attribute = (i / 10).toString(); + set = []; + } + set.push(data[i]); + if (i === (n - 1)) { + this.packages[attribute] = { + set: set, + }; + } + } + this.length_packages = this.packages.length; + if (this.packages.length < 2) { + this.not_last = false; + } + } + + start() { + this.started = true; + } + + increment() { + if (this.package_id === (this.length_packages - 2)) { + this.not_last = false; + } + if (this.package_id === (this.length_packages - 1)) { + this.started = false; + this.finish = true; + } else { + this.package_id += 1; + } + } + + goodEvent(goods) { + this.goodNow = goods; + this.filled = this.goodNow; + this.good = this.goodNow; + } + + isChecked(check) { + this.checked = check; + } + + ngOnDestroy() { + this.flashcardSubscribtionMeta.unsubscribe(); + this.flashcardSubscribtion.unsubscribe(); + } + +} diff --git a/src/app/flashcards/flashcards-pairs-test-set/flashcards-pairs-test-set.component.css b/src/app/flashcards/flashcards-pairs-test-set/flashcards-pairs-test-set.component.css new file mode 100644 index 0000000..50130c9 --- /dev/null +++ b/src/app/flashcards/flashcards-pairs-test-set/flashcards-pairs-test-set.component.css @@ -0,0 +1,94 @@ +#container-small { + background-color: #181616; + padding: 30px; + width: 25%; + height: 400px; + margin: 25px; + float:left; + box-sizing: border-box; + position: relative; + text-align: center; +} +.content{ + position:fixed; + width:100%; + box-sizing: border-box; + max-width: 100%; +} +#container-large { + background-color: #181616; + padding: 30px; + width: 65%; + height: 400px; + margin: 25px; + float: right; + box-sizing: border-box; + position: relative; + text-align: center; +} + +.title{ + font-size: 48px; +} + +.text-content{ + font-size: 18px; +} + +.puzzle{ + display: flex; + min-height: 50px; + min-width: 100px; + background-color: rgb(206, 168, 86); + color: black; + align-items: center; + justify-content: center; + margin: 5px 0 5px 5px; + cursor: pointer; +} + +.puzzle-right{ + display: flex; + color: black; + min-height: 50px; + min-width: 100px; + background-color: rgb(202, 144, 17); + align-items: center; + justify-content: center; + margin: 5px 5px 5px 0; +} + +.both-sides{ + display: flex; + flex-direction: row; + flex-wrap: nowrap; +} + +.puzzle:hover{ + border: 3px solid gray; +} + +.active{ + border: 3px solid white; +} + +.puzzles-container{ + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: center; + align-items: center; +} + +.empty{ + background-color: gray; + cursor: not-allowed; +} + +.good{ + background-color: green; +} + +.wrong{ + background-color: red; +} \ No newline at end of file diff --git a/src/app/flashcards/flashcards-pairs-test-set/flashcards-pairs-test-set.component.html b/src/app/flashcards/flashcards-pairs-test-set/flashcards-pairs-test-set.component.html new file mode 100644 index 0000000..60e160e --- /dev/null +++ b/src/app/flashcards/flashcards-pairs-test-set/flashcards-pairs-test-set.component.html @@ -0,0 +1,31 @@ +
+

Dostępne lewe strony:

+
+
+ {{item.left_side}} +
+
+

Twoja odpowiedź:

+
+
+
+ {{item.left_side}} + +
+
+ {{item.left_side}} + +
+
+ {{item.left_side}} + +
+
+ {{rightSides[i].right_side}} +
+
+ +
+
+ +
\ No newline at end of file diff --git a/src/app/flashcards/flashcards-pairs-test-set/flashcards-pairs-test-set.component.spec.ts b/src/app/flashcards/flashcards-pairs-test-set/flashcards-pairs-test-set.component.spec.ts new file mode 100644 index 0000000..31964ef --- /dev/null +++ b/src/app/flashcards/flashcards-pairs-test-set/flashcards-pairs-test-set.component.spec.ts @@ -0,0 +1,34 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FlashcardsPairsTestSetComponent } from './flashcards-pairs-test-set.component'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { FlashcardsService } from '../flashcards.service'; +import { AuthenticationService } from '../../authentication.service'; +import { RouterTestingModule } from '@angular/router/testing'; +import { FormsModule } from '@angular/forms'; +import { HttpClientModule } from '@angular/common/http'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; + +describe('FlashcardsPairsTestSetComponent', () => { + let component: FlashcardsPairsTestSetComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ FlashcardsPairsTestSetComponent ], + schemas: [NO_ERRORS_SCHEMA], + providers: [FlashcardsService, AuthenticationService], + imports: [ RouterTestingModule, FormsModule, HttpClientModule, MatSnackBarModule] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(FlashcardsPairsTestSetComponent); + component = fixture.componentInstance; + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/flashcards/flashcards-pairs-test-set/flashcards-pairs-test-set.component.ts b/src/app/flashcards/flashcards-pairs-test-set/flashcards-pairs-test-set.component.ts new file mode 100644 index 0000000..3589a34 --- /dev/null +++ b/src/app/flashcards/flashcards-pairs-test-set/flashcards-pairs-test-set.component.ts @@ -0,0 +1,121 @@ +import { Component, OnInit, OnChanges, SimpleChanges, Input, Output, EventEmitter, OnDestroy } from '@angular/core'; +import { FlashcardsService } from '../flashcards.service'; +import { Subscription } from 'rxjs/Subscription'; +import * as $ from 'jquery'; +import { ISubscription } from 'rxjs/Subscription'; + +@Component({ + selector: 'app-flashcards-pairs-test-set', + templateUrl: './flashcards-pairs-test-set.component.html', + styleUrls: ['./flashcards-pairs-test-set.component.css'] +}) +export class FlashcardsPairsTestSetComponent implements OnInit, OnChanges, OnDestroy { + + @Input() package: Array; + @Input() id: number; + @Input() package_id: number; + + @Output() goodEvent = new EventEmitter(); + @Output() isChecked = new EventEmitter(); + + private verifyAnswerSubscription: ISubscription; + leftSides = []; + rightSides = []; + leftSidesToSend = []; + result = []; + verify = false; + good = 0; + selectedLeftSide = { indexOfLeftSide: Number, left_side: String, from: '', id: Number }; + + constructor(private flashcardsService: FlashcardsService) { } + + ngOnInit() { + this.isChecked.emit(false); + this.prepareLists(); + } + + ngOnChanges(changes: SimpleChanges) { + this.prepareLists(); + } + + ngOnDestroy() { + if (this.verifyAnswerSubscription) { + this.verifyAnswerSubscription.unsubscribe(); + } + } + + nextQuestion(f) { + let canSend = true; + this.good = 0; + this.result = []; + this.leftSides.forEach(element => { + if (element !== '') { + canSend = false; + } + }); + if (canSend) { + this.verify = true; + this.leftSidesToSend.forEach((element, index) => { + const body = { + content: this.rightSides[index].right_side, + id: element.id, + side: 'left' + }; + this.verifyAnswerSubscription = this.flashcardsService.testCheck(this.id, body).subscribe(d => { + this.result[index] = d.result; + if (d.result) { + this.good += 1; + } + if (this.result.length === this.leftSidesToSend.length) { + this.send(); + } + }); + }); + } + } + + send() { + this.isChecked.emit(true); + this.goodEvent.emit(this.good); + } + + leftSideClick(item, i) { + if (item !== '' && item !== undefined) { + if (this.selectedLeftSide.from === 'leftSides') { + this.selectedLeftSide = { indexOfLeftSide: undefined, left_side: undefined, from: undefined, id: undefined }; + } else { + this.selectedLeftSide = { indexOfLeftSide: i, left_side: item.left_side, from: 'leftSides', id: item.id }; + } + } + } + + leftSideToSendClick(item, i) { + if (this.selectedLeftSide.from === 'leftSides') { + const helper = this.leftSidesToSend[i]; + this.leftSidesToSend[i] = this.selectedLeftSide; + this.leftSides[Number(this.selectedLeftSide.indexOfLeftSide)] = helper; + this.selectedLeftSide = { indexOfLeftSide: undefined, left_side: undefined, from: undefined, id: undefined }; + } else if (this.selectedLeftSide.from === 'leftSidesToSend') { + const helper = this.leftSidesToSend[i]; + this.leftSidesToSend[i] = this.selectedLeftSide; + this.leftSidesToSend[Number(this.selectedLeftSide.indexOfLeftSide)] = helper; + this.selectedLeftSide = { indexOfLeftSide: undefined, left_side: undefined, from: undefined, id: undefined }; + } else if (item !== '' && item !== undefined) { + this.selectedLeftSide = { indexOfLeftSide: i, left_side: item.left_side, from: 'leftSidesToSend', id: item.id }; + } + } + + prepareLists() { + this.selectedLeftSide = { indexOfLeftSide: undefined, left_side: undefined, from: undefined, id: undefined }; + this.leftSides = []; + this.rightSides = []; + this.leftSidesToSend = []; + this.verify = false; + this.leftSides = JSON.parse(JSON.stringify(this.package[0].setLeft)); + this.rightSides = JSON.parse(JSON.stringify(this.package[0].setRight)); + this.leftSides.forEach(element => { + this.leftSidesToSend.push(''); + }); + } + +} diff --git a/src/app/flashcards/flashcards-pairs-test/flashcards-pairs-test.component.css b/src/app/flashcards/flashcards-pairs-test/flashcards-pairs-test.component.css new file mode 100644 index 0000000..48eaf62 --- /dev/null +++ b/src/app/flashcards/flashcards-pairs-test/flashcards-pairs-test.component.css @@ -0,0 +1,51 @@ +#container-small { + background-color: #181616; + padding: 30px; + width: 32%; + height: 400px; + box-sizing: border-box; + position: relative; + text-align: center; +} + +.content { + display: flex; + justify-content: space-around; + align-content: center; + margin-top: 2rem; +} + +#container-large { + background-color: #181616; + padding: 30px; + width: 62%; + min-height: 400px; + text-align: center; +} + +.container { + margin-bottom: 4rem; +} + +@media screen and (max-width: 800px) { + .content { + flex-wrap: wrap; + } + + #container-small { + height: 250px; + } + + #container-small, #container-large { + width: 90%; + margin-bottom: 2.5rem; + } +} + +@media screen and (max-width: 350px) { + + #container-small { + height: 350px; + } + +} \ No newline at end of file diff --git a/src/app/flashcards/flashcards-pairs-test/flashcards-pairs-test.component.html b/src/app/flashcards/flashcards-pairs-test/flashcards-pairs-test.component.html new file mode 100644 index 0000000..554bd88 --- /dev/null +++ b/src/app/flashcards/flashcards-pairs-test/flashcards-pairs-test.component.html @@ -0,0 +1,55 @@ +
+
+

Łączenie fiszek w pary

+
+
+ Nazwa zestawu: + {{ name }} +
+ Kategoria: + {{ category }} +
+
+
+
+ Uzupełniono: + {{ filled }} z {{ length_test }} fiszek +
+
+ Poprawnych odpowiedzi: + {{ good }} na {{ length_test }} +
+
+ Złych odpowiedzi: + {{ bad }} na {{ length_test }} +
+
+
+
+
+
+
+ +
+
+ Ekran: + {{ package_id + 1}} z {{ length_packages }} +
+
+ + + +
+ +
+
+
+ + + +
+
+ +
+
diff --git a/src/app/flashcards/flashcards-pairs-test/flashcards-pairs-test.component.spec.ts b/src/app/flashcards/flashcards-pairs-test/flashcards-pairs-test.component.spec.ts new file mode 100644 index 0000000..e90354a --- /dev/null +++ b/src/app/flashcards/flashcards-pairs-test/flashcards-pairs-test.component.spec.ts @@ -0,0 +1,34 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FlashcardsPairsTestComponent } from './flashcards-pairs-test.component'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { FlashcardsService } from '../flashcards.service'; +import { AuthenticationService } from '../../authentication.service'; +import { RouterTestingModule } from '@angular/router/testing'; +import { FormsModule } from '@angular/forms'; +import { HttpClientModule } from '@angular/common/http'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; + +describe('FlashcardsPairsTestComponent', () => { + let component: FlashcardsPairsTestComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ FlashcardsPairsTestComponent ], + schemas: [NO_ERRORS_SCHEMA], + providers: [FlashcardsService, AuthenticationService], + imports: [ RouterTestingModule, FormsModule, HttpClientModule, MatSnackBarModule] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(FlashcardsPairsTestComponent); + component = fixture.componentInstance; + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/flashcards/flashcards-pairs-test/flashcards-pairs-test.component.ts b/src/app/flashcards/flashcards-pairs-test/flashcards-pairs-test.component.ts new file mode 100644 index 0000000..1ebf142 --- /dev/null +++ b/src/app/flashcards/flashcards-pairs-test/flashcards-pairs-test.component.ts @@ -0,0 +1,110 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { FlashcardsService } from '../flashcards.service'; +import { Subscription } from 'rxjs/Subscription'; +import { ActivatedRoute } from '@angular/router'; + +@Component({ + selector: 'app-flashcards-pairs-test', + templateUrl: './flashcards-pairs-test.component.html', + styleUrls: ['./flashcards-pairs-test.component.css'] +}) +export class FlashcardsPairsTestComponent implements OnInit, OnDestroy { + + id: number; + flashcardSubscribtionMeta: Subscription; + flashcardSubscribtion: Subscription; + name: String; + category: String; + length_test: number; + length_packages: number; + goodNow: number; + started: Boolean = false; + finish: Boolean = false; + left: Boolean = true; + right: Boolean = false; + checked: Boolean = false; + not_last: Boolean = true; + packages: Array = []; + filled = 0; + good = 0; + bad = 0; + package_id = 0; + + constructor(private flashcardsService: FlashcardsService, private route: ActivatedRoute) {} + + ngOnInit() { + this.id = this.route.snapshot.params.id; + this.flashcardSubscribtionMeta = this.flashcardsService.getSet(this.id).subscribe(data => { + this.name = data['name']; + this.category = data['category']; + }); + this.flashcardSubscribtion = this.flashcardsService.getTestPairing(this.id).subscribe(data => { + this.length_test = data['left'].length; + this.createPackages(data); + }); + } + + createPackages(data) { + let attribute = '0'; + let setLeft = []; + let setRight = []; + const n = this.length_test; + for (let i = 0; i < n; i++) { + if (i % 5 === 0) { + this.packages[attribute] = { + setLeft: setLeft, + setRight: setRight + }; + attribute = (i / 5).toString(); + setLeft = []; + setRight = []; + } + setLeft.push(data['left'][i]); + setRight.push(data['right'][i]); + if (i === (n - 1)) { + this.packages[attribute] = { + setLeft: setLeft, + setRight: setRight + }; + } + } + this.length_packages = this.packages.length; + if (this.packages.length < 2) { + this.not_last = false; + } + } + + start() { + this.started = true; + } + + increment() { + if (this.package_id === (this.length_packages - 2)) { + this.not_last = false; + } + if (this.package_id === (this.length_packages - 1)) { + this.started = false; + this.finish = true; + } else { + this.package_id += 1; + } + } + + goodEvent(goods) { + this.goodNow = goods; + const filledNow = this.packages[this.package_id]['setLeft'].length; + this.filled = filledNow; + this.good = this.goodNow; + this.bad = this.filled - this.good; + } + + isChecked(check) { + this.checked = check; + } + + ngOnDestroy() { + this.flashcardSubscribtionMeta.unsubscribe(); + this.flashcardSubscribtion.unsubscribe(); + } + +} diff --git a/src/app/flashcards/flashcards-set-detail/flashcards-set-detail.component.css b/src/app/flashcards/flashcards-set-detail/flashcards-set-detail.component.css new file mode 100644 index 0000000..a93a165 --- /dev/null +++ b/src/app/flashcards/flashcards-set-detail/flashcards-set-detail.component.css @@ -0,0 +1,65 @@ +.listItem:hover { + background-color: rgb(206, 168, 86); +} + +.wrapper{ + width: 100%; + padding: 30px; + min-height: 100%; +} + +.btn{ + margin-bottom: 5px; +} + +.content{ + background-color: #181616; + padding: 30px; + margin-top: 2rem; + margin-bottom: 2rem; + text-align: center; +} + +.buttons-container > div{ + margin: 10px; +} + +.puzzle{ + min-height: 50px; + min-width: 100px; + background-color: rgb(206, 168, 86); + align-items: center; + color: black; + justify-content: center; + margin: 5px 5px 5px 5px; +} + +.puzzle-right{ + min-height: 50px; + min-width: 100px; + color: black; + background-color: rgb(202, 144, 17);; + align-items: center; + justify-content: center; + margin: 5px 5px 5px 5px; +} + +.both-sides{ + display: flex; + flex-direction: row; + flex-wrap: nowrap; +} + +.puzzles-container{ + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: center; + align-items: center; +} + +.empty{ + background-color: gray; + cursor: not-allowed; +} + diff --git a/src/app/flashcards/flashcards-set-detail/flashcards-set-detail.component.html b/src/app/flashcards/flashcards-set-detail/flashcards-set-detail.component.html new file mode 100644 index 0000000..b5af84a --- /dev/null +++ b/src/app/flashcards/flashcards-set-detail/flashcards-set-detail.component.html @@ -0,0 +1,45 @@ +
+
+
+ + + + +
+ +
+
+ + +
+
+

{{set.name}}

+ + + + + + + + + + + +
+ Lewa strona + + Prawa strona +
{{flashcard.left_side}}{{flashcard.right_side}}
+
+ + +
+ + + Czy chcesz usunąć fiszkę? + + + + + +
\ No newline at end of file diff --git a/src/app/flashcards/flashcards-set-detail/flashcards-set-detail.component.spec.ts b/src/app/flashcards/flashcards-set-detail/flashcards-set-detail.component.spec.ts new file mode 100644 index 0000000..e976d65 --- /dev/null +++ b/src/app/flashcards/flashcards-set-detail/flashcards-set-detail.component.spec.ts @@ -0,0 +1,65 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FlashcardsSetDetailComponent } from './flashcards-set-detail.component'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { FlashcardsService } from '../flashcards.service'; +import { AuthenticationService } from '../../authentication.service'; +import { RouterTestingModule } from '@angular/router/testing'; +import { FormsModule } from '@angular/forms'; +import { HttpClientModule } from '@angular/common/http'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { By } from '@angular/platform-browser'; +import { RoutingStateService } from '../../routing-state.service'; + +describe('FlashcardsSetDetailComponent', () => { + let component: FlashcardsSetDetailComponent; + let fixture: ComponentFixture; + let mockSet: any; + + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ FlashcardsSetDetailComponent ], + schemas: [NO_ERRORS_SCHEMA], + providers: [FlashcardsService, AuthenticationService, RoutingStateService], + imports: [ RouterTestingModule, FormsModule, HttpClientModule, MatSnackBarModule] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(FlashcardsSetDetailComponent); + component = fixture.componentInstance; + mockSet = { + name: 'testName', + flashcards: [ + { + left_side: 'testLeftSide', + right_side: 'testRightSide' + } + ] + }; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + // testowanie DOM + it('should render set name', () => { + component.set = mockSet; + fixture.detectChanges(); + const setName = document.querySelector('h2').innerHTML; + expect(setName).toEqual(mockSet.name); + }); + + it('should render flashcards sides', () => { + component.set = mockSet; + fixture.detectChanges(); + const leftSide = document.querySelector('.puzzle').innerHTML; + expect(leftSide).toEqual('testLeftSide'); + const rightSide = document.querySelector('.puzzle-right').innerHTML; + expect(rightSide).toEqual('testRightSide'); + }); +}); diff --git a/src/app/flashcards/flashcards-set-detail/flashcards-set-detail.component.ts b/src/app/flashcards/flashcards-set-detail/flashcards-set-detail.component.ts new file mode 100644 index 0000000..2943c37 --- /dev/null +++ b/src/app/flashcards/flashcards-set-detail/flashcards-set-detail.component.ts @@ -0,0 +1,102 @@ +import { Component, OnInit, OnDestroy, Input } from '@angular/core'; +import { Set } from '../set'; +import { ActivatedRoute, Router } from '@angular/router'; +import { FlashcardsService } from '../flashcards.service'; +import { Subscription } from 'rxjs/Subscription'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { RoutingStateService } from '../../routing-state.service'; + +@Component({ + selector: 'app-flashcards-set-detail', + templateUrl: './flashcards-set-detail.component.html', + styleUrls: ['./flashcards-set-detail.component.css'] +}) +export class FlashcardsSetDetailComponent implements OnInit, OnDestroy { + id: number; + set: any; + flashcardSubscribtion: Subscription; + testTypeMenu = false; + user: Boolean = false; + ShowStatus: Boolean = false; + permission: string; + owner; + owned: Boolean = false; + display = false; + + constructor(private route: ActivatedRoute, private flashcardsService: FlashcardsService, private router: Router, + private routingState: RoutingStateService, public snackBar: MatSnackBar) { } + ngOnInit() { + const previousUrl = this.routingState.getPreviousUrl(); + if (previousUrl.indexOf('test-gen') === -1) { + sessionStorage.setItem('previousUrl', previousUrl); + } + this.id = this.route.snapshot.params.id; + this.flashcardSubscribtion = this.flashcardsService.getSet(this.id).subscribe(data => { this.set = data; }); + this.IsLogin(); + this.owner = this.flashcardsService.getOwner(); + this.isOwner(); + } + + isOwner() { + const currentUser = JSON.parse(localStorage.getItem('currentUser')); + if (localStorage.getItem('currentUser')) { + if (currentUser.username === this.owner) { + this.owned = true; + } + } else { + this.owned = false; + } + } + + showTestTypeMenu() { + this.testTypeMenu = true; + } + + IsLogin() { + if (localStorage.getItem('currentUser')) { + this.user = true; + } else { + this.user = false; + } + } + + showPopup() { + this.display = true; + } + + changePermission(): void { + if (this.set.permission === 'Public') { + this.permission = 'Private'; + } else { + this.permission = 'Public'; + } + this.flashcardsService.changeSetPermission(this.id, this.permission); + this.snackBar.open('Zmieniono pozwolenie na: ' + this.permission, null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-success'] }); + } + + handleCancelFlashcardsTestyTypeMenu(e) { + this.testTypeMenu = e; + } + + navigateToEditMode() { + this.router.navigate(['flashcards/sets/edit', this.id]); + } + + ngOnDestroy() { + this.flashcardSubscribtion.unsubscribe(); + } + + deleteSet() { + const data = this.id; + this.flashcardSubscribtion = this.flashcardsService.deleteSet(data); + this.display = false; + } + + goBack() { + const previousUrl = sessionStorage.getItem('previousUrl'); + sessionStorage.removeItem('previousUrl'); + this.router.navigate([previousUrl]); + } + +} diff --git a/src/app/flashcards/flashcards-sets-list/flashcards-sets-list.component.css b/src/app/flashcards/flashcards-sets-list/flashcards-sets-list.component.css new file mode 100644 index 0000000..f4a5593 --- /dev/null +++ b/src/app/flashcards/flashcards-sets-list/flashcards-sets-list.component.css @@ -0,0 +1,46 @@ +.listItem:hover { + background-color: rgb(206, 168, 86); +} + +.listItem:active{ + background-color: #4BC7FA; +} + +.listItem{ + cursor: pointer; +} + +.wrapper{ + width: 100%; + padding: 30px; + min-height: 100%; + margin-bottom: 40px; +} + +@media screen and (max-width: 800px) { + .mobile{ + display: none; + } +} + +.empty{ + text-align: center; +} + +.content{ + background-color: #181616; + padding: 30px; + margin-top: 2rem; + margin-bottom: 2rem; + text-align: center; + margin-left: auto; + margin-right: auto; +} + +ag-grid-angular{ + margin: 10px; +} + +.buttons-container > div{ + margin: 10px; +} \ No newline at end of file diff --git a/src/app/flashcards/flashcards-sets-list/flashcards-sets-list.component.html b/src/app/flashcards/flashcards-sets-list/flashcards-sets-list.component.html new file mode 100644 index 0000000..6710519 --- /dev/null +++ b/src/app/flashcards/flashcards-sets-list/flashcards-sets-list.component.html @@ -0,0 +1,35 @@ +
+
+ +
+

FISZKI

+
+
+
+ +
+
+ + +
+
+
+ + + + Czy chcesz usunąć fiszkę? + + + + + + +
+
\ No newline at end of file diff --git a/src/app/flashcards/flashcards-sets-list/flashcards-sets-list.component.spec.ts b/src/app/flashcards/flashcards-sets-list/flashcards-sets-list.component.spec.ts new file mode 100644 index 0000000..759dfca --- /dev/null +++ b/src/app/flashcards/flashcards-sets-list/flashcards-sets-list.component.spec.ts @@ -0,0 +1,35 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FlashcardsSetsListComponent } from './flashcards-sets-list.component'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { FlashcardsService } from '../flashcards.service'; +import { AuthenticationService } from '../../authentication.service'; +import { RouterTestingModule } from '@angular/router/testing'; +import { FormsModule } from '@angular/forms'; +import { HttpClientModule } from '@angular/common/http'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; + +describe('FlashcardsSetsListComponent', () => { + let component: FlashcardsSetsListComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ FlashcardsSetsListComponent ], + schemas: [NO_ERRORS_SCHEMA], + providers: [FlashcardsService, AuthenticationService], + imports: [ RouterTestingModule, FormsModule, HttpClientModule, MatSnackBarModule] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(FlashcardsSetsListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/flashcards/flashcards-sets-list/flashcards-sets-list.component.ts b/src/app/flashcards/flashcards-sets-list/flashcards-sets-list.component.ts new file mode 100644 index 0000000..a60bf94 --- /dev/null +++ b/src/app/flashcards/flashcards-sets-list/flashcards-sets-list.component.ts @@ -0,0 +1,271 @@ +import { Component, OnInit, Output, EventEmitter, OnDestroy } from '@angular/core'; +import { FlashcardsService } from '../flashcards.service'; +import { Set } from '../set'; +import { Router } from '@angular/router'; +import { Subscription } from 'rxjs/Subscription'; +import { FilterPipe } from '../../filter.pipe'; +import { GridOptions, RowDoubleClickedEvent } from 'ag-grid-community/main'; +import localeText from './localeText'; +import { MatSnackBar } from '@angular/material/snack-bar'; + +@Component({ + selector: 'app-flashcards-sets-list', + templateUrl: './flashcards-sets-list.component.html', + styleUrls: ['./flashcards-sets-list.component.css'] +}) +export class FlashcardsSetsListComponent implements OnInit, OnDestroy { + + sets = []; + privatesets = []; + setsEmpty = true; + selectedSet: Set; + flashcardSubscription: Subscription; + flashcardSubscriptionOwners: Subscription; + user: Boolean; + ShowStatus: Boolean = false; + owner; + searchPublic = 'Public'; + searchOwner; + gridApi; + gridColumnApi; + public gridOptions: GridOptions; + localeText = localeText; + logged = false; + publicMode = true; + permission; + display = false; + isGroup = false; + + columnDefs = [ + { headerName: 'Nazwa', field: 'name', headerTooltip: 'Nazwa' }, + { headerName: 'Data dodania', field: 'add_date', headerTooltip: 'Data dodania', hide: false }, + { headerName: 'Data modyfikacji', field: 'edit_date', headerTooltip: 'Data modyfikacji', hide: false }, + { headerName: 'Właściciel', field: 'owner', headerTooltip: 'Właściciel', hide: false }, + { headerName: 'Grupa', field: 'group', headerTooltip: 'Grupa', hide: !this.isGroup }, + { + headerName: '', + suppressMenu: true, + suppressSorting: true, + cellRenderer: this.customCellRendererFunc, + hide: !this.isGroup + } + ]; + setToDelete: any; + constructor(private flashcardsService: FlashcardsService, private router: Router, public snackBar: MatSnackBar) { } + + customCellRendererFunc(params) { + const currentUser = JSON.parse(localStorage.getItem('currentUser')); + if (!currentUser) { + return ''; + } else if (params.data['owner'] === currentUser.username) { + return ` + + + `; + } else { + return ''; + } + } + + onSelect(set: Set): void { + this.selectedSet = set; + this.router.navigate(['flashcards/sets', this.selectedSet.id]); + this.flashcardsService.setOwner(this.selectedSet.owner); + } + + toFlashcardsMaker() { + this.router.navigate(['flashcards/add']); + } + + public onRowClicked(e) { + if (e.event.target !== undefined) { + const data = e.data; + const actionType = e.event.target.getAttribute('data-action-type'); + + switch (actionType) { + case 'remove': + return this.onActionRemoveClick(e); + case 'changePermission': + return this.changePermission(e); + default: + return this.goToSets(e); + } + } + } + public onActionRemoveClick(e) { + this.display = true; + this.setToDelete = e.data.id; + } + + deleteSet() { + const data = this.setToDelete; + this.flashcardSubscription = this.flashcardsService.deleteSet(data); + this.display = false; + } + + changePermission(e): void { + if (e.data.permission === 'Public') { + this.permission = 'Private'; + } else { + this.permission = 'Public'; + } + this.flashcardsService.changeSetPermission(e.data.id, this.permission); + this.snackBar.open('Zmieniono pozwolenie na: ' + this.permission, null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-success'] }); + } + + goToSets(e) { + this.router.navigate(['flashcards/sets', e.data.id]); + this.flashcardsService.setOwner(e.data.owner); + } + + IsLogin() { + const own = JSON.parse(localStorage.getItem('currentUser')); + if (localStorage.getItem('currentUser')) { + this.user = true; + this.searchOwner = own.username; + } else { + this.user = false; + this.searchOwner = ' '; + } + } + + ShowPublic() { + this.sets = []; + this.ShowStatus = false; + this.getSets(); + } + + ShowPrivate() { + this.privatesets = []; + this.ShowStatus = true; + this.getSetsOwners(); + } + + ngOnInit() { + if (localStorage.getItem('currentUser')) { + this.logged = true; + } + + this.gridOptions = { + rowHeight: 50, + headerHeight: 25, + getRowStyle: function (params) { + return { + cursor: 'pointer' + }; + }, + }; + this.getSets(); + this.IsLogin(); + this.refreshColumns(); + } + + getSets(): void { + this.isGroup = false; + this.flashcardSubscription = this.flashcardsService.getSets() + .subscribe(data => { + this.sets = data; + if (this.sets.length > 0) { + this.setsEmpty = false; + } else { + this.setsEmpty = true; + } + this.refreshColumns(); + }); + } + + getSetsOwners(): void { + this.isGroup = true; + this.flashcardSubscriptionOwners = this.flashcardsService.getSetsOwners() + .subscribe(data => { + data.forEach((x, i) => { + if (!x['group']) { + x['group'] = 'Brak'; + } + }); + this.sets = data; + if (this.sets.length > 0) { + this.setsEmpty = false; + } else { + this.setsEmpty = true; + } + this.refreshColumns(); + }); + } + + onGridReady(params) { + this.gridApi = params.api; + this.gridColumnApi = params.columnApi; + this.gridApi.sizeColumnsToFit(); + } + + onGridSizeChanged(params) { + if (params.clientWidth < 800) { + this.columnDefs = [ + { headerName: 'Nazwa', field: 'name', headerTooltip: 'Nazwa' }, + { headerName: 'Data dodania', field: 'add_date', headerTooltip: 'Data dodania', hide: true }, + { headerName: 'Data modyfikacji', field: 'edit_date', headerTooltip: 'Data modyfikacji', hide: true }, + { headerName: 'Właściciel', field: 'owner', headerTooltip: 'Właściciel', hide: true }, + { headerName: 'Grupa', field: 'group', headerTooltip: 'Grupa', hide: !this.isGroup }, + { + headerName: '', + suppressMenu: true, + suppressSorting: true, + cellRenderer: this.customCellRendererFunc, + hide: !this.isGroup + } + ]; + } else { + this.columnDefs = [ + { headerName: 'Nazwa', field: 'name', headerTooltip: 'Nazwa' }, + { headerName: 'Data dodania', field: 'add_date', headerTooltip: 'Data dodania', hide: false }, + { headerName: 'Data modyfikacji', field: 'edit_date', headerTooltip: 'Data modyfikacji', hide: false }, + { headerName: 'Właściciel', field: 'owner', headerTooltip: 'Właściciel', hide: false }, + { headerName: 'Grupa', field: 'group', headerTooltip: 'Grupa', hide: !this.isGroup }, + { + headerName: '', + suppressMenu: true, + suppressSorting: true, + cellRenderer: this.customCellRendererFunc, + hide: !this.isGroup + } + ]; + } + + params.api.sizeColumnsToFit(); + } + + onGidColumnsChanged(params) { + params.api.sizeColumnsToFit(); + } + + + ngOnDestroy() { + if (this.flashcardSubscription) { + this.flashcardSubscription.unsubscribe(); + } + if (this.flashcardSubscriptionOwners) { + this.flashcardSubscriptionOwners.unsubscribe(); + } + } + + refreshColumns() { + this.columnDefs = [ + { headerName: 'Nazwa', field: 'name', headerTooltip: 'Nazwa' }, + { headerName: 'Data dodania', field: 'add_date', headerTooltip: 'Data dodania', hide: false }, + { headerName: 'Data modyfikacji', field: 'edit_date', headerTooltip: 'Data modyfikacji', hide: false }, + { headerName: 'Właściciel', field: 'owner', headerTooltip: 'Właściciel', hide: false }, + { headerName: 'Grupa', field: 'group', headerTooltip: 'Grupa', hide: !this.isGroup }, + { + headerName: '', + suppressMenu: true, + suppressSorting: true, + cellRenderer: this.customCellRendererFunc, + hide: !this.isGroup + } + ]; + } +} diff --git a/src/app/flashcards/flashcards-sets-list/localeText.ts b/src/app/flashcards/flashcards-sets-list/localeText.ts new file mode 100644 index 0000000..7f3d9fd --- /dev/null +++ b/src/app/flashcards/flashcards-sets-list/localeText.ts @@ -0,0 +1,90 @@ +const localeText = { + + // for filter panel + page: 'Strona', + more: 'Więcej', + to: 'do', + of: 'z', + next: '>', + last: '>>', + first: '<<', + previous: '<', + loadingOoo: 'Ładowanie...', + + // for set filter + selectAll: 'Wybierz wszystkie', + searchOoo: 'Szukaj', + blanks: 'Luki', + + // for number filter and text filter + filterOoo: 'Filtruj...', + applyFilter: 'Zastosuj filtr', + + // for number filter + equals: 'Równy', + notEqual: 'Nie równy', + lessThan: 'Mniejszy niż', + greaterThan: 'Większy niż', + + // for text filter + contains: 'Zawiera', + notContains: 'Nie zawiera', + startsWith: 'Zaczyna się od', + endsWith: 'Kończy się na', + + // the header of the default group column + group: 'Grupa', + + // tool panel + columns: 'Kolumny', + rowGroupColumns: 'Grupa wiersza kolumn', + rowGroupColumnsEmptyMessage: 'Przeciągnij kolumny do grupy', + valueColumns: 'Wartości kolumn', + pivotMode: 'Pivot-Mode', + groups: 'Grupy', + values: 'Wartości', + pivots: 'Pivots', + valueColumnsEmptyMessage: 'Przeciągnij kolumny do agregacji', + pivotColumnsEmptyMessage: 'Przeciągnij kolumny na oś', + toolPanelButton: 'Narzędzia', + + // other + noRowsToShow: 'Brak fiszek do wyświetlenia', + + // enterprise menu + pinColumn: 'Przypiąta kolumna', + valueAggregation: 'Sumuj', + autosizeThiscolumn: 'Autosize this column', + autosizeAllColumns: 'Autosize All Columns', + groupBy: 'Grupuj po', + ungroupBy: 'Nie grupuj po', + resetColumns: 'Resetuj kolumny', + expandAll: 'Rozszerz wszystko', + collapseAll: 'Zwiń wszystko', + toolPanel: 'Narzędzia', + export: 'Export', + csvExport: 'CSV Export', + excelExport: 'Excel Export', + + // enterprise menu pinning + pinLeft: 'Przypnij <<', + pinRight: 'Przypnij >>', + noPin: 'Nie przypinaj <>', + + // enterprise menu aggregation and status panel + sum: 'Suma', + min: 'Minimum', + max: 'Maksimum', + none: 'Nic', + count: 'Ilość', + average: 'Średnia', + + // standard menu + copy: 'Kopiuj', + copyWithHeaders: 'Kopiuj z nagłówkami', + ctrlC: 'ctrl + C', + paste: 'wklej', + ctrlV: 'ctrl + C' +}; + +export default localeText; diff --git a/src/app/flashcards/flashcards-test-type-menu/flashcards-test-type-menu.component.css b/src/app/flashcards/flashcards-test-type-menu/flashcards-test-type-menu.component.css new file mode 100644 index 0000000..ebd4ed4 --- /dev/null +++ b/src/app/flashcards/flashcards-test-type-menu/flashcards-test-type-menu.component.css @@ -0,0 +1,4 @@ +div{ + margin-top: 5px; + margin-bottom: 5px; +} \ No newline at end of file diff --git a/src/app/flashcards/flashcards-test-type-menu/flashcards-test-type-menu.component.html b/src/app/flashcards/flashcards-test-type-menu/flashcards-test-type-menu.component.html new file mode 100644 index 0000000..e01d94e --- /dev/null +++ b/src/app/flashcards/flashcards-test-type-menu/flashcards-test-type-menu.component.html @@ -0,0 +1,7 @@ +
+ + + + + +
\ No newline at end of file diff --git a/src/app/flashcards/flashcards-test-type-menu/flashcards-test-type-menu.component.spec.ts b/src/app/flashcards/flashcards-test-type-menu/flashcards-test-type-menu.component.spec.ts new file mode 100644 index 0000000..90ffece --- /dev/null +++ b/src/app/flashcards/flashcards-test-type-menu/flashcards-test-type-menu.component.spec.ts @@ -0,0 +1,34 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FlashcardsTestTypeMenuComponent } from './flashcards-test-type-menu.component'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { FlashcardsService } from '../flashcards.service'; +import { AuthenticationService } from '../../authentication.service'; +import { RouterTestingModule } from '@angular/router/testing'; +import { FormsModule } from '@angular/forms'; +import { HttpClientModule } from '@angular/common/http'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; + +describe('FlashcardsTestTypeMenuComponent', () => { + let component: FlashcardsTestTypeMenuComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ FlashcardsTestTypeMenuComponent ], + schemas: [NO_ERRORS_SCHEMA], + providers: [FlashcardsService, AuthenticationService], + imports: [ RouterTestingModule, FormsModule, HttpClientModule, MatSnackBarModule] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(FlashcardsTestTypeMenuComponent); + component = fixture.componentInstance; + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/flashcards/flashcards-test-type-menu/flashcards-test-type-menu.component.ts b/src/app/flashcards/flashcards-test-type-menu/flashcards-test-type-menu.component.ts new file mode 100644 index 0000000..b98fc8f --- /dev/null +++ b/src/app/flashcards/flashcards-test-type-menu/flashcards-test-type-menu.component.ts @@ -0,0 +1,37 @@ +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; +import { Router } from '@angular/router'; + +@Component({ + selector: 'app-flashcards-test-type-menu', + templateUrl: './flashcards-test-type-menu.component.html', + styleUrls: ['./flashcards-test-type-menu.component.css'] +}) +export class FlashcardsTestTypeMenuComponent implements OnInit { + @Input() id: Number; + @Output() cancel = new EventEmitter; + + cancelMenu() { + this.cancel.emit(false); + } + + goToTestGen() { + this.router.navigate(['flashcards/test-gen/flashcards-pairs', this.id]); + } + + goToTestGenFill() { + this.router.navigate(['flashcards/test-gen/flashcards-filling-in', this.id]); + } + + goToTestGenMemory() { + this.router.navigate(['flashcards/test-gen/flashcards-memory', this.id]); + } + goToTestGenTyperace() { + this.router.navigate(['flashcards/test-gen/flashcards-typerace', this.id]); + } + + constructor(private router: Router) { } + + ngOnInit() { + } + +} diff --git a/src/app/flashcards/flashcards-typerace-test/flashcards-typerace-test.component.css b/src/app/flashcards/flashcards-typerace-test/flashcards-typerace-test.component.css new file mode 100644 index 0000000..e1f5971 --- /dev/null +++ b/src/app/flashcards/flashcards-typerace-test/flashcards-typerace-test.component.css @@ -0,0 +1,43 @@ +#container-small { + background-color: #181616; + padding: 30px; + width: 32%; + min-height: 400px; + box-sizing: border-box; + position: relative; + text-align: center; +} + +.content { + display: flex; + justify-content: space-around; + align-content: center; + margin-top: 2rem; +} + +#container-large { + background-color: #181616; + padding: 30px; + width: 62%; + min-height: 400px; + text-align: center; +} + +.container { + margin-bottom: 4rem; +} + +@media screen and (max-width: 800px) { + .content { + flex-wrap: wrap; + } + + #container-small { + height: 250px; + } + + #container-small, #container-large { + width: 90%; + margin-bottom: 2.5rem; + } +} \ No newline at end of file diff --git a/src/app/flashcards/flashcards-typerace-test/flashcards-typerace-test.component.html b/src/app/flashcards/flashcards-typerace-test/flashcards-typerace-test.component.html new file mode 100644 index 0000000..0d0cb41 --- /dev/null +++ b/src/app/flashcards/flashcards-typerace-test/flashcards-typerace-test.component.html @@ -0,0 +1,62 @@ +
+
+

Przepisywanie fiszek na czas

+
+
+ Nazwa zestawu: + {{ name }} +
+ Kategoria: + {{ category }} +
+
+
+
+ Uzupełniono: + {{ filled }} z {{ length_test }} fiszek +
+
+ Poprawnych odpowiedzi: + {{ good }} na {{ length_test }}
+ Złych odpowiedzi: + {{ bad }} na {{ length_test }}
+
+ Twój czas: + {{minute}} m {{second}} s {{millisecond}} ms +
+
+
+
+
+
+
+
+ Poprzednia odpowiedź:
+ {{flashcards[index-1].content}} {{stoptimes[index-1].m}} m {{stoptimes[index-1].s}} s {{stoptimes[index-1].ms}} ms +
+
+ Podaj odpowiedź:
+ {{flashcards[index].content}} + + +
+
ODPOWIEDŹ POPRAWNA
+
ODPOWIEDŹ NIEPOPRAWNA
+
+ Następne pytanie:
+ {{flashcards[index+1].content}} +
+
+ +
+ +
+
+ +
+ + +
+ +
+
diff --git a/src/app/flashcards/flashcards-typerace-test/flashcards-typerace-test.component.spec.ts b/src/app/flashcards/flashcards-typerace-test/flashcards-typerace-test.component.spec.ts new file mode 100644 index 0000000..a267380 --- /dev/null +++ b/src/app/flashcards/flashcards-typerace-test/flashcards-typerace-test.component.spec.ts @@ -0,0 +1,34 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FlashcardsTyperaceTestComponent } from './flashcards-typerace-test.component'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { FlashcardsService } from '../flashcards.service'; +import { AuthenticationService } from '../../authentication.service'; +import { FormsModule } from '@angular/forms'; +import { HttpClientModule } from '@angular/common/http'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; + +describe('FlashcardsTyperaceTestComponent', () => { + let component: FlashcardsTyperaceTestComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ FlashcardsTyperaceTestComponent ], + schemas: [NO_ERRORS_SCHEMA], + providers: [FlashcardsService, AuthenticationService], + imports: [ RouterTestingModule, FormsModule, HttpClientModule, MatSnackBarModule] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(FlashcardsTyperaceTestComponent); + component = fixture.componentInstance; + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/flashcards/flashcards-typerace-test/flashcards-typerace-test.component.ts b/src/app/flashcards/flashcards-typerace-test/flashcards-typerace-test.component.ts new file mode 100644 index 0000000..3cfbbf1 --- /dev/null +++ b/src/app/flashcards/flashcards-typerace-test/flashcards-typerace-test.component.ts @@ -0,0 +1,167 @@ +import { Component, OnInit, Input, OnDestroy } from '@angular/core'; +import { FlashcardsService } from '../flashcards.service'; +import { Subscription } from 'rxjs/Subscription'; +import { ActivatedRoute } from '@angular/router'; + + +@Component({ + selector: 'app-flashcards-typerace-test', + templateUrl: './flashcards-typerace-test.component.html', + styleUrls: ['./flashcards-typerace-test.component.css'] +}) +export class FlashcardsTyperaceTestComponent implements OnInit, OnDestroy { + + id: number; + private flashcardSubscribtionMeta: Subscription; + private flashcardSubscribtion: Subscription; + private flashcardSubscribtionCheck: Subscription; + + name: String; + category: String; + length_test: number; + goodNow: number; + started: Boolean = false; + finish: Boolean = false; + flashcards: Array = []; + filled = 0; + good = 0; + bad = 0; + answer: String; + index = 0; + not_last: Boolean = true; + is_correct: Boolean; + allAnswers = []; + stoptimes = []; + + hour= 0; + minute= 0; + second= 0; + millisecond= 0; + starttime= false; + pause= false; + x= 10; + intervalId; + + finalhour= 0; + finalminute= 0; + finalsecond= 0; + finalmillisecond= 0; + + constructor(private flashcardsService: FlashcardsService, private route: ActivatedRoute) { } + + ngOnInit() { + this.id = this.route.snapshot.params.id; + this.flashcardSubscribtionMeta = this.flashcardsService.getSet(this.id).subscribe(data => { + this.name = data['name']; + this.category = data['category']; + }); + this.flashcardSubscribtion = this.flashcardsService.getTestFilling(this.id).subscribe(data => { + this.length_test = data.length; + this.flashcards = data; + }); + + } + + start() { + this.started = true; + this.onStart(); + } + finished() { + this.finish = true; + this.onPause(); + } + verifyAnswer() { + this.reset(); + const body = []; + const n = this.length_test; + if (this.answer === '') { + this.answer = ' '; + } + this.allAnswers.push({ + id: this.index, + answer: this.answer, + }); + body.push({ + id: this.flashcards[this.index]['id'], + content: this.answer, + side: this.flashcards[this.index]['side'], + }); + this.flashcardSubscribtionCheck = this.flashcardsService.testCheck(this.id, body[0]) + .subscribe(data => { + this.is_correct = data.result; + if (this.is_correct === true) { + this.good = this.good + 1; + } else { + this.bad = this.bad + 1; + } + }); + if (this.index < this.length_test) { + this.index = this.index + 1; + this.filled = this.filled + 1; + this.answer = ''; + } + if (this.index === this.length_test) { + this.not_last = false; + } + } + + reset() { + this.finalhour = this.finalhour + this.hour; + this.finalminute = this.finalminute + this.minute; + this.finalsecond = this.finalsecond + this.second; + this.finalmillisecond = this.finalmillisecond + this.millisecond; + this.stoptimes.push({ + id: this.index, + m: this.minute, + s: this.second, + ms: this.millisecond, + }); + + this.x = 0; + this.hour = this.minute = this.second = this.millisecond = 0; + this.starttime = false; + this.pause = false; + clearInterval(this.intervalId); + this.onStart(); +} + +onStart() { + this.x = 10; + this.starttime = true; + this.intervalId = setInterval(() => { + this.updateTime(); + }, 100); +} + +onPause() { + this.pause = true; + clearInterval(this.intervalId); +} + +updateTime() { + this.millisecond += this.x; + + if (this.millisecond > 90) { + this.millisecond = 0; + this.second++; + } + + if (this.second > 59) { + this.second = 0; + this.minute++; + } + + if (this.minute > 59) { + this.minute = 0; + this.hour++; + } +} + + ngOnDestroy() { + this.flashcardSubscribtionMeta.unsubscribe(); + this.flashcardSubscribtion.unsubscribe(); + if (this.flashcardSubscribtionCheck) { + this.flashcardSubscribtionCheck.unsubscribe(); + } + } +} diff --git a/src/app/flashcards/flashcards.module.ts b/src/app/flashcards/flashcards.module.ts new file mode 100644 index 0000000..05a37e7 --- /dev/null +++ b/src/app/flashcards/flashcards.module.ts @@ -0,0 +1,66 @@ +import * as $ from 'jquery'; +import { BrowserModule } from '@angular/platform-browser'; +import { NgModule } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { HttpModule } from '@angular/http'; +import { HttpClientModule } from '@angular/common/http'; + +import { FlashcardsComponent } from './flashcards/flashcards.component'; +import { FlashcardsSetsListComponent } from './flashcards-sets-list/flashcards-sets-list.component'; +import { FlashcardsService } from './flashcards.service'; +import { FlashcardsAddComponent } from './flashcards-add/flashcards-add.component'; +import { FlashcardsAddTableComponent } from './flashcards-add-table/flashcards-add-table.component'; +import { FlashcardsAddCsvComponent } from './flashcards-add-csv/flashcards-add-csv.component'; +import { FlashcardsSetDetailComponent } from './flashcards-set-detail/flashcards-set-detail.component'; +import { FlashcardsEditTableComponent } from './flashcards-edit-table/flashcards-edit-table.component'; +import { FlashcardsPairsTestComponent } from './flashcards-pairs-test/flashcards-pairs-test.component'; +import { FlashcardsPairsTestSetComponent } from './flashcards-pairs-test-set/flashcards-pairs-test-set.component'; +import { FlashcardsTestTypeMenuComponent } from './flashcards-test-type-menu/flashcards-test-type-menu.component'; +import { FlashcardsFillingInTestComponent } from './flashcards-filling-in-test/flashcards-filling-in-test.component'; +import { FlashcardsMemoryTestComponent } from './flashcards-memory-test/flashcards-memory-test.component'; +import { FlashcardsMemoryTestSetComponent } from './flashcards-memory-test-set/flashcards-memory-test-set.component'; +import { FlashcardsTyperaceTestComponent } from './flashcards-typerace-test/flashcards-typerace-test.component'; +import { RouterModule } from '@angular/router'; +import { TestResultsComponent } from './test-results/test-results.component'; +import { FilterUserPipe } from '../filter-user.pipe'; +import { FilterPipe } from '../filter.pipe'; +import { AgGridModule } from 'ag-grid-angular'; +import { DialogModule } from 'primeng/dialog'; +import { ConfirmDialogModule } from 'primeng/confirmdialog'; +import { SharedModule } from '../shared/shared.module'; + +@NgModule({ + imports: [ + RouterModule, + BrowserModule, + FormsModule, + HttpModule, + HttpClientModule, + AgGridModule.withComponents([]), + DialogModule, + ConfirmDialogModule, + SharedModule + + ], + declarations: [ + TestResultsComponent, + FlashcardsComponent, + FlashcardsSetsListComponent, + FlashcardsAddComponent, + FlashcardsAddTableComponent, + FlashcardsAddCsvComponent, + FlashcardsSetDetailComponent, + FlashcardsEditTableComponent, + FlashcardsPairsTestComponent, + FlashcardsPairsTestSetComponent, + FlashcardsTestTypeMenuComponent, + FlashcardsFillingInTestComponent, + FlashcardsMemoryTestComponent, + FlashcardsMemoryTestSetComponent, + FlashcardsTyperaceTestComponent, + FilterUserPipe, + FilterPipe + ], + providers: [FlashcardsService, FilterPipe, FilterUserPipe], +}) +export class FlashcardsModule { } diff --git a/src/app/flashcards/flashcards.service.spec.ts b/src/app/flashcards/flashcards.service.spec.ts new file mode 100644 index 0000000..4569100 --- /dev/null +++ b/src/app/flashcards/flashcards.service.spec.ts @@ -0,0 +1,23 @@ +import { TestBed, inject } from '@angular/core/testing'; + +import { FlashcardsService } from './flashcards.service'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { HttpClientModule } from '@angular/common/http'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { AuthenticationService } from '../authentication.service'; + +describe('FlashcardsService', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [FlashcardsService, AuthenticationService], + schemas: [NO_ERRORS_SCHEMA], + imports: [ RouterTestingModule, FormsModule, HttpClientModule, MatSnackBarModule] + }); + }); + + it('should be created', inject([FlashcardsService], (service: FlashcardsService) => { + expect(service).toBeTruthy(); + })); +}); diff --git a/src/app/flashcards/flashcards.service.ts b/src/app/flashcards/flashcards.service.ts new file mode 100644 index 0000000..ae2eae2 --- /dev/null +++ b/src/app/flashcards/flashcards.service.ts @@ -0,0 +1,160 @@ +import { Injectable } from '@angular/core'; +import { HttpClient, HttpHeaders, HttpRequest, HttpEvent } from '@angular/common/http'; +import { Http, Response, Headers, RequestOptions } from '@angular/http'; +import { Observable } from 'rxjs/Observable'; +import { of } from 'rxjs/observable/of'; +import { Set } from './set'; +import 'rxjs/add/operator/map'; +import { Router } from '@angular/router'; +import { AuthenticationService } from '../authentication.service'; +import { THIS_EXPR } from '@angular/compiler/src/output/output_ast'; +import { MatSnackBar } from '@angular/material/snack-bar'; + +@Injectable() +export class FlashcardsService { + + // tslint:disable-next-line:max-line-length + constructor(private httpClient: HttpClient, private router: Router, private authenticationService: AuthenticationService, + public snackBar: MatSnackBar) { this.setHeaders(); } + private headers; + owner; + + setHeaders() { + if (localStorage.getItem('currentUser')) { + this.headers = new HttpHeaders({ + 'Content-Type': 'application/json', + 'Authorization': '' + this.authenticationService.getToken() + }); + } else { + this.headers = new HttpHeaders({ + 'Content-Type': 'application/json' + }); + } + } + + changeSetPermission(id, permission) { + this.setHeaders(); + this.httpClient.put('sets/' + id + '/permission', permission, { headers: this.headers }) + .subscribe(); + } + + add(body) { + const url = 'sets/'; + this.sendData(url, body); + } + + edit(body) { + const url = 'sets/'; + this.putData(url, body); + } + + getSets(): Observable { + this.setHeaders(); + return this.httpClient.get('sets', { headers: this.headers, params: { permission: 'Public' } }); + } + getSetsOwners(): Observable { + this.setHeaders(); + const owner = JSON.parse(localStorage.getItem('currentUser')); + return this.httpClient.get('sets', { headers: this.headers, params: { owner: owner.username } }); + } + + setOwner(owner) { + if (owner === null) { + this.owner = ' '; + } else { + this.owner = owner; + } + } + + getOwner() { + return this.owner; + } + + sendData(url, body) { + this.setHeaders(); + this.httpClient.post(url, body, { headers: this.headers, observe: 'response' }) + .subscribe(data => { this.sendResponse(data); }, + error => { + this.snackBar.open('Coś poszło nie tak. Spróbuj ponownie później.', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } + ); + } + + putData(url, body) { + this.setHeaders(); + this.httpClient.put(url, body, { headers: this.headers, observe: 'response' }) + .subscribe(data => { this.sendResponse(data); }, + error => { + this.snackBar.open('Coś poszło nie tak. Spróbuj ponownie później.', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } + ); + } + + sendResponse(data) { + if (data.status === 200) { + this.snackBar.open('Operacja przebiegła pomyślnie!', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-success'] }); + this.router.navigate(['flashcards/sets']); + } else { + this.snackBar.open('Coś poszło nie tak. Spróbuj ponownie później.', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } + } + + // sending file - import flashcards from CSV + pushFileToStorage(file: File, user: string, permission: string, url: string): Observable> { + const formdata: FormData = new FormData(); + formdata.append('file', file); + formdata.append('owner', user); + formdata.append('permission', permission); + const req = new HttpRequest('POST', url, formdata, { + reportProgress: true, + responseType: 'text' + }); + return this.httpClient.request(req); + } + + getSet(id) { + return this.httpClient.get('sets/' + id + '/'); + } + + getTestPairing(id) { + return this.httpClient.get('sets/' + id + '/test/pairing/'); + } + + getTestFilling(id) { + return this.httpClient.get('sets/' + id + '/test/filling-in/').map((data: Array) => data); + } + + getTestMemory(id) { + return this.httpClient.get('sets/' + id + '/test/memory/').map((data: Array) => data); + } + + deleteSet(id) { + this.setHeaders(); + return this.httpClient.delete('sets/' + id, { headers: this.headers, observe: 'response' }) + .subscribe(data => { this.sendResponse(data); }, + error => { + this.snackBar.open('Coś poszło nie tak. Spróbuj ponownie później.', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } + ); + } + + // sprawdzanie testu - zapytanie dla jednej fiszki + testCheck(id, body) { + // id - id zestawu fiszek + // ciało body = {id - idFiszki, content - wpisana odpowiedź, side - strona fiszki, którą widział użytkownik} + this.setHeaders(); + return this.httpClient.get(`sets/${id}/${body['id']}/${body['content']}/${body['side']}/test/check/`, { headers: this.headers }) + .map((data: any) => data); + } + + testMemory(id, body) { + this.setHeaders(); + return this.httpClient.get(`sets/${id}/test/memory/check?x=${body['x']}&y=${body['y']}`, { headers: this.headers }) + .map((data: any) => data); + } +} diff --git a/src/app/flashcards/flashcards/flashcards.component.css b/src/app/flashcards/flashcards/flashcards.component.css new file mode 100644 index 0000000..01530aa --- /dev/null +++ b/src/app/flashcards/flashcards/flashcards.component.css @@ -0,0 +1,3 @@ +.btn{ + margin-bottom: 5px; +} \ No newline at end of file diff --git a/src/app/flashcards/flashcards/flashcards.component.html b/src/app/flashcards/flashcards/flashcards.component.html new file mode 100644 index 0000000..3c5b93d --- /dev/null +++ b/src/app/flashcards/flashcards/flashcards.component.html @@ -0,0 +1,4 @@ +
+ + +
diff --git a/src/app/flashcards/flashcards/flashcards.component.spec.ts b/src/app/flashcards/flashcards/flashcards.component.spec.ts new file mode 100644 index 0000000..c82041f --- /dev/null +++ b/src/app/flashcards/flashcards/flashcards.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FlashcardsComponent } from './flashcards.component'; + +describe('FlashcardsComponent', () => { + let component: FlashcardsComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ FlashcardsComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(FlashcardsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/flashcards/flashcards/flashcards.component.ts b/src/app/flashcards/flashcards/flashcards.component.ts new file mode 100644 index 0000000..bddafd8 --- /dev/null +++ b/src/app/flashcards/flashcards/flashcards.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-flashcards', + templateUrl: './flashcards.component.html', + styleUrls: ['./flashcards.component.css'] +}) +export class FlashcardsComponent implements OnInit { + + constructor() { } + + ngOnInit() { + } + +} diff --git a/src/app/flashcards/set.ts b/src/app/flashcards/set.ts new file mode 100644 index 0000000..897085a --- /dev/null +++ b/src/app/flashcards/set.ts @@ -0,0 +1,10 @@ +export interface Set { + id: number; + name: string; + category: string; + owner: string; + add_date: string; + edit_date: string; + grade: number; + permission: string; +} diff --git a/src/app/flashcards/test-results/test-results.component.css b/src/app/flashcards/test-results/test-results.component.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/flashcards/test-results/test-results.component.html b/src/app/flashcards/test-results/test-results.component.html new file mode 100644 index 0000000..bb7067d --- /dev/null +++ b/src/app/flashcards/test-results/test-results.component.html @@ -0,0 +1,6 @@ +

Twój wynik to {{(result/maxPts*100) | number : '1.0-2' }}%!

+

Poprawne odpowiedzi: {{result}}

+

Niepoprawne odpowiedzi: {{maxPts-result}}

+
+

Twój czas: {{minutes}} m {{seconds}} s {{milliseconds}} ms

+
\ No newline at end of file diff --git a/src/app/flashcards/test-results/test-results.component.spec.ts b/src/app/flashcards/test-results/test-results.component.spec.ts new file mode 100644 index 0000000..54d1c9a --- /dev/null +++ b/src/app/flashcards/test-results/test-results.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TestResultsComponent } from './test-results.component'; + +describe('TestResultsComponent', () => { + let component: TestResultsComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ TestResultsComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(TestResultsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/flashcards/test-results/test-results.component.ts b/src/app/flashcards/test-results/test-results.component.ts new file mode 100644 index 0000000..76ef84e --- /dev/null +++ b/src/app/flashcards/test-results/test-results.component.ts @@ -0,0 +1,20 @@ +import { Component, OnInit, Input } from '@angular/core'; +@Component({ + selector: 'app-test-results', + templateUrl: './test-results.component.html', + styleUrls: ['./test-results.component.css'] +}) +export class TestResultsComponent implements OnInit { + @Input() result: number; + @Input() maxPts: number; + @Input() minutes: number; + @Input() seconds: number; + @Input() milliseconds: number; + @Input() timer: Boolean; + + constructor() { } + + ngOnInit() { + } + +} diff --git a/src/app/footer/footer.component.css b/src/app/footer/footer.component.css new file mode 100644 index 0000000..dbbc5a3 --- /dev/null +++ b/src/app/footer/footer.component.css @@ -0,0 +1,10 @@ +.footer{ + color: white; + text-align: center; + position: fixed; + bottom: 0; + width: 100%; + max-width: 100%; + background-color: rgb(24, 22, 22); + padding: 1rem 0; +} diff --git a/src/app/footer/footer.component.html b/src/app/footer/footer.component.html new file mode 100644 index 0000000..2715926 --- /dev/null +++ b/src/app/footer/footer.component.html @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/src/app/footer/footer.component.spec.ts b/src/app/footer/footer.component.spec.ts new file mode 100644 index 0000000..2ca6c45 --- /dev/null +++ b/src/app/footer/footer.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FooterComponent } from './footer.component'; + +describe('FooterComponent', () => { + let component: FooterComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ FooterComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(FooterComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/footer/footer.component.ts b/src/app/footer/footer.component.ts new file mode 100644 index 0000000..67f1378 --- /dev/null +++ b/src/app/footer/footer.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-footer', + templateUrl: './footer.component.html', + styleUrls: ['./footer.component.css'] +}) +export class FooterComponent implements OnInit { + + constructor() { } + + ngOnInit() { + } + +} diff --git a/src/app/groups/group-creator/group-creator.component.css b/src/app/groups/group-creator/group-creator.component.css new file mode 100644 index 0000000..bcf64d8 --- /dev/null +++ b/src/app/groups/group-creator/group-creator.component.css @@ -0,0 +1,40 @@ +label { + display: block; +} + +.container { + background-color: #181616; + padding: 30px; + margin-top: 2rem; + margin-bottom: 2rem; + text-align: center; +} + +.wrapper-add { + width: 100%; + display: flex; + justify-content: center; + align-content: center; + margin-bottom: 4rem; +} + +.wrapper { + width: 100%; + padding: 30px; + min-height: 100%; + margin-bottom: 40px; +} + +.content { + background-color: #181616; + padding: 30px; + margin-top: 2rem; + margin-bottom: 2rem; + text-align: center; + margin-left: auto; + margin-right: auto; +} + +.buttons-container > div { + margin: 10px; +} diff --git a/src/app/groups/group-creator/group-creator.component.html b/src/app/groups/group-creator/group-creator.component.html new file mode 100644 index 0000000..3e749ac --- /dev/null +++ b/src/app/groups/group-creator/group-creator.component.html @@ -0,0 +1,35 @@ +
+
+
+

Tworzenie grupy

+
+
+ +
+ +

+ + +
+
+
+
+ + +

Twoja grupa została utworzona prawidłowo.

+

Poniżej znajduje się kod dostępu poprzez który możesz dodawać członków:

+ +

{{ createdGroup.groupKey }}

+
+

Kod ten będzie dostępny także w sekcji "Zarządzaj grupą."

+
+ + +
diff --git a/src/app/groups/group-creator/group-creator.component.spec.ts b/src/app/groups/group-creator/group-creator.component.spec.ts new file mode 100644 index 0000000..220622d --- /dev/null +++ b/src/app/groups/group-creator/group-creator.component.spec.ts @@ -0,0 +1,49 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { GroupCreatorComponent } from './group-creator.component'; +import { GroupsService } from '../groups.service'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { BrowserAnimationsModule, NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { AuthenticationService } from '../../authentication.service'; +import { RouterTestingModule } from '@angular/router/testing'; +import { FormsModule } from '@angular/forms'; +import { HttpClientModule } from '@angular/common/http'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; + +describe('GroupCreatorComponent', () => { + let component: GroupCreatorComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [GroupCreatorComponent], + schemas: [NO_ERRORS_SCHEMA], + providers: [GroupsService, AuthenticationService], + imports: [RouterTestingModule, FormsModule, HttpClientModule, MatSnackBarModule, NoopAnimationsModule] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(GroupCreatorComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should create group', async(() => { + fixture.whenStable().then(() => { + fixture.autoDetectChanges(); + spyOn(component, 'add').and.callThrough(); + fixture.debugElement.nativeElement.querySelectorAll('.form-control')[0].value = 'group-name'; + fixture.debugElement.nativeElement.querySelectorAll('.form-control')[1].value = 'group-descripion'; + fixture.debugElement.nativeElement.querySelectorAll('.btn-study-cave')[0].click(); + expect(component.add).toHaveBeenCalled(); + }); + + })); + +}); diff --git a/src/app/groups/group-creator/group-creator.component.ts b/src/app/groups/group-creator/group-creator.component.ts new file mode 100644 index 0000000..53b4e9a --- /dev/null +++ b/src/app/groups/group-creator/group-creator.component.ts @@ -0,0 +1,63 @@ +import { Component, OnInit, OnDestroy, ElementRef, ViewChild } from '@angular/core'; +import { ISubscription } from 'rxjs/Subscription'; +import { Router } from '@angular/router'; +import { MatSnackBar } from '@angular/material/snack-bar'; + +import { GroupsService } from '../groups.service'; +import { Group } from '../group'; + +@Component({ + selector: 'app-group-creator', + templateUrl: './group-creator.component.html', + styleUrls: ['./group-creator.component.css'] +}) +export class GroupCreatorComponent implements OnInit, OnDestroy { + + public currentUser: string; + public postGroupsSubscription: ISubscription; + public createdGroup: Group = {}; + public showInfoDialog: boolean; + + @ViewChild('btn') btn: ElementRef; + + constructor(private groupsService: GroupsService, private router: Router, + private snackBar: MatSnackBar) { } + + ngOnInit() { + this.showInfoDialog = false; + if (localStorage.getItem('currentUser') !== null) { + this.currentUser = JSON.parse(localStorage.getItem('currentUser')).username; + } + } + + add(value: any) { + this.btn.nativeElement.disabled = true; + const toSend = { + name: value.name, + description: value.description, + owner: this.currentUser + }; + this.postGroupsSubscription = this.groupsService.postGroup(toSend).subscribe( + success => { + this.createdGroup = success; + this.showInfoDialog = true; + }, + error => { + this.btn.nativeElement.disabled = false; + this.snackBar.open('Wystąpił błąd serwera. Spróbuj ponownie później.', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } + ); + } + + goToManageGroup() { + this.router.navigate(['groups/manage', this.createdGroup.id]); + } + + ngOnDestroy() { + if (this.postGroupsSubscription) { + this.postGroupsSubscription.unsubscribe(); + } + } + +} diff --git a/src/app/groups/group-details/group-details.component.css b/src/app/groups/group-details/group-details.component.css new file mode 100644 index 0000000..722b21b --- /dev/null +++ b/src/app/groups/group-details/group-details.component.css @@ -0,0 +1,24 @@ +.wrapper{ + width: 100%; + padding: 30px; + min-height: 100%; + margin-bottom: 40px; +} + +.content{ + background-color: #181616; + padding: 30px; + margin-top: 2rem; + margin-bottom: 2rem; + text-align: center; + margin-left: auto; + margin-right: auto; +} + +ag-grid-angular{ + margin: 10px; +} + +.buttons-container > div{ + margin: 10px; +} diff --git a/src/app/groups/group-details/group-details.component.html b/src/app/groups/group-details/group-details.component.html new file mode 100644 index 0000000..9881f36 --- /dev/null +++ b/src/app/groups/group-details/group-details.component.html @@ -0,0 +1,51 @@ +
+
+
+ + + +
+
+
+
+ +
+
+

{{group?.name}}

+
+

Opis grupy:

+

{{group?.description}}

+
+
+ + +
+
+ + + +
+
+ + +
+

Lista {{dataToDisplay}}

+ + +
+
+ + + Czy chcesz usunąć zasób? + + + + + +
\ No newline at end of file diff --git a/src/app/groups/group-details/group-details.component.spec.ts b/src/app/groups/group-details/group-details.component.spec.ts new file mode 100644 index 0000000..388c641 --- /dev/null +++ b/src/app/groups/group-details/group-details.component.spec.ts @@ -0,0 +1,91 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { GroupDetailsComponent } from './group-details.component'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { RouterTestingModule } from '@angular/router/testing'; +import { GroupsService } from '../groups.service'; +import { HttpClientModule } from '@angular/common/http'; +import { AuthenticationService } from '../../authentication.service'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { AppRoutingModule } from '../../app-routing.module'; +import { RouterModule, Routes, Router } from '@angular/router'; +import { AuthGuard } from '../../auth-guard.service'; +import { APP_BASE_HREF } from '@angular/common'; +import { GroupServiceMockService } from '../group-service-mock.service'; +import { ManageGroupComponent } from '../manage-group/manage-group.component'; +import { HistoryOfActivityInGroupComponent } from '../history-of-activity-in-group/history-of-activity-in-group.component'; + +describe('GroupDetailsComponent', () => { + let component: GroupDetailsComponent; + let fixture: ComponentFixture; + const routes: Routes = [{ path: 'groups/:id', component: GroupDetailsComponent, canActivate: [AuthGuard] }, + { path: 'groups/manage/:id', component: ManageGroupComponent, canActivate: [AuthGuard] }, + { path: 'groups/history/:id', component: HistoryOfActivityInGroupComponent, canActivate: [AuthGuard] } + ]; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [GroupDetailsComponent, ManageGroupComponent, HistoryOfActivityInGroupComponent], + schemas: [NO_ERRORS_SCHEMA], + imports: [RouterTestingModule, HttpClientModule, MatSnackBarModule, RouterModule.forRoot(routes)], + providers: [AuthenticationService, + AuthGuard, + { provide: GroupsService, useClass: GroupServiceMockService }, + { provide: APP_BASE_HREF, useValue: 'groups/0' } + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(GroupDetailsComponent); + component = fixture.componentInstance; + component.currentUser = + localStorage.setItem('currentUser', JSON.stringify({ + username: 'lukasz', authorization: 'a' + })); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should call mock service and display flashcards table', async(() => { + fixture.autoDetectChanges(); + spyOn(component, 'isDisplayed').and.callThrough(); + fixture.debugElement.nativeElement.querySelectorAll('.buttons-container .btn-study-cave')[3].click(); + expect(component.isDisplayed).toHaveBeenCalledWith('fiszek'); + })); + + it('should call mock service and display tests table', async(() => { + fixture.autoDetectChanges(); + spyOn(component, 'isDisplayed').and.callThrough(); + fixture.debugElement.nativeElement.querySelectorAll('.buttons-container .btn-study-cave')[4].click(); + expect(component.isDisplayed).toHaveBeenCalledWith('testów'); + })); + + it('should call mock service and display materials table', async(() => { + fixture.autoDetectChanges(); + spyOn(component, 'isDisplayed').and.callThrough(); + fixture.debugElement.nativeElement.querySelectorAll('.buttons-container .btn-study-cave')[2].click(); + expect(component.isDisplayed).toHaveBeenCalledWith('materiałów'); + })); + + it('should navigate to group management', async(() => { + fixture.autoDetectChanges(); + const router = TestBed.get(Router); + fixture.debugElement.nativeElement.querySelectorAll('.buttons-container .btn-study-cave')[0].click(); + router.navigate(['groups/manage', 1]).then(() => { + expect(router.url).toEqual('/groups/manage/1'); + }); + })); + + it('should navigate to user history', async(() => { + fixture.autoDetectChanges(); + const router = TestBed.get(Router); + fixture.debugElement.nativeElement.querySelectorAll('.btn-study-cave')[2].click(); + router.navigate(['groups/history', 1]).then(() => { + expect(router.url).toEqual('/groups/history/1'); + }); + })); +}); diff --git a/src/app/groups/group-details/group-details.component.ts b/src/app/groups/group-details/group-details.component.ts new file mode 100644 index 0000000..0670d8a --- /dev/null +++ b/src/app/groups/group-details/group-details.component.ts @@ -0,0 +1,259 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { GroupsService } from '../groups.service'; +import { Group } from '../group'; +import { Subscription } from 'rxjs/Subscription'; +import localeText from './../../../assets/localeText'; +import { GridOptions, RowDoubleClickedEvent } from 'ag-grid-community/main'; + +@Component({ + selector: 'app-group-details', + templateUrl: './group-details.component.html', + styleUrls: ['./group-details.component.css'] +}) +export class GroupDetailsComponent implements OnInit, OnDestroy { + id: number; + currentUser; + dataToDisplay = ''; + public group: Group; + data; + display = false; + whatToDelete; + groupDetailsSubscription: Subscription; + resourceDeleteSubscription: Subscription; + flashcardsSusbscritpion: Subscription; + materialsSubscription: Subscription; + testsSubscription: Subscription; + localeText = localeText; + public gridApi; + public gridOptions: GridOptions; + + + columnDefs = [ + { headerName: 'ID', field: 'id', headerTooltip: 'ID' }, + { headerName: 'Nazwa', field: 'title', headerTooltip: 'Nazwa' }, + { headerName: 'Data dodania', field: 'addDate', headerTooltip: 'Data dodania', hide: false }, + { headerName: 'Właściciel', field: 'owner', headerTooltip: 'Właściciel', hide: false }, + { + headerName: '', + suppressMenu: true, + suppressSorting: true, + cellRenderer: this.customCellRendererFunc + } + ]; + + constructor(private route: ActivatedRoute, private groupService: GroupsService, private router: Router) { } + + + customCellRendererFunc(params) { + const currentUsername = JSON.parse(localStorage.getItem('currentUser')).username; + const groupOwnerUsername = localStorage.getItem('groupOwnerUsername'); + return groupOwnerUsername === currentUsername ? + `` : ''; + } + + ngOnInit() { + this.id = this.route.snapshot.params.id; + this.currentUser = JSON.parse(localStorage.getItem('currentUser')); + this.groupDetailsSubscription = this.groupService.getGroupDetails(this.id) + .subscribe(data => { + this.group = data; + localStorage.setItem('groupOwnerUsername', this.group.owner); + }); + + this.gridOptions = { + rowHeight: 50, + headerHeight: 25, + getRowStyle: function (params) { + return { + cursor: 'pointer' + }; + }, + }; + } + + public onRowClicked(e) { + if (e.event.target !== undefined) { + const data = e.data; + const actionType = e.event.target.getAttribute('data-action-type'); + + switch (actionType) { + case 'remove': + return this.onActionRemoveClick(e); + default: + this.goTo(e); + } + } + } + + goTo(event: RowDoubleClickedEvent) { + if (this.dataToDisplay === 'fiszek') { + this.router.navigate(['flashcards/sets', event.data.id]); + + } + if (this.dataToDisplay === 'materiałów') { + this.router.navigate(['materials', event.data.id]); + + } + if (this.dataToDisplay === 'testów') { + this.router.navigate(['tests', event.data.id]); + } + } + + + redirectTo(uri) { + this.router.navigateByUrl('/', { skipLocationChange: true }).then(() => + this.router.navigate([uri])); + } + + public onActionRemoveClick(e) { + this.whatToDelete = e.data.id; + this.display = true; + } + + deleteResource() { + if (this.dataToDisplay === 'fiszek') { + this.resourceDeleteSubscription = this.groupService.deleteResource(this.id, 'sets', this.whatToDelete).subscribe( + data => { + this.isDisplayed('fiszek'); + this.display = false; + this.gridApi.refreshCells(); + } + ); + + } + if (this.dataToDisplay === 'materiałów') { + this.resourceDeleteSubscription = this.groupService.deleteResource(this.id, 'materials', this.whatToDelete).subscribe( + data => { + this.isDisplayed('materiałów'); + this.display = false; + this.gridApi.refreshCells(); + } + ); + + } + if (this.dataToDisplay === 'testów') { + this.resourceDeleteSubscription = this.groupService.deleteResource(this.id, 'tests', this.whatToDelete).subscribe( + data => { + this.isDisplayed('testów'); + this.display = false; + this.gridApi.refreshCells(); + } + ); + + } + + } + + onGridReady(params) { + this.gridApi = params.api; + this.gridApi.sizeColumnsToFit(); + } + + onGridColumnsChanged(params) { + params.api.sizeColumnsToFit(); + } + + onGridSizeChanged(params) { + if (params.clientWidth < 800) { + this.columnDefs = [ + { headerName: 'ID', field: 'id', headerTooltip: 'ID' }, + { headerName: 'Nazwa', field: 'title', headerTooltip: 'Nazwa' }, + { headerName: 'Data dodania', field: 'addDate', headerTooltip: 'Data dodania', hide: false }, + { headerName: 'Właściciel', field: 'owner', headerTooltip: 'Właściciel', hide: false }, + { + headerName: '', + suppressMenu: true, + suppressSorting: true, + cellRenderer: this.customCellRendererFunc + } + ]; + } else { + this.columnDefs = [ + { headerName: 'ID', field: 'id', headerTooltip: 'ID' }, + { headerName: 'Nazwa', field: 'title', headerTooltip: 'Nazwa' }, + { headerName: 'Data dodania', field: 'addDate', headerTooltip: 'Data dodania', hide: false }, + { headerName: 'Właściciel', field: 'owner', headerTooltip: 'Właściciel', hide: false }, + { + headerName: '', + suppressMenu: true, + suppressSorting: true, + cellRenderer: this.customCellRendererFunc + } + ]; + } + + params.api.sizeColumnsToFit(); + } + onGidColumnsChanged(params) { + params.api.sizeColumnsToFit(); + } + + ngOnDestroy() { + if (this.groupDetailsSubscription) { + this.groupDetailsSubscription.unsubscribe(); + } + + if (this.flashcardsSusbscritpion) { + this.flashcardsSusbscritpion.unsubscribe(); + } + if (this.materialsSubscription) { + this.materialsSubscription.unsubscribe(); + } + if (this.testsSubscription) { + this.testsSubscription.unsubscribe(); + } + if (this.resourceDeleteSubscription) { + this.resourceDeleteSubscription.unsubscribe(); + } + localStorage.removeItem('groupOwnerUsername'); + } + + goToEditing() { + this.router.navigate(['groups/manage', this.id]); + } + isDisplayed(resource) { + if (resource === 'fiszek') { + this.localeText.noRowsToShow = 'Brak fiszek do wyświetlenia'; + setTimeout(() => { + this.flashcardsSusbscritpion = this.groupService.getResource(this.id, 'flashcardsets').subscribe(data => this.data = data); + this.dataToDisplay = resource; + }, 200); + + } + if (resource === 'materiałów') { + this.localeText.noRowsToShow = 'Brak materiałów do wyświetlenia'; + setTimeout(() => { + this.materialsSubscription = this.groupService.getResource(this.id, 'materials').subscribe(data => this.data = data); + this.dataToDisplay = resource; + }, 200); + + } + if (resource === 'testów') { + this.localeText.noRowsToShow = 'Brak testów do wyświetlenia'; + setTimeout(() => { + this.testsSubscription = this.groupService.getResource(this.id, 'tests').subscribe(data => this.data = data); + this.dataToDisplay = resource; + }, 200); + + } + } + + goToAddingResource() { + this.router.navigate(['groups/add-resources', this.id]); + } + + goToRankings() { + this.router.navigate(['groups/ranking', this.id]); + } + + goToHistory() { + this.router.navigate(['groups/history', this.id]); + } + + goToGroupsList() { + this.router.navigate(['/my-groups/']); + } +} diff --git a/src/app/groups/group-service-mock.service.ts b/src/app/groups/group-service-mock.service.ts new file mode 100644 index 0000000..1ca3a0f --- /dev/null +++ b/src/app/groups/group-service-mock.service.ts @@ -0,0 +1,101 @@ +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs/Observable'; +import { of } from 'rxjs/observable/of'; + +@Injectable() +export class GroupServiceMockService { + + constructor() { } + + getResource(id, resource): Observable { + const mockData = [ + { + 'id': 12, + 'title': 'x', + 'addDate': '2019-01-11', + 'owner': 'lukasz', + 'grade': 0 + } + ]; + return of(mockData); + } + + getGroupDetails(id): Observable { + const mockData = { + 'id': 1, + 'owner': 'lukasz', + 'name': 'aaa', + 'description': 'aaa', + 'groupKey': 'wicRLCNW5m', + 'users': [ + { + 'username': 'ukasz', + 'id': 13 + } + ] + }; + return of(mockData); + } + + getActivityHistory(id: number, sort = 'DESC', startDate: Date = null, endDate: Date = null): Observable { + const mockData = [{ + 'date': '01/02/2019', + 'from': 'test', + 'to': 'test2', + 'type': 'type1', + 'points': 21, + 'comment': 'comment1', + 'resourceType': 'resourceType1', + 'resourceName': 'resourceName1', + 'd': 21, + }]; + return of(mockData); + } + + newKeyGenerate(id): Observable { + const mockData = 'XYZ'; + return of(mockData); + } + + getGroups(): Observable { + const mockData = [{ + 'id': 1, + 'owner': 'lukasz', + 'name': 'aaa', + 'description': 'aaa', + 'groupKey': 'wicRLCNW5m', + 'users': [ + { + 'username': 'ukasz', + 'id': 13 + } + ] + }]; + return of(mockData); + } + + getGlobalRanking(groupId: number): Observable { + const mockData = [{ + 'points': 1, + 'username': 'lukasz', + }, + { + 'points': 2, + 'username': 'lukasz2', + }]; + return of(mockData); + } + + getTestsRanking(groupId: number): Observable { + const mockData = [{ + 'points': 1, + 'username': 'lukasz', + }, + { + 'points': 2, + 'username': 'lukasz2', + }]; + return of(mockData); + } + +} diff --git a/src/app/groups/group.ts b/src/app/groups/group.ts new file mode 100644 index 0000000..f9a5516 --- /dev/null +++ b/src/app/groups/group.ts @@ -0,0 +1,32 @@ +export class Group { + public id?: number; + public name?: string; + public description?: string; + public groupKey?: string; // kod dostępu + // public key?: string; // kod dostępu + public role?: string; // MEMBER || OWNER + public owner?: string; // nazwa użytkownika z rolą OWNER + public users?: UsersConfig[]; + + constructor() {} +} + +export interface UsersConfig { + id: number; + username: string; +} + +export class ActivityHistory { + + public date?: Date; + public from?: string; + public to?: string; + public type?: string; + public points?: number; + public comment?: string; + public resourceType?: string; + public resourceName?: string; + public id?: number; + + constructor() {} +} diff --git a/src/app/groups/groups.module.ts b/src/app/groups/groups.module.ts new file mode 100644 index 0000000..9a2d1b5 --- /dev/null +++ b/src/app/groups/groups.module.ts @@ -0,0 +1,72 @@ +import { BrowserModule } from '@angular/platform-browser'; +import { NgModule } from '@angular/core'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { HttpModule } from '@angular/http'; +import { HttpClientModule } from '@angular/common/http'; +import { RouterModule } from '@angular/router'; +import { AgGridModule } from 'ag-grid-angular'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { DialogModule } from 'primeng/dialog'; +import { ConfirmDialogModule } from 'primeng/confirmdialog'; +import { ConfirmationService } from 'primeng/api'; +import { MatDatepickerModule } from '@angular/material/datepicker'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatInputModule } from '@angular/material/input'; +import { MatNativeDateModule } from '@angular/material'; + +import { GroupsService } from './groups.service'; + +import { MyGroupsComponent } from './my-groups/my-groups.component'; +import { GroupCreatorComponent } from './group-creator/group-creator.component'; +import { JoinToGroupComponent } from './join-to-group/join-to-group.component'; +import { GroupDetailsComponent } from './group-details/group-details.component'; +import { ManageGroupComponent } from './manage-group/manage-group.component'; +import { SharingResourcesInGroupsComponent } from './sharing-resources-in-groups/sharing-resources-in-groups.component'; +import { ListboxModule } from 'primeng/listbox'; +import { WaitingResourcesComponent } from './waiting-resources/waiting-resources.component'; +import { TestToGroupPreviewComponent } from './waiting-resources/test-to-group-preview/test-to-group-preview.component'; +import { MaterialToGroupPreviewComponent } from './waiting-resources/material-to-group-preview/material-to-group-preview.component'; +import { FlashcardsToGroupPreviewComponent } from './waiting-resources/flashcards-to-group-preview/flashcards-to-group-preview.component'; +import { RankingComponent } from './ranking/ranking.component'; +import { HistoryOfActivityInGroupComponent } from './history-of-activity-in-group/history-of-activity-in-group.component'; + + +@NgModule({ + imports: [ + BrowserModule, + FormsModule, + HttpModule, + HttpClientModule, + RouterModule, + AgGridModule.withComponents([]), + BrowserAnimationsModule, + MatSnackBarModule, + DialogModule, + ConfirmDialogModule, + ListboxModule, + MatFormFieldModule, + MatInputModule, + MatDatepickerModule, + ReactiveFormsModule, + MatNativeDateModule + ], + declarations: [ + MyGroupsComponent, + GroupCreatorComponent, + JoinToGroupComponent, + GroupDetailsComponent, + ManageGroupComponent, + SharingResourcesInGroupsComponent, + WaitingResourcesComponent, + TestToGroupPreviewComponent, + MaterialToGroupPreviewComponent, + FlashcardsToGroupPreviewComponent, + RankingComponent, + HistoryOfActivityInGroupComponent + + ], + providers: [GroupsService, ConfirmationService] +}) +export class GroupsModule { } + diff --git a/src/app/groups/groups.service.spec.ts b/src/app/groups/groups.service.spec.ts new file mode 100644 index 0000000..caee70c --- /dev/null +++ b/src/app/groups/groups.service.spec.ts @@ -0,0 +1,20 @@ +import { TestBed, inject } from '@angular/core/testing'; + +import { GroupsService } from './groups.service'; +import { RouterTestingModule } from '@angular/router/testing'; +import { HttpClientModule } from '@angular/common/http'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { AuthenticationService } from '../authentication.service'; + +describe('GroupsService', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [GroupsService, AuthenticationService], + imports: [ RouterTestingModule, HttpClientModule, MatSnackBarModule] + }); + }); + + it('should be created', inject([GroupsService], (service: GroupsService) => { + expect(service).toBeTruthy(); + })); +}); diff --git a/src/app/groups/groups.service.ts b/src/app/groups/groups.service.ts new file mode 100644 index 0000000..33a1a4a --- /dev/null +++ b/src/app/groups/groups.service.ts @@ -0,0 +1,370 @@ +import { Injectable } from '@angular/core'; +import { HttpClient, HttpHeaders } from '@angular/common/http'; +import { Observable } from 'rxjs/Observable'; +import { Router } from '@angular/router'; +import 'rxjs/add/operator/map'; + +import { AuthenticationService } from '../authentication.service'; +import { ResourceStatus } from './resource'; +import { JoinToGroupForm } from './join-to-group/join-to-group'; + +@Injectable() +export class GroupsService { + + private headers; + + private getGroupsURL = 'groups'; + private getMaterialsToAddURL = 'users/materials?excludedGroupId={groupId}'; + private getTestsToAddURL = 'users/tests?excludedGroupId={groupId}'; + private getFlashcardsToAddURL = 'users/sets?excludedGroupId={groupId}'; + + private getWaitingMaterialsInGroupURL = 'groups/{groupId}/content/materials/unverified'; + private getWaitingTestsInGroupURL = 'groups/{groupId}/content/tests/unverified'; + private getWaitingFlashcardsInGroupURL = 'groups/{groupId}/content/flashcards/unverified'; + + private acceptMaterialsInGroupURL = 'groups/{groupId}/materials/{materialId}/status'; + private acceptTestsInGroupURL = 'groups/{groupId}/tests/{testId}/status'; + private acceptFlashcardsInGroupURL = 'groups/{groupId}/flashcard-sets/{setId}/status'; + + private getActivityHistoryURL = 'groups/{groupId}/users/activity?sort={sort}'; + + constructor(private httpClient: HttpClient, private authenticationService: AuthenticationService) { + this.setHeaders(); + } + + setHeaders() { + if (localStorage.getItem('currentUser')) { + this.headers = new HttpHeaders({ + 'Content-Type': 'application/json', + 'Authorization': '' + this.authenticationService.getToken() + }); + } else { + this.headers = new HttpHeaders({ + 'Content-Type': 'application/json' + }); + } + } + + getGroups(): Observable { + this.setHeaders(); + const url = this.getGroupsURL; + return this.httpClient.get(url, { headers: this.headers }); + } + + postGroup(body): Observable { + this.setHeaders(); + const url = this.getGroupsURL; + return this.httpClient.post(url, body, { headers: this.headers }); + } + + public joinToGroup(nameAndCodeOfGroup: JoinToGroupForm): Observable { + this.setHeaders(); + return this.httpClient.post(`groups/members`, + { groupCode: nameAndCodeOfGroup.code }, + { + headers: this.headers, + observe: 'response', + responseType: 'text' + }).catch((error: any) => { + return Observable.throw(error); + }); + } + + deleteResource(id, resource, resId) { + this.setHeaders(); + return this.httpClient.delete('groups/' + id + '/content/' + resource + '/' + resId, { + headers: this.headers, + observe: 'response', + responseType: 'text' + }).catch((error: any) => { + return Observable.throw(error); + }); + } + + getGroupDetails(id): Observable { + const url = 'groups/' + id + '/info'; + this.setHeaders(); + return this.httpClient.get(url, { headers: this.headers }); + } + + getResource(id, resource): Observable { + const url = 'groups/' + id + '/content/' + resource; + this.setHeaders(); + return this.httpClient.get(url, { headers: this.headers }); + } + + deleteUser(id, userId): Observable { + const url = 'groups/' + id + '/member/' + userId; + this.setHeaders(); + return this.httpClient.delete(url, { headers: this.headers }); + } + + deleteGroup(id): Observable { + const url = 'groups/' + id; + this.setHeaders(); + return this.httpClient.delete(url, { headers: this.headers }); + } + + newKeyGenerate(id): Observable { + if (localStorage.getItem('currentUser')) { + this.headers = new HttpHeaders({ + 'Content-Type': 'text/plain', + 'Authorization': '' + this.authenticationService.getToken() + }); + } else { + this.headers = new HttpHeaders({ + 'Content-Type': 'text/plain' + }); + } + + const url = 'groups/' + id + '/generate'; + this.setHeaders(); + return this.httpClient.get(url, { headers: this.headers, responseType: 'text' }); + } + + getMaterialsToAdd(id: number): Observable { + this.setHeaders(); + const url = this.getMaterialsToAddURL.replace('{groupId}', id.toString()); + return this.httpClient.get(url, { headers: this.headers }).catch((error: any) => { + return Observable.throw(error); + }); + } + + getTestsToAdd(id: number): Observable { + this.setHeaders(); + const url = this.getTestsToAddURL.replace('{groupId}', id.toString()); + return this.httpClient.get(url, { headers: this.headers }).catch((error: any) => { + return Observable.throw(error); + }); + } + + getFlashcardsToAdd(id: number): Observable { + this.setHeaders(); + const url = this.getFlashcardsToAddURL.replace('{groupId}', id.toString()); + return this.httpClient.get(url, { headers: this.headers }).catch((error: any) => { + return Observable.throw(error); + }); + } + + addFlashcardsToGroup(group: number, flashcard: Array): Observable { + const testToSend = flashcard.map(item => { + return { setId: item }; + }); + this.setHeaders(); + return this.httpClient.post(`groups/${group}/flashcard-sets`, + testToSend, + { + headers: this.headers, + observe: 'response', + responseType: 'text' + }).catch((error: any) => { + return Observable.throw(error); + }); + } + + addTestsToGroup(group: number, tests: Array): Observable { + const testToSend = tests.map(item => { + return { testId: item }; + }); + this.setHeaders(); + return this.httpClient.post(`groups/${group}/tests`, + testToSend, + { + headers: this.headers, + observe: 'response', + responseType: 'text' + }).catch((error: any) => { + return Observable.throw(error); + }); + } + + addMaterialsToGroup(group: number, materials: Array): Observable { + const testToSend = materials.map(item => { + return { materialId: item }; + }); + this.setHeaders(); + return this.httpClient.post(`groups/${group}/materials`, + testToSend, + { + headers: this.headers, + observe: 'response', + responseType: 'text' + }).catch((error: any) => { + return Observable.throw(error); + }); + } + + getWaitingMaterialsInGroup(id: number): Observable { + this.setHeaders(); + const url = this.getWaitingMaterialsInGroupURL.replace('{groupId}', id.toString()); + return this.httpClient.get(url, { headers: this.headers }).catch((error: any) => { + return Observable.throw(error); + }); + } + + getWaitingTestsInGroup(id: number): Observable { + this.setHeaders(); + const url = this.getWaitingTestsInGroupURL.replace('{groupId}', id.toString()); + return this.httpClient.get(url, { headers: this.headers }).catch((error: any) => { + return Observable.throw(error); + }); + } + + getWaitingFlashcardsInGroup(id: number): Observable { + this.setHeaders(); + const url = this.getWaitingFlashcardsInGroupURL.replace('{groupId}', id.toString()); + return this.httpClient.get(url, { headers: this.headers }).catch((error: any) => { + return Observable.throw(error); + }); + } + + acceptMaterialsInGroup(groupId: number, materialId: number, points: number, comment: string): Observable { + this.setHeaders(); + const url = this.acceptMaterialsInGroupURL.replace('{groupId}', groupId.toString()).replace('{materialId}', materialId.toString()); + const body = { + points: points, + status: ResourceStatus.accepted + }; + if (comment.trim().length > 0) { + body['comment'] = comment; + } + return this.httpClient.put(url, body, { + headers: this.headers, + observe: 'response', + responseType: 'text' + }).catch((error: any) => { + return Observable.throw(error); + }); + } + + acceptTestsInGroup(groupId: number, testId: number, points: number, comment: string): Observable { + this.setHeaders(); + const url = this.acceptTestsInGroupURL.replace('{groupId}', groupId.toString()).replace('{testId}', testId.toString()); + const body = { + points: points, + status: ResourceStatus.accepted + }; + if (comment.trim().length > 0) { + body['comment'] = comment; + } + return this.httpClient.put(url, body, { + headers: this.headers, + observe: 'response', + responseType: 'text' + }).catch((error: any) => { + return Observable.throw(error); + }); + } + + acceptFlashcardsInGroup(groupId: number, setId: number, points: number, comment: string): Observable { + this.setHeaders(); + const url = this.acceptFlashcardsInGroupURL.replace('{groupId}', groupId.toString()).replace('{setId}', setId.toString()); + const body = { + points: points, + status: ResourceStatus.accepted + }; + if (comment.trim().length > 0) { + body['comment'] = comment; + } + return this.httpClient.put(url, body, { + headers: this.headers, + observe: 'response', + responseType: 'text' + }).catch((error: any) => { + return Observable.throw(error); + }); + } + + rejectMaterialsFromGroup(groupId: number, materialId: number, comment: string): Observable { + this.setHeaders(); + const url = this.acceptMaterialsInGroupURL.replace('{groupId}', groupId.toString()).replace('{materialId}', materialId.toString()); + const body = { + points: 0, + status: ResourceStatus.rejected + }; + if (comment.trim().length > 0) { + body['comment'] = comment; + } + return this.httpClient.put(url, body, { + headers: this.headers, + observe: 'response', + responseType: 'text' + }).catch((error: any) => { + return Observable.throw(error); + }); + } + + rejectTestsFromGroup(groupId: number, testId: number, comment: string): Observable { + this.setHeaders(); + const url = this.acceptTestsInGroupURL.replace('{groupId}', groupId.toString()).replace('{testId}', testId.toString()); + const body = { + points: 0, + status: ResourceStatus.rejected + }; + if (comment.trim().length > 0) { + body['comment'] = comment; + } + return this.httpClient.put(url, body, { + headers: this.headers, + observe: 'response', + responseType: 'text' + }).catch((error: any) => { + return Observable.throw(error); + }); + } + + rejectFlashcardsFromGroup(groupId: number, setId: number, comment: string): Observable { + this.setHeaders(); + const url = this.acceptFlashcardsInGroupURL.replace('{groupId}', groupId.toString()).replace('{setId}', setId.toString()); + const body = { + points: 0, + status: ResourceStatus.rejected + }; + if (comment.trim().length > 0) { + body['comment'] = comment; + } + return this.httpClient.put(url, body, { + headers: this.headers, + observe: 'response', + responseType: 'text' + }).catch((error: any) => { + return Observable.throw(error); + }); + } + + getGlobalRanking(groupId: number): Observable { + this.setHeaders(); + return this.httpClient.get(`groups/${groupId}/leaderboard`, { headers: this.headers }).catch((error: any) => { + return Observable.throw(error); + }); + } + + getTestsRanking(groupId: number): Observable { + this.setHeaders(); + return this.httpClient.get(`groups/${groupId}/testleaderboard`, { headers: this.headers }).catch((error: any) => { + return Observable.throw(error); + }); + } + + getActivityHistory(id: number, sort = 'DESC', startDate: Date = null, endDate: Date = null): Observable { + this.setHeaders(); + let url = this.getActivityHistoryURL.replace('{groupId}', id.toString()) + .replace('{sort}', sort); + if (startDate !== null) { + const startDateStr = `${startDate.getFullYear()}-${this.pad(startDate.getMonth() + 1)}-${this.pad(startDate.getDate())}`; + const endDateStr = `${endDate.getFullYear()}-${this.pad(endDate.getMonth() + 1)}-${this.pad(endDate.getDate())}`; + url = url + `&startDate=${startDateStr}&endDate=${endDateStr}`; + } + return this.httpClient.get(url, { headers: this.headers }).catch((error: any) => { + return Observable.throw(error); + }); + } + + private pad(number) { + if (number < 10) { + return '0' + number; + } + return number; + } + +} diff --git a/src/app/groups/history-of-activity-in-group/history-of-activity-in-group.component.css b/src/app/groups/history-of-activity-in-group/history-of-activity-in-group.component.css new file mode 100644 index 0000000..ae160bb --- /dev/null +++ b/src/app/groups/history-of-activity-in-group/history-of-activity-in-group.component.css @@ -0,0 +1,20 @@ +.wrapper { + width: 100%; + padding: 30px; + min-height: 100%; + margin-bottom: 40px; +} + +.content { + background-color: #181616; + padding: 30px; + margin-top: 2rem; + margin-bottom: 2rem; + text-align: center; + margin-left: auto; + margin-right: auto; +} + +.buttons-container > div { + margin: 10px; +} diff --git a/src/app/groups/history-of-activity-in-group/history-of-activity-in-group.component.html b/src/app/groups/history-of-activity-in-group/history-of-activity-in-group.component.html new file mode 100644 index 0000000..740482c --- /dev/null +++ b/src/app/groups/history-of-activity-in-group/history-of-activity-in-group.component.html @@ -0,0 +1,125 @@ +
+
+
+ +
+
+

Twoja historia aktywności

+

+
+
+

Filtrowanie i sortowanie:

+
+
+
+ +
+
+
+
+

Zakres czasu

+
+ Pokaż wszystkie aktywności +

+ +


+

Pokaż aktywności z wybranego przedziału

+ + + + + + + + + + +
+ +
+
+
+

+
+
+
+

Brak historii do wyświetlenia.

+

+ {{ activity.date }} - + + lider grupy {{ activity.from }} + + zatwierdził dodany przez Ciebie + test + materiał + zestaw fiszek + {{ activity.resourceName }}. + Nie przyznał Ci za to punktów. + Przyznał Ci za to + {{ activity.points }} pkt. + + + + odrzucił dodany przez Ciebie + test + materiał + zestaw fiszek + {{ activity.resourceName }}. + + + Otrzymałeś komentarz: +
+ {{ activity.comment }} +
+
+ + + zatwierdziłeś + test + materiał + zestaw fiszek + {{ activity.resourceName }} użytkownika {{ activity.to }}. + Nie przyznałeś mu za to punktów. + Przyznałeś mu za to + {{ activity.points }} pkt. + + + + odrzuciłeś + test + materiał + zestaw fiszek + {{ activity.resourceName }} użytkownika {{ activity.to }}. + + + Dodałeś komentarz: +
+ {{ activity.comment }} +
+
+ + otrzymałeś + {{ activity.points }} pkt za rozwiązany test + {{ activity.resourceName }}. + + + dodałeś do grupy + test + materiał + zestaw fiszek + {{ activity.resourceName }}. + Obecnie czeka na przegląd przez lidera grupy. + + + +

+
+
+
+
+
diff --git a/src/app/groups/history-of-activity-in-group/history-of-activity-in-group.component.spec.ts b/src/app/groups/history-of-activity-in-group/history-of-activity-in-group.component.spec.ts new file mode 100644 index 0000000..3e6a96a --- /dev/null +++ b/src/app/groups/history-of-activity-in-group/history-of-activity-in-group.component.spec.ts @@ -0,0 +1,59 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { HistoryOfActivityInGroupComponent } from './history-of-activity-in-group.component'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { RouterTestingModule } from '@angular/router/testing'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { BrowserAnimationsModule, NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { HttpClientModule } from '@angular/common/http'; +import { + MatSnackBarModule, MatAutocompleteModule, + MatFormFieldModule, MatInputModule, MatDatepicker, MatDatepickerModule, MatNativeDateModule +} from '@angular/material'; +import { GroupsService } from '../groups.service'; +import { AuthenticationService } from '../../authentication.service'; +import { AuthGuard } from '../../auth-guard.service'; +import { APP_BASE_HREF } from '@angular/common'; +import { GroupServiceMockService } from '../group-service-mock.service'; + +describe('HistoryOfActivityInGroupComponent', () => { + let component: HistoryOfActivityInGroupComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [HistoryOfActivityInGroupComponent], + schemas: [NO_ERRORS_SCHEMA], + providers: [AuthenticationService, + AuthGuard, + { provide: GroupsService, useClass: GroupServiceMockService }, + { provide: APP_BASE_HREF, useValue: 'groups/history/0' } + ], + imports: [RouterTestingModule, FormsModule, HttpClientModule, + MatSnackBarModule, MatFormFieldModule, MatInputModule, MatDatepickerModule, + NoopAnimationsModule, ReactiveFormsModule, MatNativeDateModule] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(HistoryOfActivityInGroupComponent); + component = fixture.componentInstance; + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should display user activity', async(() => { + fixture.autoDetectChanges(); + spyOn(component, 'getHistory').and.callThrough(); + fixture.debugElement.nativeElement.querySelectorAll('.btn-study-cave')[1].click(); + expect(component.getHistory).toHaveBeenCalledWith(true); + })); + + it('should display mock data', async(() => { + fixture.autoDetectChanges(); + expect(fixture.debugElement.nativeElement.querySelector('.col-lg-8 b').textContent).toEqual('01/02/2019'); + })); +}); diff --git a/src/app/groups/history-of-activity-in-group/history-of-activity-in-group.component.ts b/src/app/groups/history-of-activity-in-group/history-of-activity-in-group.component.ts new file mode 100644 index 0000000..5b9cc4d --- /dev/null +++ b/src/app/groups/history-of-activity-in-group/history-of-activity-in-group.component.ts @@ -0,0 +1,53 @@ +import { Component, OnInit } from '@angular/core'; +import { FormControl } from '@angular/forms'; +import { ActivatedRoute } from '@angular/router'; +import { GroupsService } from '../groups.service'; +import { ActivityHistory } from '../group'; +import { MatSnackBar } from '@angular/material'; + +@Component({ + selector: 'app-history-of-activity-in-group', + templateUrl: './history-of-activity-in-group.component.html', + styleUrls: ['./history-of-activity-in-group.component.css'] +}) +export class HistoryOfActivityInGroupComponent implements OnInit { + + public sort = 'DESC'; + public dateStart = new FormControl(new Date()); + public dateEnd = new FormControl(new Date()); + public maxDate = new Date(); + public user = null; + + public id = 0; + + public activityHistory: ActivityHistory[] = []; + + constructor(private route: ActivatedRoute, + private groupsService: GroupsService, + public snackBar: MatSnackBar) {} + + ngOnInit() { + this.id = this.route.snapshot.params.id; + this.user = JSON.parse(localStorage.getItem('currentUser'))['username']; + this.getHistory(true); + } + + getHistory(all = false) { + let dateStart = this.dateStart.value; + let dateEnd = this.dateEnd.value; + if (all) { + dateStart = null; + dateEnd = null; + } + this.groupsService.getActivityHistory(this.id, this.sort, dateStart, dateEnd).subscribe( + success => { + this.activityHistory = success; + }, + error => { + this.snackBar.open('Coś poszło nie tak. Spróbuj ponownie później.', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } + ); + } + +} diff --git a/src/app/groups/join-to-group/join-to-group.component.css b/src/app/groups/join-to-group/join-to-group.component.css new file mode 100644 index 0000000..2b80839 --- /dev/null +++ b/src/app/groups/join-to-group/join-to-group.component.css @@ -0,0 +1,25 @@ +.wrapper{ + width: 100%; + padding: 30px; + min-height: 100%; + margin-bottom: 40px; +} + +.content{ + background-color: #181616; + padding: 30px; + margin-top: 2rem; + margin-bottom: 2rem; + text-align: center; + margin-left: auto; + margin-right: auto; +} + + +.join-to-group-form-item{ + margin-bottom: 10px; +} + +.join-to-group-form{ + margin-top: 10px; +} \ No newline at end of file diff --git a/src/app/groups/join-to-group/join-to-group.component.html b/src/app/groups/join-to-group/join-to-group.component.html new file mode 100644 index 0000000..ab29ffc --- /dev/null +++ b/src/app/groups/join-to-group/join-to-group.component.html @@ -0,0 +1,12 @@ +
+
+

Dołącz do grupy!

+

Wpisz kod dostepu, aby dołączyć do grupy.

+
{{errorMessage}}
+
Dołączyłeś do grupy, przekierowanie do niej nastąpi za 3 sekundy.
+
+ + +
+
+
\ No newline at end of file diff --git a/src/app/groups/join-to-group/join-to-group.component.spec.ts b/src/app/groups/join-to-group/join-to-group.component.spec.ts new file mode 100644 index 0000000..f5da072 --- /dev/null +++ b/src/app/groups/join-to-group/join-to-group.component.spec.ts @@ -0,0 +1,43 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { JoinToGroupComponent } from './join-to-group.component'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { RouterTestingModule } from '@angular/router/testing'; +import { HttpClientModule } from '@angular/common/http'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { GroupsService } from '../groups.service'; +import { AuthenticationService } from '../../authentication.service'; +import { FormsModule } from '@angular/forms'; + +describe('JoinToGroupComponent', () => { + let component: JoinToGroupComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ JoinToGroupComponent ], + schemas: [NO_ERRORS_SCHEMA], + imports: [ RouterTestingModule, HttpClientModule, MatSnackBarModule, FormsModule], + providers: [GroupsService, AuthenticationService] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(JoinToGroupComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should call join to group', async(() => { + fixture.autoDetectChanges(); + spyOn(component, 'joinToGroup').and.callThrough(); + fixture.debugElement.nativeElement.querySelector('input').value = 'XYZ'; + fixture.debugElement.nativeElement.querySelectorAll('.btn-study-cave')[0].click(); + expect(component.joinToGroup).toHaveBeenCalled(); + })); +}); diff --git a/src/app/groups/join-to-group/join-to-group.component.ts b/src/app/groups/join-to-group/join-to-group.component.ts new file mode 100644 index 0000000..388e12f --- /dev/null +++ b/src/app/groups/join-to-group/join-to-group.component.ts @@ -0,0 +1,58 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { GroupsService } from '../groups.service'; +import { Router } from '@angular/router'; +import { Subscription } from 'rxjs/Subscription'; +import { JoinToGroupForm } from './join-to-group'; + +@Component({ + selector: 'app-join-to-group', + templateUrl: './join-to-group.component.html', + styleUrls: ['./join-to-group.component.css'] +}) +export class JoinToGroupComponent implements OnInit, OnDestroy { + + public isCodeWrong = false; + public reditectToGroup = false; + public errorMessage = ''; + private joinSubscription: Subscription; + + constructor(private groupsService: GroupsService, private router: Router) { } + + public joinToGroup(formValues: JoinToGroupForm): void { + this.joinSubscription = this.groupsService.joinToGroup(formValues).subscribe( + (data) => { + if (data.status === 200) { + this.isCodeWrong = false; + this.reditectToGroup = true; + if (data.body) { + const groupID = JSON.parse(data.body).id; + this.isCodeWrong = false; + this.reditectToGroup = true; + setTimeout(() => this.router.navigate(['groups', groupID]), 3000); + } + } + }, + (error) => { + if (error.status === 409) { + this.errorMessage = 'Już jesteś w tej grupie.'; + this.isCodeWrong = true; + this.reditectToGroup = false; + } else { + this.errorMessage = 'Nieprawidłowy kod.'; + this.isCodeWrong = true; + this.reditectToGroup = false; + } + } + ); + } + + public ngOnInit(): void { + } + + public ngOnDestroy(): void { + if (this.joinSubscription) { + this.joinSubscription.unsubscribe(); + } + } + +} diff --git a/src/app/groups/join-to-group/join-to-group.ts b/src/app/groups/join-to-group/join-to-group.ts new file mode 100644 index 0000000..7b3d103 --- /dev/null +++ b/src/app/groups/join-to-group/join-to-group.ts @@ -0,0 +1,4 @@ +export interface JoinToGroupForm { + name: string; + code: string; +} diff --git a/src/app/groups/manage-group/manage-group.component.css b/src/app/groups/manage-group/manage-group.component.css new file mode 100644 index 0000000..9b353e3 --- /dev/null +++ b/src/app/groups/manage-group/manage-group.component.css @@ -0,0 +1,24 @@ +a.wrapper{ + width: 100%; + padding: 30px; + min-height: 100%; + margin-bottom: 40px; +} + +.content{ + background-color: #181616; + padding: 30px; + margin-top: 2rem; + margin-bottom: 2rem; + text-align: center; + margin-left: auto; + margin-right: auto; +} + +ag-grid-angular{ + margin: 10px; +} + +.buttons-container > div{ + margin: 10px; +} diff --git a/src/app/groups/manage-group/manage-group.component.html b/src/app/groups/manage-group/manage-group.component.html new file mode 100644 index 0000000..5cd48e4 --- /dev/null +++ b/src/app/groups/manage-group/manage-group.component.html @@ -0,0 +1,29 @@ +
+
+

Nazwa: {{group?.name}}

+

Klucz: {{group?.groupKey}}

+
+
+ + + + +
+
+ +

Lista użytkowników

+ + +
+
+ + + Czy chcesz usunąć grupę? + + + + + \ No newline at end of file diff --git a/src/app/groups/manage-group/manage-group.component.spec.ts b/src/app/groups/manage-group/manage-group.component.spec.ts new file mode 100644 index 0000000..4f40e86 --- /dev/null +++ b/src/app/groups/manage-group/manage-group.component.spec.ts @@ -0,0 +1,89 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ManageGroupComponent } from './manage-group.component'; +import { NO_ERRORS_SCHEMA, ChangeDetectionStrategy } from '@angular/core'; +import { RouterTestingModule } from '@angular/router/testing'; +import { HttpClientModule } from '@angular/common/http'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { GroupsService } from '../groups.service'; +import { AuthenticationService } from '../../authentication.service'; +import { ConfirmationService } from 'primeng/api'; +import { AuthGuard } from '../../auth-guard.service'; +import { GroupServiceMockService } from '../group-service-mock.service'; +import { APP_BASE_HREF } from '@angular/common'; +import { GroupDetailsComponent } from '../group-details/group-details.component'; +import { Routes, RouterModule, Router } from '@angular/router'; +import { WaitingResourcesComponent } from '../waiting-resources/waiting-resources.component'; + +describe('ManageGroupComponent', () => { + let component: ManageGroupComponent; + let fixture: ComponentFixture; + const routes: Routes = [{ path: 'groups/:id', component: GroupDetailsComponent, canActivate: [AuthGuard] }, + { path: 'groups/manage/:id', component: ManageGroupComponent, canActivate: [AuthGuard] }, + { path: 'groups/waiting-resources/:id', component: WaitingResourcesComponent, canActivate: [AuthGuard] }, + ]; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ManageGroupComponent, GroupDetailsComponent, WaitingResourcesComponent], + schemas: [NO_ERRORS_SCHEMA], + imports: [RouterTestingModule, HttpClientModule, MatSnackBarModule, RouterModule.forRoot(routes)], + providers: [GroupsService, AuthenticationService, ConfirmationService, AuthGuard, + { provide: GroupsService, useClass: GroupServiceMockService }, + { provide: APP_BASE_HREF, useValue: 'groups/manage/0' }] + }) + .overrideComponent(ManageGroupComponent, { + set: { changeDetection: ChangeDetectionStrategy.Default } + }); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ManageGroupComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should navigate to group details', async(() => { + fixture.autoDetectChanges(); + const navigateSpy = spyOn(component.router, 'navigate').and.callThrough(); + component.id = 0; + fixture.debugElement.nativeElement.querySelectorAll('.buttons-container .btn-study-cave')[0].click(); + expect(navigateSpy).toHaveBeenCalledWith(['/groups/', 0]); + })); + + it('should generate new key', async(() => { + fixture.autoDetectChanges(); + spyOn(component, 'newKeyGenerate').and.callThrough(); + component.id = 0; + let promise; + let resolve; + promise = new Promise(r => resolve = r); + fixture.debugElement.nativeElement.querySelectorAll('.buttons-container .btn-study-cave')[1].click(); + fixture.debugElement.nativeElement.querySelectorAll('.buttons-container .btn-study-cave')[1].dispatchEvent(new Event('click')); + expect(component.newKeyGenerate).toHaveBeenCalled(); + promise.then(() => { + fixture.detectChanges(); + expect(fixture.debugElement.nativeElement.querySelector('.content p').textContent).toEqual('Klucz: XYZ'); + }); + })); + + it('should navigate to waiting resorces', async(() => { + fixture.autoDetectChanges(); + const navigateSpy = spyOn(component.router, 'navigate').and.callThrough(); + component.id = 0; + fixture.debugElement.nativeElement.querySelectorAll('.buttons-container .btn-study-cave')[2].click(); + expect(navigateSpy).toHaveBeenCalledWith(['/groups/waiting-resources/', 0]); + })); + + it('should show dialog with group removal', async(() => { + fixture.autoDetectChanges(); + spyOn(component, 'showDialog').and.callThrough(); + component.id = 0; + fixture.debugElement.nativeElement.querySelectorAll('.buttons-container .btn-study-cave')[3].click(); + expect(component.showDialog).toHaveBeenCalled(); + })); +}); diff --git a/src/app/groups/manage-group/manage-group.component.ts b/src/app/groups/manage-group/manage-group.component.ts new file mode 100644 index 0000000..64ba718 --- /dev/null +++ b/src/app/groups/manage-group/manage-group.component.ts @@ -0,0 +1,195 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { GroupsService } from '../groups.service'; +import { Subscription } from 'rxjs/Subscription'; +import { Group, UsersConfig } from '../group'; +import { GridOptions, RowDoubleClickedEvent } from 'ag-grid-community/main'; +import { ConfirmationService } from 'primeng/api'; +import localeText from './../../../assets/localeText'; +import { ApiInterceptor } from '../../http-interceptors/api-interceptor'; +import { MatSnackBar } from '@angular/material'; + +@Component({ + selector: 'app-manage-group', + templateUrl: './manage-group.component.html', + styleUrls: ['./manage-group.component.css'] +}) + +export class ManageGroupComponent implements OnInit, OnDestroy { + id: number; + currentUser: string; + groupDetailsSubscription: Subscription; + groupUserListSubscription: Subscription; + userDeleteSubscription: Subscription; + groupDeleteSubscription: Subscription; + newKeyGenerateSubscription: Subscription; + display = false; + localeText = localeText; + + private gridApi; + public gridOptions: GridOptions; + + public columnDefs = [ + { headerName: 'Użytkownik', field: 'username', headerTooltip: 'Login użytkownika' }, + { + headerName: '', + suppressMenu: true, + suppressSorting: true, + cellRenderer: this.customCellRendererFunc + } + ]; + + public group: Group; + + + // tslint:disable-next-line:max-line-length + constructor(private route: ActivatedRoute, private groupsService: GroupsService, + private confirmationService: ConfirmationService, public router: Router, public snackBar: MatSnackBar) { + this.localeText.noRowsToShow = 'Brak użytkowników do wyświetlenia'; + } + + customCellRendererFunc(params) { + return ``; + } + + + ngOnInit() { + this.id = this.route.snapshot.params.id; + this.currentUser = JSON.parse(localStorage.getItem('currentUser')); + this.groupDetailsSubscription = this.groupsService.getGroupDetails(this.id).subscribe(data => { this.group = data; }); + + this.gridOptions = { + rowHeight: 50, + headerHeight: 25, + getRowStyle: function (params) { + return { + cursor: 'pointer' + }; + }, + }; + } + + + public onRowClicked(e) { + if (e.event.target !== undefined) { + const data = e.data; + const actionType = e.event.target.getAttribute('data-action-type'); + + switch (actionType) { + case 'remove': + return this.onActionRemoveClick(e); + } + } + } + redirectTo(uri) { + this.router.navigateByUrl('/', {skipLocationChange: true}).then(() => + this.router.navigate([uri])); + } + + public onActionRemoveClick(e) { + // this.userDeleteSubscription = this.groupsService.deleteUser(this.id, e.data.id).subscribe(); + // setTimeout(() => { + // this.redirectTo('/groups/manage/' + this.id); + // }, 200); + } + + + onGridReady(params) { + this.gridApi = params.api; + this.gridApi.sizeColumnsToFit(); + } + + onGridColumnsChanged(params) { + params.api.sizeColumnsToFit(); + } + + onGridSizeChanged(params) { + if (params.clientWidth < 800) { + this.columnDefs = [ + { headerName: 'Użytkownik', field: 'username', headerTooltip: 'Login użytkownika' }, + { + headerName: '', + suppressMenu: true, + suppressSorting: true, + cellRenderer: this.customCellRendererFunc + } + ]; + } else { + this.columnDefs = [ + { headerName: 'Użytkownik', field: 'username', headerTooltip: 'Login użytkownika' }, + { + headerName: '', + suppressMenu: true, + suppressSorting: true, + cellRenderer: this.customCellRendererFunc + } + ]; + } + + params.api.sizeColumnsToFit(); + } + onGidColumnsChanged(params) { + params.api.sizeColumnsToFit(); + } + + + + deleteUser(userId) { + this.userDeleteSubscription = this.groupsService.deleteUser(this.id, userId).subscribe(); + this.gridApi.refreshCells(); + this.snackBar.open('Usunięto użytkownika!', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-success'] }); + } + deleteGroup() { + this.groupDeleteSubscription = this.groupsService.deleteGroup(this.id).subscribe(); + this.snackBar.open('Usunięto grupę!', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-success'] }); + this.display = false; + setTimeout(() => { + this.router.navigate(['/my-groups']); + + }, 100); + } + + newKeyGenerate() { + this.newKeyGenerateSubscription = this.groupsService.newKeyGenerate(this.id).subscribe( + success => { + this.snackBar.open('Kod dostępu został zmieniony!', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-success'] }); + this.group.groupKey = success; + }, + error => { + this.snackBar.open('Coś poszło nie tak. Spróbuj ponownie później.', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } + ); + } + showDialog() { + this.display = true; + } + + goToGroup() { + this.router.navigate(['/groups/', this.id]); + } + + showWaitingResources() { + this.router.navigate(['/groups/waiting-resources/', this.id]); + } + + ngOnDestroy() { + if (this.groupDetailsSubscription) { + this.groupDetailsSubscription.unsubscribe(); + } + if (this.userDeleteSubscription) { + this.userDeleteSubscription.unsubscribe(); + } + if (this.groupDeleteSubscription) { + this.groupDeleteSubscription.unsubscribe(); + } + if (this.newKeyGenerateSubscription) { + this.newKeyGenerateSubscription.unsubscribe(); + } + } + +} diff --git a/src/app/groups/my-groups/localeText.ts b/src/app/groups/my-groups/localeText.ts new file mode 100644 index 0000000..7f74d4a --- /dev/null +++ b/src/app/groups/my-groups/localeText.ts @@ -0,0 +1,90 @@ +const localeText = { + + // for filter panel + page: 'Strona', + more: 'Więcej', + to: 'do', + of: 'z', + next: '>', + last: '>>', + first: '<<', + previous: '<', + loadingOoo: 'Ładowanie...', + + // for set filter + selectAll: 'Wybierz wszystkie', + searchOoo: 'Szukaj', + blanks: 'Luki', + + // for number filter and text filter + filterOoo: 'Filtruj...', + applyFilter: 'Zastosuj filtr', + + // for number filter + equals: 'Równy', + notEqual: 'Nie równy', + lessThan: 'Mniejszy niż', + greaterThan: 'Większy niż', + + // for text filter + contains: 'Zawiera', + notContains: 'Nie zawiera', + startsWith: 'Zaczyna się od', + endsWith: 'Kończy się na', + + // the header of the default group column + group: 'Grupa', + + // tool panel + columns: 'Kolumny', + rowGroupColumns: 'Grupa wiersza kolumn', + rowGroupColumnsEmptyMessage: 'Przeciągnij kolumny do grupy', + valueColumns: 'Wartości kolumn', + pivotMode: 'Pivot-Mode', + groups: 'Grupy', + values: 'Wartości', + pivots: 'Pivots', + valueColumnsEmptyMessage: 'Przeciągnij kolumny do agregacji', + pivotColumnsEmptyMessage: 'Przeciągnij kolumny na oś', + toolPanelButton: 'Narzędzia', + + // other + noRowsToShow: 'Brak grup do wyświetlenia', + + // enterprise menu + pinColumn: 'Przypiąta kolumna', + valueAggregation: 'Sumuj', + autosizeThiscolumn: 'Autosize this column', + autosizeAllColumns: 'Autosize All Columns', + groupBy: 'Grupuj po', + ungroupBy: 'Nie grupuj po', + resetColumns: 'Resetuj kolumny', + expandAll: 'Rozszerz wszystko', + collapseAll: 'Zwiń wszystko', + toolPanel: 'Narzędzia', + export: 'Export', + csvExport: 'CSV Export', + excelExport: 'Excel Export', + + // enterprise menu pinning + pinLeft: 'Przypnij <<', + pinRight: 'Przypnij >>', + noPin: 'Nie przypinaj <>', + + // enterprise menu aggregation and status panel + sum: 'Suma', + min: 'Minimum', + max: 'Maksimum', + none: 'Nic', + count: 'Ilość', + average: 'Średnia', + + // standard menu + copy: 'Kopiuj', + copyWithHeaders: 'Kopiuj z nagłówkami', + ctrlC: 'ctrl + C', + paste: 'wklej', + ctrlV: 'ctrl + C' +}; + +export default localeText; diff --git a/src/app/groups/my-groups/my-groups.component.css b/src/app/groups/my-groups/my-groups.component.css new file mode 100644 index 0000000..722b21b --- /dev/null +++ b/src/app/groups/my-groups/my-groups.component.css @@ -0,0 +1,24 @@ +.wrapper{ + width: 100%; + padding: 30px; + min-height: 100%; + margin-bottom: 40px; +} + +.content{ + background-color: #181616; + padding: 30px; + margin-top: 2rem; + margin-bottom: 2rem; + text-align: center; + margin-left: auto; + margin-right: auto; +} + +ag-grid-angular{ + margin: 10px; +} + +.buttons-container > div{ + margin: 10px; +} diff --git a/src/app/groups/my-groups/my-groups.component.html b/src/app/groups/my-groups/my-groups.component.html new file mode 100644 index 0000000..79a8813 --- /dev/null +++ b/src/app/groups/my-groups/my-groups.component.html @@ -0,0 +1,20 @@ +
+
+

GRUPY

+
+
+
+ +
+
+ +
+
+
+ + +
+
diff --git a/src/app/groups/my-groups/my-groups.component.spec.ts b/src/app/groups/my-groups/my-groups.component.spec.ts new file mode 100644 index 0000000..f8978a2 --- /dev/null +++ b/src/app/groups/my-groups/my-groups.component.spec.ts @@ -0,0 +1,61 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MyGroupsComponent } from './my-groups.component'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { RouterTestingModule } from '@angular/router/testing'; +import { HttpClientModule } from '@angular/common/http'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { GroupsService } from '../groups.service'; +import { AuthenticationService } from '../../authentication.service'; +import { AuthGuard } from '../../auth-guard.service'; +import { Routes, RouterModule } from '@angular/router'; +import { GroupCreatorComponent } from '../group-creator/group-creator.component'; +import { JoinToGroupComponent } from '../join-to-group/join-to-group.component'; +import { GroupServiceMockService } from '../group-service-mock.service'; +import { APP_BASE_HREF } from '@angular/common'; +import { FormsModule } from '@angular/forms'; + +describe('MyGroupsComponent', () => { + let component: MyGroupsComponent; + let fixture: ComponentFixture; + const routes: Routes = [{ path: 'my-groups', component: MyGroupsComponent, canActivate: [AuthGuard] }, + { path: 'create-group', component: GroupCreatorComponent, canActivate: [AuthGuard] }, + { path: 'join-to-group', component: JoinToGroupComponent, canActivate: [AuthGuard] }, + ]; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [MyGroupsComponent, JoinToGroupComponent, GroupCreatorComponent], + schemas: [NO_ERRORS_SCHEMA], + imports: [RouterTestingModule, HttpClientModule, MatSnackBarModule, RouterModule.forRoot(routes), FormsModule], + providers: [GroupsService, AuthenticationService, AuthGuard, + { provide: GroupsService, useClass: GroupServiceMockService }, + { provide: APP_BASE_HREF, useValue: 'my-groups' }] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(MyGroupsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should go to create group component', async(() => { + fixture.autoDetectChanges(); + const navigateSpy = spyOn(component.router, 'navigate').and.callThrough(); + fixture.debugElement.nativeElement.querySelectorAll('.btn-study-cave')[0].click(); + expect(navigateSpy).toHaveBeenCalledWith(['create-group']); + })); + + it('should go to join to group component', async(() => { + fixture.autoDetectChanges(); + const navigateSpy = spyOn(component.router, 'navigate').and.callThrough(); + fixture.debugElement.nativeElement.querySelectorAll('.btn-study-cave')[1].click(); + expect(navigateSpy).toHaveBeenCalledWith(['join-to-group']); + })); +}); diff --git a/src/app/groups/my-groups/my-groups.component.ts b/src/app/groups/my-groups/my-groups.component.ts new file mode 100644 index 0000000..39c8f53 --- /dev/null +++ b/src/app/groups/my-groups/my-groups.component.ts @@ -0,0 +1,149 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { GridOptions, RowDoubleClickedEvent } from 'ag-grid-community/main'; +import { ISubscription } from 'rxjs/Subscription'; +import { Router } from '@angular/router'; + +import localeText from './localeText'; + +import { Group } from '../group'; +import { GroupsService } from '../groups.service'; + +@Component({ + selector: 'app-my-groups', + templateUrl: './my-groups.component.html', + styleUrls: ['./my-groups.component.css'] +}) +export class MyGroupsComponent implements OnInit, OnDestroy { + + public groups: Group[] = []; + + private gridApi; + public gridOptions: GridOptions; + public localeText = localeText; + + public columnDefs = [ + { headerName: 'Nazwa', field: 'name', headerTooltip: 'Nazwa' }, + { headerName: 'Rola', field: 'role', headerTooltip: 'Rola', hide: false }, + { + headerName: '', + suppressMenu: true, + suppressSorting: true, + cellRenderer: this.customCellRendererFunc + } + ]; + + private getGroupsSubscription: ISubscription; + + constructor(private groupsService: GroupsService, public router: Router) { } + + ngOnInit() { + this.gridOptions = { + rowHeight: 50, + headerHeight: 25, + getRowStyle: function (params) { + return { + cursor: 'pointer' + }; + }, + }; + this.getGroupsSubscription = this.groupsService.getGroups().subscribe( + success => { + for (let i = 0; i < success.length; i++) { + if (success[i]['role'] === 'OWNER') { + success[i]['role'] = 'Lider'; + } else { + success[i]['role'] = 'Członek'; + } + } + this.groups = success; + }, + error => { + console.log('Something went wrong :( \nError: ', error); + } + ); + } + + toGroupMaker() { + this.router.navigate(['create-group']); + } + + goToGroup(event: RowDoubleClickedEvent) { + this.router.navigate(['groups', event.data.id]); + } + + public goToJoinToGroup(): void { + this.router.navigate(['join-to-group']); + } + + onActionManageClick(e) { + this.router.navigate(['groups/manage', e.data.id]); + } + + onRowClicked(e) { + if (e.event.target !== undefined) { + const actionType = e.event.target.getAttribute('data-action-type'); + + switch (actionType) { + case 'edit': + return this.onActionManageClick(e); + default: + return this.goToGroup(e); + } + } + } + + customCellRendererFunc(params) { + if (params.data['role'] === 'Lider') { + return ` + + `; + } else { + return ''; + } + } + + onGridReady(params) { + this.gridApi = params.api; + this.gridApi.sizeColumnsToFit(); + } + + onGridColumnsChanged(params) { + params.api.sizeColumnsToFit(); + } + + onGridSizeChanged(params) { + if (params.clientWidth < 800) { + this.columnDefs = [ + { headerName: 'Nazwa', field: 'name', headerTooltip: 'Nazwa' }, + { headerName: 'Rola', field: 'role', headerTooltip: 'Rola', hide: true }, + { + headerName: '', + suppressMenu: true, + suppressSorting: true, + cellRenderer: this.customCellRendererFunc + } + ]; + } else { + this.columnDefs = [ + { headerName: 'Nazwa', field: 'name', headerTooltip: 'Nazwa' }, + { headerName: 'Rola', field: 'role', headerTooltip: 'Rola', hide: false }, + { + headerName: '', + suppressMenu: true, + suppressSorting: true, + cellRenderer: this.customCellRendererFunc + } + ]; + } + + params.api.sizeColumnsToFit(); + } + + ngOnDestroy() { + if (this.getGroupsSubscription) { + this.getGroupsSubscription.unsubscribe(); + } + } + +} diff --git a/src/app/groups/ranking/ranking.component.css b/src/app/groups/ranking/ranking.component.css new file mode 100644 index 0000000..8847472 --- /dev/null +++ b/src/app/groups/ranking/ranking.component.css @@ -0,0 +1,27 @@ +.wrapper { + width: 100%; + padding: 30px; + min-height: 100%; + margin-bottom: 40px; +} + +.content { + background-color: #181616; + padding: 30px; + margin-top: 2rem; + margin-bottom: 2rem; + text-align: center; + margin-left: auto; + margin-right: auto; +} + +.btn-group { + margin: 10px; +} + +.chart-container { + margin: 10px; + width: 100%; + height: 500px; + position: relative; +} \ No newline at end of file diff --git a/src/app/groups/ranking/ranking.component.html b/src/app/groups/ranking/ranking.component.html new file mode 100644 index 0000000..76b7ff6 --- /dev/null +++ b/src/app/groups/ranking/ranking.component.html @@ -0,0 +1,28 @@ +
+
+
+ +
+
+

{{group?.name}}

+

Ranking {{setRankingTypeTitle()}}

+
+ + +
+ + + +
+ +
+
+
\ No newline at end of file diff --git a/src/app/groups/ranking/ranking.component.spec.ts b/src/app/groups/ranking/ranking.component.spec.ts new file mode 100644 index 0000000..6e2d727 --- /dev/null +++ b/src/app/groups/ranking/ranking.component.spec.ts @@ -0,0 +1,64 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { RankingComponent } from './ranking.component'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { RouterTestingModule } from '@angular/router/testing'; +import { FormsModule } from '@angular/forms'; +import { HttpClientModule } from '@angular/common/http'; +import { MatSnackBarModule, MatFormFieldModule } from '@angular/material'; +import { GroupsService } from '../groups.service'; +import { AuthenticationService } from '../../authentication.service'; +import { APP_BASE_HREF } from '@angular/common'; +import { GroupDetailsComponent } from '../group-details/group-details.component'; +import { Routes, RouterModule, Router } from '@angular/router'; +import { WaitingResourcesComponent } from '../waiting-resources/waiting-resources.component'; +import { AuthGuard } from '../../auth-guard.service'; +import { ManageGroupComponent } from '../manage-group/manage-group.component'; +import { GroupServiceMockService } from '../group-service-mock.service'; + +describe('RankingComponent', () => { + let component: RankingComponent; + let fixture: ComponentFixture; + const routes: Routes = [{ path: 'groups/:id', component: GroupDetailsComponent, canActivate: [AuthGuard] }, + { path: 'groups/manage/:id', component: ManageGroupComponent, canActivate: [AuthGuard] }, + { path: 'groups/waiting-resources/:id', component: WaitingResourcesComponent, canActivate: [AuthGuard] }, + { path: 'groups/ranking/:id', component: RankingComponent, canActivate: [AuthGuard] }, + ]; + + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [RankingComponent, ManageGroupComponent, GroupDetailsComponent, WaitingResourcesComponent], + schemas: [NO_ERRORS_SCHEMA], + imports: [RouterTestingModule, FormsModule, HttpClientModule, MatSnackBarModule, MatFormFieldModule, RouterModule.forRoot(routes)], + providers: [GroupsService, AuthenticationService, AuthGuard, + { provide: GroupsService, useClass: GroupServiceMockService }, + { provide: APP_BASE_HREF, useValue: 'groups/ranking/0' }] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(RankingComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should show global ranking', async(() => { + fixture.autoDetectChanges(); + spyOn(component, 'showGlobalRanking').and.callThrough(); + fixture.debugElement.nativeElement.querySelector('#option1').click(); + expect(component.showGlobalRanking).toHaveBeenCalled(); + })); + + it('should show tests ranking', async(() => { + fixture.autoDetectChanges(); + spyOn(component, 'showOnlyTestsRanking').and.callThrough(); + fixture.debugElement.nativeElement.querySelector('#option2').click(); + expect(component.showOnlyTestsRanking).toHaveBeenCalled(); + })); +}); diff --git a/src/app/groups/ranking/ranking.component.ts b/src/app/groups/ranking/ranking.component.ts new file mode 100644 index 0000000..dbb28e4 --- /dev/null +++ b/src/app/groups/ranking/ranking.component.ts @@ -0,0 +1,180 @@ +import { Component, OnInit, OnDestroy, ViewChild, ElementRef } from '@angular/core'; +import { Group } from '../group'; +import { ActivatedRoute, Router, Data } from '@angular/router'; +import { GroupsService } from '../groups.service'; +import { Subscription } from 'rxjs/Subscription'; +import { RankingType } from './ranking'; +import { GridOptions } from 'ag-grid-community/main'; +import localeText from './../../../assets/localeText'; +import * as picasso from 'picasso.js'; +picasso.default('canvas'); + +@Component({ + selector: 'app-ranking', + templateUrl: './ranking.component.html', + styleUrls: ['./ranking.component.css'] +}) +export class RankingComponent implements OnInit, OnDestroy { + + @ViewChild('chartContainer') + public elemRef: ElementRef; + public group: Group; + public id: number; + public currentUser: any; + public typeOfRankingToDisplay: RankingType = RankingType.all; + private gridApi; + public gridOptions: GridOptions; + public groupDetailsSubscription: Subscription; + public rankingSubscription: Subscription; + public chart; + public localeText = localeText; + public columnDefs = [ + { headerName: 'Użytkownik', field: 'username', headerTooltip: 'Użytkownik' }, + { headerName: 'Punkty', field: 'points', headerTooltip: 'Punkty' }, + ]; + public data; + + + constructor(private route: ActivatedRoute, private groupService: GroupsService, private router: Router) { } + + ngOnInit(): void { + this.id = this.route.snapshot.params.id; + this.currentUser = JSON.parse(localStorage.getItem('currentUser')); + this.groupDetailsSubscription = this.groupService.getGroupDetails(this.id) + .subscribe(data2 => { + this.group = data2; + this.rankingSubscription = this.groupService.getGlobalRanking(this.group.id).subscribe(data3 => { + this.data = data3.sort((a, b) => { + if (a.points > b.points) { + return -1; + } else if (a.points < b.points) { + return 1; + } else { + return 0; + } + }); + this.showChart(); + }); + }); + } + + ngOnDestroy(): void { + if (this.groupDetailsSubscription) { + this.groupDetailsSubscription.unsubscribe(); + } + if (this.rankingSubscription) { + this.rankingSubscription.unsubscribe(); + } + } + + showChart(): void { + const data: Data = { + type: 'matrix', + data: [['username', 'points']] + }; + this.data.forEach(item => data.data.push([item.username, item.points])); + this.chart = picasso.default.chart({ + element: this.elemRef.nativeElement, + data, + settings: { + scales: { + y: { + data: { field: 'points' }, + invert: true, + include: [0] + }, + c: { + data: { field: 'points' }, + type: 'color' + }, + t: { data: { extract: { field: 'username' } }, padding: 0.3 }, + }, + components: [{ + type: 'axis', + dock: 'left', + scale: 'y' + }, { + type: 'axis', + dock: 'bottom', + scale: 't' + }, { + key: 'bars', + type: 'box', + data: { + extract: { + field: 'username', + props: { + start: 0, + end: { field: 'points' } + } + } + }, + settings: { + major: { scale: 't' }, + minor: { scale: 'y' }, + box: { + fill: function (d) { + return d.datum.value === JSON.parse(localStorage.getItem('currentUser')).username ? '#cea856' : '#ffffff'; + }, + stroke: 'transparent' + } + } + } + ] + } + }); + } + + showGlobalRanking(): void { + this.typeOfRankingToDisplay = RankingType.all; + this.rankingSubscription = this.groupService.getGlobalRanking(this.group.id).subscribe(data => { + this.data = data.sort((a, b) => { + if (a.points > b.points) { + return -1; + } else if (a.points < b.points) { + return 1; + } else { + return 0; + } + }); + this.showChart(); + }); + } + + showOnlyTestsRanking(): void { + this.typeOfRankingToDisplay = RankingType.test; + this.rankingSubscription = this.groupService.getTestsRanking(this.group.id).subscribe(data => { + this.data = data.sort((a, b) => { + if (a.points > b.points) { + return -1; + } else if (a.points < b.points) { + return 1; + } else { + return 0; + } + }); + this.showChart(); + }); + } + + onGridReady(params) { + this.gridApi = params.api; + this.gridApi.sizeColumnsToFit(); + } + + onGridColumnsChanged(params) { + params.api.sizeColumnsToFit(); + } + + onGridSizeChanged(params) { + params.api.sizeColumnsToFit(); + if (this.data) { + this.showChart(); + } + } + + setRankingTypeTitle(): string { + return this.typeOfRankingToDisplay === RankingType.test ? 'testów' : 'ogólny'; + } + +} diff --git a/src/app/groups/ranking/ranking.ts b/src/app/groups/ranking/ranking.ts new file mode 100644 index 0000000..361cbfd --- /dev/null +++ b/src/app/groups/ranking/ranking.ts @@ -0,0 +1,9 @@ +export enum RankingType { + all = 'all', + test = 'test' +} + +export interface Data { + type: string; + data: Array>; +} diff --git a/src/app/groups/resource.ts b/src/app/groups/resource.ts new file mode 100644 index 0000000..7dcf4a4 --- /dev/null +++ b/src/app/groups/resource.ts @@ -0,0 +1,20 @@ +export class Resource { + + add_date?: string; + edit_date?: string; + grade?: number; + id?: number; + owner?: string; + ownerId?: number; + permission?: string; + title?: string; + comment?: string = ''; + points?: number = 0; + + constructor() {} +} + +export enum ResourceStatus { + accepted = 'ACCEPTED', + rejected = 'REJECTED' +} diff --git a/src/app/groups/sharing-resources-in-groups/sharing-resources-in-groups.component.css b/src/app/groups/sharing-resources-in-groups/sharing-resources-in-groups.component.css new file mode 100644 index 0000000..845f9ef --- /dev/null +++ b/src/app/groups/sharing-resources-in-groups/sharing-resources-in-groups.component.css @@ -0,0 +1,25 @@ +.wrapper { + width: 100%; + padding: 30px; + min-height: 100%; + margin-bottom: 40px; +} + +.content { + background-color: #181616; + padding: 30px; + margin-top: 2rem; + margin-bottom: 2rem; + text-align: center; + margin-left: auto; + margin-right: auto; +} + +.buttons-container > div { + margin: 10px; +} + +.multiselect-wrapper{ + display: flex; + justify-content: center; +} \ No newline at end of file diff --git a/src/app/groups/sharing-resources-in-groups/sharing-resources-in-groups.component.html b/src/app/groups/sharing-resources-in-groups/sharing-resources-in-groups.component.html new file mode 100644 index 0000000..bdaff88 --- /dev/null +++ b/src/app/groups/sharing-resources-in-groups/sharing-resources-in-groups.component.html @@ -0,0 +1,33 @@ +
+
+
+ +
+
+

Dodawanie zasobów do grupy

+
+
+

Wybierz typ zasobu do dodania:

+
+ + + +
+
+

+
+
+ + +

Materiały

+

Fiszki

+

Testy

+
+
+

+ +
+
+
+
\ No newline at end of file diff --git a/src/app/groups/sharing-resources-in-groups/sharing-resources-in-groups.component.spec.ts b/src/app/groups/sharing-resources-in-groups/sharing-resources-in-groups.component.spec.ts new file mode 100644 index 0000000..5a33865 --- /dev/null +++ b/src/app/groups/sharing-resources-in-groups/sharing-resources-in-groups.component.spec.ts @@ -0,0 +1,64 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SharingResourcesInGroupsComponent } from './sharing-resources-in-groups.component'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { RouterTestingModule } from '@angular/router/testing'; +import { HttpClientModule } from '@angular/common/http'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { GroupsService } from '../groups.service'; +import { AuthenticationService } from '../../authentication.service'; +import { AuthGuard } from '../../auth-guard.service'; +import { Routes, RouterModule } from '@angular/router'; +import { GroupServiceMockService } from '../group-service-mock.service'; +import { APP_BASE_HREF } from '@angular/common'; + +describe('SharingResourcesInGroupsComponent', () => { + let component: SharingResourcesInGroupsComponent; + let fixture: ComponentFixture; + const routes: Routes = [ + { path: 'groups/add-resources/:id', component: SharingResourcesInGroupsComponent, canActivate: [AuthGuard] }, + ]; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [SharingResourcesInGroupsComponent], + schemas: [NO_ERRORS_SCHEMA], + imports: [RouterTestingModule, HttpClientModule, MatSnackBarModule, RouterModule.forRoot(routes)], + providers: [GroupsService, AuthenticationService, AuthGuard, + { provide: GroupsService, useClass: GroupServiceMockService }, + { provide: APP_BASE_HREF, useValue: 'groups/add-resources/0' }] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SharingResourcesInGroupsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should show resources', async(() => { + fixture.autoDetectChanges(); + spyOn(component, 'getMaterialsToAdd').and.callThrough(); + fixture.debugElement.nativeElement.querySelectorAll('button')[1].click(); + expect(component.getMaterialsToAdd).toHaveBeenCalled(); + })); + + it('should show flashcards', async(() => { + fixture.autoDetectChanges(); + spyOn(component, 'getFlashcardsToAdd').and.callThrough(); + fixture.debugElement.nativeElement.querySelectorAll('button')[2].click(); + expect(component.getFlashcardsToAdd).toHaveBeenCalled(); + })); + + it('should show tests', async(() => { + fixture.autoDetectChanges(); + spyOn(component, 'getTestsToAdd').and.callThrough(); + fixture.debugElement.nativeElement.querySelectorAll('button')[3].click(); + expect(component.getTestsToAdd).toHaveBeenCalled(); + })); +}); diff --git a/src/app/groups/sharing-resources-in-groups/sharing-resources-in-groups.component.ts b/src/app/groups/sharing-resources-in-groups/sharing-resources-in-groups.component.ts new file mode 100644 index 0000000..72b723d --- /dev/null +++ b/src/app/groups/sharing-resources-in-groups/sharing-resources-in-groups.component.ts @@ -0,0 +1,204 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { GroupsService } from '../groups.service'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { Resource } from '../resource'; +import { SelectItem } from 'primeng/api'; +import { ResourceType } from './sharing-resources-in-groups'; +import { Subscription } from 'rxjs/Subscription'; + +@Component({ + selector: 'app-sharing-resources-in-groups', + templateUrl: './sharing-resources-in-groups.component.html', + styleUrls: ['./sharing-resources-in-groups.component.css'] +}) +export class SharingResourcesInGroupsComponent implements OnInit, OnDestroy { + + public id = 0; + + public materialsToAdd: Resource[] = []; + public testsToAdd: Resource[] = []; + public flashcardsToAdd: Resource[] = []; + public selectedTypeOfResource: ResourceType; + public selected: string[]; + private getMaterialsToAddSub: Subscription; + private getTestsToAddSub: Subscription; + private getFlashcardsToAddSub: Subscription; + private addTestsToGroupSub: Subscription; + private addMaterialsToGroupSub: Subscription; + private addFlashcardsToGroupSub: Subscription; + + constructor(private route: ActivatedRoute, + private groupService: GroupsService, + public snackBar: MatSnackBar) { } + + ngOnInit() { + this.id = this.route.snapshot.params.id; + } + + ngOnDestroy() { + if (this.getMaterialsToAddSub) { + this.getMaterialsToAddSub.unsubscribe(); + } + if (this.getTestsToAddSub) { + this.getTestsToAddSub.unsubscribe(); + } + if (this.getFlashcardsToAddSub) { + this.getFlashcardsToAddSub.unsubscribe(); + } + if (this.addTestsToGroupSub) { + this.addTestsToGroupSub.unsubscribe(); + } + if (this.addMaterialsToGroupSub) { + this.addMaterialsToGroupSub.unsubscribe(); + } + if (this.addFlashcardsToGroupSub) { + this.addFlashcardsToGroupSub.unsubscribe(); + } + } + + getMaterialsToAdd() { + this.selectedTypeOfResource = ResourceType.materials; + this.selected = []; + this.getMaterialsToAddSub = this.groupService.getMaterialsToAdd(this.id).subscribe( + success => { + this.testsToAdd = []; + this.flashcardsToAdd = []; + this.materialsToAdd = success.map(this.addPropertiesToDisplayInMultiselectList); + if (this.materialsToAdd.length === 0) { + this.snackBar.open('Brak materiałów, które możesz dodać do grupy.', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } + }, + error => { + this.snackBar.open('Wystąpił błąd serwera. Spróbuj ponownie później.', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + console.log('Something went wrong :( \nError: ', error); + } + ); + } + + getTestsToAdd() { + this.selectedTypeOfResource = ResourceType.test; + this.selected = []; + this.getTestsToAddSub = this.groupService.getTestsToAdd(this.id).subscribe( + success => { + this.flashcardsToAdd = []; + this.materialsToAdd = []; + this.testsToAdd = success.map(this.addPropertiesToDisplayInMultiselectList); + if (this.testsToAdd.length === 0) { + this.snackBar.open('Brak testów, które możesz dodać do grupy.', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } + }, + error => { + this.snackBar.open('Wystąpił błąd serwera. Spróbuj ponownie później.', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + console.log('Something went wrong :( \nError: ', error); + } + ); + } + + getFlashcardsToAdd() { + this.selectedTypeOfResource = ResourceType.flashcards; + this.selected = []; + this.getFlashcardsToAddSub = this.groupService.getFlashcardsToAdd(this.id).subscribe( + success => { + this.materialsToAdd = []; + this.testsToAdd = []; + this.flashcardsToAdd = success.map(this.addPropertiesToDisplayInMultiselectList); + if (this.flashcardsToAdd.length === 0) { + this.snackBar.open('Brak fiszek, które możesz dodać do grupy.', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } + }, + error => { + this.snackBar.open('Wystąpił błąd serwera. Spróbuj ponownie później.', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + console.log('Something went wrong :( \nError: ', error); + } + ); + } + + addPropertiesToDisplayInMultiselectList(item) { + if (item.title) { + item.label = item.title; + } else { + item.label = item.name; + } + item.value = item.id; + return item; + } + + setResources() { + if (this.selectedTypeOfResource === ResourceType.test) { + return this.testsToAdd; + } + if (this.selectedTypeOfResource === ResourceType.flashcards) { + return this.flashcardsToAdd; + } + if (this.selectedTypeOfResource === ResourceType.materials) { + return this.materialsToAdd; + } + return {}; + } + + refreshList() { + this.selected = []; + if (this.selectedTypeOfResource === ResourceType.test) { + this.getTestsToAdd(); + } + if (this.selectedTypeOfResource === ResourceType.flashcards) { + this.getFlashcardsToAdd(); + } + if (this.selectedTypeOfResource === ResourceType.materials) { + this.getMaterialsToAdd(); + } + } + + addResources() { + this.id = this.route.snapshot.params.id; + if (this.selectedTypeOfResource === ResourceType.test) { + this.addTestsToGroupSub = this.groupService.addTestsToGroup(this.id, this.selected).subscribe( + success => { + this.snackBar.open('Twoje testy zostały dodane do grupy.', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-success'] }); + this.getTestsToAdd(); + }, + error => { + this.snackBar.open('Wystąpił błąd serwera. Spróbuj ponownie później.', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } + ); + } else if (this.selectedTypeOfResource === ResourceType.flashcards) { + this.addFlashcardsToGroupSub = this.groupService.addFlashcardsToGroup(this.id, this.selected).subscribe( + success => { + this.snackBar.open('Twoje fiszki zostały dodane do grupy.', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-success'] }); + this.getFlashcardsToAdd(); + }, + error => { + this.snackBar.open('Wystąpił błąd serwera. Spróbuj ponownie później.', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } + ); + } else if (this.selectedTypeOfResource === ResourceType.materials) { + this.addMaterialsToGroupSub = this.groupService.addMaterialsToGroup(this.id, this.selected).subscribe( + success => { + this.snackBar.open('Twoje materiały zostały dodane do grupy.', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-success'] }); + this.getMaterialsToAdd(); + }, + error => { + this.snackBar.open('Wystąpił błąd serwera. Spróbuj ponownie później.', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } + ); + } else { + this.snackBar.open('Wystąpił błąd serwera. Spróbuj ponownie później.', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } + this.refreshList(); + } + +} diff --git a/src/app/groups/sharing-resources-in-groups/sharing-resources-in-groups.ts b/src/app/groups/sharing-resources-in-groups/sharing-resources-in-groups.ts new file mode 100644 index 0000000..1ae1b28 --- /dev/null +++ b/src/app/groups/sharing-resources-in-groups/sharing-resources-in-groups.ts @@ -0,0 +1,5 @@ +export enum ResourceType { + materials = 'materials', + flashcards = 'flashcards', + test = 'test' +} diff --git a/src/app/groups/waiting-resources/flashcards-to-group-preview/flashcards-to-group-preview.component.css b/src/app/groups/waiting-resources/flashcards-to-group-preview/flashcards-to-group-preview.component.css new file mode 100644 index 0000000..2b08783 --- /dev/null +++ b/src/app/groups/waiting-resources/flashcards-to-group-preview/flashcards-to-group-preview.component.css @@ -0,0 +1,19 @@ +.puzzle{ + min-height: 50px; + min-width: 100px; + background-color: rgb(206, 168, 86); + align-items: center; + color: black; + justify-content: center; + margin: 5px 5px 5px 5px; +} + +.puzzle-right{ + min-height: 50px; + min-width: 100px; + color: black; + background-color: rgb(202, 144, 17);; + align-items: center; + justify-content: center; + margin: 5px 5px 5px 5px; +} diff --git a/src/app/groups/waiting-resources/flashcards-to-group-preview/flashcards-to-group-preview.component.html b/src/app/groups/waiting-resources/flashcards-to-group-preview/flashcards-to-group-preview.component.html new file mode 100644 index 0000000..522f665 --- /dev/null +++ b/src/app/groups/waiting-resources/flashcards-to-group-preview/flashcards-to-group-preview.component.html @@ -0,0 +1,19 @@ +
+

{{ set.name }}

+ + + + + + + + + + + +
+ Lewa strona + + Prawa strona +
{{flashcard.left_side}}{{flashcard.right_side}}
+
diff --git a/src/app/groups/waiting-resources/flashcards-to-group-preview/flashcards-to-group-preview.component.spec.ts b/src/app/groups/waiting-resources/flashcards-to-group-preview/flashcards-to-group-preview.component.spec.ts new file mode 100644 index 0000000..b07a10c --- /dev/null +++ b/src/app/groups/waiting-resources/flashcards-to-group-preview/flashcards-to-group-preview.component.spec.ts @@ -0,0 +1,46 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FlashcardsToGroupPreviewComponent } from './flashcards-to-group-preview.component'; + +describe('FlashcardsToGroupPreviewComponent', () => { + let component: FlashcardsToGroupPreviewComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ FlashcardsToGroupPreviewComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(FlashcardsToGroupPreviewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should input set correct title', async(() => { + fixture.autoDetectChanges(); + component.set = { + 'id': 1, + 'name': 'abc', + 'flashcards': [ + {left_side: 'a', right_side: 'b'}, + {left_side: 'c', right_side: 'd'} + ], + 'grade': 0, + 'owner': 2, + 'add_date': '2019-01-06', + 'edit_date': '2019-01-06', + 'body': [] + }; + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(fixture.debugElement.nativeElement.querySelectorAll('h2')[0].textContent).toEqual('abc'); + }); + })); +}); diff --git a/src/app/groups/waiting-resources/flashcards-to-group-preview/flashcards-to-group-preview.component.ts b/src/app/groups/waiting-resources/flashcards-to-group-preview/flashcards-to-group-preview.component.ts new file mode 100644 index 0000000..09adc53 --- /dev/null +++ b/src/app/groups/waiting-resources/flashcards-to-group-preview/flashcards-to-group-preview.component.ts @@ -0,0 +1,16 @@ +import { Component, OnInit, Input } from '@angular/core'; + +@Component({ + selector: 'app-flashcards-to-group-preview', + templateUrl: './flashcards-to-group-preview.component.html', + styleUrls: ['./flashcards-to-group-preview.component.css'] +}) +export class FlashcardsToGroupPreviewComponent implements OnInit { + + @Input() public set: any; + + constructor() {} + + ngOnInit() {} + +} diff --git a/src/app/groups/waiting-resources/localeText.ts b/src/app/groups/waiting-resources/localeText.ts new file mode 100644 index 0000000..6c291e9 --- /dev/null +++ b/src/app/groups/waiting-resources/localeText.ts @@ -0,0 +1,90 @@ +const localeText = { + + // for filter panel + page: 'Strona', + more: 'Więcej', + to: 'do', + of: 'z', + next: '>', + last: '>>', + first: '<<', + previous: '<', + loadingOoo: 'Ładowanie...', + + // for set filter + selectAll: 'Wybierz wszystkie', + searchOoo: 'Szukaj', + blanks: 'Luki', + + // for number filter and text filter + filterOoo: 'Filtruj...', + applyFilter: 'Zastosuj filtr', + + // for number filter + equals: 'Równy', + notEqual: 'Nie równy', + lessThan: 'Mniejszy niż', + greaterThan: 'Większy niż', + + // for text filter + contains: 'Zawiera', + notContains: 'Nie zawiera', + startsWith: 'Zaczyna się od', + endsWith: 'Kończy się na', + + // the header of the default group column + group: 'Grupa', + + // tool panel + columns: 'Kolumny', + rowGroupColumns: 'Grupa wiersza kolumn', + rowGroupColumnsEmptyMessage: 'Przeciągnij kolumny do grupy', + valueColumns: 'Wartości kolumn', + pivotMode: 'Pivot-Mode', + groups: 'Grupy', + values: 'Wartości', + pivots: 'Pivots', + valueColumnsEmptyMessage: 'Przeciągnij kolumny do agregacji', + pivotColumnsEmptyMessage: 'Przeciągnij kolumny na oś', + toolPanelButton: 'Narzędzia', + + // other + noRowsToShow: 'Brak elementów do wyświetlenia', + + // enterprise menu + pinColumn: 'Przypiąta kolumna', + valueAggregation: 'Sumuj', + autosizeThiscolumn: 'Autosize this column', + autosizeAllColumns: 'Autosize All Columns', + groupBy: 'Grupuj po', + ungroupBy: 'Nie grupuj po', + resetColumns: 'Resetuj kolumny', + expandAll: 'Rozszerz wszystko', + collapseAll: 'Zwiń wszystko', + toolPanel: 'Narzędzia', + export: 'Export', + csvExport: 'CSV Export', + excelExport: 'Excel Export', + + // enterprise menu pinning + pinLeft: 'Przypnij <<', + pinRight: 'Przypnij >>', + noPin: 'Nie przypinaj <>', + + // enterprise menu aggregation and status panel + sum: 'Suma', + min: 'Minimum', + max: 'Maksimum', + none: 'Nic', + count: 'Ilość', + average: 'Średnia', + + // standard menu + copy: 'Kopiuj', + copyWithHeaders: 'Kopiuj z nagłówkami', + ctrlC: 'ctrl + C', + paste: 'wklej', + ctrlV: 'ctrl + C' +}; + +export default localeText; diff --git a/src/app/groups/waiting-resources/material-to-group-preview/material-to-group-preview.component.css b/src/app/groups/waiting-resources/material-to-group-preview/material-to-group-preview.component.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/groups/waiting-resources/material-to-group-preview/material-to-group-preview.component.html b/src/app/groups/waiting-resources/material-to-group-preview/material-to-group-preview.component.html new file mode 100644 index 0000000..ede97c1 --- /dev/null +++ b/src/app/groups/waiting-resources/material-to-group-preview/material-to-group-preview.component.html @@ -0,0 +1,5 @@ +
+

{{ title }}

+
+ +
diff --git a/src/app/groups/waiting-resources/material-to-group-preview/material-to-group-preview.component.spec.ts b/src/app/groups/waiting-resources/material-to-group-preview/material-to-group-preview.component.spec.ts new file mode 100644 index 0000000..041fd3e --- /dev/null +++ b/src/app/groups/waiting-resources/material-to-group-preview/material-to-group-preview.component.spec.ts @@ -0,0 +1,34 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MaterialToGroupPreviewComponent } from './material-to-group-preview.component'; + +describe('MaterialToGroupPreviewComponent', () => { + let component: MaterialToGroupPreviewComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ MaterialToGroupPreviewComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(MaterialToGroupPreviewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should input set correct title', async(() => { + fixture.autoDetectChanges(); + component.title = 'test1'; + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(fixture.debugElement.nativeElement.querySelectorAll('h2')[0].textContent).toEqual('test1'); + }); + })); +}); diff --git a/src/app/groups/waiting-resources/material-to-group-preview/material-to-group-preview.component.ts b/src/app/groups/waiting-resources/material-to-group-preview/material-to-group-preview.component.ts new file mode 100644 index 0000000..cded6d6 --- /dev/null +++ b/src/app/groups/waiting-resources/material-to-group-preview/material-to-group-preview.component.ts @@ -0,0 +1,20 @@ +import { Component, OnInit, Input } from '@angular/core'; + +@Component({ + selector: 'app-material-to-group-preview', + templateUrl: './material-to-group-preview.component.html', + styleUrls: ['./material-to-group-preview.component.css'] +}) +export class MaterialToGroupPreviewComponent implements OnInit { + + @Input() public id: number; + @Input() public title: string; + + public serverURL = 'http://studycave.eu-west-1.elasticbeanstalk.com/file/files/'; // działa na globalu + // public serverURL = 'http://localhost:8080/file/files/' ; // działa na localhost + + constructor() {} + + ngOnInit() {} + +} diff --git a/src/app/groups/waiting-resources/test-to-group-preview/test-to-group-preview.component.css b/src/app/groups/waiting-resources/test-to-group-preview/test-to-group-preview.component.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/groups/waiting-resources/test-to-group-preview/test-to-group-preview.component.html b/src/app/groups/waiting-resources/test-to-group-preview/test-to-group-preview.component.html new file mode 100644 index 0000000..12e8ad7 --- /dev/null +++ b/src/app/groups/waiting-resources/test-to-group-preview/test-to-group-preview.component.html @@ -0,0 +1,51 @@ +
+

{{ test.title }}

+
+
+
+

{{ j + 1 }}. Prawda/fałsz

+

{{ j + 1 }}. Jednokrotny wybór

+

{{ j + 1 }}. Wielokrotny wybór

+

{{ j + 1 }}. Rozsypanka wyrazowa

+

{{ j + 1 }}. Uzupełnianie luk

+

{{ j + 1 }}. Łączenie w pary

+
+ {{ question.question }} ({{ question.points }} pkt) +
+
+

+ {{ i + 1 }}. + (Prawidłowa odpowiedź) + (Nieprawidłowa odpowiedź) + {{ answer.content }} +

+
+
+
+

Kolejne elementy rozsypanki:

+

+ {{ correct }}, +

+
+
+
+ + + + {{ content }} | + {{ content }} + + +
+
+

+
+
+

+ {{ answer.first }}{{ answer.second }} +

+
+
+
+
+
diff --git a/src/app/groups/waiting-resources/test-to-group-preview/test-to-group-preview.component.spec.ts b/src/app/groups/waiting-resources/test-to-group-preview/test-to-group-preview.component.spec.ts new file mode 100644 index 0000000..4e5e5bb --- /dev/null +++ b/src/app/groups/waiting-resources/test-to-group-preview/test-to-group-preview.component.spec.ts @@ -0,0 +1,43 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TestToGroupPreviewComponent } from './test-to-group-preview.component'; + +describe('TestToGroupPreviewComponent', () => { + let component: TestToGroupPreviewComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [TestToGroupPreviewComponent] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(TestToGroupPreviewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should input set correct title', async(() => { + fixture.autoDetectChanges(); + component.test = { + 'id': 1, + 'title': 'abc', + 'permission': 'public', + 'grade': 0, + 'owner': 2, + 'add_date': '2019-01-06', + 'edit_date': '2019-01-06', + 'body': [] + }; + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(fixture.debugElement.nativeElement.querySelectorAll('h2')[0].textContent).toEqual('abc'); + }); + })); +}); diff --git a/src/app/groups/waiting-resources/test-to-group-preview/test-to-group-preview.component.ts b/src/app/groups/waiting-resources/test-to-group-preview/test-to-group-preview.component.ts new file mode 100644 index 0000000..76ada2e --- /dev/null +++ b/src/app/groups/waiting-resources/test-to-group-preview/test-to-group-preview.component.ts @@ -0,0 +1,17 @@ +import { Component, OnInit, Input } from '@angular/core'; +import { Test } from '../../../tests/test_model'; + +@Component({ + selector: 'app-test-to-group-preview', + templateUrl: './test-to-group-preview.component.html', + styleUrls: ['./test-to-group-preview.component.css'] +}) +export class TestToGroupPreviewComponent implements OnInit { + + @Input() public test: Test; + + constructor() { } + + ngOnInit() {} + +} diff --git a/src/app/groups/waiting-resources/waiting-resources.component.css b/src/app/groups/waiting-resources/waiting-resources.component.css new file mode 100644 index 0000000..648d4b5 --- /dev/null +++ b/src/app/groups/waiting-resources/waiting-resources.component.css @@ -0,0 +1,25 @@ +.wrapper { + width: 100%; + padding: 30px; + min-height: 100%; + margin-bottom: 40px; +} + +.content { + background-color: #181616; + padding: 30px; + margin-top: 2rem; + margin-bottom: 2rem; + text-align: center; + margin-left: auto; + margin-right: auto; +} + +.buttons-container > div { + margin: 10px; +} + +.multiselect-wrapper { + display: flex; + justify-content: center; +} diff --git a/src/app/groups/waiting-resources/waiting-resources.component.html b/src/app/groups/waiting-resources/waiting-resources.component.html new file mode 100644 index 0000000..2e4887a --- /dev/null +++ b/src/app/groups/waiting-resources/waiting-resources.component.html @@ -0,0 +1,85 @@ +
+
+
+ +
+
+

Oczekujące zasoby

+
+
+

Wybierz typ zasobu:

+
+ + + +
+
+

+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
+ + +
+ + + +
+
+ +

+
+ + + +
+ +

+ + +
+
+ + + +

+ + +
+
diff --git a/src/app/groups/waiting-resources/waiting-resources.component.spec.ts b/src/app/groups/waiting-resources/waiting-resources.component.spec.ts new file mode 100644 index 0000000..890b5f2 --- /dev/null +++ b/src/app/groups/waiting-resources/waiting-resources.component.spec.ts @@ -0,0 +1,67 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { WaitingResourcesComponent } from './waiting-resources.component'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { RouterTestingModule } from '@angular/router/testing'; +import { HttpClientModule } from '@angular/common/http'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { GroupsService } from '../groups.service'; +import { AuthenticationService } from '../../authentication.service'; +import { TestsService } from '../../tests/tests.service'; +import { MaterialsService } from '../../materials/materials.service'; +import { FlashcardsService } from '../../flashcards/flashcards.service'; +import { Routes, RouterModule } from '@angular/router'; +import { AuthGuard } from '../../auth-guard.service'; +import { GroupServiceMockService } from '../group-service-mock.service'; +import { APP_BASE_HREF } from '@angular/common'; + +describe('WaitingResourcesComponent', () => { + let component: WaitingResourcesComponent; + let fixture: ComponentFixture; + const routes: Routes = [ + { path: 'groups/waiting-resources/:id', component: WaitingResourcesComponent, canActivate: [AuthGuard] }, + ]; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ WaitingResourcesComponent ], + schemas: [NO_ERRORS_SCHEMA], + imports: [ RouterTestingModule, HttpClientModule, MatSnackBarModule, RouterModule.forRoot(routes)], + providers: [GroupsService, AuthenticationService, TestsService, FlashcardsService, MaterialsService, AuthGuard, + { provide: GroupsService, useClass: GroupServiceMockService }, + { provide: APP_BASE_HREF, useValue: 'groups/add-resources/0' }] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(WaitingResourcesComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should show resources', async(() => { + fixture.autoDetectChanges(); + spyOn(component, 'getWaitingMaterials').and.callThrough(); + fixture.debugElement.nativeElement.querySelectorAll('button')[1].click(); + expect(component.getWaitingMaterials).toHaveBeenCalled(); + })); + + it('should show flashcards', async(() => { + fixture.autoDetectChanges(); + spyOn(component, 'getWaitingFlashcards').and.callThrough(); + fixture.debugElement.nativeElement.querySelectorAll('button')[2].click(); + expect(component.getWaitingFlashcards).toHaveBeenCalled(); + })); + + it('should show tests', async(() => { + fixture.autoDetectChanges(); + spyOn(component, 'getWaitingTests').and.callThrough(); + fixture.debugElement.nativeElement.querySelectorAll('button')[3].click(); + expect(component.getWaitingTests).toHaveBeenCalled(); + })); +}); diff --git a/src/app/groups/waiting-resources/waiting-resources.component.ts b/src/app/groups/waiting-resources/waiting-resources.component.ts new file mode 100644 index 0000000..0b95591 --- /dev/null +++ b/src/app/groups/waiting-resources/waiting-resources.component.ts @@ -0,0 +1,405 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { GroupsService } from '../groups.service'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { Resource } from '../resource'; +import { ResourceType } from '../sharing-resources-in-groups/sharing-resources-in-groups'; +import { Subscription } from 'rxjs/Subscription'; +import localeText from './localeText'; +import { GridOptions } from 'ag-grid-community'; +import { TestsService } from '../../tests/tests.service'; +import { Test } from '../../tests/test_model'; +import { FlashcardsService } from '../../flashcards/flashcards.service'; + +import * as $ from 'jquery'; + +@Component({ + selector: 'app-waiting-resources', + templateUrl: './waiting-resources.component.html', + styleUrls: ['./waiting-resources.component.css'] +}) +export class WaitingResourcesComponent implements OnInit, OnDestroy { + + public id = 0; + + public waitingMaterials: Resource[] = []; + public waitingTests: Resource[] = []; + public waitingFlashcards: Resource[] = []; + + public selectedTypeOfResource: ResourceType; + public selectedResource: Resource = {}; + + private getWaitingMaterialsSub: Subscription; + private getWaitingTestsSub: Subscription; + private getWaitingFlashcardsSub: Subscription; + + private confirmTestSub: Subscription; + private confirmMaterialSub: Subscription; + private confirmFlashcardsSub: Subscription; + + private testSubscribtion: Subscription; + private flashcardSubscribtion: Subscription; + + private gridApi: any; + public gridOptions: GridOptions; + public localeText = localeText; + + public columnDefs = [ + { headerName: 'Nazwa', field: 'title', headerTooltip: 'Nazwa' }, + { headerName: 'Właściciel', field: 'owner', headerTooltip: 'Właściciel', hide: false }, + { + headerName: '', + suppressMenu: true, + suppressSorting: true, + cellRenderer: this.customCellRendererFunc + } + ]; + + public displayMaterials = false; + public displayTests = false; + public displayFlashcards = false; + + public displayPreviewDialog = false; + public displayAcceptDialog = false; + public displayRejectDialog = false; + + public test: Test; + public set: any; + + public width = 300; + + constructor(private route: ActivatedRoute, + private groupService: GroupsService, + private testService: TestsService, + private flashcardsService: FlashcardsService, + public snackBar: MatSnackBar) { } + + ngOnInit() { + this.id = this.route.snapshot.params.id; + this.gridOptions = { + rowHeight: 50, + headerHeight: 25, + getRowStyle: function (params) { + return { + cursor: 'pointer' + }; + }, + }; + } + + getWaitingMaterials() { + this.selectedTypeOfResource = ResourceType.materials; + this.displayMaterials = true; + this.displayTests = false; + this.displayFlashcards = false; + this.getWaitingMaterialsSub = this.groupService.getWaitingMaterialsInGroup(this.id).subscribe( + success => { + this.waitingTests = []; + this.waitingFlashcards = []; + this.waitingMaterials = success; + }, + error => { + this.snackBar.open('Wystąpił błąd serwera. Spróbuj ponownie później.', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + console.log('Something went wrong :( \nError: ', error); + } + ); + } + + getWaitingTests() { + this.selectedTypeOfResource = ResourceType.test; + this.displayMaterials = false; + this.displayTests = true; + this.displayFlashcards = false; + this.getWaitingTestsSub = this.groupService.getWaitingTestsInGroup(this.id).subscribe( + success => { + this.waitingFlashcards = []; + this.waitingMaterials = []; + this.waitingTests = success; + }, + error => { + this.snackBar.open('Wystąpił błąd serwera. Spróbuj ponownie później.', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + console.log('Something went wrong :( \nError: ', error); + } + ); + } + + getWaitingFlashcards() { + this.selectedTypeOfResource = ResourceType.flashcards; + this.displayMaterials = false; + this.displayTests = false; + this.displayFlashcards = true; + this.getWaitingFlashcardsSub = this.groupService.getWaitingFlashcardsInGroup(this.id).subscribe( + success => { + this.waitingMaterials = []; + this.waitingTests = []; + this.waitingFlashcards = success; + }, + error => { + this.snackBar.open('Wystąpił błąd serwera. Spróbuj ponownie później.', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + console.log('Something went wrong :( \nError: ', error); + } + ); + } + + refreshList() { + if (this.selectedTypeOfResource === ResourceType.test) { + this.getWaitingTests(); + } + + if (this.selectedTypeOfResource === ResourceType.flashcards) { + this.getWaitingFlashcards(); + } + + if (this.selectedTypeOfResource === ResourceType.materials) { + this.getWaitingMaterials(); + } + } + + save(resource: Resource) { + this.id = this.route.snapshot.params.id; + + if (this.selectedTypeOfResource === ResourceType.test) { + this.confirmTestSub = this.groupService.acceptTestsInGroup(this.id, resource.id, resource.points, + resource.comment).subscribe( + success => { + this.refreshList(); + this.displayAcceptDialog = false; + this.snackBar.open('Test został zatwierdzony.', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-success'] }); + }, + error => { + this.snackBar.open('Wystąpił błąd serwera. Spróbuj ponownie później.', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } + ); + } else if (this.selectedTypeOfResource === ResourceType.flashcards) { + this.confirmFlashcardsSub = this.groupService.acceptFlashcardsInGroup(this.id, resource.id, resource.points, + resource.comment).subscribe( + success => { + this.refreshList(); + this.displayAcceptDialog = false; + this.snackBar.open('Zestaw fiszek został zatwierdzony.', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-success'] }); + }, + error => { + this.snackBar.open('Wystąpił błąd serwera. Spróbuj ponownie później.', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } + ); + } else if (this.selectedTypeOfResource === ResourceType.materials) { + this.confirmMaterialSub = this.groupService.acceptMaterialsInGroup(this.id, resource.id, resource.points, + resource.comment).subscribe( + success => { + this.refreshList(); + this.displayAcceptDialog = false; + this.snackBar.open('Materiał został zatwierdzony.', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-success'] }); + }, + error => { + this.snackBar.open('Wystąpił błąd serwera. Spróbuj ponownie później.', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } + ); + } + } + + reject(resource: Resource) { + this.id = this.route.snapshot.params.id; + + if (this.selectedTypeOfResource === ResourceType.test) { + this.confirmTestSub = this.groupService.rejectTestsFromGroup(this.id, resource.id, resource.comment).subscribe( + success => { + this.refreshList(); + this.displayRejectDialog = false; + this.snackBar.open('Test został odrzucony.', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-success'] }); + }, + error => { + this.snackBar.open('Wystąpił błąd serwera. Spróbuj ponownie później.', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } + ); + } else if (this.selectedTypeOfResource === ResourceType.flashcards) { + this.confirmFlashcardsSub = this.groupService.rejectFlashcardsFromGroup(this.id, resource.id, resource.comment).subscribe( + success => { + this.refreshList(); + this.displayRejectDialog = false; + this.snackBar.open('Zestaw fiszek został odrzucony.', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-success'] }); + }, + error => { + this.snackBar.open('Wystąpił błąd serwera. Spróbuj ponownie później.', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } + ); + } else if (this.selectedTypeOfResource === ResourceType.materials) { + this.confirmMaterialSub = this.groupService.rejectMaterialsFromGroup(this.id, resource.id, resource.comment).subscribe( + success => { + this.refreshList(); + this.displayRejectDialog = false; + this.snackBar.open('Materiał został odrzucony.', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-success'] }); + }, + error => { + this.snackBar.open('Wystąpił błąd serwera. Spróbuj ponownie później.', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } + ); + } + } + + setWidth() { + const width = $('body').innerWidth(); + if (width > 1000) { + this.width = width - 700; + } else { + this.width = 300; + } + } + + onActionPreviewClick(e) { + this.setWidth(); + if (this.waitingTests.length > 0) { + this.testSubscribtion = this.testService.getTest(e.data.id).subscribe( + data => { + this.test = data; + this.selectedResource = e.data; + this.displayPreviewDialog = true; + }, + error => { + this.snackBar.open('Coś poszło nie tak. Spróbuj ponownie później.', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } + ); + } else if (this.waitingFlashcards.length > 0) { + this.flashcardSubscribtion = this.flashcardsService.getSet(e.data.id).subscribe( + data => { + this.set = data; + this.selectedResource = e.data; + this.displayPreviewDialog = true; + }, + error => { + this.snackBar.open('Wystąpił błąd serwera. Spróbuj ponownie później.', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + console.log('Something went wrong :( \nError: ', error); + }); + } else { + this.selectedResource = e.data; + this.displayPreviewDialog = true; + } + } + + onActionAcceptClick(e) { + this.selectedResource = e.data; + this.selectedResource.comment = ''; + this.selectedResource.points = 0; + this.displayAcceptDialog = true; + } + + onActionRejectClick(e) { + this.selectedResource = e.data; + this.selectedResource.comment = ''; + this.displayRejectDialog = true; + } + + onRowClicked(e) { + if (e.event.target !== undefined) { + const actionType = e.event.target.getAttribute('data-action-type'); + switch (actionType) { + case 'preview': + return this.onActionPreviewClick(e); + case 'accept': + return this.onActionAcceptClick(e); + case 'reject': + return this.onActionRejectClick(e); + default: + return this.onActionPreviewClick(e); + } + } + } + + customCellRendererFunc(params) { + return ` + + + + `; + } + + onGridReady(params) { + this.gridApi = params.api; + this.gridApi.sizeColumnsToFit(); + } + + onGridColumnsChanged(params) { + params.api.sizeColumnsToFit(); + } + + onGridSizeChanged(params) { + if (params.clientWidth < 800) { + this.columnDefs = [ + { headerName: 'Nazwa', field: 'title', headerTooltip: 'Nazwa' }, + { headerName: 'Właściciel', field: 'owner', headerTooltip: 'Właściciel', hide: true }, + { + headerName: '', + suppressMenu: true, + suppressSorting: true, + cellRenderer: this.customCellRendererFunc + } + ]; + } else { + this.columnDefs = [ + { headerName: 'Nazwa', field: 'title', headerTooltip: 'Nazwa' }, + { headerName: 'Właściciel', field: 'owner', headerTooltip: 'Właściciel', hide: false }, + { + headerName: '', + suppressMenu: true, + suppressSorting: true, + cellRenderer: this.customCellRendererFunc + } + ]; + } + params.api.sizeColumnsToFit(); + } + + ngOnDestroy() { + if (this.getWaitingMaterialsSub) { + this.getWaitingMaterialsSub.unsubscribe(); + } + + if (this.getWaitingTestsSub) { + this.getWaitingTestsSub.unsubscribe(); + } + + if (this.getWaitingFlashcardsSub) { + this.getWaitingFlashcardsSub.unsubscribe(); + } + + if (this.confirmTestSub) { + this.confirmTestSub.unsubscribe(); + } + + if (this.confirmMaterialSub) { + this.confirmMaterialSub.unsubscribe(); + } + + if (this.confirmFlashcardsSub) { + this.confirmFlashcardsSub.unsubscribe(); + } + + if (this.testSubscribtion) { + this.testSubscribtion.unsubscribe(); + } + + if (this.flashcardSubscribtion) { + this.flashcardSubscribtion.unsubscribe(); + } + } + +} diff --git a/src/app/home-page/home-page.component.css b/src/app/home-page/home-page.component.css new file mode 100644 index 0000000..78c2f8b --- /dev/null +++ b/src/app/home-page/home-page.component.css @@ -0,0 +1,96 @@ +.wrapper{ + height: 100%; +} + +.top-section{ + min-height: 40%; + width: 100%; + color: white; + text-align: center; + padding-top: 100px; + padding-left: 10px; + padding-right: 10px; +} + +.title{ + font-size: 48px; +} + +.text-content{ + font-size: 24px; +} + +.yellow-font{ + color: rgb(206, 168, 86); +} + +.bottom-section{ + font-size: 20px; + box-sizing: border-box; + min-height: 60%; + width: 100%; + padding: 36px; + padding-top: 20px; + padding-bottom: 64px; + color: white; + text-align: justify; + text-justify: inter-word; + background-color: rgb(24, 22, 22); + max-width: 90%; + margin: 0 auto; + line-height: 200%; +} + +h2{ + text-align: center; + color: rgb(206, 168, 86); + font-weight: bold; + padding: 25px 0; +} +.students-paragraph{ + background-image:url("./../../assets/study1.png"); + background-position: center right; + background-repeat: no-repeat; + background-size: auto 100%; + text-shadow: 2px 2px #000000; +} + +.teachers-paragraph{ + background-image:url("./../../assets/study2.png"); + background-position: center left; + background-repeat: no-repeat; + background-size: auto 100%; + text-shadow: 2px 2px #000000; +} + +@media screen and (max-width: 800px) { + + h2{ + background-size: auto 100%; + } + + .bottom-section{ + display: block; + padding-bottom: 20px; + } + + .top-section{ + width: 100%; + padding: 70px 0; + } + +} + +@media screen and (max-width: 440px) { + .title { + font-size: 36px; + } + + .students-paragraph{ + background-size: 100% auto; + } + + .teachers-paragraph{ + background-size: 100% auto; + } +} diff --git a/src/app/home-page/home-page.component.html b/src/app/home-page/home-page.component.html new file mode 100644 index 0000000..325e9c1 --- /dev/null +++ b/src/app/home-page/home-page.component.html @@ -0,0 +1,58 @@ +
+
+
+

Platforma + e-learningowa dla uczniów i nauczycieli +

+
+ Witamy w naszej platformie e-learningowej stworzonej z myślą o uczniach i + nauczycielach. Tutaj znajdziesz materiały oraz testy do nauki z wielu przedmiotów, jak również minigry oparte + na materiałach. +
+ +
+
+
+

Dla uczniów

+

Nauka jest dla Ciebie raczej przykrym obowiązkiem niż ciekawym zajęciem? Masz trudności z + zapamiętywaniem lub + nie znalazłeś jeszcze swojej metody na przyswajanie wiedzy? Oferujemy Ci ogromny wybór metod i materiałów, + zaczynając + od tworzenia i przeglądania fiszek, aż do testów prawda-fałsz i tych wielokrotnego wyboru. Dbamy o + różnorodność + oferowanych Ci metod, więc ponadto możesz wybierać też spośród gry memory, uzupełniania luk w tekście, + rozsypanki + wyrazowej i łączenia w pary.

+

+ Naszym celem jest wspomaganie Twojej nauki i pokazanie Ci, że nie taki diabeł straszny - oderwij się na + chwilę + od czytania w kółko jednego rozdziału i usiłowania zapamiętać wszystkiego, co tam napisano. Gwarantujemy + świetną + zabawę i miłe urozmaicenie. Dlaczego warto skorzystać akurat z naszej platformy? Według zasady: uczniowie + uczniom. + Rozumiemy was i wiemy najlepiej, że na naukę nie zawsze jest czas i chęć. A może te chęci pojawią się gdy + spróbujecie + nieco innej metody? Czy nie warto dać sobie szansy na poprawę swoich wyników i wzbogacenie wiedzy? + Niezależnie + od tego, czy masz lat -naście czy -dziesiąt, uczysz się przez całe życie. +

+
+
+

Dla nauczycieli

+

Szukają Państwo miejsca gdzie można przygotować interaktywne testy lub materiały dla + uczniów? Te miejsce jest + do tego idealne. Oferujemy dużą różnorodność zadań, które mogą być dostosowane do wielu przedmiotów. Nasze + zasoby + pozwolą uczniom łątwiej przyswoić wiedzę i przygotować się do sprawdzianów. Nasza strona jest przystosowana + również + do urządzeń mobilnych, więc w łatwy sposób można korzystać z niej w każdym miejscu. + Mają Państwo możliwość stworzyć własne testy i materiały, jak również skorzystania z publicznych dóbr + stworzonych przez innych użytkowników. + Zaawansowane narzędzia szukania pozwolą w łatwy sposób znaleźć to czego Państwo potrzebują. Mamy nadzieję, że + nasza platforma ułatwi proces przygotowywania testów i materiałów oraz nada im ciekawą formę. + Nam też zależy, żeby nauka była dla uczniów czymś przyjemnym i żeby Państwa praca przynosiła jak najlepsze + efekty. +

+
+
+
\ No newline at end of file diff --git a/src/app/home-page/home-page.component.spec.ts b/src/app/home-page/home-page.component.spec.ts new file mode 100644 index 0000000..e180332 --- /dev/null +++ b/src/app/home-page/home-page.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { HomePageComponent } from './home-page.component'; + +describe('HomePageComponent', () => { + let component: HomePageComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ HomePageComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(HomePageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/home-page/home-page.component.ts b/src/app/home-page/home-page.component.ts new file mode 100644 index 0000000..24e1872 --- /dev/null +++ b/src/app/home-page/home-page.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-home-page', + templateUrl: './home-page.component.html', + styleUrls: ['./home-page.component.css'] +}) +export class HomePageComponent implements OnInit { + + constructor() { } + + ngOnInit() { + } + +} diff --git a/src/app/http-interceptors/api-interceptor.ts b/src/app/http-interceptors/api-interceptor.ts new file mode 100644 index 0000000..d4cbfdd --- /dev/null +++ b/src/app/http-interceptors/api-interceptor.ts @@ -0,0 +1,11 @@ +import { Injectable } from '@angular/core'; +import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest } from '@angular/common/http'; +import { Observable } from 'rxjs/Observable'; + +@Injectable() +export class ApiInterceptor implements HttpInterceptor { + intercept(req: HttpRequest, next: HttpHandler): Observable> { + const apiReq = req.clone({ url: `http://localhost:8080/${req.url}` }); + return next.handle(apiReq); + } +} diff --git a/src/app/http-interceptors/index.ts b/src/app/http-interceptors/index.ts new file mode 100644 index 0000000..23b4a90 --- /dev/null +++ b/src/app/http-interceptors/index.ts @@ -0,0 +1,9 @@ +/* "Barrel" of Http Interceptors */ +import { HTTP_INTERCEPTORS } from '@angular/common/http'; + +import { ApiInterceptor } from './api-interceptor'; + +/** Http interceptor providers in outside-in order */ +export const httpInterceptorProviders = [ + { provide: HTTP_INTERCEPTORS, useClass: ApiInterceptor, multi: true }, +]; diff --git a/src/app/login/login.component.css b/src/app/login/login.component.css new file mode 100644 index 0000000..cc7993f --- /dev/null +++ b/src/app/login/login.component.css @@ -0,0 +1,48 @@ +#container-small { + background-color: #181616; + padding: 30px; + width: 25%; + height: 400px; + margin: 25px; + float:left; + box-sizing: border-box; + position: relative; + text-align: center; +} +#container-login { + background-color: #181616; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + padding: 30px; + width: 50%; + min-width: 250px; + box-sizing: border-box; + position: fixed; + text-align: center; +} +.content{ + position:fixed; + width:100%; + box-sizing: border-box; + max-width: 100%; +} +#container-large { + background-color: #181616; + padding: 30px; + width: 65%; + min-height: 400px; + margin: 25px; + float: right; + box-sizing: border-box; + position: relative; + text-align: center; +} + +.title{ + font-size: 48px; +} + +.text-content{ + font-size: 18px; +} \ No newline at end of file diff --git a/src/app/login/login.component.html b/src/app/login/login.component.html new file mode 100644 index 0000000..0fe4929 --- /dev/null +++ b/src/app/login/login.component.html @@ -0,0 +1,13 @@ +
+
+
+
+ Nazwa użytkownika:
+
+ Hasło:
+

+ +
+
+
+
\ No newline at end of file diff --git a/src/app/login/login.component.spec.ts b/src/app/login/login.component.spec.ts new file mode 100644 index 0000000..d3f77f4 --- /dev/null +++ b/src/app/login/login.component.spec.ts @@ -0,0 +1,34 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { LoginComponent } from './login.component'; +import { FormsModule } from '@angular/forms'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { RouterTestingModule } from '@angular/router/testing'; +import { HttpClientModule } from '@angular/common/http'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { AuthenticationService } from '../authentication.service'; + +describe('LoginComponent', () => { + let component: LoginComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ LoginComponent ], + schemas: [NO_ERRORS_SCHEMA], + imports: [ RouterTestingModule, HttpClientModule, MatSnackBarModule, FormsModule], + providers: [AuthenticationService] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(LoginComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/login/login.component.ts b/src/app/login/login.component.ts new file mode 100644 index 0000000..7680b14 --- /dev/null +++ b/src/app/login/login.component.ts @@ -0,0 +1,52 @@ +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; +import { AuthenticationService } from '../authentication.service'; +import { HttpClientModule } from '@angular/common/http'; +import { HttpModule } from '@angular/http'; +import { MatSnackBar } from '@angular/material/snack-bar'; +@Component({ + moduleId: module.id, + templateUrl: 'login.component.html', + styleUrls: ['login.component.css'] +}) + +export class LoginComponent implements OnInit { + + public isLogin: Boolean = false; + model: any = {}; + loading = false; + error = ''; + + constructor( + private router: Router, + private authenticationService: AuthenticationService, + public snackBar: MatSnackBar) { } + + ngOnInit() { + } + + login() { + this.loading = true; + this.authenticationService.login(this.model.username, this.model.password) + .subscribe(result => { + if (result === true) { + // login successful + this.isLogin = true; + this.router.navigate(['home']); + this.snackBar.open('Zalogowano pomyślnie.', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-success'] }); + } else { + // login failed + this.error = 'Username or password is incorrect'; + this.loading = false; + this.snackBar.open('Niepoprawne hasło lub login.', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } + }, error => { + this.loading = false; + this.error = error; + this.snackBar.open('Niepoprawne hasło lub login.', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + }); + } +} diff --git a/src/app/main-navigation/main-navigation.component.css b/src/app/main-navigation/main-navigation.component.css new file mode 100644 index 0000000..0b11cd1 --- /dev/null +++ b/src/app/main-navigation/main-navigation.component.css @@ -0,0 +1,101 @@ +.wrapper{ + position: fixed; + top: 0px; + box-sizing: border-box; + width: 100%; + max-width: 100%; + margin: 0 auto; + background-color: #181616; + height: 100px; + /*border-bottom: 5px solid white;*/ + display: flex; + flex-direction: row; + flex-shrink: 0; + align-items: center; + justify-content: center; +} + +.logo-and-name{ + margin: 0 20px; + display: flex; + flex-direction: row; + flex-shrink: 0; + align-items: center; + cursor: pointer; +} + +.logo-and-name:hover{ + opacity: 0.8; +} + +.logo-and-name:focus{ + outline: none !important; +} + +.study-cave{ + display: inline-block; + font-size: 3rem; + margin-left: 0.5rem; + font-weight: bold; +} + +.study { + color: white; +} + +.cave { + color: #cea856; +} + +a { + color: white; + font-size: 1.25rem; + padding: 0px 10px; + cursor: pointer; +} + +a:hover{ + opacity: 0.8; + text-decoration: none; +} + +.menu{ + margin: 0 20px; + display: flex; + flex-direction: row; + flex-shrink: 0; + align-items: center; +} + +.log-reg-container{ + display: flex; + flex-direction: column; +} + +.logo { + height: 4rem; + width: 4rem; +} + +@media screen and (max-width: 1200px) { + .study-cave{ + font-size: 32px; + } + a{ + font-size: 20px; + } +} + +@media screen and (max-width: 800px) { + .wrapper{ + flex-wrap: wrap; + height: 170px; + justify-content: center; + } + .study-cave{ + font-size: 32px; + } + a{ + font-size: 14px; + } +} diff --git a/src/app/main-navigation/main-navigation.component.html b/src/app/main-navigation/main-navigation.component.html new file mode 100644 index 0000000..315e658 --- /dev/null +++ b/src/app/main-navigation/main-navigation.component.html @@ -0,0 +1,22 @@ + \ No newline at end of file diff --git a/src/app/main-navigation/main-navigation.component.spec.ts b/src/app/main-navigation/main-navigation.component.spec.ts new file mode 100644 index 0000000..4718b6b --- /dev/null +++ b/src/app/main-navigation/main-navigation.component.spec.ts @@ -0,0 +1,34 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MainNavigationComponent } from './main-navigation.component'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { RouterTestingModule } from '@angular/router/testing'; +import { HttpClientModule } from '@angular/common/http'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { FormsModule } from '@angular/forms'; +import { AuthenticationService } from '../authentication.service'; + +describe('MainNavigationComponent', () => { + let component: MainNavigationComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ MainNavigationComponent ], + schemas: [NO_ERRORS_SCHEMA], + imports: [ RouterTestingModule, HttpClientModule, MatSnackBarModule, FormsModule], + providers: [AuthenticationService] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(MainNavigationComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/main-navigation/main-navigation.component.ts b/src/app/main-navigation/main-navigation.component.ts new file mode 100644 index 0000000..77b1ac5 --- /dev/null +++ b/src/app/main-navigation/main-navigation.component.ts @@ -0,0 +1,45 @@ +import { Component, OnInit, Input, OnChanges, SimpleChanges, SimpleChange } from '@angular/core'; +import { Router } from '@angular/router'; +import { AuthenticationService } from '../authentication.service'; + +@Component({ + selector: 'app-main-navigation', + templateUrl: './main-navigation.component.html', + styleUrls: ['./main-navigation.component.css'] +}) +export class MainNavigationComponent implements OnInit { + currentUser; + isLogin: Boolean; + + constructor(private router: Router, private authenticationService: AuthenticationService) { + authenticationService.getLoggedInName.subscribe(name => this.isLoggedIn()); + } + + navToProfile() { + const currentUser = JSON.parse(localStorage.getItem('currentUser')); + this.router.navigate(['/profile', currentUser.username]); + } + + logout() { + this.authenticationService.logout(); + this.router.navigate(['/home']); + this.isLogin = false; + } + + isLoggedIn() { + if (localStorage.getItem('currentUser') === null) { + this.isLogin = false; + } else { + this.isLogin = true; + } + } + + ngOnInit(): void { + this.authenticationService.watchStorage().subscribe((data: string) => { + this.currentUser = JSON.parse(localStorage.getItem('currentUser')); + this.isLoggedIn(); + }); + this.authenticationService.watchStorageChanges(); + } + +} diff --git a/src/app/materials/materials-add/materials-add.component.css b/src/app/materials/materials-add/materials-add.component.css new file mode 100644 index 0000000..f5a0a77 --- /dev/null +++ b/src/app/materials/materials-add/materials-add.component.css @@ -0,0 +1,55 @@ +.description { + background-color: #22272a; + padding: 1rem; + margin-top: 1rem; + margin-bottom: 2rem; + text-align: left; + +} + +.container { + background-color: #181616; + padding: 30px; + margin-top: 2rem; + margin-bottom: 2rem; + text-align: center; +} + +.wrapper-description { + display: flex; + justify-content: center; + align-content: center; +} + +.wrapper-add { + width: 100%; + display: flex; + justify-content: center; + align-content: center; + margin-bottom: 4rem; +} + +.wrapper{ + width: 100%; + padding: 30px; + min-height: 100%; + margin-bottom: 40px; +} + +.content{ + background-color: #181616; + padding: 30px; + margin-top: 2rem; + margin-bottom: 2rem; + text-align: center; + margin-left: auto; + margin-right: auto; +} + +ag-grid-angular{ + margin: 10px; +} + +.buttons-container > div{ + margin: 10px; +} \ No newline at end of file diff --git a/src/app/materials/materials-add/materials-add.component.html b/src/app/materials/materials-add/materials-add.component.html new file mode 100644 index 0000000..9a2c2b8 --- /dev/null +++ b/src/app/materials/materials-add/materials-add.component.html @@ -0,0 +1,42 @@ +
+
+
+ +
+
+
+
+

Dodawanie pliku

+
+ +
+
+ {{progress.percentage}}%
+
+
+ +
+
+ +
+
+ + Udostępnij publicznie +

+
+ +

+ +
+
+
+ \ No newline at end of file diff --git a/src/app/materials/materials-add/materials-add.component.spec.ts b/src/app/materials/materials-add/materials-add.component.spec.ts new file mode 100644 index 0000000..7f76360 --- /dev/null +++ b/src/app/materials/materials-add/materials-add.component.spec.ts @@ -0,0 +1,35 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MaterialsAddComponent } from './materials-add.component'; +import { MaterialsService } from '../materials.service'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { RouterTestingModule } from '@angular/router/testing'; +import { HttpClientModule } from '@angular/common/http'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { FormsModule } from '@angular/forms'; +import { AuthenticationService } from '../../authentication.service'; + +describe('MaterialsAddComponent', () => { + let component: MaterialsAddComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ MaterialsAddComponent ], + schemas: [NO_ERRORS_SCHEMA], + imports: [ RouterTestingModule, HttpClientModule, MatSnackBarModule, FormsModule], + providers: [MaterialsService, AuthenticationService] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(MaterialsAddComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/materials/materials-add/materials-add.component.ts b/src/app/materials/materials-add/materials-add.component.ts new file mode 100644 index 0000000..8c66bfe --- /dev/null +++ b/src/app/materials/materials-add/materials-add.component.ts @@ -0,0 +1,74 @@ +import { Component, OnInit } from '@angular/core'; +import { HttpClient, HttpResponse, HttpEventType } from '@angular/common/http'; + +import { Router } from '@angular/router'; +import { MaterialsService } from '../materials.service'; +import { MatSnackBar } from '@angular/material/snack-bar'; + +@Component({ + selector: 'app-materials-add', + templateUrl: './materials-add.component.html', + styleUrls: ['./materials-add.component.css'] +}) +export class MaterialsAddComponent implements OnInit { + + selectedFiles: FileList; + currentFileUpload: File; + progress: { percentage: number } = { percentage: 0 }; + currentUser = JSON.parse(localStorage.getItem('currentUser')); + user: string; + title: string; + permission: Boolean = true; + constructor(private uploadService: MaterialsService, private router: Router, public snackBar: MatSnackBar) { } + + + ngOnInit() { this.isLoggedIn(); } + + changePermission(): void { + this.permission = !this.permission; + } + + isLoggedIn() { + if (localStorage.getItem('currentUser') === null) { + this.user = 'Anonim'; + } else { + this.user = this.currentUser.username; + } + } + selectFile(event) { + this.selectedFiles = event.target.files; + } + + upload() { + this.progress.percentage = 0; + const title = this.title; + const url = 'file/save'; + let p = 'Private'; + if (this.permission) { + p = 'Public'; + } + const permission = p; + this.currentFileUpload = this.selectedFiles.item(0); + this.uploadService.pushFileToStorage(this.currentFileUpload, this.user, title, permission, url).subscribe( + event => { + if (event.type === HttpEventType.UploadProgress) { + this.progress.percentage = Math.round(100 * event.loaded / event.total); + } else if (event instanceof HttpResponse) { + this.currentFileUpload = undefined; + this.snackBar.open(`Plik został zaimportowany. + Swoje materiały możesz podejrzeć na liście materiałów.`, null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-success'] }); + this.router.navigate(['materials/list']); + } + }, + error => { + this.snackBar.open('Wystąpił błąd serwera. Spróbuj ponownie później.', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + this.currentFileUpload = undefined; + } + ); + this.selectedFiles = undefined; + } +} + + diff --git a/src/app/materials/materials-details/materials-details.component.css b/src/app/materials/materials-details/materials-details.component.css new file mode 100644 index 0000000..5780197 --- /dev/null +++ b/src/app/materials/materials-details/materials-details.component.css @@ -0,0 +1,21 @@ +.listItem:hover { + background-color: rgb(206, 168, 86); +} + +.wrapper{ + width: 100%; + padding: 30px; + min-height: 100%; +} + +.btn{ + margin-bottom: 5px; +} + +.content{ + background-color: #181616; + padding: 30px; + margin-top: 2rem; + margin-bottom: 2rem; + text-align: center; +} \ No newline at end of file diff --git a/src/app/materials/materials-details/materials-details.component.html b/src/app/materials/materials-details/materials-details.component.html new file mode 100644 index 0000000..e30c0f5 --- /dev/null +++ b/src/app/materials/materials-details/materials-details.component.html @@ -0,0 +1,26 @@ +
+
+
+ +
+ MATERIAŁY: {{title}}
+ WŁAŚCICIEL: {{own}}
+ UPRAWNIENIA: {{perm}}
+
+
+ +
+ + +
+
+ +
+ + Czy chcesz usunąć materiał? + + + + + +
\ No newline at end of file diff --git a/src/app/materials/materials-details/materials-details.component.spec.ts b/src/app/materials/materials-details/materials-details.component.spec.ts new file mode 100644 index 0000000..ac0693f --- /dev/null +++ b/src/app/materials/materials-details/materials-details.component.spec.ts @@ -0,0 +1,36 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MaterialsDetailsComponent } from './materials-details.component'; +import { MaterialsService } from '../materials.service'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { RouterTestingModule } from '@angular/router/testing'; +import { HttpClientModule } from '@angular/common/http'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { FormsModule } from '@angular/forms'; +import { AuthenticationService } from '../../authentication.service'; +import { RoutingStateService } from '../../routing-state.service'; + +describe('MaterialsDetailsComponent', () => { + let component: MaterialsDetailsComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ MaterialsDetailsComponent ], + schemas: [NO_ERRORS_SCHEMA], + imports: [ RouterTestingModule, HttpClientModule, MatSnackBarModule, FormsModule], + providers: [MaterialsService, AuthenticationService, RoutingStateService] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(MaterialsDetailsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/materials/materials-details/materials-details.component.ts b/src/app/materials/materials-details/materials-details.component.ts new file mode 100644 index 0000000..08fb0b1 --- /dev/null +++ b/src/app/materials/materials-details/materials-details.component.ts @@ -0,0 +1,98 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { MaterialsService } from '../materials.service'; +import { Subscription } from 'rxjs/Subscription'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { RoutingStateService } from '../../routing-state.service'; + +@Component({ + selector: 'app-materials-details', + templateUrl: './materials-details.component.html', + styleUrls: ['./materials-details.component.css'] +}) +export class MaterialsDetailsComponent implements OnInit, OnDestroy { + id: number; + mat: any; + matSubscribtion: Subscription; + testTypeMenu = false; + user: Boolean = false; + ShowStatus: Boolean = false; + permission: string; + title: string; + owner: string; + own: string; + perm: string; + owned: Boolean = false; + display = false; + serverURL = 'http://studycave.eu-west-1.elasticbeanstalk.com/file/files/'; // działa na globalu + // serverURL = 'http://localhost:8080/file/files/' ; // działa na localhost + constructor(private route: ActivatedRoute, private materialsService: MaterialsService, private router: Router, + private routingState: RoutingStateService, public snackBar: MatSnackBar) { } + + ngOnInit() { + this.id = this.route.snapshot.params.id; + this.own = this.materialsService.getOwner(); + this.title = this.materialsService.getTitle(); + this.perm = this.materialsService.getPerm(); + this.isOwner(); + this.IsLogin(); + } + + openPopup() { + this.display = true; + } + + isOwner() { + const currentUser = JSON.parse(localStorage.getItem('currentUser')); + if (localStorage.getItem('currentUser')) { + if (currentUser.username === this.own) { + this.owned = true; + } + } else { + this.owned = false; + } + } + + IsLogin() { + if (localStorage.getItem('currentUser')) { + this.user = true; + } else { + this.user = false; + } + } + + changePermission(): void { + if (this.perm === 'Public') { + this.permission = 'Private'; + } else { + this.permission = 'Public'; + } + this.materialsService.changeMatPermission(this.id, this.permission); + this.snackBar.open('Zmieniono pozwolenie na: ' + this.permission, null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-success'] }); + this.goBack(); + } + + ngOnDestroy() { + if (this.matSubscribtion) { + this.matSubscribtion.unsubscribe(); + } + } + deleteMat() { + const data = this.id; + this.matSubscribtion = this.materialsService.deleteMat(data); + this.display = false; + + } + + download() { + this.materialsService.downloadFile(this.id); + // this.goBack(); + } + + goBack() { + const previousUrl = this.routingState.getPreviousUrl(); + this.router.navigate([previousUrl]); + } + +} diff --git a/src/app/materials/materials-list/localeText.ts b/src/app/materials/materials-list/localeText.ts new file mode 100644 index 0000000..b8b32c9 --- /dev/null +++ b/src/app/materials/materials-list/localeText.ts @@ -0,0 +1,90 @@ +const localeText = { + + // for filter panel + page: 'Strona', + more: 'Więcej', + to: 'do', + of: 'z', + next: '>', + last: '>>', + first: '<<', + previous: '<', + loadingOoo: 'Ładowanie...', + + // for set filter + selectAll: 'Wybierz wszystkie', + searchOoo: 'Szukaj', + blanks: 'Luki', + + // for number filter and text filter + filterOoo: 'Filtruj...', + applyFilter: 'Zastosuj filtr', + + // for number filter + equals: 'Równy', + notEqual: 'Nie równy', + lessThan: 'Mniejszy niż', + greaterThan: 'Większy niż', + + // for text filter + contains: 'Zawiera', + notContains: 'Nie zawiera', + startsWith: 'Zaczyna się od', + endsWith: 'Kończy się na', + + // the header of the default group column + group: 'Grupa', + + // tool panel + columns: 'Kolumny', + rowGroupColumns: 'Grupa wiersza kolumn', + rowGroupColumnsEmptyMessage: 'Przeciągnij kolumny do grupy', + valueColumns: 'Wartości kolumn', + pivotMode: 'Pivot-Mode', + groups: 'Grupy', + values: 'Wartości', + pivots: 'Pivots', + valueColumnsEmptyMessage: 'Przeciągnij kolumny do agregacji', + pivotColumnsEmptyMessage: 'Przeciągnij kolumny na oś', + toolPanelButton: 'Narzędzia', + + // other + noRowsToShow: 'Brak materiałów do wyświetlenia', + + // enterprise menu + pinColumn: 'Przypiąta kolumna', + valueAggregation: 'Sumuj', + autosizeThiscolumn: 'Autosize this column', + autosizeAllColumns: 'Autosize All Columns', + groupBy: 'Grupuj po', + ungroupBy: 'Nie grupuj po', + resetColumns: 'Resetuj kolumny', + expandAll: 'Rozszerz wszystko', + collapseAll: 'Zwiń wszystko', + toolPanel: 'Narzędzia', + export: 'Export', + csvExport: 'CSV Export', + excelExport: 'Excel Export', + + // enterprise menu pinning + pinLeft: 'Przypnij <<', + pinRight: 'Przypnij >>', + noPin: 'Nie przypinaj <>', + + // enterprise menu aggregation and status panel + sum: 'Suma', + min: 'Minimum', + max: 'Maksimum', + none: 'Nic', + count: 'Ilość', + average: 'Średnia', + + // standard menu + copy: 'Kopiuj', + copyWithHeaders: 'Kopiuj z nagłówkami', + ctrlC: 'ctrl + C', + paste: 'wklej', + ctrlV: 'ctrl + C' +}; + +export default localeText; diff --git a/src/app/materials/materials-list/materials-list.component.css b/src/app/materials/materials-list/materials-list.component.css new file mode 100644 index 0000000..ada8734 --- /dev/null +++ b/src/app/materials/materials-list/materials-list.component.css @@ -0,0 +1,47 @@ +.listItem:hover { + background-color: rgb(206, 168, 86); +} + +.listItem:active{ + background-color: #4BC7FA; +} + +.listItem{ + cursor: pointer; +} + + +@media screen and (max-width: 800px) { + .mobile{ + display: none; + } +} + +.empty{ + text-align: center; +} + +.wrapper{ + width: 100%; + padding: 30px; + min-height: 100%; + margin-bottom: 40px; +} + +.content{ + background-color: #181616; + padding: 30px; + margin-top: 2rem; + margin-bottom: 2rem; + text-align: center; + margin-left: auto; + margin-right: auto; +} + +ag-grid-angular{ + margin: 10px; +} + +.buttons-container > div{ + margin: 10px; +} diff --git a/src/app/materials/materials-list/materials-list.component.html b/src/app/materials/materials-list/materials-list.component.html new file mode 100644 index 0000000..2e3eadd --- /dev/null +++ b/src/app/materials/materials-list/materials-list.component.html @@ -0,0 +1,29 @@ + +
+
+ +
+

MATERIAŁY

+
+
+
+ +
+
+ + +
+
+
+ + + +
+
diff --git a/src/app/materials/materials-list/materials-list.component.spec.ts b/src/app/materials/materials-list/materials-list.component.spec.ts new file mode 100644 index 0000000..01c3be0 --- /dev/null +++ b/src/app/materials/materials-list/materials-list.component.spec.ts @@ -0,0 +1,35 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MaterialsListComponent } from './materials-list.component'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { RouterTestingModule } from '@angular/router/testing'; +import { HttpClientModule } from '@angular/common/http'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { FormsModule } from '@angular/forms'; +import { MaterialsService } from '../materials.service'; +import { AuthenticationService } from '../../authentication.service'; + +describe('MaterialsListComponent', () => { + let component: MaterialsListComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ MaterialsListComponent ], + schemas: [NO_ERRORS_SCHEMA], + imports: [ RouterTestingModule, HttpClientModule, MatSnackBarModule, FormsModule], + providers: [MaterialsService, AuthenticationService] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(MaterialsListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/materials/materials-list/materials-list.component.ts b/src/app/materials/materials-list/materials-list.component.ts new file mode 100644 index 0000000..85e3f48 --- /dev/null +++ b/src/app/materials/materials-list/materials-list.component.ts @@ -0,0 +1,277 @@ +import { Component, OnInit, Output, EventEmitter, OnDestroy } from '@angular/core'; +import { Router } from '@angular/router'; +import { Subscription } from 'rxjs/Subscription'; +import { MaterialsService } from '../materials.service'; +import { GridOptions, RowDoubleClickedEvent } from 'ag-grid-community/main'; +import localeText from './localeText'; +import { MatSnackBar } from '@angular/material/snack-bar'; + +@Component({ + selector: 'app-materials-list', + templateUrl: './materials-list.component.html', + styleUrls: ['./materials-list.component.css'] +}) +export class MaterialsListComponent implements OnInit, OnDestroy { + + mats = []; + matsPrivate = []; + matsEmpty = true; + selectedMat: any; + matSubscription: Subscription; + materialsSubscription: Subscription; + materialsSubscriptionOwners: Subscription; + ShowStatus: Boolean = false; + user: Boolean = false; + private gridApi; + private gridColumnApi; + public gridOptions: GridOptions; + localeText = localeText; + logged = false; + private publicMode = true; + private permission; + isGroup = false; + // serverURL = 'http://studycave-api.eu-west-1.elasticbeanstalk.com/file/files/' ; // działa na globalu + // serverURL = 'http://localhost:8080/file/files/' ; // działa na localhost + + columnDefs = [ + { headerName: 'Nazwa', field: 'title', headerTooltip: 'Nazwa' }, + { headerName: 'Data dodania', field: 'add_date', headerTooltip: 'Data dodania', hide: false }, + { headerName: 'Data modyfikacji', field: 'edit_date', headerTooltip: 'Data modyfikacji', hide: false }, + { headerName: 'Właściciel', field: 'owner', headerTooltip: 'Właściciel', hide: false }, + { headerName: 'Grupa', field: 'group', headerTooltip: 'Grupa', hide: !this.isGroup }, + { + headerName: '', + suppressMenu: true, + suppressSorting: true, + cellRenderer: this.customCellRendererFunc, + hide: !this.isGroup + } + ]; + + constructor(private materialsService: MaterialsService, private router: Router, public snackBar: MatSnackBar) { } + + customCellRendererFunc(params) { + const currentUser = JSON.parse(localStorage.getItem('currentUser')); + if (!currentUser) { + return ''; + } else if (params.data['owner'] === currentUser.username) { + return ` + + + `; + } else { + return ''; + } + } + + ngOnInit() { + if (localStorage.getItem('currentUser')) { + this.logged = true; + } + + this.gridOptions = { + rowHeight: 50, + headerHeight: 25, + getRowStyle: function (params) { + return { + cursor: 'pointer' + }; + }, + }; + this.getMats(); + this.IsLogin(); + this.refreshColumns(); + } + + + toMaterialsMaker() { + this.router.navigate(['materials/add-materials']); + } + + IsLogin() { + if (localStorage.getItem('currentUser')) { + this.user = true; + } else { + this.user = false; + } + } + + ShowPublic() { + this.ShowStatus = false; + this.getMats(); + } + + ShowPrivate() { + this.ShowStatus = true; + this.getMatsOwners(); + } + + public onRowClicked(e) { + if (e.event.target !== undefined) { + const data = e.data; + const actionType = e.event.target.getAttribute('data-action-type'); + + switch (actionType) { + case 'remove': + return this.onActionRemoveClick(e); + case 'changePermission': + return this.changePermission(e); + case 'download': + return this.download(e); + default: + return this.goToMats(e); + } + } + + } + + public onActionRemoveClick(e) { + this.matSubscription = this.materialsService.deleteMat(e.data.id); + + } + + download(e) { + this.materialsService.downloadFile(e.data.id); + } + + changePermission(e): void { + if (e.data.permission === 'Public') { + this.permission = 'Private'; + } else { + this.permission = 'Public'; + } + this.materialsService.changeMatPermission(e.data.id, this.permission); + this.snackBar.open('Zmieniono pozwolenie na: ' + this.permission, null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-success'] }); + this.router.navigate(['materials/list']); + } + + goToMats(e) { + this.materialsService.setOwner(e.data.owner); + this.materialsService.setTitle(e.data.title); + this.materialsService.setPerm(e.data.permission); + this.router.navigate(['materials/', e.data.id]); + } + + + onSelect(mat: any): void { + this.selectedMat = mat; + this.materialsService.setOwner(this.selectedMat.owner); + this.materialsService.setTitle(this.selectedMat.title); + this.materialsService.setPerm(this.selectedMat.permission); + this.router.navigate(['materials/', this.selectedMat.id]); + } + + getMats(): void { + this.isGroup = false; + this.materialsSubscription = this.materialsService.getMaterials() + .subscribe(data => { + this.mats = data; + if (this.mats.length > 0) { + this.matsEmpty = false; + } else { + this.matsEmpty = true; + } + this.refreshColumns(); + }); + } + + getMatsOwners(): void { + this.isGroup = true; + this.materialsSubscriptionOwners = this.materialsService.getMaterialsOwners() + .subscribe(data => { + data.forEach((x, i) => { + if (!x['group']) { + x['group'] = 'Brak'; + } + }); + this.mats = []; + this.mats = data; + if (this.mats.length > 0) { + this.matsEmpty = false; + } else { + this.matsEmpty = true; + } + this.refreshColumns(); + }); + } + + onGridReady(params) { + this.gridApi = params.api; + this.gridColumnApi = params.columnApi; + this.gridApi.sizeColumnsToFit(); + } + + onGridSizeChanged(params) { + if (params.clientWidth < 800) { + this.columnDefs = [ + { headerName: 'Nazwa', field: 'title', headerTooltip: 'Nazwa' }, + { headerName: 'Data dodania', field: 'add_date', headerTooltip: 'Data dodania', hide: true }, + { headerName: 'Data modyfikacji', field: 'edit_date', headerTooltip: 'Data modyfikacji', hide: true }, + { headerName: 'Właściciel', field: 'owner', headerTooltip: 'Właściciel', hide: true }, + { headerName: 'Grupa', field: 'group', headerTooltip: 'Grupa', hide: !this.isGroup }, + { + headerName: '', + suppressMenu: true, + suppressSorting: true, + cellRenderer: this.customCellRendererFunc, + hide: !this.isGroup + } + ]; + } else { + this.columnDefs = [ + { headerName: 'Nazwa', field: 'title', headerTooltip: 'Nazwa' }, + { headerName: 'Data dodania', field: 'add_date', headerTooltip: 'Data dodania', hide: false }, + { headerName: 'Data modyfikacji', field: 'edit_date', headerTooltip: 'Data modyfikacji', hide: false }, + { headerName: 'Właściciel', field: 'owner', headerTooltip: 'Właściciel', hide: false }, + { headerName: 'Ocena', field: 'grade', headerTooltip: 'Ocena', hide: false }, + { headerName: 'Grupa', field: 'group', headerTooltip: 'Grupa', hide: !this.isGroup }, + { + headerName: '', + suppressMenu: true, + suppressSorting: true, + cellRenderer: this.customCellRendererFunc, + hide: !this.isGroup + } + ]; + } + + params.api.sizeColumnsToFit(); + } + + onGidColumnsChanged(params) { + params.api.sizeColumnsToFit(); + } + + ngOnDestroy() { + if (this.materialsSubscription) { + this.materialsSubscription.unsubscribe(); + } + if (this.materialsSubscriptionOwners) { + this.materialsSubscriptionOwners.unsubscribe(); + } + if (this.matSubscription) { + this.matSubscription.unsubscribe(); + } + } + + refreshColumns() { + this.columnDefs = [ + { headerName: 'Nazwa', field: 'title', headerTooltip: 'Nazwa' }, + { headerName: 'Data dodania', field: 'add_date', headerTooltip: 'Data dodania', hide: false }, + { headerName: 'Data modyfikacji', field: 'edit_date', headerTooltip: 'Data modyfikacji', hide: false }, + { headerName: 'Właściciel', field: 'owner', headerTooltip: 'Właściciel', hide: false }, + { headerName: 'Grupa', field: 'group', headerTooltip: 'Grupa', hide: !this.isGroup }, + { + headerName: '', + suppressMenu: true, + suppressSorting: true, + cellRenderer: this.customCellRendererFunc, + hide: !this.isGroup + } + ]; + } + +} diff --git a/src/app/materials/materials-menu/materials-menu.component.css b/src/app/materials/materials-menu/materials-menu.component.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/materials/materials-menu/materials-menu.component.html b/src/app/materials/materials-menu/materials-menu.component.html new file mode 100644 index 0000000..ea5a3db --- /dev/null +++ b/src/app/materials/materials-menu/materials-menu.component.html @@ -0,0 +1,4 @@ +
+ + +
\ No newline at end of file diff --git a/src/app/materials/materials-menu/materials-menu.component.spec.ts b/src/app/materials/materials-menu/materials-menu.component.spec.ts new file mode 100644 index 0000000..046b688 --- /dev/null +++ b/src/app/materials/materials-menu/materials-menu.component.spec.ts @@ -0,0 +1,35 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MaterialsMenuComponent } from './materials-menu.component'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { RouterTestingModule } from '@angular/router/testing'; +import { HttpClientModule } from '@angular/common/http'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { FormsModule } from '@angular/forms'; +import { MaterialsService } from '../materials.service'; +import { AuthenticationService } from '../../authentication.service'; + +describe('MaterialsMenuComponent', () => { + let component: MaterialsMenuComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ MaterialsMenuComponent ], + schemas: [NO_ERRORS_SCHEMA], + imports: [ RouterTestingModule, HttpClientModule, MatSnackBarModule, FormsModule], + providers: [MaterialsService, AuthenticationService] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(MaterialsMenuComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/materials/materials-menu/materials-menu.component.ts b/src/app/materials/materials-menu/materials-menu.component.ts new file mode 100644 index 0000000..dd5faf3 --- /dev/null +++ b/src/app/materials/materials-menu/materials-menu.component.ts @@ -0,0 +1,17 @@ +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; + + +@Component({ + selector: 'app-materials-menu', + templateUrl: './materials-menu.component.html', + styleUrls: ['./materials-menu.component.css'] +}) +export class MaterialsMenuComponent implements OnInit { + + constructor(private router: Router) {} + + ngOnInit() { + } + +} diff --git a/src/app/materials/materials.module.ts b/src/app/materials/materials.module.ts new file mode 100644 index 0000000..f0b827c --- /dev/null +++ b/src/app/materials/materials.module.ts @@ -0,0 +1,34 @@ +import { CommonModule } from '@angular/common'; +import * as $ from 'jquery'; +import { BrowserModule } from '@angular/platform-browser'; +import { NgModule } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { HttpModule } from '@angular/http'; +import { HttpClientModule } from '@angular/common/http'; +import { MaterialsMenuComponent } from './materials-menu/materials-menu.component'; +import { MaterialsListComponent } from './materials-list/materials-list.component'; +import { RouterModule } from '@angular/router'; +import { MaterialsService } from './materials.service'; +import { MaterialsAddComponent } from './materials-add/materials-add.component'; +import { MaterialsDetailsComponent } from './materials-details/materials-details.component'; +import { AgGridModule } from 'ag-grid-angular'; +import { DialogModule } from 'primeng/dialog'; +import { ConfirmDialogModule } from 'primeng/confirmdialog'; +import { SharedModule } from '../shared/shared.module'; + +@NgModule({ + imports: [ + RouterModule, + BrowserModule, + FormsModule, + HttpModule, + HttpClientModule, + AgGridModule.withComponents([]), + DialogModule, + ConfirmDialogModule, + SharedModule + ], + declarations: [MaterialsMenuComponent, MaterialsListComponent, MaterialsAddComponent, MaterialsDetailsComponent], + providers: [MaterialsService] +}) +export class MaterialsModule { } diff --git a/src/app/materials/materials.service.spec.ts b/src/app/materials/materials.service.spec.ts new file mode 100644 index 0000000..f8f894b --- /dev/null +++ b/src/app/materials/materials.service.spec.ts @@ -0,0 +1,23 @@ +import { TestBed, inject } from '@angular/core/testing'; + +import { MaterialsService } from './materials.service'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { HttpClientModule } from '@angular/common/http'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { FormsModule } from '@angular/forms'; +import { AuthenticationService } from '../authentication.service'; +import { RouterTestingModule } from '@angular/router/testing'; + +describe('MaterialsService', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [MaterialsService, AuthenticationService], + schemas: [NO_ERRORS_SCHEMA], + imports: [RouterTestingModule, HttpClientModule, MatSnackBarModule, FormsModule] + }); + }); + + it('should be created', inject([MaterialsService], (service: MaterialsService) => { + expect(service).toBeTruthy(); + })); +}); diff --git a/src/app/materials/materials.service.ts b/src/app/materials/materials.service.ts new file mode 100644 index 0000000..2efab96 --- /dev/null +++ b/src/app/materials/materials.service.ts @@ -0,0 +1,150 @@ +import { Injectable } from '@angular/core'; +import { HttpClient, HttpHeaders, HttpRequest, HttpEvent } from '@angular/common/http'; +import { Http, Response, Headers, RequestOptions } from '@angular/http'; +import { Observable } from 'rxjs/Observable'; +import { of } from 'rxjs/observable/of'; +import 'rxjs/add/operator/map'; +import { Router } from '@angular/router'; +import { AuthenticationService } from '../authentication.service'; +import { THIS_EXPR } from '@angular/compiler/src/output/output_ast'; +import { MatSnackBar } from '@angular/material/snack-bar'; + +@Injectable() +export class MaterialsService { + + // tslint:disable-next-line:max-line-length + constructor(private httpClient: HttpClient, private router: Router, private authenticationService: AuthenticationService, + public snackBar: MatSnackBar) { this.setHeaders(); } + + private headers; + + private owner = JSON.parse(localStorage.getItem('currentUser')); + private own; + private title; + private perm; + + setHeaders() { + if (localStorage.getItem('currentUser')) { + this.headers = new HttpHeaders({ + 'Content-Type': 'application/json', + 'Authorization': '' + this.authenticationService.getToken() + }); + } else { + this.headers = new HttpHeaders({ + 'Content-Type': 'application/json' + }); + } + } + + getMaterials(): Observable { + this.setHeaders(); + return this.httpClient.get('file/materials/', { headers: this.headers, params: { permission: 'Public' } }); + } + + getMaterialsOwners(): Observable { + const owner = JSON.parse(localStorage.getItem('currentUser')); + this.setHeaders(); + return this.httpClient.get('file/materials/', { headers: this.headers, params: { owner: owner.username } }); + } + pushFileToStorage(file: File, user: string, title: string, permission: string, url: string): Observable> { + const formdata: FormData = new FormData(); + formdata.append('file', file); + formdata.append('owner', user); + formdata.append('title', title); + formdata.append('permission', permission); + formdata.append('grade', '0'); + const req = new HttpRequest('POST', url, formdata, { + reportProgress: true, + responseType: 'text' + }); + return this.httpClient.request(req); + } + + changeMatPermission(id, permission) { + this.setHeaders(); + this.httpClient.put('file/materials/' + id + '/permission', permission, { headers: this.headers }) + .subscribe(); + } + + setOwner(own) { + this.own = own; + } + + getOwner() { + return this.own; + } + setTitle(title) { + this.title = title; + } + + getTitle() { + return this.title; + } + setPerm(perm) { + this.perm = perm; + } + + getPerm() { + return this.perm; + } + deleteMat(id) { + if (localStorage.getItem('currentUser')) { + this.headers = new HttpHeaders({ + 'Content-Type': 'application/json', + 'Authorization': '' + this.authenticationService.getToken() + }); + } else { + this.headers = new HttpHeaders({ + 'Content-Type': 'application/json' + }); + } + return this.httpClient.delete('file/delete/' + id, { headers: this.headers, observe: 'response', responseType: 'text' }) + .subscribe(data => { this.sendResponse(data); }, + error => { + this.snackBar.open('Coś poszło nie tak. Spróbuj ponownie później.', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } + ); + + } + sendData(url, body) { + this.setHeaders(); + this.httpClient.post(url, body, { headers: this.headers, observe: 'response' }) + .subscribe(data => { this.sendResponse(data); }, + error => { + this.snackBar.open('Coś poszło nie tak. Spróbuj ponownie później.', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } + ); + } + + putData(url, body) { + this.setHeaders(); + this.httpClient.put(url, body, { headers: this.headers, observe: 'response' }) + .subscribe(data => { this.sendResponse(data); }, + error => { + this.snackBar.open('Coś poszło nie tak. Spróbuj ponownie później.', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } + ); + } + + downloadFile(id) { + this.setHeaders(); + return this.httpClient.get('/file/files/' + id, { + responseType: 'blob', + headers: this.headers + }); + } + + sendResponse(data) { + if (data.status === 200) { + this.snackBar.open('Operacja przebiegła pomyślnie!', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-success'] }); + this.router.navigate(['materials/list']); + } else { + this.snackBar.open('Coś poszło nie tak. Spróbuj ponownie później.', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } + } +} diff --git a/src/app/routing-state.service.spec.ts b/src/app/routing-state.service.spec.ts new file mode 100644 index 0000000..f8d32c3 --- /dev/null +++ b/src/app/routing-state.service.spec.ts @@ -0,0 +1,17 @@ +import { TestBed, inject } from '@angular/core/testing'; + +import { RoutingStateService } from './routing-state.service'; +import { RouterTestingModule } from '@angular/router/testing'; + +describe('RoutingStateService', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [RoutingStateService], + imports: [RouterTestingModule] + }); + }); + + it('should be created', inject([RoutingStateService], (service: RoutingStateService) => { + expect(service).toBeTruthy(); + })); +}); diff --git a/src/app/routing-state.service.ts b/src/app/routing-state.service.ts new file mode 100644 index 0000000..f3c87f5 --- /dev/null +++ b/src/app/routing-state.service.ts @@ -0,0 +1,28 @@ +import { Injectable } from '@angular/core'; +import { Router, NavigationEnd } from '@angular/router'; +import { filter } from 'rxjs/operators'; + +@Injectable() +export class RoutingStateService { + + private history = []; + + constructor(private router: Router) {} + + public loadRouting(): void { + this.router.events + .pipe(filter(event => event instanceof NavigationEnd)) + .subscribe(({urlAfterRedirects}: NavigationEnd) => { + this.history = [...this.history, urlAfterRedirects]; + }); + } + + public getHistory(): string[] { + return this.history; + } + + public getPreviousUrl(): string { + return this.history[this.history.length - 2] || '/home'; + } + +} diff --git a/src/app/shared/comments/comments.component.css b/src/app/shared/comments/comments.component.css new file mode 100644 index 0000000..97d4679 --- /dev/null +++ b/src/app/shared/comments/comments.component.css @@ -0,0 +1,45 @@ +.content{ + width: 100%; + position: fixed; + margin-top: 100px; + max-width: 100%; + overflow: auto; + height: calc(100vh - 154px); + min-height: calc(100vh - 154px); + +} + +.wrapper{ + overflow: auto; +} + +@media screen and (max-width: 800px) { + .content{ + margin-top: 170px; + height: calc(100vh - 200px); + min-height: calc(100vh - 200px); + } +} + +@media screen and (max-width: 352px) { + .content{ + margin-top: 170px; + height: calc(100vh - 250px); + min-height: calc(100vh - 250px); + } +} + +hr { + border: solid 1px; +} + +input { + width: 80%; + height: 200px; +} +.comment{ + border-radius: 25px; + border: 2px solid; + padding: 20px; + text-align: justify; +} \ No newline at end of file diff --git a/src/app/shared/comments/comments.component.html b/src/app/shared/comments/comments.component.html new file mode 100644 index 0000000..b67de90 --- /dev/null +++ b/src/app/shared/comments/comments.component.html @@ -0,0 +1,32 @@ +
+
+
+
+
+ + + +
+
+
+
+
+
+ Dodaj komentarz:
+ +
+ +
+
+
+
+ Kto: {{comment.username}}
+ Komentarz:
+ {{comment.text}}
+ +
+
+
+
+
\ No newline at end of file diff --git a/src/app/shared/comments/comments.component.spec.ts b/src/app/shared/comments/comments.component.spec.ts new file mode 100644 index 0000000..ce8959a --- /dev/null +++ b/src/app/shared/comments/comments.component.spec.ts @@ -0,0 +1,35 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CommentsComponent } from './comments.component'; +import { SharedService } from '../shared.service'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { RouterTestingModule } from '@angular/router/testing'; +import { HttpClientModule } from '@angular/common/http'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { FormsModule } from '@angular/forms'; +import { AuthenticationService } from '../../authentication.service'; + +describe('CommentsComponent', () => { + let component: CommentsComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ CommentsComponent ], + schemas: [NO_ERRORS_SCHEMA], + imports: [ RouterTestingModule, HttpClientModule, MatSnackBarModule, FormsModule], + providers: [SharedService, AuthenticationService] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CommentsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/shared/comments/comments.component.ts b/src/app/shared/comments/comments.component.ts new file mode 100644 index 0000000..197ac35 --- /dev/null +++ b/src/app/shared/comments/comments.component.ts @@ -0,0 +1,97 @@ +import { Component, OnInit, Input, OnDestroy, ChangeDetectorRef } from '@angular/core'; +import { SharedService } from '../shared.service'; +import { Subscription } from 'rxjs/Subscription'; +import { ActivatedRoute } from '@angular/router'; +import { MatSnackBar } from '@angular/material'; + +@Component({ + selector: 'app-comments', + templateUrl: './comments.component.html', + styleUrls: ['./comments.component.css'] +}) +export class CommentsComponent implements OnInit, OnDestroy { + + id: number; + currentUser; + comment: string; + comments = []; + display = false; + @Input() + what: string; + + commentsSubscription: Subscription; + deleteCommentSubscription: Subscription; + + constructor( + private sharedService: SharedService, + private route: ActivatedRoute, + private cd: ChangeDetectorRef, + public snackBar: MatSnackBar) { } + + + ngOnInit() { + this.id = this.route.snapshot.params.id; + this.currentUser = JSON.parse(localStorage.getItem('currentUser')); + this.getComments(); + console.log(this.comments); + } + + getComments() { + this.commentsSubscription = this.sharedService.getComments(this.id, this.what).subscribe(data => { + this.comments = data; + this.cd.markForCheck(); + }); + } + + toggle() { + this.display = !this.display; + } + + submitComment() { + if (this.comment && this.comment.trim().length > 0) { + const dataToSend = { + username: this.currentUser.username, + text: this.comment + }; + this.sharedService.sendComment(this.id, this.what, dataToSend).subscribe( + response => { + this.comment = ''; + this.getComments(); + this.snackBar.open('Dodano komentarz', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-success'] }); + }, + error => { + this.snackBar.open('Coś poszło nie tak. Spróbuj ponownie później.', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + }); + } else { + this.snackBar.open('Nie można dodać pustego komentarza', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } + + } + + deleteComment(comment) { + this.deleteCommentSubscription = this.sharedService.deleteComment(comment.id).subscribe( + response => { + this.getComments(); + this.snackBar.open('Usunięto komentarz', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-success'] }); + }, + error => { + this.snackBar.open('Coś poszło nie tak. Spróbuj ponownie później.', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + }); + } + + + ngOnDestroy() { + if (this.commentsSubscription) { + this.commentsSubscription.unsubscribe(); + } + if (this.deleteCommentSubscription) { + this.deleteCommentSubscription.unsubscribe(); + } + } + +} diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts new file mode 100644 index 0000000..aaa997d --- /dev/null +++ b/src/app/shared/shared.module.ts @@ -0,0 +1,22 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { CommentsComponent } from './comments/comments.component'; +import { BrowserModule } from '@angular/platform-browser'; +import { FormsModule } from '@angular/forms'; +import { HttpModule } from '@angular/http'; +import { HttpClientModule } from '@angular/common/http'; +import { SharedService } from './shared.service'; + +@NgModule({ + imports: [ + CommonModule, + BrowserModule, + FormsModule, + HttpModule, + HttpClientModule + ], + declarations: [CommentsComponent], + providers: [SharedService], + exports: [CommentsComponent] +}) +export class SharedModule { } diff --git a/src/app/shared/shared.service.spec.ts b/src/app/shared/shared.service.spec.ts new file mode 100644 index 0000000..42da0ee --- /dev/null +++ b/src/app/shared/shared.service.spec.ts @@ -0,0 +1,23 @@ +import { TestBed, inject } from '@angular/core/testing'; + +import { SharedService } from './shared.service'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { RouterTestingModule } from '@angular/router/testing'; +import { HttpClientModule } from '@angular/common/http'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { FormsModule } from '@angular/forms'; +import { AuthenticationService } from '../authentication.service'; + +describe('SharedService', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [SharedService, AuthenticationService], + schemas: [NO_ERRORS_SCHEMA], + imports: [ RouterTestingModule, HttpClientModule, MatSnackBarModule, FormsModule], + }); + }); + + it('should be created', inject([SharedService], (service: SharedService) => { + expect(service).toBeTruthy(); + })); +}); diff --git a/src/app/shared/shared.service.ts b/src/app/shared/shared.service.ts new file mode 100644 index 0000000..9c2f915 --- /dev/null +++ b/src/app/shared/shared.service.ts @@ -0,0 +1,55 @@ +import { Injectable } from '@angular/core'; +import { HttpClient, HttpHeaders } from '@angular/common/http'; +import { Router } from '@angular/router'; +import { AuthenticationService } from '../authentication.service'; +import { Observable } from 'rxjs/Observable'; +import { MatSnackBar } from '@angular/material'; + +@Injectable() +export class SharedService { + + constructor(private httpClient: HttpClient, private router: Router, private authenticationService: AuthenticationService, + public snackBar: MatSnackBar) + // tslint:disable-next-line:one-line + { + this.setHeaders(); + } + + private headers; + + setHeaders() { + if (localStorage.getItem('currentUser')) { + this.headers = new HttpHeaders({ + 'Content-Type': 'application/json', + 'Authorization': '' + this.authenticationService.getToken() + }); + } else { + this.headers = new HttpHeaders({ + 'Content-Type': 'application/json' + }); + } + } + + getComments(id, what): Observable { + this.setHeaders(); + return this.httpClient.get('groups/' + what + '/' + id + '/comments', {headers: this.headers}); + } + + sendComment(id, what, data) { + this.setHeaders(); + return this.httpClient.post('groups/' + what + '/' + id + '/comments', data , { + headers: this.headers, + observe: 'response', + responseType: 'text' + }); + } + + deleteComment(comment) { + this.setHeaders(); + return this.httpClient.delete('groups/comments/' + comment, { + headers: this.headers, + observe: 'response', + responseType: 'text' + }); + } +} diff --git a/src/app/tests/gaps-question/gaps-question.component.css b/src/app/tests/gaps-question/gaps-question.component.css new file mode 100644 index 0000000..438c0b0 --- /dev/null +++ b/src/app/tests/gaps-question/gaps-question.component.css @@ -0,0 +1,34 @@ +.block { + display: block; +} + +.container { + background-color: #22272a; + padding: 30px; + margin-top: 2rem; + margin-bottom: 1rem; + text-align: center; +} + +.wrapper-add { + width: 100%; + padding: 30px; + display: flex; + justify-content: center; + align-content: center; +} + +.wrapper { + width: 100%; + margin-bottom: 5rem; +} + +.align { + text-align: center; +} + +@media screen and (max-width: 800px) { + .mobile{ + display: none; + } +} diff --git a/src/app/tests/gaps-question/gaps-question.component.html b/src/app/tests/gaps-question/gaps-question.component.html new file mode 100644 index 0000000..9421f2e --- /dev/null +++ b/src/app/tests/gaps-question/gaps-question.component.html @@ -0,0 +1,91 @@ +
+
+
+

Uzupełnianie luk

+
+
+ + +
+

Wpisz tekst widoczny i tekst luki (luka może mieć więcej niż 1 dobrą odpowiedź - każdą odpowiedź oddziel średnikiem).

+
+
+ + + +
+ Akcja +
+ +
+
+ Przesuń +
+ + + --- +
+
+
+
+ +
+ Akcja +
+ +
+
+
+
+ +
+ Akcja +
+ +
+
+
+ + + +
+
+
Podgląd:
+ + {{ answer.content }} + ____________ +
+
+
+
+
+
+ + + +
+
+
+
diff --git a/src/app/tests/gaps-question/gaps-question.component.spec.ts b/src/app/tests/gaps-question/gaps-question.component.spec.ts new file mode 100644 index 0000000..0d5ea15 --- /dev/null +++ b/src/app/tests/gaps-question/gaps-question.component.spec.ts @@ -0,0 +1,35 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { GapsQuestionComponent } from './gaps-question.component'; +import { TestsService } from '../tests.service'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { RouterTestingModule } from '@angular/router/testing'; +import { HttpClientModule } from '@angular/common/http'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { FormsModule } from '@angular/forms'; +import { AuthenticationService } from '../../authentication.service'; + +describe('GapsQuestionComponent', () => { + let component: GapsQuestionComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ GapsQuestionComponent ], + schemas: [NO_ERRORS_SCHEMA], + imports: [ RouterTestingModule, HttpClientModule, MatSnackBarModule, FormsModule], + providers: [TestsService, AuthenticationService] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(GapsQuestionComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/tests/gaps-question/gaps-question.component.ts b/src/app/tests/gaps-question/gaps-question.component.ts new file mode 100644 index 0000000..92788d5 --- /dev/null +++ b/src/app/tests/gaps-question/gaps-question.component.ts @@ -0,0 +1,220 @@ +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; +import { MatSnackBar } from '@angular/material/snack-bar'; + +@Component({ + selector: 'app-gaps-question', + templateUrl: './gaps-question.component.html', + styleUrls: ['./gaps-question.component.css'] +}) +export class GapsQuestionComponent implements OnInit { + + @Input() content: Object = {}; + @Input() edit: Boolean; + + visibleText: Boolean = false; + gap: Boolean = false; + + noGapText = ''; + gapText = ''; + + answersCorrect: Array = []; + answers: Array = []; + question: String = 'Uzupełnij luki w tekście:'; + points: Number = 1; + id: Number = null; + + @Output() add: EventEmitter = new EventEmitter(); + @Output() editing: EventEmitter = new EventEmitter(); + + constructor(public snackBar: MatSnackBar) { } + + ngOnInit() { + if (this.edit) { + this.content['edit'] = true; + this.question = this.content['content']['question']; + const answ = this.content['content']['answers']; + this.id = this.content['content']['id']; + this.addToAnswersCorrect(answ); + this.points = this.content['content']['points']; + } else { + this.content = {}; + this.content['content'] = { + id: null, + type: 'gaps', + question: 'Uzupełnij luki w tekście.', + answers: [], + points: 1 + }; + this.content['edit'] = false; + } + } + + addToAnswersCorrect(answ): void { + for (let i = 0; i < answ.length; i++) { + if (answ[i]['is_gap']) { + this.answersCorrect.push({ + id: answ[i]['id'], + content: answ[i]['content'].join(';'), + is_gap: true + }); + } else { + this.answersCorrect.push({ + id: answ[i]['id'], + content: answ[i]['content'][0], + is_gap: false + }); + } + } + } + + showVisibleTextInput(): void { + this.visibleText = true; + this.gap = false; + } + + showGapInput(): void { + this.visibleText = false; + this.gap = true; + } + + addToVisibleText(): void { + if ((this.noGapText === undefined) || (this.noGapText.trim().length === 0)) { + this.snackBar.open('Tekst widoczny nie może być pusty!', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } else { + this.answersCorrect.push({ + id: null, + content: this.noGapText, + is_gap: false + }); + this.visibleText = false; + this.noGapText = ''; + } + } + + addToGaps(): void { + if ((this.gapText === undefined) || (this.gapText.trim().length === 0)) { + this.snackBar.open('Tekst luki nie może być pusty!', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } else { + this.answersCorrect.push({ + id: null, + content: this.gapText, + is_gap: true + }); + this.gapText = ''; + this.gap = false; + } + } + + addNewLine(): void { + this.visibleText = false; + this.gap = false; + this.answersCorrect.push({ + id: null, + content: '\n', + is_gap: false + }); + } + + removefromAnswers(index: number): void { + this.answersCorrect.splice(index, 1); + } + + moveUp(nr: number): void { + const temp = this.answersCorrect[nr - 2]; + this.answersCorrect[nr - 2] = this.answersCorrect[nr - 1]; + this.answersCorrect[nr - 1] = temp; + } + + moveDown(nr: number): void { + const temp = this.answersCorrect[nr]; + this.answersCorrect[nr] = this.answersCorrect[nr - 1]; + this.answersCorrect[nr - 1] = temp; + } + + addToAnswers(): void { + for (let i = 0; i < this.answersCorrect.length; i++) { + if (this.answersCorrect[i]['is_gap']) { + this.answers.push({ + id: this.answersCorrect[i]['id'], + content: this.answersCorrect[i]['content'].split(';'), + is_gap: true + }); + } else { + this.answers.push({ + id: this.answersCorrect[i]['id'], + content: [this.answersCorrect[i]['content']], + is_gap: false + }); + } + } + } + + addTable(): void { + if ((this.question === undefined) || (this.question.trim().length === 0)) { + this.snackBar.open('Pytanie nie może być puste!', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } else { + let hasGap = false; + for (let i = 0; i < this.answersCorrect.length; i++) { + if ((this.answersCorrect[i]['is_gap']) && (this.answersCorrect[i]['content'].trim().length !== 0)) { + hasGap = true; + break; + } + } + if (!hasGap) { + this.snackBar.open('Zadanie musi zawierać co najmniej 1 niepustą lukę!', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } else { + let hasVisibleText = false; + for (let i = 0; i < this.answersCorrect.length; i++) { + if (!this.answersCorrect[i]['is_gap']) { + if ((this.answersCorrect[i]['content'][0] !== '\n') && + (this.answersCorrect[i]['content'][0].trim().length !== 0)) { + hasVisibleText = true; + break; + } + } + } + if (!hasVisibleText) { + this.snackBar.open('Zadanie musi zawierać co najmniej 1 niepusty tekst widoczny!', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } else { + this.addToAnswers(); + this.content['content']['question'] = this.question; + this.content['content']['answers'] = this.answers; + this.content['content']['points'] = this.points; + this.content['content']['id'] = this.id; + if (this.edit) { + this.editing.emit(this.content); + } else { + this.add.emit(this.content); + } + this.clear(); + } + } + } + } + + empty(): void { + if (this.edit) { + this.editing.emit({}); + } else { + this.add.emit({}); + } + this.clear(); + } + + clear(): void { + this.content = {}; + this.edit = false; + this.question = ''; + this.answers = []; + this.noGapText = ''; + this.gapText = ''; + this.visibleText = false; + this.gap = false; + } + +} diff --git a/src/app/tests/multiple-choice-question/multiple-choice-question.component.css b/src/app/tests/multiple-choice-question/multiple-choice-question.component.css new file mode 100644 index 0000000..2ea51f8 --- /dev/null +++ b/src/app/tests/multiple-choice-question/multiple-choice-question.component.css @@ -0,0 +1,28 @@ +.block { + display: block; +} + +.container { + background-color: #22272a; + padding: 30px; + margin-top: 2rem; + margin-bottom: 1rem; + text-align: center; +} + +.wrapper-add { + width: 100%; + padding: 30px; + display: flex; + justify-content: center; + align-content: center; +} + +.wrapper { + width: 100%; + margin-bottom: 5rem; +} + +.align { + text-align: center; +} \ No newline at end of file diff --git a/src/app/tests/multiple-choice-question/multiple-choice-question.component.html b/src/app/tests/multiple-choice-question/multiple-choice-question.component.html new file mode 100644 index 0000000..ea1bc81 --- /dev/null +++ b/src/app/tests/multiple-choice-question/multiple-choice-question.component.html @@ -0,0 +1,59 @@ +
+
+
+

Pytanie wielokrotnego wyboru

+
+
+ + +
+

Wpisz możliwe odpowiedzi i zaznacz prawidłową:

+
+
+ + +
+ Akcja +
+ +
+
+
+
+ + +
+ Akcja +
+ +
+
+
+
+
+ + + +
+
+
+
+ \ No newline at end of file diff --git a/src/app/tests/multiple-choice-question/multiple-choice-question.component.spec.ts b/src/app/tests/multiple-choice-question/multiple-choice-question.component.spec.ts new file mode 100644 index 0000000..4cea499 --- /dev/null +++ b/src/app/tests/multiple-choice-question/multiple-choice-question.component.spec.ts @@ -0,0 +1,35 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MultipleChoiceQuestionComponent } from './multiple-choice-question.component'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { RouterTestingModule } from '@angular/router/testing'; +import { HttpClientModule } from '@angular/common/http'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { FormsModule } from '@angular/forms'; +import { TestsService } from '../tests.service'; +import { AuthenticationService } from '../../authentication.service'; + +describe('MultipleChoiceQuestionComponent', () => { + let component: MultipleChoiceQuestionComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ MultipleChoiceQuestionComponent ], + schemas: [NO_ERRORS_SCHEMA], + imports: [ RouterTestingModule, HttpClientModule, MatSnackBarModule, FormsModule], + providers: [TestsService, AuthenticationService] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(MultipleChoiceQuestionComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/tests/multiple-choice-question/multiple-choice-question.component.ts b/src/app/tests/multiple-choice-question/multiple-choice-question.component.ts new file mode 100644 index 0000000..9ed5c11 --- /dev/null +++ b/src/app/tests/multiple-choice-question/multiple-choice-question.component.ts @@ -0,0 +1,177 @@ +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; +import { MatSnackBar } from '@angular/material/snack-bar'; + +@Component({ + selector: 'app-multiple-choice-question', + templateUrl: './multiple-choice-question.component.html', + styleUrls: ['./multiple-choice-question.component.css'] +}) +export class MultipleChoiceQuestionComponent implements OnInit { + + @Input() content: Object = {}; + @Input() edit: Boolean; + + isChecked: Boolean = false; + answers: Array = []; + answersCorrect: Array = []; + newAttribute: any = { + id: null, + content: '', + is_good: false + }; + question: String = ''; + points: Number = 1; + id: Number = null; + + @Output() private add: EventEmitter = new EventEmitter(); + @Output() private editing: EventEmitter = new EventEmitter(); + + constructor(public snackBar: MatSnackBar) { } + + ngOnInit() { + if (this.edit) { + this.content['edit'] = true; + this.question = this.content['content']['question']; + this.answers = []; + const answ = this.content['content']['answers']; + this.id = this.content['content']['id']; + for (let i = 0; i < answ.length; i++) { + this.answersCorrect.push({ + id: answ[i]['id'], + content: answ[i]['content'], + is_good: answ[i]['is_good'] + }); + } + this.points = this.content['content']['points']; + } else { + this.content = {}; + this.content['content'] = { + id: this.id, + type: 'multiple-choice', + question: '', + answers: [], + points: 1 + }; + this.content['edit'] = false; + } + } + + addFieldValue(): void { + const undefinedAttr = (this.newAttribute['content'] === undefined); + if (undefinedAttr) { + this.snackBar.open('Nie można dodać pustej odpowiedzi!', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } else { + const length = (this.newAttribute['content'].trim().length === 0); + if (length) { + this.snackBar.open('Nie można dodać pustej odpowiedzi!', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } else { + this.newAttribute['is_good'] = this.isChecked; + + let exists = false; + for (let i = 0; i < this.answersCorrect.length; i++) { + if (this.newAttribute['content'] === this.answersCorrect[i]['content']) { + exists = true; + this.snackBar.open('Odpowiedź już istnieje!', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + break; + } + } + + if (!exists) { + this.answersCorrect.push(this.newAttribute); + this.newAttribute = { + id: null, + content: '', + is_good: this.isChecked + }; + } + } + } + } + + deleteFieldValue(index): void { + this.answersCorrect.splice(index, 1); + } + + changeCheckbox(i: number): void { + this.answersCorrect[i]['is_good'] = !this.answersCorrect[i]['is_good']; + } + + changeCheckbox2(event): void { + if (event.target.checked) { + this.isChecked = true; + } else { + this.isChecked = false; + } + } + + addTable(): void { + if ((this.question === undefined) || (this.question.trim().length === 0)) { + this.snackBar.open('Pytanie nie może być puste!', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } else { + if (this.answersCorrect.length < 2) { + this.snackBar.open('Pytanie musi zawierać co najmniej 2 odpowiedzi!', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } else { + let checked = false; + for (let i = 0; i < this.answersCorrect.length; i++) { + if (this.answersCorrect[i]['is_good']) { + checked = true; + break; + } + } + if (!checked) { + this.snackBar.open('Zaznacz prawidłową odpowiedź!', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } else { + let empty = false; + for (let i = 0; i < this.answersCorrect.length; i++) { + if (this.answersCorrect[i]['content'].trim().length === 0) { + empty = true; + break; + } + } + if (empty) { + this.snackBar.open('Żadna z odpowiedzi nie może być pusta!', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } else { + this.content['content']['question'] = this.question; + this.answers = this.answersCorrect; + this.content['content']['answers'] = this.answers; + this.content['content']['points'] = this.points; + this.content['content']['id'] = this.id; + if (this.edit) { + this.editing.emit(this.content); + } else { + this.add.emit(this.content); + } + this.clear(); + } + } + } + } + } + + empty(): void { + if (this.edit) { + this.editing.emit({}); + } else { + this.add.emit({}); + } + this.clear(); + } + + clear(): void { + this.content = {}; + this.edit = false; + this.isChecked = false; + this.question = ''; + this.answers = []; + this.answersCorrect = []; + this.newAttribute = {}; + } + +} diff --git a/src/app/tests/pairs-question/pairs-question.component.css b/src/app/tests/pairs-question/pairs-question.component.css new file mode 100644 index 0000000..2ea51f8 --- /dev/null +++ b/src/app/tests/pairs-question/pairs-question.component.css @@ -0,0 +1,28 @@ +.block { + display: block; +} + +.container { + background-color: #22272a; + padding: 30px; + margin-top: 2rem; + margin-bottom: 1rem; + text-align: center; +} + +.wrapper-add { + width: 100%; + padding: 30px; + display: flex; + justify-content: center; + align-content: center; +} + +.wrapper { + width: 100%; + margin-bottom: 5rem; +} + +.align { + text-align: center; +} \ No newline at end of file diff --git a/src/app/tests/pairs-question/pairs-question.component.html b/src/app/tests/pairs-question/pairs-question.component.html new file mode 100644 index 0000000..ad76b9b --- /dev/null +++ b/src/app/tests/pairs-question/pairs-question.component.html @@ -0,0 +1,57 @@ +
+
+
+

Łączenie w pary

+
+
+ + +
+

Wpisz wszystkie dopasowania:

+
+
+ + +
+ Akcja +
+ +
+
+
+
+ + +
+ Akcja +
+ +
+
+
+
+
+ + + +
+
+
+
diff --git a/src/app/tests/pairs-question/pairs-question.component.spec.ts b/src/app/tests/pairs-question/pairs-question.component.spec.ts new file mode 100644 index 0000000..503ea36 --- /dev/null +++ b/src/app/tests/pairs-question/pairs-question.component.spec.ts @@ -0,0 +1,35 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PairsQuestionComponent } from './pairs-question.component'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { RouterTestingModule } from '@angular/router/testing'; +import { HttpClientModule } from '@angular/common/http'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { FormsModule } from '@angular/forms'; +import { TestsService } from '../tests.service'; +import { AuthenticationService } from '../../authentication.service'; + +describe('PairsQuestionComponent', () => { + let component: PairsQuestionComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ PairsQuestionComponent ], + schemas: [NO_ERRORS_SCHEMA], + imports: [ RouterTestingModule, HttpClientModule, MatSnackBarModule, FormsModule], + providers: [TestsService, AuthenticationService] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(PairsQuestionComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/tests/pairs-question/pairs-question.component.ts b/src/app/tests/pairs-question/pairs-question.component.ts new file mode 100644 index 0000000..a4f1eec --- /dev/null +++ b/src/app/tests/pairs-question/pairs-question.component.ts @@ -0,0 +1,169 @@ +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; +import { MatSnackBar } from '@angular/material/snack-bar'; + +@Component({ + selector: 'app-pairs-question', + templateUrl: './pairs-question.component.html', + styleUrls: ['./pairs-question.component.css'] +}) +export class PairsQuestionComponent implements OnInit { + + @Input() content: Object = {}; + @Input() edit: Boolean; + + answers: Array = []; + answersCorrect: Array = []; + newAttribute: any = { + id: null, + first: '', + second: '' + }; + question: String = 'Połącz w pary:'; + points: Number = 1; + id: Number = null; + + @Output() add: EventEmitter = new EventEmitter(); + @Output() editing: EventEmitter = new EventEmitter(); + + constructor(public snackBar: MatSnackBar) { } + + ngOnInit() { + if (this.edit) { + this.answers = []; + this.content['edit'] = true; + this.question = this.content['content']['question']; + this.id = this.content['content']['id']; + const answ = this.content['content']['answers']; + for (let i = 0; i < answ.length; i++) { + this.answersCorrect.push({ + id: answ[i]['id'], + first: answ[i]['first'], + second: answ[i]['second'] + }); + } + this.points = this.content['content']['points']; + } else { + this.content = {}; + this.content['content'] = { + id: null, + type: 'pairs', + question: 'Połącz w pary.', + answers: [], + points: 1 + }; + this.content['edit'] = false; + } + } + + addFieldValue(): void { + const undefinedAttr = ((this.newAttribute['first'] === undefined) || (this.newAttribute['second'] === undefined)); + if (undefinedAttr) { + this.snackBar.open('Żaden z elementów dopasowania nie może być pusty!', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } else { + const length = ((this.newAttribute['first'].trim().length === 0) || (this.newAttribute['second'].trim().length === 0)); + if (length) { + this.snackBar.open('Żaden z elementów dopasowania nie może być pusty!', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } else { + let exists = false; + for (let i = 0; i < this.answersCorrect.length; i++) { + if ((this.newAttribute['first'] === this.answersCorrect[i]['first']) || + (this.newAttribute['second'] === this.answersCorrect[i]['second'])) { + exists = true; + this.snackBar.open('Elementy dopasowania nie mogą się powtarzać!', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + break; + } + } + if (!exists) { + this.answersCorrect.push(this.newAttribute); + this.newAttribute = { + id: null, + first: '', + second: '' + }; + } + } + } + } + + deleteFieldValue(index): void { + this.answersCorrect.splice(index, 1); + } + + addTable(): void { + if ((this.question === undefined) || (this.question.trim().length === 0)) { + this.snackBar.open('Pytanie nie może być puste!', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } else { + if (this.answersCorrect.length < 2) { + this.snackBar.open('Pytanie musi zawierać co najmniej 2 odpowiedzi!', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } else { + let empty = false; + for (let i = 0; i < this.answersCorrect.length; i++) { + if ((this.answersCorrect[i]['first'].trim().length === 0) || + (this.answersCorrect[i]['second'].trim().length === 0)) { + empty = true; + break; + } + } + if (empty) { + this.snackBar.open('Żadne pole dopasowania nie może być puste!', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } else { + let exists = false; + for (let i = 0; i < this.answersCorrect.length; i++) { + for (let j = 0; j < this.answersCorrect.length; j++) { + if (i !== j) { + if ((this.answersCorrect[i]['first'] === this.answersCorrect[j]['first']) || + (this.answersCorrect[i]['second'] === this.answersCorrect[j]['second'])) { + exists = true; + this.snackBar.open('Elementy dopasowania nie mogą się powtarzać!', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + break; + } + } + } + if (exists) { + break; + } + } + if (!exists) { + this.content['content']['question'] = this.question; + this.answers = this.answersCorrect; + this.content['content']['answers'] = this.answers; + this.content['content']['points'] = this.points; + this.content['content']['id'] = this.id; + if (this.edit) { + this.editing.emit(this.content); + } else { + this.add.emit(this.content); + } + this.clear(); + } + } + } + } + } + + empty(): void { + if (this.edit) { + this.editing.emit({}); + } else { + this.add.emit({}); + } + this.clear(); + } + + clear(): void { + this.content = {}; + this.edit = false; + this.question = ''; + this.answers = []; + this.answersCorrect = []; + this.newAttribute = {}; + } + +} diff --git a/src/app/tests/puzzle-question/puzzle-question.component.css b/src/app/tests/puzzle-question/puzzle-question.component.css new file mode 100644 index 0000000..438c0b0 --- /dev/null +++ b/src/app/tests/puzzle-question/puzzle-question.component.css @@ -0,0 +1,34 @@ +.block { + display: block; +} + +.container { + background-color: #22272a; + padding: 30px; + margin-top: 2rem; + margin-bottom: 1rem; + text-align: center; +} + +.wrapper-add { + width: 100%; + padding: 30px; + display: flex; + justify-content: center; + align-content: center; +} + +.wrapper { + width: 100%; + margin-bottom: 5rem; +} + +.align { + text-align: center; +} + +@media screen and (max-width: 800px) { + .mobile{ + display: none; + } +} diff --git a/src/app/tests/puzzle-question/puzzle-question.component.html b/src/app/tests/puzzle-question/puzzle-question.component.html new file mode 100644 index 0000000..eab16ba --- /dev/null +++ b/src/app/tests/puzzle-question/puzzle-question.component.html @@ -0,0 +1,58 @@ +
+
+
+

Rozsypanka wyrazowa

+
+
+ + +
+

Wpisz kolejno wszystkie elementy rozsypanki:

+
+
+ +
+ Akcja +
+ +
+
+ Przesuń +
+ + + --- +
+
+
+
+ +
+ Akcja +
+ +
+
+
+
+
+ + + +
+
+
+
+ \ No newline at end of file diff --git a/src/app/tests/puzzle-question/puzzle-question.component.spec.ts b/src/app/tests/puzzle-question/puzzle-question.component.spec.ts new file mode 100644 index 0000000..b988650 --- /dev/null +++ b/src/app/tests/puzzle-question/puzzle-question.component.spec.ts @@ -0,0 +1,35 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PuzzleQuestionComponent } from './puzzle-question.component'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { RouterTestingModule } from '@angular/router/testing'; +import { HttpClientModule } from '@angular/common/http'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { FormsModule } from '@angular/forms'; +import { TestsService } from '../tests.service'; +import { AuthenticationService } from '../../authentication.service'; + +describe('PuzzleQuestionComponent', () => { + let component: PuzzleQuestionComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ PuzzleQuestionComponent ], + schemas: [NO_ERRORS_SCHEMA], + imports: [ RouterTestingModule, HttpClientModule, MatSnackBarModule, FormsModule], + providers: [TestsService, AuthenticationService] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(PuzzleQuestionComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/tests/puzzle-question/puzzle-question.component.ts b/src/app/tests/puzzle-question/puzzle-question.component.ts new file mode 100644 index 0000000..bdb5737 --- /dev/null +++ b/src/app/tests/puzzle-question/puzzle-question.component.ts @@ -0,0 +1,165 @@ +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; +import { MatSnackBar } from '@angular/material/snack-bar'; + +@Component({ + selector: 'app-puzzle-question', + templateUrl: './puzzle-question.component.html', + styleUrls: ['./puzzle-question.component.css'] +}) +export class PuzzleQuestionComponent implements OnInit { + + @Input() content: Object = {}; + @Input() edit: Boolean; + + answers: Array = []; + answersCorrect: Array = []; + answersCorrect2: Array = []; + newAttribute: Object = { + correct: '' + }; + question: String = 'Ułóż elementy w prawidłowej kolejności:'; + points: Number = 1; + id: Number = null; + idAnsw: Number = null; + + @Output() add: EventEmitter = new EventEmitter(); + @Output() editing: EventEmitter = new EventEmitter(); + + constructor(public snackBar: MatSnackBar) { } + + ngOnInit() { + if (this.edit) { + this.content['edit'] = true; + this.question = this.content['content']['question']; + this.id = this.content['content']['id']; + this.answers = []; + const answ = this.content['content']['answers'][0]['correct']; + this.idAnsw = this.content['content']['answers'][0]['id']; + for (let i = 0; i < answ.length; i++) { + this.answersCorrect.push({ + correct: answ[i] + }); + } + this.points = this.content['content']['points']; + } else { + this.content = {}; + this.content['content'] = { + type: 'puzzle', + question: 'Ułóż elementy w prawidłowej kolejności.', + answers: [], + points: 1 + }; + this.content['edit'] = false; + } + } + + addFieldValue(): void { + const undefinedAttr = (this.newAttribute['correct'] === undefined); + if (undefinedAttr) { + this.snackBar.open('Żaden element rozsypanki nie może być pusty!', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } else { + const length = (this.newAttribute['correct'].trim().length === 0); + if (length) { + this.snackBar.open('Żaden element rozsypanki nie może być pusty!', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } else { + const exists = false; + /*for (let i = 0; i < this.answersCorrect.length; i++) { + if (this.newAttribute['correct'] === this.answersCorrect[i]['correct']) { + exists = true; + this.snackBar.open('Elementy rozsypanki nie mogą się powtarzać!', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + break; + } + }*/ + + if (!exists) { + this.answersCorrect.push(this.newAttribute); + this.newAttribute = {}; + } + } + } + } + + deleteFieldValue(index): void { + this.answersCorrect.splice(index, 1); + } + + addToAnswers(): void { + for (let i = 0; i < this.answersCorrect.length; i++) { + this.answersCorrect2.push(this.answersCorrect[i]['correct']); + } + } + + addTable(): void { + if ((this.question === undefined) || (this.question.trim().length === 0)) { + this.snackBar.open('Pytanie nie może być puste!', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } else { + if (this.answersCorrect.length < 2) { + this.snackBar.open('Rozsypanka musi zawierać co najmniej 2 elementy!', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } else { + let empty = false; + for (let i = 0; i < this.answersCorrect.length; i++) { + if (this.answersCorrect[i]['correct'].trim().length === 0) { + empty = true; + break; + } + } + if (empty) { + this.snackBar.open('Żaden element rozsypanki nie może być pusty!', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } else { + this.addToAnswers(); + this.answers.push({ + id: this.idAnsw, + correct: this.answersCorrect2 + }); + this.content['content']['question'] = this.question; + this.content['content']['answers'] = this.answers; + this.content['content']['points'] = this.points; + this.content['content']['id'] = this.id; + if (this.edit) { + this.editing.emit(this.content); + } else { + this.add.emit(this.content); + } + this.clear(); + } + } + } + } + + moveUp(nr: number): void { + const temp = this.answersCorrect[nr - 2]; + this.answersCorrect[nr - 2] = this.answersCorrect[nr - 1]; + this.answersCorrect[nr - 1] = temp; + } + + moveDown(nr: number): void { + const temp = this.answersCorrect[nr]; + this.answersCorrect[nr] = this.answersCorrect[nr - 1]; + this.answersCorrect[nr - 1] = temp; + } + + empty(): void { + if (this.edit) { + this.editing.emit({}); + } else { + this.add.emit({}); + } + this.clear(); + } + + clear(): void { + this.content = {}; + this.edit = false; + this.question = ''; + this.answers = []; + this.answersCorrect = []; + this.newAttribute = {}; + } + +} diff --git a/src/app/tests/single-choice-question/single-choice-question.component.css b/src/app/tests/single-choice-question/single-choice-question.component.css new file mode 100644 index 0000000..2ea51f8 --- /dev/null +++ b/src/app/tests/single-choice-question/single-choice-question.component.css @@ -0,0 +1,28 @@ +.block { + display: block; +} + +.container { + background-color: #22272a; + padding: 30px; + margin-top: 2rem; + margin-bottom: 1rem; + text-align: center; +} + +.wrapper-add { + width: 100%; + padding: 30px; + display: flex; + justify-content: center; + align-content: center; +} + +.wrapper { + width: 100%; + margin-bottom: 5rem; +} + +.align { + text-align: center; +} \ No newline at end of file diff --git a/src/app/tests/single-choice-question/single-choice-question.component.html b/src/app/tests/single-choice-question/single-choice-question.component.html new file mode 100644 index 0000000..9a9b527 --- /dev/null +++ b/src/app/tests/single-choice-question/single-choice-question.component.html @@ -0,0 +1,58 @@ +
+
+
+

Pytanie jednokrotnego wyboru

+
+
+ + +
+

Wpisz możliwe odpowiedzi i zaznacz prawidłową:

+
+
+ + +
+ Akcja +
+ +
+
+
+
+ + +
+ Akcja +
+ +
+
+
+
+
+ + + +
+
+
+
diff --git a/src/app/tests/single-choice-question/single-choice-question.component.spec.ts b/src/app/tests/single-choice-question/single-choice-question.component.spec.ts new file mode 100644 index 0000000..677101b --- /dev/null +++ b/src/app/tests/single-choice-question/single-choice-question.component.spec.ts @@ -0,0 +1,35 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SingleChoiceQuestionComponent } from './single-choice-question.component'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { RouterTestingModule } from '@angular/router/testing'; +import { HttpClientModule } from '@angular/common/http'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { FormsModule } from '@angular/forms'; +import { TestsService } from '../tests.service'; +import { AuthenticationService } from '../../authentication.service'; + +describe('SingleChoiceQuestionComponent', () => { + let component: SingleChoiceQuestionComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ SingleChoiceQuestionComponent ], + schemas: [NO_ERRORS_SCHEMA], + imports: [ RouterTestingModule, HttpClientModule, MatSnackBarModule, FormsModule], + providers: [TestsService, AuthenticationService] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SingleChoiceQuestionComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/tests/single-choice-question/single-choice-question.component.ts b/src/app/tests/single-choice-question/single-choice-question.component.ts new file mode 100644 index 0000000..f1c081e --- /dev/null +++ b/src/app/tests/single-choice-question/single-choice-question.component.ts @@ -0,0 +1,192 @@ +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; +import { MatSnackBar } from '@angular/material/snack-bar'; + +@Component({ + selector: 'app-single-choice-question', + templateUrl: './single-choice-question.component.html', + styleUrls: ['./single-choice-question.component.css'] +}) +export class SingleChoiceQuestionComponent implements OnInit { + + @Input() content: Object = {}; + @Input() edit: Boolean; + + isChecked: Boolean = false; + answers: Array = []; + answersCorrect: Array = []; + newAttribute: any = { + id: null, + content: '', + is_good: false + }; + question: String = ''; + points: Number = 1; + id: Number = null; + + @Output() add: EventEmitter = new EventEmitter(); + @Output() editing: EventEmitter = new EventEmitter(); + + constructor(public snackBar: MatSnackBar) { } + + ngOnInit() { + if (this.edit) { + this.content['edit'] = true; + this.question = this.content['content']['question']; + this.answers = []; + const answ = this.content['content']['answers']; + this.id = this.content['content']['id']; + for (let i = 0; i < answ.length; i++) { + this.answersCorrect.push({ + id: answ[i]['id'], + content: answ[i]['content'], + is_good: answ[i]['is_good'] + }); + } + this.points = this.content['content']['points']; + } else { + this.content = {}; + this.content['content'] = { + id: this.id, + type: 'single-choice', + question: '', + answers: [], + points: 1 + }; + this.content['edit'] = false; + } + } + + addFieldValue(): void { + const undefinedAttr = (this.newAttribute['content'] === undefined); + if (undefinedAttr) { + this.snackBar.open('Nie można dodać pustej odpowiedzi!', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } else { + const length = (this.newAttribute['content'].trim().length === 0); + if (length) { + this.snackBar.open('Nie można dodać pustej odpowiedzi!', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } else { + this.newAttribute['is_good'] = this.isChecked; + + let exists = false; + for (let i = 0; i < this.answersCorrect.length; i++) { + if (this.newAttribute['content'] === this.answersCorrect[i]['content']) { + exists = true; + this.snackBar.open('Odpowiedź już istnieje!', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + break; + } + } + + if (!exists) { + this.answersCorrect.push(this.newAttribute); + this.newAttribute = { + id: null, + content: '', + is_good: this.isChecked + }; + } + } + } + } + + deleteFieldValue(index): void { + this.answersCorrect.splice(index, 1); + } + + changeCheckbox(i: number): void { + this.answersCorrect[i]['is_good'] = !this.answersCorrect[i]['is_good']; + } + + changeCheckbox2(event): void { + if (event.target.checked) { + this.isChecked = true; + } else { + this.isChecked = false; + } + } + + addTable(): void { + if ((this.question === undefined) || (this.question.trim().length === 0)) { + this.snackBar.open('Pytanie nie może być puste!', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } else { + if (this.answersCorrect.length < 2) { + this.snackBar.open('Pytanie musi zawierać co najmniej 2 odpowiedzi!', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } else { + let checked = false; + for (let i = 0; i < this.answersCorrect.length; i++) { + if (this.answersCorrect[i]['is_good']) { + checked = true; + break; + } + } + if (!checked) { + this.snackBar.open('Zaznacz prawidłową odpowiedź!', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } else { + let t = true; + let times = 0; + for (let i = 0; i < this.answersCorrect.length; i++) { + if (this.answersCorrect[i]['is_good']) { + times++; + } + if (times > 1) { + t = false; + this.snackBar.open('Tylko 1 odpowiedź może być prawidłowa!', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + break; + } + } + if (t) { + let empty = false; + for (let i = 0; i < this.answersCorrect.length; i++) { + if (this.answersCorrect[i]['content'].trim().length === 0) { + empty = true; + break; + } + } + if (empty) { + this.snackBar.open('Żadna z odpowiedzi nie może być pusta!', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } else { + this.content['content']['question'] = this.question; + this.content['content']['id'] = this.id; + this.answers = this.answersCorrect; + this.content['content']['answers'] = this.answers; + this.content['content']['points'] = this.points; + if (this.edit) { + this.editing.emit(this.content); + } else { + this.add.emit(this.content); + } + this.clear(); + } + } + } + } + } + } + + empty(): void { + if (this.edit) { + this.editing.emit({}); + } else { + this.add.emit({}); + } + this.clear(); + } + + clear(): void { + this.content = {}; + this.edit = false; + this.isChecked = false; + this.question = ''; + this.answers = []; + this.answersCorrect = []; + this.newAttribute = {}; + } + +} diff --git a/src/app/tests/test-details/question-view/gaps/gaps.component.css b/src/app/tests/test-details/question-view/gaps/gaps.component.css new file mode 100644 index 0000000..da60678 --- /dev/null +++ b/src/app/tests/test-details/question-view/gaps/gaps.component.css @@ -0,0 +1,11 @@ +.answers{ + display: flex; + flex-direction: row; + align-items: center; + justify-content: flex-start; + flex-wrap: wrap; +} + +.answers > div{ + margin: 5px; +} \ No newline at end of file diff --git a/src/app/tests/test-details/question-view/gaps/gaps.component.html b/src/app/tests/test-details/question-view/gaps/gaps.component.html new file mode 100644 index 0000000..33859a1 --- /dev/null +++ b/src/app/tests/test-details/question-view/gaps/gaps.component.html @@ -0,0 +1,13 @@ +
+
+ {{question.question}} ({{question.points}}pkt.) +
+
+ {{item.content}} + +
+
+
+ + +
\ No newline at end of file diff --git a/src/app/tests/test-details/question-view/gaps/gaps.component.spec.ts b/src/app/tests/test-details/question-view/gaps/gaps.component.spec.ts new file mode 100644 index 0000000..db276ed --- /dev/null +++ b/src/app/tests/test-details/question-view/gaps/gaps.component.spec.ts @@ -0,0 +1,34 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { GapsComponent } from './gaps.component'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { RouterTestingModule } from '@angular/router/testing'; +import { HttpClientModule } from '@angular/common/http'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { FormsModule } from '@angular/forms'; +import { TestsService } from '../../../tests.service'; +import { AuthenticationService } from '../../../../authentication.service'; + +describe('GapsComponent', () => { + let component: GapsComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ GapsComponent ], + schemas: [NO_ERRORS_SCHEMA], + imports: [ RouterTestingModule, HttpClientModule, MatSnackBarModule, FormsModule], + providers: [TestsService, AuthenticationService] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(GapsComponent); + component = fixture.componentInstance; + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/tests/test-details/question-view/gaps/gaps.component.ts b/src/app/tests/test-details/question-view/gaps/gaps.component.ts new file mode 100644 index 0000000..c4d30ed --- /dev/null +++ b/src/app/tests/test-details/question-view/gaps/gaps.component.ts @@ -0,0 +1,46 @@ +import { Component, OnInit, Input, Output, EventEmitter, OnDestroy } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { TestsService } from '../../../tests.service'; +import * as $ from 'jquery'; +import { ISubscription } from 'rxjs/Subscription'; + +@Component({ + selector: 'app-gaps', + templateUrl: './gaps.component.html', + styleUrls: ['./gaps.component.css'] +}) +export class GapsComponent implements OnInit, OnDestroy { + @Input() + question; + @Output() emitNextQuestionRequest = new EventEmitter(); + private id; + private verifyAnswerSubscription: ISubscription; + + constructor(private route: ActivatedRoute, private testsService: TestsService) { } + + nextQuestion(f) { + this.id = this.route.snapshot.params.id; + const answers = this.question.answers.filter(element => { + return element.is_gap; + }); + const gapsContent = []; + answers.forEach(element => { + gapsContent.push({ id: element.id, content: f.value[element.id] }); + }); + const body = { id: this.question.id, type: 'gaps', answers: gapsContent }; + this.verifyAnswerSubscription = this.testsService.verifyAnswer(this.id, body).subscribe(d => { + $('.answers').find('[type="text"]').prop('value', ''); + this.emitNextQuestionRequest.emit(d); + }); + } + + ngOnInit() { + } + + ngOnDestroy() { + if (this.verifyAnswerSubscription) { + this.verifyAnswerSubscription.unsubscribe(); + } + } + +} diff --git a/src/app/tests/test-details/question-view/multiple-choice/multiple-choice.component.css b/src/app/tests/test-details/question-view/multiple-choice/multiple-choice.component.css new file mode 100644 index 0000000..2f54d55 --- /dev/null +++ b/src/app/tests/test-details/question-view/multiple-choice/multiple-choice.component.css @@ -0,0 +1,6 @@ +.answers{ + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: flex-start; +} \ No newline at end of file diff --git a/src/app/tests/test-details/question-view/multiple-choice/multiple-choice.component.html b/src/app/tests/test-details/question-view/multiple-choice/multiple-choice.component.html new file mode 100644 index 0000000..94e6208 --- /dev/null +++ b/src/app/tests/test-details/question-view/multiple-choice/multiple-choice.component.html @@ -0,0 +1,12 @@ +
+
+ {{question.question}} ({{question.points}}pkt.) +
+
+ {{item.content}} +
+
+
+ + +
\ No newline at end of file diff --git a/src/app/tests/test-details/question-view/multiple-choice/multiple-choice.component.spec.ts b/src/app/tests/test-details/question-view/multiple-choice/multiple-choice.component.spec.ts new file mode 100644 index 0000000..b2abdc7 --- /dev/null +++ b/src/app/tests/test-details/question-view/multiple-choice/multiple-choice.component.spec.ts @@ -0,0 +1,34 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MultipleChoiceComponent } from './multiple-choice.component'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { RouterTestingModule } from '@angular/router/testing'; +import { HttpClientModule } from '@angular/common/http'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { FormsModule } from '@angular/forms'; +import { TestsService } from '../../../tests.service'; +import { AuthenticationService } from '../../../../authentication.service'; + +describe('MultipleChoiceComponent', () => { + let component: MultipleChoiceComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ MultipleChoiceComponent ], + schemas: [NO_ERRORS_SCHEMA], + imports: [ RouterTestingModule, HttpClientModule, MatSnackBarModule, FormsModule], + providers: [TestsService, AuthenticationService] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(MultipleChoiceComponent); + component = fixture.componentInstance; + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/tests/test-details/question-view/multiple-choice/multiple-choice.component.ts b/src/app/tests/test-details/question-view/multiple-choice/multiple-choice.component.ts new file mode 100644 index 0000000..010f882 --- /dev/null +++ b/src/app/tests/test-details/question-view/multiple-choice/multiple-choice.component.ts @@ -0,0 +1,43 @@ +import { Component, OnInit, Input, Output, EventEmitter, OnDestroy } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { TestsService } from '../../../tests.service'; +import * as $ from 'jquery'; +import { ISubscription } from 'rxjs/Subscription'; + +@Component({ + selector: 'app-multiple-choice', + templateUrl: './multiple-choice.component.html', + styleUrls: ['./multiple-choice.component.css'] +}) +export class MultipleChoiceComponent implements OnInit, OnDestroy{ + @Input() + question; + @Output() emitNextQuestionRequest = new EventEmitter(); + private id; + private verifyAnswerSubscription: ISubscription; + + constructor(private route: ActivatedRoute, private testsService: TestsService) { } + + nextQuestion(f) { + this.id = this.route.snapshot.params.id; + const answers = this.question.answers; + answers.forEach(element => { + element.is_good = f.value[element.id] ? true : false; + }); + const body = { id: this.question.id, type: 'multiple-choice', answers: answers }; + this.verifyAnswerSubscription = this.testsService.verifyAnswer(this.id, body).subscribe(d => { + $('.answers').find('[type="checkbox"]').prop('checked', false); + this.emitNextQuestionRequest.emit(d); + }); + } + + ngOnInit() { + } + + ngOnDestroy() { + if (this.verifyAnswerSubscription) { + this.verifyAnswerSubscription.unsubscribe(); + } + } + +} diff --git a/src/app/tests/test-details/question-view/pairs/pairs.component.css b/src/app/tests/test-details/question-view/pairs/pairs.component.css new file mode 100644 index 0000000..fe68316 --- /dev/null +++ b/src/app/tests/test-details/question-view/pairs/pairs.component.css @@ -0,0 +1,49 @@ +.puzzle{ + display: flex; + min-height: 50px; + min-width: 100px; + background-color: rgb(206, 168, 86); + align-items: center; + justify-content: center; + margin: 5px 0 5px 5px; + color: black; + cursor: pointer; +} + +.puzzle-right{ + display: flex; + min-height: 50px; + min-width: 100px; + background-color: rgb(202, 144, 17); + color: black; + align-items: center; + justify-content: center; + margin: 5px 5px 5px 0; +} + +.both-sides{ + display: flex; + flex-direction: row; + flex-wrap: nowrap; +} + +.puzzle:hover{ + border: 3px solid gray; +} + +.active{ + border: 3px solid white; +} + +.puzzles-container{ + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: center; + align-items: center; +} + +.empty{ + background-color: gray; + cursor: not-allowed; +} diff --git a/src/app/tests/test-details/question-view/pairs/pairs.component.html b/src/app/tests/test-details/question-view/pairs/pairs.component.html new file mode 100644 index 0000000..befa258 --- /dev/null +++ b/src/app/tests/test-details/question-view/pairs/pairs.component.html @@ -0,0 +1,23 @@ +
+ {{question.question}} ({{question.points}}pkt.) +

Dostępne lewe strony:

+
+
+ {{item}} +
+
+

Twoja odpowiedź:

+
+
+
+ {{item}} +
+
+ {{rightSides[i]}} +
+
+ +
+
+ +
\ No newline at end of file diff --git a/src/app/tests/test-details/question-view/pairs/pairs.component.spec.ts b/src/app/tests/test-details/question-view/pairs/pairs.component.spec.ts new file mode 100644 index 0000000..0b72ecc --- /dev/null +++ b/src/app/tests/test-details/question-view/pairs/pairs.component.spec.ts @@ -0,0 +1,34 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PairsComponent } from './pairs.component'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { RouterTestingModule } from '@angular/router/testing'; +import { HttpClientModule } from '@angular/common/http'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { FormsModule } from '@angular/forms'; +import { TestsService } from '../../../tests.service'; +import { AuthenticationService } from '../../../../authentication.service'; + +describe('PairsComponent', () => { + let component: PairsComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ PairsComponent ], + schemas: [NO_ERRORS_SCHEMA], + imports: [ RouterTestingModule, HttpClientModule, MatSnackBarModule, FormsModule], + providers: [TestsService, AuthenticationService] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(PairsComponent); + component = fixture.componentInstance; + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/tests/test-details/question-view/pairs/pairs.component.ts b/src/app/tests/test-details/question-view/pairs/pairs.component.ts new file mode 100644 index 0000000..26b08c6 --- /dev/null +++ b/src/app/tests/test-details/question-view/pairs/pairs.component.ts @@ -0,0 +1,95 @@ +import { Component, OnInit, Input, Output, EventEmitter, OnChanges, OnDestroy } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { TestsService } from '../../../tests.service'; +import * as $ from 'jquery'; +import { ISubscription } from 'rxjs/Subscription'; + +@Component({ + selector: 'app-pairs', + templateUrl: './pairs.component.html', + styleUrls: ['./pairs.component.css'] +}) +export class PairsComponent implements OnInit, OnChanges, OnDestroy { + @Input() + question; + @Output() emitNextQuestionRequest = new EventEmitter(); + private id; + private verifyAnswerSubscription: ISubscription; + leftSides = []; + rightSides = []; + leftSidesToSend = []; + selectedLeftSide = { indexOfLeftSide: Number, leftSideText: String, from: '' }; + + constructor(private route: ActivatedRoute, private testsService: TestsService) { } + + nextQuestion(f = null) { + this.id = this.route.snapshot.params.id; + const answers = []; + this.leftSidesToSend.forEach((element, index) => { + answers.push({ + left: element, + right: this.rightSides[index] + }); + }); + const body = { + id: this.question.id, type: 'pairs', + answers: answers + }; + this.verifyAnswerSubscription = this.testsService.verifyAnswer(this.id, body).subscribe(d => { + $('.answers').find('[type="text"]').prop('value', ''); + this.emitNextQuestionRequest.emit(d); + }); + } + + leftSideClick(item, i) { + if (item !== '' && item !== undefined) { + if (this.selectedLeftSide.from === 'leftSides') { + this.selectedLeftSide = { indexOfLeftSide: undefined, leftSideText: undefined, from: undefined }; + } else { + this.selectedLeftSide = { indexOfLeftSide: i, leftSideText: item, from: 'leftSides' }; + } + } + } + + leftSideToSendClick(item, i) { + if (this.selectedLeftSide.from === 'leftSides') { + const helper = this.leftSidesToSend[i]; + this.leftSidesToSend[i] = this.selectedLeftSide.leftSideText; + this.leftSides[Number(this.selectedLeftSide.indexOfLeftSide)] = helper; + this.selectedLeftSide = { indexOfLeftSide: undefined, leftSideText: undefined, from: undefined }; + } else if (this.selectedLeftSide.from === 'leftSidesToSend') { + const helper = this.leftSidesToSend[i]; + this.leftSidesToSend[i] = this.selectedLeftSide.leftSideText; + this.leftSidesToSend[Number(this.selectedLeftSide.indexOfLeftSide)] = helper; + this.selectedLeftSide = { indexOfLeftSide: undefined, leftSideText: undefined, from: undefined }; + } else if (item !== '' && item !== undefined) { + this.selectedLeftSide = { indexOfLeftSide: i, leftSideText: item, from: 'leftSidesToSend' }; + } + } + + prepareLists() { + this.selectedLeftSide = { indexOfLeftSide: undefined, leftSideText: undefined, from: undefined }; + this.leftSides = []; + this.rightSides = []; + this.leftSidesToSend = []; + this.leftSides = JSON.parse(JSON.stringify(this.question.answers[0].left)); + this.rightSides = JSON.parse(JSON.stringify(this.question.answers[0].right)); + this.leftSides.forEach(element => { + this.leftSidesToSend.push(''); + }); + } + + ngOnInit() { + this.prepareLists(); + } + + ngOnChanges() { + this.prepareLists(); + } + + ngOnDestroy() { + if (this.verifyAnswerSubscription) { + this.verifyAnswerSubscription.unsubscribe(); + } + } +} diff --git a/src/app/tests/test-details/question-view/puzzle/puzzle.component.css b/src/app/tests/test-details/question-view/puzzle/puzzle.component.css new file mode 100644 index 0000000..8b7a531 --- /dev/null +++ b/src/app/tests/test-details/question-view/puzzle/puzzle.component.css @@ -0,0 +1,32 @@ +.puzzle{ + display: flex; + min-height: 50px; + min-width: 100px; + color: black; + background-color: rgb(206, 168, 86); + align-items: center; + justify-content: center; + margin: 5px; + cursor: pointer; +} + +.puzzle:hover{ + border: 3px solid gray; +} + +.active{ + border: 3px solid white; +} + +.puzzles-container{ + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: center; + align-items: center; +} + +.empty{ + background-color: gray; + cursor: not-allowed; +} \ No newline at end of file diff --git a/src/app/tests/test-details/question-view/puzzle/puzzle.component.html b/src/app/tests/test-details/question-view/puzzle/puzzle.component.html new file mode 100644 index 0000000..2627220 --- /dev/null +++ b/src/app/tests/test-details/question-view/puzzle/puzzle.component.html @@ -0,0 +1,17 @@ +
+ {{question.question}} ({{question.points}}pkt.) +

Dostępne elementy rozsypanki:

+
+
+ {{item}} +
+
+

Twoja odpowiedź:

+
+
+ {{item}} +
+
+
+ +
\ No newline at end of file diff --git a/src/app/tests/test-details/question-view/puzzle/puzzle.component.spec.ts b/src/app/tests/test-details/question-view/puzzle/puzzle.component.spec.ts new file mode 100644 index 0000000..e95483c --- /dev/null +++ b/src/app/tests/test-details/question-view/puzzle/puzzle.component.spec.ts @@ -0,0 +1,34 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PuzzleComponent } from './puzzle.component'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { RouterTestingModule } from '@angular/router/testing'; +import { HttpClientModule } from '@angular/common/http'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { FormsModule } from '@angular/forms'; +import { TestsService } from '../../../tests.service'; +import { AuthenticationService } from '../../../../authentication.service'; + +describe('PuzzleComponent', () => { + let component: PuzzleComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ PuzzleComponent ], + schemas: [NO_ERRORS_SCHEMA], + imports: [ RouterTestingModule, HttpClientModule, MatSnackBarModule, FormsModule], + providers: [TestsService, AuthenticationService] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(PuzzleComponent); + component = fixture.componentInstance; + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/tests/test-details/question-view/puzzle/puzzle.component.ts b/src/app/tests/test-details/question-view/puzzle/puzzle.component.ts new file mode 100644 index 0000000..03a34c8 --- /dev/null +++ b/src/app/tests/test-details/question-view/puzzle/puzzle.component.ts @@ -0,0 +1,83 @@ +import { Component, OnInit, Input, Output, EventEmitter, OnChanges, OnDestroy } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { TestsService } from '../../../tests.service'; +import * as $ from 'jquery'; +import { ISubscription } from 'rxjs/Subscription'; + +@Component({ + selector: 'app-puzzle', + templateUrl: './puzzle.component.html', + styleUrls: ['./puzzle.component.css'] +}) +export class PuzzleComponent implements OnInit, OnChanges, OnDestroy { + @Input() + question; + @Output() emitNextQuestionRequest = new EventEmitter(); + private id; + private verifyAnswerSubscription: ISubscription; + puzzles = []; + puzzlesToSend = []; + private selectedPuzzle = { indexOfPuzzle: Number, puzzleText: String, from: '' }; + + constructor(private route: ActivatedRoute, private testsService: TestsService) { } + + nextQuestion(f = null) { + this.id = this.route.snapshot.params.id; + const body = { id: this.question.id, type: 'puzzle', answers: [{ id: this.question.answers[0].id, puzzles: this.puzzlesToSend }] }; + this.verifyAnswerSubscription = this.testsService.verifyAnswer(this.id, body).subscribe(d => { + $('.answers').find('[type="text"]').prop('value', ''); + this.emitNextQuestionRequest.emit(d); + }); + } + + puzzleClick(item, i) { + if (item !== '' && item !== undefined) { + if (this.selectedPuzzle.from === 'puzzles') { + this.selectedPuzzle = { indexOfPuzzle: undefined, puzzleText: undefined, from: undefined }; + } else { + this.selectedPuzzle = { indexOfPuzzle: i, puzzleText: item, from: 'puzzles' }; + } + } + } + + puzzleToSendClick(item, i) { + if (this.selectedPuzzle.from === 'puzzles') { + const helper = this.puzzlesToSend[i]; + this.puzzlesToSend[i] = this.selectedPuzzle.puzzleText; + this.puzzles[Number(this.selectedPuzzle.indexOfPuzzle)] = helper; + this.selectedPuzzle = { indexOfPuzzle: undefined, puzzleText: undefined, from: undefined }; + } else if (this.selectedPuzzle.from === 'puzzlesToSend') { + const helper = this.puzzlesToSend[i]; + this.puzzlesToSend[i] = this.selectedPuzzle.puzzleText; + this.puzzlesToSend[Number(this.selectedPuzzle.indexOfPuzzle)] = helper; + this.selectedPuzzle = { indexOfPuzzle: undefined, puzzleText: undefined, from: undefined }; + } else if (item !== '' && item !== undefined) { + this.selectedPuzzle = { indexOfPuzzle: i, puzzleText: item, from: 'puzzlesToSend' }; + } + } + + prepareLists() { + this.selectedPuzzle = { indexOfPuzzle: undefined, puzzleText: undefined, from: undefined }; + this.puzzles = []; + this.puzzlesToSend = []; + this.puzzles = JSON.parse(JSON.stringify(this.question.answers[0].puzzles)); + this.puzzles.forEach(element => { + this.puzzlesToSend.push(''); + }); + } + + ngOnInit() { + this.prepareLists(); + } + + ngOnChanges() { + this.prepareLists(); + } + + ngOnDestroy() { + if (this.verifyAnswerSubscription) { + this.verifyAnswerSubscription.unsubscribe(); + } + } + +} diff --git a/src/app/tests/test-details/question-view/question-view.component.css b/src/app/tests/test-details/question-view/question-view.component.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/tests/test-details/question-view/question-view.component.html b/src/app/tests/test-details/question-view/question-view.component.html new file mode 100644 index 0000000..a2824b3 --- /dev/null +++ b/src/app/tests/test-details/question-view/question-view.component.html @@ -0,0 +1,26 @@ +
+
+ Pytanie typu prawda-fałsz + +
+
+ Pytanie jednokrotnego wyboru + +
+
+ Pytanie wielokrotnego wyboru + +
+
+ Pytanie typu rozsypanka wyrazowa + +
+
+ Pytanie typu uzupełnianie luk + +
+
+ Pytanie typu łączenie w pary + +
+
\ No newline at end of file diff --git a/src/app/tests/test-details/question-view/question-view.component.spec.ts b/src/app/tests/test-details/question-view/question-view.component.spec.ts new file mode 100644 index 0000000..2334bba --- /dev/null +++ b/src/app/tests/test-details/question-view/question-view.component.spec.ts @@ -0,0 +1,34 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { QuestionViewComponent } from './question-view.component'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { RouterTestingModule } from '@angular/router/testing'; +import { HttpClientModule } from '@angular/common/http'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { FormsModule } from '@angular/forms'; +import { TestsService } from '../../tests.service'; +import { AuthenticationService } from '../../../authentication.service'; + +describe('QuestionViewComponent', () => { + let component: QuestionViewComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ QuestionViewComponent ], + schemas: [NO_ERRORS_SCHEMA], + imports: [ RouterTestingModule, HttpClientModule, MatSnackBarModule, FormsModule], + providers: [TestsService, AuthenticationService] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(QuestionViewComponent); + component = fixture.componentInstance; + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/tests/test-details/question-view/question-view.component.ts b/src/app/tests/test-details/question-view/question-view.component.ts new file mode 100644 index 0000000..e22b177 --- /dev/null +++ b/src/app/tests/test-details/question-view/question-view.component.ts @@ -0,0 +1,28 @@ +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; + +@Component({ + selector: 'app-question-view', + templateUrl: './question-view.component.html', + styleUrls: ['./question-view.component.css'] +}) +export class QuestionViewComponent implements OnInit { + @Input() + question; + @Input() + private questionIndex; + @Output() emitNextQuestionRequest = new EventEmitter(); + + constructor() { } + + nextQuestion(e) { + this.emitNextQuestionRequest.emit(e); + } + + handleEmitNextQuestionRequest(e) { + this.nextQuestion(e); + } + + ngOnInit() { + } + +} diff --git a/src/app/tests/test-details/question-view/single-choice/single-choice.component.css b/src/app/tests/test-details/question-view/single-choice/single-choice.component.css new file mode 100644 index 0000000..2f54d55 --- /dev/null +++ b/src/app/tests/test-details/question-view/single-choice/single-choice.component.css @@ -0,0 +1,6 @@ +.answers{ + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: flex-start; +} \ No newline at end of file diff --git a/src/app/tests/test-details/question-view/single-choice/single-choice.component.html b/src/app/tests/test-details/question-view/single-choice/single-choice.component.html new file mode 100644 index 0000000..afd2409 --- /dev/null +++ b/src/app/tests/test-details/question-view/single-choice/single-choice.component.html @@ -0,0 +1,12 @@ +
+
+ {{question.question}} ({{question.points}}pkt.) +
+
+ {{item.content}} +
+
+
+ + +
\ No newline at end of file diff --git a/src/app/tests/test-details/question-view/single-choice/single-choice.component.spec.ts b/src/app/tests/test-details/question-view/single-choice/single-choice.component.spec.ts new file mode 100644 index 0000000..89aac33 --- /dev/null +++ b/src/app/tests/test-details/question-view/single-choice/single-choice.component.spec.ts @@ -0,0 +1,34 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SingleChoiceComponent } from './single-choice.component'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { RouterTestingModule } from '@angular/router/testing'; +import { HttpClientModule } from '@angular/common/http'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { FormsModule } from '@angular/forms'; +import { TestsService } from '../../../tests.service'; +import { AuthenticationService } from '../../../../authentication.service'; + +describe('SingleChoiceComponent', () => { + let component: SingleChoiceComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ SingleChoiceComponent ], + schemas: [NO_ERRORS_SCHEMA], + imports: [ RouterTestingModule, HttpClientModule, MatSnackBarModule, FormsModule], + providers: [TestsService, AuthenticationService] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SingleChoiceComponent); + component = fixture.componentInstance; + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/tests/test-details/question-view/single-choice/single-choice.component.ts b/src/app/tests/test-details/question-view/single-choice/single-choice.component.ts new file mode 100644 index 0000000..e4b52cc --- /dev/null +++ b/src/app/tests/test-details/question-view/single-choice/single-choice.component.ts @@ -0,0 +1,44 @@ +import { Component, OnInit, Input, Output, EventEmitter, OnDestroy } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { TestsService } from '../../../tests.service'; +import * as $ from 'jquery'; +import { ISubscription } from 'rxjs/Subscription'; + +@Component({ + selector: 'app-single-choice', + templateUrl: './single-choice.component.html', + styleUrls: ['./single-choice.component.css'] +}) +export class SingleChoiceComponent implements OnInit, OnDestroy { + @Input() + question; + private answer; + @Output() emitNextQuestionRequest = new EventEmitter(); + private id; + private verifyAnswerSubscription: ISubscription; + + constructor(private route: ActivatedRoute, private testsService: TestsService) { } + + nextQuestion(f) { + this.id = this.route.snapshot.params.id; + const answers = this.question.answers; + answers.forEach(element => { + element.is_good = element.content === f.value.answer ? true : false; + }); + const body = { id: this.question.id, type: 'single-choice', answers: answers }; + this.verifyAnswerSubscription = this.testsService.verifyAnswer(this.id, body).subscribe(d => { + $('.answers').find('[type="radio"]').prop('checked', false); + this.emitNextQuestionRequest.emit(d); + }); + } + + ngOnInit() { + } + + ngOnDestroy() { + if (this.verifyAnswerSubscription) { + this.verifyAnswerSubscription.unsubscribe(); + } + } + +} diff --git a/src/app/tests/test-details/question-view/true-false/true-false.component.css b/src/app/tests/test-details/question-view/true-false/true-false.component.css new file mode 100644 index 0000000..f6338ef --- /dev/null +++ b/src/app/tests/test-details/question-view/true-false/true-false.component.css @@ -0,0 +1,17 @@ +.question{ + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: space-between; + align-items: center; +} + +label{ + margin: 0; +} + +@media screen and (max-width: 800px) { + .question{ + display: block; + } +} \ No newline at end of file diff --git a/src/app/tests/test-details/question-view/true-false/true-false.component.html b/src/app/tests/test-details/question-view/true-false/true-false.component.html new file mode 100644 index 0000000..5783abe --- /dev/null +++ b/src/app/tests/test-details/question-view/true-false/true-false.component.html @@ -0,0 +1,14 @@ +
+ {{question.question}} ({{question.points}}pkt.) + +
+ + +
+
+ + \ No newline at end of file diff --git a/src/app/tests/test-details/question-view/true-false/true-false.component.spec.ts b/src/app/tests/test-details/question-view/true-false/true-false.component.spec.ts new file mode 100644 index 0000000..18bc2b9 --- /dev/null +++ b/src/app/tests/test-details/question-view/true-false/true-false.component.spec.ts @@ -0,0 +1,34 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TrueFalseComponent } from './true-false.component'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { RouterTestingModule } from '@angular/router/testing'; +import { HttpClientModule } from '@angular/common/http'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { FormsModule } from '@angular/forms'; +import { TestsService } from '../../../tests.service'; +import { AuthenticationService } from '../../../../authentication.service'; + +describe('TrueFalseComponent', () => { + let component: TrueFalseComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ TrueFalseComponent ], + schemas: [NO_ERRORS_SCHEMA], + imports: [ RouterTestingModule, HttpClientModule, MatSnackBarModule, FormsModule], + providers: [TestsService, AuthenticationService] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(TrueFalseComponent); + component = fixture.componentInstance; + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/tests/test-details/question-view/true-false/true-false.component.ts b/src/app/tests/test-details/question-view/true-false/true-false.component.ts new file mode 100644 index 0000000..b675294 --- /dev/null +++ b/src/app/tests/test-details/question-view/true-false/true-false.component.ts @@ -0,0 +1,49 @@ +import { Component, OnInit, Input, Output, EventEmitter, OnDestroy } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { TestsService } from '../../../tests.service'; +import * as $ from 'jquery'; +import { ISubscription } from 'rxjs/Subscription'; + +@Component({ + selector: 'app-true-false', + templateUrl: './true-false.component.html', + styleUrls: ['./true-false.component.css'] +}) +export class TrueFalseComponent implements OnInit, OnDestroy { + @Input() + question; + private answer; + @Output() emitNextQuestionRequest = new EventEmitter(); + private id; + private verifyAnswerSubscription: ISubscription; + + constructor(private route: ActivatedRoute, private testsService: TestsService) { } + + nextQuestion() { + this.id = this.route.snapshot.params.id; + const answers = this.question.answers; + answers.forEach(element => { + element.is_good = element.content === this.answer ? true : false; + }); + const body = { id: this.question.id, type: 'true-false', answers: answers }; + this.verifyAnswerSubscription = this.testsService.verifyAnswer(this.id, body).subscribe(d => { + $('.btn-group').find('label').removeClass('active').end().find('[type="radio"]').prop('checked', false); + this.answer = undefined; + this.emitNextQuestionRequest.emit(d); + }); + } + + chooseAnswer(answer: any) { + this.answer = answer; + } + + ngOnInit() { + } + + ngOnDestroy() { + if (this.verifyAnswerSubscription) { + this.verifyAnswerSubscription.unsubscribe(); + } + } + +} diff --git a/src/app/tests/test-details/test-details.component.css b/src/app/tests/test-details/test-details.component.css new file mode 100644 index 0000000..717e759 --- /dev/null +++ b/src/app/tests/test-details/test-details.component.css @@ -0,0 +1,42 @@ +.wrapper{ + width: 100%; + padding: 30px; + min-height: 100%; + margin-bottom: 40px; +} + +.content{ + background-color: #181616; + padding: 30px; + margin-top: 2rem; + margin-bottom: 2rem; + text-align: center; + margin-left: auto; + margin-right: auto; +} + +.header{ + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; +} + +.header button{ + margin: 5px; +} + +.questions-count { + margin: 10px; +} + +.question-view{ + margin: 10px; +} + +@media screen and (max-width: 800px) { + .header{ + display: block; + } +} \ No newline at end of file diff --git a/src/app/tests/test-details/test-details.component.html b/src/app/tests/test-details/test-details.component.html new file mode 100644 index 0000000..25277cc --- /dev/null +++ b/src/app/tests/test-details/test-details.component.html @@ -0,0 +1,61 @@ +
+
+ +
+
+ + +
Twój maksymalny wynik: {{prevMaxResult}}/{{maxPoints}}
+ + + +
+
+
+

{{test.title}}

+
+ +
+ Poprzednia odpowiedź była poprawna. +
+
+ Poprzednia odpowiedź była niepoprawna. +
+ +
+ +
+
+ + +
+ +
+ + + +
+ + + + +

+ Koniec pytań. Twój wynik to {{points}}/{{maxPoints}}. +

+ + +
+ +
+ Błąd. Test nie istnieje lub nie masz do niego dostępu. +
+ +
+ + Czy chcesz usunąć test? + + + + + +
\ No newline at end of file diff --git a/src/app/tests/test-details/test-details.component.spec.ts b/src/app/tests/test-details/test-details.component.spec.ts new file mode 100644 index 0000000..c918a79 --- /dev/null +++ b/src/app/tests/test-details/test-details.component.spec.ts @@ -0,0 +1,51 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TestDetailsComponent } from './test-details.component'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { RouterTestingModule } from '@angular/router/testing'; +import { HttpClientModule } from '@angular/common/http'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { FormsModule } from '@angular/forms'; +import { TestsService } from '../tests.service'; +import { AuthenticationService } from '../../authentication.service'; +import { By } from '@angular/platform-browser'; +import { RoutingStateService } from '../../routing-state.service'; + +describe('TestDetailsComponent', () => { + let component: TestDetailsComponent; + let fixture: ComponentFixture; + let mockTest: any; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ TestDetailsComponent ], + schemas: [NO_ERRORS_SCHEMA], + providers: [TestsService, AuthenticationService, RoutingStateService], + imports: [ RouterTestingModule, FormsModule, HttpClientModule, MatSnackBarModule] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(TestDetailsComponent); + component = fixture.componentInstance; + mockTest = { + title: 'Test', + body: [], + owner: 'test' + }; + + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should render test title', () => { + component.test = mockTest; + fixture.detectChanges(); + const testName = document.querySelector('h3').innerHTML; + expect(testName).toEqual(mockTest.title); + }); +}); diff --git a/src/app/tests/test-details/test-details.component.ts b/src/app/tests/test-details/test-details.component.ts new file mode 100644 index 0000000..b6b4211 --- /dev/null +++ b/src/app/tests/test-details/test-details.component.ts @@ -0,0 +1,145 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { TestsService } from '../tests.service'; +import { ISubscription } from 'rxjs/Subscription'; +import { Test } from '../test_model'; +import { Test2PDF } from '../test2PDF'; +import { RoutingStateService } from '../../routing-state.service'; + +@Component({ + selector: 'app-test-details', + templateUrl: './test-details.component.html', + styleUrls: ['./test-details.component.css'] +}) +export class TestDetailsComponent implements OnInit, OnDestroy { + id: number; + test; + questionsCount = 0; + isStarted = false; + currentQuestionIndex = 0; + isEnded = false; + points = 0; + maxPoints = 0; + prevMaxResult = 0; + prevAnswerResultBool; + currentUser; + sendResultSubscription: ISubscription; + removeTestSubscription: ISubscription; + getResultSubscription: ISubscription; + getTestWithoutAnswersSubscription: ISubscription; + display = false; + + constructor(private route: ActivatedRoute, private router: Router, private testService: TestsService, + private routingState: RoutingStateService) { } + + openPopup() { + this.display = true; + } + + handleEmitNextQuestionRequest(e) { + this.points += e.points; + this.prevAnswerResultBool = e.points > 0 ? true : false; + if (this.currentQuestionIndex < this.questionsCount - 1) { + this.currentQuestionIndex += 1; + } else { + this.isEnded = true; + if (this.currentUser) { + this.sendResultSubscription = this.testService.sendResult(this.test.id, this.points, this.currentUser.username).subscribe(d => { + this.getMaxResult(); + }); + } + } + } + + start() { + this.isStarted = true; + } + + back() { + const previousUrl = this.routingState.getPreviousUrl(); + this.router.navigate([previousUrl]); + } + + edit() { + this.router.navigate(['tests/edit', this.id]); + } + + deleteTest() { + this.display = false; + this.remove(); + } + remove() { + this.removeTestSubscription = this.testService.removeTest(this.id).subscribe( + d => { + const previousUrl = this.routingState.getPreviousUrl(); + this.router.navigate([previousUrl]); + } + ); + } + + getMaxResult() { + if (this.currentUser) { + this.getResultSubscription = this.testService.getResult(this.test.id, this.currentUser.username).subscribe(d => { + this.prevMaxResult = d.userScore; + }); + } + } + + ngOnInit() { + this.currentUser = JSON.parse(localStorage.getItem('currentUser')); + this.id = this.route.snapshot.params.id; + this.getTestWithoutAnswersSubscription = this.testService.getTestWithoutAnswers(this.id).subscribe( + d => { + this.test = d; + this.questionsCount = d.body.length; + d.body.forEach(element => { + this.maxPoints += element.points; + }); + this.getMaxResult(); + } + ); + } + + get() { + this.testService.getTest(this.id).subscribe( + success => { + success.body = this.sortArrayByProperty(success.body, 'nr'); + this.getPDF(success); + }, + error => { + console.log(error); + } + ); + } + + sortArrayByProperty(array: Object[], property: string): Object[] { + return array.sort(function(a, b) { + if (a[property] < b[property]) { + return -1; + } + if (a[property] > b[property]) { + return 1; + } + return 0; + }); + } + + getPDF(test: Test) { + const pdf = new Test2PDF(); + pdf.getPDF(test); + } + + ngOnDestroy() { + if (this.removeTestSubscription) { + this.removeTestSubscription.unsubscribe(); + } + if (this.sendResultSubscription) { + this.sendResultSubscription.unsubscribe(); + } + if (this.getResultSubscription) { + this.getResultSubscription.unsubscribe(); + } + this.getTestWithoutAnswersSubscription.unsubscribe(); + } + +} diff --git a/src/app/tests/test-edit/test-edit.component.css b/src/app/tests/test-edit/test-edit.component.css new file mode 100644 index 0000000..8285305 --- /dev/null +++ b/src/app/tests/test-edit/test-edit.component.css @@ -0,0 +1,45 @@ +label { + display: block; +} + +.container { + background-color: #181616; + padding: 30px; + margin-top: 2rem; + margin-bottom: 1rem; + text-align: center; +} + +.wrapper-add { + width: 100%; + display: flex; + justify-content: center; + align-content: center; + margin-bottom: 4rem; +} + +.wrapper { + width: 100%; + padding: 30px; + margin-bottom: 5rem; +} + +.btn { + margin-bottom: 1rem; + margin-left: 1rem; +} + +.table td { + border-top-width: 0; +} + +.flexrow { + display: flex; + width: 100%; +} + +@media screen and (max-width: 800px) { + .mobile{ + display: none; + } +} diff --git a/src/app/tests/test-edit/test-edit.component.html b/src/app/tests/test-edit/test-edit.component.html new file mode 100644 index 0000000..bdec0c2 --- /dev/null +++ b/src/app/tests/test-edit/test-edit.component.html @@ -0,0 +1,112 @@ +
+
+
+

Edytowanie testu

+
+
+ + +
+
+
+

Wybierz rodzaj pytania:

+ + + +
+ + + +
+
+
+
+ + + + + + +
+
+

Dodane pytania:

+
+
Maksymalna liczba punktów do zdobycia: {{ pointsAll }}
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Lp.TypPunktyPytanieUsuńPrzenieś
{{ question.nr }}Prawda/fałszJednokrotny wybórWielokrotny wybórRozsypanka wyrazowaUzupełnianie lukŁączenie w pary{{ question.content.points }}{{ question.shortcut }} + + + --- +
+ + + + + + +
+
+
+
+ + +
+ +
+
+
diff --git a/src/app/tests/test-edit/test-edit.component.spec.ts b/src/app/tests/test-edit/test-edit.component.spec.ts new file mode 100644 index 0000000..ba92d7e --- /dev/null +++ b/src/app/tests/test-edit/test-edit.component.spec.ts @@ -0,0 +1,35 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TestEditComponent } from './test-edit.component'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { RouterTestingModule } from '@angular/router/testing'; +import { HttpClientModule } from '@angular/common/http'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { FormsModule } from '@angular/forms'; +import { TestsService } from '../tests.service'; +import { AuthenticationService } from '../../authentication.service'; + +describe('TestEditComponent', () => { + let component: TestEditComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ TestEditComponent ], + schemas: [NO_ERRORS_SCHEMA], + imports: [ RouterTestingModule, HttpClientModule, MatSnackBarModule, FormsModule], + providers: [TestsService, AuthenticationService] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(TestEditComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/tests/test-edit/test-edit.component.ts b/src/app/tests/test-edit/test-edit.component.ts new file mode 100644 index 0000000..f2264cc --- /dev/null +++ b/src/app/tests/test-edit/test-edit.component.ts @@ -0,0 +1,230 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { TestsService } from '../tests.service'; +import { Subscription } from 'rxjs/Subscription'; +import { ActivatedRoute } from '@angular/router'; +import { MatSnackBar } from '@angular/material/snack-bar'; + +@Component({ + selector: 'app-test-edit', + templateUrl: './test-edit.component.html', + styleUrls: ['./test-edit.component.css'] +}) +export class TestEditComponent implements OnInit, OnDestroy { + + ident: Number; + owner: Number = 0; + title: String = ''; + permission: Boolean = false; + + test: Array = []; + shown: Boolean = false; + + trueFalse: Boolean = false; + singleChoice: Boolean = false; + multipleChoice: Boolean = false; + puzzle: Boolean = false; + gaps: Boolean = false; + pairs: Boolean = false; + + componentVisible: Boolean = false; + nr: Number = 0; + pointsAll: Number = 0; + + private testSubscribtion: Subscription; + + constructor(private testsService: TestsService, private route: ActivatedRoute, public snackBar: MatSnackBar) { } + + ngOnInit() { + this.ident = this.route.snapshot.params.id; + this.testSubscribtion = this.testsService.getTest(this.ident).subscribe( + data => { + this.title = data['title']; + this.owner = data['owner']; + if (data['permission'] === 'public') { + this.permission = true; + } else { + this.permission = false; + } + const d = data['body']; + for (let i = 0; i < d.length; i++) { + let short = d[i]['question']; + if (short.length > 15) { + short = short.substr(0, 14) + '...'; + } + const obj = { + nr: d[i]['nr'], + content: { + id: d[i]['id'], + type: d[i]['type'], + question: d[i]['question'], + answers: d[i]['answers'], + points: d[i]['points'] + }, + shortcut: short + }; + this.test.push(obj); + } + this.test.sort(this.compare); + this.countPoints(); + }, + error => { + this.snackBar.open('Coś poszło nie tak. Spróbuj ponownie później.', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } + ); + } + + compare(a, b) { + let comparison = 0; + if (a['nr'] > b['nr']) { + comparison = 1; + } else if (b['nr'] > a['nr']) { + comparison = -1; + } + return comparison; + } + + onAdd(question: Object): void { + this.pointsAll = 0; + this.shown = false; + this.trueFalse = false; + this.singleChoice = false; + this.multipleChoice = false; + this.puzzle = false; + this.gaps = false; + this.pairs = false; + this.componentVisible = false; + if (Object.keys(question).length !== 0) { + let short = question['content']['question']; + if (short.length > 15) { + short = short.substr(0, 14) + '...'; + } + question['shortcut'] = short; + + if (question['edit'] === true) { + this.test[question['nr'] - 1] = question; + } else { + question['nr'] = this.test.length + 1; + this.test.push(question); + } + } + this.countPoints(); + } + + countPoints(): void { + this.pointsAll = 0; + for (let i = 0; i < this.test.length; i++) { + this.pointsAll += this.test[i]['content']['points']; + } + } + + show(type: String): void { + this.shown = true; + this.showComponents(type, 0); + } + + showComponents(type: String, nr: Number): void { + if (nr > 0) { + this.componentVisible = true; + } + this.trueFalse = false; + this.singleChoice = false; + this.multipleChoice = false; + this.puzzle = false; + this.gaps = false; + this.pairs = false; + this.nr = nr; + + if (type === 'true-false') { + this.trueFalse = true; + } + if (type === 'single-choice') { + this.singleChoice = true; + } + if (type === 'multiple-choice') { + this.multipleChoice = true; + } + if (type === 'puzzle') { + this.puzzle = true; + } + if (type === 'gaps') { + this.gaps = true; + } + if (type === 'pairs') { + this.pairs = true; + } + } + + setIndexes(): void { + this.countPoints(); + const n = this.test.length; + for (let i = 0; i < n; i++) { + this.test[i]['nr'] = i + 1; + } + } + + delete(nr: number) { + this.test.splice((nr - 1), 1); + this.setIndexes(); + } + + moveUp(nr: number): void { + const temp = this.test[nr - 2]; + this.test[nr - 2] = this.test[nr - 1]; + this.test[nr - 1] = temp; + this.setIndexes(); + } + + moveDown(nr: number): void { + const temp = this.test[nr]; + this.test[nr] = this.test[nr - 1]; + this.test[nr - 1] = temp; + this.setIndexes(); + } + + changePermission(): void { + this.permission = !this.permission; + } + + save(): void { + if ((this.title === undefined) || (this.title.trim().length === 0)) { + this.snackBar.open('Podaj tytuł testu.', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } else { + let p = 'private'; + if (this.permission) { + p = 'public'; + } + const own = JSON.parse(localStorage.getItem('currentUser')); + let owner = '0'; + if (own !== null) { + owner = own['username']; + } + const toSend = { + id: this.ident, + title: this.title, + owner: owner, + permission: p + }; + const body = []; + const n = this.test.length; + for (let i = 0; i < n; i++) { + body.push({ + nr: this.test[i]['nr'], + type: this.test[i]['content']['type'], + question: this.test[i]['content']['question'], + answers: this.test[i]['content']['answers'], + points: this.test[i]['content']['points'], + id: this.test[i]['content']['id'] + }); + } + toSend['body'] = body; + this.testsService.edit(toSend); + } + } + + ngOnDestroy() { + this.testSubscribtion.unsubscribe(); + } + +} diff --git a/src/app/tests/test-maker/test-maker.component.css b/src/app/tests/test-maker/test-maker.component.css new file mode 100644 index 0000000..8285305 --- /dev/null +++ b/src/app/tests/test-maker/test-maker.component.css @@ -0,0 +1,45 @@ +label { + display: block; +} + +.container { + background-color: #181616; + padding: 30px; + margin-top: 2rem; + margin-bottom: 1rem; + text-align: center; +} + +.wrapper-add { + width: 100%; + display: flex; + justify-content: center; + align-content: center; + margin-bottom: 4rem; +} + +.wrapper { + width: 100%; + padding: 30px; + margin-bottom: 5rem; +} + +.btn { + margin-bottom: 1rem; + margin-left: 1rem; +} + +.table td { + border-top-width: 0; +} + +.flexrow { + display: flex; + width: 100%; +} + +@media screen and (max-width: 800px) { + .mobile{ + display: none; + } +} diff --git a/src/app/tests/test-maker/test-maker.component.html b/src/app/tests/test-maker/test-maker.component.html new file mode 100644 index 0000000..ef3cbd0 --- /dev/null +++ b/src/app/tests/test-maker/test-maker.component.html @@ -0,0 +1,112 @@ +
+
+
+

Tworzenie testu

+
+
+ + +
+
+
+

Wybierz rodzaj pytania:

+ + + +
+ + + +
+
+
+
+ + + + + + +
+
+

Dodane pytania:

+
+
Maksymalna liczba punktów do zdobycia: {{ pointsAll }}
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Lp.TypPunktyPytanieUsuńPrzenieś
{{ question.nr }}Prawda/fałszJednokrotny wybórWielokrotny wybórRozsypanka wyrazowaUzupełnianie lukŁączenie w pary{{ question.content.points }}{{ question.shortcut }} + + + --- +
+ + + + + + +
+
+
+
+ + +
+ +
+
+
diff --git a/src/app/tests/test-maker/test-maker.component.spec.ts b/src/app/tests/test-maker/test-maker.component.spec.ts new file mode 100644 index 0000000..f886ae2 --- /dev/null +++ b/src/app/tests/test-maker/test-maker.component.spec.ts @@ -0,0 +1,418 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TestMakerComponent } from './test-maker.component'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { RouterTestingModule } from '@angular/router/testing'; +import { HttpClientModule } from '@angular/common/http'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { FormsModule } from '@angular/forms'; +import { TestsService } from '../tests.service'; +import { AuthenticationService } from '../../authentication.service'; + +import { By } from '@angular/platform-browser'; +import { BrowserAnimationsModule, NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { TrueFalseQuestionComponent } from '../true-false-question/true-false-question.component'; +import { SingleChoiceQuestionComponent } from '../single-choice-question/single-choice-question.component'; +import { MultipleChoiceQuestionComponent } from '../multiple-choice-question/multiple-choice-question.component'; +import { PuzzleQuestionComponent } from '../puzzle-question/puzzle-question.component'; +import { PairsQuestionComponent } from '../pairs-question/pairs-question.component'; +import { GapsQuestionComponent } from '../gaps-question/gaps-question.component'; + +describe('TestMakerComponent', () => { + let component: TestMakerComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ TestMakerComponent, TrueFalseQuestionComponent, SingleChoiceQuestionComponent, MultipleChoiceQuestionComponent, + PuzzleQuestionComponent, PairsQuestionComponent, GapsQuestionComponent ], + schemas: [NO_ERRORS_SCHEMA], + imports: [ RouterTestingModule, HttpClientModule, MatSnackBarModule, FormsModule, NoopAnimationsModule], + providers: [TestsService, AuthenticationService] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(TestMakerComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should create true-false question', async(() => { + fixture.whenStable().then(() => { + fixture.autoDetectChanges(); + spyOn(component, 'show').and.callThrough(); + fixture.debugElement.nativeElement.querySelector('button').click(); + expect(component.show).toHaveBeenCalledWith('true-false'); + + const childDebugElement = fixture.debugElement.query(By.directive(TrueFalseQuestionComponent)); + + childDebugElement.context.content.content.points = 2; + childDebugElement.context.content.content.question = 'Test?'; + const trueCheckbox = childDebugElement.nativeElement.querySelector('input[value=true]'); + trueCheckbox.click(); + expect(trueCheckbox.checked).toBeTruthy(); + + const values = { + question: 'Test?', + points: 2 + }; + childDebugElement.context.addTable(values); + const testMock = [ + { + content: { + answers: [ + { + id: null, + content: 'Prawda', + is_good: true + }, + { + id: null, + content: 'Fałsz', + is_good: false + } + ], + id: null, + points: 2, + question: 'Test?', + type: 'true-false' + }, + edit: false, + nr: 1, + shortcut: 'Test?' + } + ]; + expect(component.test).toEqual(testMock); + }); + })); + + it('should create single-choice question', async(() => { + fixture.whenStable().then(() => { + fixture.autoDetectChanges(); + spyOn(component, 'show').and.callThrough(); + fixture.debugElement.nativeElement.querySelectorAll('button')[1].click(); + expect(component.show).toHaveBeenCalledWith('single-choice'); + + const childDebugElement = fixture.debugElement.query(By.directive(SingleChoiceQuestionComponent)); + + childDebugElement.context.points = 2; + childDebugElement.context.question = 'Test?'; + + childDebugElement.context.newAttribute.content = 'a'; + childDebugElement.context.addFieldValue(); + + childDebugElement.context.newAttribute.content = 'b'; + spyOn(childDebugElement.context, 'changeCheckbox2').and.callThrough(); + const trueCheckbox = childDebugElement.nativeElement.querySelectorAll('input[type=checkbox]')[0]; + trueCheckbox.click(); + expect(trueCheckbox.checked).toBeTruthy(); + expect(childDebugElement.context.changeCheckbox2).toHaveBeenCalled(); + childDebugElement.context.addFieldValue(); + + childDebugElement.context.addTable(); + + const testMock = [ + { + content: { + answers: [ + { + id: null, + content: 'a', + is_good: false + }, + { + id: null, + content: 'b', + is_good: true + } + ], + id: null, + points: 2, + question: 'Test?', + type: 'single-choice' + }, + edit: false, + nr: 1, + shortcut: 'Test?' + } + ]; + + expect(component.test).toEqual(testMock); + }); + })); + + it('should create multiple-choice question', async(() => { + fixture.whenStable().then(() => { + fixture.autoDetectChanges(); + spyOn(component, 'show').and.callThrough(); + fixture.debugElement.nativeElement.querySelectorAll('button')[2].click(); + expect(component.show).toHaveBeenCalledWith('multiple-choice'); + + const childDebugElement = fixture.debugElement.query(By.directive(MultipleChoiceQuestionComponent)); + + childDebugElement.context.points = 2; + childDebugElement.context.question = 'Test?'; + + childDebugElement.context.newAttribute.content = 'a'; + spyOn(childDebugElement.context, 'changeCheckbox2').and.callThrough(); + const trueCheckbox1 = childDebugElement.nativeElement.querySelectorAll('input[type=checkbox]')[0]; + trueCheckbox1.click(); + expect(trueCheckbox1.checked).toBeTruthy(); + expect(childDebugElement.context.changeCheckbox2).toHaveBeenCalled(); + childDebugElement.context.addFieldValue(); + + childDebugElement.context.newAttribute.content = 'b'; + childDebugElement.context.addFieldValue(); + + childDebugElement.context.addTable(); + + const testMock = [ + { + content: { + answers: [ + { + id: null, + content: 'a', + is_good: true + }, + { + id: null, + content: 'b', + is_good: true + } + ], + id: null, + points: 2, + question: 'Test?', + type: 'multiple-choice' + }, + edit: false, + nr: 1, + shortcut: 'Test?' + } + ]; + + expect(component.test).toEqual(testMock); + }); + })); + + it('should create puzzle question', async(() => { + fixture.whenStable().then(() => { + fixture.autoDetectChanges(); + spyOn(component, 'show').and.callThrough(); + fixture.debugElement.nativeElement.querySelectorAll('button')[3].click(); + expect(component.show).toHaveBeenCalledWith('puzzle'); + + const childDebugElement = fixture.debugElement.query(By.directive(PuzzleQuestionComponent)); + + childDebugElement.context.points = 2; + + childDebugElement.context.newAttribute.correct = 'a'; + childDebugElement.context.addFieldValue(); + + childDebugElement.context.newAttribute.correct = 'b'; + childDebugElement.context.addFieldValue(); + + childDebugElement.context.addTable(); + + const testMock = [ + { + content: { + answers: [{ + id: null, + correct: ['a', 'b'] + }], + id: null, + points: 2, + question: 'Ułóż elementy w prawidłowej kolejności:', + type: 'puzzle' + }, + edit: false, + nr: 1, + shortcut: 'Ułóż elementy ...' + } + ]; + expect(component.test).toEqual(testMock); + }); + })); + + it('should create gaps question', async(() => { + fixture.whenStable().then(() => { + fixture.autoDetectChanges(); + spyOn(component, 'show').and.callThrough(); + fixture.debugElement.nativeElement.querySelectorAll('button')[4].click(); + expect(component.show).toHaveBeenCalledWith('gaps'); + + const childDebugElement = fixture.debugElement.query(By.directive(GapsQuestionComponent)); + + childDebugElement.context.points = 2; + + childDebugElement.context.showVisibleTextInput(); + childDebugElement.context.noGapText = 'noGap1'; + childDebugElement.context.addToVisibleText(); + + childDebugElement.context.showGapInput(); + childDebugElement.context.gapText = 'gap1;gap2'; + childDebugElement.context.addToGaps(); + + childDebugElement.context.showVisibleTextInput(); + childDebugElement.context.noGapText = 'noGap2'; + childDebugElement.context.addToVisibleText(); + + childDebugElement.context.addNewLine(); + + childDebugElement.context.showVisibleTextInput(); + childDebugElement.context.noGapText = 'noGap3'; + childDebugElement.context.addToVisibleText(); + + childDebugElement.context.showGapInput(); + childDebugElement.context.gapText = 'gap3;gap4'; + childDebugElement.context.addToGaps(); + + childDebugElement.context.showVisibleTextInput(); + childDebugElement.context.noGapText = 'noGap4'; + childDebugElement.context.addToVisibleText(); + + childDebugElement.context.addTable(); + + const testMock = [ + { + content: { + answers: [ + { + id: null, + content: ['noGap1'], + is_gap: false + }, + { + id: null, + content: ['gap1', 'gap2'], + is_gap: true + }, + { + id: null, + content: ['noGap2'], + is_gap: false + }, + { + id: null, + content: ['\n'], + is_gap: false + }, + { + id: null, + content: ['noGap3'], + is_gap: false + }, + { + id: null, + content: ['gap3', 'gap4'], + is_gap: true + }, + { + id: null, + content: ['noGap4'], + is_gap: false + } + ], + id: null, + points: 2, + question: 'Uzupełnij luki w tekście:', + type: 'gaps' + }, + edit: false, + nr: 1, + shortcut: 'Uzupełnij luki...' + } + ]; + + expect(component.test).toEqual(testMock); + }); + })); + + it('should create pairs question', async(() => { + fixture.whenStable().then(() => { + fixture.autoDetectChanges(); + spyOn(component, 'show').and.callThrough(); + fixture.debugElement.nativeElement.querySelectorAll('button')[5].click(); + expect(component.show).toHaveBeenCalledWith('pairs'); + + const childDebugElement = fixture.debugElement.query(By.directive(PairsQuestionComponent)); + + childDebugElement.context.points = 2; + + childDebugElement.context.newAttribute.first = 'a1'; + childDebugElement.context.newAttribute.second = 'a2'; + childDebugElement.context.addFieldValue(); + + childDebugElement.context.newAttribute.first = 'b1'; + childDebugElement.context.newAttribute.second = 'b2'; + childDebugElement.context.addFieldValue(); + + childDebugElement.context.addTable(); + + const testMock = [ + { + content: { + answers: [ + { + id: null, + first: 'a1', + second: 'a2' + }, + { + id: null, + first: 'b1', + second: 'b2' + } + ], + id: null, + points: 2, + question: 'Połącz w pary:', + type: 'pairs' + }, + edit: false, + nr: 1, + shortcut: 'Połącz w pary:' + } + ]; + + expect(component.test).toEqual(testMock); + }); + })); + + it('should count the points for the test', () => { + component.test = [ + { + content: { + points: 1 + } + }, + { + content: { + points: 5 + } + }, + { + content: { + points: 2 + } + }, + { + content: { + points: 1 + } + } + ]; + + component.countPoints(); + + expect(component.pointsAll).toEqual(9); + }); +}); diff --git a/src/app/tests/test-maker/test-maker.component.ts b/src/app/tests/test-maker/test-maker.component.ts new file mode 100644 index 0000000..9284efd --- /dev/null +++ b/src/app/tests/test-maker/test-maker.component.ts @@ -0,0 +1,172 @@ +import { Component, OnInit } from '@angular/core'; +import { TestsService } from '../tests.service'; +import { MatSnackBar } from '@angular/material/snack-bar'; + +@Component({ + selector: 'app-test-maker', + templateUrl: './test-maker.component.html', + styleUrls: ['./test-maker.component.css'] +}) +export class TestMakerComponent implements OnInit { + + owner: Number = 0; + title: String = ''; + permission: Boolean = true; + + test: Array = []; + shown: Boolean = false; + + trueFalse: Boolean = false; + singleChoice: Boolean = false; + multipleChoice: Boolean = false; + puzzle: Boolean = false; + gaps: Boolean = false; + pairs: Boolean = false; + + componentVisible: Boolean = false; + nr: Number = 0; + pointsAll: Number = 0; + + constructor(private testsService: TestsService, public snackBar: MatSnackBar) { } + + ngOnInit() { } + + onAdd(question: Object): void { + this.pointsAll = 0; + this.shown = false; + this.trueFalse = false; + this.singleChoice = false; + this.multipleChoice = false; + this.puzzle = false; + this.gaps = false; + this.pairs = false; + this.componentVisible = false; + if (Object.keys(question).length !== 0) { + let short = question['content']['question']; + if (short.length > 15) { + short = short.substr(0, 14) + '...'; + } + question['shortcut'] = short; + + if (question['edit'] === true) { + this.test[question['nr'] - 1] = question; + } else { + question['nr'] = this.test.length + 1; + this.test.push(question); + } + } + this.countPoints(); + } + + countPoints(): void { + this.pointsAll = 0; + for (let i = 0; i < this.test.length; i++) { + this.pointsAll += this.test[i]['content']['points']; + } + } + + show(type: String): void { + this.shown = true; + this.showComponents(type, 0); + } + + showComponents(type: String, nr: Number): void { + if (nr > 0) { + this.componentVisible = true; + } + this.trueFalse = false; + this.singleChoice = false; + this.multipleChoice = false; + this.puzzle = false; + this.gaps = false; + this.pairs = false; + this.nr = nr; + + if (type === 'true-false') { + this.trueFalse = true; + } + if (type === 'single-choice') { + this.singleChoice = true; + } + if (type === 'multiple-choice') { + this.multipleChoice = true; + } + if (type === 'puzzle') { + this.puzzle = true; + } + if (type === 'gaps') { + this.gaps = true; + } + if (type === 'pairs') { + this.pairs = true; + } + } + + setIndexes(): void { + this.countPoints(); + const n = this.test.length; + for (let i = 0; i < n; i++) { + this.test[i]['nr'] = i + 1; + } + } + + delete(nr: number) { + this.test.splice((nr - 1), 1); + this.setIndexes(); + } + + moveUp(nr: number): void { + const temp = this.test[nr - 2]; + this.test[nr - 2] = this.test[nr - 1]; + this.test[nr - 1] = temp; + this.setIndexes(); + } + + moveDown(nr: number): void { + const temp = this.test[nr]; + this.test[nr] = this.test[nr - 1]; + this.test[nr - 1] = temp; + this.setIndexes(); + } + + changePermission(): void { + this.permission = !this.permission; + } + + save(): void { + if ((this.title === undefined) || (this.title.trim().length === 0)) { + this.snackBar.open('Podaj tytuł testu.', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } else { + let p = 'private'; + if (this.permission) { + p = 'public'; + } + const own = JSON.parse(localStorage.getItem('currentUser')); + let owner = '0'; + if (own !== null) { + owner = own['username']; + } + const toSend = { + title: this.title, + owner: owner, + permission: p + }; + const body = []; + const n = this.test.length; + for (let i = 0; i < n; i++) { + body.push({ + nr: this.test[i]['nr'], + type: this.test[i]['content']['type'], + question: this.test[i]['content']['question'], + answers: this.test[i]['content']['answers'], + points: this.test[i]['content']['points'], + id: this.test[i]['content']['id'] + }); + } + toSend['body'] = body; + this.testsService.add(toSend); + } + } + +} diff --git a/src/app/tests/test2PDF.ts b/src/app/tests/test2PDF.ts new file mode 100644 index 0000000..b7cb227 --- /dev/null +++ b/src/app/tests/test2PDF.ts @@ -0,0 +1,292 @@ +import { Test } from './test_model'; +declare var jsPDF: any; + +export class Test2PDF { + constructor() {} + + getPDF(test: Test) { + const pdf = new jsPDF(); + pdf.addFont('DidactGothic-Regular.ttf', 'DidactGothic-Regular', 'normal'); + pdf.setFont('DidactGothic-Regular'); + + let height = 20; + let textSize = 0; + + pdf.setFontSize(20); + + const testTitle = pdf.splitTextToSize(test.title, 440); + for (let j = 0; j < testTitle.length; j++) { + pdf.text(20, height, testTitle[j]); + textSize = textSize + 20; + height = height + 9; + } + + height = height + 3; + + pdf.setFontSize(12); + + pdf.text(20, height, `Imię i nazwisko: _______________________________________________________________________`); + textSize = textSize + 12; + + height = height + 10; + + let points = 0; + + for (let i = 0; i < test.body.length; i++) { + points = points + test.body[i].points; + } + + pdf.text(20, height, `Wynik: _______ pkt/${points} pkt`); + textSize = textSize + 12; + + height = height + 7; + + for (let i = 0; i < test.body.length; i++) { + height = height + 7; + if (height + textSize >= 620) { + pdf.addPage(); + height = 20; + textSize = 0; + } + const question = test.body[i]; + + pdf.setFontSize(14); + + const questionText = pdf.splitTextToSize(question.question, 425); + for (let j = 0; j < questionText.length; j++) { + if (j === 0) { + questionText[j] = `${question.nr}. ` + questionText[j]; + } + if (j === questionText.length - 1) { + questionText[j] = questionText[j] + ` (${question.points} pkt)`; + } + pdf.text(20, height, questionText[j]); + textSize = textSize + 14; + height = height + 9; + if (height + textSize >= 620) { + pdf.addPage(); + height = 20; + textSize = 0; + } + } + + pdf.setFontSize(12); + + if (question.type === 'pairs') { + let first = []; + for (let k = 0; k < question.answers.length; k++) { + first.push(question.answers[k]['first']); + } + first = this.shuffle(first); + + let second = []; + for (let k = 0; k < question.answers.length; k++) { + second.push(question.answers[k]['second']); + } + second = this.shuffle(second); + + const letterStart = 65; + + for (let k = 0; k < question.answers.length; k++) { + const firstText = pdf.splitTextToSize(first[k], 150); + const secondText = pdf.splitTextToSize(second[k], 150); + + const letter = String.fromCharCode(letterStart + k); + const max = Math.max(firstText.length, secondText.length); + for (let j = 0; j < max; j++) { + if (j === 0) { + if (firstText[j] !== undefined) { + firstText[j] = `${k + 1}. ` + firstText[j]; + } + if (secondText[j] !== undefined) { + secondText[j] = `${letter}. ` + secondText[j]; + } + } + if (firstText[j] !== undefined) { + pdf.text(20, height, firstText[j]); + } + if (secondText[j] !== undefined) { + pdf.text(120, height, secondText[j]); + } + textSize = textSize + 12; + height = height + 7; + if (j === max - 1) { + height = height + 4; + } + if (height + textSize >= 620) { + pdf.addPage(); + height = 20; + textSize = 0; + } + } + + } + + height = height + 2; + if (height + textSize >= 620) { + pdf.addPage(); + height = 20; + textSize = 0; + } + + pdf.text(20, height, 'Miejsce na odpowiedź: '); + textSize = textSize + 12; + height = height + 10; + if (height + textSize >= 620) { + pdf.addPage(); + height = 20; + textSize = 0; + } + + let giveAnswer = ''; + for (let k = 0; k < question.answers.length; k++) { + giveAnswer = giveAnswer + `${k + 1}. _______ `; + } + + const giveAnswerText = pdf.splitTextToSize(giveAnswer, 420); + for (let j = 0; j < giveAnswerText.length; j++) { + pdf.text(20, height, giveAnswerText[j]); + textSize = textSize + 12; + height = height + 7; + if (height + textSize >= 620) { + pdf.addPage(); + height = 20; + textSize = 0; + } + } + + } else if (question.type === 'puzzle') { + const answers = this.shuffle(question.answers[0]['correct']); + let puzzle = ''; + for (let k = 0; k < answers.length; k++) { + if (k < answers.length - 1) { + puzzle = puzzle + answers[k] + ' | '; + } else { + puzzle = puzzle + answers[k]; + } + } + + let count = 0; + + const answersText = pdf.splitTextToSize(puzzle, 400); + for (let j = 0; j < answersText.length; j++) { + pdf.text(20, height, answersText[j]); + textSize = textSize + 12; + height = height + 7; + if (height + textSize >= 620) { + pdf.addPage(); + height = 20; + textSize = 0; + } + count = count + 1; + } + + height = height + 3; + if (height + textSize >= 620) { + pdf.addPage(); + height = 20; + textSize = 0; + } + pdf.text(20, height, 'Miejsce na odpowiedź: '); + textSize = textSize + 12; + height = height + 10; + if (height + textSize >= 620) { + pdf.addPage(); + height = 20; + textSize = 0; + } + for (let j = 0; j < count; j++) { + pdf.text(20, height, '______________________________________________________________________________________'); + textSize = textSize + 12; + height = height + 7; + if (height + textSize >= 620) { + pdf.addPage(); + height = 20; + textSize = 0; + } + } + + } else if (question.type === 'gaps') { + let text = ''; + for (let k = 0; k < question.answers.length; k++) { + if (!question.answers[k]['is_gap']) { + if (question.answers[k]['content'][0] === '\n') { + text = text + question.answers[k]['content'][0]; + } else { + text = text + question.answers[k]['content'][0] + ' '; + } + } else { + const gapLength = question.answers[k]['content'][0].length; + for (let m = 0; m < gapLength; m++) { + if (m === gapLength - 1) { + text = text + ' '; + } else { + text = text + '____'; + } + } + } + } + const answerText = pdf.splitTextToSize(text, 375); + for (let j = 0; j < answerText.length; j++) { + pdf.text(20, height, answerText[j]); + textSize = textSize + 12; + height = height + 7; + if (height + textSize >= 620) { + pdf.addPage(); + height = 20; + textSize = 0; + } + } + } else { + if (question.type === 'multiple-choice') { + pdf.setFontSize(10); + height = height - 2; + pdf.text(20, height, '(możliwych jest kilka prawidłowych odpowiedzi)'); + textSize = textSize + 10; + pdf.setFontSize(12); + height = height + 9; + if (height + textSize >= 620) { + pdf.addPage(); + height = 20; + textSize = 0; + } + } + const letterStart = 65; + for (let k = 0; k < question.answers.length; k++) { + const answer = question.answers[k]; + const answerText = pdf.splitTextToSize(answer['content'], 410); + const letter = String.fromCharCode(letterStart + k); + + for (let j = 0; j < answerText.length; j++) { + if (j === 0) { + answerText[j] = `${letter}. ` + answerText[j]; + } + pdf.text(20, height, answerText[j]); + textSize = textSize + 12; + height = height + 7; + if (height + textSize >= 620) { + pdf.addPage(); + height = 20; + textSize = 0; + } + } + } + } + + } + + pdf.save(`${test.title}.pdf`); + } + + shuffle(a) { + let j, x, i; + for (i = a.length - 1; i > 0; i--) { + j = Math.floor(Math.random() * (i + 1)); + x = a[i]; + a[i] = a[j]; + a[j] = x; + } + return a; + } + +} diff --git a/src/app/tests/test_model.ts b/src/app/tests/test_model.ts new file mode 100644 index 0000000..dd95c1e --- /dev/null +++ b/src/app/tests/test_model.ts @@ -0,0 +1,69 @@ +export class Test { + + public add_date: string; + public body: TestResource[]; + public edit_date?: string; + public grade?: number; + public id: number; + public owner: number; + public permission: string; + public title: string; + + constructor() {} +} + +export class TestResource { + + public type: string; // 'true-false', 'single-choice', 'multiple-choice', 'puzzle', 'gaps', 'pairs' + public id: number; + public points: number; + public question: string; + public nr: number; + public answers: Answer[]; + + constructor() {} +} + +export class Answer { + + constructor(public id: number) {} +} + +export class ChoiceAnswer extends Answer { // 'true-false', 'single-choice', 'multiple-choice' + + public content: string; + public is_good: boolean; + + constructor(public id: number) { + super(id); + } +} + +export class PuzzleAnswer extends Answer { // 'puzzle' + + public correct: string[]; + + constructor(public id: number) { + super(id); + } +} + +export class GapAnswer extends Answer { // 'gaps' + + public content: string[]; + public is_gap: boolean; + + constructor(public id: number) { + super(id); + } +} + +export class PairsAnswer extends Answer { // 'pairs' + + public first: string; + public second: string; + + constructor(public id: number) { + super(id); + } +} diff --git a/src/app/tests/tests-list/localeText.ts b/src/app/tests/tests-list/localeText.ts new file mode 100644 index 0000000..36df79e --- /dev/null +++ b/src/app/tests/tests-list/localeText.ts @@ -0,0 +1,90 @@ +const localeText = { + + // for filter panel + page: 'Strona', + more: 'Więcej', + to: 'do', + of: 'z', + next: '>', + last: '>>', + first: '<<', + previous: '<', + loadingOoo: 'Ładowanie...', + + // for set filter + selectAll: 'Wybierz wszystkie', + searchOoo: 'Szukaj', + blanks: 'Luki', + + // for number filter and text filter + filterOoo: 'Filtruj...', + applyFilter: 'Zastosuj filtr', + + // for number filter + equals: 'Równy', + notEqual: 'Nie równy', + lessThan: 'Mniejszy niż', + greaterThan: 'Większy niż', + + // for text filter + contains: 'Zawiera', + notContains: 'Nie zawiera', + startsWith: 'Zaczyna się od', + endsWith: 'Kończy się na', + + // the header of the default group column + group: 'Grupa', + + // tool panel + columns: 'Kolumny', + rowGroupColumns: 'Grupa wiersza kolumn', + rowGroupColumnsEmptyMessage: 'Przeciągnij kolumny do grupy', + valueColumns: 'Wartości kolumn', + pivotMode: 'Pivot-Mode', + groups: 'Grupy', + values: 'Wartości', + pivots: 'Pivots', + valueColumnsEmptyMessage: 'Przeciągnij kolumny do agregacji', + pivotColumnsEmptyMessage: 'Przeciągnij kolumny na oś', + toolPanelButton: 'Narzędzia', + + // other + noRowsToShow: 'Brak testów do wyświetlenia', + + // enterprise menu + pinColumn: 'Przypiąta kolumna', + valueAggregation: 'Sumuj', + autosizeThiscolumn: 'Autosize this column', + autosizeAllColumns: 'Autosize All Columns', + groupBy: 'Grupuj po', + ungroupBy: 'Nie grupuj po', + resetColumns: 'Resetuj kolumny', + expandAll: 'Rozszerz wszystko', + collapseAll: 'Zwiń wszystko', + toolPanel: 'Narzędzia', + export: 'Export', + csvExport: 'CSV Export', + excelExport: 'Excel Export', + + // enterprise menu pinning + pinLeft: 'Przypnij <<', + pinRight: 'Przypnij >>', + noPin: 'Nie przypinaj <>', + + // enterprise menu aggregation and status panel + sum: 'Suma', + min: 'Minimum', + max: 'Maksimum', + none: 'Nic', + count: 'Ilość', + average: 'Średnia', + + // standard menu + copy: 'Kopiuj', + copyWithHeaders: 'Kopiuj z nagłówkami', + ctrlC: 'ctrl + C', + paste: 'wklej', + ctrlV: 'ctrl + C' +}; + +export default localeText; diff --git a/src/app/tests/tests-list/tests-list.component.css b/src/app/tests/tests-list/tests-list.component.css new file mode 100644 index 0000000..722b21b --- /dev/null +++ b/src/app/tests/tests-list/tests-list.component.css @@ -0,0 +1,24 @@ +.wrapper{ + width: 100%; + padding: 30px; + min-height: 100%; + margin-bottom: 40px; +} + +.content{ + background-color: #181616; + padding: 30px; + margin-top: 2rem; + margin-bottom: 2rem; + text-align: center; + margin-left: auto; + margin-right: auto; +} + +ag-grid-angular{ + margin: 10px; +} + +.buttons-container > div{ + margin: 10px; +} diff --git a/src/app/tests/tests-list/tests-list.component.html b/src/app/tests/tests-list/tests-list.component.html new file mode 100644 index 0000000..6a531a5 --- /dev/null +++ b/src/app/tests/tests-list/tests-list.component.html @@ -0,0 +1,33 @@ +
+
+

TESTY

+
+
+
+ +
+
+ + +
+
+
+ + + +
+ + Czy chcesz usunąć test? + + + + + +
\ No newline at end of file diff --git a/src/app/tests/tests-list/tests-list.component.spec.ts b/src/app/tests/tests-list/tests-list.component.spec.ts new file mode 100644 index 0000000..42c4202 --- /dev/null +++ b/src/app/tests/tests-list/tests-list.component.spec.ts @@ -0,0 +1,35 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TestsListComponent } from './tests-list.component'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { RouterTestingModule } from '@angular/router/testing'; +import { HttpClientModule } from '@angular/common/http'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { FormsModule } from '@angular/forms'; +import { TestsService } from '../tests.service'; +import { AuthenticationService } from '../../authentication.service'; + +describe('TestsListComponent', () => { + let component: TestsListComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ TestsListComponent ], + schemas: [NO_ERRORS_SCHEMA], + imports: [ RouterTestingModule, HttpClientModule, MatSnackBarModule, FormsModule], + providers: [TestsService, AuthenticationService] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(TestsListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/tests/tests-list/tests-list.component.ts b/src/app/tests/tests-list/tests-list.component.ts new file mode 100644 index 0000000..a250b28 --- /dev/null +++ b/src/app/tests/tests-list/tests-list.component.ts @@ -0,0 +1,271 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Router } from '@angular/router'; +import { TestsService } from '../tests.service'; +import { GridOptions, RowDoubleClickedEvent } from 'ag-grid-community/main'; +import localeText from './localeText'; +import { ISubscription } from 'rxjs/Subscription'; +import { Test2PDF } from '../test2PDF'; +import { Test } from '../test_model'; + +@Component({ + selector: 'app-tests-list', + templateUrl: './tests-list.component.html', + styleUrls: ['./tests-list.component.css'] +}) +export class TestsListComponent implements OnInit, OnDestroy { + tests = []; + private gridApi; + private gridColumnApi; + public gridOptions: GridOptions; + localeText = localeText; + logged = false; + private publicMode = true; + private getTestsSubscription: ISubscription; + private removeTestSubscription: ISubscription; + private getUserTestsSubscription: ISubscription; + private currentUser = JSON.parse(localStorage.getItem('currentUser')); + display = false; + isGroup = false; + toDelete; + columnDefs = [ + { headerName: 'Nazwa', field: 'title', headerTooltip: 'Nazwa' }, + { headerName: 'Data dodania', field: 'addDate', headerTooltip: 'Data dodania', hide: false }, + { headerName: 'Data modyfikacji', field: 'editDate', headerTooltip: 'Data modyfikacji', hide: false }, + { headerName: 'Właściciel', field: 'owner', headerTooltip: 'Właściciel', hide: false }, + { headerName: 'Grupa', field: 'group', headerTooltip: 'Grupa', hide: !this.isGroup }, + { headerName: 'Ocena', field: 'grade', headerTooltip: 'Ocena', hide: false }, + { + headerName: '', + suppressMenu: true, + suppressSorting: true, + cellRenderer: this.customCellRendererFunc, + hide: !this.isGroup + } + ]; + + + constructor(private router: Router, private testService: TestsService) { } + + customCellRendererFunc(params) { + const currentUser = JSON.parse(localStorage.getItem('currentUser')); + if (!currentUser) { + return ''; + } else if (params.data['owner'] === currentUser.username) { + return ` + + + + `; + } else { + return ''; + } + } + + ngOnInit() { + this.currentUser = JSON.parse(localStorage.getItem('currentUser')); + if (localStorage.getItem('currentUser')) { + this.logged = true; + } + + this.gridOptions = { + rowHeight: 50, + headerHeight: 25, + getRowStyle: function (params) { + return { + cursor: 'pointer' + }; + }, + }; + this.getPublicTestsData(); + } + + public onRowClicked(e) { + if (e.event.target !== undefined) { + const actionType = e.event.target.getAttribute('data-action-type'); + switch (actionType) { + case 'edit': + return this.onActionEditClick(e); + case 'remove': + return this.onActionRemoveClick(e); + case 'get': + return this.onActionGetClick(e); + default: + return this.goToTest(e); + } + } + + } + + public onActionGetClick(e) { + this.testService.getTest(e.data.id).subscribe( + success => { + success.body = this.sortArrayByProperty(success.body, 'nr'); + this.getPDF(success); + }, + error => { + console.log(error); + } + ); + } + + public sortArrayByProperty(array: Object[], property: string): Object[] { + return array.sort(function(a, b) { + if (a[property] < b[property]) { + return -1; + } + if (a[property] > b[property]) { + return 1; + } + return 0; + }); + } + + public onActionEditClick(e) { + this.router.navigate(['tests/edit', e.data.id]); + } + + openPopup() { + this.display = true; + } + + deleteTest() { + this.display = false; + this.removeTestSubscription = this.testService.removeTest(this.toDelete).subscribe( + d => { + if (this.publicMode) { + this.getPublicTestsData(); + } else { + this.showPrivateTests(); + } + } + ); + } + + public onActionRemoveClick(e) { + this.openPopup(); + this.toDelete = e.data.id; + } + + getPDF(test: Test) { + const pdf = new Test2PDF(); + pdf.getPDF(test); + } + + getPublicTestsData() { + this.publicMode = true; + this.isGroup = false; + this.testService.getTests().subscribe( + d => { + this.tests = d; + this.refreshColumns(); + } + ); + } + + showPrivateTests() { + this.publicMode = false; + this.isGroup = true; + this.getTestsSubscription = this.getUserTestsSubscription = this.testService.getUserTests().subscribe( + d => { + d.forEach((x, i) => { + if (!x['group']) { + x['group'] = 'Brak'; + } + }); + this.tests = d; + this.refreshColumns(); + this.gridApi.sizeColumnsToFit(); + } + ); + } + + goToTest(event: RowDoubleClickedEvent) { + this.router.navigate(['tests', event.data.id]); + } + + toTestMaker() { + this.router.navigate(['test-maker']); + } + + onGridReady(params) { + this.gridApi = params.api; + this.gridColumnApi = params.columnApi; + this.gridApi.sizeColumnsToFit(); + } + + onGridSizeChanged(params) { + if (params.clientWidth < 800) { + this.columnDefs = [ + { headerName: 'Nazwa', field: 'title', headerTooltip: 'Nazwa' }, + { headerName: 'Data dodania', field: 'addDate', headerTooltip: 'Data dodania', hide: true }, + { headerName: 'Data modyfikacji', field: 'editDate', headerTooltip: 'Data modyfikacji', hide: true }, + { headerName: 'Właściciel', field: 'owner', headerTooltip: 'Właściciel', hide: true }, + { headerName: 'Grupa', field: 'group', headerTooltip: 'Grupa', hide: !this.isGroup }, + { headerName: 'Ocena', field: 'grade', headerTooltip: 'Ocena', hide: true }, + { + headerName: '', + suppressMenu: true, + suppressSorting: true, + cellRenderer: this.customCellRendererFunc, + hide: !this.isGroup + } + ]; + } else { + this.columnDefs = [ + { headerName: 'Nazwa', field: 'title', headerTooltip: 'Nazwa' }, + { headerName: 'Data dodania', field: 'addDate', headerTooltip: 'Data dodania', hide: false }, + { headerName: 'Data modyfikacji', field: 'editDate', headerTooltip: 'Data modyfikacji', hide: false }, + { headerName: 'Właściciel', field: 'owner', headerTooltip: 'Właściciel', hide: false }, + { headerName: 'Grupa', field: 'group', headerTooltip: 'Grupa', hide: !this.isGroup }, + { headerName: 'Ocena', field: 'grade', headerTooltip: 'Ocena', hide: false }, + { + headerName: '', + suppressMenu: true, + suppressSorting: true, + cellRenderer: this.customCellRendererFunc, + hide: !this.isGroup + } + ]; + } + + params.api.sizeColumnsToFit(); + } + + onGidColumnsChanged(params) { + params.api.sizeColumnsToFit(); + } + + ngOnDestroy() { + if (this.removeTestSubscription) { + this.removeTestSubscription.unsubscribe(); + } + if (this.getTestsSubscription) { + this.getTestsSubscription.unsubscribe(); + } + if (this.getUserTestsSubscription) { + this.getUserTestsSubscription.unsubscribe(); + } + } + + refreshColumns() { + this.columnDefs = [ + { headerName: 'Nazwa', field: 'title', headerTooltip: 'Nazwa' }, + { headerName: 'Data dodania', field: 'addDate', headerTooltip: 'Data dodania', hide: false }, + { headerName: 'Data modyfikacji', field: 'editDate', headerTooltip: 'Data modyfikacji', hide: false }, + { headerName: 'Właściciel', field: 'owner', headerTooltip: 'Właściciel', hide: false }, + { headerName: 'Grupa', field: 'group', headerTooltip: 'Grupa', hide: !this.isGroup }, + { headerName: 'Ocena', field: 'grade', headerTooltip: 'Ocena', hide: false }, + { + headerName: '', + suppressMenu: true, + suppressSorting: true, + cellRenderer: this.customCellRendererFunc, + hide: !this.isGroup + } + ]; + } + +} diff --git a/src/app/tests/tests.module.ts b/src/app/tests/tests.module.ts new file mode 100644 index 0000000..fe5b022 --- /dev/null +++ b/src/app/tests/tests.module.ts @@ -0,0 +1,65 @@ +import * as $ from 'jquery'; +import { BrowserModule } from '@angular/platform-browser'; +import { NgModule } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { HttpModule } from '@angular/http'; +import { HttpClientModule } from '@angular/common/http'; +import { RouterModule } from '@angular/router'; + +import { TestMakerComponent } from './test-maker/test-maker.component'; +import { TrueFalseQuestionComponent } from './true-false-question/true-false-question.component'; +import { SingleChoiceQuestionComponent } from './single-choice-question/single-choice-question.component'; +import { MultipleChoiceQuestionComponent } from './multiple-choice-question/multiple-choice-question.component'; +import { TestsService } from './tests.service'; +import { PuzzleQuestionComponent } from './puzzle-question/puzzle-question.component'; +import { GapsQuestionComponent } from './gaps-question/gaps-question.component'; +import { PairsQuestionComponent } from './pairs-question/pairs-question.component'; +import { TestsListComponent } from './tests-list/tests-list.component'; +import { TestEditComponent } from './test-edit/test-edit.component'; +import { AgGridModule } from 'ag-grid-angular'; +import { TestDetailsComponent } from './test-details/test-details.component'; +import { QuestionViewComponent } from './test-details/question-view/question-view.component'; +import { TrueFalseComponent } from './test-details/question-view/true-false/true-false.component'; +import { SingleChoiceComponent } from './test-details/question-view/single-choice/single-choice.component'; +import { MultipleChoiceComponent } from './test-details/question-view/multiple-choice/multiple-choice.component'; +import { PuzzleComponent } from './test-details/question-view/puzzle/puzzle.component'; +import { GapsComponent } from './test-details/question-view/gaps/gaps.component'; +import { PairsComponent } from './test-details/question-view/pairs/pairs.component'; +import { SharedModule } from '../shared/shared.module'; +import { DialogModule } from 'primeng/dialog'; +import { ConfirmDialogModule } from 'primeng/confirmdialog'; + +@NgModule({ + imports: [ + BrowserModule, + FormsModule, + HttpModule, + HttpClientModule, + RouterModule, + AgGridModule.withComponents([]), + SharedModule, + DialogModule, + ConfirmDialogModule + ], + declarations: [ + TestMakerComponent, + TrueFalseQuestionComponent, + SingleChoiceQuestionComponent, + MultipleChoiceQuestionComponent, + PuzzleQuestionComponent, + GapsQuestionComponent, + PairsQuestionComponent, + TestsListComponent, + TestEditComponent, + TestDetailsComponent, + QuestionViewComponent, + TrueFalseComponent, + SingleChoiceComponent, + MultipleChoiceComponent, + PuzzleComponent, + GapsComponent, + PairsComponent + ], + providers: [TestsService], +}) +export class TestsModule { } diff --git a/src/app/tests/tests.service.spec.ts b/src/app/tests/tests.service.spec.ts new file mode 100644 index 0000000..158bb21 --- /dev/null +++ b/src/app/tests/tests.service.spec.ts @@ -0,0 +1,23 @@ +import { TestBed, inject } from '@angular/core/testing'; + +import { TestsService } from './tests.service'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { RouterTestingModule } from '@angular/router/testing'; +import { HttpClientModule } from '@angular/common/http'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { FormsModule } from '@angular/forms'; +import { AuthenticationService } from '../authentication.service'; + +describe('TestsService', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + schemas: [NO_ERRORS_SCHEMA], + imports: [ RouterTestingModule, HttpClientModule, MatSnackBarModule, FormsModule], + providers: [TestsService, AuthenticationService] + }); + }); + + it('should be created', inject([TestsService], (service: TestsService) => { + expect(service).toBeTruthy(); + })); +}); diff --git a/src/app/tests/tests.service.ts b/src/app/tests/tests.service.ts new file mode 100644 index 0000000..dbd10a4 --- /dev/null +++ b/src/app/tests/tests.service.ts @@ -0,0 +1,122 @@ +import { Injectable } from '@angular/core'; +import { HttpClient, HttpHeaders, HttpRequest, HttpEvent } from '@angular/common/http'; +import { Http, Response, Headers, RequestOptions } from '@angular/http'; +import { Observable } from 'rxjs/Observable'; +import { of } from 'rxjs/observable/of'; +import 'rxjs/add/operator/map'; +import { Router } from '@angular/router'; +import { AuthenticationService } from '../authentication.service'; +import { MatSnackBar } from '@angular/material/snack-bar'; + +@Injectable() +export class TestsService { + + // tslint:disable-next-line:max-line-length + constructor(private httpClient: HttpClient, private router: Router, + private authenticationService: AuthenticationService, + public snackBar: MatSnackBar) { + this.setHeaders(); + } + + private headers; + + setHeaders() { + if (localStorage.getItem('currentUser')) { + this.headers = new HttpHeaders({ + 'Content-Type': 'application/json', + 'Authorization': '' + this.authenticationService.getToken() + }); + } else { + this.headers = new HttpHeaders({ + 'Content-Type': 'application/json' + }); + } + } + + add(body) { + const url = 'tests/'; + this.sendData(url, body); + } + + edit(body) { + const url = 'tests/'; + this.putData(url, body); + } + + putData(url, body) { + this.setHeaders(); + this.httpClient.put(url, body, { headers: this.headers, observe: 'response' }) + .subscribe( + data => { this.sendResponse(data); }, + error => { + this.snackBar.open('Coś poszło nie tak. Spróbuj ponownie później.', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } + ); + } + + sendData(url, body) { + this.setHeaders(); + this.httpClient.post(url, body, { headers: this.headers, observe: 'response' }) + .subscribe( + data => { this.sendResponse(data); }, + error => { + this.snackBar.open('Coś poszło nie tak. Spróbuj ponownie później.', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } + ); + } + + sendResponse(data) { + if (data.status === 200) { + this.snackBar.open('Operacja przebiegła pomyślnie!', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-success'] }); + this.router.navigate(['tests']); + } else { + this.snackBar.open('Coś poszło nie tak. Spróbuj ponownie później.', null, + { duration: 3000, verticalPosition: 'top', panelClass: ['snackbar-error'] }); + } + } + + getTest(id): Observable { + this.setHeaders(); + return this.httpClient.get('tests/' + id + '/', { headers: this.headers }); + } + + getTestWithoutAnswers(id): Observable { + this.setHeaders(); + return this.httpClient.get('tests/' + id + '/solve', { headers: this.headers }); + } + + getUserTests(): Observable { + const user = JSON.parse(localStorage.getItem('currentUser')); + this.setHeaders(); + return this.httpClient.get('tests?owner=' + user.username, { headers: this.headers }); + } + + getTests(): Observable { + this.setHeaders(); + return this.httpClient.get('tests?permission=public', { headers: this.headers }); + } + + removeTest(id): Observable { + this.setHeaders(); + return this.httpClient.delete('tests/' + id + '/', { headers: this.headers }); + } + + verifyAnswer(id, body): Observable { + this.setHeaders(); + return this.httpClient.post('tests/' + id + '/questions/verify', body, { headers: this.headers }); + } + + sendResult(id, points, currentUser): Observable { + const body = { id: id, owner: currentUser, userScore: points }; + this.setHeaders(); + return this.httpClient.post('tests/results', body, { headers: this.headers }); + } + + getResult(id, currentUser): Observable { + this.setHeaders(); + return this.httpClient.get('tests/results/max?id=' + id + '&username=' + currentUser, { headers: this.headers }); + } +} diff --git a/src/app/tests/true-false-question/true-false-question.component.css b/src/app/tests/true-false-question/true-false-question.component.css new file mode 100644 index 0000000..1fb2393 --- /dev/null +++ b/src/app/tests/true-false-question/true-false-question.component.css @@ -0,0 +1,24 @@ +.block { + display: block; +} + +.container { + background-color: #22272a; + padding: 30px; + margin-top: 2rem; + margin-bottom: 1rem; + text-align: center; +} + +.wrapper-add { + width: 100%; + padding: 30px; + display: flex; + justify-content: center; + align-content: center; +} + +.wrapper { + width: 100%; + margin-bottom: 5rem; +} diff --git a/src/app/tests/true-false-question/true-false-question.component.html b/src/app/tests/true-false-question/true-false-question.component.html new file mode 100644 index 0000000..e9cfe3e --- /dev/null +++ b/src/app/tests/true-false-question/true-false-question.component.html @@ -0,0 +1,34 @@ +
+
+
+

Pytanie typu prawda/fałsz

+
+
+ + +
+

Zaznacz prawidłową odpowiedź:

+ +
+ +
+
+ + + +
+
+
+
\ No newline at end of file diff --git a/src/app/tests/true-false-question/true-false-question.component.spec.ts b/src/app/tests/true-false-question/true-false-question.component.spec.ts new file mode 100644 index 0000000..4ab46d0 --- /dev/null +++ b/src/app/tests/true-false-question/true-false-question.component.spec.ts @@ -0,0 +1,35 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TrueFalseQuestionComponent } from './true-false-question.component'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { RouterTestingModule } from '@angular/router/testing'; +import { HttpClientModule } from '@angular/common/http'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { FormsModule } from '@angular/forms'; +import { TestsService } from '../tests.service'; +import { AuthenticationService } from '../../authentication.service'; + +describe('TrueFalseQuestionComponent', () => { + let component: TrueFalseQuestionComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ TrueFalseQuestionComponent ], + schemas: [NO_ERRORS_SCHEMA], + imports: [ RouterTestingModule, HttpClientModule, MatSnackBarModule, FormsModule], + providers: [TestsService, AuthenticationService] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(TrueFalseQuestionComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/tests/true-false-question/true-false-question.component.ts b/src/app/tests/true-false-question/true-false-question.component.ts new file mode 100644 index 0000000..f8c2c4d --- /dev/null +++ b/src/app/tests/true-false-question/true-false-question.component.ts @@ -0,0 +1,94 @@ +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; + +@Component({ + selector: 'app-true-false-question', + templateUrl: './true-false-question.component.html', + styleUrls: ['./true-false-question.component.css'] +}) +export class TrueFalseQuestionComponent implements OnInit { + + @Input() content: Object = {}; + @Input() edit: Boolean; + + isChecked: Boolean = false; + id: Number = null; + + @Output() add: EventEmitter = new EventEmitter(); + @Output() editing: EventEmitter = new EventEmitter(); + + constructor() {} + + ngOnInit() { + if (this.edit) { + this.content['edit'] = true; + this.id = this.content['content']['id']; + } else { + this.content = {}; + this.content['content'] = { + id: null, + type: 'true-false', + question: '', + answers: [ + {id: null, content: 'Prawda', is_good: false}, + {id: null, content: 'Fałsz', is_good: false} + ], + points: 1 + }; + this.content['edit'] = false; + } + } + + changeCheckbox(i: number): void { + if (i === 0) { + this.isChecked = true; + } + } + + addTable(value: any): void { + if (this.isChecked) { + this.content['content'] = { + id: this.id, + type: 'true-false', + question: value['question'], + answers: [ + {id: this.content['content']['answers'][0]['id'], content: 'Prawda', is_good: true}, + {id: this.content['content']['answers'][1]['id'], content: 'Fałsz', is_good: false} + ], + points: value['points'] + }; + } else { + this.content['content'] = { + id: this.id, + type: 'true-false', + question: value['question'], + answers: [ + {id: this.content['content']['answers'][0]['id'], content: 'Prawda', is_good: false}, + {id: this.content['content']['answers'][1]['id'], content: 'Fałsz', is_good: true} + ], + points: value['points'] + }; + } + if (this.edit) { + this.editing.emit(this.content); + } else { + this.add.emit(this.content); + } + this.clear(); + } + + empty(): void { + if (this.edit) { + this.editing.emit({}); + } else { + this.add.emit({}); + } + this.clear(); + } + + clear(): void { + this.content = {}; + this.edit = false; + this.isChecked = false; + } + +} diff --git a/src/app/user.ts b/src/app/user.ts new file mode 100644 index 0000000..3757ec9 --- /dev/null +++ b/src/app/user.ts @@ -0,0 +1,8 @@ +export class User { + username: string; + password: string; + constructor(username: string, password: string) { + this.username = username; + this.password = password; + } +} diff --git a/src/app/user/bagdes/bagdes.component.css b/src/app/user/bagdes/bagdes.component.css new file mode 100644 index 0000000..662beee --- /dev/null +++ b/src/app/user/bagdes/bagdes.component.css @@ -0,0 +1,62 @@ +.centerMargin{ + margin: 0 auto; +} + + +.wrapper{ + width: 100%; + padding: 30px; + min-height: 100%; + margin-bottom: 40px; +} + +.content{ + background-color: #181616; + padding: 30px; + margin-top: 2rem; + margin-bottom: 2rem; + text-align: center; +} + +.locked{ + opacity: 0.3; +} + +.badges{ + display: flex; + flex-flow: row wrap; + flex-direction: row; + flex-wrap: wrap; + justify-content: center; + align-items: center; + align-content: center; +} + +.badge-text{ + flex-grow: 2; + margin: auto; + display: flex; +} + +p { + margin: auto; + text-align: center; +} + +.badge{ + display: flex; + justify-content: space-between; + width: 300px; + border-style: solid; + border-color: #cea856; + padding-top: 5px; + padding-bottom: 5px; + +} + +.image{ + border-style: solid; + width: 60px; + height: 60px; + padding: 5px; +} \ No newline at end of file diff --git a/src/app/user/bagdes/bagdes.component.html b/src/app/user/bagdes/bagdes.component.html new file mode 100644 index 0000000..caefd9d --- /dev/null +++ b/src/app/user/bagdes/bagdes.component.html @@ -0,0 +1,14 @@ +
+
+

Twoje odznaki!

+ +

+
+
+ + image

{{badge.badgeName}}

+
+
+
+
+
\ No newline at end of file diff --git a/src/app/user/bagdes/bagdes.component.spec.ts b/src/app/user/bagdes/bagdes.component.spec.ts new file mode 100644 index 0000000..f9c00a3 --- /dev/null +++ b/src/app/user/bagdes/bagdes.component.spec.ts @@ -0,0 +1,35 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { BagdesComponent } from './bagdes.component'; +import { UserService } from '../user.service'; +import { AuthenticationService } from '../../authentication.service'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { RouterTestingModule } from '@angular/router/testing'; +import { FormsModule } from '@angular/forms'; +import { HttpClientModule } from '@angular/common/http'; +import { MatSnackBarModule, MatFormFieldModule } from '@angular/material'; + +describe('BagdesComponent', () => { + let component: BagdesComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ BagdesComponent ], + providers: [UserService, AuthenticationService], + schemas: [NO_ERRORS_SCHEMA], + imports: [ RouterTestingModule, FormsModule, HttpClientModule, MatSnackBarModule, MatFormFieldModule] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(BagdesComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/user/bagdes/bagdes.component.ts b/src/app/user/bagdes/bagdes.component.ts new file mode 100644 index 0000000..38bb6bf --- /dev/null +++ b/src/app/user/bagdes/bagdes.component.ts @@ -0,0 +1,32 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { UserService } from '../user.service'; +import { Subscription } from 'rxjs/Subscription'; +import { Router } from '@angular/router'; + +@Component({ + selector: 'app-bagdes', + templateUrl: './bagdes.component.html', + styleUrls: ['./bagdes.component.css'] +}) +export class BagdesComponent implements OnInit, OnDestroy { + + badges; + badgesSubscription: Subscription; + constructor(private userService: UserService, private router: Router) { } + + ngOnInit() { + this.badgesSubscription = this.userService.getBadges().subscribe(val => { + this.badges = val; + }); + } + + goBack() { + const usr = JSON.parse(localStorage.getItem('currentUser')); + this.router.navigate(['/profile/', usr.username]); + } + + ngOnDestroy() { + if (this.badgesSubscription) {this.badgesSubscription.unsubscribe(); } + } + +} diff --git a/src/app/user/edit-user/edit-user.component.css b/src/app/user/edit-user/edit-user.component.css new file mode 100644 index 0000000..e710b66 --- /dev/null +++ b/src/app/user/edit-user/edit-user.component.css @@ -0,0 +1,30 @@ +.centerMargin{ + margin: 0 auto; +} + +input:invalid{ + border-color: red; +} + +input:valid{ + border-color: green; +} + +input.ng-untouched { + border-color: rgb(206, 212, 218); +} + +.wrapper{ + width: 100%; + padding: 30px; + min-height: 100%; + margin-bottom: 40px; +} + +.content{ + background-color: #181616; + padding: 30px; + margin-top: 2rem; + margin-bottom: 2rem; + text-align: center; +} \ No newline at end of file diff --git a/src/app/user/edit-user/edit-user.component.html b/src/app/user/edit-user/edit-user.component.html new file mode 100644 index 0000000..0384546 --- /dev/null +++ b/src/app/user/edit-user/edit-user.component.html @@ -0,0 +1,72 @@ +
+
+

Edytuj profil

+
Edycja udana!
+
{{errorMessage}}
+
Hasła muszą być takie same.
+
+
+
+
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+ +
+
+
+
+
+
+
\ No newline at end of file diff --git a/src/app/user/edit-user/edit-user.component.spec.ts b/src/app/user/edit-user/edit-user.component.spec.ts new file mode 100644 index 0000000..d2fa2ab --- /dev/null +++ b/src/app/user/edit-user/edit-user.component.spec.ts @@ -0,0 +1,34 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { EditUserComponent } from './edit-user.component'; +import { UserService } from '../user.service'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { RouterTestingModule } from '@angular/router/testing'; +import { HttpClientModule } from '@angular/common/http'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { FormsModule } from '@angular/forms'; +import { AuthenticationService } from '../../authentication.service'; + +describe('EditUserComponent', () => { + let component: EditUserComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ EditUserComponent ], + schemas: [NO_ERRORS_SCHEMA], + imports: [ RouterTestingModule, HttpClientModule, MatSnackBarModule, FormsModule], + providers: [UserService, AuthenticationService] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(EditUserComponent); + component = fixture.componentInstance; + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/user/edit-user/edit-user.component.ts b/src/app/user/edit-user/edit-user.component.ts new file mode 100644 index 0000000..92b4f93 --- /dev/null +++ b/src/app/user/edit-user/edit-user.component.ts @@ -0,0 +1,112 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Router } from '@angular/router'; +import { UserService } from '../user.service'; +import { Subscription } from 'rxjs/Subscription'; + +declare global { + interface User { + id: Number; + email: string; + username: string; + password: string; + name: string; + surname: string; + } +} + +@Component({ + selector: 'app-edit-user', + templateUrl: './edit-user.component.html', + styleUrls: ['./edit-user.component.css'] +}) +export class EditUserComponent implements OnInit, OnDestroy { + editStatus = false; + invalidEdit = false; + invalidPassword = false; + errorMessage = ''; + editSub: Subscription; + private userProfileSub: Subscription; + private relogSub: Subscription; + private user: User = { + id: 0, + email: '', + username: '', + password: '', + name: '', + surname: '' + }; + + constructor(private router: Router, private userService: UserService) { } + + edit(value: any) { + if (value.password === value.password2) { + const body = { + id: this.user.id, + email: value.email, + username: value.login, + password: value.password, + name: value.name, + surname: value.surname + }; + this.editSub = this.userService.edit(body).subscribe(data => { + this.invalidPassword = false; + if (data === 'Login zajety') { + this.errorMessage = 'Login zajęty. Wybierz inny.'; + this.invalidEdit = true; + } else if (data === 'Email zajety') { + this.errorMessage = 'E-mail zajęty. Wybierz inny.'; + this.invalidEdit = true; + } else { + const usernameChange = JSON.parse(localStorage.getItem('currentUser')); + usernameChange.username = value.login; + this.relogSub = this.userService.login(body.username, body.password).subscribe( + d => { + this.editSub.unsubscribe(); + } + ); + const usernameChangeStr = JSON.stringify(usernameChange); + localStorage.setItem('currentUser', usernameChangeStr); + this.editStatus = true; + this.invalidEdit = false; + } + }, + error => { + this.errorMessage = 'Wystąpił błąd. Spróbuj ponownie później.'; + this.invalidEdit = true; + this.invalidPassword = false; + }, + () => { } + ); + } else { + this.invalidPassword = true; + this.invalidEdit = false; + } + } + + getUserInfo() { + this.userProfileSub = this.userService.getUserProfile().subscribe( + (d: User) => { + this.user.id = d.id; + this.user.email = d.email; + this.user.username = d.username; + this.user.password = d.password; + this.user.name = d.name; + this.user.surname = d.surname; + } + ); + + } + + ngOnInit() { + this.getUserInfo(); + } + + ngOnDestroy() { + if (this.editSub) { this.userProfileSub.unsubscribe(); } + if (this.relogSub) { this.relogSub.unsubscribe(); } + if (this.editSub) { + this.editSub.unsubscribe(); + } + } + +} diff --git a/src/app/user/register/register.component.css b/src/app/user/register/register.component.css new file mode 100644 index 0000000..e710b66 --- /dev/null +++ b/src/app/user/register/register.component.css @@ -0,0 +1,30 @@ +.centerMargin{ + margin: 0 auto; +} + +input:invalid{ + border-color: red; +} + +input:valid{ + border-color: green; +} + +input.ng-untouched { + border-color: rgb(206, 212, 218); +} + +.wrapper{ + width: 100%; + padding: 30px; + min-height: 100%; + margin-bottom: 40px; +} + +.content{ + background-color: #181616; + padding: 30px; + margin-top: 2rem; + margin-bottom: 2rem; + text-align: center; +} \ No newline at end of file diff --git a/src/app/user/register/register.component.html b/src/app/user/register/register.component.html new file mode 100644 index 0000000..0ca7a97 --- /dev/null +++ b/src/app/user/register/register.component.html @@ -0,0 +1,70 @@ +
+
+

Rejestracja

+
Rejestracja udana!
+
Hasła muszą być takie same.
+
{{errorMessage}}
+
+
+
+
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+ +
+
+
+
+
+
+
\ No newline at end of file diff --git a/src/app/user/register/register.component.spec.ts b/src/app/user/register/register.component.spec.ts new file mode 100644 index 0000000..f8b345f --- /dev/null +++ b/src/app/user/register/register.component.spec.ts @@ -0,0 +1,35 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { RegisterComponent } from './register.component'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { RouterTestingModule } from '@angular/router/testing'; +import { HttpClientModule } from '@angular/common/http'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { FormsModule } from '@angular/forms'; +import { UserService } from '../user.service'; +import { AuthenticationService } from '../../authentication.service'; + +describe('RegisterComponent', () => { + let component: RegisterComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ RegisterComponent ], + schemas: [NO_ERRORS_SCHEMA], + imports: [ RouterTestingModule, HttpClientModule, MatSnackBarModule, FormsModule], + providers: [UserService, AuthenticationService] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(RegisterComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/user/register/register.component.ts b/src/app/user/register/register.component.ts new file mode 100644 index 0000000..0847c72 --- /dev/null +++ b/src/app/user/register/register.component.ts @@ -0,0 +1,63 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { Subscription } from 'rxjs/Subscription'; + +import { UserService } from '../user.service'; + +@Component({ + selector: 'app-register', + templateUrl: './register.component.html', + styleUrls: ['./register.component.css'] +}) +export class RegisterComponent implements OnInit, OnDestroy { + + registerStatus = false; + invalidRegister = false; + invalidPassword = false; + errorMessage = ''; + signInSub: Subscription; + constructor(private userService: UserService) { } + + onSubmit(value: any) { + if (value.password === value.password2) { + this.signInSub = this.userService.register({ + email: value.email, + username: value.login, + password: value.password, + name: value.name, + surname: value.surname + }).subscribe( + data => { + this.invalidPassword = false; + if (data === 'Login zajety') { + this.errorMessage = 'Login zajęty. Wybierz inny.'; + this.invalidRegister = true; + } else if (data === 'Email zajety') { + this.errorMessage = 'E-mail zajęty. Wybierz inny.'; + this.invalidRegister = true; + } else { + this.registerStatus = true; + this.invalidRegister = false; + } + }, + error => { + this.errorMessage = 'Wystąpił błąd. Spróbuj ponownie później.'; + this.invalidRegister = true; + }, + () => { } + ); + } else { + this.invalidPassword = true; + this.invalidRegister = true; + } + } + + ngOnInit() { + } + + ngOnDestroy() { + if (this.signInSub) { + this.signInSub.unsubscribe(); + } + } + +} diff --git a/src/app/user/user.module.ts b/src/app/user/user.module.ts new file mode 100644 index 0000000..1f1f518 --- /dev/null +++ b/src/app/user/user.module.ts @@ -0,0 +1,21 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { RegisterComponent } from './register/register.component'; +import { UserComponent } from './user/user.component'; +import { FormsModule } from '@angular/forms'; +import { HttpModule } from '@angular/http'; +import { UserService } from './user.service'; +import { EditUserComponent } from './edit-user/edit-user.component'; +import { BagdesComponent } from './bagdes/bagdes.component'; + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + HttpModule + ], + declarations: [RegisterComponent, UserComponent, EditUserComponent, BagdesComponent], + exports: [RegisterComponent], + providers: [UserService] +}) +export class UserModule { } diff --git a/src/app/user/user.service.spec.ts b/src/app/user/user.service.spec.ts new file mode 100644 index 0000000..0717b50 --- /dev/null +++ b/src/app/user/user.service.spec.ts @@ -0,0 +1,23 @@ +import { TestBed, inject } from '@angular/core/testing'; + +import { UserService } from './user.service'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { RouterTestingModule } from '@angular/router/testing'; +import { HttpClientModule } from '@angular/common/http'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { FormsModule } from '@angular/forms'; +import { AuthenticationService } from '../authentication.service'; + +describe('UserService', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + schemas: [NO_ERRORS_SCHEMA], + imports: [ RouterTestingModule, HttpClientModule, MatSnackBarModule, FormsModule], + providers: [UserService, AuthenticationService] + }); + }); + + it('should be created', inject([UserService], (service: UserService) => { + expect(service).toBeTruthy(); + })); +}); diff --git a/src/app/user/user.service.ts b/src/app/user/user.service.ts new file mode 100644 index 0000000..bc3e2c5 --- /dev/null +++ b/src/app/user/user.service.ts @@ -0,0 +1,62 @@ +import { Injectable } from '@angular/core'; +import { HttpClient, HttpHeaders, HttpRequest, HttpEvent } from '@angular/common/http'; +import { Observable } from 'rxjs/Observable'; +import { AuthenticationService } from '../authentication.service'; + +@Injectable() +export class UserService { + + constructor(private httpClient: HttpClient, private authenticationService: AuthenticationService) { } + + private headers; + + setHeaders() { + if (localStorage.getItem('currentUser')) { + this.headers = new HttpHeaders({ + 'Content-Type': 'application/json', + 'Authorization': '' + this.authenticationService.getToken() + }); + } else { + this.headers = new HttpHeaders({ + 'Content-Type': 'application/json' + }); + } + } + + register(body) { + const headers = new HttpHeaders({ 'Content-Type': 'application/json' }); + return this.httpClient.post('user/register', body, { headers: headers, responseType: 'text' }); + } + + getBadges() { + this.setHeaders(); + return this.httpClient.get('user/badges', {headers: this.headers}); + } + + getUserProfile() { + this.setHeaders(); + const currentUser = JSON.parse(localStorage.getItem('currentUser')); + return this.httpClient.get('user/' + currentUser.username, { headers: this.headers }); + } + + edit(body) { + this.setHeaders(); + const currentUser = JSON.parse(localStorage.getItem('currentUser')); + return this.httpClient.put('user/info/update', body, { headers: this.headers, responseType: 'text' }); + } + + login(username: string, password: string) { + return this.httpClient.post('login', { password: password, username: username }, { headers: this.headers, observe: 'response' }) + .map((response) => { + const token = response.headers.get('authorization'); + if (token) { + // store username and jwt token w local storage aby nie wylogowało przy zmianie stron + localStorage.setItem('currentUser', JSON.stringify({ username: username, authorization: token })); + return true; + } else { + return false; + } + }); + } + +} diff --git a/src/app/user/user/user.component.css b/src/app/user/user/user.component.css new file mode 100644 index 0000000..7d0fb54 --- /dev/null +++ b/src/app/user/user/user.component.css @@ -0,0 +1,34 @@ +.wrapper{ + width: 100%; + padding: 30px; + min-height: 100%; + margin-bottom: 40px; +} + +.content{ + background-color: #181616; + padding: 30px; + margin-top: 2rem; + margin-bottom: 2rem; + text-align: center; + margin-left: auto; + margin-right: auto; +} + +.profile{ + display: flex; + flex-direction: row; + flex-wrap: nowrap; + align-items: center; + justify-content: center; +} + +.profile > div{ + display: flex; + flex-direction: column; + flex-wrap: nowrap; + align-items: left; + justify-content: center; + margin: 20px; + text-align: left; +} diff --git a/src/app/user/user/user.component.html b/src/app/user/user/user.component.html new file mode 100644 index 0000000..a8f2b1c --- /dev/null +++ b/src/app/user/user/user.component.html @@ -0,0 +1,22 @@ +
+
+

Twój profil

+ + + +
+
+
Login:
+
E-mail:
+
Imię:
+
Nazwisko:
+
+
+
{{user.username}}
+
{{user.email}}
+
{{user.name}}
+
{{user.surname}}
+
+
+
+
\ No newline at end of file diff --git a/src/app/user/user/user.component.spec.ts b/src/app/user/user/user.component.spec.ts new file mode 100644 index 0000000..0f23fad --- /dev/null +++ b/src/app/user/user/user.component.spec.ts @@ -0,0 +1,34 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { UserComponent } from './user.component'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { RouterTestingModule } from '@angular/router/testing'; +import { HttpClientModule } from '@angular/common/http'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { FormsModule } from '@angular/forms'; +import { UserService } from '../user.service'; +import { AuthenticationService } from '../../authentication.service'; + +describe('UserComponent', () => { + let component: UserComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ UserComponent ], + schemas: [NO_ERRORS_SCHEMA], + imports: [ RouterTestingModule, HttpClientModule, MatSnackBarModule, FormsModule], + providers: [UserService, AuthenticationService] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(UserComponent); + component = fixture.componentInstance; + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/user/user/user.component.ts b/src/app/user/user/user.component.ts new file mode 100644 index 0000000..f79f059 --- /dev/null +++ b/src/app/user/user/user.component.ts @@ -0,0 +1,62 @@ +import { Component, OnInit } from '@angular/core'; +import { UserService } from '../user.service'; +import { Subscription } from 'rxjs/Subscription'; +import { Router } from '@angular/router'; + +declare global { + interface User { + id: Number; + email: string; + username: string; + password: string; + name: string; + surname: string; + } +} + +@Component({ + selector: 'app-user', + templateUrl: './user.component.html', + styleUrls: ['./user.component.css'] +}) +export class UserComponent implements OnInit { + + private userProfileSub: Subscription; + user: User = { + id: 0, + email: '', + username: '', + password: '', + name: '', + surname: '' + }; + + constructor(private userService: UserService, private router: Router) { } + + getUserInfo() { + this.userProfileSub = this.userService.getUserProfile().subscribe( + (d: User) => { + this.user.id = d.id; + this.user.email = d.email; + this.user.username = d.username; + this.user.password = d.password; + this.user.name = d.name; + this.user.surname = d.surname; + } + ); + + } + + edit() { + this.router.navigate(['edit-profile']); + } + + gotoBadges() { + this.router.navigate(['badges']); + } + + ngOnInit() { + this.getUserInfo(); + } + +} diff --git a/src/app/work-in-progress/work-in-progress.component.css b/src/app/work-in-progress/work-in-progress.component.css new file mode 100644 index 0000000..6f435b0 --- /dev/null +++ b/src/app/work-in-progress/work-in-progress.component.css @@ -0,0 +1,16 @@ +.wrapper{ + width: 100%; + padding: 30px; + min-height: 100%; + margin-bottom: 40px; +} + +.content{ + background-color: #181616; + padding: 30px; + margin-top: 2rem; + margin-bottom: 2rem; + text-align: center; + margin-left: auto; + margin-right: auto; +} \ No newline at end of file diff --git a/src/app/work-in-progress/work-in-progress.component.html b/src/app/work-in-progress/work-in-progress.component.html new file mode 100644 index 0000000..4551ee5 --- /dev/null +++ b/src/app/work-in-progress/work-in-progress.component.html @@ -0,0 +1,5 @@ +
+
+ Strona w trakcie tworzenia. +
+
\ No newline at end of file diff --git a/src/app/work-in-progress/work-in-progress.component.spec.ts b/src/app/work-in-progress/work-in-progress.component.spec.ts new file mode 100644 index 0000000..8c2ca3f --- /dev/null +++ b/src/app/work-in-progress/work-in-progress.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { WorkInProgressComponent } from './work-in-progress.component'; + +describe('WorkInProgressComponent', () => { + let component: WorkInProgressComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ WorkInProgressComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(WorkInProgressComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/work-in-progress/work-in-progress.component.ts b/src/app/work-in-progress/work-in-progress.component.ts new file mode 100644 index 0000000..5c74f83 --- /dev/null +++ b/src/app/work-in-progress/work-in-progress.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-work-in-progress', + templateUrl: './work-in-progress.component.html', + styleUrls: ['./work-in-progress.component.css'] +}) +export class WorkInProgressComponent implements OnInit { + + constructor() { } + + ngOnInit() { + } + +} diff --git a/src/assets/.gitkeep b/src/assets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/assets/cave2.jpg b/src/assets/cave2.jpg new file mode 100644 index 0000000..fd50614 Binary files /dev/null and b/src/assets/cave2.jpg differ diff --git a/src/assets/favicon.ico b/src/assets/favicon.ico new file mode 100644 index 0000000..a4072df Binary files /dev/null and b/src/assets/favicon.ico differ diff --git a/src/assets/icon.png b/src/assets/icon.png new file mode 100644 index 0000000..f45b676 Binary files /dev/null and b/src/assets/icon.png differ diff --git a/src/assets/js/default_vfs.js b/src/assets/js/default_vfs.js new file mode 100644 index 0000000..8dc793a --- /dev/null +++ b/src/assets/js/default_vfs.js @@ -0,0 +1,4 @@ +(function (jsPDFAPI) { +"use strict"; +jsPDFAPI.addFileToVFS('DidactGothic-Regular.ttf','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AAMAyP/EAMn/xADK/8QAAwDI/8QAyf/EAMr/xAADAMj/xADJ/8QAyv/EAAMAyP/EAMn/xADK/8QAAwDI/8QAyf/EAMr/xAADAMj/xADJ/8QAyv/EAAMAyP/EAMn/xADK/8QAAwDI/8QAyf/EAMr/xAADAMj/9gDJ//YAyv/2AAMAyP/2AMn/9gDK//YAAwDI//YAyf/2AMr/9gADAMj/9gDJ//YAyv/2AAEA7v/iAAEA7v/iAAEA7v/iAAEA7v/iAAEA7v/iAAEA7v/iAAEA7v/iAAEA7v/iAAEA7v/iAAMAyP+mAMn/pgDK/6YAAwDI/6YAyf+mAMr/pgADAMj/pgDJ/6YAyv+mAAMAyP+mAMn/pgDK/6YAAwDI/6YAyf+mAMr/pgADAMj/pgDJ/6YAyv+mAAMAyP+mAMn/pgDK/6YAAwDI/6YAyf+mAMr/pgADAMj/pgDJ/6YAyv+mAAEA7v/iAAEA7v/iAAEA7v/iAAEA7v/iAAEA7v/iAAEA7v/iAAEA7v/iAAEA7v/iAAEA7v/iAAEA7v/iAAEA7v/iAAEA7v/iAAEA7v/iAAEA7v/iAAEA7v/iAAEA7v/iAAEA7v/iAAEA7v/iAAEA7v/iAAEA7v/iAAEA7v/iAAEA7v/iAAEA7v/iAAEA7v/iAAEA7v/iAAEA7gAUAAEA7gAUAAEA7gAUAAEA7gAUAAEA7gAUAAEA7gAUAAEA7gAUAAEA7gAUAAEA7gAUAAMAyAAUAMkAFADKABQAAwDIABQAyQAUAMoAFAADAMgAFADJABQAygAUAAMAyAAUAMkAFADKABQAAwDIABQAyQAUAMoAFAADAMgAFADJABQAygAUAAMAyP+SAMn/kgDK/5IAAwDI/5IAyf+SAMr/kgADAMj/kgDJ/5IAyv+SAAMAyP+SAMn/kgDK/5IAAwDI/5IAyf+SAMr/kgADAMj/kgDJ/5IAyv+SAAMAyP+SAMn/kgDK/5IAAwDI/5IAyf+SAMr/kgADAMj/kgDJ/5IAyv+SAAMAyP+SAMn/kgDK/5IAAwDI/5IAyf+SAMr/kgADAMj/kgDJ/5IAyv+SAAMAyP+SAMn/kgDK/5IAAwDI/5IAyf+SAMr/kgADAMj/kgDJ/5IAyv+SAAMAyP+SAMn/kgDK/5IAAwDI/5IAyf+SAMr/kgADAMj/kgDJ/5IAyv+SAAMAyP+SAMn/kgDK/5IAAwDI/6YAyf+mAMr/pgADAMj/pgDJ/6YAyv+mAAMAyP+6AMn/ugDK/7oAAwDI/7oAyf+6AMr/ugADAMj/ugDJ/7oAyv+6AAMAyP+6AMn/ugDK/7oAAwDI/7oAyf+6AMr/ugADAMj/ugDJ/7oAyv+6AAMAyP+6AMn/ugDK/7oAAwDI/6YAyf+mAMr/pgADAMj/pgDJ/6YAyv+mAAMAyP+mAMn/pgDK/6YAAwDI/6YAyf+mAMr/pgADAMj/pgDJ/6YAyv+mAAMAyP+mAMn/pgDK/6YAAwDI/6YAyf+mAMr/pgADAMj/pgDJ/6YAyv+mAAMAyP+mAMn/pgDK/6YAAwDI/6YAyf+mAMr/pgADAMj/pgDJ/6YAyv+mAAMAyP+mAMn/pgDK/6YAAwDI/6YAyf+mAMr/pgADAMj/pgDJ/6YAyv+mAAMAyP+mAMn/pgDK/6YAAwDI/6YAyf+mAMr/pgADAMj/pgDJ/6YAyv+mAAMAyP+mAMn/pgDK/6YAAwDI/6YAyf+mAMr/pgADAMj/kgDJ/5IAyv+SAAMAyP+SAMn/kgDK/5IAAwDI/5IAyf+SAMr/kgADAMj/kgDJ/5IAyv+SAAMAyP+SAMn/kgDK/5IAAwDI/5IAyf+SAMr/kgADAMj/kgDJ/5IAyv+SAAMAyP+SAMn/kgDK/5IAAwDI/5IAyf+SAMr/kgADAMj/kgDJ/5IAyv+SAAMAyP+SAMn/kgDK/5IAAwDI/5IAyf+SAMr/kgADAMj/kgDJ/5IAyv+SAAMAyP+SAMn/kgDK/5IAAwDI/5IAyf+SAMr/kgADAMj/kgDJ/5IAyv+SAAMAyP+SAMn/kgDK/5IAAwDI/5IAyf+SAMr/kgADAMj/kgDJ/5IAyv+SAAMAyP+SAMn/kgDK/5IAAwDI/5IAyf+SAMr/kgADAMj/pgDJ/6YAyv+mAAMAyP+cAMn/nADK/5wAAwDI/5wAyf+cAMr/nAADAMj/nADJ/5wAyv+cAAMAyP+cAMn/nADK/5wAAwDI/5wAyf+cAMr/nAADAMj/nADJ/5wAyv+cAAMAyP+cAMn/nADK/5wAAwDI/5wAyf+cAMr/nAADAMj/ugDJ/7oAyv+6AAMAyP+6AMn/ugDK/7oAAwDI/7oAyf+6AMr/ugADAMj/ugDJ/7oAyv+6AAMAyP+6AMn/ugDK/7oAAwDI/7oAyf+6AMr/ugAD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BU4GXAZiBnQFTgVOBU4FTgZ6BU4FTgY+BU4GRAVOBU4FTgVOBoAFTgVOBU4FTgaABU4FTgVOBU4GhgVOBU4FTgVOBpIGjAVOBU4FTgaYBowFTgaeBU4GpAVOBU4GqgVOBrYGsAVOBrwGyAbOBsIG1AbaBU4G4AVOBU4G5gVOBuwFTgVOBvIFTgb+BvgFTgVOBU4HBAVOBU4FTgVOBwQFTgVOBwoFTgcQBU4FTgVOBU4HHAcWBU4FTgVOByIFTgVOBygFTgcuBU4FTgbmBU4G7AVOBU4HNAVOBzoFTgVOB0AHRgdMBU4FTgdAB0YHUgVOBU4HWAVOB14FTgVOB2QFTgVOB2oFTga8BsgGzgbCBtQFTgVOBlAGSgVOBU4FTgZ6BU4FTgY+BU4GRAVOBU4FTgVOBpIGjAVOBU4FTgaSBowFTgVOBU4HdgdwBU4GqgVOBrYGsAVOBuYFTgbsBU4FTgd8BU4HiAeCBU4HfAVOB4gHggVOBU4FTgccBxYFTgVOBU4HHAcWBU4HjgVOBU4HlAVOB0AHRgdMBU4FTgVOBU4GegVOBU4FTgVOBxwHFgVOBiwGMgY4BU4FTgYsBjIHmgVOBU4HoAVOB6YFTgVOBlwGYgZoBU4FTgVOBU4HrAVOBU4FTgVOB7IFTgVOBj4FTge4BU4FTgVOBU4HvgVOBU4FTgVOB8QFTgVOBrwGyAfKBsIG1Aa8BsgGzgbCBtQFTgVOB9AFTgVOBU4FTgfWBU4FTgVOBU4H3AVOBU4FTgVOB+IHFgVOBU4FTgZQBkoFTgVOBU4H6AVOBU4GqgbIBrYGwgbUB+4FTgf0BU4FTgf6CAAIBgVOBU4FTgVOCBIIDAVOBU4FTggYCAwFTggeCCQIKgVOBU4IHggkCDAFTgVOCB4IJAg2BU4FTgVOBU4IPAVOBU4IQgVOCEgFTgVOBU4FTghOBU4FTgVOBU4ITgVOBU4FTgVOCFQFTgVOBU4FTghgCFoFTgVOBU4IZghaBU4IbAh4CH4IcgiECIoFTgiQBU4FTgiWBU4InAVOBU4IogVOCKgFTgVOCKIFTgioBU4FTgVOBU4ItAiuBU4FTgVOCLoFTgVOCMAFTghgBU4FTgiWBU4InAVOBU4IxgVOCMwFTgVOCNII2AjeBU4FTgjSCNgI5AVOBU4I6gVOCPAFTgVOBvIFTgj8CPYFTghsCHgIfghy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CnYL2Ap2CnYL3gp2C+QKdgp2C+oKdgvwCnYKdguiC64LtAuoC7oL9gp2C/wKdgp2DAIKdgwODAgKdgwUCnYMGgp2CnYMIAp2DCYKdgp2DCwKdgp2CnYKdgtUC1oLYAp2CnYLcgt4C34Kdgp2C5AKdgucC5YKdgvAC8YLzAp2CnYLoguuC7QLqAu6DBQKdgwaCnYKdgwsCnYKdgp2CnYLwAvGDDIKdgp2DBQKdgw4CnYKdgtUC1oLYAp2CnYLVAtaC2AKdgp2C1QLWgtgCnYKdgtUC1oLYAp2CnYLVAtaC2AKdgp2C1QLWgtgCnYKdgtUC1oMPgp2CnYLVAtaDD4Kdgp2C1QLWgtgCnYKdgtUC1oLYAp2CnYLVAtaDEQKdgp2C1QLWgxKCnYKdgtUC1oLYAp2CnYLVAtaC2AKdgp2C1QLWgtgCnYKdgtUC1oLYAp2CnYLVAtaC2AKdgp2C1QLWgtgCnYKdgtUC1oLYAp2CnYLVAtaDD4Kdgp2C1QLWgw+CnYKdgtyC3gLfgp2CnYLcgt4C34Kdgp2C3ILeAt+CnYKdgtyC3gLfgp2CnYLcgt4C34Kdgp2C3ILeAt+CnYKdgtyC3gLfgp2CnYLcgt4C34Kdgp2C5AKdgucC5YKdguQCnYLnAuWCnYLkAp2C5wLlgp2C5AKdgucC5YKdguQCnYLnAuWCnYLkAp2C5wLlgp2C5AKdgxQC5YKdguQCnYMUAuWCnYLkAp2C5wLlgp2C5AKdgucC5YKdguQCnYLnAuWCnYLkAp2C5wLlgp2C5AKdgucC5YKdguQCnYLnAuWCnYLkAp2C5wLlgp2C5AKdgucC5YKdguQCnYLnAuWCnYLkAp2DFALlgp2C5AKdgxQC5YKdgvAC8YLzAp2CnYLwAvGC8wKdgp2C8ALxgvMCnYKdgvAC8YLzAp2CnYLwAvGC8wKdgp2C8ALxgvMCnYKdgvAC8YMVgp2CnYLwAvGDFYKdgp2C8ALxgvMCnYKdgvAC8YLzAp2CnYLwAvGDFwKdgp2C8ALxgxiCnYKdguiC64LtAuoC7oLoguuC7QLqAu6C6ILrgu0C6gLuguiC64LtAuoC7oLoguuC7QLqAu6C6ILrgu0C6gLuguiC64LtAuoC7oLoguuC7QLqAu6C/YKdgv8CnYKdgwUCnYMGgp2CnYMFAp2DBoKdgp2DBQKdgwaCnYKdgwUCnYMaAp2CnYMFAp2DBoKdgp2DBQKdgwaCnYKdgwUCnYMbgp2CnYMFAp2DHQKdgp2DCwKdgp2CnYKdgwsCnYKdgp2CnYMLAp2CnYKdgp2DCwKdgp2CnYKdgwsCnYKdgp2CnYMLAp2CnYKdgp2DCwKdgx6CnYKdgwsCnYMegp2CnYMLAp2CnYKdgp2DCwKdgp2CnYKdgwsCnYKdgp2CnYMLAp2CnYKdgp2DCwKdgp2CnYKdgwsCnYKdgp2CnYMLAp2CnYKdgp2DCwKdgp2CnYKdgwsCnYKdgp2CnYMLAp2DHoKdgp2DCwKdgx6CnYKdgyACnYMhgp2CnYKdgp2DIwKdgp2DJIKdgyYCnYKdgp2CnYMngp2CnYMpAywDLYMqgy8CnYKdgzCCnYKdgp2CnYMyAp2CnYMzgp2DNQKdgp2CnYKdgyeCnYKdgp2CnYM2gp2CnYKdgp2DJ4Kdgp2CnYKdgzICnYKdgp2CnYM4Ap2CnYKdgp2DMgKdgp2DKQMsAy2DKoMvAzOCnYM1Ap2CnYMgAp2DIYKdgp2CnYKdgyMCnYKdgySCnYMmAp2CnYMgAp2DIYKdgp2DIAKdgyGCnYKdgyACnYMhgp2CnYKdgp2DIYKdgp2CnYKdgyGCnYKdgp2CnYMhgp2CnYMgAp2DOYKdgp2CnYKdgzmCnYKdgyACnYMhgp2CnYMgAp2DIYKdgp2DIAKdgzsCnYKdgyACnYM8gp2CnYMgAp2DPgKdgp2DIAKdgyGCnYKdgyACnYMhgp2CnYMgAp2DIYKdgp2DIAKdgyGCnYKdgyACnYMhgp2CnYMgAp2DIYKdgp2CnYKdgyGCnYKdgp2CnYMhgp2CnYKdgp2DIYKdgp2DIAKdgzmCnYKdgp2CnYM5gp2CnYMgAp2DOwKdgp2CnYKdgyMCnYKdgp2CnYMjAp2CnYKdgp2DIwKdgp2CnYKdgyMCnYKdgp2CnYMjAp2CnYKdgp2DIwKdgp2CnYKdgyMCnYKdgp2CnYMjAp2CnYMkgp2DJgKdgp2DJIKdgyYCnYKdgySCnYMmAp2CnYKdgp2DJgKdgp2CnYKdgyYCnYKdgp2CnYMmAp2CnYMkgp2DP4Kdgp2CnYKdgz+CnYKdgySCnYMmAp2CnYMkgp2DJgKdgp2DJIKdg0ECnYKdgySCnYMmAp2CnYMkgp2DJgKdgp2DJIKdgyYCnYKdgySCnYMmAp2CnYMkgp2DJgKdgp2DJIKdgyYCnYKdgp2CnYMmAp2CnYKdgp2DJgKdgp2CnYKdgyYCnYKdgySCnYM/gp2CnYKdgp2DP4Kdgp2DJIKdg0ECnYKdgp2CnYMngp2CnYKdgp2DJ4Kdgp2CnYKdgyeCnYKdgp2CnYMngp2CnYKdgp2DJ4Kdgp2CnYKdgyeCnYKdgp2CnYNCgp2CnYKdgp2DQoKdgp2CnYKdgyeCnYKdgp2CnYMngp2CnYKdgp2DRAKdgp2CnYKdg0WCnYKdgp2CnYNHAp2CnYKdgp2DJ4Kdgp2CnYKdgyeCnYKdgp2CnYNIgp2CnYMpAywDLYMqgy8DKQMsAy2DKoMvAykDLAMtgyqDLwKdgywDLYMqgy8CnYMsAy2DKoMvAp2DLAMtgyqDLwMpAywDLYMqgy8DKQMsAy2DKoMvAp2CnYMwgp2CnYKdgp2DMIKdgp2CnYKdgzICnYKdgp2CnYMyAp2CnYKdgp2DMgKdgp2CnYKdgzICnYKdgp2CnYMyAp2CnYKdgp2DMgKdgp2CnYKdg0oCnYKdgp2CnYNKAp2CnYKdgp2DMgKdgp2CnYKdgzICnYKdgp2CnYNLgp2CnYKdgp2DTQKdgp2CnYKdg06CnYKdgp2CnYMyAp2CnYKdgp2DMgKdgp2CnYKdg1ACnYKdgzOCnYM1Ap2CnYMzgp2DNQKdgp2DM4KdgzUCnYKdgp2CnYM1Ap2CnYKdgp2DNQKdgp2CnYKdgzUCnYKdgzOCnYNRgp2CnYKdgp2DUYKdgp2DM4KdgzUCnYKdgzOCnYM1Ap2CnYMzgp2DUwKdgp2DM4KdgzUCnYKdgzOCnYM1Ap2CnYMzgp2DNQKdgp2DM4KdgzUCnYKdgzOCnYM1Ap2CnYMzgp2DNQKdgp2CnYKdgzUCnYKdgp2CnYM1Ap2CnYKdgp2DNQKdgp2DM4Kdg1GCnYKdgp2CnYNRgp2CnYMzgp2DUwKdgp2AAH/mAAAAAH/NgAAAAH/awAAAAH/mQAAAAH/aAAAAAH/ugAAAAH/RwAAAAH/IwAAAAH/SQAAAAH/FwAAAAH/SwAAAAEAAAAAAAH/fwAAAAH/NgHUAAH/mAHUAAH/UQHUAAH+qAHUAAH/RwHUAAH/awHUAAH/IwHUAAH/SQHUAAH/ugHUAAH/MgHUAAH/NgKqAAH/mAKqAAH/UQKqAAH+dAKqAAH+YgKqAAH/MwKqAAH/PAKqAAH+PgKqAAH/IwKqAAH+YAKqAAH+/QKqAAH/TwHUAAH/sgHUAAH/WwHUAAH/RgHUAAH/SwHUAAH/bAHUAAH/WgHUAAEAAAHUAAH/PwHUAAH/PwKqAAH/SwENAAH+1gENAAH/TAGQAAH/YwHUAAEBKgAAAAECDgAKAAEBJQKqAAEBHwAAAAEBHwKqAAEBEAAAAAEBsgAAAAEBGgKqAAEA/AAAAAEA/AKqAAEBWAAAAAEBTwFVAAEBTwKqAAEBXAAAAAEBXAFVAAECcgAKAAEBXAKqAAECpAKqAAEAnwAAAAEA6AAYAAEAnwKqAAEBEQAAAAEBEQKqAAEBnQAAAAEBnQKqAAEBPAAAAAEBPAKqAAEBDAAAAAEBDAKqAAEBCwAAAAEBCwFVAAEBCwKqAAEA8gAAAAEA8gKqAAEBAgAAAAEBAgKqAAEBYAAAAAEAnwNPAAEA8gNPAAH/1AKCAAEBJQNaAAEBJQMrAAH/VQKCAAH/NwKCAAEAnwNaAAEAnwMrAAH/HgKCAAEA8gNaAAEA8gMrAAH/aQKCAAEA7gAAAAEA+gHUAAEBAAHUAAEAjwAAAAEBCQHUAAEAagHUAAEBCgAAAAEBCgDqAAEBU//7AAEBFAHUAAEB/wHUAAEBHgHUAAEA6QHUAAEBawAAAAEBbAHUAAEAagJfAAEA6QJfAAEA7gNuAAEA+QKEAAEA+gK6AAEA+gJjAAEA/QNuAAEBCAKEAAEAXgNuAAEAaQKEAAEAagK6AAEAagJjAAEAXwMXAAEA3QNuAAEA6AKEAAEA6QK6AAEA6QJjAAEA3gMXAAEBYANuAAEBawKEAAEotiniAAUADAEKAD8AAhKSAAISmAACEp4AAhKeAAISpAAEE14AAhKqAAISqgACEqoAAhKwAAIStgACErwAAhLCAAISyAACEqoAAhKYAAISmAACEpgAABJEAAASSgAAElAAABJWAAASXAABEowAABJiAAASaAAAEmgAABJuAAASdAAAEnoAAxNMAAMTUgADE1gAAhLOAAIS1AACEtoAAhLaAAIS4AACEuYAAhLsAAIS8gACEvgAAhL+AAITBAACEwoAAhMQAAITFgACExwAAhKqAAITHAACEyIAAhMoAAITLgACEzQAABKAAAITOgACEzoAAhM6AAAShgACEyIAAhLyAAITQAACE0YBuhJmEmwSchGIEYgSZhJsEngRiBGIEmYSbBJ+EYgRiBJmEmwShBGIEYgSZhJsEooRiBGIEpASbBKKEYgRiBJmEmwSlhGIEYgSZhJsEpwRiBGIEmYSbBKiEYgRiBJmEmwSqBGIEYgSkBJsEnIRiBGIEmYSbBJ4EYgRiBJmEmwSrhGIEYgSZhJsErQRiBGIEmYSbBJyEYgRiBJmEmwSuhGIEYgSZhJsEsARiBGIEsYSbBJyEYgRiBJmEmwSzBGIEYgS0hGIEtgRiBGIEtIRiBLeEYgRiBLkEYgS6hGIEYgS5BGIEvARiBGIEvYRiBLqEYgRiBL8EYgTAhGIEYgTCBGIEuoRiBGIEw4RiBMUEYgRiBMOEYgTGhGIEYgTDhGIEyARiBGIEyYRiBMUEYgRiBMmEYgTLBGIEYgTDhGIEzIRiBGIEw4RiBM4EYgRiBMOEYgTFBGIEYgTDhGIExQRiBGIEz4RiBNKE0QRiBM+EYgTShNEEYgTPhGIE0oTRBGIEz4RiBNQE0QRiBNWEYgTShNEEYgTXBGIE0oTRBGIEz4RiBNKE0QRiBM+EYgTYhNEEYgTaBGIE0oTRBGIE24RiBN6E3QRiBOAEYgTShNEEYgThhOME5IRiBGIE4YTjBOYEYgRiBOGE4wTnhGIEYgThhOME6QRiBGIE6oTjBOSEYgRiBOGE4wTsBGIEYgTthOME7ARiBGIE4YTjBO8EYgRiBOGE4wTwhGIEYgThhOME8gRiBGIE7YTjBOSEYgRiBOGE4wTmBGIEYgThhOME84RiBGIE4YTjBPUEYgRiBOGE4wT2hGIEYgThhOME9oRiBGIE4YTjBOSEYgRiBOGE4wT4BGIEYgT5hOME5IRiBGIE+wRiBPyEYgRiBP4EYgT/hGIEYgT+BGIFAQRiBGIE/gRiBQKEYgRiBP4EYgUEBGIEYgUFhGIE/4RiBGIE/gRiBQcEYgRiBQiEYgUKBGIEYgULhGIFDoUNBGIFEARiBRMFEYRiBQuEYgUUhQ0EYgULhGIFFgUNBGIFC4RiBReFDQRiBQuEYgUZBQ0EYgUahGIFDoUNBGIFHAUdhR8EYgRiBRwFHYUghGIEYgUcBR2FIgRiBGIFHAUdhSOEYgRiBRwFHYUlBGIEYgUcBR2FJoRiBGIFHAUdhSgEYgRiBRwFHYUphGIEYgUcBR2FKwRiBGIFLIUdhR8EYgRiBRwFHYUghGIEYgUcBR2FLgRiBGIFHAUdhS+EYgRiBRwFHYUfBGIEYgUxBTKFNARiBGIFHAUdhTWEYgRiBTcFHYUfBGIEYgU4hGIFOgRiBGIFOIRiBTuEYgRiBTiEYgU6BGIEYgU9BGIFPoRiBGIFQARiBT6EYgRiBT0EYgRiBGIEYgVBhGIFPoRiBGIFQwRiBUYFRIVHhUMEYgVJBUSFR4VDBGIFRgVEhUeFSoRiBUYFRIVHhUwEYgVGBUSFR4VDBGIFRgVEhUeFTYRiBUYFRIVHhU8EYgVGBUSFR4VDBGIFRgVEhUeFUIRiBVIEYgRiBVCEYgVThGIEYgVVBGIFUgRiBGIFVoRiBVgEYgRiBVaEYgVZhGIEYgVWhGIFWwRiBGIFXIRiBVgEYgRiBV4EYgVYBGIEYgVWhGIFX4RiBGIFYQRiBVgEYgRiBVaEYgVZhGIEYgVihGIFWARiBGIFVoRiBWQEYgRiBWWFaIVqBWcFa4VlhWiFbQVnBWuFZYVohW6FZwVrhWWFaIVwBWcFa4VlhWiFagVnBWuFZYVohXGFZwVrhXMFaIVxhWcFa4VlhWiFdIVnBWuFZYVohXYFZwVrhWWFaIV3hWcFa4VlhWiFeQVnBWuFZYVohXqFZwVrhXMFaIVqBWcFa4VlhWiFbQVnBWuFZYVohXwFZwVrhWWFaIV9hWcFa4VlhWiFfwVnBWuFZYVohYCFZwVrhWWFaIWAhWcFa4VlhWiFagVnBWuE4YRiBYIEYgRiBWWFaIVqBWcFa4VlhWiFbQVnBWuFZYVohYOFZwVrhWWFaIWFBWcFa4VlhWiFhoVnBWuFiARiBYmEYgRiBYgEYgWLBGIEYgWMhGIFjgRiBGIFC4VohQ6FZwVrhY+EYgWRBGIEYgWPhGIFkoRiBGIFj4RiBZQEYgRiBZWEYgWRBGIEYgWPhGIFlwRiBGIFmIRiBZEEYgRiBY+EYgWaBGIEYgWbhGIFkQRiBGIFj4RiBZEEYgRiBZ0EYgWehGIEYgWdBGIFoARiBGIFnQRiBaGEYgRiBaMEYgWehGIEYgWdBGIFpIRiBGIFpgRiBZ6EYgRiBZ0EYgWnhGIEYgWpBGIFnoRiBGIEYgRiBaqEYgRiBawEYgWvBa2EYgWsBGIFrwWthGIFrARiBbCFrYRiBbIEYgWvBa2EYgWzhGIFrwWthGIFtQRiBa8FrYRiBawEYgW2ha2EYgW4BGIFrwWthGIFuYRiBa8FrYRiBawEYgWvBa2EYgW7BbyFvgRiBb+FuwW8hcEEYgW/hMOFwoXEBGIFxYW7BbyFxwRiBb+FuwW8hciEYgW/hbsFvIXKBGIFv4W7BbyFy4RiBb+FuwW8hc0EYgW/hc6FvIW+BGIFv4W7BbyFwQRiBb+FuwW8hdAEYgW/hbsFvIXRhGIFv4W7BbyF0wRiBb+FuwW8hb4EYgW/hbsFvIXUhGIFv4W7BbyF1gRiBb+F14W8hb4EYgW/hUMEYgWCBGIEYgXZBGIF2oRiBGIF2QRiBdwEYgRiBdkEYgXdhGIEYgXZBGIF3wRiBGIF2QRiBdwEYgRiBeCEYgXiBGIEYgXjhGIF5QRiBGIF44RiBeaEYgRiBeOEYgXoBGIEYgXjhGIF6YRiBGIF44RiBeaEYgRiBeOEYgXrBGIEYgXjhGIF7IRiBGIF7gRiBe+EYgRiBe4EYgXxBGIEYgXuBGIF8oRiBGIF7gRiBfQEYgRiBfWEYgXvhGIEYgX3BGIF74RiBGIFVoRiBVgEYgRiBfiF+gX7hGIEYgX4hfoF/QRiBGIF+IX6Bf6EYgRiBfiF+gYABGIEYgX4hfoGAYRiBGIGAwX6BgGEYgRiBfiF+gYEhGIEYgX4hfoGBgRiBGIF+IX6BgeEYgRiBfiF+gYJBGIEYgYDBfoF+4RiBGIF+IX6BgqEYgRiBfiF+gX+hGIEYgX4hfoGDARiBGIF+IX6BfuEYgRiBfiF+gYNhGIEYgX4hfoGDwRiBGIGEIX6BfuEYgRiBfiF+gYSBGIEYgYThGIGFQRiBGIGE4RiBhaEYgRiBhgEYgYZhGIEYgYYBGIGGwRiBGIGHIRiBhmEYgRiBh4EYgYZhGIEYgYfhGIGIQRiBGIGIoRiBiQEYgRiBiKEYgYlhGIEYgYihGIGJwRiBGIGKIRiBiQEYgRiBiiEYgYlhGIEYgYihGIGKgRiBGIGIoRiBiuEYgRiBiKEYgYkBGIEYgYihGIGJARiBGIF+IRiBfuGLQYuhfiEYgX7hi0GLoYwBGIF+4YtBi6GMYRiBfuGLQYuhfiEYgX7hi0GLoX4hGIGCQYtBi6GAwRiBfuGLQYuhjMEYgX7hi0GLoY0hjYGN4RiBGIGNIY2BJyEYgRiBjSGNgY5BGIEYgY0hjYGOoRiBGIGPAY2BjeEYgRiBjSGNgY9hGIEYgY/BjYGPYRiBGIGNIY2BkCEYgRiBjSGNgZCBGIEYgY0hjYGQ4RiBGIGPwY2BjeEYgRiBjSGNgZFBGIEYgY0hjYGOQRiBGIGNIY2BkaEYgRiBjSGNgZIBGIEYgY0hjYGSYRiBGIGNIY2BjeEYgRiBGIEYgZLBGIEYgY0hjYGTIRiBGIGTgY2BjeEYgRiBk+GUQZShGIEYgZPhlEGUoRiBGIGVARiBlWEYgRiBjAEYgZXBGIEYgYwBGIGWIRiBGIGMARiBloEYgRiBjAEYgZbhGIEYgYwBGIGXQRiBGIGMARiBl6EYgRiBawEYgZhhmAEYgWsBGIGYYZgBGIFrARiBmMGYARiBawEYgZkhmAEYgWsBGIGZgZgBGIFrARiBmGGYARiBbgEYgZhhmAEYgW5hGIGYYZgBGIGZ4ZpBmqEYgRiBmeGaQZsBGIEYgZnhmkGbYRiBGIGZ4ZpBm8EYgRiBmeGaQZwhGIEYgZnhmkGcgRiBGIGZ4ZpBnOEYgRiBmeGaQZ1BGIEYgZnhmkGdoRiBGIGZ4ZpBmqEYgRiBngGaQZqhGIEYgZnhmkGeYRiBGIGZ4ZpBm8EYgRiBmeGaQZ7BGIEYgZnhmkGaoRiBGIGZ4ZpBmqEYgRiBmeGaQZ8hGIEYgZ+BmkGaoRiBGIGf4RiBoEEYgRiBn+EYgaChGIEYgZ/hGIGhARiBGIGf4RiBoWEYgRiBocEYgaIhGIEYgaKBGIGi4RiBGIGjQRiBouEYgRiBo6EYgaQBGIEYgaRhGIGi4RiBGIGkwRiBpYGlIaXhpMEYgaZBpSGl4aTBGIGlgaUhpeGmoRiBpYGlIaXhpwEYgaWBpSGl4aTBGIGlgaUhpeGnYRiBpYGlIaXhp8EYgaWBpSGl4aTBGIGlgaUhpeGoIRiBqIEYgRiBqCEYgajhGIEYgalBGIGogRiBGIGpoRiBqgEYgRiBqaEYgaphGIEYgamhGIGqwRiBGIGrIRiBqgEYgRiBq4EYgaoBGIEYgamhGIGr4RiBGIGsQRiBqgEYgRiBqaEYgayhGIEYga0BGIGqARiBGIGpoRiBrWEYgRiBrcGuga7hriGvQa3BroGvoa4hr0Gtwa6BruGuIa9BrcGugbABriGvQa3BroGwYa4hr0Gtwa6BsMGuIa9BsSGugbDBriGvQa3BroGxga4hr0Gtwa6BseGuIa9BrcGugbJBriGvQa3BroGyoa4hr0Gtwa6BswGuIa9BsSGuga7hriGvQa3BroGzYa4hr0Gtwa6Bs8GuIa9BrcGugbABriGvQa3BroG0Ia4hr0Gtwa6BtIGuIa9BrcGugbThriGvQa3BroGu4a4hr0GigRiBtUEYgRiBrcGuga7hriGvQa3BroGvoa4hr0Gtwa6BtaGuIa9BrcGugbYBriGvQa3BroG2Ya4hr0Ez4RiBtsEYgRiBM+EYgbchGIEYgX4hGIF+4RiBGIG3gRiBlKEYgRiBt4EYgbfhGIEYgbeBGIG4QRiBGIG4oRiBlKEYgRiBt4EYgbkBGIEYgblhGIGUoRiBGIG3gRiBucEYgRiBuiEYgZShGIEYgaOhGIGkARiBGIGjoRiBuoEYgRiBo6EYgbrhGIEYgbtBGIGkARiBGIGjoRiBu6EYgRiBvAEYgaQBGIEYgaOhGIG8YRiBGIG8wRiBpAEYgRiBocEYgb2BvSG94aHBGIG9gb0hveGhwRiBvYG9Ib3hvkEYgb2BvSG94b6hGIG9gb0hveG/ARiBvYG9Ib3hocEYgb9hvSG94b/BGIG9gb0hveHAIRiBvYG9Ib3hqaHAgYkBGIHA4amhwIGJYRiBwOGpocCBiQEYgcDhqaHAgcFBGIHA4amhwIGJwRiBwOGpocCBioEYgcDhqaHAgcGhGIHA4amhwIHCARiBwOGsQcCBiQEYgcDhqaHAgcJhGIHA4amhwIHCwRiBwOGpocCBwUEYgcDhqaHAgcMhGIHA4amhwIGJARiBwOGpocCBw4EYgcDhqaHAgcPhGIHA4cRBwIGJARiBwOHEoRiBxQEYgRiBxWEYgcXBGIEYgcYhGIHGgRiBGIHGIRiBY4EYgRiBxiEYgcbhGIEYgcYhGIHHQRiBGIHGIRiBx6EYgRiByAEYgchhGIEYgcgBGIHIwRiBGIHIARiBySEYgRiByAEYgcmBGIEYgcgBGIHJ4RiBGIHIARiBykEYgRiByAEYgcqhGIEYgcsBGIHLYRiBGIHLARiBy8EYgRiBywEYgcwhGIEYgcsBGIHMgRiBGIHM4RiBy2EYgRiBzUEYgcthGIEYgAAf+YAAAAAf82AAAAAf9rAAAAAf+ZAAAAAf9oAAAAAf+6AAAAAf9HAAAAAf8jAAAAAf9JAAAAAf8XAAAAAf9LAAAAAQAAAAAAAf9/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AegB6wCrAe4B8gCvAfQB+QC0AfsB/AC6A/4EAgC8BAYEBgDBBAsECwDCBA4EDwDDBBYEFwDFBBkEGQDHBB4EHgDIBCMEIwDJBCgEKADKBDQENADLBDYENgDMBD0EPQDNBEAEQADOBFAEUADPBFcEWADQBHMEcwDSBHgEeADTBKEEoQDUAAEAFQLGAscCygLOAtUC1gLXAtgC2gLbAuIC5QNIA0wDTgNQA1EDWQNaA10EGAACADoABAAQAAAAEgAcAA0AHgAeABgAIAAmABkAKQApACAAKwAxACEAMwBEACgASABJADoASwBLADwATQBNAD0ATwBTAD4AVwBdAEMAXwBsAEoAbwByAFgAdAB1AFwAdwCLAF4AjgCOAHMAkACRAHQAkwCVAHYAlwClAHkApwCuAIgAsACxAJAAswC6AJIAyADPAJoA0QDRAKIA0wDUAKMA1gDgAKUA4gDlALAA6ADsALQA7gDzALkA9QD9AL8CDwIPAMgCEQIRAMkCFgInAMoCKgIsANwCLgIuAN8CMAIwAOACMwIzAOECNwI7AOICPQI+AOcCQQJBAOkCQwJDAOoCRgJLAOsCTQJSAPECVAJhAPcCZQJlAQUCZwJpAQYEJwQnAQkEKQQpAQoELQQtAQsELwQwAQwEMgQyAQ4ENAQ1AQ8ENwQ4AREEPQQ+ARMEVQRVARUEVwRYARYEWwReARgAAgADBEIETwAABFEEVAAOBFYEVgASAAIABwP8BAgAAAQLBCMADQRABEEAJgRgBGAAKARzBHMAKQR4BHgAKgShBKEAKwACAC8BAAEMAAABDgEYAA0BGgEaABgBJgEmABkBMAFAABoBQwFEACsBSAFIAC0BVQFbAC4BXQFdADUBhwGRADYBlQGYAEEBmgGqAEUBrAGwAFYBsgG0AFsBtgG2AF4BuQG+AF8BwQHCAGUBxgHGAGcB2QHaAGgB3AHmAGoB6AHrAHUB7gHyAHkB9AH0AH4CAwIDAH8CagJrAIACcQJ0AIICdgJ4AIYCewKAAIkChQKHAI8CiQKJAJICiwKLAJMCjgKOAJQCkwKTAJUCmAKZAJYCnAKcAJgCngKeAJkCoQKhAJoCpgKmAJsCqAKoAJwCqgKuAJ0CsAK3AKICuQK8AKoCwALAAK4CwgLCAK8CxALEALAECQQKALEEUARQALMAAQAFA+oD8APxA/MD+AABAAUCCAIJBFkEWgRfAAEAAgADBCYAAQACAoIDWgABABcCxQLGAscCyQLKAswCzgLPAtIC0wLVAtYC1wLYAtkC2gLbAt0C3gLhAuIC4wLlAAIABwNHA08AAANRA1EACQNTA1cACgNZA1kADwNbA1sAEANdA2oAEQPlA+UAHwACAAcD/AQIAAAECwQkAA0EQARBACcEYARgACkEcwRzACoEeAR4ACsEoQShACwAAQACA/ED+AACAAUEYwRnAAAEaQSDAAUEiwSgACAEtgS7ADYE2gTbADwAAgAHBCcEKQAABC0ELQADBC8ELwAEBDIEMgAFBDUENQAGBD4EPgAHBNwE9gAIAAIABARjBIMAAASLBKAAIQS2BLsANwTaBNsAPQACABsCDwIPAAACEQITAAECFgIfAAQCIQIjAA4CJQIpABECKwIsABYCMwIzABgCNgI8ABkCQgJCACACRAJEACECRgJLACICTgJqACgCbQJuAEUCcQJ6AEcCfgJ+AFECgAKBAFICgwKEAFQChwKHAFYCjgKOAFcCkQKXAFgCnQKdAF8CnwKfAGACoQKlAGECqQKuAGYCsAK1AGwCtwLEAHIE9wT3AIAAAgARAsUCxgAAAskCzgACAtAC0QAIAtMC0wAKAtUC1QALAtcC2AAMAtoC2gAOAtwC5QAPAucDRwAZA0sDSwB6A00DTQB7A08DTwB8A1UDVQB9A1cDVwB+A1sDWwB/A18DagCAA2wD5ACMAAIANgAEABAAAAASAB4ADQAgAEQAGgBIAEkAPwBLAEsAQQBNAE0AQgBPAFQAQwBXAF0ASQBfAGwAUABuAIsAXgCOAI4AfACQAJEAfQCTAKsAfwCtAK8AmACxALEAmwCzALoAnAC8AL4ApADAAMUApwDHAM8ArQDRAOAAtgDiAOUAxgDoAOwAygDuAPMAzwD1APwA1QD+AP4A3QEAAQwA3gEOARgA6wEaASUA9gEnASwBAgEuAS4BCAEwAUEBCQFDAUYBGwFIAUkBHwFLAU8BIQFVAVsBJgFdAV0BLQFfAW0BLgFvAXkBPQF7AX0BSAGAAZEBSwGVAbABXQGzAbQBeQG3AbcBewG5Ab4BfAHBAcIBggHEAcUBhAHHAcwBhgHOAdUBjAHXAdcBlAHZAeYBlQHoAesBowHtAfIBpwH1AfkBrQH7AgIBsgACAAQEdQR5AAAEewSAAAUEoASgAAsEuQS5AAwAAgADBHUEeQAABHsEgAAFBKAEoAALAAIABgRjBGcAAARpBHQABQSLBJ8AEQS2BLgAJgS6BLsAKQTaBNsAKwACAAcEYwRnAAAEaQR0AAUEiwSfABEEoQShACYEowSjACcEqgSqACgEsQSxACkAAQALBLYEtwS6BLsEzATNBM4EzwTSBNcE2AABA+oACgAJAAQACAAHAAIAAQAGAAUAAAADAAIAEAPrA+sAAQPsA+wABgPtA+0ACgPuA+4AAwPvA+8ABAPwA/AACQPxA/EAAgPyA/IABwP+A/8ACAQABAIABQQGBAYABQQOBA4ABQQWBBcACAQZBBkACAQeBB4ABQQjBCMABQACAAkEAAQCAAEEBgQGAAEECwQLAAEEDgQOAAEEHgQeAAEEIwQjAAEEcwRzAAEEeAR4AAEEoQShAAEAAgADA+oD6gADA/ED8QABA/MD8wACAAIAAAABA+4AAQABAAIAawAYABkAAwAaABwAAQAeAB4AAQAgACYAAgApACkACQArADEACQAzADMACQA0AEQAAwBIAEkAAwBLAEsABABNAE0AAgBPAFMAAgBXAFcABQBYAFgABgBZAF0ABQBfAGwABQBvAHAABQBxAHIADQB0AHUABwB3AHcABwB4AIAACACBAIsABQCOAI4ABQCQAJAABQCRAJEACQCTAJUACQCXAKUACQCnAKsACQCsAKwAAwCtAK4ACgCwALAACgCxALEACQCzALoACwDIAM8ADADRANEADADTANQADQDWAOAADQDiAOQADQDlAOUADgDoAOwADgDuAO4ADwDvAPMAEAD1APYAEAD3APwAEQD9AP0ABQIRAhEAAQIWAhgAAwIZAhkABwIaAhoAAQIbAh0ABQIeAh8ABwIgAiIABQIjAiMACQIkAiQABQIlAiUACgImAiYAAgInAicADAIqAioACQIrAisADwIsAiwABQIuAi4ABQIwAjAABQIzAjMABQI3AjcAAgI4AjgACQI5AjoABQI7AjsADQI9Aj0ACQI+Aj4ABQJBAkEADwJDAkMADgJGAkYABwJHAkcAAQJIAkoABwJLAksABQJNAk0ABQJOAk4AAgJPAlAAEAJRAlIABQJUAlQABQJVAlUABwJWAlYABQJZAloAAwJbAlsACQJcAlwABwJdAl0AAQJeAl8ABQJgAmEACQJl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AmsAAwJ0AnQACAJ2AngABgJ7AnwABgJ9An0AAgJ+An4AAwJ/An8ABgKAAoAAAwKFAoUAAwKGAoYACAKHAocABgKJAokABgKLAosABgKOAo4ABgKTApMAAwKYApgAAwKZApkABgKcApwACAKeAp4ABwKhAqEACAKmAqYAAgKoAqgABgKqAqsABwKsAq0ABgKuAq4AAgKwArAACAKxArEABgKyArMAAgK2ArYAAwK3ArcACAK5AroABgK7ArwAAwLAAsAABgLCAsIABgLEAsQABwQJBAoABARQBFAABgACAJAABAAQAAcAEgAZAAcAIAAmABEATQBNABEATwBTABEAkQCRABEAkwCVABEAlwClABEApwCsABEAsQCxABEA7wDzABIA9QD2ABIBAAEMABMBDgEVABMBFgEYACABGgEaACABHAEiABUBJQEsABUBLgEuABUBMAFAABUBQwFEABUBVQFZACABWwFbACABXQFdACABdwF4ACABeQF5ABQBewF9ACABgAGGACABhwGSABQBlQGWABQBlwGYABUBmgGqABUBrAGwABUBsgGyABUBswG0ABQBtgG2ACABtwG3ABUBuQG+ABQBwQHCABQBxAHFABYBxgHGAAIBxwHMABYBzQHNACAB6wHrAAMB7gHyAAMB9AH0AAQCDwIPAAcCIwIjABECJgImABECKgIqABECNwI3ABECTgJOABECTwJQABICVwJZAAcCYAJhABECaAJoABECagJqABMCbAJvABQCcQJzABUCdAJ0AAQCdgJ6ABQCfAJ9ABQCfgJ+ABUCfwKAABQCgQKBABUChQKFABUChgKGAAQCiAKMABQCjgKOABQCkAKQABQCkQKRABYCkgKSABUClwKXACACmAKYABQCmgKbACACnAKcAAQCngKeAAMCnwKgABQCoQKhAAQCowKkABQCpgKoABQCqQKpABUCqgKrAAMCrgKuACACsAKwAAQCsgK0ABMCtQK2ABUCtwK3AAQCuQK6ABQCuwK8ABUCwQLCABQCwwLDABUCxALEAAMCxQLFAAgCzALMAAoCzwLPAAgC0wLTAAoC2QLZAAoC2gLaAB8C3QLdAAgC3gLgAAkC4QLhAAsC4gLiABgC4wLjAAsDRwNHAAwDSQNJAA4DSgNKABADSwNLAA0DTANMAB4DTgNOABwDUANQABoDUwNTAA4DVANU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AFsAZQBvAHkAAP//AA0ABAAOABgAIgAsADYAQABIAFIAXABmAHAAegAA//8ADQAFAA8AGQAjAC0ANwBBAEkAUwBdAGcAcQB7AAD//wANAAYAEAAaACQALgA4AEIASgBUAF4AaAByAHwAAP//AA0ABwARABsAJQAvADkAQwBLAFUAXwBpAHMAfQAA//8ADQAIABIAHAAmADAAOgBEAEwAVgBgAGoAdAB+AAD//wANAAkAEwAdACcAMQA7AEUATQBXAGEAawB1AH8AgGFhbHQDAmFhbHQDCmFhbHQDEmFhbHQDGmFhbHQDImFhbHQDKmFhbHQDMmFhbHQDOmFhbHQDQmFhbHQDSmNhc2UDUmNhc2UDWGNhc2UDXmNhc2UDZGNhc2UDamNhc2UDcGNhc2UDdmNhc2UDfGNhc2UDgmNhc2UDiGNjbXADjmNjbXADlmNjbXADoGNjbXADqGNjbXADsGNjbXADuGNjbXADwGNjbXADyGNjbXAD0GNjbXAD2GRsaWcD4GRsaWcD5mRsaWcD7GRsaWcD8mRsaWcD+GRsaWcD/mRsaWcEBGRsaWcECmRsaWcEEGRsaWcEFmZyYWMEHGZyYWMEImZyYWMEKGZyYWMELmZyYWMENGZyYWMEOmZyYWMEQGZyYWMERmZyYWMETGZyYWMEUmxpZ2EEWGxpZ2EEXmxpZ2EEZGxpZ2EEamxpZ2EEcGxpZ2EEdmxpZ2EEfGxpZ2EEgmxpZ2EEiGxpZ2EEjmxudW0ElGxudW0EmmxudW0EoGxudW0EpmxudW0ErGxudW0EsmxudW0EuGxudW0EvmxudW0ExGxudW0EymxvY2wE0GxvY2wE1mxvY2wE3GxvY2wE4mxvY2wE6GxvY2wE7mxvY2wE9GxvY2wE+m9udW0FAG9udW0FBm9udW0FDG9udW0FEm9udW0FGG9udW0FHm9udW0FJG9udW0FKm9udW0FMG9udW0FNm9yZG4FPG9yZG4FRG9yZG4FTG9yZG4FVG9yZG4FXG9yZG4FZG9yZG4FbG9yZG4FdG9yZG4FfG9yZG4FhHNhbHQFjHNhbHQFknNhbHQFmHNhbHQFnnNhbHQFpHNhbHQFqnNhbHQFsHNhbHQFtnNhbHQFvHNhbHQFwnNzMDEFyHNzMDEFznNzMDEF1HNzMDEF2nNzMDEF4HNzMDEF5nNzMDEF7HNzMDEF8nNzMDEF+HNzMDEF/nN1cHMGBHN1cHMGCnN1cHMGEHN1cHMGFnN1cHMGHHN1cHMGInN1cHMGKHN1cHMGLnN1cHMGNHN1cHMGOgAAAAIAAAABAAAAAgAAAAEAAAACAAAAAQAAAAIAAAABAAAAAgAAAAEAAAACAAAAAQAAAAIAAAABAAAAAgAAAAEAAAACAAAAAQAAAAIAAAABAAAAAQATAAAAAQATAAAAAQATAAAAAQATAAAAAQATAAAAAQATAAAAAQATAAAAAQATAAAAAQATAAAAAQATAAAAAgACAAMAAAADAAIAAwAEAAAAAgACAAMAAAACAAIAAwAAAAIAAgADAAAAAgACAAMAAAACAAIAAwAAAAIAAgADAAAAAgACAAMAAAACAAIAAwAAAAEAFAAAAAEAFAAAAAEAFAAAAAEAFAAAAAEAFAAAAAEAFAAAAAEAFAAAAAEAFAAAAAEAFAAAAAEAFAAAAAEADgAAAAEADgAAAAEADgAAAAEADgAAAAEADgAAAAEADgAAAAEADgAAAAEADgAAAAEADgAAAAEADgAAAAEAFQAAAAEAFQAAAAEAFQAAAAEAFQAAAAEAFQAAAAEAFQAAAAEAFQAAAAEAFQAAAAEAFQAAAAEAFQAAAAEAEQAAAAEAEQAAAAEAEQAAAAEAEQAAAAEAEQAAAAEAEQAAAAEAEQAAAAEAEQAAAAEAEQAAAAEAEQAAAAEADAAAAAEABQAAAAEACwAAAAEACAAAAAEABwAAAAEABgAAAAEACQAAAAEACgAAAAEAEgAAAAEAEgAAAAEAEgAAAAEAEgAAAAEAEgAAAAEAEgAAAAEAEgAAAAEAEgAAAAEAEgAAAAEAEgAAAAIADwAQAAAAAgAPABAAAAACAA8AEAAAAAIADwAQAAAAAgAPABAAAAACAA8AEAAAAAIADwAQAAAAAgAPABAAAAACAA8AEAAAAAIADwAQAAAAAQAWAAAAAQAWAAAAAQAWAAAAAQAWAAAAAQAWAAAAAQAWAAAAAQAWAAAAAQAWAAAAAQAWAAAAAQAWAAAAAQAXAAAAAQAXAAAAAQAXAAAAAQAXAAAAAQAXAAAAAQAXAAAAAQAXAAAAAQAXAAAAAQAXAAAAAQAXAAAAAQANAAAAAQANAAAAAQANAAAAAQANAAAAAQANAAAAAQANAAAAAQANAAAAAQANAAAAAQANAAAAAQAN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B3QHdgd6AAAAAwQDBAkEBQQrBFEEVAQKBBQEFQP8BEIEAQQYBAYEDAPqA+sD7APtA+4D7wPwA/ED8gPzBAAECwRJBEYESAQHBFMABAAaACAAKQA0AEsATQBXAF8AcQB0AHgAgQCEAJEArQCxALMAvQDIANMA5QDoAO4A7wD3BBID/QQTBGAEDgSwAQABFgEcASUBMAFIAUkBVQFfAXIBdwF8AYcBigGXAbMBtwG5AcQBzgHZAesB7gH0AfUB/QQQBFsEEQROBCYEBAQoBDoEKgQ+BFwEVgSuBFcCCAQaBE8EGQRYBLIEWgRMA/UD9gSpBFAEVQP+BKwD9AIJBBsD+gP5A/sECAAPAAUACAAXAAsAFAAYACMAPwA1ADkAPABpAGAAYwBlACsAkACfAJMAlwCpAJoERACnANwA1ADYANoA8ACwAc0BCwEBAQQBEwEHARABFAEfATsBMQE1ATgBagFhAWQBZgEmAZYBpAGYAZwBrgGfBEUBrAHiAdoB3gHgAfYBtgH4ABIBDgAGAQIAEwEPACEBHQAlASEAJgEiACIBHgAsAScALwEqAEEBPQA2ATIAPQE5AEQBQAA3ATMAUQFNAE8BSwBTAU8AUgFOAFoBWABYAVYAbwFwAGsBbABhAWIAbAFtAGcBYAByAXUAdQF4AXkAeQF9AHwBggB6AYAAfQGDAIABhgCFAYsAiAGOAIYBjACMAZIAogGnAJQBmgCgAaUArAGyALQBugC2AbwAtQG7AL4BxQDCAckAwQHIAMABxwDLAdEAygHQAMkBzwDjAekA3wHlANYB3ADiAegA3QHjAOAB5gDqAfAA8QH3APIA+AH+APoCAAD5Af8BGwAdAKYAJwEjACoAMgBGAMcARQBMBC4AVABOAG0AbgB2AXoAjQGUAJYArwG1ANAB1gDSAOEA5gD0AfoABwEDAGIBYwCVAZsA1wHdAUUADAEIAFABTAClAaoAiwGRABUBEQAZARUAqAGtAAoBBgAQAQwAOwE3AEABPABkAWUAagFrAJkBngChAaYAtwG9ALkBwQDZAd8A3gHkAMMBygDNAdMAVgFUAFkBVwCPAJIBsQANAQkAOAE0AJsBoACrAbAAnAGhAJ0BogD1AfsAKAEkAFUBUwAfANUA5wBzAXYAsgG4ALsBGQGrAS8BLQFeAVwBbwFuAP8BwAG/AdgB2wHnAewB7QSiBKEEpwSmBKUErQSrBKMEqASkBKoErwS0BLMEtQSxBGUEZgRpBG0EbgRrBGQEYwRsBGcEagRvBLwEvgLdBCQC3gLfAuAC4gLjA2IC1gLXAtgC2QLaAtsC3ALkAuUDaANpA2oDYANlA0cDSANJA0oDSwNMA00DTgNPA1ADUQNSA1MDVANVA1YDVwNYA1kDWgNbA1wDXQNeA18DYQNkA2YDYwNnAuYCFwIYAj8CEwI3AjYCOQI6AjsCNAI1AjwCHwIdAikCMAIPAhACEQISAhUCFgIZAhoCGwIcAh4CIAIhAiICIwIkAiUCJgInAigCKgIrAi0CLAIuAi8CMgIzAjECOAI9Aj4CagJrAmwCbQJwAnECdAJ1AnYCdwJ5AnsCfAJ9An4CfwKAAoECggKDAoUChgKIAocCiQKKAo0CjgKMApMCmAKZAnICcwKaAm4CkgKRApQClQKWAo8CkAKXAnoCeAKEAosCQAKbAkECnAJCAp0CQwKeAhQCbwJEAp8CRQKgAkYCoQJHAqICSAKjAkkCpAJKAqUCSwKmAkwCpwJOAqkCTwKqAlACqwJRAqwCUgKtAlMCrgJUAlUCsAJWArECrwJXArICWAKzAlkCtAJaArUCWwK2AlwCtwJdArgCXgK5Al8CugJgArsCYQK8AmICvQJjAr4CZAK/AmUCwAJmAsECZwLCAmgCwwJpAsQCTQKoBIkEiASKBIcAFgESABsBFwAcARgAHgEaACQBIAAwASsAMQEsADMBLgAtASgALgEpAEMBPwBCAT4ASQFEAFwBWgBdAVsAWwFZAHABcQBmAWcAdwF7AH4BhAB/AYUAewGBAIIBiACDAYkAiQGPAIoBkACOAZUAhwGNAKoBrwCkAakAowGoAK4BtAC4Ab4AugHCAMQBywDFAcwAzgHUAM8B1QDRAdcAzAHSAOQB6gDsAfIA6QHvAOsB8QD7AgEA/AICAV0ADgEKAAkBBQA+AToASAFDADoBNgBoAWkAngGjAJgBnQDbAeEA8wH5APYB/AM0AzUDNgM3AzgDOQM6AzsDdAN1A4sDjAOVA5YDrAOtA7oDuwPGA8cD1gPXAz8DQANBA0IDQwNEA0UDRgN3A3gDegN5A3sDdgOEAvEC8gLvAvAC8wTBA+YEvwTXBNIDmQOYA5oDlwOjAwIDAwMMAw0DDgTFBMgEzAOuA7MDIQMiAx8DIATG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Tk6OYN5XCj5qHI1oT3E6AQE6cU9ojRxqPgpX3v////0AAAI9AqsAIgLFAAABBgS9w64ACbECAbj/rrAzKwAAAP///2AAAAHQAqsAIgLJAAABBwS9/yb/rgAJsQEBuP+usDMrAP///34AAAJOAqsAIgLLAAABBwS9/0T/rgAJsQEBuP+usDMrAP///2AAAAEGAqsAIgLNAAABBwS9/yb/rgAJsQEBuP+usDMrAP///6b/8AKBArkAIgLTAAABBwS9/2z/rgAJsQIBuP+usDMrAP///0cAAAHzAqsAIgLYAAABBwS9/w3/rgAJsQEBuP+usDMrAP///5IAAAJ9ArkAIgLcAAABBwS9/1j/rgAJsQEBuP+usDMrAP//AAcAAAE3A2MAIgLNAAAAAwSLAWkAAP////EAAAHzA2MAIgLYAAAAAwSLAbwAAAABAE//OAIhAqoAEgBVQA8QDwUCBAQAAUoOBgIEAUlLsC9QWEAWAQEAAClLAAQEKksAAwMCXwACAi4CTBtAGQAEAAMABAN+AQEAAClLAAMDAl8AAgIuAkxZtxYRFBIQBQgZKxMzEQEzAQEGBgc1PgI3AwcVI09LASBT/wEBExeJfUNLKhDqSEsCqv6nAVn+0v6EXWMITQQbMCwBSFXzAAD//wAIAAACPQKrACICxQAAAQYEwNmuAAmxAgG4/66wMysAAAD//wAIAAACPQKrACICxQAAAQYExNmuAAmxAgG4/66wMysAAAD///9yAAACPQKrACICxQAAAQcE2f9D/64ACbECArj/rrAzKwD///9yAAACPQKrACICxQAAAQcEx/9D/64ACbECArj/rrAzKwD///+PAAACPQKrACICxQAAAQcEyf9g/64ACbECArj/rrAzKwD///+PAAACPQKrACICxQAAAQcEy/9g/64ACbECArj/rrAzKwD///8yAAACPQKqACICxQAAAQcEzf73/xQAEbECArj/FLAzK7EFAbDqsDMrAP///zIAAAI9AqoAIgLFAAABBwTP/vf/FAAJsQICuP8UsDMrAP////YAAAI9AqoAIgLFAAABBgTUpq0ACbECAbj/rbAzKwAAAP////0AAAI9AqsAIgLFAAABBgTWw64ACbECAbj/rrAzKwAAAP//AAwAAAI9A1oAIgLFAAAAAwS7AekAAP//AAwAAAI9AysAIgLFAAAAAwSV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/27/rgEDA+cA/AAAAAmxAQG4/66wMysA////nf84An0CuQAiAtwAAAAnBMD/bv+uAQMD5wD8AAAACbEBAbj/rrAzKwD//wA3/zgCfQK5ACIC3AAAAAMD5wD8AAD///6z/zgCTgKqACICywAAACcEz/54/xQBAwPnAPQAAAAJsQECuP8UsDMrAP///rP/OAJOAqoAIgLLAAAAJwS//vr/FAAmBG08/gEDA+cA9AAAABKxAQG4/xSwMyuxAgG4//6wMysAAP///xD/OAJOAqsAIgLLAAAAJwTL/uH/rgEDA+cA9AAAAAmxAQK4/66wMysA////EP84Ak4CqwAiAssAAAAnBMn+4f+uAQMD5wD0AAAACbEBArj/rrAzKwD///7z/zgCTgKrACICywAAACcEx/7E/64BAwPnAPQAAAAJsQECuP+usDMrAP///vP/OAJOAqsAIgLLAAAAJwS//sT/rgAnBNP/Kv+uAQMD5wD0AAAAErEBAbj/rrAzK7ECAbj/rrAzK////4n/OAJOAqsAIgLLAAAAJwTE/1r/rgEDA+cA9AAAAAmxAQG4/66wMysA////if84Ak4CqwAiAssAAAAnBMD/Wv+uAQMD5wD0AAAACbEBAbj/rrAzKwD//wBQ/zgCTgKqACICywAAAAMD5wD0AAD///8y/zgCPQKqACICxQAAACcEz/73/xQBAwPnAMYAAAAJsQICuP8UsDMrAP///zL/OAI9AqoAIgLFAAAAJwS//3n/FAAnBG0Au//+AQMD5wDGAAAAErECAbj/FLAzK7EDAbj//rAzK////4//OAI9AqsAIgLFAAAAJwTL/2D/rgEDA+cAxgAAAAmxAgK4/66wMysA////j/84Aj0CqwAiAsUAAAAnBMn/YP+uAQMD5wDGAAAACbECArj/rrAzKwD///9y/zgCPQKrACICxQAAACcEx/9D/64BAwPnAMYAAAAJsQICuP+usDMrAP///3L/OAI9AqsAIgLFAAAAJwS//0P/rgAmBNOprgEDA+cAxgAAABKxAgG4/66wMyuxAwG4/66wMysAAP//AAj/OAI9AqsAIgLFAAAAJgTE2a4BAwPnAMYAAAAJsQIBuP+usDMrAAAA//8ACP84Aj0CqwAiAsUAAAAmBMDZrgEDA+cAxgAAAAmxAgG4/66wMysAAAD//wAM/zgCPQKqACICxQAAAAMD5wDGAAAAAwAAAAABBQMNAA4AGgAeAJC1DgEEAwFKS7AYUFhAHwABAAACAQBnBgEDAwJfAAICPUsABARASwcBBQU+BUwbS7AvUFhAHQABAAACAQBnAAIGAQMEAgNnAAQEQEsHAQUFPgVMG0AiAAEAAAIBAGcAAgYBAwQCA2cABAUFBFUABAQFXQcBBQQFTVlZQBQbGw8PGx4bHh0cDxoPGSokEggJFysTNjUiJjU0NjMyFhUUBgc2JjU0NjMyFhUUBiMDETMRATgYIR4YGSA4LLQdHRUUHR0UcEQCPCU/HxgZHSUcMVsXCx0VFBwcFRMe/cwB1P4sAAAAAAEAAAACGdv8+H7YXw889QADA+gAAAAA1CaokgAAAADUrkVo/Mf+9QPiBGcAAAAHAAIAAAAAAAAAAQAABBb+9wAAA+L8x/84A+IAAQAAAAAAAAAAAAAAAAAABPgB9AAyAAAAAADfAAAA3wAAAkkADAJJAAwCSQAMAkkADAJJAAwCSQAMAkkADAJJAAwCSQAMAkkADAJJAAwCSQAMAkkADALEADcCSQAMAkkADAJJAAwCSQAMAkkADAJJAAwDNgAAAzYAAAI+AE8CPgBPAj4ATwK5ABsCPgBPAj4AKwJdADcCXQA3Al0ANwJdADcCXQA3Al0ANwJdADcCrQA3Al0ANwKBAFACgQAKAoEACgKBAFACgQBQAoEAUAKBAAoCgQBQAoEAUAL7ABsCgQBQAfcATwH3AE8B9wBPAfcATwH3AE8B9wBPAfcATwH3ABUB9wBPAfcATwH3AE8B9wBPAfcATwH3AE8B9wBPAfcATwH3AE8CBAAuAfcAJwHyACsB9wBPAfcATwIkAC0BwwBPAd//3gJeAB0CDwAAAl4AHQJeAB0CXgAdAl4AHQJeAB0CjgA3AcwAHQHRACACngBQAu4AUAKeAFACngBQAp4AUAKeAFACngBQAl8ATwE9ADcBPQAiAT0ADQE9AAMBPQADAT3/mgE9AAcBPQAHAT0ANwE9ADcBPQAiAT0AFwE9ABkBPQA3AQgASwFEACMBPf/zAT3/8wHwAAwB8AAMAfAADAIhAE8CIQBPAoYATwIhAE8B3ABPAdz/+wHcAE8B3ABPAdwATwG0AE8B3ABPAdwATwHcAAgDOQBQAzkAUAM5AFAClgBQApYAUAKWAFAClgBQApYAUAKWAFAClgBQApYAUAJ/AFACf//pApYAUAKDAE8ClgBQArgANwIyACMCuAA3ArgANwK4ADcCuAA3ArgANwK4ADcCuAA3ArgANwK4ADcCuAA3ArgANwK4ADcCuAA3ArgANwK4ADcCuAA3ArgANwK4ADcCuAA3Al0AIwK4ADcCuAA3ArgANwK4ADcCuAA3Az4ANwIXAFACFwBQApEAGwIOAFACngA3AsIANwJEAE8CRABPAkQATwJEAE8CRAAdAkQATwJEAE8CRABPApQAMgJEAE8CRwArAkcAKwDoAFICRwArAkcAKwJHACsCRwArAkcAKwJHACsCSABQArYANwIWAA8CFgAPAhYADwIWAA8CFgAPAhYADwIWAA8CFgAPAjUAEAIWAA8B+gAPApQAUAKUAFACoAAoApQAUAKUAFAClABQApQARQKUAFAClABQApQAUAKUAFAClABQApQAUAKUAFACrgAyApQAUAKUAFAClABQAisAAAKBAEgCNQAMA1MAFgNTABYDUwAWA1MAFgNTABYD4gAWAgQABQHk//EB5P/xAeT/8QHk//EB5P/xAoD/8QHk//EB5P/xAfcAJgH3ACYB9wAmAfcAJgH3ACYB9wAmApYAUAKW/98A4wAVAjEAIgIxACICMQAiAjEAIgIxACICMQAiAjEAIgIxACICMQAiAjEAIgIxACICMQAiAjEAIgJVACICMQAiAjEAIgIxACICMQAiAjEAIgIxACIDbAA3A2wANwIxAEACMQBAAjEAQAIxAEACMQBAAjkAAQHdACAB3QAgAd0AIAHdACAB3QAgAd0AIAHdACAB3QAgAd0AIAIxACICEwAiAncAIgIxACICMQAiAjEAIgIxACICMQAiAjcAIgIxACICMQAiAg0AIgINACICDQAiAg0AIgINACICDQAiAg0AIgINACICDQAiAg0AIgINACICDQAiAg0AIgINACICDQAiAg0AIgINACIBzwAqAMH/zgINACICDQAiAg0AJQINACUBpAAYARMAHAIxACIBjQABAjEAIgIxACICMQAiAjEAIgIxACICSwAiAc0AHQHNACcBuwAeAawAGQIWAD8CFgABAhb/3QIW/8kCFv/NAhYAPwIWAD8CGAA/AhYAPwIZAEYBDABQAQwAZAEMAFgBDAASAQwAEgEMABIBDP/AAQwABwEMAAcBDABQAQwAUAEMAAEBDAASAQwAIwEMAA0A5QA7AQwACgEMAAIBDAACAMH/0wDB/9MAwf/TAMH/0wD0/+8BtAA/AbQAPwHPAD8BswA+AbQAPwDLAEQAy//pAckAAgFnAAoBLwBEAMv/3gDLAC4BHwBEAMsAMADL/+AAy//pAwIAPwMCAD8DAgA/AhkAPwIZAD8CGQA/AhkAPwIZAD8CGQA/AhkAPwIZAD8CGQA/Ahb/1QIWAD8CGQA/AhkAPwITACICEwAiAhMAIgITACICEwAiAhMAIgITACICEwAiAhMAIgITACICEwAiAhMAIgITACICEwAiAhMAIgITACICEwAiAhMAIgITACICEwAiAd0AGQITACICEwAiAhMAIgITACICEwAiAggAPwOZACICMQBAAjEAQAIwAD8CNQBEAjEAIwIxACIBUwA/AVMAPwFTAD8BUwAtAVMANAFTAC8BrQA5AVMAPwFTAD8BUwA+AdgAJwHQAB4B0AAeALoAOwHQAB4B0AAeAdAAHgHQAB4B0AAeAdAAHgIUAFABEwAIARMACAETAAgBEwAIARP/9QETAAgBEwAIARMACAErABwBE//3AQ0AGQIZAEYCGQBGAjEAHQIZAEYCGQBGAhkARgIZAEYCGQBGAhkARgIZAEYCGQBGAhkARgIZAEYCGQBGAiwAKAIZAEYCGQBGAhkARgHHABUCFgBGAccAFALPABACzwAQAs8AEALPABACzwAQA20AEAHOACABwQAKAcEACgHBAAoBwQAKAcEACgJtAAoBwQAKAcEACgGkACUBpAAlAaQAJQGkACUBpAAlAaQAJQImABwDDQAcAvEAHAH6ABwB3gAcAZgAPAGYADwBOgAXAYIAPADcAB8CCgAlAYYALwJJAAwCOQBQAj4ATwHXAE8B1wBPAdgAUAKiABkB9wBPAfcATwH3AE8DzQAPAikAMgKaAFACmgBQApoAUAJcAFACXABQAn8ACgM5AFACngBQArgANwKLAFACFwBQAl0ANwIWAA8CHQAKAh0ACgMIADcCBAAFAlIANwKeAFADnQBQA4kAUAKKAFACGwBPAmMAAALpAFADiAAKA6cAUAJHACsCXQA3AmcALQE9ADcBPQAHAfAADAK2AA8DgQBQAk0AKAKsAA8CB//cAucAKAK4ADcCWP/2AdcAFwI+AFADzQAPAikAMgJcAFACXABQAsoADwKeAFADbQBQAosAUAJdADcB5P/xAeT/8QJSADcCUgA3AlIAUAE9ADcDzQAPAlIANwJJAAwCSQAMAzYAAAH3AE8CtgA3A80ADwIpADICmgBQApoAUAK4ADcCuAA3Ah0ACgIdAAoCHQAKAlIANwHXAE8C6QBQAp4ANwNTABYCMQAiAh4ALQHwAEYBjQBGAY0ARgF0AEYCIQAZAg0AIgINACICDQAiArQADwGgACgCKgBGAioARgIqAEYBzwA/Ac8APwICABkCeABGAioARgITACICFgBGAjEAQAHdACAB3AAPAcEACgHBAAoCfQAiAc4AIAH3ADcCEwBGArEAPwLIAD8CCABGAcYAPwH4AAACeABGAuEAGQL/AEYB0AAeAd0AIAHyAC8BDABQAQwABwDB/9MCFv/2AtwARgHmAC0CLQAKAcYAAAIsAC0CEwAiAcEAAAGNAAgB2gBGArQADwGgACgBzwA/Ac8APwIsAA8CKgBGArUARgIWAEYB3QAgAccAAQHHAAEB9wA3AfcANwIWAD8AyAA/ArQADwH3ADcCMQAiAjEAIgNsADcCDQAiAg4AHQK0AA8BoAAoAioARgIqAEYCEwAiAhMAIgHBAAoBwQAKAcEACgH3ADcBjQBGAngARgIxACMCzwAQAkkADAI+AE8BwwBPAjoADAH3AE8B9wAmAp4AUAK4ADcBPQA3AiEATwI1AAwDOQBQApYAUAH/ACcCuAA3ApUAUAIXAFAB7AAmAhYADwHk//EDCAA3AgQABQLbAEsCtAA3Akn//QH3/2ACnv9+AT3/YAK4/6YB5P9HArT/kgE9AAcB5P/xAiEATwJJAAgCSQAIAkn/cgJJ/3ICSf+PAkn/jwJJ/zICSf8yAkn/9gJJ//0CSQAMAkkADAMUAAwDFAAIAxQACAMU/3IDFP9yAxT/jwMU/48DFP8yAxT/MgH3/2sB9/9rAff+1QH3/tUB9/7yAff+8gH3/1kB9/9gAp7/iQKe/4kCnv7zAp7+8wKe/xACnv8QAp7+swKe/rMCnv93Ap7/fgNpAFADaf+JA2n/iQNp/vMDaf7zA2n/EANp/xADaf6zA2n+swE9/2sBPf9rAT3+1QE9/tUBPf7yAT3+8gE9/pUBPf6VAT3/WQE9/2ABPQANAT0AGQK4/7ECuP+xArj/GwK4/xsCuP84Arj/OAK4/58CuP+mAhf/iQHk/1IB5P68AeT+2QHk/nwB5P9AAeT/RwHk//EB5P/xArT/nQK0/50CtP8HArT/BwK0/yQCtP8kArT+xwK0/scCtP+LArT/kgN/ADcDf/+dA3//nQN//wcDf/8HA3//JAN//yQDf/7HA3/+xwJ0ACMCMQBAAdYAFQIQACIBzwAqAaoAIgJJACoCIAAsAREARgHeAD8B+AAFAkAAQAHqABUBuwAsAhMAIgJvABkCJwA2AdIAIAJHACIB3AAPAecAPAJ9ACIB7wAZAlsAPALVACcBEQBGARH/0gER/8YB5wA8AecAPAHnADwCEwAiAtUAJwJ0ACMBzwAqAkkAKgHKAD8CdAAjAnQAIwJ0ACMCdAAjAnQAIwJ0ACMCdAAjAnQAIwJ0ACMCdAAjAnQAIwJ0ACMCdAAjAnQAIwJ0ACMCdAAjAnQAIwJ0ACMCdAAjAnQAIwJ0ACMCdAAjAnQAIwJ0ACMCdAAjAc8AKgHPACoBzwAqAc8AKgHPACoBzwAqAc8AKgHPACoCSQAqAkkAKgJJACoCSQAqAkkAKgJJACoCSQAqAkkAKgJJACoCSQAqAkkAKgJJACoCSQAqAkkAKgJJACoCSQAqAkkAKgJJACoCSQAqAkkAKgJJACoCSQAqAkkAKgERADEBEQAlARH/5wER/+cBEf/7ARH/7AER/7wBEf+8AREAEAERAEYBEf/HARH/4gER/+QBEf+UARH/xgER/70CEwAiAhMAIgITACICEwAiAhMAIgITACICEwAiAhMAIgInADYCJwA2AecAPAHnADwB5wA8AecAPAHnADwB5wA8AecAOwHnADsB5wA8AecAPAHnADwB5wA8AecAPAHnABMB5wA8AecAPALVACcC1QAnAtUAJwLVACcC1QAnAtUAJwLVACcC1QAnAtUAJwLVACcC1QAnAtUAJwLVACcC1QAnAtUAJwLVACcC1QAnAtUAJwLVACcC1QAnAtUAJwLVACcC1QAnAoIAIgERAEYA5wBGAV4AFAF8ACICMQAdAZ4AKAHzABkCEwAyAj4AHgIxADgCJwAqAdMALQIqAC0CMQAuAQ4AHgFbACMBVAAbAWwAFAEE/84DHAAeAyIAHgNKABsBZAAyAXkAIQDcADgBSQAtANwAOADMAC8CUQAwAOgAPwDmAD4CZAA8AMsAMAHNAB0BzQAnASAAMgC6ADsA3AA3AXkAIgEUAD8BygAAANwAOAD3AAcA9wAbAQMAQwEDABQA6gArAOoAKwLTADgCDwA4AVQAOQFMADkB+AAoAfgAPAFOACgBTgA8AY8AMAFZACoBVgAnALwAKgCoACcAzAAwANwAOADcADcA3wAAAl0ANwHdACACXQA3AegALQJHAEcCMQA4AnEACgEh/+wBwwAAAl4AHQJcAC0CKwAAAdYANQIRACgCdQAKA2wAUAIeAAACLwAoAfcAMgHWADUCFgAPAhYADwM7ACgB5P/xAeMAIwFJAC0BBP/sAfQAMgH0ADIB9ABYAfQAMgH0ADIB9AAyAfQAWgH0ABkB9AA8AfQANAH0ADIB9AAyAfQAMgH0ADICQABAAuIAGQKVABkC5gAiAhQALQITACABtgAtAr4ALQK+AC0CTAAwAXEAMgCtAEIAswBFAdMAMgHTADIDqgBQAfQAIwEgADoBIABQAAD+ngAA/2IAAP7UAAD/EAAA/kYAAP9aAAD+vwAA/r8AAP6/AAD/BwAA/ncAAP7DAAD/pgAA/mwAAP6/AAD/YAAA/2EAAP9gAAD/YgAA/p4AAP8HAAD/YQAA/wEAAP7lAAD/pgAA/r8AAP6/AAD+dwAA/sMAAP5fAAD+yAAA/d0AAP7FAAD9XwAA/McAAPzHAAD+UAAA/lAAAP5QAAD+UAAA/p4AAP9iAAD+1AAA/tQAAP34AAD9xgAA/pcAAP6qAAD92gAA/ncAAP3aAAD9+AAA/tAAAP8tAAD/LQAA/tMAAP7nAAD+0gAA/scAAP8JAAD+5gAA/scAPf/OAPUAOQFvADIBhQA7AZcANgESADoBEgA6AYUAOwGEADoBdAAyAZoAOgFZADoBmQA7AdAAUAD3ADoBhAA7AZQAMgGVADsBcQA6ATMAOgHhADsAAP9dAAD/yQAA/1wAAP/iAAD+vgAA/qoBDAA6AQwAOgIMAFAAzgAvAM4ALwDOAC8AzgAvAM4ALwDOAC8BcgAvAXIALwFyAC8BiAAvAYgALwGIAC8BiAAvAeEAOwHhADsB4QA7AeEAOwIMAFACDABQAeEAOwEMAFABDABQAQwAOgEMADoB4QA7AbsAMgFyAC8AAP6wAAD+sAK0/scCtP7HArT/JAK0/yQCtP8HArT/BwK0/50CtP+dArQANwKe/rMCnv6zAp7/EAKe/xACnv7zAp7+8wKe/4kCnv+JAp4AUAJJ/zICSf8yAkn/jwJJ/48CSf9yAkn/cgJJAAgCSQAIAkkADAEMAAAAAAAYABgAGAAYAFgAZABwAHwAiACYAKQAsADGANIA3gDqAPwBbAF4AfgCBAIaAiYCMgKOApoC+gMGAxIDjAOYBBQEaAR0BIYFPAVOBVoFbAXQBkAGggbeBzoHRgdSB14HZgdyB34H3AfoCCgINAhACEwIWAhkCHQIgAiMCJgIpAiwCMIIzgjkCPoJegnkCiQKZApwCnwK3gsYC0wLqgvyC/4MCgwWDCIMLgycDOoNQA18DdoN5g3yDf4OCg4WDk4OiA6UDqAOrA64DsQO0A7mDvIO/g8KDxwPKA+KD7oQBhASEB4QWhBmELQQ8BD8EWYRchGeEaoRvBHIEdQR5hHyEf4SOBJwEnwSiBK+EsoS1hLiEu4S+hMGExITcBPOE9oUIhQuFIYU/BUIFRQVIBUoFTQVRBVQFVwVchV+FZQVoBWsFbgVyhXWFewWAhYUFmgW4hbuFvoXEBcmF+gYMBg8GLoZCBl0GggaXhpqGnYaghqOGpoarBq4GxgbfBvcG+gcAhwOHNIc3hzqHPYdAh16HYIdsh30HgAeDB4YHiQeMB48Hn4eih62HvAe/B9QH1wfaB90H4AfjB+YH6QfsB/CH84gSiCkILAgvCDIIPQhNCE8IXYhgiGOIZohpiIEIjwibCJ4IoQikCKcIvAi/CMII0QjUCNcI2gjdCOAI84kGiRWJMQk0CTcJOgk9CUEJRAlHCUyJT4lSiVWJWIlziXaJoQmkCamJrImviecJ6goGCgkKDAorCi4KTwplCmgKawqZCsmKzIrPiuoLB4sji0MLRgt9C4ALoAujC6YLxYvIi+cMAYwEjAeMCow1DDgMPAw/DEIMRQxIDEsMTgxRDFaMXAyHDIkMlIyXjJqMtYy3jNUM6A0HDR2NII0jjSaNKY0sjU6NYw13jYwNoY20jcyN0Q3VjdoN3Q3gDfaN+Y4Ljg6OGI4bjh6OIY4kjieOKo4wDjMONw46Dj0OQA5ljnIOjg6RDpUOmA6kDqcOqg7JjtmO3I7rjv0PAA8JDw2PHg83DzoPPQ9AD0SPR49Kj1cPcA9zD3YPiI+Lj46PkY+Uj5ePmo+dj7MPyo/dj+CP44/6D/0P/xACEAUQCBAMEA8QEhAXkBqQIBAjECYQKRAsEC8QNJA6EFoQcBCPEJIQlRC8EMGQ3ZEFESARIxFBEVwRdxGVkaeRqpGtkbCRs5G2kcWR2xHeEeER9pIPEhISFBIXEj2SQJJDkkaSSZJmEnUSiJKLko6SkZKUkpkSnBKxkrSSxpLZEtwS9ZL4kvuS/pMBkwSTB5MKkw2TEJMTkzSTS5NOk1GTVJNfk3CTexOJk4yTj5OSk5WTrhO8E8eTypPNk9CT05Pmk+mT7JP8k/+UApQFlAiUC5QkFEuUZ5SHlJyUvpTRFOAU8JUAFQqVEpUilTYVOBVDlUaVV5VslXyVf5WClZgVs5XBFcQVxxXWldmV6JXqleyWApYPFhEWExYVFiSWJ5Y+FkAWUhZhFm8WgBaTlqUWuJbNluWW/Rb/FxaXL5cxlzSXNpdLl2mXfxeal7KXyJfKl9sX6xgFGB2YNRhIGGMYdJiGGJcYphi3GLkYypjgGPcZCRkLGQ4ZI5kmmSmZK5kumUkZTBlPGVIZVRlYGXKZdxl6GX0ZgBmOGZEZkxmVGbCZy5nkGfAZ8xoEmhmaNBo3GjoaTxprGngaexp+Go2akJqfmrAavxrVmuKa5Jrmmuia9Br3GxKbFJsnmzcbRhtYG2wbfpuSm6ibwJvYm9qb8pwLnA6cEZwUnCycShxfHH+cl5ytHK8cvpzOnOec/x0bnS4dRB1VHWadd52HnZ2dqp28HdGd6R3rHfQd9x4Mng+eEp4UnheeMh41HjgeOx4+HkEeW55enmGeZJ5nnnYeeR57Hn0ejR6PHpqeqR65Hrseyh7lHvOe9Z8AnwKfBJ8YHy4fOp9Mn1yfXp9qn2yfbp+Bn5gfnJ+hH6Wfqh+un7Mft5+6n72f0Z/WH9qf3x/jn+gf7J/yH/af+x//oAKgBaAIoA4gE6AbIDygX6CBIIigtqC7IL+gxiDjIQAhHSEhoSYhKqEvITWhUCFqIYShiyGyIbahuyG+IcOhySHQofKiFKI2oj4ibKJxInWifCKXIrIizSLTovgi/KMBIwQjByMLoxAjFqM5o1yjf6OEI4ijjSORo6ejviPho+Yj6qPto/Cj9SP5pAAkIiREJGYkbKSaJJ6koySmJKuksSS4pOIlC6U1JTylcaWPpbIlwKXfpfkmDCYoJkSmUSZmpoWmnKappsUm2yb1pwynIKc1J0enWCd4p5SnqKfAp8OnxqfJp8ynz6fSp9Wn2Kfbp96n4afzp/an+af9qCyoW6iKqJAozSjQKNMo1ijZKNwo3yjjKOco6yjvKPQpLqloqaMpqanxqfWp+Kn7qf+qKypWqoIqhSqIKosqjiqSKsGq8ishKyarZqtpq2yrb6tyq3Yreit+K4IrhqvBq/2sN6w9rIksjKyPrJKsliyzLNAs7SzyLRytH60irSWtKK0rrS6tMa03LTotPS1BLWgtjy22LbktvC2/LcItxS3ILcwt7S4OLi8uNK5jLmYuaS5sLm8uci51Lngufa6AroOuh66xLtqvBC8Jr0CvQ69Gr0mvTK9Qr1SvWK9cr2Gvlq/LsACwBzBJME0wXLBmsHCwgbCWMKuwtjDLsOcw+LEQsSwxNzFXMXIxejGKsaAxrLG2MbuxwTHGsdQx3bHnMfEyBTISMhYyJrIzMk6yWrJ1MowyjzKVspoyo7Knsq+ysbLIMt2y5zLwsv0zCTMQMxczHjMgMygzKzMwMzUzSLNeM24zfLOHM5Qzl7OcM5wzvDPRs/W0FLQ0tF20fjSStKW0zLTytQa1KTVAtWM1rLXPNei19TYTNiS2N7ZhNne2mractqE2q7avtra2ybbUtt224zbotvA297cFtws3HTcptyu3UjeCt8Q35bf4uBg4OThaOGO4dDh6OIU4lDinOMm4zzjTuNe457jyOPa4+zkBuQo5DzkUOSC5MTlBuUm5UblYOWS5b7l6uYY5kLmUuaU5sDnDOdK51rnbud+58Dn4OgA6CDoQOhS6HDokOiw6NLo+Oke6UDpfOmi6bTpxung6fTqCOo06nLqzurq6wTrTOte63DrhOuY68LsFOww7FzsrOy07Ors9O0G7UjtfO2s7b7t0O3a7e7uOO5M7orutO7G7tDu8O8k72bvpO+u77bvwO/I8ALwLvBA8FLwnvDM8Pbw/vEG8TTxXvFq8abx3vIY8k7yivLC8tTy5vNE857z6vQ29Ej0WvRs9H70kPTS9Nz06PUS9Tz1UvVw9Yb1nPWy9dD15vX89gj2HvY89lL2aPZ+9pz2svbI9tT26vcI9x73NPdK92j3fveU96D4GQAAAAEAAAT4AGAABwBgAAUAAgA2AEcAiwAAAJgNFgAEAAIAAAAaAT4AAQAAAAAAAABLAAAAAQAAAAAAAQANAEsAAQAAAAAAAgAHAFgAAQAAAAAAAwAfAF8AAQAAAAAABAAVAH4AAQAAAAAABQANAJMAAQAAAAAABgAUAKAAAQAAAAAACAAOALQAAQAAAAAACQAOAMIAAQAAAAAACwAZANAAAQAAAAAADAAZAOkAAQAAAAAADQCQAQIAAQAAAAAADgAaAZIAAwABBAkAAACWAawAAwABBAkAAQAaAkIAAwABBAkAAgAOAlwAAwABBAkAAwA+AmoAAwABBAkABAAqAqgAAwABBAkABQAaAtIAAwABBAkABgAoAuwAAwABBAkACAAcAxQAAwABBAkACQAcAzAAAwABBAkACwAyA0wAAwABBAkADAAyA34AAwABBAkADQEgA7AAAwABBAkADgA0BNBDb3B5cmlnaHQgMjAxNiBUaGUgRGlkYWN0IEdvdGhpYyBQcm9qZWN0IEF1dGhvcnMgKGlsLmJhc3NvLmJ1ZmZvQGdtYWlsLmNvbSlEaWRhY3QgR290aGljUmVndWxhcjIuMTAxO0NZUkU7RGlkYWN0R290aGljLVJlZ3VsYXJEaWRhY3QgR290aGljIFJlZ3VsYXJWZXJzaW9uIDIuMTAxRGlkYWN0R290aGljLVJlZ3VsYXJEYW5pZWwgSm9obnNvbkRhbmllbCBKb2huc29uaHR0cDovL2RhbmllbGpvaG5zb24ubmFtZWh0dHA6Ly9kYW5pZWxqb2huc29uLm5hbWVUaGlzIEZvbnQgU29mdHdhcmUgaXMgbGljZW5zZWQgdW5kZXIgdGhlIFNJTCBPcGVuIEZvbnQgTGljZW5zZSwgVmVyc2lvbiAxLjEuIFRoaXMgbGljZW5zZSBpcyBhdmFpbGFibGUgd2l0aCBhIEZBUSBhdDogaHR0cDovL3NjcmlwdHMuc2lsLm9yZy9PRkxodHRwOi8vc2NyaXB0cy5zaWwub3JnL09GTABDAG8AcAB5AHIAaQBnAGgAdAAgADIAMAAxADYAIABUAGgAZQAgAEQAaQBkAGEAYwB0ACAARwBvAHQAaABpAGMAIABQAHIAbwBqAGUAYwB0ACAAQQB1AHQAaABvAHIAcwAgACgAaQBsAC4AYgBhAHMAcwBvAC4AYgB1AGYAZgBvAEAAZwBtAGEAaQBsAC4AYwBvAG0AKQBEAGkAZABhAGMAdAAgAEcAbwB0AGgAaQBjAFIAZQBnAHUAbABhAHIAMgAuADEAMAAxADsAQwBZAFIARQA7AEQAaQBkAGEAYwB0AEcAbwB0AGgAaQBjAC0AUgBlAGcAdQBsAGEAcgBEAGkAZABhAGMAdAAgAEcAbwB0AGgAaQBjACAAUgBlAGcAdQBsAGEAcgBWAGUAcgBzAGkAbwBuACAAMgAuADEAMAAxAEQAaQBkAGEAYwB0AEcAbwB0AGgAaQBjAC0AUgBlAGcAdQBsAGEAcgBEAGEAbgBpAGUAbAAgAEoAbwBoAG4AcwBvAG4ARABhAG4AaQBlAGwAIABKAG8AaABuAHMAbwBuAGgAdAB0AHAAOgAvAC8AZABhAG4AaQBlAGwAagBvAGgAbgBzAG8AbgAuAG4AYQBtAGUAaAB0AHQAcAA6AC8ALwBkAGEAbgBpAGUAbABqAG8AaABuAHMAbwBuAC4AbgBhAG0AZQBUAGgAaQBzACAARgBvAG4AdAAgAFMAbwBmAHQAdwBhAHIAZQAgAGkAcwAgAGwAaQBjAGUAbgBzAGUAZAAgAHUAbgBkAGUAcgAgAHQAaABlACAAUwBJAEwAIABPAHAAZQBuACAARgBvAG4AdAAgAEwAaQBjAGUAbgBzAGUALAAgAFYAZQByAHMAaQBvAG4AIAAxAC4AMQAuACAAVABoAGkAcwAgAGwAaQBjAGUAbgBzAGUAIABpAHMAIABhAHYAYQBpAGwAYQBiAGwAZQAgAHcAaQB0AGgAIABhACAARgBBAFEAIABhAHQAOgAgAGgAdAB0AHAAOgAvAC8AcwBjAHIAaQBwAHQAcwAuAHMAaQBsAC4AbwByAGcALwBPAEYATABoAHQAdABwADoALwAvAHMAYwByAGkAcAB0AHMALgBzAGkAbAAuAG8AcgBnAC8ATwBGAEwAAAACAAAAAAAA/7UAMgAAAAAAAAAAAAAAAAAAAAAAAAAABPgAAAECAAIAAwAkAMkBAwEEAMcBBQEGAGIBBwEIAQkArQEKAQsBDAENAGMBDgEPAK4AkAEQACUBEQESARMBFAEVACYA/QD/AGQBFgEXARgBGQEaACcBGwDpARwBHQEeAR8BIAEhASIBIwAoAGUBJAElASYAyAEnASgAygEpASoAywErASwBLQEuAS8BMAExATIBMwE0ATUAKQE2ACoBNwD4ATgBOQE6ATsBPAE9AT4AKwE/AUABQQFCAUMBRAFFACwAzAFGAUcAzQFIAM4BSQD6AUoAzwFLAUwBTQFOAU8BUAFRAC0BUgFTAC4BVAFVAVYALwFXAVgBWQFaAVsBXAFdAOIAMAFeAV8AMQFgAWEBYgFjAWQBZQFmAWcBaAFpAWoAZgAyAWsA0AFsAW0BbgDRAW8BcABnAXEBcgFzAXQA0wF1AXYBdwF4AXkBegF7AJEBfACvAX0BfgCwADMBfwGAAO0ANAGBADUBggGDAYQBhQGGAYcBiAGJAYoANgGLAYwA5AD7AY0BjgGPAZABkQGSADcBkwGUAZUBlgGXAZgBmQGaAZsBnAA4ANQBnQGeAZ8A1QGgAGgBoQDWAaIBowGkAaUBpgGnAagBqQA5AaoBqwA6AawBrQGuAa8BsAA7ADwA6wGxALsBsgGzAbQBtQA9AbYA5gG3AbgBuQG6AbsBvABEAGkBvQG+AGsBvwHAAGwBwQHCAcMAagHEAcUBxgHHAG4ByAHJAG0AoAHKAEUBywHMAc0BzgHPAEYA/gEAAG8B0AHRAdIB0wHUAEcA6gHVAdYB1wEBAdgB2QHaAdsB3ABIAHAB3QHeAd8AcgHgAeEAcwHiAeMAcQHkAeUB5gHnAegB6QHqAesB7AHtAe4B7wBJAEoB8AD5AfEB8gHzAfQB9QH2AfcB+AH5AEsB+gH7AfwB/QH+Af8CAAIBAgIATADXAHQCAwIEAHYCBQB3AgYCBwIIAHUCCQIKAgsCDAINAg4CDwBNAhACEQISAhMATgIUAhUCFgIXAE8CGAIZAhoCGwIcAh0CHgIfAiAA4wBQAiECIgBRAiMCJAIlAiYCJwIoAikCKgIrAiwCLQB4AFIAeQIuAi8CMAB7AjECMgB8AjMCNAI1AjYAegI3AjgCOQI6AjsCPAI9AKECPgB9Aj8CQAJBALEAUwJCAkMA7gBUAkQAVQJFAkYCRwJIAkkCSgJLAkwCTQJOAFYCTwJQAOUA/AJRAlICUwJUAIkAVwJVAlYCVwJYAlkCWgJbAlwCXQJeAFgAfgJfAmACYQCAAmIAgQJjAH8CZAJlAmYCZwJoAmkCagJrAFkCbAJtAFoCbgJvAnACcQJyAFsAXADsAnMAugJ0AnUCdgJ3AF0CeADnAnkCegJ7AnwCfQJ+AMAAwQCdAJ4CfwKAAoECggKDAoQChQKGAocCiAKJAooCiwKMAo0CjgKPApACkQKSApMClAKVApYClwKYApkCmgKbApwCnQKeAp8CoAKhAqICowKkAqUCpgKnAqgCqQKqAqsCrAKtAq4CrwKwArECsgKzArQCtQK2ArcCuAK5AroCuwK8Ar0CvgK/AsACwQLCAsMCxALFAsYCxwLIAskCygLLAswCzQLOAs8C0ALRAtIC0wLUAtUC1gLXAtgC2QLaAtsC3ALdAt4C3wLgAuEC4gLjAuQC5QLmAucC6ALpAuoC6wLsAu0C7gLvAvAC8QLyAvMC9AL1AvYC9wL4AvkC+gL7AvwC/QL+Av8DAAMBAwIDAwMEAwUDBgMHAwgDCQMKAwsDDAMNAw4DDwMQAxEDEgMTAxQDFQMWAxcDGAMZAxoDGwMcAx0DHgMfAyADIQMiAyMDJAMlAyYDJwMoAykDKgMrAywDLQMuAy8DMAMxAzIDMwM0AzUDNgM3AzgDOQM6AzsDPAM9Az4DPwNAA0EDQgNDA0QDRQNGA0cDSANJA0oDSwNMA00DTgNPA1ADUQNSA1MDVANVA1YDVwNYA1kDWgNbA1wDXQNeA18DYANhA2IDYwNkA2UDZgNnA2gDaQNqA2sDbANtA24DbwNwA3EDcgNzA3QDdQN2A3cDeAN5A3oDewN8A30DfgN/A4ADgQOCA4MDhAOFA4YDhwOIA4kDigOLA4wDjQOOA48DkAORA5IDkwOUA5UDlgOXA5gDmQOaA5sDnAOdA54DnwOgA6EDogOjA6QDpQOmA6cDqAOpA6oDqwOsA60DrgOvA7ADsQOyA7MDtAO1A7YDtwO4A7kDugO7A7wDvQO+A78DwAPBA8IDwwPEA8UDxgPHA8gDyQPKAJsDywPMA80DzgPPA9AD0QPSA9MD1APVA9YD1wPYA9kD2gPbA9wD3QPeA98D4APhA+ID4wPkA+UD5gPnA+gD6QPqA+sD7APtA+4D7wPwA/ED8gPzA/QD9QP2A/cD+AP5A/oD+wP8A/0D/gP/BAAEAQQCBAMEBAQFBAYEBwQIBAkECgQLBAwEDQQOBA8EEAQRBBIEEwQUBBUEFgQXBBgEGQQaBBsEHAQdBB4EHwQgBCEEIgQjBCQEJQQmBCcEKAQpBCoEKwQsBC0ELgQvBDAEMQQyBDMENAQ1BDYENwQ4BDkEOgQ7BDwEPQQ+BD8EQARBBEIEQwREBEUERgRHBEgESQRKBEsETARNBE4ETwRQBFEEUgRTBFQEVQRWBFcEWARZBFoEWwRcBF0AEwAUABUAFgAXABgAGQAaABsAHAReBF8EYARhALwA9AD1APYADQA/AMMAhwAdAA8AqwAEAKMABgARACIAogAFAAoAHgASBGIAQgRjAF4AYAA+AEAACwAMALMAsgAQBGQAqQCqAL4AvwDFALQAtQC2ALcAxARlBGYEZwRoAIQEaQC9AAcEagRrAKYA9wRsBG0EbgRvBHAEcQRyBHMEdAR1AIUEdgR3BHgAlgR5BHoEewAOAO8A8AC4ACAAjwAhAB8AlQCUAJMApwBhAKQEfAAIAMYAIwAJAIgAhgCLAIoAjACDAF8A6ACCAMIEfQBBBH4EfwSABIEEggSDBIQEhQSGBIcEiASJBIoEiwSMBI0EjgSPBJAEkQSSBJMElASVBJYElwSYBJkEmgSbBJwEnQSeBJ8EoAShBKIEowSkBKUEpgSnBKgEqQSqBKsErAStBK4ErwSwBLEEsgSzBLQEtQS2BLcEuAS5BLoEuwS8BL0EvgS/BMAEwQTCBMMExATFAI0A2wDhAN4A2ACOANwAQwDfANoA4ADdANkExgTHBMgEyQTKBMsEzATNBM4EzwTQBNEE0gTTBNQE1QTWBNcE2ATZBNoE2wTcBN0E3gTfBOAE4QTiBOME5ATlBOYE5wToBOkE6gTrBOwE7QTuBO8E8ATxBPIE8wT0BPUE9gT3BPgE+QT6BPsE/AT9BP4E/wUABQEFAgUDBQQFBQUGBQcETlVMTAZBYnJldmUHdW5pMDFDRAd1bmkxRUFDB3VuaTAyMDAHdW5pMDFERQd1bmkwMjI2B3VuaTFFQTAHdW5pMDIwMgd1bmkyQzZEB0FtYWNyb24HQW9nb25lawpBcmluZ2FjdXRlB3VuaTFFMDAHQUVhY3V0ZQd1bmkxRTAyB3VuaTFFMDQHdW5pMDE4MQd1bmkxRTA2B3VuaTAyNDMHdW5pMUUwOAtDY2lyY3VtZmxleApDZG90YWNjZW50B3VuaTAxODcHdW5pMDIzQgd1bmkwMTg5BkRjYXJvbgd1bmkxRTEwB3VuaTFFMTIGRGNyb2F0B3VuaTFFMEEHdW5pMUUwQwd1bmkwMThBB3VuaTFFMEUGRWJyZXZlBkVjYXJvbgd1bmkwMjI4B3VuaTFFQzYHdW5pMDIwNApFZG90YWNjZW50B3VuaTFFQjgHdW5pMDIwNgdFbWFjcm9uB3VuaTFFMTYHdW5pMUUxNAdFb2dvbmVrB3VuaTAxOTAHdW5pMDE4RQd1bmkwMUE5B3VuaTFFQkMHdW5pMUUxQQd1bmkwMUI3B3VuaTAxOTEHdW5pMDE5NAZHY2Fyb24LR2NpcmN1bWZsZXgMR2NvbW1hYWNjZW50Ckdkb3RhY2NlbnQHdW5pMDE5Mwd1bmkwMjQxB3VuaTAyMUMESGJhcgd1bmkwMjFFC0hjaXJjdW1mbGV4B3VuaTFFMjYHdW5pMUUyMgd1bmkxRTI0B3VuaUE3OEQGSWJyZXZlB3VuaTAxQ0YHdW5pMDIwOAd1bmkxRTJFB3VuaTFFQ0EHdW5pMDIwQQdJbWFjcm9uB0lvZ29uZWsHdW5pMDE5Ngd1bmkwMTk3Bkl0aWxkZQd1bmkxRTJDC0pjaXJjdW1mbGV4B3VuaTAyNDgMS2NvbW1hYWNjZW50B3VuaTAxOTgHdW5pMUUzNAZMYWN1dGUGTGNhcm9uB3VuaTFFM0MMTGNvbW1hYWNjZW50BExkb3QHdW5pMUUzNgd1bmkxRTNBB3VuaTFFM0UHdW5pMUU0MgZOYWN1dGUGTmNhcm9uB3VuaTFFNEEMTmNvbW1hYWNjZW50B3VuaTFFNDQHdW5pMUU0Ngd1bmkwMUY4A0VuZwd1bmkwMTlEB3VuaTFFNDgHdW5pMDIyMAd1bmkwMjIyBk9icmV2ZQd1bmkwMUQxB3VuaTAxOUYHdW5pMUVEOAd1bmkwMjBDB3VuaTAyMkEHdW5pMDIyRQd1bmkwMjMwB3VuaTFFQ0MNT2h1bmdhcnVtbGF1dAd1bmkwMjBFB09tYWNyb24HdW5pMUU1Mgd1bmkxRTUwB3VuaTAxRUEHdW5pMDE4NgtPc2xhc2hhY3V0ZQd1bmkxRTRDB3VuaTAyMkMHdW5pMUU1Ngd1bmkwMUE0B3VuaTAyNEEGUmFjdXRlBlJjYXJvbgxSY29tbWFhY2NlbnQHdW5pMDIxMAd1bmkxRTVBB3VuaTAyMTIHdW5pMUU1RQd1bmkwMjRDB3VuaTJDNjQGU2FjdXRlB3VuaUE3OEILU2NpcmN1bWZsZXgMU2NvbW1hYWNjZW50B3VuaTFFNjAHdW5pMUU2Mgd1bmkxRTlFB3VuaTAxOEYEVGJhcgZUY2Fyb24HdW5pMDE2Mgd1bmkxRTcwB3VuaTAyMUEHdW5pMUU2QQd1bmkxRTZDB3VuaTAxQUMHdW5pMUU2RQd1bmkwMUFFB3VuaTAyNDQGVWJyZXZlB3VuaTAxRDMHdW5pMDIxNAd1bmkxRUU0DVVodW5nYXJ1bWxhdXQHdW5pMDIxNgdVbWFjcm9uB1VvZ29uZWsHdW5pMDFCMQVVcmluZwZVdGlsZGUHdW5pMUU3NAd1bmkwMUIyB3VuaTAyNDUGV2FjdXRlC1djaXJjdW1mbGV4CVdkaWVyZXNpcwZXZ3JhdmUHdW5pMkM3MgtZY2lyY3VtZmxleAZZZ3JhdmUHdW5pMDFCMwd1bmkwMjMyB3VuaTFFRjgGWmFjdXRlClpkb3RhY2NlbnQHdW5pMUU5Mgd1bmkxRTk0CEVuZy5zczAxDHVuaTAxOUQuc3MwMQd1bmkwMjZBBmFicmV2ZQd1bmkwMUNFB3VuaTFFQUQHdW5pMDIwMQd1bmkwMURGB3VuaTAyMjcHdW5pMUVBMQd1bmkwMjAzB3VuaTAyNTEHYW1hY3Jvbgdhb2dvbmVrCmFyaW5nYWN1dGUHdW5pMUUwMQdhZWFjdXRlB3VuaTFFMDMHdW5pMUUwNQd1bmkwMjUzB3VuaTFFMDcHdW5pMDE4MAd1bmkxRTA5C2NjaXJjdW1mbGV4CmNkb3RhY2NlbnQHdW5pMDE4OAd1bmkwMjNDBmRjYXJvbgd1bmkxRTExB3VuaTFFMTMHdW5pMUUwQgd1bmkxRTBEB3VuaTAyNTcHdW5pMUUwRgd1bmkwMjU2BmVicmV2ZQZlY2Fyb24HdW5pMDIyOQd1bmkxRUM3B3VuaTAyMDUKZWRvdGFjY2VudAd1bmkxRUI5B3VuaTAyMDcHZW1hY3Jvbgd1bmkxRTE3B3VuaTFFMTUHZW9nb25lawd1bmkwMjVCB3VuaTAyODMHdW5pMUVCRAd1bmkxRTFCB3VuaTAxREQHdW5pMDI1OQd1bmkwMjkyB3VuaTAyNjMGZ2Nhcm9uC2djaXJjdW1mbGV4DGdjb21tYWFjY2VudApnZG90YWNjZW50B3VuaTAyNjAHdW5pMDI5NAd1bmkwMjk1B3VuaTAyNDIHdW5pMDIxRARoYmFyB3VuaTAyMUYLaGNpcmN1bWZsZXgHdW5pMUUyNwd1bmkxRTIzB3VuaTFFMjUHdW5pMDI2Ngd1bmkxRTk2B3VuaTAyNjUGaWJyZXZlB3VuaTAxRDAHdW5pMDIwOQd1bmkxRTJGCWkubG9jbFRSSwd1bmkxRUNCB3VuaTAyMEIHaW1hY3Jvbgdpb2dvbmVrB3VuaTAyNjkHdW5pMDI2OAZpdGlsZGUHdW5pMUUyRAd1bmkwMjM3B3VuaTAxRjALamNpcmN1bWZsZXgHdW5pMDI0OQxrY29tbWFhY2NlbnQMa2dyZWVubGFuZGljB3VuaTAxOTkHdW5pMUUzNQZsYWN1dGUHdW5pMDE5Qgd1bmkwMjZDBmxjYXJvbgd1bmkxRTNEDGxjb21tYWFjY2VudARsZG90B3VuaTFFMzcHdW5pMUUzQgd1bmkxRTNGB3VuaTFFNDMGbmFjdXRlBm5jYXJvbgd1bmkxRTRCDG5jb21tYWFjY2VudAd1bmkxRTQ1B3VuaTFFNDcHdW5pMDFGOQNlbmcHdW5pMDI3Mgd1bmkwMTlFB3VuaTFFNDkHdW5pMDI3NQZvYnJldmUHdW5pMDFEMgd1bmkxRUQ5B3VuaTAyMEQHdW5pMDIyQgd1bmkwMjJGB3VuaTAyMzEHdW5pMUVDRA1vaHVuZ2FydW1sYXV0B3VuaTAyMEYHb21hY3Jvbgd1bmkxRTUzB3VuaTFFNTEHdW5pMDFFQgd1bmkwMjU0C29zbGFzaGFjdXRlB3VuaTFFNEQHdW5pMDIyRAd1bmkwMjIzB3VuaTFFNTcHdW5pMDFBNQd1bmkwMjRCBnJhY3V0ZQZyY2Fyb24McmNvbW1hYWNjZW50B3VuaTAyMTEHdW5pMUU1Qgd1bmkwMjdFB3VuaTAyN0QHdW5pMDIxMwd1bmkxRTVGB3VuaTAyNzkGc2FjdXRlB3VuaUE3OEMLc2NpcmN1bWZsZXgMc2NvbW1hYWNjZW50B3VuaTFFNjEHdW5pMUU2MwR0YmFyBnRjYXJvbgd1bmkwMTYzB3VuaTFFNzEHdW5pMDIxQgd1bmkxRTZCB3VuaTFFNkQHdW5pMDFBRAd1bmkxRTZGB3VuaTAyODgHdW5pMDI4OQZ1YnJldmUHdW5pMDFENAd1bmkwMjE1B3VuaTFFRTUNdWh1bmdhcnVtbGF1dAd1bmkwMjE3B3VtYWNyb24HdW9nb25lawd1bmkwMjhBBXVyaW5nBnV0aWxkZQd1bmkxRTc1B3VuaTAyOEIHdW5pMDI4QwZ3YWN1dGULd2NpcmN1bWZsZXgJd2RpZXJlc2lzBndncmF2ZQd1bmkyQzczC3ljaXJjdW1mbGV4BnlncmF2ZQd1bmkwMUI0B3VuaTAyMzMHdW5pMUVGOQZ6YWN1dGUKemRvdGFjY2VudAd1bmkxRTkzB3VuaTFFOTUDZl9mBWZfZl9pBWZfZl9sB3VuaTAxQzIHdW5pMDJCMAd1bmkwMkIyB3VuaTAyQjcHdW5pMDJCOAd1bmkwNDEwB3VuaTA0MTEHdW5pMDQxMgd1bmkwNDEzB3VuaTA0MDMHdW5pMDQ5MAd1bmkwNDE0B3VuaTA0MTUHdW5pMDQwMAd1bmkwNDAxB3VuaTA0MTYHdW5pMDQxNwd1bmkwNDE4B3VuaTA0MTkHdW5pMDQwRAd1bmkwNDFBB3VuaTA0MEMHdW5pMDQxQgd1bmkwNDFDB3VuaTA0MUQHdW5pMDQxRQd1bmkwNDFGB3VuaTA0MjAHdW5pMDQyMQd1bmkwNDIyB3VuaTA0MjMHdW5pMDQwRQd1bmkwNDI0B3VuaTA0MjUHdW5pMDQyNwd1bmkwNDI2B3VuaTA0MjgHdW5pMDQyOQd1bmkwNDBGB3VuaTA0MkMHdW5pMDQyQQd1bmkwNDJCB3VuaTA0MDkHdW5pMDQwQQd1bmkwNDA1B3VuaTA0MDQHdW5pMDQyRAd1bmkwNDA2B3VuaTA0MDcHdW5pMDQwOAd1bmkwNDBCB3VuaTA0MkUHdW5pMDQyRgd1bmkwNDAyB3VuaTA0NjIHdW5pMDQ2QQd1bmkwNDcyB3VuaTA0NzQHdW5pMDQ5Mgd1bmkwNDk0B3VuaTA0OTYHdW5pMDQ5OAd1bmkwNDlBB3VuaTA0OUMHdW5pMDRBMAd1bmkwNEEyB3VuaTA0QTQHdW5pMDUyNAd1bmkwNEFBB3VuaTA0QUUHdW5pMDRCMAd1bmkwNEI2B3VuaTA0QjgHdW5pMDRCQQd1bmkwNEMwB3VuaTA0QzEHdW5pMDRDQgd1bmkwNEQwB3VuaTA0RDIHdW5pMDRENAd1bmkwNEQ2B3VuaTA0RDgHdW5pMDREQwd1bmkwNERFB3VuaTA0RTIHdW5pMDRFNAd1bmkwNEU2B3VuaTA0RTgHdW5pMDRFRQd1bmkwNEYwB3VuaTA0RjIHdW5pMDRGNAd1bmkwNEY2B3VuaTA0RjgHdW5pMDUxQQd1bmkwNTFDB3VuaTA0MzAHdW5pMDQzMQd1bmkwNDMyB3VuaTA0MzMHdW5pMDQ1Mwd1bmkwNDkxB3VuaTA0MzQHdW5pMDQzNQd1bmkwNDUwB3VuaTA0NTEHdW5pMDQzNgd1bmkwNDM3B3VuaTA0MzgHdW5pMDQzOQd1bmkwNDVEB3VuaTA0M0EHdW5pMDQ1Qwd1bmkwNDNCB3VuaTA0M0MHdW5pMDQzRAd1bmkwNDNFB3VuaTA0M0YHdW5pMDQ0MAd1bmkwNDQxB3VuaTA0NDIHdW5pMDQ0Mwd1bmkwNDVFB3VuaTA0NDQHdW5pMDQ0NQd1bmkwNDQ3B3VuaTA0NDYHdW5pMDQ0OAd1bmkwNDQ5B3VuaTA0NUYHdW5pMDQ0Qwd1bmkwNDRBB3VuaTA0NEIHdW5pMDQ1OQd1bmkwNDVBB3VuaTA0NTUHdW5pMDQ1NAd1bmkwNDREB3VuaTA0NTYHdW5pMDQ1Nwd1bmkwNDU4B3VuaTA0NUIHdW5pMDQ0RQd1bmkwNDRGB3VuaTA0NTIHdW5pMDQ2Mwd1bmkwNDZCB3VuaTA0NzMHdW5pMDQ3NQd1bmkwNDkzB3VuaTA0OTUHdW5pMDQ5Nwd1bmkwNDk5B3VuaTA0OUIHdW5pMDQ5RAd1bmkwNEExB3VuaTA0QTMHdW5pMDRBNQd1bmkwNTI1B3VuaTA0QUIHdW5pMDRBRgd1bmkwNEIxB3VuaTA0QjcHdW5pMDRCOQd1bmkwNEJCB3VuaTA0Q0YHdW5pMDRDMgd1bmkwNENDB3VuaTA0RDEHdW5pMDREMwd1bmkwNEQ1B3VuaTA0RDcHdW5pMDREOQd1bmkwNEREB3VuaTA0REYHdW5pMDRFMwd1bmkwNEU1B3VuaTA0RTcHdW5pMDRFOQd1bmkwNEVGB3VuaTA0RjEHdW5pMDRGMwd1bmkwNEY1B3VuaTA0RjcHdW5pMDRGOQd1bmkwNTFCB3VuaTA1MUQFQWxwaGEEQmV0YQVHYW1tYQd1bmkwMzk0B0Vwc2lsb24EWmV0YQNFdGEFVGhldGEESW90YQVLYXBwYQZMYW1iZGECTXUCTnUCWGkHT21pY3JvbgJQaQNSaG8FU2lnbWEDVGF1B1Vwc2lsb24DUGhpA0NoaQNQc2kHdW5pMDNBOQpBbHBoYXRvbm9zDEVwc2lsb250b25vcwhFdGF0b25vcwlJb3RhdG9ub3MMT21pY3JvbnRvbm9zDFVwc2lsb250b25vcwpPbWVnYXRvbm9zDElvdGFkaWVyZXNpcw9VcHNpbG9uZGllcmVzaXMHdW5pMDNDRgd1bmkxRjA4B3VuaTFGMDkHdW5pMUYwQQd1bmkxRjBCB3VuaTFGMEMHdW5pMUYwRAd1bmkxRjBFB3VuaTFGMEYHdW5pMUZCQQd1bmkxRkJCB3VuaTFGQjgHdW5pMUZCOQd1bmkxRkJDB3VuaTFGODgHdW5pMUY4OQd1bmkxRjhBB3VuaTFGOEIHdW5pMUY4Qwd1bmkxRjhEB3VuaTFGOEUHdW5pMUY4Rgd1bmkxRjE4B3VuaTFGMTkHdW5pMUYxQQd1bmkxRjFCB3VuaTFGMUMHdW5pMUYxRAd1bmkxRkM4B3VuaTFGQzkHdW5pMUYyOAd1bmkxRjI5B3VuaTFGMkEHdW5pMUYyQgd1bmkxRjJDB3VuaTFGMkQHdW5pMUYyRQd1bmkxRjJGB3VuaTFGQ0EHdW5pMUZDQgd1bmkxRkNDB3VuaTFGOTgHdW5pMUY5OQd1bmkxRjlBB3VuaTFGOUIHdW5pMUY5Qwd1bmkxRjlEB3VuaTFGOUUHdW5pMUY5Rgd1bmkxRjM4B3VuaTFGMzkHdW5pMUYzQQd1bmkxRjNCB3VuaTFGM0MHdW5pMUYzRAd1bmkxRjNFB3VuaTFGM0YHdW5pMUZEQQd1bmkxRkRCB3VuaTFGRDgHdW5pMUZEOQd1bmkxRjQ4B3VuaTFGNDkHdW5pMUY0QQd1bmkxRjRCB3VuaTFGNEMHdW5pMUY0RAd1bmkxRkY4B3VuaTFGRjkHdW5pMUZFQwd1bmkxRjU5B3VuaTFGNUIHdW5pMUY1RAd1bmkxRjVGB3VuaTFGRUEHdW5pMUZFQgd1bmkxRkU4B3VuaTFGRTkHdW5pMUY2OAd1bmkxRjY5B3VuaTFGNkEHdW5pMUY2Qgd1bmkxRjZDB3VuaTFGNkQHdW5pMUY2RQd1bmkxRjZGB3VuaTFGRkEHdW5pMUZGQgd1bmkxRkZDB3VuaTFGQTgHdW5pMUZBOQd1bmkxRkFBB3VuaTFGQUIHdW5pMUZBQwd1bmkxRkFEB3VuaTFGQUUHdW5pMUZBRgVhbHBoYQRiZXRhBWdhbW1hBWRlbHRhB2Vwc2lsb24EemV0YQNldGEFdGhldGEEaW90YQVrYXBwYQZsYW1iZGEHdW5pMDNCQwJudQJ4aQdvbWljcm9uA3Jobwd1bmkwM0MyBXNpZ21hA3RhdQd1cHNpbG9uA3BoaQNjaGkDcHNpBW9tZWdhCWlvdGF0b25vcwxpb3RhZGllcmVzaXMRaW90YWRpZXJlc2lzdG9ub3MMdXBzaWxvbnRvbm9zD3Vwc2lsb25kaWVyZXNpcxR1cHNpbG9uZGllcmVzaXN0b25vcwxvbWljcm9udG9ub3MKb21lZ2F0b25vcwphbHBoYXRvbm9zDGVwc2lsb250b25vcwhldGF0b25vcwd1bmkwM0Q3B3VuaTFGMDAHdW5pMUYwMQd1bmkxRjAyB3VuaTFGMDMHdW5pMUYwNAd1bmkxRjA1B3VuaTFGMDYHdW5pMUYwNwd1bmkxRjcwB3VuaTFGNzEHdW5pMUZCNgd1bmkxRkIwB3VuaTFGQjEHdW5pMUZCMwd1bmkxRkIyB3VuaTFGQjQHdW5pMUY4MAd1bmkxRjgxB3VuaTFGODIHdW5pMUY4Mwd1bmkxRjg0B3VuaTFGODUHdW5pMUY4Ngd1bmkxRjg3B3VuaTFGQjcHdW5pMUYxMAd1bmkxRjExB3VuaTFGMTIHdW5pMUYxMwd1bmkxRjE0B3VuaTFGMTUHdW5pMUY3Mgd1bmkxRjczB3VuaTFGMjAHdW5pMUYyMQd1bmkxRjIyB3VuaTFGMjMHdW5pMUYyNAd1bmkxRjI1B3VuaTFGMjYHdW5pMUYyNwd1bmkxRjc0B3VuaTFGNzUHdW5pMUZDNgd1bmkxRkMzB3VuaTFGQzIHdW5pMUZDNAd1bmkxRjkwB3VuaTFGOTEHdW5pMUY5Mgd1bmkxRjkzB3VuaTFGOTQHdW5pMUY5NQd1bmkxRjk2B3VuaTFGOTcHdW5pMUZDNwd1bmkxRjMwB3VuaTFGMzEHdW5pMUYzMgd1bmkxRjMzB3VuaTFGMzQHdW5pMUYzNQd1bmkxRjM2B3VuaTFGMzcHdW5pMUY3Ngd1bmkxRjc3B3VuaTFGRDYHdW5pMUZEMAd1bmkxRkQxB3VuaTFGRDIHdW5pMUZEMwd1bmkxRkQ3B3VuaTFGNDAHdW5pMUY0MQd1bmkxRjQyB3VuaTFGNDMHdW5pMUY0NAd1bmkxRjQ1B3VuaTFGNzgHdW5pMUY3OQd1bmkxRkU0B3VuaTFGRTUHdW5pMUY1MAd1bmkxRjUxB3VuaTFGNTIHdW5pMUY1Mwd1bmkxRjU0B3VuaTFGNTUHdW5pMUY1Ngd1bmkxRjU3B3VuaTFGN0EHdW5pMUY3Qgd1bmkxRkU2B3VuaTFGRTAHdW5pMUZFMQd1bmkxRkUyB3VuaTFGRTMHdW5pMUZFNwd1bmkxRjYwB3VuaTFGNjEHdW5pMUY2Mgd1bmkxRjYzB3VuaTFGNjQHdW5pMUY2NQd1bmkxRjY2B3VuaTFGNjcHdW5pMUY3Qwd1bmkxRjdEB3VuaTFGRjYHdW5pMUZGMwd1bmkxRkYyB3VuaTFGRjQHdW5pMUZBMAd1bmkxRkExB3VuaTFGQTIHdW5pMUZBMwd1bmkxRkE0B3VuaTFGQTUHdW5pMUZBNgd1bmkxRkE3B3VuaTFGRjcFcGhpLjEHdW5pMUZCRQd1bmkwMzdBB3VuaTFENTIHdW5pMURCRgd1bmkwMEI5B3VuaTAwQjIHdW5pMDBCMwd1bmkyMDc0B3VuaTIwMjMWcGVyaW9kY2VudGVyZWQubG9jbENBVAd1bmkwMEFECWFub3RlbGVpYQd1bmkwMzdFB3VuaTAwQTAHdW5pMjBCNQ1jb2xvbm1vbmV0YXJ5BGRvbmcERXVybwd1bmkyMEIyB3VuaTIwQjQHdW5pMjBBRARsaXJhB3VuaTIwQkEHdW5pMjBBNgZwZXNldGEHdW5pMjBCMQd1bmkyMEJEB3VuaTIwQjkHdW5pMjBCOAd1bmkyMEFFB3VuaTIwQTkKZG9sbGFyLm9zZgd1bmkyMjE5B3VuaTIyMTUHdW5pMDBCNQd1bmkyMTE2B3VuaTAzNzQHdW5pMDM3NQd1bmkwMzA4B3VuaTAzMDcJZ3JhdmVjb21iCWFjdXRlY29tYgd1bmkwMzBCDWNhcm9uY29tYi5hbHQHdW5pMDMwMgd1bmkwMzBDB3VuaTAzMDYHdW5pMDMwQQl0aWxkZWNvbWIHdW5pMDMwNAd1bmkwMzBEB3VuaTAzMEYHdW5pMDMxMQd1bmkwMzEyB3VuaTAzMTMHdW5pMDMxNAxkb3RiZWxvd2NvbWIHdW5pMDMyNAd1bmkwMzI1B3VuaTAzMjYHdW5pMDMyNwd1bmkwMzI4B3VuaTAzMjkHdW5pMDMyRAd1bmkwMzJFB3VuaTAzMzAHdW5pMDMzMQd1bmkwMzMyB3VuaTAzMzUHdW5pMDMzNgd1bmkwMzM3B3VuaTAzMzgHdW5pMDM1RQd1bmkwMzVGB3VuaTFEQzcHdW5pMURDNQd1bmkxREM0B3VuaTFEQzYMdW5pMDMwOC5jYXNlDHVuaTAzMDcuY2FzZQ5ncmF2ZWNvbWIuY2FzZQ5hY3V0ZWNvbWIuY2FzZQx1bmkwMzBCLmNhc2UMdW5pMDMwMi5jYXNlDHVuaTAzMEMuY2FzZQx1bmkwMzA2LmNhc2UMdW5pMDMwQS5jYXNlDnRpbGRlY29tYi5jYXNlDHVuaTAzMDQuY2FzZQx1bmkwMzBGLmNhc2UJdW5pMDMwOC5pC2dyYXZlY29tYi5pC2FjdXRlY29tYi5pCXVuaTAzMDIuaQl1bmkwMzBDLmkJdW5pMDMwNi5pC3RpbGRlY29tYi5pCXVuaTAzMDQuaQl1bmkwMzExLmkJdW5pMDMzMC5pB3VuaTAyQkMHdW5pMDJCQgd1bmkwMkM5B3VuaTAyQ0IHdW5pMDJDMAd1bmkwMkJGB3VuaTAyQkUHdW5pMDJDQQd1bmkwMzQyB3VuaTAzNDMHdW5pMDM0NAd1bmkwMzQ1DXVuaTAzMDYuZ3JlZWsSdW5pMDMwNi5ncmVlay5jYXNlBXRvbm9zCnRvbm9zLmNhc2UNZGllcmVzaXN0b25vcwd1bmkxRkJGDHVuaTFGQkYuY2FzZQd1bmkxRkJEDHVuaTFGQkQuY2FzZQd1bmkxRkZFDHVuaTFGRkUuY2FzZQd1bmkxRkNEB3VuaTFGREQMdW5pMUZERC5jYXNlB3VuaTFGQ0UMdW5pMUZDRS5jYXNlB3VuaTFGREUMdW5pMUZERS5jYXNlB3VuaTFGQ0YMdW5pMUZDRi5jYXNlB3VuaTFGREYMdW5pMUZERi5jYXNlB3VuaTFGRUQHdW5pMUZFRQd1bmkxRkMxB3VuaTFGRUYMdW5pMUZFRi5jYXNlB3VuaTFGRkQMdW5pMUZGRC5jYXNlB3VuaTFGQzAMdW5pMUZDMC5jYXNlDHVuaTFGQ0QuY2FzZQticmV2ZWNvbWJjeRBicmV2ZWNvbWJjeS5jYXNlIk9tZWdhZGFzaWFwZXJpc3BvbWVuaXlwb2dlZ3JhbW1lbmkiT21lZ2Fwc2lsaXBlcmlzcG9tZW5peXBvZ2VncmFtbWVuaRtPbWVnYWRhc2lhb3hpYXlwb2dlZ3JhbW1lbmkbT21lZ2Fwc2lsaW94aWF5cG9nZWdyYW1tZW5pHE9tZWdhZGFzaWF2YXJpYXlwb2dlZ3JhbW1lbmkcT21lZ2Fwc2lsaXZhcmlheXBvZ2VncmFtbWVuaRdPbWVnYWRhc2lheXBvZ2VncmFtbWVuaRdPbWVnYXBzaWxpeXBvZ2VncmFtbWVuaRJPbWVnYXlwb2dlZ3JhbW1lbmkgRXRhZGFzaWFwZXJpc3BvbWVuaXlwb2dlZ3JhbW1lbmkfRXRhcHNpbGlwZXJpc3BtZW5peXBvZ2VncmFtbWVuaRZFdGFkYXNpYW94aWFnZWdyYW1tZW5pGUV0YXBzaWxpb3hpYXlwb2dlZ3JhbW1lbmkaRXRhZGFzaWF2YXJpYXlwb2dlZ3JhbW1lbmkaRXRhcHNpbGl2YXJpYXlwb2dlZ3JhbW1lbmkSRXRhZGFzaWFnZWdyYW1tZW5pEkV0YXBzaWxpZ2VncmFtbWVuaRBFdGF5cG9nZWdyYW1tZW5pIkFscGhhZGFzaWFwZXJpc3BvbWVuaXlwb2dlZ3JhbW1lbmkiQWxwaGFwc2lsaXBlcmlzcG9tZW5peXBvZ2VncmFtbWVuaRtBbHBoYWRhc2lhb3hpYXlwb2dlZ3JhbW1lbmkbQWxwaGFwc2lsaW94aWF5cG9nZWdyYW1tZW5pHEFscGhhZGFzaWF2YXJpYXlwb2dlZ3JhbW1lbmkcQWxwaGFwc2lsaXZhcmlheXBvZ2VncmFtbWVuaRdBbHBoYWRhc2lheXBvZ2VncmFtbWVuaRdBbHBoYXBzaWxpeXBvZ2VncmFtbWVuaRJBbHBoYXlwb2dlZ3JhbW1lbmkPcXVvdGVyaWdodF95aWN5AAABAAH//wAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARABEAD4APgKqAAAB1AAA/zgEkf71Arn/8AHk//D/OASR/vUARABEAD4APgKqAAAC1gHU//H/OASR/vUCuf/wAucB5P/x/y4Ekf71AEQARAA+AD4CqgAAArgB1AAA/zgEkf71Arn/8AK4AeT/8P84BJH+9QA5ADkAMwAzAqoBAgLBAdQAAP84BJH+9QK5//ACwQHk//D/OASR/vUAALAALCCwAFVYRVkgIEu4AA5RS7AGU1pYsDQbsChZYGYgilVYsAIlYbkIAAgAY2MjYhshIbAAWbAAQyNEsgABAENgQi2wASywIGBmLbACLCBkILDAULAEJlqyKAEKQ0VjRbAGRVghsAMlWVJbWCEjIRuKWCCwUFBYIbBAWRsgsDhQWCGwOFlZILEBCkNF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'); +})(jsPDF.API); \ No newline at end of file diff --git a/src/assets/js/jspdf.customfonts.min.js b/src/assets/js/jspdf.customfonts.min.js new file mode 100644 index 0000000..c949dda --- /dev/null +++ b/src/assets/js/jspdf.customfonts.min.js @@ -0,0 +1,9 @@ +/*! + * jsPDF customfonts plugin v0.0.4-rc.4 + * Copyright (c) 2018 GH Lee, https://github.com/sphilee/jsPDF-CustomFonts-support + * + * Licensed under the MIT License. + * https://opensource.org/licenses/mit-license + * + */ +!function(t,e){if("object"==typeof exports&&"object"==typeof module)module.exports=e(require("jspdf"));else if("function"==typeof define&&define.amd)define(["jspdf"],e);else{var r="object"==typeof exports?e(require("jspdf")):e(t.jsPDF);for(var n in r)("object"==typeof exports?exports:t)[n]=r[n]}}(window,function(t){return function(t){var e={};function r(n){if(e[n])return e[n].exports;var i=e[n]={i:n,l:!1,exports:{}};return t[n].call(i.exports,i,i.exports,r),i.l=!0,i.exports}return r.m=t,r.c=e,r.d=function(t,e,n){r.o(t,e)||Object.defineProperty(t,e,{configurable:!1,enumerable:!0,get:n})},r.r=function(t){Object.defineProperty(t,"__esModule",{value:!0})},r.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return r.d(e,"a",e),e},r.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},r.p="",r(r.s=3)}([function(t,e,r){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.postProcessText=e.putFont=void 0;e.putFont=function(t){var e=t.font,r=t.newObject,n=t.out;if(!(e.id.slice(1)<15)){var i=e.metadata.embedTTF(e.encoding,r,n);i&&(e.objectNumber=i,e.isAlreadyPutted=!0)}};e.postProcessText=function(t){var e=t.text,r=t.mutex,n=r.activeFontKey,i=r.fonts,s=n.slice(1)>=15,o=i[n].metadata,a=o.encode,h=o.subset;t.text=s?e.map(function(t){return Array.isArray(t)?[a(h,t[0]),t[1],t[2]]:a(h,t)}):e,t.mutex.isHex=s}},function(t,e,r){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0;var n=function(){function t(t,e){var r;if(this.rawData=t,r=this.contents=new i(t),this.contents.pos=4,"ttcf"===r.readString(4)){if(!e)throw new Error("Must specify a font name for TTC files.");throw[],new Error("Font "+e+" not found in TTC file.")}r.pos=0,this.parse(),this.subset=new U(this),this.registerTTF()}t.open=function(r,n,i){return new t(e(i),n)},t.prototype.parse=function(){return this.directory=new s(this.contents),this.head=new c(this),this.name=new y(this),this.cmap=new u(this),this.hhea=new f(this),this.maxp=new w(this),this.hmtx=new I(this),this.post=new l(this),this.os2=new d(this),this.loca=new b(this),this.glyf=new S(this),this.ascender=this.os2.exists&&this.os2.ascender||this.hhea.ascender,this.decender=this.os2.exists&&this.os2.decender||this.hhea.decender,this.lineGap=this.os2.exists&&this.os2.lineGap||this.hhea.lineGap,this.bbox=[this.head.xMin,this.head.yMin,this.head.xMax,this.head.yMax],this},t.prototype.registerTTF=function(){var t,e,r,n,i;if(this.scaleFactor=1e3/this.head.unitsPerEm,this.bbox=function(){var e,r,n,i;for(i=[],e=0,r=(n=this.bbox).length;e>16)&&(e=-(1+(65535^e))),this.italicAngle=+(e+"."+r)):this.italicAngle=0,this.ascender=Math.round(this.ascender*this.scaleFactor),this.decender=Math.round(this.decender*this.scaleFactor),this.lineGap=Math.round(this.lineGap*this.scaleFactor),this.capHeight=this.os2.exists&&this.os2.capHeight||this.ascender,this.xHeight=this.os2.exists&&this.os2.xHeight||0,this.familyClass=(this.os2.exists&&this.os2.familyClass||0)>>8,this.isSerif=1===(i=this.familyClass)||2===i||3===i||4===i||5===i||7===i,this.isScript=10===this.familyClass,this.flags=0,this.post.isFixedPitch&&(this.flags|=1),this.isSerif&&(this.flags|=2),this.isScript&&(this.flags|=8),0!==this.italicAngle&&(this.flags|=64),this.flags|=32,!this.cmap.unicode)throw new Error("No unicode cmap for font")},t.prototype.characterToGlyph=function(t){var e;return(void 0!==(e=this.cmap.unicode)?e.codeMap[t]:void 0)||0},t.prototype.widthOfGlyph=function(t){var e;return e=1e3/this.head.unitsPerEm,this.hmtx.forGlyph(t).advance*e},t.prototype.widthOfString=function(t,e,r){var n,i,s,o,a;for(s=0,i=o=0,a=(t=""+t).length;0<=a?oa;i=0<=a?++o:--o)n=t.charCodeAt(i),s+=this.widthOfGlyph(this.characterToGlyph(n))+r*(1e3/e)||0;return s*(e/1e3)},t.prototype.lineHeight=function(t,e){var r;return void 0===e&&(e=!1),r=e?this.lineGap:0,(this.ascender+r-this.decender)/1e3*t},t.prototype.encode=function(t,e,r){return t.use(e),e=r?h(t.encodeText(e)):t.encodeText(e),e=function(){for(var t=[],r=0,n=e.length;0<=n?rn;0<=n?r++:r--)t.push(e.charCodeAt(r).toString(16));return t}().join("")},t.prototype.embedTTF=function(t,e,r){var i,s,h,c,u,p,f,d,l="MacRomanEncoding"===t;return c=this.subset.encode(),{},f=l?c:this.rawData,u={Type:"FontDescriptor",FontName:this.subset.postscriptName,FontFile2:f,FontBBox:this.bbox,Flags:this.flags,StemV:this.stemV,ItalicAngle:this.italicAngle,Ascent:this.ascender,Descent:this.decender,CapHeight:this.capHeight,XHeight:this.xHeight},(33==(p=+Object.keys(this.subset.cmap)[0])||!l)&&(i=function(){var t,e;for(h in e=[],t=this.subset.cmap)Object.prototype.hasOwnProperty.call(t,h)&&(d=t[h],e.push(Math.round(this.widthOfGlyph(d))));return e}.call(this),s=a(this.subset.subset),function t(i){var s;return"Font"===i.Type?(l&&(i.ToUnicode=t(i.ToUnicode)+" 0 R"),i.FontDescriptor=t(i.FontDescriptor)+" 0 R",s=e(),r(M.convert(i))):"FontDescriptor"===i.Type?(i.FontFile2=t(i.FontFile2)+" 0 R",s=e(),r(M.convert(i))):(s=e(),r("<>"),r("stream"),Array.isArray(i)||i.constructor===Uint8Array?r(n(i)):r(i),r("endstream")),r("endobj"),s}(l?{Type:"Font",BaseFont:this.subset.postscriptName,Subtype:"TrueType",FontDescriptor:u,FirstChar:p,LastChar:p+i.length-1,Widths:i,Encoding:t,ToUnicode:s}:{Type:"Font",BaseFont:this.subset.postscriptName,Subtype:"TrueType",FontDescriptor:u,FirstChar:0,LastChar:255,Widths:o(this),Encoding:t}))};var e=function(t){var e,n,i,s,o,a;if(t.length%4>0)throw new Error("Invalid string. Length must be a multiple of 4");var h=t.length;o="="===t.charAt(h-2)?2:"="===t.charAt(h-1)?1:0,a=new Uint8Array(3*t.length/4-o),i=o>0?t.length-4:t.length;var c=0;function u(t){a[c++]=t}for(e=0,n=0;e>16),u((65280&s)>>8),u(255&s);return 2===o?u(255&(s=r(t.charAt(e))<<2|r(t.charAt(e+1))>>4)):1===o&&(u((s=r(t.charAt(e))<<10|r(t.charAt(e+1))<<4|r(t.charAt(e+2))>>2)>>8&255),u(255&s)),a},r=function(t){var e="+".charCodeAt(0),r="/".charCodeAt(0),n="0".charCodeAt(0),i="a".charCodeAt(0),s="A".charCodeAt(0),o="-".charCodeAt(0),a="_".charCodeAt(0),h=t.charCodeAt(0);return h===e||h===o?62:h===r||h===a?63:h=100&&(s+="\n"+n.length+" beginbfchar\n"+n.join("\n")+"\nendbfchar",n=[]),i=("0000"+t[e].toString(16)).slice(-4),e=(+e).toString(16),n.push("<"+e+"><"+i+">");return n.length&&(s+="\n"+n.length+" beginbfchar\n"+n.join("\n")+"\nendbfchar\n"),s+="endcmap\nCMapName currentdict /CMap defineresource pop\nend\nend"},h=function(t){return t.split("").reverse().join("")};return t}();e.default=n;var i=function(){function t(t){this.data=void 0!==t?t:[],this.pos=0,this.length=this.data.length}return t.prototype.readByte=function(){return this.data[this.pos++]},t.prototype.writeByte=function(t){return this.data[this.pos++]=t,this},t.prototype.readUInt32=function(){return 16777216*this.readByte()+(this.readByte()<<16)+(this.readByte()<<8)+this.readByte()},t.prototype.writeUInt32=function(t){return this.writeByte(t>>>24&255),this.writeByte(t>>16&255),this.writeByte(t>>8&255),this.writeByte(255&t)},t.prototype.readInt32=function(){var t;return(t=this.readUInt32())>=2147483648?t-4294967296:t},t.prototype.writeInt32=function(t){return t<0&&(t+=4294967296),this.writeUInt32(t)},t.prototype.readUInt16=function(){return this.readByte()<<8|this.readByte()},t.prototype.writeUInt16=function(t){return this.writeByte(t>>8&255),this.writeByte(255&t)},t.prototype.readInt16=function(){var t;return(t=this.readUInt16())>=32768?t-65536:t},t.prototype.writeInt16=function(t){return t<0&&(t+=65536),this.writeUInt16(t)},t.prototype.readString=function(t){var e,r,n;for(r=[],e=n=0;0<=t?nt;e=0<=t?++n:--n)r[e]=String.fromCharCode(this.readByte());return r.join("")},t.prototype.writeString=function(t){var e,r,n,i;for(i=[],e=r=0,n=t.length;0<=n?rn;e=0<=n?++r:--r)i.push(this.writeByte(t.charCodeAt(e)));return i},t.prototype.stringAt=function(t,e){return this.pos=t,this.readString(e)},t.prototype.readShort=function(){return this.readInt16()},t.prototype.writeShort=function(t){return this.writeInt16(t)},t.prototype.readLongLong=function(){var t,e,r,n,i,s,o,a;return t=this.readByte(),e=this.readByte(),r=this.readByte(),n=this.readByte(),i=this.readByte(),s=this.readByte(),o=this.readByte(),a=this.readByte(),128&t?-1*(72057594037927940*(255^t)+281474976710656*(255^e)+1099511627776*(255^r)+4294967296*(255^n)+16777216*(255^i)+65536*(255^s)+256*(255^o)+(255^a)+1):72057594037927940*t+281474976710656*e+1099511627776*r+4294967296*n+16777216*i+65536*s+256*o+a},t.prototype.writeLongLong=function(t){var e,r;return e=Math.floor(t/4294967296),r=4294967295&t,this.writeByte(e>>24&255),this.writeByte(e>>16&255),this.writeByte(e>>8&255),this.writeByte(255&e),this.writeByte(r>>24&255),this.writeByte(r>>16&255),this.writeByte(r>>8&255),this.writeByte(255&r)},t.prototype.readInt=function(){return this.readInt32()},t.prototype.writeInt=function(t){return this.writeInt32(t)},t.prototype.slice=function(t,e){return this.data.slice(t,e)},t.prototype.read=function(t){var e,r;for(e=[],r=0;0<=t?rt;0<=t?++r:--r)e.push(this.readByte());return e},t.prototype.write=function(t){var e,r,n,i;for(i=[],r=0,n=t.length;rn;0<=n?++r:--r)e={tag:t.readString(4),checksum:t.readInt(),offset:t.readInt(),length:t.readInt()},this.tables[e.tag]=e}return e.prototype.encode=function(e){var r,n,s,o,a,h,c,u,p,f,d,l,g;for(g in d=Object.keys(e).length,h=Math.log(2),p=16*Math.floor(Math.log(d)/h),o=Math.floor(p/h),u=16*d-p,(n=new i).writeInt(this.scalarType),n.writeShort(d),n.writeShort(p),n.writeShort(o),n.writeShort(u),s=16*d,c=n.pos+s,a=null,l=[],e)if(Object.prototype.hasOwnProperty.call(e,g))for(f=e[g],n.writeString(g),n.writeInt(t(f)),n.writeInt(c),n.writeInt(f.length),l=l.concat(f),"head"===g&&(a=c),c+=f.length;c%4;)l.push(0),c++;return n.write(l),r=2981146554-t(n.data),n.pos=a+8,n.writeUInt32(r),n.data},t=function(t){var e,r,n,s;for(t=o.call(t);t.length%4;)t.push(0);for(r=new i(t),e=0,n=0,s=t.length;nr;0<=r?++n:--n)e=new p(t,this.offset),this.tables.push(e),e.isUnicode&&void 0===this.unicode&&(this.unicode=e);return!0},e.encode=function(t,e){var r,n;return void 0===e&&(e="macroman"),r=p.encode(t,e),(n=new i).writeUInt16(0),n.writeUInt16(1),r.table=n.data.concat(r.subtable),r},e}(),p=function(){function t(t,e){var r,n,i,s,o,a,h,c,u,p,f,d,l,g,y,m,w,I;switch(this.platformID=t.readUInt16(),this.encodingID=t.readShort(),this.offset=e+t.readInt(),u=t.pos,t.pos=this.offset,this.format=t.readUInt16(),this.length=t.readUInt16(),this.language=t.readUInt16(),this.isUnicode=3===this.platformID&&1===this.encodingID&&4===this.format||0===this.platformID&&4===this.format,this.codeMap={},this.format){case 0:for(a=y=0;y<256;a=++y)this.codeMap[a]=t.readByte();break;case 4:for(f=t.readUInt16(),p=f/2,t.pos+=6,i=function(){var e,r;for(r=[],a=e=0;0<=p?ep;a=0<=p?++e:--e)r.push(t.readUInt16());return r}(),t.pos+=2,l=function(){var e,r;for(r=[],a=e=0;0<=p?ep;a=0<=p?++e:--e)r.push(t.readUInt16());return r}(),h=function(){var e,r;for(r=[],a=e=0;0<=p?ep;a=0<=p?++e:--e)r.push(t.readUInt16());return r}(),c=function(){var e,r;for(r=[],a=e=0;0<=p?ep;a=0<=p?++e:--e)r.push(t.readUInt16());return r}(),n=(this.length-t.pos+this.offset)/2,o=function(){var e,r;for(r=[],a=e=0;0<=n?en;a=0<=n?++e:--e)r.push(t.readUInt16());return r}(),a=m=0,I=i.length;m=g;r=d<=g?++w:--w)0===c[a]?s=r+h[a]:0!==(s=o[c[a]/2+(r-d)-(p-a)]||0)&&(s+=h[a]),this.codeMap[r]=65535&s}t.pos=u}return t.encode=function(t,e){var r,n,s,o,a,h,c,u,p,f,d,l,g,y,m,w,I,S,v,x,b,U,O,C,M,A,F,D,P,T,_,j,B,k,E,N,G,L,q,R,H,z,Y,V,W,X;switch(D=new i,o=Object.keys(t).sort(function(t,e){return t-e}),e){case"macroman":for(g=0,y=function(){var t,e;for(e=[],l=t=0;t<256;l=++t)e.push(0);return e}(),w={0:0},s={},P=0,B=o.length;P=32768)for(h.push(0),x.push(2*(d.length+O-l)),n=j=M;M<=u?j<=u:j>=u;n=M<=u?++j:--j)d.push(r[n].new);else h.push(F-M),x.push(0)}for(D.writeUInt16(3),D.writeUInt16(1),D.writeUInt32(12),D.writeUInt16(4),D.writeUInt16(16+8*O+2*d.length),D.writeUInt16(0),D.writeUInt16(C),D.writeUInt16(U),D.writeUInt16(f),D.writeUInt16(b),H=0,N=p.length;H0&&(this.ascent=t.readShort(),this.descent=t.readShort(),this.lineGap=t.readShort(),this.winAscent=t.readShort(),this.winDescent=t.readShort(),this.codePageRange=function(){var e,r;for(r=[],e=0;e<2;++e)r.push(t.readInt());return r}(),this.version>1))return this.xHeight=t.readShort(),this.capHeight=t.readShort(),this.defaultChar=t.readShort(),this.breakChar=t.readShort(),this.maxContext=t.readShort(),this},e.prototype.encode=function(){return this.raw()},e}(),l=function(t){var e;function r(){return r.__super__.constructor.apply(this,arguments)}return a(r,h),r.prototype.tag="post",r.prototype.parse=function(t){var e,r,n,i;switch(t.pos=this.offset,this.format=t.readInt(),this.italicAngle=t.readInt(),this.underlinePosition=t.readShort(),this.underlineThickness=t.readShort(),this.isFixedPitch=t.readInt(),this.minMemType42=t.readInt(),this.maxMemType42=t.readInt(),this.minMemType1=t.readInt(),this.maxMemType1=t.readInt(),this.format){case 65536:break;case 131072:for(r=t.readUInt16(),this.glyphNameIndex=[],n=0;0<=r?nr;0<=r?++n:--n)this.glyphNameIndex.push(t.readUInt16());for(this.names=[],i=[];t.posr;0<=r?++e:--e)n.push(t.readUInt32());return n}.call(this),this.map}},r.prototype.glyphFor=function(t){var r;switch(this.format){case 65536:return e[t]||".notdef";case 131072:return(r=this.glyphNameIndex[t])<=257?e[r]:this.names[r-258]||".notdef";case 151552:return e[t+this.offsets[t]]||".notdef";case 196608:return".notdef";case 262144:return this.map[t]||65535}},r.prototype.encode=function(t){var r,n,s,o,a,h,c,u,p,f,d,l,g,y,m;if(!this.exists)return null;if(h=this.raw(),196608===this.format)return h;for((p=new i(h.slice(0,32))).writeUInt32(131072),p.pos=32,s=[],u=[],f=0,g=t.length;fe;i=0<=e?++c:--c)r.push({platformID:t.readShort(),encodingID:t.readShort(),languageID:t.readShort(),nameID:t.readShort(),length:t.readShort(),offset:this.offset+o+t.readShort()});for(a={},i=u=0,p=r.length;u=0;){if(o=t.charAt(--n),isNaN(o)){if(-1===(i=e.indexOf(o.toLowerCase())))h=o,r=!0;else if(h=e.charAt((i+1)%a),(s=o===o.toUpperCase())&&(h=h.toUpperCase()),(r=i+1>=a)&&0===n){c=(s?"A":"a")+h+c.slice(1);break}}else if((r=(h=+o+1)>9)&&(h=0),r&&0===n){c="1"+h+c.slice(1);break}if(c=c.slice(0,n)+h+c.slice(n+1),!r)break}return c},w=function(t){function e(){return e.__super__.constructor.apply(this,arguments)}return a(e,h),e.prototype.tag="maxp",e.prototype.parse=function(t){return t.pos=this.offset,this.version=t.readInt(),this.numGlyphs=t.readUInt16(),this.maxPoints=t.readUInt16(),this.maxContours=t.readUInt16(),this.maxCompositePoints=t.readUInt16(),this.maxComponentContours=t.readUInt16(),this.maxZones=t.readUInt16(),this.maxTwilightPoints=t.readUInt16(),this.maxStorage=t.readUInt16(),this.maxFunctionDefs=t.readUInt16(),this.maxInstructionDefs=t.readUInt16(),this.maxStackElements=t.readUInt16(),this.maxSizeOfInstructions=t.readUInt16(),this.maxComponentElements=t.readUInt16(),this.maxComponentDepth=t.readUInt16(),this},e.prototype.encode=function(t){var e;return(e=new i).writeInt(this.version),e.writeUInt16(t.length),e.writeUInt16(this.maxPoints),e.writeUInt16(this.maxContours),e.writeUInt16(this.maxCompositePoints),e.writeUInt16(this.maxComponentContours),e.writeUInt16(this.maxZones),e.writeUInt16(this.maxTwilightPoints),e.writeUInt16(this.maxStorage),e.writeUInt16(this.maxFunctionDefs),e.writeUInt16(this.maxInstructionDefs),e.writeUInt16(this.maxStackElements),e.writeUInt16(this.maxSizeOfInstructions),e.writeUInt16(this.maxComponentElements),e.writeUInt16(this.maxComponentDepth),e.data},e}(),I=function(t){function e(){return e.__super__.constructor.apply(this,arguments)}return a(e,h),e.prototype.tag="hmtx",e.prototype.parse=function(t){var e,r,n,i,s,o,a;for(t.pos=this.offset,this.metrics=[],i=0,o=this.file.hhea.numberOfMetrics;0<=o?io;0<=o?++i:--i)this.metrics.push({advance:t.readUInt16(),lsb:t.readInt16()});for(r=this.file.maxp.numGlyphs-this.file.hhea.numberOfMetrics,this.leftSideBearings=function(){var e,n;for(n=[],e=0;0<=r?er;0<=r?++e:--e)n.push(t.readInt16());return n}(),this.widths=function(){var t,e,r,i;for(i=[],t=0,e=(r=this.metrics).length;tr;0<=r?++s:--s)a.push(this.widths.push(e));return a},e.prototype.forGlyph=function(t){return t in this.metrics?this.metrics[t]:{advance:this.metrics[this.metrics.length-1].advance,lsb:this.leftSideBearings[t-this.metrics.length]}},e.prototype.encode=function(t){var e,r,n,s,o;for(n=new i,s=0,o=t.length;s65535){for(s=0,h=(u=this.offsets).length;sn;e=0<=n?++r:--r)this.use(t.charCodeAt(e))},t.prototype.encodeText=function(t){var e,r,n,i,s;for(n="",r=i=0,s=t.length;0<=s?is;r=0<=s?++i:--i)e=this.unicodes[t.charCodeAt(r)],n+=String.fromCharCode(e);return n},t.prototype.generateCmap=function(){var t,e,r,n,i;for(e in n=this.font.cmap.tables[0].codeMap,t={},i=this.subset)Object.prototype.hasOwnProperty.call(i,e)&&(r=i[e],t[e]=n[r]);return t},t.prototype.glyphIDs=function(){var t,e,r,n,i;for(e in r=this.font.cmap.tables[0].codeMap,t=[0],i=this.subset)Object.prototype.hasOwnProperty.call(i,e)&&null!==(n=r[i[e]])&&O.call(t,n)<0&&t.push(n);return t.sort()},t.prototype.glyphsFor=function(t){var e,r,n,i,s,o,a;for(n={},s=0,o=t.length;s0)for(i in a=this.glyphsFor(e))Object.prototype.hasOwnProperty.call(a,i)&&(r=a[i],n[i]=r);return n},t.prototype.encode=function(){var t,e,r,n,i,s,o,a,h,c,p,f,d,l,g,y,m;for(e in t=u.encode(this.generateCmap(),"unicode"),n=this.glyphsFor(this.glyphIDs()),f={0:0},y=t.charMap)Object.prototype.hasOwnProperty.call(y,e)&&(f[(s=y[e]).old]=s.new);for(d in p=t.maxGlyphID,n)d in f||(f[d]=p++);for(e in h=C(f),c=Object.keys(h).sort(function(t,e){return t-e}),l=function(){var t,e,r;for(r=[],t=0,e=c.length;t>"),s.join("\n")}return""+r},e}()},function(e,r){e.exports=t},function(t,e,r){"use strict";var n,i=function(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var r in t)if(Object.prototype.hasOwnProperty.call(t,r)){var n=Object.defineProperty&&Object.getOwnPropertyDescriptor?Object.getOwnPropertyDescriptor(t,r):{};n.get||n.set?Object.defineProperty(e,r,n):e[r]=t[r]}return e.default=t,e}(r(2)),s=(n=r(1))&&n.__esModule?n:{default:n},o=r(0);i.API.TTFFont=s.default,i.API.events.splice(-4),i.API.events.push(["addFont",function(t){var e=t.id,r=t.fontName,n=t.postScriptName;if(i.API.existsFileInVFS(n)){t.metadata=i.API.TTFFont.open(n,r,i.API.getFileFromVFS(n));var s=t.metadata,o=s.hmtx.widths,a=s.capHeight;t.encoding=o.length<500&&a<800?"WinAnsiEncoding":"MacRomanEncoding"}else e.slice(1)>=15&&console.error("Font does not exist in FileInVFS, import fonts or remove declaration doc.addFont('".concat(n,"')."))}]),i.API.events.push(["putFont",o.putFont]),i.API.events.push(["postProcessText",o.postProcessText])}])}); \ No newline at end of file diff --git a/src/assets/js/jspdf.min.js b/src/assets/js/jspdf.min.js new file mode 100644 index 0000000..917d7df --- /dev/null +++ b/src/assets/js/jspdf.min.js @@ -0,0 +1,302 @@ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):t.jsPDF=e()}(this,function(){"use strict";var t,y,e,I,i,o,a,h,C,T,d,p,F,n,r,s,c,P,E,q,g,m,w,l,v,b,x,S,u,k,_,f,A,O,B,R,j,D,M,U,N,z,L,H,W,G,V,Y,X,J,K,Q,Z,vt="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},$=function(pt){var gt="1.3",mt={a0:[2383.94,3370.39],a1:[1683.78,2383.94],a2:[1190.55,1683.78],a3:[841.89,1190.55],a4:[595.28,841.89],a5:[419.53,595.28],a6:[297.64,419.53],a7:[209.76,297.64],a8:[147.4,209.76],a9:[104.88,147.4],a10:[73.7,104.88],b0:[2834.65,4008.19],b1:[2004.09,2834.65],b2:[1417.32,2004.09],b3:[1000.63,1417.32],b4:[708.66,1000.63],b5:[498.9,708.66],b6:[354.33,498.9],b7:[249.45,354.33],b8:[175.75,249.45],b9:[124.72,175.75],b10:[87.87,124.72],c0:[2599.37,3676.54],c1:[1836.85,2599.37],c2:[1298.27,1836.85],c3:[918.43,1298.27],c4:[649.13,918.43],c5:[459.21,649.13],c6:[323.15,459.21],c7:[229.61,323.15],c8:[161.57,229.61],c9:[113.39,161.57],c10:[79.37,113.39],dl:[311.81,623.62],letter:[612,792],"government-letter":[576,756],legal:[612,1008],"junior-legal":[576,360],ledger:[1224,792],tabloid:[792,1224],"credit-card":[153,243]};function wt(o){var a={};this.subscribe=function(t,e,n){if("function"!=typeof e)return!1;a.hasOwnProperty(t)||(a[t]={});var r=Math.random().toString(35);return a[t][r]=[e,!!n],r},this.unsubscribe=function(t){for(var e in a)if(a[e][t])return delete a[e][t],!0;return!1},this.publish=function(t){if(a.hasOwnProperty(t)){var e=Array.prototype.slice.call(arguments,1),n=[];for(var r in a[t]){var i=a[t][r];try{i[0].apply(o,e)}catch(t){pt.console&&console.error("jsPDF PubSub Error",t.message,t)}i[1]&&n.push(r)}n.length&&n.forEach(this.unsubscribe)}}}function yt(t,e,n,r){var i={};"object"===(void 0===t?"undefined":vt(t))&&(t=(i=t).orientation,e=i.unit||e,n=i.format||n,r=i.compress||i.compressPdf||r),e=e||"mm",n=n||"a4",t=(""+(t||"P")).toLowerCase();(""+n).toLowerCase();var K,w,y,o,u,v,a,s,h,c,l,f=!!r&&"function"==typeof Uint8Array,Q=i.textColor||"0 g",d=i.drawColor||"0 G",Z=i.fontSize||16,$=i.charSpace||0,tt=i.R2L||!1,et=i.lineHeight||1.15,p=i.lineWidth||.200025,g="00000000000000000000000000000000",m=2,b=!1,x=[],nt={},S={},k=0,_=[],A=[],I=[],C=[],T=[],F=0,P=0,E=0,q={title:"",subject:"",author:"",keywords:"",creator:""},O={},rt=new wt(O),B=i.hotfixes||[],R=function(t){var e,n=t.ch1,r=t.ch2,i=t.ch3,o=t.ch4,a=(t.precision,"draw"===t.pdfColorType?["G","RG","K"]:["g","rg","k"]);if("string"==typeof n&&"#"!==n.charAt(0)){var s=new RGBColor(n);s.ok&&(n=s.toHex())}if("string"==typeof n&&/^#[0-9A-Fa-f]{3}$/.test(n)&&(n="#"+n[1]+n[1]+n[2]+n[2]+n[3]+n[3]),"string"==typeof n&&/^#[0-9A-Fa-f]{6}$/.test(n)){var h=parseInt(n.substr(1),16);n=h>>16&255,r=h>>8&255,i=255&h}if(void 0===r||void 0===o&&n===r&&r===i)if("string"==typeof n)e=n+" "+a[0];else switch(t.precision){case 2:e=N(n/255)+" "+a[0];break;case 3:default:e=z(n/255)+" "+a[0]}else if(void 0===o||"object"===(void 0===o?"undefined":vt(o))){if("string"==typeof n)e=[n,r,i,a[1]].join(" ");else switch(t.precision){case 2:e=[N(n/255),N(r/255),N(i/255),a[1]].join(" ");break;default:case 3:e=[z(n/255),z(r/255),z(i/255),a[1]].join(" ")}o&&0===o.a&&(e=["255","255","255",a[1]].join(" "))}else if("string"==typeof n)e=[n,r,i,o,a[2]].join(" ");else switch(t.precision){case 2:e=[N(n),N(r),N(i),N(o),a[2]].join(" ");break;case 3:default:e=[z(n),z(r),z(i),z(o),a[2]].join(" ")}return e},j=function(t){var e=function(t){return("0"+parseInt(t)).slice(-2)},n=t.getTimezoneOffset(),r=n<0?"+":"-",i=Math.floor(Math.abs(n/60)),o=Math.abs(n%60),a=[r,e(i),"'",e(o),"'"].join("");return["D:",t.getFullYear(),e(t.getMonth()+1),e(t.getDate()),e(t.getHours()),e(t.getMinutes()),e(t.getSeconds()),a].join("")},D=function(t){var e;return void 0===(void 0===t?"undefined":vt(t))&&(t=new Date),e="object"===(void 0===t?"undefined":vt(t))&&"[object Date]"===Object.prototype.toString.call(t)?j(t):/^D:(20[0-2][0-9]|203[0-7]|19[7-9][0-9])(0[0-9]|1[0-2])([0-2][0-9]|3[0-1])(0[0-9]|1[0-9]|2[0-3])(0[0-9]|[1-5][0-9])(0[0-9]|[1-5][0-9])(\+0[0-9]|\+1[0-4]|\-0[0-9]|\-1[0-1])\'(0[0-9]|[1-5][0-9])\'?$/.test(t)?t:j(new Date),c=e},M=function(t){var e=c;return"jsDate"===t&&(e=function(t){var e=parseInt(t.substr(2,4),10),n=parseInt(t.substr(6,2),10)-1,r=parseInt(t.substr(8,2),10),i=parseInt(t.substr(10,2),10),o=parseInt(t.substr(12,2),10),a=parseInt(t.substr(14,2),10);parseInt(t.substr(16,2),10),parseInt(t.substr(20,2),10);return new Date(e,n,r,i,o,a,0)}(c)),e},U=function(t){return t=t||"12345678901234567890123456789012".split("").map(function(){return"ABCDEF0123456789".charAt(Math.floor(16*Math.random()))}).join(""),g=t},N=function(t){return t.toFixed(2)},z=function(t){return t.toFixed(3)},it=function(t){t="string"==typeof t?t:t.toString(),b?_[o].push(t):(E+=t.length+1,C.push(t))},L=function(){return x[++m]=E,it(m+" 0 obj"),m},H=function(t){it("stream"),it(t),it("endstream")},W=function(){for(var t in it("/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]"),it("/Font <<"),nt)nt.hasOwnProperty(t)&&it("/"+t+" "+nt[t].objectNumber+" 0 R");it(">>"),it("/XObject <<"),rt.publish("putXobjectDict"),it(">>")},G=function(){!function(){for(var t in nt)nt.hasOwnProperty(t)&&(e=nt[t],rt.publish("putFont",{font:e,out:it,newObject:L}),!0!==e.isAlreadyPutted&&(e.objectNumber=L(),it("<<"),it("/Type /Font"),it("/BaseFont /"+e.postScriptName),it("/Subtype /Type1"),"string"==typeof e.encoding&&it("/Encoding /"+e.encoding),it("/FirstChar 32"),it("/LastChar 255"),it(">>"),it("endobj")));var e}(),rt.publish("putResources"),x[2]=E,it("2 0 obj"),it("<<"),W(),it(">>"),it("endobj"),rt.publish("postPutResources")},V=function(t,e,n){S.hasOwnProperty(e)||(S[e]={}),S[e][n]=t},Y=function(t,e,n,r){var i="F"+(Object.keys(nt).length+1).toString(10),o=nt[i]={id:i,postScriptName:t,fontName:e,fontStyle:n,encoding:r,metadata:{}};return V(i,e,n),rt.publish("addFont",o),i},ot=function(t,e){return function(t,e){var n,r,i,o,a,s,h,c,l;if(i=(e=e||{}).sourceEncoding||"Unicode",a=e.outputEncoding,(e.autoencode||a)&&nt[K].metadata&&nt[K].metadata[i]&&nt[K].metadata[i].encoding&&(o=nt[K].metadata[i].encoding,!a&&nt[K].encoding&&(a=nt[K].encoding),!a&&o.codePages&&(a=o.codePages[0]),"string"==typeof a&&(a=o[a]),a)){for(h=!1,s=[],n=0,r=t.length;n>8&&(h=!0);t=s.join("")}for(n=t.length;void 0===h&&0!==n;)t.charCodeAt(n-1)>>8&&(h=!0),n--;if(!h)return t;for(s=e.noBOM?[]:[254,255],n=0,r=t.length;n>8)>>8)throw new Error("Character at position "+n+" of string '"+t+"' exceeds 16bits. Cannot be encoded into UCS-2 BE");s.push(l),s.push(c-(l<<8))}return String.fromCharCode.apply(void 0,s)}(t,e).replace(/\\/g,"\\\\").replace(/\(/g,"\\(").replace(/\)/g,"\\)")},X=function(){(function(t,e){var n="string"==typeof e&&e.toLowerCase();if("string"==typeof t){var r=t.toLowerCase();mt.hasOwnProperty(r)&&(t=mt[r][0]/w,e=mt[r][1]/w)}if(Array.isArray(t)&&(e=t[1],t=t[0]),n){switch(n.substr(0,1)){case"l":t>"),it("endobj"),e=_[t].join("\n"),L(),f){for(n=[],r=e.length;r--;)n[r]=e.charCodeAt(r);o=a.from(e),(i=new Deflater(6)).append(new Uint8Array(n)),e=i.flush(),(n=new Uint8Array(e.length+6)).set(new Uint8Array([120,156])),n.set(e,2),n.set(new Uint8Array([255&o,o>>8&255,o>>16&255,o>>24&255]),e.length+2),e=String.fromCharCode.apply(null,n),it("<>")}else it("<>");H(e),it("endobj")}x[1]=E,it("1 0 obj"),it("<>"),it("endobj"),rt.publish("postPutPages")}(),function(){rt.publish("putAdditionalObjects");for(var t=0;t>"),it("endobj"),L(),it("<<"),function(){switch(it("/Type /Catalog"),it("/Pages 1 0 R"),s||(s="fullwidth"),s){case"fullwidth":it("/OpenAction [3 0 R /FitH null]");break;case"fullheight":it("/OpenAction [3 0 R /FitV null]");break;case"fullpage":it("/OpenAction [3 0 R /Fit]");break;case"original":it("/OpenAction [3 0 R /XYZ null null 1]");break;default:var t=""+s;"%"===t.substr(t.length-1)&&(s=parseInt(s)/100),"number"==typeof s&&it("/OpenAction [3 0 R /XYZ null null "+N(s)+"]")}switch(h||(h="continuous"),h){case"continuous":it("/PageLayout /OneColumn");break;case"single":it("/PageLayout /SinglePage");break;case"two":case"twoleft":it("/PageLayout /TwoColumnLeft");break;case"tworight":it("/PageLayout /TwoColumnRight")}a&&it("/PageMode /"+a),rt.publish("putCatalog")}(),it(">>"),it("endobj");var t,e=E,n="0000000000";for(it("xref"),it("0 "+(m+1)),it(n+" 65535 f "),t=1;t<=m;t++){var r=x[t];it("function"==typeof r?(n+x[t]()).slice(-10)+" 00000 n ":(n+x[t]).slice(-10)+" 00000 n ")}return it("trailer"),it("<<"),it("/Size "+(m+1)),it("/Root "+m+" 0 R"),it("/Info "+(m-1)+" 0 R"),it("/ID [ <"+g+"> <"+g+"> ]"),it(">>"),it("startxref"),it(""+e),it("%%EOF"),b=!0,C.join("\n")},ht=function(t){var e="S";return"F"===t?e="f":"FD"===t||"DF"===t?e="B":"f"!==t&&"f*"!==t&&"B"!==t&&"B*"!==t||(e=t),e},ct=function(){for(var t=st(),e=t.length,n=new ArrayBuffer(e),r=new Uint8Array(n);e--;)r[e]=t.charCodeAt(e);return n},lt=function(){return new Blob([ct()],{type:"application/pdf"})},ut=((l=function(t,e){var n="dataur"===(""+t).substr(0,6)?"data:application/pdf;base64,"+btoa(st()):0;switch(t){case void 0:return st();case"save":if("object"===("undefined"==typeof navigator?"undefined":vt(navigator))&&navigator.getUserMedia&&(void 0===pt.URL||void 0===pt.URL.createObjectURL))return O.output("dataurlnewwindow");bt(lt(),e),"function"==typeof bt.unload&&pt.setTimeout&&setTimeout(bt.unload,911);break;case"arraybuffer":return ct();case"blob":return lt();case"bloburi":case"bloburl":return pt.URL&&pt.URL.createObjectURL(lt())||void 0;case"datauristring":case"dataurlstring":return n;case"dataurlnewwindow":var r=pt.open(n);if(r||"undefined"==typeof safari)return r;case"datauri":case"dataurl":return pt.document.location.href=n;default:throw new Error('Output type "'+t+'" is not supported.')}}).foo=function(){try{return l.apply(this,arguments)}catch(t){var e=t.stack||"";~e.indexOf(" at ")&&(e=e.split(" at ")[1]);var n="Error in function "+e.split("\n")[0].split("<")[0]+": "+t.message;if(!pt.console)throw new Error(n);pt.console.error(n,t),pt.alert&&alert(n)}},(l.foo.bar=l).foo),ft=function(t){return!0===Array.isArray(B)&&-1":")")):"[object Array]"===Object.prototype.toString.call(y[H])&&(W=parseFloat(y[H][1]).toFixed(2),G=parseFloat(y[H][2]).toFixed(2),V=(r?"<":"(")+y[H][0]+(r?">":")"),Y=1),void 0!==D&&void 0!==D[H]&&(X=D[H]+" Tw\n"),0!==k.length&&0===H?t.push(X+k.join(" ")+" "+W+" "+G+" Tm\n"+V):1===Y||0===Y&&0===H?t.push(X+W+" "+G+" Td\n"+V):t.push(X+V);t=0===Y?t.join(" Tj\nT* "):t.join(" Tj\n"),t+=" Tj\n";var J="BT\n/"+K+" "+Z+" Tf\n"+(Z*s).toFixed(2)+" TL\n"+Q+"\n";return J+=a,J+=t,it(J+="ET"),h},O.lstext=function(t,e,n,r){console.warn("jsPDF.lstext is deprecated");for(var i=0,o=t.length;i, https://github.com/MrRio/jsPDF + * 2010 Aaron Spike, https://github.com/acspike + * 2012 Willow Systems Corporation, willow-systems.com + * 2012 Pablo Hess, https://github.com/pablohess + * 2012 Florian Jenett, https://github.com/fjenett + * 2013 Warren Weckesser, https://github.com/warrenweckesser + * 2013 Youssef Beddad, https://github.com/lifof + * 2013 Lee Driscoll, https://github.com/lsdriscoll + * 2013 Stefan Slonevskiy, https://github.com/stefslon + * 2013 Jeremy Morel, https://github.com/jmorel + * 2013 Christoph Hartmann, https://github.com/chris-rock + * 2014 Juan Pablo Gaviria, https://github.com/juanpgaviria + * 2014 James Makes, https://github.com/dollaruw + * 2014 Diego Casorran, https://github.com/diegocr + * 2014 Steven Spungin, https://github.com/Flamenco + * 2014 Kenneth Glassey, https://github.com/Gavvers + * + * Licensed under the MIT License + * + * Contributor(s): + * siefkenj, ahwolf, rickygu, Midnith, saintclair, eaparango, + * kim3er, mfo, alnorth, Flamenco + */ +/** + * jsPDF AcroForm Plugin Copyright (c) 2016 Alexander Weidt, + * https://github.com/BiggA94 + * + * Licensed under the MIT License. http://opensource.org/licenses/mit-license + */ +!function(n,t){var l,a,e=1,r=function(t,e){t.prototype=Object.create(e.prototype),t.prototype.constructor=t},s=function(t){return t*(e/1)},h=function(t){var e=new I,n=N.internal.getHeight(t)||0,r=N.internal.getWidth(t)||0;return e.BBox=[0,0,r.toFixed(2),n.toFixed(2)],e},i=function(t,e,n){t=t||0;var r=1;if(r<<=e-1,1==(n=n||1))t=t|r;else t=t&~r;return t},o=function(t,e,n){n=n||1.3,t=t||0;return 1==e.readOnly&&(t=i(t,1)),1==e.required&&(t=i(t,2)),1==e.noExport&&(t=i(t,3)),1==e.multiline&&(t=i(t,13)),e.password&&(t=i(t,14)),e.noToggleToOff&&(t=i(t,15)),e.radio&&(t=i(t,16)),e.pushbutton&&(t=i(t,17)),e.combo&&(t=i(t,18)),e.edit&&(t=i(t,19)),e.sort&&(t=i(t,20)),e.fileSelect&&1.4<=n&&(t=i(t,21)),e.multiSelect&&1.4<=n&&(t=i(t,22)),e.doNotSpellCheck&&1.4<=n&&(t=i(t,23)),1==e.doNotScroll&&1.4<=n&&(t=i(t,24)),e.richText&&1.4<=n&&(t=i(t,25)),t},u=function(t){var e=t[0],n=t[1],r=t[2],i=t[3],o={};return Array.isArray(e)?(e[0]=s(e[0]),e[1]=s(e[1]),e[2]=s(e[2]),e[3]=s(e[3])):(e=s(e),n=s(n),r=s(r),i=s(i)),o.lowerLeft_X=e||0,o.lowerLeft_Y=s(a)-n-i||0,o.upperRight_X=e+r||0,o.upperRight_Y=s(a)-n||0,[o.lowerLeft_X.toFixed(2),o.lowerLeft_Y.toFixed(2),o.upperRight_X.toFixed(2),o.upperRight_Y.toFixed(2)]},f=function(t){if(t.appearanceStreamContent)return t.appearanceStreamContent;if(t.V||t.DV){var e=[],n=t.V||t.DV,r=c(t,n);e.push("/Tx BMC"),e.push("q"),e.push("/F1 "+r.fontSize.toFixed(2)+" Tf"),e.push("1 0 0 1 0 0 Tm"),e.push("BT"),e.push(r.text),e.push("ET"),e.push("Q"),e.push("EMC");var i=new h(t);return i.stream=e.join("\n"),i}},c=function(t,e,i,n){n=n||12,i=i||"helvetica";var r={text:"",fontSize:""},o=(e=")"==(e="("==e.substr(0,1)?e.substr(1):e).substr(e.length-1)?e.substr(0,e.length-1):e).split(" "),a=n,s=N.internal.getHeight(t)||0;s=s<0?-s:s;var h=N.internal.getWidth(t)||0;h=h<0?-h:h;var c=function(t,e,n){if(t+1=o.length-1;if(!x||S){if(x||S){if(S)g=b;else if(t.multiline&&s<(l+2)*(y+2)+2)continue t}else{if(!t.multiline)continue t;if(s<(l+2)*(y+2)+2)continue t;g=b}for(var k="",_=p;_<=g;_++)k+=o[_]+" ";switch(k=" "==k.substr(k.length-1)?k.substr(0,k.length-1):k,m=A(k,a+"px",i).width,t.Q){case 2:f=h-m-2;break;case 1:f=(h-m)/2;break;case 0:default:f=2}e+=f.toFixed(2)+" "+d.toFixed(2)+" Td\n",e+="("+k+") Tj\n",e+=-f.toFixed(2)+" 0 Td\n",d=-(a+2),m=0,p=g+1,y++,w=""}else w+=" "}break}return r.text=e,r.fontSize=a,r},A=function(t,e,n){n=n||"helvetica";var r=l.internal.getFont(n),i=l.getStringUnitWidth(t,{font:r,fontSize:parseFloat(e),charSpace:0})*parseFloat(e);return{height:l.getStringUnitWidth("3",{font:r,fontSize:parseFloat(e),charSpace:0})*parseFloat(e)*1.5,width:i}},d={fields:[],xForms:[],acroFormDictionaryRoot:null,printedOut:!1,internal:null,isInitialized:!1},p=function(){for(var t in l.internal.acroformPlugin.acroFormDictionaryRoot.Fields){var e=l.internal.acroformPlugin.acroFormDictionaryRoot.Fields[t];e.hasAnnotation&&m.call(l,e)}},g=function(t){l.internal.acroformPlugin.printedOut&&(l.internal.acroformPlugin.printedOut=!1,l.internal.acroformPlugin.acroFormDictionaryRoot=null),l.internal.acroformPlugin.acroFormDictionaryRoot||x.call(l),l.internal.acroformPlugin.acroFormDictionaryRoot.Fields.push(t)},m=function(t){var e={type:"reference",object:t};l.annotationPlugin.annotations[l.internal.getPageInfo(t.page).pageNumber].push(e)},w=function(){void 0!==l.internal.acroformPlugin.acroFormDictionaryRoot?l.internal.write("/AcroForm "+l.internal.acroformPlugin.acroFormDictionaryRoot.objId+" 0 R"):console.log("Root missing...")},y=function(){l.internal.events.unsubscribe(l.internal.acroformPlugin.acroFormDictionaryRoot._eventID),delete l.internal.acroformPlugin.acroFormDictionaryRoot._eventID,l.internal.acroformPlugin.printedOut=!0},v=function(t){var e=!t;t||(l.internal.newObjectDeferredBegin(l.internal.acroformPlugin.acroFormDictionaryRoot.objId),l.internal.out(l.internal.acroformPlugin.acroFormDictionaryRoot.getString()));t=t||l.internal.acroformPlugin.acroFormDictionaryRoot.Kids;for(var n in t){var r=t[n],i=r.Rect;r.Rect&&(r.Rect=u.call(this,r.Rect)),l.internal.newObjectDeferredBegin(r.objId);var o=r.objId+" 0 obj\n<<\n";if("object"===(void 0===r?"undefined":vt(r))&&"function"==typeof r.getContent&&(o+=r.getContent()),r.Rect=i,r.hasAppearanceStream&&!r.appearanceStreamContent){var a=f.call(this,r);o+="/AP << /N "+a+" >>\n",l.internal.acroformPlugin.xForms.push(a)}if(r.appearanceStreamContent){for(var s in o+="/AP << ",r.appearanceStreamContent){var h=r.appearanceStreamContent[s];if(o+="/"+s+" ",o+="<< ",1<=Object.keys(h).length||Array.isArray(h))for(var n in h){var c;"function"==typeof(c=h[n])&&(c=c.call(this,r)),o+="/"+n+" "+c+" ",0<=l.internal.acroformPlugin.xForms.indexOf(c)||l.internal.acroformPlugin.xForms.push(c)}else"function"==typeof(c=h)&&(c=c.call(this,r)),o+="/"+n+" "+c+" \n",0<=l.internal.acroformPlugin.xForms.indexOf(c)||l.internal.acroformPlugin.xForms.push(c);o+=" >>\n"}o+=">>\n"}o+=">>\nendobj\n",l.internal.out(o)}e&&b.call(this,l.internal.acroformPlugin.xForms)},b=function(t){for(var e in t){var n=e,r=t[e];l.internal.newObjectDeferredBegin(r&&r.objId);var i="";"object"===(void 0===r?"undefined":vt(r))&&"function"==typeof r.getString&&(i=r.getString()),l.internal.out(i),delete t[n]}},x=function(){if(void 0!==this.internal&&(void 0===this.internal.acroformPlugin||!1===this.internal.acroformPlugin.isInitialized)){if(l=this,T.FieldNum=0,this.internal.acroformPlugin=JSON.parse(JSON.stringify(d)),this.internal.acroformPlugin.acroFormDictionaryRoot)throw new Error("Exception while creating AcroformDictionary");e=l.internal.scaleFactor,a=l.internal.pageSize.getHeight(),l.internal.acroformPlugin.acroFormDictionaryRoot=new C,l.internal.acroformPlugin.acroFormDictionaryRoot._eventID=l.internal.events.subscribe("postPutResources",y),l.internal.events.subscribe("buildDocument",p),l.internal.events.subscribe("putCatalog",w),l.internal.events.subscribe("postPutPages",v),l.internal.acroformPlugin.isInitialized=!0}},S=function(t){if(Array.isArray(t)){var e=" [";for(var n in t){e+=t[n].toString(),e+=n>\n",this.stream&&(t+="stream\n",t+=this.stream,t+="\nendstream\n"),t+="endobj\n"},_.prototype.getContent=function(){var t="";return t+=function(t){var e="",n=Object.keys(t).filter(function(t){return"content"!=t&&"appearanceStreamContent"!=t&&"_"!=t.substring(0,1)});for(var r in n){var i=n[r],o=t[i];o&&(Array.isArray(o)?e+="/"+i+" "+S(o)+"\n":e+=o instanceof _?"/"+i+" "+o.objId+" 0 R\n":"/"+i+" "+o+"\n")}return e}(this)};var I=function(){var e;_.call(this),this.Type="/XObject",this.Subtype="/Form",this.FormType=1,this.BBox,this.Matrix,this.Resources="2 0 R",this.PieceInfo,Object.defineProperty(this,"Length",{enumerable:!0,get:function(){return void 0!==e?e.length:0}}),Object.defineProperty(this,"stream",{enumerable:!1,set:function(t){e=t.trim()},get:function(){return e||null}})};r(I,_);var C=function(){_.call(this);var t=[];Object.defineProperty(this,"Kids",{enumerable:!1,configurable:!0,get:function(){return 0>"},YesPushDown:function(t){var e=h(t),n=[],r=l.internal.getFont("zapfdingbats","normal").id;t.Q=1;var i=c(t,"3","ZapfDingbats",50);return n.push("0.749023 g"),n.push("0 0 "+N.internal.getWidth(t).toFixed(2)+" "+N.internal.getHeight(t).toFixed(2)+" re"),n.push("f"),n.push("BMC"),n.push("q"),n.push("0 0 1 rg"),n.push("/"+r+" "+i.fontSize.toFixed(2)+" Tf 0 g"),n.push("BT"),n.push(i.text),n.push("ET"),n.push("Q"),n.push("EMC"),e.stream=n.join("\n"),e},YesNormal:function(t){var e=h(t),n=l.internal.getFont("zapfdingbats","normal").id,r=[];t.Q=1;var i=N.internal.getHeight(t),o=N.internal.getWidth(t),a=c(t,"3","ZapfDingbats",.9*i);return r.push("1 g"),r.push("0 0 "+o.toFixed(2)+" "+i.toFixed(2)+" re"),r.push("f"),r.push("q"),r.push("0 0 1 rg"),r.push("0 0 "+(o-1).toFixed(2)+" "+(i-1).toFixed(2)+" re"),r.push("W"),r.push("n"),r.push("0 g"),r.push("BT"),r.push("/"+n+" "+a.fontSize.toFixed(2)+" Tf 0 g"),r.push(a.text),r.push("ET"),r.push("Q"),e.stream=r.join("\n"),e},OffPushDown:function(t){var e=h(t),n=[];return n.push("0.749023 g"),n.push("0 0 "+N.internal.getWidth(t).toFixed(2)+" "+N.internal.getHeight(t).toFixed(2)+" re"),n.push("f"),e.stream=n.join("\n"),e}},RadioButton:{Circle:{createAppearanceStream:function(t){var e={D:{Off:N.RadioButton.Circle.OffPushDown},N:{}};return e.N[t]=N.RadioButton.Circle.YesNormal,e.D[t]=N.RadioButton.Circle.YesPushDown,e},createMK:function(){return"<< /CA (l)>>"},YesNormal:function(t){var e=h(t),n=[],r=N.internal.getWidth(t)<=N.internal.getHeight(t)?N.internal.getWidth(t)/4:N.internal.getHeight(t)/4;r*=.9;var i=N.internal.Bezier_C;return n.push("q"),n.push("1 0 0 1 "+N.internal.getWidth(t)/2+" "+N.internal.getHeight(t)/2+" cm"),n.push(r+" 0 m"),n.push(r+" "+r*i+" "+r*i+" "+r+" 0 "+r+" c"),n.push("-"+r*i+" "+r+" -"+r+" "+r*i+" -"+r+" 0 c"),n.push("-"+r+" -"+r*i+" -"+r*i+" -"+r+" 0 -"+r+" c"),n.push(r*i+" -"+r+" "+r+" -"+r*i+" "+r+" 0 c"),n.push("f"),n.push("Q"),e.stream=n.join("\n"),e},YesPushDown:function(t){var e=h(t),n=[],r=N.internal.getWidth(t)<=N.internal.getHeight(t)?N.internal.getWidth(t)/4:N.internal.getHeight(t)/4,i=2*(r*=.9),o=i*N.internal.Bezier_C,a=r*N.internal.Bezier_C;return n.push("0.749023 g"),n.push("q"),n.push("1 0 0 1 "+(N.internal.getWidth(t)/2).toFixed(2)+" "+(N.internal.getHeight(t)/2).toFixed(2)+" cm"),n.push(i+" 0 m"),n.push(i+" "+o+" "+o+" "+i+" 0 "+i+" c"),n.push("-"+o+" "+i+" -"+i+" "+o+" -"+i+" 0 c"),n.push("-"+i+" -"+o+" -"+o+" -"+i+" 0 -"+i+" c"),n.push(o+" -"+i+" "+i+" -"+o+" "+i+" 0 c"),n.push("f"),n.push("Q"),n.push("0 g"),n.push("q"),n.push("1 0 0 1 "+(N.internal.getWidth(t)/2).toFixed(2)+" "+(N.internal.getHeight(t)/2).toFixed(2)+" cm"),n.push(r+" 0 m"),n.push(r+" "+a+" "+a+" "+r+" 0 "+r+" c"),n.push("-"+a+" "+r+" -"+r+" "+a+" -"+r+" 0 c"),n.push("-"+r+" -"+a+" -"+a+" -"+r+" 0 -"+r+" c"),n.push(a+" -"+r+" "+r+" -"+a+" "+r+" 0 c"),n.push("f"),n.push("Q"),e.stream=n.join("\n"),e},OffPushDown:function(t){var e=h(t),n=[],r=N.internal.getWidth(t)<=N.internal.getHeight(t)?N.internal.getWidth(t)/4:N.internal.getHeight(t)/4,i=2*(r*=.9),o=i*N.internal.Bezier_C;return n.push("0.749023 g"),n.push("q"),n.push("1 0 0 1 "+(N.internal.getWidth(t)/2).toFixed(2)+" "+(N.internal.getHeight(t)/2).toFixed(2)+" cm"),n.push(i+" 0 m"),n.push(i+" "+o+" "+o+" "+i+" 0 "+i+" c"),n.push("-"+o+" "+i+" -"+i+" "+o+" -"+i+" 0 c"),n.push("-"+i+" -"+o+" -"+o+" -"+i+" 0 -"+i+" c"),n.push(o+" -"+i+" "+i+" -"+o+" "+i+" 0 c"),n.push("f"),n.push("Q"),e.stream=n.join("\n"),e}},Cross:{createAppearanceStream:function(t){var e={D:{Off:N.RadioButton.Cross.OffPushDown},N:{}};return e.N[t]=N.RadioButton.Cross.YesNormal,e.D[t]=N.RadioButton.Cross.YesPushDown,e},createMK:function(){return"<< /CA (8)>>"},YesNormal:function(t){var e=h(t),n=[],r=N.internal.calculateCross(t);return n.push("q"),n.push("1 1 "+(N.internal.getWidth(t)-2).toFixed(2)+" "+(N.internal.getHeight(t)-2).toFixed(2)+" re"),n.push("W"),n.push("n"),n.push(r.x1.x.toFixed(2)+" "+r.x1.y.toFixed(2)+" m"),n.push(r.x2.x.toFixed(2)+" "+r.x2.y.toFixed(2)+" l"),n.push(r.x4.x.toFixed(2)+" "+r.x4.y.toFixed(2)+" m"),n.push(r.x3.x.toFixed(2)+" "+r.x3.y.toFixed(2)+" l"),n.push("s"),n.push("Q"),e.stream=n.join("\n"),e},YesPushDown:function(t){var e=h(t),n=N.internal.calculateCross(t),r=[];return r.push("0.749023 g"),r.push("0 0 "+N.internal.getWidth(t).toFixed(2)+" "+N.internal.getHeight(t).toFixed(2)+" re"),r.push("f"),r.push("q"),r.push("1 1 "+(N.internal.getWidth(t)-2).toFixed(2)+" "+(N.internal.getHeight(t)-2).toFixed(2)+" re"),r.push("W"),r.push("n"),r.push(n.x1.x.toFixed(2)+" "+n.x1.y.toFixed(2)+" m"),r.push(n.x2.x.toFixed(2)+" "+n.x2.y.toFixed(2)+" l"),r.push(n.x4.x.toFixed(2)+" "+n.x4.y.toFixed(2)+" m"),r.push(n.x3.x.toFixed(2)+" "+n.x3.y.toFixed(2)+" l"),r.push("s"),r.push("Q"),e.stream=r.join("\n"),e},OffPushDown:function(t){var e=h(t),n=[];return n.push("0.749023 g"),n.push("0 0 "+N.internal.getWidth(t).toFixed(2)+" "+N.internal.getHeight(t).toFixed(2)+" re"),n.push("f"),e.stream=n.join("\n"),e}}},createDefaultAppearanceStream:function(t){return"/F1 0 Tf 0 g"}};N.internal={Bezier_C:.551915024494,calculateCross:function(t){var e,n,r=N.internal.getWidth(t),i=N.internal.getHeight(t),o=(n=i)<(e=r)?n:e;return{x1:{x:(r-o)/2,y:(i-o)/2+o},x2:{x:(r-o)/2+o,y:(i-o)/2},x3:{x:(r-o)/2,y:(i-o)/2},x4:{x:(r-o)/2+o,y:(i-o)/2+o}}}},N.internal.getWidth=function(t){var e=0;return"object"===(void 0===t?"undefined":vt(t))&&(e=s(t.Rect[2])),e},N.internal.getHeight=function(t){var e=0;return"object"===(void 0===t?"undefined":vt(t))&&(e=s(t.Rect[3])),e},n.addField=function(t){return x.call(this),t instanceof M?this.addTextField.call(this,t):t instanceof F?this.addChoiceField.call(this,t):t instanceof O?this.addButton.call(this,t):t instanceof j?g.call(this,t):t&&g.call(this,t),t.page=l.internal.getCurrentPageInfo().pageNumber,this},n.addButton=function(t){x.call(this);var e=t||new T;e.FT="/Btn",e.Ff=o(e.Ff,t,l.internal.getPDFVersion()),g.call(this,e)},n.addTextField=function(t){x.call(this);var e=t||new T;e.FT="/Tx",e.Ff=o(e.Ff,t,l.internal.getPDFVersion()),g.call(this,e)},n.addChoiceField=function(t){x.call(this);var e=t||new T;e.FT="/Ch",e.Ff=o(e.Ff,t,l.internal.getPDFVersion()),g.call(this,e)},"object"==(void 0===t?"undefined":vt(t))&&(t.ChoiceField=F,t.ListBox=P,t.ComboBox=E,t.EditBox=q,t.Button=O,t.PushButton=B,t.RadioButton=R,t.CheckBox=D,t.TextField=M,t.PasswordField=U,t.AcroForm={Appearance:N}),n.AcroFormChoiceField=F,n.AcroFormListBox=P,n.AcroFormComboBox=E,n.AcroFormEditBox=q,n.AcroFormButton=O,n.AcroFormPushButton=B,n.AcroFormRadioButton=R,n.AcroFormCheckBox=D,n.AcroFormTextField=M,n.AcroFormPasswordField=U,n.AcroForm={ChoiceField:F,ListBox:P,ComboBox:E,EditBox:q,Button:O,PushButton:B,RadioButton:R,CheckBox:D,TextField:M,PasswordField:U}}($.API,"undefined"!=typeof window&&window||"undefined"!=typeof global&&global),$.API.addHTML=function(t,p,g,s,m){if("undefined"==typeof html2canvas&&"undefined"==typeof rasterizeHTML)throw new Error("You need either https://github.com/niklasvh/html2canvas or https://github.com/cburgmer/rasterizeHTML.js");"number"!=typeof p&&(s=p,m=g),"function"==typeof s&&(m=s,s=null),"function"!=typeof m&&(m=function(){});var e=this.internal,w=e.scaleFactor,y=e.pageSize.getWidth(),v=e.pageSize.getHeight();if((s=s||{}).onrendered=function(h){p=parseInt(p)||0,g=parseInt(g)||0;var t=s.dim||{},c=Object.assign({top:0,right:0,bottom:0,left:0,useFor:"content"},s.margin),e=t.h||Math.min(v,h.height/w),l=t.w||Math.min(y,h.width/w)-p,u=s.format||"JPEG",f=s.imageCompression||"SLOW";if(h.height>v-c.top-c.bottom&&s.pagesplit){var d=function(t,e,n,r,i){var o=document.createElement("canvas");o.height=i,o.width=r;var a=o.getContext("2d");return a.mozImageSmoothingEnabled=!1,a.webkitImageSmoothingEnabled=!1,a.msImageSmoothingEnabled=!1,a.imageSmoothingEnabled=!1,a.fillStyle=s.backgroundColor||"#ffffff",a.fillRect(0,0,r,i),a.drawImage(t,e,n,r,i,0,0,r,i),o},n=function(){for(var t,e,n=0,r=0,i={},o=!1;;){var a;if(r=0,i.top=0!==n?c.top:g,i.left=0!==n?c.left:p,o=(y-c.left-c.right)*w=h.width)break;this.addPage()}else s=[a=d(h,0,n,t,e),i.left,i.top,a.width/w,a.height/w,u,null,f],this.addImage.apply(this,s);if((n+=e)>=h.height)break;this.addPage()}m(l,n,null,s)}.bind(this);if("CANVAS"===h.nodeName){var r=new Image;r.onload=n,r.src=h.toDataURL("image/png"),h=r}else n()}else{var i=Math.random().toString(35),o=[h,p,g,l,e,u,i,f];this.addImage.apply(this,o),m(l,e,i,o)}}.bind(this),"undefined"!=typeof html2canvas&&!s.rstz)return html2canvas(t,s);if("undefined"!=typeof rasterizeHTML){var n="drawDocument";return"string"==typeof t&&(n=/^http/.test(t)?"drawURL":"drawHTML"),s.width=s.width||y*w,rasterizeHTML[n](t,void 0,s).then(function(t){s.onrendered(t.image)},function(t){m(null,t)})}return null}, +/** @preserve + * jsPDF addImage plugin + * Copyright (c) 2012 Jason Siefken, https://github.com/siefkenj/ + * 2013 Chris Dowling, https://github.com/gingerchris + * 2013 Trinh Ho, https://github.com/ineedfat + * 2013 Edwin Alejandro Perez, https://github.com/eaparango + * 2013 Norah Smith, https://github.com/burnburnrocket + * 2014 Diego Casorran, https://github.com/diegocr + * 2014 James Robb, https://github.com/jamesbrobb + * + * + */ +function(b){var x="addImage_",h={PNG:[[137,80,78,71]],TIFF:[[77,77,0,42],[73,73,42,0]],JPEG:[[255,216,255,224,void 0,void 0,74,70,73,70,0],[255,216,255,225,void 0,void 0,69,120,105,102,0,0]],JPEG2000:[[0,0,0,12,106,80,32,32]],GIF87a:[[71,73,70,56,55,97]],GIF89a:[[71,73,70,56,57,97]],BMP:[[66,77],[66,65],[67,73],[67,80],[73,67],[80,84]]};b.getImageFileTypeByImageData=function(t,e){var n,r;e=e||"UNKNOWN";var i,o,a,s="UNKNOWN";for(a in h)for(i=h[a],n=0;n>"),"trns"in e&&e.trns.constructor==Array){for(var o="",a=0,s=e.trns.length;a>"),i(e.data),r("endobj"),"smask"in e){var h="/Predictor "+e.p+" /Colors 1 /BitsPerComponent "+e.bpc+" /Columns "+e.w,c={w:e.w,h:e.h,cs:"DeviceGray",bpc:e.bpc,dp:h,data:e.smask};"f"in e&&(c.f=e.f),t.call(this,c)}e.cs===this.color_spaces.INDEXED&&(this.internal.newObject(),r("<< /Length "+e.pal.length+">>"),i(this.arrayBufferToBinaryString(new Uint8Array(e.pal))),r("endobj"))},S=function(){var t=this.internal.collections[x+"images"];for(var e in t)n.call(this,t[e])},k=function(){var t,e=this.internal.collections[x+"images"],n=this.internal.write;for(var r in e)n("/I"+(t=e[r]).i,t.n,"0","R")},_=function(t){return"function"==typeof b["process"+t.toUpperCase()]},A=function(t){return"object"===(void 0===t?"undefined":vt(t))&&1===t.nodeType},I=function(t,e){if("IMG"===t.nodeName&&t.hasAttribute("src")){var n=""+t.getAttribute("src");if(0===n.indexOf("data:image/"))return n;!e&&/\.png(?:[?#].*)?$/i.test(n)&&(e="png")}if("CANVAS"===t.nodeName)var r=t;else{(r=document.createElement("canvas")).width=t.clientWidth||t.width,r.height=t.clientHeight||t.height;var i=r.getContext("2d");if(!i)throw"addImage requires canvas to be supported by browser.";i.drawImage(t,0,0,r.width,r.height)}return r.toDataURL("png"==(""+e).toLowerCase()?"image/png":"image/jpeg")},C=function(t,e){var n;if(e)for(var r in e)if(t===e[r].alias){n=e[r];break}return n};b.color_spaces={DEVICE_RGB:"DeviceRGB",DEVICE_GRAY:"DeviceGray",DEVICE_CMYK:"DeviceCMYK",CAL_GREY:"CalGray",CAL_RGB:"CalRGB",LAB:"Lab",ICC_BASED:"ICCBased",INDEXED:"Indexed",PATTERN:"Pattern",SEPARATION:"Separation",DEVICE_N:"DeviceN"},b.decode={DCT_DECODE:"DCTDecode",FLATE_DECODE:"FlateDecode",LZW_DECODE:"LZWDecode",JPX_DECODE:"JPXDecode",JBIG2_DECODE:"JBIG2Decode",ASCII85_DECODE:"ASCII85Decode",ASCII_HEX_DECODE:"ASCIIHexDecode",RUN_LENGTH_DECODE:"RunLengthDecode",CCITT_FAX_DECODE:"CCITTFaxDecode"},b.image_compression={NONE:"NONE",FAST:"FAST",MEDIUM:"MEDIUM",SLOW:"SLOW"},b.sHashCode=function(t){return t=t||"",Array.prototype.reduce&&t.split("").reduce(function(t,e){return(t=(t<<5)-t+e.charCodeAt(0))&t},0)},b.isString=function(t){return"string"==typeof t},b.validateStringAsBase64=function(t){var e=!0;return(t=t||"").length%4!=0&&(e=!1),!1===/[A-Za-z0-9\/]+/.test(t.substr(0,t.length-2))&&(e=!1),!1===/[A-Za-z0-9\/][A-Za-z0-9+\/]|[A-Za-z0-9+\/]=|==/.test(t.substr(-2))&&(e=!1),e},b.extractInfoFromBase64DataURI=function(t){return/^data:([\w]+?\/([\w]+?));base64,(.+)$/g.exec(t)},b.supportsArrayBuffer=function(){return"undefined"!=typeof ArrayBuffer&&"undefined"!=typeof Uint8Array},b.isArrayBuffer=function(t){return!!this.supportsArrayBuffer()&&t instanceof ArrayBuffer},b.isArrayBufferView=function(t){return!!this.supportsArrayBuffer()&&("undefined"!=typeof Uint32Array&&(t instanceof Int8Array||t instanceof Uint8Array||"undefined"!=typeof Uint8ClampedArray&&t instanceof Uint8ClampedArray||t instanceof Int16Array||t instanceof Uint16Array||t instanceof Int32Array||t instanceof Uint32Array||t instanceof Float32Array||t instanceof Float64Array))},b.binaryStringToUint8Array=function(t){for(var e=t.length,n=new Uint8Array(e),r=0;r>18]+r[(258048&e)>>12]+r[(4032&e)>>6]+r[63&e];return 1==a?n+=r[(252&(e=i[s]))>>2]+r[(3&e)<<4]+"==":2==a&&(n+=r[(64512&(e=i[s]<<8|i[s+1]))>>10]+r[(1008&e)>>4]+r[(15&e)<<2]+"="),n},b.createImageInfo=function(t,e,n,r,i,o,a,s,h,c,l,u,f){var d={alias:s,w:e,h:n,cs:r,bpc:i,i:a,data:t};return o&&(d.f=o),h&&(d.dp=h),c&&(d.trns=c),l&&(d.pal=l),u&&(d.smask=u),f&&(d.p=f),d},b.addImage=function(t,e,n,r,i,o,a,s,h){var c="";if("string"!=typeof e){var l=o;o=i,i=r,r=n,n=e,e=l}if("object"===(void 0===t?"undefined":vt(t))&&!A(t)&&"imageData"in t){var u=t;t=u.imageData,e=u.format||e,n=u.x||n||0,r=u.y||r||0,i=u.w||i,o=u.h||o,a=u.alias||a,s=u.compression||s,h=u.rotation||u.angle||h}if(isNaN(n)||isNaN(r))throw console.error("jsPDF.addImage: Invalid coordinates",arguments),new Error("Invalid coordinates passed to jsPDF.addImage");var f,d,p,g,m,w,y,v=function(){var t=this.internal.collections[x+"images"];return t||(this.internal.collections[x+"images"]=t={},this.internal.events.subscribe("putResources",S),this.internal.events.subscribe("putXobjectDict",k)),t}.call(this);if(!(f=C(t,v))&&(A(t)&&(t=I(t,e)),(null==(y=a)||0===y.length)&&(a="string"==typeof(w=t)&&b.sHashCode(w)),!(f=C(a,v)))){if(this.isString(t)&&(""!==(c=this.convertStringToImageData(t))?t=c:void 0!==(c=this.loadImageFile(t))&&(t=c)),e=this.getImageFileTypeByImageData(t,e),!_(e))throw new Error("addImage does not support files of type '"+e+"', please ensure that a plugin for '"+e+"' support is added.");if(this.supportsArrayBuffer()&&(t instanceof Uint8Array||(d=t,t=this.binaryStringToUint8Array(t))),!(f=this["process"+e.toUpperCase()](t,(m=0,(g=v)&&(m=Object.keys?Object.keys(g).length:function(t){var e=0;for(var n in t)t.hasOwnProperty(n)&&e++;return e}(g)),m),a,((p=s)&&"string"==typeof p&&(p=p.toUpperCase()),p in b.image_compression?p:b.image_compression.NONE),d)))throw new Error("An unkwown error occurred whilst processing the image")}return function(t,e,n,r,i,o,a,s){var h=function(t,e,n){return t||e||(e=t=-96),t<0&&(t=-1*n.w*72/t/this.internal.scaleFactor),e<0&&(e=-1*n.h*72/e/this.internal.scaleFactor),0===t&&(t=e*n.w/n.h),0===e&&(e=t*n.h/n.w),[t,e]}.call(this,n,r,i),c=this.internal.getCoordinateString,l=this.internal.getVerticalCoordinateString;if(n=h[0],r=h[1],a[o]=i,s){s*=Math.PI/180;var u=Math.cos(s),f=Math.sin(s),d=function(t){return t.toFixed(4)},p=[d(u),d(f),d(-1*f),d(u),0,0,"cm"]}this.internal.write("q"),s?(this.internal.write([1,"0","0",1,c(t),l(e+r),"cm"].join(" ")),this.internal.write(p.join(" ")),this.internal.write([c(n),"0","0",c(r),"0","0","cm"].join(" "))):this.internal.write([c(n),"0","0",c(r),c(t),l(e+r),"cm"].join(" ")),this.internal.write("/I"+i.i+" Do"),this.internal.write("Q")}.call(this,n,r,i,o,f,f.i,v,h),this},b.convertStringToImageData=function(t){var e,n="";this.isString(t)&&(null!==(e=this.extractInfoFromBase64DataURI(t))?b.validateStringAsBase64(e[3])&&(n=atob(e[3])):b.validateStringAsBase64(t)&&(n=atob(t)));return n};var c=function(t,e){return t.subarray(e,e+5)};b.processJPEG=function(t,e,n,r,i,o){var a,s=this.decode.DCT_DECODE;if(!this.isString(t)&&!this.isArrayBuffer(t)&&!this.isArrayBufferView(t))return null;if(this.isString(t)&&(a=function(t){var e;if(255===!t.charCodeAt(0)||216===!t.charCodeAt(1)||255===!t.charCodeAt(2)||224===!t.charCodeAt(3)||!t.charCodeAt(6)==="J".charCodeAt(0)||!t.charCodeAt(7)==="F".charCodeAt(0)||!t.charCodeAt(8)==="I".charCodeAt(0)||!t.charCodeAt(9)==="F".charCodeAt(0)||0===!t.charCodeAt(10))throw new Error("getJpegSize requires a binary string jpeg file");for(var n=256*t.charCodeAt(4)+t.charCodeAt(5),r=4,i=t.length;r>",c.content=m;var f=c.objId+" 0 R";m="<>";else if(h.options.pageNumber)switch(m="<>",this.internal.write(m))}}this.internal.write("]")}}]),t.createAnnotation=function(t){switch(t.type){case"link":this.link(t.bounds.x,t.bounds.y,t.bounds.w,t.bounds.h,t);break;case"text":case"freetext":this.annotationPlugin.annotations[this.internal.getCurrentPageInfo().pageNumber].push(t)}},t.link=function(t,e,n,r,i){this.annotationPlugin.annotations[this.internal.getCurrentPageInfo().pageNumber].push({x:t,y:e,w:n,h:r,options:i,type:"link"})},t.textWithLink=function(t,e,n,r){var i=this.getTextWidth(t),o=this.internal.getLineHeight()/this.internal.scaleFactor;return this.text(t,e,n),n+=.2*o,this.link(e,n-o,i,o,r),i},t.getTextWidth=function(t){var e=this.internal.getFontSize();return this.getStringUnitWidth(t)*e/this.internal.scaleFactor},t.getLineHeight=function(){return this.internal.getLineHeight()},function(t){var a=Object.keys({ar:"Arabic (Standard)","ar-DZ":"Arabic (Algeria)","ar-BH":"Arabic (Bahrain)","ar-EG":"Arabic (Egypt)","ar-IQ":"Arabic (Iraq)","ar-JO":"Arabic (Jordan)","ar-KW":"Arabic (Kuwait)","ar-LB":"Arabic (Lebanon)","ar-LY":"Arabic (Libya)","ar-MA":"Arabic (Morocco)","ar-OM":"Arabic (Oman)","ar-QA":"Arabic (Qatar)","ar-SA":"Arabic (Saudi Arabia)","ar-SY":"Arabic (Syria)","ar-TN":"Arabic (Tunisia)","ar-AE":"Arabic (U.A.E.)","ar-YE":"Arabic (Yemen)",fa:"Persian","fa-IR":"Persian/Iran",ur:"Urdu"}),u={1569:[65152],1570:[65153,65154,65153,65154],1571:[65155,65156,65155,65156],1572:[65157,65158],1573:[65159,65160,65159,65160],1574:[65161,65162,65163,65164],1575:[65165,65166,65165,65166],1576:[65167,65168,65169,65170],1577:[65171,65172],1578:[65173,65174,65175,65176],1579:[65177,65178,65179,65180],1580:[65181,65182,65183,65184],1581:[65185,65186,65187,65188],1582:[65189,65190,65191,65192],1583:[65193,65194,65193],1584:[65195,65196,65195],1585:[65197,65198,65197],1586:[65199,65200,65199],1587:[65201,65202,65203,65204],1588:[65205,65206,65207,65208],1589:[65209,65210,65211,65212],1590:[65213,65214,65215,65216],1591:[65217,65218,65219,65220],1592:[65221,65222,65223,65224],1593:[65225,65226,65227,65228],1594:[65229,65230,65231,65232],1601:[65233,65234,65235,65236],1602:[65237,65238,65239,65240],1603:[65241,65242,65243,65244],1604:[65245,65246,65247,65248],1605:[65249,65250,65251,65252],1606:[65253,65254,65255,65256],1607:[65257,65258,65259,65260],1608:[65261,65262,65261],1609:[65263,65264,64488,64489],1610:[65265,65266,65267,65268],1649:[64336,64337],1655:[64477],1657:[64358,64359,64360,64361],1658:[64350,64351,64352,64353],1659:[64338,64339,64340,64341],1662:[64342,64343,64344,64345],1663:[64354,64355,64356,64357],1664:[64346,64347,64348,64349],1667:[64374,64375,64376,64377],1668:[64370,64371,64372,64373],1670:[64378,64379,64380,64381],1671:[64382,64383,64384,64385],1672:[64392,64393],1676:[64388,64389],1677:[64386,64387],1678:[64390,64391],1681:[64396,64397],1688:[64394,64395,64394],1700:[64362,64363,64364,64365],1702:[64366,64367,64368,64369],1705:[64398,64399,64400,64401],1709:[64467,64468,64469,64470],1711:[64402,64403,64404,64405],1713:[64410,64411,64412,64413],1715:[64406,64407,64408,64409],1722:[64414,64415],1723:[64416,64417,64418,64419],1726:[64426,64427,64428,64429],1728:[64420,64421],1729:[64422,64423,64424,64425],1733:[64480,64481],1734:[64473,64474],1735:[64471,64472],1736:[64475,64476],1737:[64482,64483],1739:[64478,64479],1740:[64508,64509,64510,64511],1744:[64484,64485,64486,64487],1746:[64430,64431],1747:[64432,64433]},f={1570:[65269,65270,65269,65270],1571:[65271,65272,65271,65272],1573:[65273,65274,65273,65274],1575:[65275,65276,65275,65276]},d={1570:[65153,65154,65153,65154],1571:[65155,65156,65155,65156],1573:[65159,65160,65159,65160],1575:[65165,65166,65165,65166]},p={1612:64606,1613:64607,1614:64608,1615:64609,1616:64610},e=[1570,1571,1573,1575],n=[1569,1570,1571,1572,1573,1575,1577,1583,1584,1585,1586,1608,1688],o=0,s=1,h=2,c=3;function g(t){return void 0!==t&&void 0!==u[t.charCodeAt(0)]}function l(t){return void 0!==t&&0<=n.indexOf(t.charCodeAt(0))}function m(t){return void 0!==t&&0<=e.indexOf(t.charCodeAt(0))}function w(t){return g(t)&&2<=u[t.charCodeAt(0)].length}function y(t,e,n,r){return g(t)?(r=r||{},u=Object.assign(u,r),!w(t)||!g(e)&&!g(n)||!g(n)&&l(e)||l(t)&&!g(e)||l(t)&&m(e)||l(t)&&l(e)?(u=Object.assign(u,d),o):g(i=t)&&4==u[i.charCodeAt(0)].length&&g(e)&&!l(e)&&g(n)&&w(n)?(u=Object.assign(u,d),c):l(t)||!g(n)?(u=Object.assign(u,d),s):(u=Object.assign(u,d),h)):-1;var i}var v=t.processArabic=function(t,e){t=t||"",e=e||!1;var n,r,i,o="",a=0,s=0,h="",c="",l="";for(a=0;a>"),this.internal.out("endobj")}),this.internal.events.subscribe("putCatalog",function(){this.internal.out("/OpenAction "+e+" 0 R")})}return this},( +/** + * jsPDF Canvas PlugIn + * Copyright (c) 2014 Steven Spungin (TwelveTone LLC) steven@twelvetone.tv + * + * Licensed under the MIT License. + * http://opensource.org/licenses/mit-license + */ +e=$.API).events.push(["initialized",function(){this.canvas.pdf=this}]),e.canvas={getContext:function(t){return(this.pdf.context2d._canvas=this).pdf.context2d},childNodes:[]},Object.defineProperty(e.canvas,"width",{get:function(){return this._width},set:function(t){this._width=t,this.getContext("2d").pageWrapX=t+1}}),Object.defineProperty(e.canvas,"height",{get:function(){return this._height},set:function(t){this._height=t,this.getContext("2d").pageWrapY=t+1}}), +/** ==================================================================== + * jsPDF Cell plugin + * Copyright (c) 2013 Youssef Beddad, youssef.beddad@gmail.com + * 2013 Eduardo Menezes de Morais, eduardo.morais@usp.br + * 2013 Lee Driscoll, https://github.com/lsdriscoll + * 2014 Juan Pablo Gaviria, https://github.com/juanpgaviria + * 2014 James Hall, james@parall.ax + * 2014 Diego Casorran, https://github.com/diegocr + * + * + * ==================================================================== + */ +I=$.API,C={x:void 0,y:void 0,w:void 0,h:void 0,ln:void 0},T=1,d=function(t,e,n,r,i){C={x:t,y:e,w:n,h:r,ln:i}},p=function(){return C},F={left:0,top:0,bottom:0},I.setHeaderFunction=function(t){h=t},I.getTextDimensions=function(e){i=this.internal.getFont().fontName,o=this.table_font_size||this.internal.getFontSize(),a=this.internal.getFont().fontStyle;var t,n,r=19.049976/25.4;(n=document.createElement("font")).id="jsPDFCell";try{n.style.fontStyle=a}catch(t){n.style.fontWeight=a}n.style.fontSize=o+"pt",n.style.fontFamily=i;try{n.textContent=e}catch(t){n.innerText=e}return document.body.appendChild(n),t={w:(n.offsetWidth+1)*r,h:(n.offsetHeight+1)*r},document.body.removeChild(n),t},I.cellAddPage=function(){var t=this.margins||F;this.addPage(),d(t.left,t.top,void 0,void 0),T+=1},I.cellInitialize=function(){C={x:void 0,y:void 0,w:void 0,h:void 0,ln:void 0},T=1},I.cell=function(t,e,n,r,i,o,a){var s=p(),h=!1;if(void 0!==s.ln)if(s.ln===o)t=s.x+s.w,e=s.y;else{var c=this.margins||F;s.y+s.h+r+13>=this.internal.pageSize.getHeight()-c.bottom&&(this.cellAddPage(),h=!0,this.printHeaders&&this.tableHeaderRow&&this.printHeaderRow(o,!0)),e=p().y+p().h,h&&(e=23)}if(void 0!==i[0])if(this.printingHeaderRow?this.rect(t,e,n,r,"FD"):this.rect(t,e,n,r),"right"===a){i instanceof Array||(i=[i]);for(var l=0;l=this.pageBreaks[r]){e++,0===this.lastBreak&&n++;var i=this.pageBreaks[r]-this.lastBreak;this.lastBreak=this.pageBreaks[r],n+=Math.floor(i/this.pageWrapY)}if(0===this.lastBreak)n+=Math.floor(t/this.pageWrapY)+1;return n+e}return this.pdf.internal.getCurrentPageInfo().pageNumber},_gotoPage:function(t){},lineTo:function(t,e){t=this._wrapX(t),e=this._wrapY(e);var n=this._matrix_map_point(this.ctx._transform,[t,e]),r={type:"lt",x:t=n[0],y:e=n[1]};this.path.push(r)},bezierCurveTo:function(t,e,n,r,i,o){var a;t=this._wrapX(t),e=this._wrapY(e),n=this._wrapX(n),r=this._wrapY(r),i=this._wrapX(i),o=this._wrapY(o),i=(a=this._matrix_map_point(this.ctx._transform,[i,o]))[0],o=a[1];var s={type:"bct",x1:t=(a=this._matrix_map_point(this.ctx._transform,[t,e]))[0],y1:e=a[1],x2:n=(a=this._matrix_map_point(this.ctx._transform,[n,r]))[0],y2:r=a[1],x:i,y:o};this.path.push(s)},quadraticCurveTo:function(t,e,n,r){var i;t=this._wrapX(t),e=this._wrapY(e),n=this._wrapX(n),r=this._wrapY(r),n=(i=this._matrix_map_point(this.ctx._transform,[n,r]))[0],r=i[1];var o={type:"qct",x1:t=(i=this._matrix_map_point(this.ctx._transform,[t,e]))[0],y1:e=i[1],x:n,y:r};this.path.push(o)},arc:function(t,e,n,r,i,o){if(t=this._wrapX(t),e=this._wrapY(e),!this._matrix_is_identity(this.ctx._transform)){var a=this._matrix_map_point(this.ctx._transform,[t,e]);t=a[0],e=a[1];var s=this._matrix_map_point(this.ctx._transform,[0,0]),h=this._matrix_map_point(this.ctx._transform,[0,n]);n=Math.sqrt(Math.pow(h[0]-s[0],2)+Math.pow(h[1]-s[1],2))}var c={type:"arc",x:t,y:e,radius:n,startAngle:r,endAngle:i,anticlockwise:o};this.path.push(c)},drawImage:function(t,e,n,r,i,o,a,s,h){void 0!==o&&(e=o,n=a,r=s,i=h),e=this._wrapX(e),n=this._wrapY(n);var c,l=this._matrix_map_rect(this.ctx._transform,{x:e,y:n,w:r,h:i}),u=(this._matrix_map_rect(this.ctx._transform,{x:o,y:a,w:s,h:h}),/data:image\/(\w+).*/i.exec(t));c=null!=u?u[1]:"png",this.pdf.addImage(t,c,l.x,l.y,l.w,l.h)},_matrix_multiply:function(t,e){var n=e[0],r=e[1],i=e[2],o=e[3],a=e[4],s=e[5],h=n*t[0]+r*t[2],c=i*t[0]+o*t[2],l=a*t[0]+s*t[2]+t[4];return r=n*t[1]+r*t[3],o=i*t[1]+o*t[3],s=a*t[1]+s*t[3]+t[5],[n=h,r,i=c,o,a=l,s]},_matrix_rotation:function(t){return Math.atan2(t[2],t[0])},_matrix_decompose:function(t){var e=t[0],n=t[1],r=t[2],i=t[3],o=Math.sqrt(e*e+n*n),a=(e/=o)*r+(n/=o)*i;r-=e*a,i-=n*a;var s=Math.sqrt(r*r+i*i);return a/=s,e*(i/=s)>"),s.push(">>");var h="MASK"+s.objId;this.pdf.internal.addGraphicsState(h,s.objId);var c="/"+h+" gs";n.splice(0,0,"q"),n.splice(1,0,c),n.push("Q"),window.outIntercept=a;break;default:var l="/"+this.pdf.internal.blendModeMap[this.ctx.globalCompositeOperation.toUpperCase()];l&&this.pdf.internal.out(l+" gs")}var u=this.ctx.globalAlpha;if(this.ctx._fillOpacity<1&&(u=this.ctx._fillOpacity),r){var f=this.pdf.internal.newObject2();f.push("<>");h="GS_O_"+f.objId;this.pdf.internal.addGraphicsState(h,f.objId),this.pdf.internal.out("/"+h+" gs")}for(var d=this.path,p=0;p>"),e.push(">>");var n="MASK"+e.objId;this.pdf.internal.addGraphicsState(n,e.objId);var r="/"+n+" gs";this.pdf.internal.out(r)}else console.log("jsPDF v2 not enabled")},clip:function(){if(0i.pdf.margins_doc.top&&(i.pdf.addPage(),i.y=i.pdf.margins_doc.top,i.executeWatchFunctions(n));var b=P(n),x=i.x,S=12/i.pdf.internal.scaleFactor,k=(b["margin-left"]+b["padding-left"])*S,_=(b["margin-right"]+b["padding-right"])*S,A=(b["margin-top"]+b["padding-top"])*S,I=(b["margin-bottom"]+b["padding-bottom"])*S;void 0!==b.float&&"right"===b.float?x+=i.settings.width-n.width-_:x+=k,i.pdf.addImage(y,x,i.y+A,n.width,n.height),y=void 0,"right"===b.float||"left"===b.float?(i.watchFunctions.push(function(t,e,n,r){return i.y>=e?(i.x+=t,i.settings.width+=n,!0):!!(r&&1===r.nodeType&&!B[r.nodeName]&&i.x+r.width>i.pdf.margins_doc.left+i.pdf.margins_doc.width)&&(i.x+=t,i.y=e,i.settings.width+=n,!0)}.bind(this,"left"===b.float?-n.width-k-_:0,i.y+n.height+A+I,n.width)),i.watchFunctions.push(function(t,e,n){return!(i.y]*?>/gi,""),c="jsPDFhtmlText"+Date.now().toString()+(1e3*Math.random()).toFixed(0),(h=document.createElement("div")).style.cssText="position: absolute !important;clip: rect(1px 1px 1px 1px); /* IE6, IE7 */clip: rect(1px, 1px, 1px, 1px);padding:0 !important;border:0 !important;height: 1px !important;width: 1px !important; top:auto;left:-100px;overflow: hidden;",h.innerHTML='