mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-26 04:05:40 +01:00
Update datePicker component (#18070)
1.Fix date validator 2.Add i18n support Signed-off-by: AllForNothing <sshijun@vmware.com>
This commit is contained in:
parent
e995b59a94
commit
a86740e82a
@ -8,8 +8,11 @@ import { CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { of } from 'rxjs';
|
||||
import { throwError as observableThrowError } from 'rxjs/internal/observable/throwError';
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
import { SharedTestingModule } from '../../shared/shared.module';
|
||||
import { UserPermissionService } from '../../shared/services';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { ClarityModule } from '@clr/angular';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { TranslateModule, TranslateService } from '@ngx-translate/core';
|
||||
|
||||
describe('SignInComponent', () => {
|
||||
let component: SignInComponent;
|
||||
@ -27,9 +30,15 @@ describe('SignInComponent', () => {
|
||||
};
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [SharedTestingModule],
|
||||
imports: [
|
||||
TranslateModule.forRoot(),
|
||||
RouterTestingModule,
|
||||
ClarityModule,
|
||||
FormsModule,
|
||||
],
|
||||
declarations: [SignInComponent],
|
||||
providers: [
|
||||
TranslateService,
|
||||
{
|
||||
provide: UserPermissionService,
|
||||
useValue: mockedUserPermissionService,
|
||||
|
@ -4,6 +4,9 @@ import { ErrorHandler } from '../../../../shared/units/error-handler';
|
||||
import { of } from 'rxjs';
|
||||
import { SharedTestingModule } from '../../../../shared/shared.module';
|
||||
import { SecurityComponent } from './security.component';
|
||||
import { LOCALE_ID } from '@angular/core';
|
||||
import { registerLocaleData } from '@angular/common';
|
||||
import locale_en from '@angular/common/locales/en';
|
||||
describe('SecurityComponent', () => {
|
||||
let component: SecurityComponent;
|
||||
let fixture: ComponentFixture<SecurityComponent>;
|
||||
@ -26,7 +29,18 @@ describe('SecurityComponent', () => {
|
||||
return null;
|
||||
},
|
||||
};
|
||||
registerLocaleData(locale_en, 'en-us');
|
||||
beforeEach(() => {
|
||||
TestBed.overrideComponent(SecurityComponent, {
|
||||
set: {
|
||||
providers: [
|
||||
{
|
||||
provide: LOCALE_ID,
|
||||
useValue: 'en-us',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
TestBed.configureTestingModule({
|
||||
imports: [SharedTestingModule],
|
||||
providers: [
|
||||
|
@ -1,6 +1,7 @@
|
||||
import {
|
||||
Component,
|
||||
ElementRef,
|
||||
LOCALE_ID,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
ViewChild,
|
||||
@ -10,6 +11,7 @@ import { ErrorHandler } from '../../../../shared/units/error-handler';
|
||||
import {
|
||||
ConfirmationState,
|
||||
ConfirmationTargets,
|
||||
DEFAULT_LANG_LOCALSTORAGE_KEY,
|
||||
} from '../../../../shared/entities/shared.const';
|
||||
import {
|
||||
SystemCVEAllowlist,
|
||||
@ -28,6 +30,12 @@ const TARGET_BLANK = '_blank';
|
||||
selector: 'app-security',
|
||||
templateUrl: './security.component.html',
|
||||
styleUrls: ['./security.component.scss'],
|
||||
providers: [
|
||||
{
|
||||
provide: LOCALE_ID,
|
||||
useValue: localStorage.getItem(DEFAULT_LANG_LOCALSTORAGE_KEY),
|
||||
},
|
||||
],
|
||||
})
|
||||
export class SecurityComponent implements OnInit, OnDestroy {
|
||||
onGoing = false;
|
||||
|
@ -3,13 +3,16 @@ import { AuditLogComponent } from './audit-log.component';
|
||||
import { MessageHandlerService } from '../../../shared/services/message-handler.service';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { of } from 'rxjs';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA, DebugElement } from '@angular/core';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA, DebugElement, LOCALE_ID } from '@angular/core';
|
||||
import { delay } from 'rxjs/operators';
|
||||
import { AuditLog } from '../../../../../ng-swagger-gen/models/audit-log';
|
||||
import { HttpHeaders, HttpResponse } from '@angular/common/http';
|
||||
import { ProjectService } from '../../../../../ng-swagger-gen/services/project.service';
|
||||
import { click } from '../../../shared/units/utils';
|
||||
import { SharedTestingModule } from '../../../shared/shared.module';
|
||||
import { registerLocaleData } from '@angular/common';
|
||||
import locale_en from '@angular/common/locales/en';
|
||||
import { DatePickerComponent } from '../../../shared/components/datetime-picker/datetime-picker.component';
|
||||
|
||||
describe('AuditLogComponent', () => {
|
||||
let component: AuditLogComponent;
|
||||
@ -78,8 +81,18 @@ describe('AuditLogComponent', () => {
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
registerLocaleData(locale_en, 'en-us');
|
||||
beforeEach(async () => {
|
||||
TestBed.overrideComponent(DatePickerComponent, {
|
||||
set: {
|
||||
providers: [
|
||||
{
|
||||
provide: LOCALE_ID,
|
||||
useValue: 'en-us',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
await TestBed.configureTestingModule({
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
imports: [SharedTestingModule],
|
||||
|
@ -1,70 +0,0 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
import { Directive, OnChanges, Input, SimpleChanges } from '@angular/core';
|
||||
import {
|
||||
NG_VALIDATORS,
|
||||
Validator,
|
||||
Validators,
|
||||
ValidatorFn,
|
||||
AbstractControl,
|
||||
} from '@angular/forms';
|
||||
|
||||
@Directive({
|
||||
selector: '[dateValidator]',
|
||||
providers: [
|
||||
{
|
||||
provide: NG_VALIDATORS,
|
||||
useExisting: DateValidatorDirective,
|
||||
multi: true,
|
||||
},
|
||||
],
|
||||
})
|
||||
export class DateValidatorDirective implements Validator, OnChanges {
|
||||
@Input() dateValidator: string;
|
||||
private valFn = Validators.nullValidator;
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
const change = changes['dateValidator'];
|
||||
if (change) {
|
||||
this.valFn = dateValidator();
|
||||
} else {
|
||||
this.valFn = Validators.nullValidator;
|
||||
}
|
||||
}
|
||||
validate(control: AbstractControl): { [key: string]: any } {
|
||||
return this.valFn(control) || Validators.nullValidator;
|
||||
}
|
||||
}
|
||||
|
||||
export function dateValidator(): ValidatorFn {
|
||||
return (control: AbstractControl): { [key: string]: any } => {
|
||||
let controlValue = control.value;
|
||||
let valid = true;
|
||||
if (controlValue) {
|
||||
const regYMD =
|
||||
/^(19|20)\d\d([- /.])(0[1-9]|1[012])\2(0[1-9]|[12][0-9]|3[01])$/g;
|
||||
const regDMY =
|
||||
/^(0[1-9]|[12][0-9]|3[01])[- /.](0[1-9]|1[012])[- /.](19|20)\d\d$/g;
|
||||
const regMDY =
|
||||
/^(0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])[- /.](19|20)\d\d$/g;
|
||||
valid =
|
||||
regYMD.test(controlValue) ||
|
||||
regDMY.test(controlValue) ||
|
||||
regMDY.test(controlValue);
|
||||
}
|
||||
return valid
|
||||
? Validators.nullValidator
|
||||
: { dateValidator: { value: controlValue } };
|
||||
};
|
||||
}
|
@ -12,4 +12,9 @@
|
||||
left: 75%;
|
||||
background-color: #c92100;
|
||||
}
|
||||
|
||||
>.tooltip-content::before {
|
||||
border-left-color:#c92100;
|
||||
border-top-color: #c92100;
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
import { LOCALE_ID, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { DatePickerComponent } from './datetime-picker.component';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { TranslateModule, TranslateService } from '@ngx-translate/core';
|
||||
import { DateValidatorDirective } from '../../directives/date-validator.directive';
|
||||
import { registerLocaleData } from '@angular/common';
|
||||
import locale_en from '@angular/common/locales/en';
|
||||
|
||||
describe('DatePickerComponent', () => {
|
||||
let component: DatePickerComponent;
|
||||
let fixture: ComponentFixture<DatePickerComponent>;
|
||||
registerLocaleData(locale_en, 'en-us');
|
||||
beforeEach(async () => {
|
||||
TestBed.overrideComponent(DatePickerComponent, {
|
||||
set: {
|
||||
providers: [
|
||||
{
|
||||
provide: LOCALE_ID,
|
||||
useValue: 'en-us',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [FormsModule, TranslateModule.forRoot()],
|
||||
declarations: [DatePickerComponent, DateValidatorDirective],
|
||||
providers: [TranslateService],
|
||||
schemas: [NO_ERRORS_SCHEMA],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(DatePickerComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -5,13 +5,21 @@ import {
|
||||
EventEmitter,
|
||||
ViewChild,
|
||||
OnChanges,
|
||||
LOCALE_ID,
|
||||
} from '@angular/core';
|
||||
import { NgModel } from '@angular/forms';
|
||||
import { DEFAULT_LANG_LOCALSTORAGE_KEY } from '../../entities/shared.const';
|
||||
|
||||
@Component({
|
||||
selector: 'hbr-datetime',
|
||||
templateUrl: './datetime-picker.component.html',
|
||||
styleUrls: ['./datetime-picker.component.scss'],
|
||||
providers: [
|
||||
{
|
||||
provide: LOCALE_ID,
|
||||
useValue: localStorage.getItem(DEFAULT_LANG_LOCALSTORAGE_KEY),
|
||||
},
|
||||
],
|
||||
})
|
||||
export class DatePickerComponent implements OnChanges {
|
||||
@Input() dateInput: string;
|
||||
|
@ -45,7 +45,7 @@
|
||||
clrDropdownItem
|
||||
(click)="switchLanguage(lang[0])"
|
||||
[class.lang-selected]="matchLang(lang[0])"
|
||||
>{{ lang[1] }}</a
|
||||
>{{ lang[1][0] }}</a
|
||||
>
|
||||
</clr-dropdown-menu>
|
||||
</clr-dropdown>
|
||||
|
@ -9,6 +9,9 @@ import { MessageHandlerService } from '../../services/message-handler.service';
|
||||
import { SearchTriggerService } from '../global-search/search-trigger.service';
|
||||
import { SkinableConfig } from '../../../services/skinable-config.service';
|
||||
import { SharedTestingModule } from '../../shared.module';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { DeFaultLang } from '../../entities/shared.const';
|
||||
import { of } from 'rxjs';
|
||||
|
||||
describe('NavigatorComponent', () => {
|
||||
let component: TestComponentWrapperComponent;
|
||||
|
@ -14,7 +14,7 @@
|
||||
import { Component, Output, EventEmitter, OnInit } from '@angular/core';
|
||||
import { Router, NavigationExtras } from '@angular/router';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { PlatformLocation } from '@angular/common';
|
||||
import { PlatformLocation, registerLocaleData } from '@angular/common';
|
||||
import { ModalEvent } from '../../../base/modal-event';
|
||||
import { modalEvents } from '../../../base/modal-events.const';
|
||||
import { SessionService } from '../../services/session.service';
|
||||
@ -70,6 +70,12 @@ export class NavigatorComponent implements OnInit {
|
||||
// custom skin
|
||||
this.customStyle = this.skinableConfig.getSkinConfig();
|
||||
this.selectedLang = this.translate.currentLang as SupportedLanguage;
|
||||
if (this.selectedLang) {
|
||||
registerLocaleData(
|
||||
LANGUAGES[this.selectedLang][1],
|
||||
this.selectedLang
|
||||
);
|
||||
}
|
||||
this.selectedDatetimeRendering = getDatetimeRendering();
|
||||
if (this.appConfigService.isIntegrationMode()) {
|
||||
this.appTitle = 'APP_TITLE.VIC';
|
||||
@ -91,7 +97,7 @@ export class NavigatorComponent implements OnInit {
|
||||
}
|
||||
|
||||
public get currentLang(): string {
|
||||
return LANGUAGES[this.selectedLang];
|
||||
return LANGUAGES[this.selectedLang][0] as string;
|
||||
}
|
||||
|
||||
public get currentDatetimeRendering(): string {
|
||||
@ -180,6 +186,7 @@ export class NavigatorComponent implements OnInit {
|
||||
// Switch languages
|
||||
switchLanguage(lang: SupportedLanguage): void {
|
||||
this.selectedLang = lang;
|
||||
registerLocaleData(LANGUAGES[this.selectedLang][1], this.selectedLang);
|
||||
localStorage.setItem(DEFAULT_LANG_LOCALSTORAGE_KEY, lang);
|
||||
// due to the bug(https://github.com/ngx-translate/core/issues/1258) of translate module
|
||||
// have to reload
|
||||
|
@ -53,10 +53,15 @@ export function dateValidator(): ValidatorFn {
|
||||
let valid = true;
|
||||
if (controlValue) {
|
||||
const regYMD =
|
||||
/^(19|20)\d\d([- /.])(0[1-9]|1[012])\2(0[1-9]|[12][0-9]|3[01])$/g;
|
||||
/^(19|20)\d\d([- /.])(0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])$/g;
|
||||
const regDMY =
|
||||
/^(0[1-9]|[12][0-9]|3[01])[- /.](0[1-9]|1[012])[- /.](19|20)\d\d$/g;
|
||||
valid = regYMD.test(controlValue) || regDMY.test(controlValue);
|
||||
const regMDY =
|
||||
/^(0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])[- /.](19|20)\d\d$/g;
|
||||
valid =
|
||||
regYMD.test(controlValue) ||
|
||||
regDMY.test(controlValue) ||
|
||||
regMDY.test(controlValue);
|
||||
}
|
||||
return valid ? null : { dateValidator: { value: controlValue } };
|
||||
};
|
||||
|
@ -12,6 +12,15 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import locale_en from '@angular/common/locales/en';
|
||||
import locale_zh_CN from '@angular/common/locales/zh-Hans';
|
||||
import locale_zh_TW from '@angular/common/locales/zh-Hans-HK';
|
||||
import locale_es from '@angular/common/locales/es';
|
||||
import locale_fr from '@angular/common/locales/fr';
|
||||
import locale_pt from '@angular/common/locales/pt-PT';
|
||||
import locale_tr from '@angular/common/locales/tr';
|
||||
import locale_de from '@angular/common/locales/de';
|
||||
|
||||
export const enum AlertType {
|
||||
DANGER,
|
||||
WARNING,
|
||||
@ -223,16 +232,16 @@ export const REFRESH_TIME_DIFFERENCE = 10000;
|
||||
//
|
||||
|
||||
export const DeFaultLang = 'en-us';
|
||||
export type SupportedLanguage = keyof typeof LANGUAGES;
|
||||
export type SupportedLanguage = string;
|
||||
export const LANGUAGES = {
|
||||
'en-us': 'English',
|
||||
'zh-cn': '中文简体',
|
||||
'zh-tw': '中文繁體',
|
||||
'es-es': 'Español',
|
||||
'fr-fr': 'Français',
|
||||
'pt-br': 'Português do Brasil',
|
||||
'tr-tr': 'Türkçe',
|
||||
'de-de': 'Deutsch',
|
||||
'en-us': ['English', locale_en],
|
||||
'zh-cn': ['中文简体', locale_zh_CN],
|
||||
'zh-tw': ['中文繁體', locale_zh_TW],
|
||||
'es-es': ['Español', locale_es],
|
||||
'fr-fr': ['Français', locale_fr],
|
||||
'pt-br': ['Português do Brasil', locale_pt],
|
||||
'tr-tr': ['Türkçe', locale_tr],
|
||||
'de-de': ['Deutsch', locale_de],
|
||||
} as const;
|
||||
export const supportedLangs = Object.keys(LANGUAGES) as SupportedLanguage[];
|
||||
/**
|
||||
@ -240,7 +249,7 @@ export const supportedLangs = Object.keys(LANGUAGES) as SupportedLanguage[];
|
||||
*/
|
||||
export const DEFAULT_LANG_LOCALSTORAGE_KEY = 'harbor-lang';
|
||||
|
||||
export type DatetimeRendering = keyof typeof DATETIME_RENDERINGS;
|
||||
export type DatetimeRendering = string;
|
||||
export const DATETIME_RENDERINGS = {
|
||||
'locale-default': 'TOP_NAV.DATETIME_RENDERING_DEFAULT',
|
||||
'iso-8601': 'ISO 8601',
|
||||
|
@ -79,33 +79,8 @@ import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { HarborDatetimePipe } from './pipes/harbor-datetime.pipe';
|
||||
import { RemainingTimeComponent } from './components/remaining-time/remaining-time.component';
|
||||
|
||||
import { registerLocaleData } from '@angular/common';
|
||||
import locale_en from '@angular/common/locales/en';
|
||||
import locale_zh_CN from '@angular/common/locales/zh-Hans';
|
||||
import locale_zh_TW from '@angular/common/locales/zh-Hans-HK';
|
||||
import locale_es from '@angular/common/locales/es';
|
||||
import locale_fr from '@angular/common/locales/fr';
|
||||
import locale_pt from '@angular/common/locales/pt-PT';
|
||||
import locale_tr from '@angular/common/locales/tr';
|
||||
import locale_de from '@angular/common/locales/de';
|
||||
import { SupportedLanguage } from './entities/shared.const';
|
||||
import { LabelSelectorComponent } from './components/label-selector/label-selector.component';
|
||||
|
||||
const localesForSupportedLangs: Record<SupportedLanguage, unknown[]> = {
|
||||
'en-us': locale_en,
|
||||
'zh-cn': locale_zh_CN,
|
||||
'zh-tw': locale_zh_TW,
|
||||
'es-es': locale_es,
|
||||
'fr-fr': locale_fr,
|
||||
'pt-br': locale_pt,
|
||||
'tr-tr': locale_tr,
|
||||
'de-de': locale_de,
|
||||
};
|
||||
for (const [lang, locale] of Object.entries(localesForSupportedLangs)) {
|
||||
registerLocaleData(locale, lang);
|
||||
}
|
||||
|
||||
// ClarityIcons is publicly accessible from the browser's window object.
|
||||
declare const ClarityIcons: ClarityIconsApi;
|
||||
|
||||
|
@ -17,8 +17,6 @@ import {
|
||||
import { AbstractControl } from '@angular/forms';
|
||||
import { isValidCron } from 'cron-validator';
|
||||
import { ClrDatagridStateInterface } from '@clr/angular';
|
||||
import { ScheduleListComponent } from '../../base/left-side-nav/job-service-dashboard/schedule-list/schedule-list.component';
|
||||
import { PendingListComponent } from '../../base/left-side-nav/job-service-dashboard/pending-job-list/pending-job-list.component';
|
||||
|
||||
/**
|
||||
* Api levels
|
||||
|
Loading…
Reference in New Issue
Block a user