mirror of https://github.com/abpframework/abp.git
48 changed files with 399 additions and 331 deletions
@ -1 +1 @@ |
|||||
export * from './config/config.plugin'; |
export * from './config.plugin'; |
||||
|
|||||
@ -0,0 +1,20 @@ |
|||||
|
import { createHttpFactory, HttpMethod, SpectatorHttp } from '@ngneat/spectator/jest'; |
||||
|
import { ApplicationConfigurationService, RestService } from '../services'; |
||||
|
import { Store } from '@ngxs/store'; |
||||
|
|
||||
|
describe('ApplicationConfigurationService', () => { |
||||
|
let spectator: SpectatorHttp<ApplicationConfigurationService>; |
||||
|
const createHttp = createHttpFactory({ |
||||
|
dataService: ApplicationConfigurationService, |
||||
|
providers: [RestService], |
||||
|
mocks: [Store], |
||||
|
}); |
||||
|
|
||||
|
beforeEach(() => (spectator = createHttp())); |
||||
|
|
||||
|
it('should send a GET to application-configuration API', () => { |
||||
|
spectator.get(Store).selectSnapshot.andReturn('https://abp.io'); |
||||
|
spectator.service.getConfiguration().subscribe(); |
||||
|
spectator.expectOne('https://abp.io/api/abp/application-configuration', HttpMethod.GET); |
||||
|
}); |
||||
|
}); |
||||
@ -0,0 +1,42 @@ |
|||||
|
import { createHttpFactory, HttpMethod, SpectatorHttp } from '@ngneat/spectator/jest'; |
||||
|
import { ProfileService, RestService } from '../services'; |
||||
|
import { Store } from '@ngxs/store'; |
||||
|
|
||||
|
describe('ProfileService', () => { |
||||
|
let spectator: SpectatorHttp<ProfileService>; |
||||
|
const createHttp = createHttpFactory({ |
||||
|
dataService: ProfileService, |
||||
|
providers: [RestService], |
||||
|
mocks: [Store], |
||||
|
}); |
||||
|
|
||||
|
beforeEach(() => (spectator = createHttp())); |
||||
|
|
||||
|
it('should send a GET to my-profile API', () => { |
||||
|
spectator.get(Store).selectSnapshot.andReturn('https://abp.io'); |
||||
|
spectator.service.get().subscribe(); |
||||
|
spectator.expectOne('https://abp.io/api/identity/my-profile', HttpMethod.GET); |
||||
|
}); |
||||
|
|
||||
|
it('should send a POST to change-password API', () => { |
||||
|
const mock = { currentPassword: 'test', newPassword: 'test' }; |
||||
|
spectator.get(Store).selectSnapshot.andReturn('https://abp.io'); |
||||
|
spectator.service.changePassword(mock).subscribe(); |
||||
|
const req = spectator.expectOne('https://abp.io/api/identity/my-profile/change-password', HttpMethod.POST); |
||||
|
expect(req.request.body).toEqual(mock); |
||||
|
}); |
||||
|
|
||||
|
it('should send a PUT to my-profile API', () => { |
||||
|
const mock = { |
||||
|
email: 'info@volosoft.com', |
||||
|
userName: 'admin', |
||||
|
name: 'John', |
||||
|
surname: 'Doe', |
||||
|
phoneNumber: '+123456', |
||||
|
}; |
||||
|
spectator.get(Store).selectSnapshot.andReturn('https://abp.io'); |
||||
|
spectator.service.update(mock).subscribe(); |
||||
|
const req = spectator.expectOne('https://abp.io/api/identity/my-profile', HttpMethod.PUT); |
||||
|
expect(req.request.body).toEqual(mock); |
||||
|
}); |
||||
|
}); |
||||
@ -1,21 +1,39 @@ |
|||||
import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; |
import { createServiceFactory, SpectatorService, SpyObject } from '@ngneat/spectator/jest'; |
||||
import { IdentityStateService } from '../services/identity-state.service'; |
import { IdentityStateService } from '../services/identity-state.service'; |
||||
import { IdentityState } from '../states/identity.state'; |
import { IdentityState } from '../states/identity.state'; |
||||
import { Store } from '@ngxs/store'; |
import { Store } from '@ngxs/store'; |
||||
describe('IdentityStateService', () => { |
describe('IdentityStateService', () => { |
||||
let service: IdentityStateService; |
let service: IdentityStateService; |
||||
let spectator: SpectatorService<IdentityStateService>; |
let spectator: SpectatorService<IdentityStateService>; |
||||
|
let store: SpyObject<Store>; |
||||
|
|
||||
const createService = createServiceFactory({ service: IdentityStateService, mocks: [Store] }); |
const createService = createServiceFactory({ service: IdentityStateService, mocks: [Store] }); |
||||
beforeEach(() => { |
beforeEach(() => { |
||||
spectator = createService(); |
spectator = createService(); |
||||
service = spectator.service; |
service = spectator.service; |
||||
|
store = spectator.get(Store); |
||||
}); |
}); |
||||
|
|
||||
test('should have the all IdentityState static methods', () => { |
test('should have the all IdentityState static methods', () => { |
||||
const reg = /(?<=static )(.*)(?=\()/gm; |
const reg = /(?<=static )(.*)(?=\()/gm; |
||||
IdentityState.toString() |
IdentityState.toString() |
||||
.match(reg) |
.match(reg) |
||||
.forEach(fnName => { |
.forEach(fnName => { |
||||
expect(service[fnName]).toBeTruthy(); |
expect(service[fnName]).toBeTruthy(); |
||||
|
|
||||
|
const spy = jest.spyOn(store, 'selectSnapshot'); |
||||
|
spy.mockClear(); |
||||
|
|
||||
|
const isDynamicSelector = IdentityState[fnName].name !== 'memoized'; |
||||
|
|
||||
|
if (isDynamicSelector) { |
||||
|
IdentityState[fnName] = jest.fn((...args) => args); |
||||
|
service[fnName]('test', 0, {}); |
||||
|
expect(IdentityState[fnName]).toHaveBeenCalledWith('test', 0, {}); |
||||
|
} else { |
||||
|
service[fnName](); |
||||
|
expect(spy).toHaveBeenCalledWith(IdentityState[fnName]); |
||||
|
} |
||||
}); |
}); |
||||
}); |
}); |
||||
}); |
}); |
||||
|
|||||
@ -1,21 +1,39 @@ |
|||||
import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; |
import { createServiceFactory, SpectatorService, SpyObject } from '@ngneat/spectator/jest'; |
||||
import { TenantManagementStateService } from '../services/tenant-management-state.service'; |
import { TenantManagementStateService } from '../services/tenant-management-state.service'; |
||||
import { TenantManagementState } from '../states/tenant-management.state'; |
import { TenantManagementState } from '../states/tenant-management.state'; |
||||
import { Store } from '@ngxs/store'; |
import { Store } from '@ngxs/store'; |
||||
describe('TenantManagementStateService', () => { |
describe('TenantManagementStateService', () => { |
||||
let service: TenantManagementStateService; |
let service: TenantManagementStateService; |
||||
let spectator: SpectatorService<TenantManagementStateService>; |
let spectator: SpectatorService<TenantManagementStateService>; |
||||
|
let store: SpyObject<Store>; |
||||
|
|
||||
const createService = createServiceFactory({ service: TenantManagementStateService, mocks: [Store] }); |
const createService = createServiceFactory({ service: TenantManagementStateService, mocks: [Store] }); |
||||
beforeEach(() => { |
beforeEach(() => { |
||||
spectator = createService(); |
spectator = createService(); |
||||
service = spectator.service; |
service = spectator.service; |
||||
|
store = spectator.get(Store); |
||||
}); |
}); |
||||
|
|
||||
test('should have the all TenantManagementState static methods', () => { |
test('should have the all TenantManagementState static methods', () => { |
||||
const reg = /(?<=static )(.*)(?=\()/gm; |
const reg = /(?<=static )(.*)(?=\()/gm; |
||||
TenantManagementState.toString() |
TenantManagementState.toString() |
||||
.match(reg) |
.match(reg) |
||||
.forEach(fnName => { |
.forEach(fnName => { |
||||
expect(service[fnName]).toBeTruthy(); |
expect(service[fnName]).toBeTruthy(); |
||||
|
|
||||
|
const spy = jest.spyOn(store, 'selectSnapshot'); |
||||
|
spy.mockClear(); |
||||
|
|
||||
|
const isDynamicSelector = TenantManagementState[fnName].name !== 'memoized'; |
||||
|
|
||||
|
if (isDynamicSelector) { |
||||
|
TenantManagementState[fnName] = jest.fn((...args) => args); |
||||
|
service[fnName]('test', 0, {}); |
||||
|
expect(TenantManagementState[fnName]).toHaveBeenCalledWith('test', 0, {}); |
||||
|
} else { |
||||
|
service[fnName](); |
||||
|
expect(spy).toHaveBeenCalledWith(TenantManagementState[fnName]); |
||||
|
} |
||||
}); |
}); |
||||
}); |
}); |
||||
}); |
}); |
||||
|
|||||
@ -1,21 +1,39 @@ |
|||||
import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; |
import { createServiceFactory, SpectatorService, SpyObject } from '@ngneat/spectator/jest'; |
||||
import { LayoutStateService } from '../services/layout-state.service'; |
import { LayoutStateService } from '../services/layout-state.service'; |
||||
import { LayoutState } from '../states/layout.state'; |
import { LayoutState } from '../states/layout.state'; |
||||
import { Store } from '@ngxs/store'; |
import { Store } from '@ngxs/store'; |
||||
describe('LayoutStateService', () => { |
describe('LayoutStateService', () => { |
||||
let service: LayoutStateService; |
let service: LayoutStateService; |
||||
let spectator: SpectatorService<LayoutStateService>; |
let spectator: SpectatorService<LayoutStateService>; |
||||
|
let store: SpyObject<Store>; |
||||
|
|
||||
const createService = createServiceFactory({ service: LayoutStateService, mocks: [Store] }); |
const createService = createServiceFactory({ service: LayoutStateService, mocks: [Store] }); |
||||
beforeEach(() => { |
beforeEach(() => { |
||||
spectator = createService(); |
spectator = createService(); |
||||
service = spectator.service; |
service = spectator.service; |
||||
|
store = spectator.get(Store); |
||||
}); |
}); |
||||
|
|
||||
test('should have the all LayoutState static methods', () => { |
test('should have the all LayoutState static methods', () => { |
||||
const reg = /(?<=static )(.*)(?=\()/gm; |
const reg = /(?<=static )(.*)(?=\()/gm; |
||||
LayoutState.toString() |
LayoutState.toString() |
||||
.match(reg) |
.match(reg) |
||||
.forEach(fnName => { |
.forEach(fnName => { |
||||
expect(service[fnName]).toBeTruthy(); |
expect(service[fnName]).toBeTruthy(); |
||||
|
|
||||
|
const spy = jest.spyOn(store, 'selectSnapshot'); |
||||
|
spy.mockClear(); |
||||
|
|
||||
|
const isDynamicSelector = LayoutState[fnName].name !== 'memoized'; |
||||
|
|
||||
|
if (isDynamicSelector) { |
||||
|
LayoutState[fnName] = jest.fn((...args) => args); |
||||
|
service[fnName]('test', 0, {}); |
||||
|
expect(LayoutState[fnName]).toHaveBeenCalledWith('test', 0, {}); |
||||
|
} else { |
||||
|
service[fnName](); |
||||
|
expect(spy).toHaveBeenCalledWith(LayoutState[fnName]); |
||||
|
} |
||||
}); |
}); |
||||
}); |
}); |
||||
}); |
}); |
||||
|
|||||
@ -1,31 +0,0 @@ |
|||||
<abp-modal [(visible)]="visible" [busy]="modalBusy"> |
|
||||
<ng-template #abpHeader> |
|
||||
<h4>{{ 'AbpIdentity::ChangePassword' | abpLocalization }}</h4> |
|
||||
</ng-template> |
|
||||
<ng-template #abpBody> |
|
||||
<form [formGroup]="form" (ngSubmit)="onSubmit()" [mapErrorsFn]="mapErrorsFn"> |
|
||||
<div class="form-group"> |
|
||||
<label for="current-password">{{ 'AbpIdentity::DisplayName:CurrentPassword' | abpLocalization }}</label |
|
||||
><span> * </span |
|
||||
><input type="password" id="current-password" class="form-control" formControlName="password" autofocus /> |
|
||||
</div> |
|
||||
<div class="form-group"> |
|
||||
<label for="new-password">{{ 'AbpIdentity::DisplayName:NewPassword' | abpLocalization }}</label |
|
||||
><span> * </span><input type="password" id="new-password" class="form-control" formControlName="newPassword" /> |
|
||||
</div> |
|
||||
<div class="form-group"> |
|
||||
<label for="confirm-new-password">{{ 'AbpIdentity::DisplayName:NewPasswordConfirm' | abpLocalization }}</label |
|
||||
><span> * </span |
|
||||
><input type="password" id="confirm-new-password" class="form-control" formControlName="repeatNewPassword" /> |
|
||||
</div> |
|
||||
</form> |
|
||||
</ng-template> |
|
||||
<ng-template #abpFooter> |
|
||||
<button type="button" class="btn btn-secondary color-white" #abpClose> |
|
||||
{{ 'AbpIdentity::Cancel' | abpLocalization }} |
|
||||
</button> |
|
||||
<abp-button iconClass="fa fa-check" buttonClass="btn btn-primary color-white" (click)="onSubmit()">{{ |
|
||||
'AbpIdentity::Save' | abpLocalization |
|
||||
}}</abp-button> |
|
||||
</ng-template> |
|
||||
</abp-modal> |
|
||||
@ -1,113 +0,0 @@ |
|||||
import { ChangePassword } from '@abp/ng.core'; |
|
||||
import { |
|
||||
Component, |
|
||||
EventEmitter, |
|
||||
Input, |
|
||||
OnChanges, |
|
||||
OnInit, |
|
||||
Output, |
|
||||
SimpleChanges, |
|
||||
TemplateRef, |
|
||||
ViewChild, |
|
||||
} from '@angular/core'; |
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; |
|
||||
import { comparePasswords, Validation } from '@ngx-validate/core'; |
|
||||
import { Store } from '@ngxs/store'; |
|
||||
import snq from 'snq'; |
|
||||
import { finalize } from 'rxjs/operators'; |
|
||||
import { ToasterService } from '../../services/toaster.service'; |
|
||||
|
|
||||
const { minLength, required } = Validators; |
|
||||
|
|
||||
const PASSWORD_FIELDS = ['newPassword', 'repeatNewPassword']; |
|
||||
|
|
||||
@Component({ |
|
||||
selector: 'abp-change-password', |
|
||||
templateUrl: './change-password.component.html', |
|
||||
}) |
|
||||
export class ChangePasswordComponent implements OnInit, OnChanges { |
|
||||
protected _visible; |
|
||||
|
|
||||
@Input() |
|
||||
get visible(): boolean { |
|
||||
return this._visible; |
|
||||
} |
|
||||
|
|
||||
set visible(value: boolean) { |
|
||||
this._visible = value; |
|
||||
this.visibleChange.emit(value); |
|
||||
} |
|
||||
|
|
||||
@Output() readonly visibleChange = new EventEmitter<boolean>(); |
|
||||
|
|
||||
@ViewChild('modalContent', { static: false }) |
|
||||
modalContent: TemplateRef<any>; |
|
||||
|
|
||||
form: FormGroup; |
|
||||
|
|
||||
modalBusy = false; |
|
||||
|
|
||||
mapErrorsFn: Validation.MapErrorsFn = (errors, groupErrors, control) => { |
|
||||
if (PASSWORD_FIELDS.indexOf(control.name) < 0) return errors; |
|
||||
|
|
||||
return errors.concat(groupErrors.filter(({ key }) => key === 'passwordMismatch')); |
|
||||
}; |
|
||||
|
|
||||
constructor(private fb: FormBuilder, private store: Store, private toasterService: ToasterService) {} |
|
||||
|
|
||||
ngOnInit(): void { |
|
||||
this.form = this.fb.group( |
|
||||
{ |
|
||||
password: ['', required], |
|
||||
newPassword: ['', required], |
|
||||
repeatNewPassword: ['', required], |
|
||||
}, |
|
||||
{ |
|
||||
validators: [comparePasswords(PASSWORD_FIELDS)], |
|
||||
}, |
|
||||
); |
|
||||
} |
|
||||
|
|
||||
onSubmit() { |
|
||||
if (this.form.invalid) return; |
|
||||
this.modalBusy = true; |
|
||||
|
|
||||
this.store |
|
||||
.dispatch( |
|
||||
new ChangePassword({ |
|
||||
currentPassword: this.form.get('password').value, |
|
||||
newPassword: this.form.get('newPassword').value, |
|
||||
}), |
|
||||
) |
|
||||
.pipe( |
|
||||
finalize(() => { |
|
||||
this.modalBusy = false; |
|
||||
}), |
|
||||
) |
|
||||
.subscribe({ |
|
||||
next: () => { |
|
||||
this.visible = false; |
|
||||
this.form.reset(); |
|
||||
}, |
|
||||
error: err => { |
|
||||
this.toasterService.error(snq(() => err.error.error.message, 'AbpAccount::DefaultErrorMessage'), 'Error', { |
|
||||
life: 7000, |
|
||||
}); |
|
||||
}, |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
openModal() { |
|
||||
this.visible = true; |
|
||||
} |
|
||||
|
|
||||
ngOnChanges({ visible }: SimpleChanges): void { |
|
||||
if (!visible) return; |
|
||||
|
|
||||
if (visible.currentValue) { |
|
||||
this.openModal(); |
|
||||
} else if (visible.currentValue === false && this.visible) { |
|
||||
this.visible = false; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,11 +1,9 @@ |
|||||
export * from './breadcrumb/breadcrumb.component'; |
export * from './breadcrumb/breadcrumb.component'; |
||||
export * from './button/button.component'; |
export * from './button/button.component'; |
||||
export * from './change-password/change-password.component'; |
|
||||
export * from './chart/chart.component'; |
export * from './chart/chart.component'; |
||||
export * from './confirmation/confirmation.component'; |
export * from './confirmation/confirmation.component'; |
||||
export * from './loader-bar/loader-bar.component'; |
export * from './loader-bar/loader-bar.component'; |
||||
export * from './modal/modal.component'; |
export * from './modal/modal.component'; |
||||
export * from './profile/profile.component'; |
|
||||
export * from './table-empty-message/table-empty-message.component'; |
export * from './table-empty-message/table-empty-message.component'; |
||||
export * from './toast/toast.component'; |
export * from './toast/toast.component'; |
||||
export * from './sort-order-icon/sort-order-icon.component'; |
export * from './sort-order-icon/sort-order-icon.component'; |
||||
|
|||||
@ -1,41 +0,0 @@ |
|||||
<abp-modal [(visible)]="visible" [busy]="modalBusy"> |
|
||||
<ng-template #abpHeader> |
|
||||
<h4>{{ 'AbpIdentity::PersonalInfo' | abpLocalization }}</h4> |
|
||||
</ng-template> |
|
||||
<ng-template #abpBody> |
|
||||
<form novalidate *ngIf="form" [formGroup]="form" (ngSubmit)="submit()"> |
|
||||
<div class="form-group"> |
|
||||
<label for="username">{{ 'AbpIdentity::DisplayName:UserName' | abpLocalization }}</label |
|
||||
><span> * </span><input type="text" id="username" class="form-control" formControlName="userName" autofocus /> |
|
||||
</div> |
|
||||
<div class="row"> |
|
||||
<div class="col col-md-6"> |
|
||||
<div class="form-group"> |
|
||||
<label for="name">{{ 'AbpIdentity::DisplayName:Name' | abpLocalization }}</label |
|
||||
><input type="text" id="name" class="form-control" formControlName="name" /> |
|
||||
</div> |
|
||||
</div> |
|
||||
<div class="col col-md-6"> |
|
||||
<div class="form-group"> |
|
||||
<label for="surname">{{ 'AbpIdentity::DisplayName:Surname' | abpLocalization }}</label |
|
||||
><input type="text" id="surname" class="form-control" formControlName="surname" /> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
<div class="form-group"> |
|
||||
<label for="email-address">{{ 'AbpIdentity::DisplayName:Email' | abpLocalization }}</label |
|
||||
><span> * </span><input type="text" id="email-address" class="form-control" formControlName="email" /> |
|
||||
</div> |
|
||||
<div class="form-group"> |
|
||||
<label for="phone-number">{{ 'AbpIdentity::DisplayName:PhoneNumber' | abpLocalization }}</label |
|
||||
><input type="text" id="phone-number" class="form-control" formControlName="phoneNumber" /> |
|
||||
</div> |
|
||||
</form> |
|
||||
</ng-template> |
|
||||
<ng-template #abpFooter> |
|
||||
<button #abpClose type="button" class="btn btn-secondary color-white"> |
|
||||
{{ 'AbpIdentity::Cancel' | abpLocalization }} |
|
||||
</button> |
|
||||
<abp-button iconClass="fa fa-check" buttonClass="btn btn-primary color-white" (click)="submit()">{{ 'AbpIdentity::Save' | abpLocalization }}</abp-button> |
|
||||
</ng-template> |
|
||||
</abp-modal> |
|
||||
@ -1,81 +0,0 @@ |
|||||
import { Profile, GetProfile, ProfileState, UpdateProfile } from '@abp/ng.core'; |
|
||||
import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core'; |
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; |
|
||||
import { Select, Store } from '@ngxs/store'; |
|
||||
import { Observable } from 'rxjs'; |
|
||||
import { take, withLatestFrom } from 'rxjs/operators'; |
|
||||
|
|
||||
const { maxLength, required, email } = Validators; |
|
||||
|
|
||||
@Component({ |
|
||||
selector: 'abp-profile', |
|
||||
templateUrl: './profile.component.html' |
|
||||
}) |
|
||||
export class ProfileComponent implements OnChanges { |
|
||||
protected _visible; |
|
||||
|
|
||||
@Input() |
|
||||
get visible(): boolean { |
|
||||
return this._visible; |
|
||||
} |
|
||||
|
|
||||
set visible(value: boolean) { |
|
||||
this._visible = value; |
|
||||
this.visibleChange.emit(value); |
|
||||
} |
|
||||
|
|
||||
@Output() readonly visibleChange = new EventEmitter<boolean>(); |
|
||||
|
|
||||
@Select(ProfileState.getProfile) |
|
||||
profile$: Observable<Profile.Response>; |
|
||||
|
|
||||
form: FormGroup; |
|
||||
|
|
||||
modalBusy = false; |
|
||||
|
|
||||
constructor(private fb: FormBuilder, private store: Store) {} |
|
||||
|
|
||||
buildForm() { |
|
||||
this.store |
|
||||
.dispatch(new GetProfile()) |
|
||||
.pipe( |
|
||||
withLatestFrom(this.profile$), |
|
||||
take(1) |
|
||||
) |
|
||||
.subscribe(([, profile]) => { |
|
||||
this.form = this.fb.group({ |
|
||||
userName: [profile.userName, [required, maxLength(256)]], |
|
||||
email: [profile.email, [required, email, maxLength(256)]], |
|
||||
name: [profile.name || '', [maxLength(64)]], |
|
||||
surname: [profile.surname || '', [maxLength(64)]], |
|
||||
phoneNumber: [profile.phoneNumber || '', [maxLength(16)]] |
|
||||
}); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
submit() { |
|
||||
if (this.form.invalid) return; |
|
||||
this.modalBusy = true; |
|
||||
|
|
||||
this.store.dispatch(new UpdateProfile(this.form.value)).subscribe(() => { |
|
||||
this.modalBusy = false; |
|
||||
this.visible = false; |
|
||||
this.form.reset(); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
openModal() { |
|
||||
this.buildForm(); |
|
||||
this.visible = true; |
|
||||
} |
|
||||
|
|
||||
ngOnChanges({ visible }: SimpleChanges): void { |
|
||||
if (!visible) return; |
|
||||
|
|
||||
if (visible.currentValue) { |
|
||||
this.openModal(); |
|
||||
} else if (visible.currentValue === false && this.visible) { |
|
||||
this.visible = false; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
Loading…
Reference in new issue