diff --git a/package-lock.json b/package-lock.json index f77c624..34a4fcd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2402,12 +2402,12 @@ "dev": true }, "@ucap/ng-ui-authentication": { - "version": "file:pack/ucap-ng-ui-authentication-0.0.8.tgz", - "integrity": "sha512-n6pV113DuSD5TV4jAwrY/JJlhcBtNLHEFTTxrQ8QhFIMz9hgbRC6x4R/LF8q9kzx0BWQcgxGdfgY1oN3YoqGtg==" + "version": "file:pack/ucap-ng-ui-authentication-0.0.13.tgz", + "integrity": "sha512-TrPEMg6nOJKFCsQN4EGDtA78ugBnhahvDw0pRvs67CFQ28hqQqFdMhuG7Qyl/+L2nJ7XfRZOAgYlEfddDtYEZA==" }, "@ucap/ng-ui-organization": { - "version": "file:pack/ucap-ng-ui-organization-0.0.1.tgz", - "integrity": "sha512-ihbfzCAL6f2+dOnhyjv3u3SP/8LgcXekDBB7Jt0P+4FdtIVM70Z5OjdObNlyi8rld9elc3SiWmpsxXz1cmouXw==", + "version": "file:pack/ucap-ng-ui-organization-0.0.2.tgz", + "integrity": "sha512-xywle2jhGaaNxkH+9VLvIOxfFzdLMf5nGP7fqW9gPSZiK51OR5s81UwWoXCeQRfnJuUX+6U97LoY7KAO5c0LLA==", "dev": true }, "@ucap/ng-ui-skin-default": { @@ -2421,29 +2421,10 @@ "dev": true }, "@ucap/pi": { - "version": "0.0.2", - "resolved": "http://10.81.13.221:8081/nexus/repository/npm-all/@ucap/pi/-/pi-0.0.2.tgz", - "integrity": "sha512-HgCuKMmdWMEhCi4zoPEOd/Ef+Fm7dRdI/66Jzyr2JRbbStAsrO9qIwXlmtTI40Ggy7nCrt1ywRAtV1m1pA2+Ug==", - "dev": true, - "requires": { - "@ucap/api": "^0.0.1", - "@ucap/core": "^0.0.1", - "axios": "^0.19.2", - "crypto-js": "^4.0.0", - "rxjs": "^6.5.4" - }, - "dependencies": { - "@ucap/core": { - "version": "0.0.1", - "resolved": "http://10.81.13.221:8081/nexus/repository/npm-all/@ucap/core/-/core-0.0.1.tgz", - "integrity": "sha512-TfQqgu9/Ys0a7wN+3U97ZivUcwF9Ko0uY3qWgyV41MzlCKjigH2L2+hxj6/x9yxXZIK6/ynvVW3RB7x1odR5bQ==", - "dev": true, - "requires": { - "detect-browser": "^5.0.0", - "file-type": "^14.1.4" - } - } - } + "version": "0.0.5", + "resolved": "http://10.81.13.221:8081/nexus/repository/npm-all/@ucap/pi/-/pi-0.0.5.tgz", + "integrity": "sha512-nWev387pHxeBHtOu0EvRTVQ0/JeJL44Ew0PzQaiqHsC5mghkJ6ok7z22nk9nmuZ7lONxGJmW7CHT8X8lyviZJg==", + "dev": true }, "@ucap/protocol": { "version": "0.0.1", diff --git a/package.json b/package.json index f059213..d75ee58 100644 --- a/package.json +++ b/package.json @@ -170,11 +170,11 @@ "@ucap/ng-store-group": "file:pack/ucap-ng-store-group-0.0.3.tgz", "@ucap/ng-store-organization": "file:pack/ucap-ng-store-organization-0.0.3.tgz", "@ucap/ng-ui": "file:pack/ucap-ng-ui-0.0.3.tgz", - "@ucap/ng-ui-authentication": "file:pack/ucap-ng-ui-authentication-0.0.8.tgz", - "@ucap/ng-ui-organization": "file:pack/ucap-ng-ui-organization-0.0.1.tgz", + "@ucap/ng-ui-authentication": "file:pack/ucap-ng-ui-authentication-0.0.13.tgz", + "@ucap/ng-ui-organization": "file:pack/ucap-ng-ui-organization-0.0.2.tgz", "@ucap/ng-ui-skin-default": "file:pack/ucap-ng-ui-skin-default-0.0.1.tgz", "@ucap/ng-web-storage": "file:pack/ucap-ng-web-storage-0.0.1.tgz", - "@ucap/pi": "~0.0.1", + "@ucap/pi": "~0.0.5", "@ucap/protocol": "~0.0.1", "@ucap/protocol-authentication": "~0.0.1", "@ucap/protocol-buddy": "~0.0.1", diff --git a/projects/i18n/src/lib/pipes/i18n.pipe.ts b/projects/i18n/src/lib/pipes/i18n.pipe.ts index 5552c2f..1dc5863 100644 --- a/projects/i18n/src/lib/pipes/i18n.pipe.ts +++ b/projects/i18n/src/lib/pipes/i18n.pipe.ts @@ -10,10 +10,11 @@ import { import { StringMap, TOptions } from 'i18next'; +import { ObjectUtil } from '@ucap/core'; + import { I18nService } from '../services/i18n.service'; import { UCAP_I18N_NAMESPACE } from '../types/token'; -import { ObjectUtil } from '../utils/object.util'; @Pipe({ name: 'ucapI18n', pure: false }) export class I18nPipe implements PipeTransform, OnDestroy { diff --git a/projects/i18n/src/lib/utils/object.util.ts b/projects/i18n/src/lib/utils/object.util.ts deleted file mode 100644 index ac3b91b..0000000 --- a/projects/i18n/src/lib/utils/object.util.ts +++ /dev/null @@ -1,83 +0,0 @@ -export class ObjectUtil { - static equals(o1: any, o2: any): boolean { - if (o1 === o2) { - return true; - } - if (o1 === null || o2 === null) { - return false; - } - if (o1 !== o1 && o2 !== o2) { - return true; // NaN === NaN - } - - const t1 = typeof o1; - const t2 = typeof o2; - let length: number; - let key: any; - let keySet: any; - - if (t1 === t2 && t1 === 'object') { - if (Array.isArray(o1)) { - if (!Array.isArray(o2)) { - return false; - } - length = o1.length; - if (length === o2.length) { - for (key = 0; key < length; key++) { - if (!ObjectUtil.equals(o1[key], o2[key])) { - return false; - } - } - return true; - } - } else { - if (Array.isArray(o2)) { - return false; - } - keySet = Object.create(null); - for (key in o1) { - if (o1.hasOwnProperty(key)) { - if (!ObjectUtil.equals(o1[key], o2[key])) { - return false; - } - keySet[key] = true; - } - } - - for (key in o2) { - if (!(key in keySet) && typeof o2[key] !== 'undefined') { - return false; - } - } - return true; - } - } - return false; - } - - static isDefined(value: any): boolean { - return typeof value !== 'undefined' && value !== null; - } - - static isObject(item: any): boolean { - return item && typeof item === 'object' && !Array.isArray(item); - } - - static mergeDeep(target: any, source: any): any { - const output = Object.assign({}, target); - if (ObjectUtil.isObject(target) && ObjectUtil.isObject(source)) { - Object.keys(source).forEach((key: any) => { - if (ObjectUtil.isObject(source[key])) { - if (!(key in target)) { - Object.assign(output, { [key]: source[key] }); - } else { - output[key] = ObjectUtil.mergeDeep(target[key], source[key]); - } - } else { - Object.assign(output, { [key]: source[key] }); - } - }); - } - return output; - } -} diff --git a/projects/ui-authentication/ng-package.json b/projects/ui-authentication/ng-package.json index a65bb52..29fe59a 100644 --- a/projects/ui-authentication/ng-package.json +++ b/projects/ui-authentication/ng-package.json @@ -4,6 +4,7 @@ "lib": { "entryFile": "src/public-api.ts", "umdModuleIds": { + "moment": "moment", "@ucap/pi": "@ucap/pi", "@ucap/ng-i18n": "@ucap/ng-i18n", "@ucap/ng-ui": "@ucap/ng-ui" diff --git a/projects/ui-authentication/package.json b/projects/ui-authentication/package.json index 5b52fd3..eab2f29 100644 --- a/projects/ui-authentication/package.json +++ b/projects/ui-authentication/package.json @@ -1,6 +1,6 @@ { "name": "@ucap/ng-ui-authentication", - "version": "0.0.8", + "version": "0.0.13", "publishConfig": { "registry": "http://10.81.13.221:8081/nexus/repository/npm-ucap/" }, diff --git a/projects/ui-authentication/src/lib/components/login.component.html b/projects/ui-authentication/src/lib/components/login.component.html index b41c02d..af2b6b1 100644 --- a/projects/ui-authentication/src/lib/components/login.component.html +++ b/projects/ui-authentication/src/lib/components/login.component.html @@ -81,6 +81,7 @@ @@ -88,32 +89,63 @@ {{ 'login.errors.requireLoginId' | ucapI18n }} {{ 'login.errors.requireLoginPw' | ucapI18n }} + + {{ 'login.errors.failed' | ucapI18n }} + diff --git a/projects/ui-authentication/src/lib/components/login.component.ts b/projects/ui-authentication/src/lib/components/login.component.ts index cd968c4..6466c22 100644 --- a/projects/ui-authentication/src/lib/components/login.component.ts +++ b/projects/ui-authentication/src/lib/components/login.component.ts @@ -1,3 +1,5 @@ +import moment from 'moment'; + import { Component, OnInit, @@ -16,6 +18,7 @@ import { ValidatorFn } from '@angular/forms'; import { Company } from '@ucap/api-external'; +import { LoginTry } from '@ucap/pi'; @Component({ selector: 'ucap-authentication-login', @@ -41,6 +44,9 @@ export class LoginComponent implements OnInit { @Input() processing = false; + @Input() + loginTry: LoginTry; + @Output() login = new EventEmitter<{ companyCode: string; @@ -55,6 +61,9 @@ export class LoginComponent implements OnInit { companyCodeFormControl = new FormControl(''); loginIdFormControl = new FormControl(''); loginPwFormControl = new FormControl(''); + loginFailed = false; + + moment = moment; constructor( private formBuilder: FormBuilder, @@ -90,6 +99,7 @@ export class LoginComponent implements OnInit { loginId: this.loginForm.get('loginIdFormControl').value, loginPw: this.loginForm.get('loginPwFormControl').value, notValid: () => { + this.loginFailed = true; this.loginPwElementRef.nativeElement.focus(); } }); diff --git a/projects/ui-organization/ng-package.json b/projects/ui-organization/ng-package.json index 397ac92..02ce55b 100644 --- a/projects/ui-organization/ng-package.json +++ b/projects/ui-organization/ng-package.json @@ -4,6 +4,7 @@ "lib": { "entryFile": "src/public-api.ts", "umdModuleIds": { + "@ucap/core": "@ucap/core", "@ucap/ng-ui": "@ucap/ng-ui" } } diff --git a/projects/ui-organization/package.json b/projects/ui-organization/package.json index 4bd97bf..d81d7f6 100644 --- a/projects/ui-organization/package.json +++ b/projects/ui-organization/package.json @@ -1,6 +1,6 @@ { "name": "@ucap/ng-ui-organization", - "version": "0.0.1", + "version": "0.0.2", "publishConfig": { "registry": "http://10.81.13.221:8081/nexus/repository/npm-ucap/" }, diff --git a/projects/ui-organization/src/lib/organization-ui.module.ts b/projects/ui-organization/src/lib/organization-ui.module.ts index 5e68ef6..99c4743 100644 --- a/projects/ui-organization/src/lib/organization-ui.module.ts +++ b/projects/ui-organization/src/lib/organization-ui.module.ts @@ -6,11 +6,15 @@ import { UiModule } from '@ucap/ng-ui'; import { ModuleConfig } from './config/module-config'; import { _MODULE_CONFIG } from './config/token'; +import { TranslatePipe } from './pipes/translate.pipe'; + +import { TranslateService } from './services/translate.service'; + const COMPONENTS = []; const DIALOGS = []; -const PIPES = []; +const PIPES = [TranslatePipe]; const DIRECTIVES = []; -const SERVICES = []; +const SERVICES = [TranslateService]; @NgModule({ declarations: [], diff --git a/projects/ui-organization/src/lib/pipes/translate.pipe.ts b/projects/ui-organization/src/lib/pipes/translate.pipe.ts new file mode 100644 index 0000000..f61cfb3 --- /dev/null +++ b/projects/ui-organization/src/lib/pipes/translate.pipe.ts @@ -0,0 +1,121 @@ +import { + ChangeDetectorRef, + Injectable, + OnDestroy, + Pipe, + PipeTransform +} from '@angular/core'; +import { Subscription } from 'rxjs'; +import { ObjectUtil } from '@ucap/core'; +import { + TranslateService, + LangChangeEvent +} from '../services/translate.service'; + +@Injectable() +@Pipe({ + name: 'ucapOrganizationTranslate', + pure: false // required to update the value when the promise is resolved +}) +export class TranslatePipe implements PipeTransform, OnDestroy { + value = ''; + lastTarget: any; + lastKey: string; + lastSeparator: string; + changedLangSubscription: Subscription; + changedDefaultLangSubscription: Subscription; + + constructor( + private translateService: TranslateService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + updateValue(target: any, key: string, separator?: string): void { + try { + const value = this.translateService.get(target, key, separator); + this.value = value; + } catch (error) { + this.value = key; + } + this.lastTarget = target; + this.lastKey = key; + this.changeDetectorRef.markForCheck(); + } + + transform(target: any, key: string, separator?: string): any { + if ( + !target || + 0 === Object.keys(target).length || + !key || + 0 === key.length + ) { + return ''; + } + + // if we ask another time for the same key, return the last value + if ( + ObjectUtil.equals(target, this.lastTarget) && + ObjectUtil.equals(key, this.lastKey) && + ObjectUtil.equals(separator, this.lastSeparator) + ) { + return this.value; + } + + // store the target, in case it changes + this.lastTarget = target; + // store the key, in case it changes + this.lastKey = key; + // store the key, in case it changes + this.lastSeparator = separator; + + // set the value + this.updateValue(target, key, separator); + + // if there is a subscription to onLangChange, clean it + this._dispose(); + + // subscribe to onLangChange event, in case the language changes + if (!this.changedLangSubscription) { + this.changedLangSubscription = this.translateService.changedLang.subscribe( + (event: LangChangeEvent) => { + if (this.lastKey) { + this.lastKey = null; // we want to make sure it doesn't return the same value until it's been updated + this.updateValue(target, key, separator); + } + } + ); + } + + // subscribe to onDefaultLangChange event, in case the default language changes + if (!this.changedDefaultLangSubscription) { + this.changedDefaultLangSubscription = this.translateService.changedDefaultLang.subscribe( + () => { + if (this.lastKey) { + this.lastKey = null; // we want to make sure it doesn't return the same value until it's been updated + this.updateValue(target, key, separator); + } + } + ); + } + + return this.value; + } + + /** + * Clean any existing subscription to change events + */ + private _dispose(): void { + if (!!this.changedLangSubscription) { + this.changedLangSubscription.unsubscribe(); + this.changedLangSubscription = undefined; + } + if (!!this.changedDefaultLangSubscription) { + this.changedDefaultLangSubscription.unsubscribe(); + this.changedDefaultLangSubscription = undefined; + } + } + + ngOnDestroy(): void { + this._dispose(); + } +} diff --git a/projects/ui-organization/src/lib/services/translate.service.ts b/projects/ui-organization/src/lib/services/translate.service.ts new file mode 100644 index 0000000..1ea6234 --- /dev/null +++ b/projects/ui-organization/src/lib/services/translate.service.ts @@ -0,0 +1,161 @@ +import { Injectable, EventEmitter } from '@angular/core'; + +export interface LangChangeEvent { + lang: string; +} + +export interface DefaultLangChangeEvent { + lang: string; +} + +@Injectable({ + providedIn: 'root' +}) +export class TranslateService { + // tslint:disable-next-line: variable-name + private _changedLang: EventEmitter = new EventEmitter< + LangChangeEvent + >(); + // tslint:disable-next-line: variable-name + private _changedDefaultLang: EventEmitter< + DefaultLangChangeEvent + > = new EventEmitter(); + // tslint:disable-next-line: variable-name + private _defaultLang: string; + // tslint:disable-next-line: variable-name + private _currentLang: string; + + /** + * An EventEmitter to listen to lang change events + * onLangChange.subscribe((params: LangChangeEvent) => { + * // do something + * }); + */ + get changedLang(): EventEmitter { + return this._changedLang; + } + + /** + * An EventEmitter to listen to default lang change events + * onDefaultLangChange.subscribe((params: DefaultLangChangeEvent) => { + * // do something + * }); + */ + get changedDefaultLang() { + return this._changedDefaultLang; + } + + /** + * The default lang to fallback when translations are missing on the current lang + */ + get defaultLang(): string { + return this._defaultLang; + } + + set defaultLang(defaultLang: string) { + this._defaultLang = defaultLang; + } + + /** + * The lang currently used + */ + get currentLang(): string { + return this._currentLang; + } + + set currentLang(currentLang: string) { + this._currentLang = currentLang; + } + + constructor() {} + + /** + * Sets the default language to use as a fallback + */ + public setDefaultLang(lang: string): void { + if (lang === this.defaultLang) { + return; + } + + this.changeDefaultLang(lang); + } + + /** + * Gets the default language used + */ + public getDefaultLang(): string { + return this.defaultLang; + } + + /** + * Changes the lang currently used + */ + public use(lang: string): void { + // don't change the language if the language given is already selected + if (lang === this.currentLang) { + return; + } + + this.changeLang(lang); + } + + /** + * Gets the translated value of a key (or an array of keys) + * @returns the translated key, or an object of translated keys + */ + public get(target: any, key: string, separator?: string): string { + if (!target || 0 === Object.keys(target).length) { + throw new Error(`Parameter "target" is not valid`); + } + if (!key || 0 === key.length) { + throw new Error(`Parameter "key" required`); + } + + if (target instanceof Array) { + const result: string[] = []; + target.forEach(t => { + result.push(this.get(t, key, separator)); + }); + return result.join(!!separator ? separator : ''); + } + + const keys = Object.keys(target); + try { + const lang = + this.currentLang.charAt(0).toUpperCase() + + this.currentLang.substring(1); + const langKey = `${key}${lang}`; + + if (-1 !== keys.indexOf(langKey)) { + return target[langKey]; + } + } catch (error) {} + + if (-1 !== keys.indexOf(key)) { + return target[key]; + } + + throw new Error(`value is not exist`); + } + + /** + * Changes the current lang + */ + private changeLang(lang: string): void { + this.currentLang = lang; + this.changedLang.emit({ lang }); + + // if there is no default lang, use the one that we just set + if (!this.defaultLang) { + this.changeDefaultLang(lang); + } + } + + /** + * Changes the default lang + */ + private changeDefaultLang(lang: string): void { + this.defaultLang = lang; + this.changedDefaultLang.emit({ lang }); + } +} diff --git a/projects/ui-organization/src/public-api.ts b/projects/ui-organization/src/public-api.ts index c826aac..b355138 100644 --- a/projects/ui-organization/src/public-api.ts +++ b/projects/ui-organization/src/public-api.ts @@ -4,4 +4,8 @@ export * from './lib/config/module-config'; +export * from './lib/pipes/translate.pipe'; + +export * from './lib/services/translate.service'; + export * from './lib/organization-ui.module'; diff --git a/projects/ui-organization/tslint.json b/projects/ui-organization/tslint.json index ca80d1a..cbfb8a8 100644 --- a/projects/ui-organization/tslint.json +++ b/projects/ui-organization/tslint.json @@ -1,17 +1,7 @@ { "extends": "../../tslint.json", "rules": { - "directive-selector": [ - true, - "attribute", - "ucapOrganization", - "camelCase" - ], - "component-selector": [ - true, - "element", - "ucap-organization", - "kebab-case" - ] + "directive-selector": [true, "attribute", "ucapOrganization", "camelCase"], + "component-selector": [true, "element", "ucap-organization", "kebab-case"] } }