diff --git a/back/pom.xml b/back/pom.xml index f56be92..6e4ea33 100644 --- a/back/pom.xml +++ b/back/pom.xml @@ -22,7 +22,13 @@ kangaroo-and-rabbit archidata - 0.3.3 + 0.3.7 + + + org.slf4j + slf4j-simple + 2.0.7 + @@ -30,6 +36,11 @@ src test/src ${project.basedir}/out/maven/ + + + src/resources + + org.apache.maven.plugins diff --git a/back/src/org/kar/karusic/WebLauncher.java b/back/src/org/kar/karusic/WebLauncher.java index 1281c14..301cb24 100755 --- a/back/src/org/kar/karusic/WebLauncher.java +++ b/back/src/org/kar/karusic/WebLauncher.java @@ -17,77 +17,91 @@ import org.kar.karusic.api.HealthCheck; import org.kar.karusic.api.PlaylistResource; import org.kar.karusic.api.TrackResource; import org.kar.karusic.api.UserResource; +import org.kar.karusic.filter.KarusicAuthenticationFilter; +import org.kar.karusic.migration.Initialization; import org.kar.archidata.GlobalConfiguration; -import org.kar.archidata.SqlWrapper; import org.kar.archidata.UpdateJwtPublicKey; import org.kar.archidata.api.DataResource; import org.kar.archidata.catcher.ExceptionCatcher; import org.kar.archidata.catcher.FailExceptionCatcher; import org.kar.archidata.catcher.InputExceptionCatcher; import org.kar.archidata.catcher.SystemExceptionCatcher; -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.util.ConfigBaseVariable; -import org.kar.karusic.model.Track; +import org.slf4j.LoggerFactory; +import org.slf4j.Logger; public class WebLauncher { - private WebLauncher() { - } + final static Logger LOGGER = LoggerFactory.getLogger(WebLauncher.class); + protected UpdateJwtPublicKey keyUpdater = null; + public WebLauncher() { + ConfigBaseVariable.bdDatabase = "karusic"; + } private static URI getBaseURI() { return UriBuilder.fromUri(ConfigBaseVariable.getlocalAddress()).build(); } - public static void main(String[] args) { - ConfigBaseVariable.bdDatabase = "karusic"; + 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("Add migration since last version"); + // NOTHING for now + WebLauncher.LOGGER.info("Migrate the DB [START]"); + migrationEngine.migrate(GlobalConfiguration.dbConfig); + WebLauncher.LOGGER.info("Migrate the DB [STOP]"); + } - // generate the BDD: - try { - String out = ""; - //out += SqlWrapper.createTable(User.class); - out += SqlWrapper.createTable(Track.class); -// out += SqlWrapper.createTable(Artist.class); -// out += SqlWrapper.createTable(Gender.class); -// out += SqlWrapper.createTable(Playlist.class); -// out += SqlWrapper.createTable(Album.class); - System.out.println(out); - } catch (Exception e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } + 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 Key updater"); + launcher.stopOther(); + WebLauncher.LOGGER.info("STOP the REST server:"); + } + + + public void process() throws InterruptedException { // =================================================================== // Configure resources // =================================================================== ResourceConfig rc = new ResourceConfig(); // add multipart 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(KarusicAuthenticationFilter.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(AlbumResource.class); - rc.registerClasses(ArtistResource.class); - rc.registerClasses(GenderResource.class); - rc.registerClasses(PlaylistResource.class); - rc.registerClasses(TrackResource.class); - rc.registerClasses(DataResource.class); + rc.register(UserResource.class); + rc.register(AlbumResource.class); + rc.register(ArtistResource.class); + rc.register(GenderResource.class); + rc.register(PlaylistResource.class); + rc.register(TrackResource.class); + rc.register(DataResource.class); - rc.registerClasses(HealthCheck.class); - rc.registerClasses(Front.class); + rc.register(HealthCheck.class); + rc.register(Front.class); - // add jackson to be discovenr when we are ins standalone server + // add jackson to be discover when we are ins standalone server rc.register(JacksonFeature.class); // enable this to show low level request //rc.property(LoggingFeature.LOGGING_FEATURE_LOGGER_LEVEL_SERVER, Level.WARNING.getName()); @@ -112,20 +126,22 @@ public class WebLauncher { // =================================================================== // start periodic update of the token ... // =================================================================== - UpdateJwtPublicKey keyUpdater = new UpdateJwtPublicKey(); - keyUpdater.start(); + this.keyUpdater = new UpdateJwtPublicKey(); + this.keyUpdater.start(); // =================================================================== // run JERSEY // =================================================================== try { server.start(); - System.out.println("Jersey app started at " + getBaseURI()); - Thread.currentThread().join(); + LOGGER.info("Jersey app started at {}", getBaseURI()); } catch (Exception e) { - System.out.println("There was an error while starting Grizzly HTTP server."); + LOGGER.error("There was an error while starting Grizzly HTTP server."); e.printStackTrace(); } + } + + public void stopOther() { keyUpdater.kill(); try { keyUpdater.join(4000, 0); diff --git a/back/src/org/kar/karusic/WebLauncherLocal.java b/back/src/org/kar/karusic/WebLauncherLocal.java index 2b624c9..1c80303 100755 --- a/back/src/org/kar/karusic/WebLauncherLocal.java +++ b/back/src/org/kar/karusic/WebLauncherLocal.java @@ -1,17 +1,39 @@ package org.kar.karusic; 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:19080/karusic/api/"; - //ConfigBaseVariable.ssoAdress = "http://localhost:15080/karso/api/"; ConfigBaseVariable.ssoAdress = "https://atria-soft.org/karso/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/karusic/api/UserResource.java b/back/src/org/kar/karusic/api/UserResource.java index d37ef96..9ba0285 100755 --- a/back/src/org/kar/karusic/api/UserResource.java +++ b/back/src/org/kar/karusic/api/UserResource.java @@ -4,6 +4,10 @@ import org.kar.archidata.SqlWrapper; import org.kar.archidata.filter.GenericContext; import org.kar.archidata.model.User; import org.kar.karusic.model.UserKarusic; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.annotation.JsonInclude; import org.kar.archidata.annotation.security.RolesAllowed; import jakarta.ws.rs.*; @@ -16,6 +20,19 @@ import java.util.List; @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 +59,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(UserKarusic.class, userId); @@ -53,19 +70,16 @@ 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/karusic/filter/KarusicAuthenticationFilter.java b/back/src/org/kar/karusic/filter/KarusicAuthenticationFilter.java new file mode 100644 index 0000000..52d4843 --- /dev/null +++ b/back/src/org/kar/karusic/filter/KarusicAuthenticationFilter.java @@ -0,0 +1,23 @@ +package org.kar.karusic.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 KarusicAuthenticationFilter extends AuthenticationFilter { + final Logger logger = LoggerFactory.getLogger(KarusicAuthenticationFilter.class); + + public KarusicAuthenticationFilter() { + super("karusic"); + } + +} diff --git a/back/src/org/kar/karusic/migration/Initialization.java b/back/src/org/kar/karusic/migration/Initialization.java new file mode 100644 index 0000000..7798b92 --- /dev/null +++ b/back/src/org/kar/karusic/migration/Initialization.java @@ -0,0 +1,83 @@ +package org.kar.karusic.migration; + +import org.kar.archidata.migration.MigrationSqlStep; +import org.kar.archidata.model.Data; +import org.kar.archidata.model.User; +import org.kar.karusic.model.Album; +import org.kar.karusic.model.Artist; +import org.kar.karusic.model.Gender; +import org.kar.karusic.model.Playlist; +import org.kar.karusic.model.Track; + +public class Initialization extends MigrationSqlStep { + + public static final int KARSO_INITIALISATION_ID = 1; + + @Override + public String getName() { + return "Initialization"; + } + + public Initialization() throws Exception { + addClass(Album.class); + addClass(Artist.class); + addClass(Data.class); + addClass(Gender.class); + addClass(Playlist.class); + addClass(Track.class); + addClass(User.class); + + addAction(""" + INSERT INTO `gender` (`id`, `name`, `description`) VALUES + (1, 'Variété française', NULL), + (2, 'Pop', NULL), + (3, 'inconnue', NULL), + (4, 'Disco', NULL), + (5, 'Enfants', NULL), + (6, 'Portugaise', NULL), + (7, 'Apprentissage', NULL), + (8, 'Blues', NULL), + (9, 'Jazz', NULL), + (10, 'Chanson Noël', NULL), + (11, 'DubStep', NULL), + (12, 'Rap français', NULL), + (13, 'Classique', NULL), + (14, 'Rock', NULL), + (15, 'Electro', NULL), + (16, 'Celtique', NULL), + (17, 'Country', NULL), + (18, 'Variété Québéquoise', NULL), + (19, 'Médiéval', NULL), + (20, 'Variété Italienne', NULL), + (21, 'Comédie Musicale', NULL), + (22, 'Vianney', NULL), + (23, 'Bande Original', NULL), + (24, 'Bande Originale', NULL), + (25, 'Variété Belge', NULL), + (26, 'Gospel', NULL); + """); + // set start increment element to permit to add after default elements + addAction(""" + ALTER TABLE `album` AUTO_INCREMENT = 1000; + """); + addAction(""" + ALTER TABLE `artist` AUTO_INCREMENT = 1000; + """); + addAction(""" + ALTER TABLE `data` AUTO_INCREMENT = 1000; + """); + addAction(""" + ALTER TABLE `gender` AUTO_INCREMENT = 1000; + """); + addAction(""" + ALTER TABLE `playlist` AUTO_INCREMENT = 1000; + """); + addAction(""" + ALTER TABLE `track` AUTO_INCREMENT = 1000; + """); + addAction(""" + ALTER TABLE `user` AUTO_INCREMENT = 1000; + """); + } + +} diff --git a/back/src/resources/simplelogger.properties b/back/src/resources/simplelogger.properties new file mode 100644 index 0000000..4314b58 --- /dev/null +++ b/back/src/resources/simplelogger.properties @@ -0,0 +1,35 @@ +# SLF4J's SimpleLogger configuration file +# Simple implementation of Logger that sends all enabled log messages, for all defined loggers, to System.err. +# Default logging detail level for all instances of SimpleLogger. +# Must be one of ("trace", "debug", "info", "warn", or "error"). +# If not specified, defaults to "info". +org.slf4j.simpleLogger.defaultLogLevel=trace + +# Logging detail level for a SimpleLogger instance named "xxxxx". +# Must be one of ("trace", "debug", "info", "warn", or "error"). +# If not specified, the default logging detail level is used. +#org.slf4j.simpleLogger.log.xxxxx= + +# Set to true if you want the current date and time to be included in output messages. +# Default is false, and will output the number of milliseconds elapsed since startup. +#org.slf4j.simpleLogger.showDateTime=false + +# The date and time format to be used in the output messages. +# The pattern describing the date and time format is the same that is used in java.text.SimpleDateFormat. +# If the format is not specified or is invalid, the default format is used. +# The default format is yyyy-MM-dd HH:mm:ss:SSS Z. +#org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd HH:mm:ss:SSS Z + +# Set to true if you want to output the current thread name. +# Defaults to true. +org.slf4j.simpleLogger.showThreadName=true + +# Set to true if you want the Logger instance name to be included in output messages. +# Defaults to true. +#org.slf4j.simpleLogger.showLogName=true + +# Set to true if you want the last component of the name to be included in output messages. +# Defaults to false. +#org.slf4j.simpleLogger.showShortLogName=false + + diff --git a/front/src/app/app.component.ts b/front/src/app/app.component.ts index cff7a30..37e139c 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", @@ -220,7 +221,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", diff --git a/front/src/app/app.module.ts b/front/src/app/app.module.ts index 7b5dd93..9d50d62 100644 --- a/front/src/app/app.module.ts +++ b/front/src/app/app.module.ts @@ -11,7 +11,7 @@ import { HttpClientModule } from '@angular/common/http'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; // this is needed for dynamic selection of the select import { AppRoutingModule } from './app-routing.module'; -import { ErrorComponent, PopInComponent, TopMenuComponent, UploadFileComponent } from 'common/component/'; +import { AsyncActionStatusComponent, BurgerPropertyComponent, CheckboxComponent, EntryComponent, EntryNumberComponent, EntryValidatorComponent, ErrorComponent, ErrorMessageStateComponent, PasswordEntryComponent, PopInComponent, RenderFormComponent, RenderSettingsComponent, SpinerComponent, TopMenuComponent, UploadFileComponent } from 'common/component/'; import { ElementDataImageComponent } from './component/data-image/data-image'; import { ElementTypeComponent } from './component/element-type/element-type'; @@ -19,8 +19,10 @@ import { PopInCreateType } from './popin/create-type/create-type'; import { PopInDeleteConfirm, PopInUploadProgress } from 'common/popin'; import { AppComponent } from './app.component'; -import { HomeScene, HelpScene, GenderScene, PlaylistScene, ArtistScene, AlbumScene, AlbumsScene, TrackScene, SettingsScene, - TrackEditScene, AlbumEditScene, ArtistEditScene, ArtistsScene, ArtistAlbumScene } from './scene'; +import { + HomeScene, HelpScene, GenderScene, PlaylistScene, ArtistScene, AlbumScene, AlbumsScene, TrackScene, SettingsScene, + TrackEditScene, AlbumEditScene, ArtistEditScene, ArtistsScene, ArtistAlbumScene +} from './scene'; import { GenderService, DataService, PlaylistService, ArtistService, AlbumService, TrackService, ArianeService, PlayerService } 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'; @@ -40,6 +42,17 @@ import { ElementSeriesComponent, ElementTrackComponent, ElementSeasonComponent, ElementVideoComponent, ElementPlayerAudioComponent, ErrorComponent, + PasswordEntryComponent, + EntryComponent, + EntryValidatorComponent, + SpinerComponent, + AsyncActionStatusComponent, + ErrorMessageStateComponent, + CheckboxComponent, + BurgerPropertyComponent, + RenderSettingsComponent, + RenderFormComponent, + EntryNumberComponent, PopInComponent, PopInCreateType, @@ -99,7 +112,7 @@ import { ElementSeriesComponent, ElementTrackComponent, ElementSeasonComponent, OnlyUnregisteredGuardHome, ], exports: [ - AppComponent, + AppComponent, TopMenuComponent, UploadFileComponent, ErrorComponent, @@ -112,13 +125,15 @@ import { ElementSeriesComponent, ElementTrackComponent, ElementSeasonComponent, PopInComponent, PopInUploadProgress, PopInDeleteConfirm, - ], + ], bootstrap: [ AppComponent ], + /* schemas: [ CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA ] + */ }) export class AppModule { } diff --git a/front/src/assets/images/block.svg b/front/src/assets/images/block.svg new file mode 100644 index 0000000..62df84f --- /dev/null +++ b/front/src/assets/images/block.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/front/src/assets/images/dashboard.svg b/front/src/assets/images/dashboard.svg new file mode 100644 index 0000000..6a3e10f --- /dev/null +++ b/front/src/assets/images/dashboard.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/front/src/assets/images/group.svg b/front/src/assets/images/group.svg new file mode 100644 index 0000000..15a0edd --- /dev/null +++ b/front/src/assets/images/group.svg @@ -0,0 +1,38 @@ + + + + + + diff --git a/front/src/assets/images/lan.svg b/front/src/assets/images/lan.svg new file mode 100644 index 0000000..44af020 --- /dev/null +++ b/front/src/assets/images/lan.svg @@ -0,0 +1,63 @@ + + + + + + + + + + diff --git a/front/src/assets/images/person.svg b/front/src/assets/images/person.svg new file mode 100644 index 0000000..db6026d --- /dev/null +++ b/front/src/assets/images/person.svg @@ -0,0 +1,38 @@ + + + + + + diff --git a/front/src/assets/images/settings.svg b/front/src/assets/images/settings.svg new file mode 100644 index 0000000..0d6f550 --- /dev/null +++ b/front/src/assets/images/settings.svg @@ -0,0 +1 @@ + \ No newline at end of file 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/assets/.gitkeep b/front/src/common/component/async-action-status/async-status-component.less similarity index 100% rename from front/src/assets/.gitkeep rename to front/src/common/component/async-action-status/async-status-component.less 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 @@