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