diff --git a/back/pom.xml b/back/pom.xml index c623d73..93f4b07 100644 --- a/back/pom.xml +++ b/back/pom.xml @@ -20,7 +20,7 @@ kangaroo-and-rabbit archidata - 0.3.3 + 0.3.7 diff --git a/back/src/org/kar/karideo/WebLauncher.java b/back/src/org/kar/karideo/WebLauncher.java index d40ee09..835bd7a 100755 --- a/back/src/org/kar/karideo/WebLauncher.java +++ b/back/src/org/kar/karideo/WebLauncher.java @@ -6,10 +6,15 @@ import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory; import org.glassfish.jersey.media.multipart.MultiPartFeature; import org.glassfish.jersey.server.ResourceConfig; import org.kar.karideo.api.*; +import org.kar.karideo.filter.KarideoAuthenticationFilter; +import org.kar.karideo.migration.Initialization; import org.kar.karideo.model.Media; import org.kar.karideo.model.Season; import org.kar.karideo.model.Series; import org.kar.karideo.model.Type; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.kar.archidata.GlobalConfiguration; import org.kar.archidata.SqlWrapper; import org.kar.archidata.UpdateJwtPublicKey; import org.kar.archidata.api.DataResource; @@ -18,9 +23,9 @@ import org.kar.archidata.catcher.FailExceptionCatcher; import org.kar.archidata.catcher.InputExceptionCatcher; import org.kar.archidata.catcher.SystemExceptionCatcher; import org.kar.archidata.db.DBConfig; -import org.kar.archidata.filter.AuthenticationFilter; import org.kar.archidata.filter.CORSFilter; import org.kar.archidata.filter.OptionFilter; +import org.kar.archidata.migration.MigrationEngine; import org.kar.archidata.model.Data; import org.kar.archidata.model.User; import org.kar.archidata.util.ConfigBaseVariable; @@ -30,30 +35,39 @@ import jakarta.ws.rs.core.UriBuilder; import java.net.URI; public class WebLauncher { + final static Logger LOGGER = LoggerFactory.getLogger(WebLauncher.class); public static DBConfig dbConfig; - private WebLauncher() { - } + public WebLauncher() { + ConfigBaseVariable.bdDatabase = "karideo"; + } private static URI getBaseURI() { return UriBuilder.fromUri(ConfigBaseVariable.getlocalAddress()).build(); } - - public static void main(String[] args) { - ConfigBaseVariable.bdDatabase = "karideo"; - try { - String out = ""; - out += SqlWrapper.createTable(Data.class); - out += SqlWrapper.createTable(Media.class); - out += SqlWrapper.createTable(Type.class); - out += SqlWrapper.createTable(Series.class); - out += SqlWrapper.createTable(Season.class); - out += SqlWrapper.createTable(User.class); - System.out.println(out); - } catch (Exception e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } + public void migrateDB() throws Exception { + WebLauncher.LOGGER.info("Create migration engine"); + MigrationEngine migrationEngine = new MigrationEngine(); + WebLauncher.LOGGER.info("Add initialization"); + migrationEngine.setInit(new Initialization()); + WebLauncher.LOGGER.info("Migrate the DB [START]"); + migrationEngine.migrate(GlobalConfiguration.dbConfig); + WebLauncher.LOGGER.info("Migrate the DB [STOP]"); + } + + public static void main(String[] args) throws Exception { + WebLauncher.LOGGER.info("[START] application wake UP"); + WebLauncher launcher = new WebLauncher(); + launcher.migrateDB(); + + launcher.process(); + WebLauncher.LOGGER.info("end-configure the server & wait finish process:"); + Thread.currentThread().join(); + WebLauncher.LOGGER.info("STOP the REST server:"); + } + + + public void process() throws InterruptedException { // =================================================================== // Configure resources @@ -61,28 +75,28 @@ public class WebLauncher { ResourceConfig rc = new ResourceConfig(); // add multi-part models .. - rc.register(new MultiPartFeature()); + rc.register(MultiPartFeature.class); // global authentication system - rc.register(new OptionFilter()); + rc.register(OptionFilter.class); // remove cors ==> all time called by an other system... - rc.register(new CORSFilter()); + rc.register(CORSFilter.class); // global authentication system - rc.registerClasses(AuthenticationFilter.class); + rc.register(KarideoAuthenticationFilter.class); // register exception catcher rc.register(InputExceptionCatcher.class); rc.register(SystemExceptionCatcher.class); rc.register(FailExceptionCatcher.class); rc.register(ExceptionCatcher.class); // add default resource: - rc.registerClasses(UserResource.class); - rc.registerClasses(SeriesResource.class); - rc.registerClasses(DataResource.class); - rc.registerClasses(SeasonResource.class); - rc.registerClasses(TypeResource.class); - rc.registerClasses(VideoResource.class); + rc.register(UserResource.class); + rc.register(SeriesResource.class); + rc.register(DataResource.class); + rc.register(SeasonResource.class); + rc.register(TypeResource.class); + rc.register(VideoResource.class); - rc.registerClasses(HealthCheck.class); - rc.registerClasses(Front.class); + rc.register(HealthCheck.class); + rc.register(Front.class); // add jackson to be discover when we are ins stand-alone server rc.register(JacksonFeature.class); diff --git a/back/src/org/kar/karideo/WebLauncherLocal.java b/back/src/org/kar/karideo/WebLauncherLocal.java index 08a936a..a867e08 100755 --- a/back/src/org/kar/karideo/WebLauncherLocal.java +++ b/back/src/org/kar/karideo/WebLauncherLocal.java @@ -2,15 +2,37 @@ package org.kar.karideo; import org.kar.archidata.util.ConfigBaseVariable; +import org.slf4j.LoggerFactory; +import org.slf4j.Logger; -public class WebLauncherLocal { +public class WebLauncherLocal extends WebLauncher { + final Logger logger = LoggerFactory.getLogger(WebLauncherLocal.class); private WebLauncherLocal() {} public static void main(String[] args) throws InterruptedException { + WebLauncherLocal launcher = new WebLauncherLocal(); + launcher.process(); + launcher.logger.info("end-configure the server & wait finish process:"); + Thread.currentThread().join(); + launcher.logger.info("STOP the REST server:"); + } + + @Override + public void process() throws InterruptedException { if (true) { // for local test: ConfigBaseVariable.apiAdress = "http://0.0.0.0:18080/karideo/api/"; } - WebLauncher.main(args); + try { + super.migrateDB(); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + while (true) { + logger.error("Migration fail ==> waiting intervention of administrator..."); + Thread.sleep(60*60*1000); + } + } + super.process(); } } diff --git a/back/src/org/kar/karideo/api/UserResource.java b/back/src/org/kar/karideo/api/UserResource.java index 426d430..79df68c 100755 --- a/back/src/org/kar/karideo/api/UserResource.java +++ b/back/src/org/kar/karideo/api/UserResource.java @@ -5,18 +5,34 @@ import org.kar.archidata.filter.GenericContext; import org.kar.archidata.model.User; import org.kar.karideo.model.UserKarideo; +import com.fasterxml.jackson.annotation.JsonInclude; + import org.kar.archidata.annotation.security.RolesAllowed; import jakarta.ws.rs.*; import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.SecurityContext; import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; @Path("/users") @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) public class UserResource { + final Logger logger = LoggerFactory.getLogger(UserResource.class); + @JsonInclude(JsonInclude.Include.NON_NULL) + public class UserOut { + public long id; + public String login; + public UserOut(long id, String login) { + super(); + this.id = id; + this.login = login; + } + + } public UserResource() { } @@ -42,7 +58,7 @@ public class UserResource { System.out.println("getUser " + userId); GenericContext gc = (GenericContext) sc.getUserPrincipal(); System.out.println("==================================================="); - System.out.println("== USER ? " + gc.user); + System.out.println("== USER ? " + gc.userByToken.name); System.out.println("==================================================="); try { return SqlWrapper.get(UserKarideo.class, userId); @@ -52,18 +68,15 @@ public class UserResource { } return null; } - - // curl http://localhost:9993/api/users/3 + @GET @Path("me") @RolesAllowed("USER") - public User getMe(@Context SecurityContext sc) { - System.out.println("getMe()"); + public UserOut getMe(@Context SecurityContext sc) { + logger.debug("getMe()"); GenericContext gc = (GenericContext) sc.getUserPrincipal(); - System.out.println("==================================================="); - System.out.println("== USER ? " + gc.user); - System.out.println("==================================================="); - return gc.user; + logger.debug("== USER ? {}", gc.userByToken); + return new UserOut(gc.userByToken.id, gc.userByToken.name); } } diff --git a/back/src/org/kar/karideo/filter/KarideoAuthenticationFilter.java b/back/src/org/kar/karideo/filter/KarideoAuthenticationFilter.java new file mode 100644 index 0000000..8d57271 --- /dev/null +++ b/back/src/org/kar/karideo/filter/KarideoAuthenticationFilter.java @@ -0,0 +1,23 @@ +package org.kar.karideo.filter; + +import org.kar.archidata.filter.AuthenticationFilter; + +import jakarta.ws.rs.Priorities; +import jakarta.ws.rs.ext.Provider; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jakarta.annotation.Priority; + +//@PreMatching +@Provider +@Priority(Priorities.AUTHENTICATION) +public class KarideoAuthenticationFilter extends AuthenticationFilter { + final Logger logger = LoggerFactory.getLogger(KarideoAuthenticationFilter.class); + + public KarideoAuthenticationFilter() { + super("karideo"); + } + +} diff --git a/back/src/org/kar/karideo/migration/Initialization.java b/back/src/org/kar/karideo/migration/Initialization.java new file mode 100644 index 0000000..e5b66fb --- /dev/null +++ b/back/src/org/kar/karideo/migration/Initialization.java @@ -0,0 +1,59 @@ +package org.kar.karideo.migration; + +import org.kar.archidata.migration.MigrationSqlStep; +import org.kar.archidata.model.Data; +import org.kar.archidata.model.User; +import org.kar.karideo.model.Media; +import org.kar.karideo.model.Season; +import org.kar.karideo.model.Series; +import org.kar.karideo.model.Type; + +public class Initialization extends MigrationSqlStep { + + public static final int KARSO_INITIALISATION_ID = 1; + + @Override + public String getName() { + return "Initialization"; + } + + public Initialization() throws Exception { + addClass(Data.class); + addClass(Media.class); + addClass(Type.class); + addClass(Series.class); + addClass(Season.class); + addClass(User.class); + + addAction(""" + INSERT INTO `type` (`id`, `name`, `description`) VALUES + (1, 'Documentary', 'Documentary (animals, space, earth...)'), + (2, 'Movie', 'Movie with real humans (film)'), + (3, 'Animation', 'Animation movies (film)'), + (4, 'Short movie', 'Small movies (less 2 minutes)'), + (5, 'TV show', 'TV show for old peoples'), + (6, 'Animation TV show', 'TV show for young peoples'), + (7, 'Theater', 'Theater play'), + (8, 'One man show', 'Recorded stand up'), + (9, 'Concert', 'Recorded concert'), + (10, 'Opera', 'Recorded opera'); + """); + // set start increment element to permit to add after default elements + addAction(""" + ALTER TABLE `data` AUTO_INCREMENT = 1000; + """); + addAction(""" + ALTER TABLE `media` AUTO_INCREMENT = 1000; + """); + addAction(""" + ALTER TABLE `type` AUTO_INCREMENT = 1000; + """); + addAction(""" + ALTER TABLE `series` AUTO_INCREMENT = 1000; + """); + addAction(""" + ALTER TABLE `season` AUTO_INCREMENT = 1000; + """); + } + +} diff --git a/front/package.json b/front/package.json index 95ab6f0..a4c5ce1 100644 --- a/front/package.json +++ b/front/package.json @@ -1,40 +1,41 @@ { - "name": "karideo", - "version": "0.0.0", - "license": "MIT", - "scripts": { - "all": "npm run build && npm run test", - "ng": "ng", - "start": "ng serve --configuration=develop --watch --port 4202", - "build": "ng build --prod", - "test": "ng test", - "lint": "ng lint", - "e2e": "ng e2e" - }, - "private": true, - "dependencies": { - "@angular/animations": "^14.2.10", - "@angular/cdk": "^14.2.7", - "@angular/common": "^14.2.10", - "@angular/compiler": "^14.2.10", - "@angular/core": "^14.2.10", - "@angular/forms": "^14.2.10", - "@angular/material": "^14.2.7", - "@angular/platform-browser": "^14.2.10", - "@angular/platform-browser-dynamic": "^14.2.10", - "@angular/router": "^14.2.10", - "rxjs": "^7.5.7", - "zone.js": "^0.12.0" - }, - "devDependencies": { - "@angular-devkit/build-angular": "^14.2.9", - "@angular-eslint/builder": "14.2.0", - "@angular-eslint/eslint-plugin": "14.2.0", - "@angular-eslint/eslint-plugin-template": "14.2.0", - "@angular-eslint/schematics": "14.2.0", - "@angular-eslint/template-parser": "14.2.0", - "@angular/cli": "^14.2.9", - "@angular/compiler-cli": "^14.2.10", - "@angular/language-service": "^14.2.10" - } + "name": "karideo", + "version": "0.0.0", + "license": "MPL-2", + "scripts": { + "all": "npm run build && npm run test", + "ng": "ng", + "start": "ng serve --configuration=develop --watch --port 4202", + "build": "ng build --prod", + "test": "ng test", + "lint": "ng lint", + "style": "prettier --write .", + "e2e": "ng e2e" + }, + "private": true, + "dependencies": { + "@angular/animations": "^14.2.10", + "@angular/cdk": "^14.2.7", + "@angular/common": "^14.2.10", + "@angular/compiler": "^14.2.10", + "@angular/core": "^14.2.10", + "@angular/forms": "^14.2.10", + "@angular/material": "^14.2.7", + "@angular/platform-browser": "^14.2.10", + "@angular/platform-browser-dynamic": "^14.2.10", + "@angular/router": "^14.2.10", + "rxjs": "^7.5.7", + "zone.js": "^0.12.0" + }, + "devDependencies": { + "@angular-devkit/build-angular": "^14.2.9", + "@angular-eslint/builder": "14.2.0", + "@angular-eslint/eslint-plugin": "14.2.0", + "@angular-eslint/eslint-plugin-template": "14.2.0", + "@angular-eslint/schematics": "14.2.0", + "@angular-eslint/template-parser": "14.2.0", + "@angular/cli": "^14.2.9", + "@angular/compiler-cli": "^14.2.10", + "@angular/language-service": "^14.2.10" + } } diff --git a/front/src/app/app.component.ts b/front/src/app/app.component.ts index f81a72c..0ce76ce 100644 --- a/front/src/app/app.component.ts +++ b/front/src/app/app.component.ts @@ -10,6 +10,7 @@ import { MenuItem, MenuPosition } from 'common/model'; import { UserService, SessionService, SSOService } from 'common/service'; import { isNullOrUndefined } from 'common/utils'; import { ArianeService } from './service'; +import { UserRoles222 } from 'common/service/session'; enum MenuEventType { SSO_LOGIN = "SSO_CALL_LOGIN", @@ -41,7 +42,7 @@ export class AppComponent implements OnInit { private sessionService: SessionService, private ssoService: SSOService, private arianeService: ArianeService) { - + } @@ -51,7 +52,7 @@ export class AppComponent implements OnInit { this.updateMainMenu(); let self = this; this.sessionService.change.subscribe((isConnected) => { - console.log(`receive event from session ...${ isConnected}`); + console.log(`receive event from session ...${isConnected}`); self.isConnected = isConnected; self.autoConnectedDone = true; self.updateMainMenu(); @@ -95,7 +96,7 @@ export class AppComponent implements OnInit { eventOnMenu(data: EventOnMenu): void { //console.log(`plopppppppppp ${JSON.stringify(this.route.snapshot.url)}`); //console.log(`Get event on menu: ${JSON.stringify(data, null, 4)}`); - switch(data.menu.otherData) { + switch (data.menu.otherData) { case MenuEventType.SSO_LOGIN: this.ssoService.requestSignIn(); break; @@ -180,7 +181,7 @@ export class AppComponent implements OnInit { icon: "add_circle", title: "Add media", navigateTo: "upload", - enable: this.sessionService.userAdmin === true, + enable: this.sessionService.hasRight(UserRoles222.admin), }, { position: MenuPosition.LEFT, icon: "settings", @@ -216,7 +217,7 @@ export class AppComponent implements OnInit { icon: "add_circle_outline", title: "Sign-up", callback: true, - model: this.signUpEnable?undefined:"disable", + model: this.signUpEnable ? undefined : "disable", otherData: MenuEventType.SSO_SIGNUP, }, { position: MenuPosition.RIGHT, diff --git a/front/src/app/app.module.ts b/front/src/app/app.module.ts index b5f89f5..b154c2c 100644 --- a/front/src/app/app.module.ts +++ b/front/src/app/app.module.ts @@ -24,25 +24,38 @@ import { PopInUploadProgress } from 'common/popin/upload-progress/upload-progres import { PopInDeleteConfirm } from 'common/popin/delete-confirm/delete-confirm'; import { AppComponent } from './app.component'; -import { HomeScene, HelpScene, TypeScene, SeriesScene, SeasonScene, VideoScene, SettingsScene, - VideoEditScene, SeasonEditScene, SeriesEditScene } from './scene'; +import { + HomeScene, HelpScene, TypeScene, SeriesScene, SeasonScene, VideoScene, SettingsScene, + VideoEditScene, SeasonEditScene, SeriesEditScene +} from './scene'; import { TypeService, DataService, SeriesService, SeasonService, VideoService, ArianeService } from './service'; import { BddService, CookiesService, HttpWrapperService, OnlyAdminGuard, OnlyUnregisteredGuardHome, OnlyUsersGuard, OnlyUsersGuardHome, PopInService, SessionService, SSOService, StorageService, UserService } from 'common/service'; import { ErrorViewerScene, ForbiddenScene, HomeOutScene, NotFound404Scene, SsoScene } from 'common/scene'; import { UploadScene } from './scene/upload/upload'; -import { ErrorComponent, TopMenuComponent } from 'common/component'; +import { AsyncActionStatusComponent, BurgerPropertyComponent, CheckboxComponent, EntryComponent, EntryNumberComponent, EntryValidatorComponent, ErrorComponent, ErrorMessageStateComponent, PasswordEntryComponent, RenderFormComponent, RenderSettingsComponent, SpinerComponent, TopMenuComponent } from 'common/component'; @NgModule({ declarations: [ - AppComponent, - TopMenuComponent, - UploadFileComponent, - ElementDataImageComponent, ElementTypeComponent, ElementSeriesComponent, ElementSeasonComponent, ElementVideoComponent, + + AppComponent, + TopMenuComponent, + UploadFileComponent, ErrorComponent, + PasswordEntryComponent, + EntryComponent, + EntryValidatorComponent, + SpinerComponent, + AsyncActionStatusComponent, + ErrorMessageStateComponent, + CheckboxComponent, + BurgerPropertyComponent, + RenderSettingsComponent, + RenderFormComponent, + EntryNumberComponent, PopInComponent, PopInCreateType, @@ -95,7 +108,7 @@ import { ErrorComponent, TopMenuComponent } from 'common/component'; OnlyUnregisteredGuardHome, ], exports: [ - AppComponent, + AppComponent, TopMenuComponent, UploadFileComponent, ErrorComponent, @@ -108,7 +121,7 @@ import { ErrorComponent, TopMenuComponent } from 'common/component'; PopInComponent, PopInUploadProgress, PopInDeleteConfirm, - ], + ], bootstrap: [ AppComponent ] diff --git a/front/src/common/component/async-action-status/async-status-component.html b/front/src/common/component/async-action-status/async-status-component.html new file mode 100644 index 0000000..0940770 --- /dev/null +++ b/front/src/common/component/async-action-status/async-status-component.html @@ -0,0 +1,3 @@ +
+ +
diff --git a/front/src/common/component/async-action-status/async-status-component.less b/front/src/common/component/async-action-status/async-status-component.less new file mode 100644 index 0000000..e69de29 diff --git a/front/src/common/component/async-action-status/async-status-component.spec.ts b/front/src/common/component/async-action-status/async-status-component.spec.ts new file mode 100644 index 0000000..0f9ce47 --- /dev/null +++ b/front/src/common/component/async-action-status/async-status-component.spec.ts @@ -0,0 +1,68 @@ +import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; +import { PasswordEntryComponent } from './async-status-component'; + +describe('PasswordEntryComponent global test', () => { + let component: PasswordEntryComponent; + let fixture: ComponentFixture; + let input: HTMLInputElement; + let button: HTMLButtonElement; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [PasswordEntryComponent], + }).compileComponents(); + fixture = TestBed.createComponent(PasswordEntryComponent); + component = fixture.componentInstance; + input = fixture.nativeElement.querySelector('div').querySelector('input'); + button = fixture.nativeElement.querySelector('div').querySelector('button'); + }); + + it('Test mode password (default)', () => { + fixture.detectChanges(); + expect(input.textContent).toEqual(''); + expect(button.textContent).toEqual('visibility_off'); + expect(input.type).toEqual('password'); + }); + + it('Test mode text', () => { + component.passwordVisibility = true; + fixture.detectChanges(); + expect(input.textContent).toEqual(''); + expect(button.textContent).toEqual('visibility'); + expect(input.type).toEqual('text'); + }); + + it('test click on hide button', fakeAsync(() => { + fixture.detectChanges(); + expect(component.passwordVisibility).toEqual(false); + button.click(); + tick(); + fixture.detectChanges(); + expect(component.passwordVisibility).toEqual(true); + expect(button.textContent).toEqual('visibility'); + expect(input.type).toEqual('text'); + button.click(); + tick(); + fixture.detectChanges(); + expect(component.passwordVisibility).toEqual(false); + expect(button.textContent).toEqual('visibility_off'); + expect(input.type).toEqual('password'); + })); + + it('Set password', fakeAsync(() => { + fixture.detectChanges(); + expect(component.passwordVisibility).toEqual(false); + let tmpData = 'My beautifull Password'; + input.value = tmpData; + input.dispatchEvent(new Event('input')); + fixture.detectChanges(); + expect(input.textContent).toEqual(tmpData); + expect(component.value).toEqual(tmpData); + tmpData = ''; + input.value = tmpData; + input.dispatchEvent(new Event('input')); + fixture.detectChanges(); + expect(input.textContent).toEqual(tmpData); + expect(component.value).toEqual(tmpData); + })); +}); diff --git a/front/src/common/component/async-action-status/async-status-component.ts b/front/src/common/component/async-action-status/async-status-component.ts new file mode 100644 index 0000000..00582f8 --- /dev/null +++ b/front/src/common/component/async-action-status/async-status-component.ts @@ -0,0 +1,37 @@ +/** @file + * @author Edouard DUPIN + * @copyright 2018, Edouard DUPIN, all right reserved + * @license PROPRIETARY (see license file) + */ +import { Component, Input } from '@angular/core'; + +export enum AsyncActionState { + IDLE = "idle", + LOADING = "loading", + DONE = "done", + FAIL = "fail", +} + +@Component({ + selector: 'app-async-status-component', + templateUrl: 'async-status-component.html', + styleUrls: ['async-status-component.less'], +}) +export class AsyncActionStatusComponent { + /// Value of the password + @Input() value: AsyncActionState = AsyncActionState.IDLE; + + public getImage(): string { + switch(this.value) { + case AsyncActionState.IDLE: + return ''; + case AsyncActionState.LOADING: + return 'assets/images/load.svg'; + case AsyncActionState.DONE: + return 'assets/images/validate.svg'; + case AsyncActionState.FAIL: + return 'assets/images/validate-not.svg'; + + } + } +} diff --git a/front/src/common/component/burger-property/burger-property.html b/front/src/common/component/burger-property/burger-property.html new file mode 100644 index 0000000..765d0fb --- /dev/null +++ b/front/src/common/component/burger-property/burger-property.html @@ -0,0 +1,13 @@ + +
+
+
+
+
+
+ +
+
+ +
+
\ No newline at end of file diff --git a/front/src/common/component/burger-property/burger-property.less b/front/src/common/component/burger-property/burger-property.less new file mode 100644 index 0000000..060015b --- /dev/null +++ b/front/src/common/component/burger-property/burger-property.less @@ -0,0 +1,78 @@ +.bmp-title { + width: 80%; + border: solid; + border-width: 1px; + margin: auto; + padding: 10px 20px; + + border-color: black; + border-radius: 10px 10px 0px 0px; + background-color: gray; + + .title { + font-size: 24px; + font-weight: bold; + line-height: 24px; + vertical-align: middle; + margin: 10px 0 10px 0; + text-align: Left; + } + .description { + font-size: 16px; + line-height: 16px; + vertical-align: middle; + margin: 10px 0 10px 0; + text-align: Left; + } +} + +.bmp-elements { + width: 80%; + border: solid; + border-width: 0px 1px; + margin: auto; + padding: 10px 20px; + + border-color: black; + border-radius: 0px; + background-color: lightgray; + vertical-align: middle; + text-align: Left; + /* + .elem-hr { + width: 100%; + border-color: black; + border: dashed; + border-width: 1px 0 0 0; + margin: 10px auto; + } + .elem-title { + font-size: 18px; + font-weight: bold; + margin: 0 10px 0 36px; + } + .elem-description { + font-size: 14px; + margin: 0 20px 10px 50px; + font-style: italic; + } + .elem-input { + font-size: 14px; + margin: 0 20px 10px 36px; + input { + width: 100%; + } + }*/ +} + +.bmp-validation { + width: 80%; + border: solid; + border-width: 1px; + margin: auto; + padding: 10px 20px; + + border-color: black; + border-radius: 0px 0px 10px 10px; + background-color: gray; +} diff --git a/front/src/common/component/burger-property/burger-property.ts b/front/src/common/component/burger-property/burger-property.ts new file mode 100644 index 0000000..db81e81 --- /dev/null +++ b/front/src/common/component/burger-property/burger-property.ts @@ -0,0 +1,18 @@ +/** @file + * @author Edouard DUPIN + * @copyright 2020, Edouard DUPIN, all right reserved + * @license MPL-2 (see license file) + */ + +import { Component } from '@angular/core'; + +@Component({ + // moduleId: module.id.toString(), + selector: 'burger-property', + templateUrl: './burger-property.html', + styleUrls: ['./burger-property.less'], +}) +export class BurgerPropertyComponent { + + constructor() {} +} diff --git a/front/src/common/component/checkbox/checkbox.html b/front/src/common/component/checkbox/checkbox.html new file mode 100644 index 0000000..c0b63f3 --- /dev/null +++ b/front/src/common/component/checkbox/checkbox.html @@ -0,0 +1,7 @@ +
+ + {{comment}} +
diff --git a/front/src/common/component/checkbox/checkbox.less b/front/src/common/component/checkbox/checkbox.less new file mode 100644 index 0000000..f9efe12 --- /dev/null +++ b/front/src/common/component/checkbox/checkbox.less @@ -0,0 +1,6 @@ + +.elem-checkbox { + font-size: 18px; + font-weight: bold; + margin: 0 10px 0 10px; +} \ No newline at end of file diff --git a/front/src/common/component/checkbox/checkbox.ts b/front/src/common/component/checkbox/checkbox.ts new file mode 100644 index 0000000..0877516 --- /dev/null +++ b/front/src/common/component/checkbox/checkbox.ts @@ -0,0 +1,29 @@ +/** @file + * @author Edouard DUPIN + * @copyright 2018, Edouard DUPIN, all right reserved + * @license PROPRIETARY (see license file) + */ +import { Component, EventEmitter, Input, Output } from '@angular/core'; + +@Component({ + selector: 'app-checkbox', + templateUrl: 'checkbox.html', + styleUrls: ['checkbox.less'], +}) +export class CheckboxComponent { + /// Checkbox value + @Input() value: boolean = false; + /// Description of the checkbox + @Input() comment: string = ""; + /// event when change the value of the password + @Output() changeValue: EventEmitter = new EventEmitter(); + + /** + * When input value change, need update the display and change the internal value. + * @param newValue New value set on the password + */ + onChange(newValue: boolean): void { + this.value = newValue; + this.changeValue.emit(this.value); + } +} diff --git a/front/src/common/component/entry-number/entry-number.html b/front/src/common/component/entry-number/entry-number.html new file mode 100644 index 0000000..50eab6e --- /dev/null +++ b/front/src/common/component/entry-number/entry-number.html @@ -0,0 +1,15 @@ +
+ + + +
diff --git a/front/src/common/component/entry-number/entry-number.less b/front/src/common/component/entry-number/entry-number.less new file mode 100644 index 0000000..30e3b2d --- /dev/null +++ b/front/src/common/component/entry-number/entry-number.less @@ -0,0 +1,39 @@ +input { + width: 100%; + padding: 12px 20px; + margin: 8px 0; + display: inline-block; + border: 1px solid #ccc; + box-sizing: border-box; + z-index: 5; +} +.eye-button { + margin-left: -44px; + margin-top: 15px; + float: right; + position: relative; + display: block; + border: none; + z-index: 15; + background: none; + padding: 4 1 13 1; + :hover { + background: none; + color: red; + } +} +.eye-button-2 { + margin-left: -70px; + margin-top: 12px; + float: right; + position: relative; + display: block; + border: none; + z-index: 15; + background: none; + padding: 4px 30px 4px 1px; + :hover { + background: none; + color: red; + } +} \ No newline at end of file diff --git a/front/src/common/component/entry-number/entry-number.ts b/front/src/common/component/entry-number/entry-number.ts new file mode 100644 index 0000000..06fd391 --- /dev/null +++ b/front/src/common/component/entry-number/entry-number.ts @@ -0,0 +1,70 @@ +/** @file + * @author Edouard DUPIN + * @copyright 2018, Edouard DUPIN, all right reserved + * @license PROPRIETARY (see license file) + */ +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { isNumeric } from 'common/utils'; + +@Component({ + selector: 'app-entry-number', + templateUrl: 'entry-number.html', + styleUrls: ['entry-number.less'], +}) +export class EntryNumberComponent implements OnInit { + /// Value of the password + @Input() value: string|undefined = ''; + /// Placeholder of the Value + @Input() placeholder: string|undefined = ''; + /// The element has an error + @Input() hasError: boolean = false; + /// event when change the value of the password + @Output() changeValue: EventEmitter = new EventEmitter(); + + public notANumber: boolean = false; + ngOnInit(): void { + //if (value) + } + + /** + * When input value change, need update the display and change the internal value. + * @param newValue New value set on the password + */ + onChangeValue(newValue: string): void { + if (newValue === "") { + this.value = undefined; + this.notANumber = false; + this.changeValue.emit(undefined); + return; + } + this.value = newValue; + this.notANumber = false; + if (!isNumeric(this.value)) { + this.notANumber = true; + } + const numValue = Number(this.value); + this.changeValue.emit(numValue); + } + onIncrement() { + this.notANumber = false; + let newValue = undefined; + if (!isNumeric(this.value)) { + newValue = 0; + } else { + newValue = Number(this.value) + 1; + } + this.value = "" + newValue; + this.changeValue.emit(newValue); + } + onDecrement() { + this.notANumber = false; + let newValue = undefined; + if (!isNumeric(this.value)) { + newValue = 0; + } else { + newValue = Number(this.value) - 1; + } + this.value = "" + newValue; + this.changeValue.emit(newValue); + } +} diff --git a/front/src/common/component/entry-validator/entry-validator.html b/front/src/common/component/entry-validator/entry-validator.html new file mode 100644 index 0000000..ead991a --- /dev/null +++ b/front/src/common/component/entry-validator/entry-validator.html @@ -0,0 +1,8 @@ +
+ + +
diff --git a/front/src/common/component/entry-validator/entry-validator.less b/front/src/common/component/entry-validator/entry-validator.less new file mode 100644 index 0000000..d3c122b --- /dev/null +++ b/front/src/common/component/entry-validator/entry-validator.less @@ -0,0 +1,9 @@ +input[type='text'] { + width: 100%; + padding: 12px 20px; + margin: 8px 0; + display: inline-block; + border: 1px solid #ccc; + box-sizing: border-box; + z-index: 5; +} diff --git a/front/src/common/component/entry-validator/entry-validator.ts b/front/src/common/component/entry-validator/entry-validator.ts new file mode 100644 index 0000000..e358fd5 --- /dev/null +++ b/front/src/common/component/entry-validator/entry-validator.ts @@ -0,0 +1,59 @@ +/** @file + * @author Edouard DUPIN + * @copyright 2018, Edouard DUPIN, all right reserved + * @license PROPRIETARY (see license file) + */ +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { isNullOrUndefined } from 'common/utils'; + +export type ReturnFunction = (a: boolean|string) => void; + +export type CheckerParameter = { + value: string, + result: ReturnFunction +} + +@Component({ + selector: 'app-entry-validator', + templateUrl: 'entry-validator.html', + styleUrls: ['entry-validator.less'], +}) +export class EntryValidatorComponent implements OnInit { + /// Value of the password + @Input() value: string = ''; + /// Placeholder of the Value + @Input() placeholder: string = ''; + /// The element has an error + @Output() checker: EventEmitter = new EventEmitter(); + /// event when change the value of the password + @Output() changeValue: EventEmitter = new EventEmitter(); + + + public state: boolean|string = false; + + ngOnInit(): void { + //if (value) + } + + updateStatus(value: boolean|string): void { + this.state = value + if (this.state === true) { + this.changeValue.emit(this.value); + } + } + + check(newValue: string): void { + let self = this; + let lambdaCallBack = (value: boolean|string) => {self.updateStatus(value)}; + this.value = newValue; + if (isNullOrUndefined(this.checker)) { + this.changeValue.emit(this.value); + return; + } + this.checker.emit({ + value: newValue, + result: lambdaCallBack, + }); + } +} + diff --git a/front/src/common/component/entry/entry.html b/front/src/common/component/entry/entry.html new file mode 100644 index 0000000..4b2a2e4 --- /dev/null +++ b/front/src/common/component/entry/entry.html @@ -0,0 +1,9 @@ +
+ +
diff --git a/front/src/common/component/entry/entry.less b/front/src/common/component/entry/entry.less new file mode 100644 index 0000000..d3c122b --- /dev/null +++ b/front/src/common/component/entry/entry.less @@ -0,0 +1,9 @@ +input[type='text'] { + width: 100%; + padding: 12px 20px; + margin: 8px 0; + display: inline-block; + border: 1px solid #ccc; + box-sizing: border-box; + z-index: 5; +} diff --git a/front/src/common/component/entry/entry.spec.ts b/front/src/common/component/entry/entry.spec.ts new file mode 100644 index 0000000..7276a8b --- /dev/null +++ b/front/src/common/component/entry/entry.spec.ts @@ -0,0 +1,68 @@ +import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; +import { PasswordEntryComponent } from './entry'; + +describe('PasswordEntryComponent global test', () => { + let component: PasswordEntryComponent; + let fixture: ComponentFixture; + let input: HTMLInputElement; + let button: HTMLButtonElement; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [PasswordEntryComponent], + }).compileComponents(); + fixture = TestBed.createComponent(PasswordEntryComponent); + component = fixture.componentInstance; + input = fixture.nativeElement.querySelector('div').querySelector('input'); + button = fixture.nativeElement.querySelector('div').querySelector('button'); + }); + + it('Test mode password (default)', () => { + fixture.detectChanges(); + expect(input.textContent).toEqual(''); + expect(button.textContent).toEqual('visibility_off'); + expect(input.type).toEqual('password'); + }); + + it('Test mode text', () => { + component.passwordVisibility = true; + fixture.detectChanges(); + expect(input.textContent).toEqual(''); + expect(button.textContent).toEqual('visibility'); + expect(input.type).toEqual('text'); + }); + + it('test click on hide button', fakeAsync(() => { + fixture.detectChanges(); + expect(component.passwordVisibility).toEqual(false); + button.click(); + tick(); + fixture.detectChanges(); + expect(component.passwordVisibility).toEqual(true); + expect(button.textContent).toEqual('visibility'); + expect(input.type).toEqual('text'); + button.click(); + tick(); + fixture.detectChanges(); + expect(component.passwordVisibility).toEqual(false); + expect(button.textContent).toEqual('visibility_off'); + expect(input.type).toEqual('password'); + })); + + it('Set password', fakeAsync(() => { + fixture.detectChanges(); + expect(component.passwordVisibility).toEqual(false); + let tmpData = 'My beautifull Password'; + input.value = tmpData; + input.dispatchEvent(new Event('input')); + fixture.detectChanges(); + expect(input.textContent).toEqual(tmpData); + expect(component.value).toEqual(tmpData); + tmpData = ''; + input.value = tmpData; + input.dispatchEvent(new Event('input')); + fixture.detectChanges(); + expect(input.textContent).toEqual(tmpData); + expect(component.value).toEqual(tmpData); + })); +}); diff --git a/front/src/common/component/entry/entry.ts b/front/src/common/component/entry/entry.ts new file mode 100644 index 0000000..1425eff --- /dev/null +++ b/front/src/common/component/entry/entry.ts @@ -0,0 +1,35 @@ +/** @file + * @author Edouard DUPIN + * @copyright 2018, Edouard DUPIN, all right reserved + * @license PROPRIETARY (see license file) + */ +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; + +@Component({ + selector: 'app-entry', + templateUrl: 'entry.html', + styleUrls: ['entry.less'], +}) +export class EntryComponent implements OnInit { + /// Value of the password + @Input() value: string|undefined = ''; + /// Placeholder of the Value + @Input() placeholder: string|undefined = ''; + /// The element has an error + @Input() hasError: boolean = false; + /// event when change the value of the password + @Output() changeValue: EventEmitter = new EventEmitter(); + + ngOnInit(): void { + //if (value) + } + + /** + * When input value change, need update the display and change the internal value. + * @param newValue New value set on the password + */ + onChangeValue(newValue: string): void { + this.value = newValue; + this.changeValue.emit(this.value); + } +} diff --git a/front/src/common/component/error-message-state/error-message-state.html b/front/src/common/component/error-message-state/error-message-state.html new file mode 100644 index 0000000..5ded0f7 --- /dev/null +++ b/front/src/common/component/error-message-state/error-message-state.html @@ -0,0 +1 @@ +
{{value}}
\ No newline at end of file diff --git a/front/src/common/component/error-message-state/error-message-state.less b/front/src/common/component/error-message-state/error-message-state.less new file mode 100644 index 0000000..c9b427a --- /dev/null +++ b/front/src/common/component/error-message-state/error-message-state.less @@ -0,0 +1,34 @@ + +.error { + background-color: #f44336; + position: absolute; + z-index: 10; + display: block; + max-width: 450px; + padding: 5px 8px; + margin: 2px 0 0; + font-size: 16px; + font-weight: 400; + border-style: solid; + border-width: 0px; + box-sizing: border-box; + + &:after, + &:before { + bottom: 100%; + left: 25px; + border: solid transparent; + content: ' '; + height: 0; + width: 0; + position: absolute; + pointer-events: none; + } + + &:after { + border-bottom-color: #f44336; + border-width: 10px; + margin-left: -10px; + } +} + diff --git a/front/src/common/component/error-message-state/error-message-state.ts b/front/src/common/component/error-message-state/error-message-state.ts new file mode 100644 index 0000000..6a4f7e3 --- /dev/null +++ b/front/src/common/component/error-message-state/error-message-state.ts @@ -0,0 +1,17 @@ +/** @file + * @author Edouard DUPIN + * @copyright 2018, Edouard DUPIN, all right reserved + * @license PROPRIETARY (see license file) + */ +import { Component, Input } from '@angular/core'; + +@Component({ + selector: 'app-error-message-state', + templateUrl: 'error-message-state.html', + styleUrls: ['error-message-state.less'], +}) +export class ErrorMessageStateComponent { + /// Value of the password + @Input() value: boolean|string = false; + +} diff --git a/front/src/common/component/error/error.html b/front/src/common/component/error/error.html index e2028a6..27d6bed 100644 --- a/front/src/common/component/error/error.html +++ b/front/src/common/component/error/error.html @@ -1,3 +1 @@ -

- error works! -

+

error works!

diff --git a/front/src/common/component/error/error.ts b/front/src/common/component/error/error.ts index 6be37ac..75901eb 100644 --- a/front/src/common/component/error/error.ts +++ b/front/src/common/component/error/error.ts @@ -9,11 +9,10 @@ import { Component, OnInit } from '@angular/core'; @Component({ selector: 'app-error', templateUrl: './error.html', - styleUrls: [ './error.less' ] + styleUrls: ['./error.less'], }) export class ErrorComponent implements OnInit { - constructor() { } + constructor() {} - ngOnInit() { - } + ngOnInit() {} } diff --git a/front/src/common/component/index.ts b/front/src/common/component/index.ts index c207617..31ee500 100644 --- a/front/src/common/component/index.ts +++ b/front/src/common/component/index.ts @@ -1,14 +1,17 @@ -import { ErrorComponent } from "./error/error"; -import { PopInComponent } from "./popin/popin"; -import { TopMenuComponent } from "./top-menu/top-menu"; -import { UploadFileComponent } from "./upload-file/upload-file"; - - -export { - PopInComponent, - TopMenuComponent, - UploadFileComponent, - ErrorComponent, -}; - +import { AsyncActionState, AsyncActionStatusComponent } from './async-action-status/async-status-component'; +import { BurgerPropertyComponent } from './burger-property/burger-property'; +import { CheckboxComponent } from './checkbox/checkbox'; +import { EntryNumberComponent } from './entry-number/entry-number'; +import { EntryValidatorComponent } from './entry-validator/entry-validator'; +import { EntryComponent } from './entry/entry'; +import { ErrorMessageStateComponent } from './error-message-state/error-message-state'; +import { ErrorComponent } from './error/error'; +import { PasswordEntryComponent } from './password-entry/password-entry'; +import { PopInComponent } from './popin/popin'; +import { RenderFormComponent } from './render-settings/render-form'; +import { RenderSettingsComponent } from './render-settings/render-settings'; +import { SpinerComponent } from './spiner/spiner'; +import { TopMenuComponent } from './top-menu/top-menu'; +import { UploadFileComponent } from './upload-file/upload-file'; +export { BurgerPropertyComponent, EntryNumberComponent, CheckboxComponent, RenderFormComponent, RenderSettingsComponent, ErrorMessageStateComponent, AsyncActionState, AsyncActionStatusComponent, EntryValidatorComponent, PopInComponent, TopMenuComponent, UploadFileComponent, ErrorComponent, SpinerComponent, PasswordEntryComponent, EntryComponent }; diff --git a/front/src/common/component/password-entry/password-entry.html b/front/src/common/component/password-entry/password-entry.html new file mode 100644 index 0000000..c7c41a5 --- /dev/null +++ b/front/src/common/component/password-entry/password-entry.html @@ -0,0 +1,12 @@ +
+ + +
diff --git a/front/src/common/component/password-entry/password-entry.less b/front/src/common/component/password-entry/password-entry.less new file mode 100644 index 0000000..9f54357 --- /dev/null +++ b/front/src/common/component/password-entry/password-entry.less @@ -0,0 +1,25 @@ +.eye-button { + margin-left: -44px; + margin-top: 15px; + float: right; + position: relative; + display: block; + border: none; + z-index: 15; + background: none; + padding: 4 1 13 1; + :hover { + background: none; + } +} + +input[type='text'], +input[type='password'] { + width: 100%; + padding: 12px 20px; + margin: 8px 0; + display: inline-block; + border: 1px solid #ccc; + box-sizing: border-box; + z-index: 5; +} diff --git a/front/src/common/component/password-entry/password-entry.spec.ts b/front/src/common/component/password-entry/password-entry.spec.ts new file mode 100644 index 0000000..975c381 --- /dev/null +++ b/front/src/common/component/password-entry/password-entry.spec.ts @@ -0,0 +1,68 @@ +import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; +import { PasswordEntryComponent } from './password-entry'; + +describe('PasswordEntryComponent global test', () => { + let component: PasswordEntryComponent; + let fixture: ComponentFixture; + let input: HTMLInputElement; + let button: HTMLButtonElement; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [PasswordEntryComponent], + }).compileComponents(); + fixture = TestBed.createComponent(PasswordEntryComponent); + component = fixture.componentInstance; + input = fixture.nativeElement.querySelector('div').querySelector('input'); + button = fixture.nativeElement.querySelector('div').querySelector('button'); + }); + + it('Test mode password (default)', () => { + fixture.detectChanges(); + expect(input.textContent).toEqual(''); + expect(button.textContent).toEqual('visibility_off'); + expect(input.type).toEqual('password'); + }); + + it('Test mode text', () => { + component.passwordVisibility = true; + fixture.detectChanges(); + expect(input.textContent).toEqual(''); + expect(button.textContent).toEqual('visibility'); + expect(input.type).toEqual('text'); + }); + + it('test click on hide button', fakeAsync(() => { + fixture.detectChanges(); + expect(component.passwordVisibility).toEqual(false); + button.click(); + tick(); + fixture.detectChanges(); + expect(component.passwordVisibility).toEqual(true); + expect(button.textContent).toEqual('visibility'); + expect(input.type).toEqual('text'); + button.click(); + tick(); + fixture.detectChanges(); + expect(component.passwordVisibility).toEqual(false); + expect(button.textContent).toEqual('visibility_off'); + expect(input.type).toEqual('password'); + })); + + it('Set password', fakeAsync(() => { + fixture.detectChanges(); + expect(component.passwordVisibility).toEqual(false); + let tmpData = 'My beautifull Password'; + input.value = tmpData; + input.dispatchEvent(new Event('input')); + fixture.detectChanges(); + expect(input.textContent).toEqual(tmpData); + expect(component.value).toEqual(tmpData); + tmpData = ''; + input.value = tmpData; + input.dispatchEvent(new Event('input')); + fixture.detectChanges(); + expect(input.textContent).toEqual(tmpData); + expect(component.value).toEqual(tmpData); + })); +}); diff --git a/front/src/common/component/password-entry/password-entry.ts b/front/src/common/component/password-entry/password-entry.ts new file mode 100644 index 0000000..68218a7 --- /dev/null +++ b/front/src/common/component/password-entry/password-entry.ts @@ -0,0 +1,39 @@ +/** @file + * @author Edouard DUPIN + * @copyright 2018, Edouard DUPIN, all right reserved + * @license PROPRIETARY (see license file) + */ +import { Component, EventEmitter, Input, Output } from '@angular/core'; + +@Component({ + selector: 'app-password-entry', + templateUrl: 'password-entry.html', + styleUrls: ['password-entry.less'], +}) +export class PasswordEntryComponent { + /// Value of the password + @Input() value: string = ''; + /// Placeholder of the Value + @Input() placeholder: string = 'Write password.'; + /// The element has an error + @Input() hasError: boolean = false; + /// event when change the value of the password + @Output() changeValue: EventEmitter = new EventEmitter(); + /// Local value of the password viwibility + public passwordVisibility: boolean = false; + /** + * Ov visibility request change (toggle the visibility) + */ + onVisibility(): void { + this.passwordVisibility = !this.passwordVisibility; + } + + /** + * When input value change, need update the display and change the internal value. + * @param newValue New value set on the password + */ + onChangeValue(newValue: string): void { + this.value = newValue; + this.changeValue.emit(this.value); + } +} diff --git a/front/src/common/component/popin/popin.html b/front/src/common/component/popin/popin.html index 90b8caa..9bc2b22 100644 --- a/front/src/common/component/popin/popin.html +++ b/front/src/common/component/popin/popin.html @@ -2,7 +2,7 @@
-
+
@@ -12,22 +12,22 @@