mirror of https://github.com/Squidex/squidex.git
committed by
GitHub
113 changed files with 1413 additions and 3862 deletions
@ -1,7 +0,0 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<configuration> |
|||
<packageSources> |
|||
<add key="myget.org" value="https://dotnet.myget.org/F/orleans-ci/api/v3/index.json" protocolVersion="3" /> |
|||
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" /> |
|||
</packageSources> |
|||
</configuration> |
|||
@ -1 +1 @@ |
|||
<iframe #iframe scrolling="no" [attr.src]="sanitizedUrl()" width="100%"></iframe> |
|||
<iframe #iframe scrolling="no" [attr.src]="sanitizedUrl" width="100%"></iframe> |
|||
|
|||
@ -0,0 +1,31 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
|||
*/ |
|||
|
|||
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http'; |
|||
import { Injectable} from '@angular/core'; |
|||
import { Observable } from 'rxjs'; |
|||
import { finalize } from 'rxjs/operators'; |
|||
|
|||
import { LoadingService, MathHelper } from './../../internal'; |
|||
|
|||
@Injectable() |
|||
export class LoadingInterceptor implements HttpInterceptor { |
|||
constructor( |
|||
private readonly loadingService: LoadingService |
|||
) { |
|||
} |
|||
|
|||
public intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { |
|||
const id = MathHelper.guid(); |
|||
|
|||
this.loadingService.startLoading(id); |
|||
|
|||
return next.handle(req).pipe(finalize(() => { |
|||
this.loadingService.completeLoading(id); |
|||
})); |
|||
} |
|||
} |
|||
@ -0,0 +1,114 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
|||
*/ |
|||
|
|||
import { Event, NavigationError, NavigationStart } from '@angular/router'; |
|||
import { Subject } from 'rxjs'; |
|||
|
|||
import { LoadingService, LoadingServiceFactory } from './loading.service'; |
|||
|
|||
describe('LoadingService', () => { |
|||
let events = new Subject<Event>(); |
|||
|
|||
it('should instantiate from factory', () => { |
|||
const loadingService = LoadingServiceFactory(<any>{ events }); |
|||
|
|||
expect(loadingService).toBeDefined(); |
|||
}); |
|||
|
|||
it('should instantiate', () => { |
|||
const loadingService = new LoadingService(<any>{ events }); |
|||
|
|||
expect(loadingService).toBeDefined(); |
|||
|
|||
loadingService.ngOnDestroy(); |
|||
}); |
|||
|
|||
it('should set to loaded', () => { |
|||
const loadingService = new LoadingService(<any>{ events }); |
|||
|
|||
let state = false; |
|||
|
|||
loadingService.loading.subscribe(v => state = v); |
|||
loadingService.startLoading('1'); |
|||
|
|||
expect(state).toBeTruthy(); |
|||
}); |
|||
|
|||
it('should set to loaded on navigation start', () => { |
|||
const loadingService = new LoadingService(<any>{ events }); |
|||
|
|||
let state = false; |
|||
|
|||
loadingService.loading.subscribe(v => state = v); |
|||
|
|||
events.next(new NavigationStart(0, '')); |
|||
|
|||
expect(state).toBeTruthy(); |
|||
}); |
|||
|
|||
it('should not unset from loaded immediately', () => { |
|||
const loadingService = new LoadingService(<any>{ events }); |
|||
|
|||
let state = false; |
|||
|
|||
loadingService.loading.subscribe(v => state = v); |
|||
loadingService.startLoading('1'); |
|||
loadingService.completeLoading('1'); |
|||
|
|||
expect(state).toBeTruthy(); |
|||
}); |
|||
|
|||
it('should not unset from loaded delayed', (cb) => { |
|||
const loadingService = new LoadingService(<any>{ events }); |
|||
|
|||
let state = false; |
|||
|
|||
loadingService.loading.subscribe(v => state = v); |
|||
loadingService.startLoading('1'); |
|||
loadingService.completeLoading('1'); |
|||
|
|||
setTimeout(() => { |
|||
expect(state).toBeFalsy(); |
|||
|
|||
cb(); |
|||
}, 400); |
|||
}); |
|||
|
|||
it('should not unset from loaded delayed on navigation event', (cb) => { |
|||
const loadingService = new LoadingService(<any>{ events }); |
|||
|
|||
let state = false; |
|||
|
|||
loadingService.loading.subscribe(v => state = v); |
|||
events.next(new NavigationStart(0, '')); |
|||
events.next(new NavigationError(0, '', 0)); |
|||
|
|||
setTimeout(() => { |
|||
expect(state).toBeFalsy(); |
|||
|
|||
cb(); |
|||
}, 400); |
|||
}); |
|||
|
|||
it('should set back to loaded after several completions', (cb) => { |
|||
const loadingService = new LoadingService(<any>{ events }); |
|||
|
|||
let state = false; |
|||
|
|||
loadingService.loading.subscribe(v => state = v); |
|||
loadingService.startLoading('1'); |
|||
loadingService.completeLoading('1'); |
|||
loadingService.completeLoading('1'); |
|||
loadingService.startLoading('2'); |
|||
|
|||
setTimeout(() => { |
|||
expect(state).toBeTruthy(); |
|||
|
|||
cb(); |
|||
}, 400); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,68 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
|||
*/ |
|||
|
|||
import { Injectable, OnDestroy } from '@angular/core'; |
|||
import { NavigationCancel, NavigationEnd, NavigationError, NavigationStart, Router } from '@angular/router'; |
|||
import { BehaviorSubject, Observable, Subscription } from 'rxjs'; |
|||
import { map } from 'rxjs/operators'; |
|||
|
|||
import { Types } from './../utils/types'; |
|||
|
|||
export const LoadingServiceFactory = (router: Router) => { |
|||
return new LoadingService(router); |
|||
}; |
|||
|
|||
@Injectable() |
|||
export class LoadingService implements OnDestroy { |
|||
private readonly routerSubscription: Subscription; |
|||
private readonly loading$ = new BehaviorSubject(0); |
|||
private readonly loadingOperations: { [key: string]: boolean } = {}; |
|||
|
|||
public get loading(): Observable<boolean> { |
|||
return this.loading$.pipe(map(x => x > 0)); |
|||
} |
|||
|
|||
constructor(router: Router) { |
|||
this.routerSubscription = |
|||
router.events.subscribe(event => { |
|||
if (Types.is(event, NavigationStart)) { |
|||
this.startLoading(event.id.toString()); |
|||
} else if ( |
|||
Types.is(event, NavigationEnd) || |
|||
Types.is(event, NavigationCancel) || |
|||
Types.is(event, NavigationError)) { |
|||
this.completeLoading(event.id.toString()); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
public ngOnDestroy() { |
|||
this.routerSubscription.unsubscribe(); |
|||
} |
|||
|
|||
public startLoading(key: string) { |
|||
if (!this.loadingOperations[key]) { |
|||
this.loadingOperations[key] = true; |
|||
|
|||
this.loading$.next(this.loading$.value + 1); |
|||
} |
|||
} |
|||
|
|||
public completeLoading(key: string) { |
|||
if (this.loadingOperations[key]) { |
|||
delete this.loadingOperations[key]; |
|||
|
|||
setTimeout(() => { |
|||
const value = this.loading$.value; |
|||
|
|||
if (value > 0) { |
|||
this.loading$.next(value - 1); |
|||
} |
|||
}, 250); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,105 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
|||
*/ |
|||
|
|||
import { Router, RouterStateSnapshot, UrlSegment } from '@angular/router'; |
|||
import { of } from 'rxjs'; |
|||
import { IMock, It, Mock, Times } from 'typemoq'; |
|||
|
|||
import { SchemaDetailsDto } from './../services/schemas.service'; |
|||
import { SchemasState } from './../state/schemas.state'; |
|||
import { SchemaMustNotBeSingletonGuard } from './schema-must-not-be-singleton.guard'; |
|||
|
|||
describe('SchemaMustNotBeSingletonGuard', () => { |
|||
const route: any = { |
|||
params: { |
|||
schemaName: '123' |
|||
}, |
|||
url: [ |
|||
new UrlSegment('schemas', {}), |
|||
new UrlSegment('name', {}), |
|||
new UrlSegment('new', {}) |
|||
] |
|||
}; |
|||
|
|||
let schemasState: IMock<SchemasState>; |
|||
let router: IMock<Router>; |
|||
let schemaGuard: SchemaMustNotBeSingletonGuard; |
|||
|
|||
beforeEach(() => { |
|||
router = Mock.ofType<Router>(); |
|||
schemasState = Mock.ofType<SchemasState>(); |
|||
schemaGuard = new SchemaMustNotBeSingletonGuard(schemasState.object, router.object); |
|||
}); |
|||
|
|||
it('should subscribe to schema and return true when not singleton', () => { |
|||
const state: RouterStateSnapshot = <any>{ url: 'schemas/name/' }; |
|||
|
|||
schemasState.setup(x => x.selectedSchema) |
|||
.returns(() => of(<SchemaDetailsDto>{ id: '123', isSingleton: false })); |
|||
|
|||
let result: boolean; |
|||
|
|||
schemaGuard.canActivate(route, state).subscribe(x => { |
|||
result = x; |
|||
}).unsubscribe(); |
|||
|
|||
expect(result!).toBeTruthy(); |
|||
|
|||
router.verify(x => x.navigate(It.isAny()), Times.never()); |
|||
}); |
|||
|
|||
it('should subscribe to schema and return false when not found', () => { |
|||
const state: RouterStateSnapshot = <any>{ url: 'schemas/name/' }; |
|||
|
|||
schemasState.setup(x => x.selectedSchema) |
|||
.returns(() => of(null)); |
|||
|
|||
let result: boolean; |
|||
|
|||
schemaGuard.canActivate(route, state).subscribe(x => { |
|||
result = x; |
|||
}).unsubscribe(); |
|||
|
|||
expect(result!).toBeFalsy(); |
|||
|
|||
router.verify(x => x.navigate(It.isAny()), Times.never()); |
|||
}); |
|||
|
|||
it('should redirect to content when singleton', () => { |
|||
const state: RouterStateSnapshot = <any>{ url: 'schemas/name/' }; |
|||
|
|||
schemasState.setup(x => x.selectedSchema) |
|||
.returns(() => of(<SchemaDetailsDto>{ id: '123', isSingleton: true })); |
|||
|
|||
let result: boolean; |
|||
|
|||
schemaGuard.canActivate(route, state).subscribe(x => { |
|||
result = x; |
|||
}).unsubscribe(); |
|||
|
|||
expect(result!).toBeFalsy(); |
|||
|
|||
router.verify(x => x.navigate([state.url, '123']), Times.once()); |
|||
}); |
|||
|
|||
it('should redirect to content when singleton on new page', () => { |
|||
const state: RouterStateSnapshot = <any>{ url: 'schemas/name/new/' }; |
|||
|
|||
schemasState.setup(x => x.selectedSchema) |
|||
.returns(() => of(<SchemaDetailsDto>{ id: '123', isSingleton: true })); |
|||
|
|||
let result: boolean; |
|||
|
|||
schemaGuard.canActivate(route, state).subscribe(x => { |
|||
result = x; |
|||
}).unsubscribe(); |
|||
|
|||
expect(result!).toBeFalsy(); |
|||
|
|||
router.verify(x => x.navigate(['schemas/name/', '123']), Times.once()); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,42 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
|||
*/ |
|||
|
|||
import { Injectable } from '@angular/core'; |
|||
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router'; |
|||
import { Observable } from 'rxjs'; |
|||
import { map, take, tap } from 'rxjs/operators'; |
|||
|
|||
import { SchemasState } from './../state/schemas.state'; |
|||
|
|||
@Injectable() |
|||
export class SchemaMustNotBeSingletonGuard implements CanActivate { |
|||
constructor( |
|||
private readonly schemasState: SchemasState, |
|||
private readonly router: Router |
|||
) { |
|||
} |
|||
|
|||
public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> { |
|||
const result = |
|||
this.schemasState.selectedSchema.pipe( |
|||
take(1), |
|||
tap(dto => { |
|||
if (dto && dto.isSingleton) { |
|||
if (state.url.indexOf('/new') >= 0) { |
|||
const parentUrl = state.url.slice(0, state.url.indexOf(route.url[route.url.length - 1].path)); |
|||
|
|||
this.router.navigate([parentUrl, dto.id]); |
|||
} else { |
|||
this.router.navigate([state.url, dto.id]); |
|||
} |
|||
} |
|||
}), |
|||
map(s => !!s && !s.isSingleton)); |
|||
|
|||
return result; |
|||
} |
|||
} |
|||
File diff suppressed because it is too large
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue