Browse Source

Styling fixes

pull/1/head
Sebastian 10 years ago
parent
commit
3ea1a6ceed
  1. 8
      src/Squidex/app-config/webpack.common.js
  2. 4
      src/Squidex/app-config/webpack.dev.js
  3. 4
      src/Squidex/app-config/webpack.prod.js
  4. 4
      src/Squidex/app-config/webpack.test.js
  5. 4
      src/Squidex/app/app.component.spec.ts
  6. 6
      src/Squidex/app/app.module.ts
  7. 6
      src/Squidex/app/app.routes.ts
  8. 18
      src/Squidex/app/components/internal/app/dashboard/dashboard-page.component.ts
  9. 12
      src/Squidex/app/components/internal/apps/apps-page.component.ts
  10. 24
      src/Squidex/app/components/internal/internal-area.component.ts
  11. 6
      src/Squidex/app/components/layout/app-form.component.html
  12. 47
      src/Squidex/app/components/layout/app-form.component.ts
  13. 12
      src/Squidex/app/components/layout/apps-menu-list.component.html
  14. 19
      src/Squidex/app/components/layout/apps-menu-list.component.scss
  15. 21
      src/Squidex/app/components/layout/apps-menu-list.component.ts
  16. 21
      src/Squidex/app/components/layout/apps-menu.component.html
  17. 17
      src/Squidex/app/components/layout/apps-menu.component.scss
  18. 49
      src/Squidex/app/components/layout/apps-menu.component.ts
  19. 1
      src/Squidex/app/components/layout/declarations.ts
  20. 3
      src/Squidex/app/components/layout/module.ts
  21. 6
      src/Squidex/app/components/layout/profile-menu.component.html
  22. 27
      src/Squidex/app/components/layout/profile-menu.component.ts
  23. 4
      src/Squidex/app/components/layout/search-form.component.ts
  24. 16
      src/Squidex/app/framework/angular/action.ts
  25. 4
      src/Squidex/app/framework/angular/cloak.directive.spec.ts
  26. 9
      src/Squidex/app/framework/angular/color-picker.component.ts
  27. 2
      src/Squidex/app/framework/angular/date-time.pipes.spec.ts
  28. 2
      src/Squidex/app/framework/angular/image-drop.directive.ts
  29. 34
      src/Squidex/app/framework/angular/modal-view.directive.ts
  30. 4
      src/Squidex/app/framework/angular/money.pipe.spec.ts
  31. 6
      src/Squidex/app/framework/angular/shortcut.component.ts
  32. 4
      src/Squidex/app/framework/angular/slider.component.ts
  33. 8
      src/Squidex/app/framework/angular/user-report.component.ts
  34. 2
      src/Squidex/app/framework/angular/validators.ts
  35. 4
      src/Squidex/app/framework/configurations.ts
  36. 6
      src/Squidex/app/framework/services/drag.service.spec.ts
  37. 2
      src/Squidex/app/framework/services/local-store.service.spec.ts
  38. 2
      src/Squidex/app/framework/services/title.service.ts
  39. 30
      src/Squidex/app/framework/utils/color.ts
  40. 6
      src/Squidex/app/framework/utils/date-time.spec.ts
  41. 2
      src/Squidex/app/framework/utils/duration.spec.ts
  42. 2
      src/Squidex/app/framework/utils/immutable-id-map.spec.ts
  43. 2
      src/Squidex/app/framework/utils/immutable-object.ts
  44. 4
      src/Squidex/app/framework/utils/modal-view.ts
  45. 6
      src/Squidex/app/framework/utils/rect2.spec.ts
  46. 4
      src/Squidex/app/framework/utils/vec2.ts
  47. 38
      src/Squidex/app/shared/guards/app-must-exist.guard.ts
  48. 2
      src/Squidex/app/shared/guards/must-be-authenticated.guard.ts
  49. 2
      src/Squidex/app/shared/guards/must-be-not-authenticated.guard.ts
  50. 1
      src/Squidex/app/shared/index.ts
  51. 44
      src/Squidex/app/shared/services/apps-store.service.spec.ts
  52. 47
      src/Squidex/app/shared/services/apps-store.service.ts
  53. 4
      src/Squidex/app/shared/services/apps.service.ts
  54. 28
      src/Squidex/app/shared/services/auth.service.ts
  55. 1
      src/Squidex/tsconfig.json
  56. 10
      src/Squidex/tslint.json

8
src/Squidex/app-config/webpack.common.js

@ -37,9 +37,9 @@ module.exports = {
helpers.root('app-libs')
],
moduleDirectories: [
"*",
"app/*",
"app/theme/*"
'*',
'app/*',
'app/theme/*'
]
},
@ -82,9 +82,11 @@ module.exports = {
}
]
},
sassLoader: {
includePaths: [helpers.root('app', 'theme')]
},
plugins: [
/**
* Plugin: CommonsChunkPlugin

4
src/Squidex/app-config/webpack.dev.js

@ -48,6 +48,10 @@ module.exports = webpackMerge(commonConfig, {
new ExtractTextPlugin('[name].css')
],
sassLoader: {
includePaths: [helpers.root('app', 'theme')]
},
tslint: {
/**
* Run tslint in production build, but do not fail if there is a warning.

4
src/Squidex/app-config/webpack.prod.js

@ -44,7 +44,7 @@ module.exports = webpackMerge(commonConfig, {
module: {
preLoaders: [{
test: /\.ts$/,
loader: "tslint"
loader: 'tslint'
}],
/**
@ -94,7 +94,7 @@ module.exports = webpackMerge(commonConfig, {
new ExtractTextPlugin('[name].[hash].css'),
function () {
this.plugin("done", function (stats) {
this.plugin('done', function (stats) {
if (stats.compilation.errors && stats.compilation.errors.length && process.argv.indexOf('--watch') == -1) {
console.log(stats.compilation.errors);

4
src/Squidex/app-config/webpack.test.js

@ -62,6 +62,10 @@ module.exports = {
}
]
},
sassLoader: {
includePaths: [helpers.root('app', 'theme')]
},
plugins: [
/**

4
src/Squidex/app/app.component.spec.ts

@ -7,7 +7,7 @@ import { AppComponent } from './app.component';
describe('App', () => {
beforeEach(() => {
TestBed.configureTestingModule({
TestBed.configureTestingModule({
declarations: [
AppComponent
],
@ -20,7 +20,7 @@ describe('App', () => {
]
});
});
it('should work', () => {
const fixture = TestBed.createComponent(AppComponent);

6
src/Squidex/app/app.module.ts

@ -10,8 +10,9 @@ import * as Ng2Browser from '@angular/platform-browser';
import { AppComponent } from './app.component';
import {
import {
ApiUrlConfig,
AppMustExistGuard,
AppsStoreService,
AppsService,
AuthService,
@ -53,6 +54,7 @@ const baseUrl = window.location.protocol + '//' + window.location.host + '/';
providers: [
AppsStoreService,
AppsService,
AppMustExistGuard,
AuthService,
MustBeAuthenticatedGuard,
MustBeNotAuthenticatedGuard,
@ -61,7 +63,7 @@ const baseUrl = window.location.protocol + '//' + window.location.host + '/';
{ provide: CurrencyConfig, useValue: new CurrencyConfig('EUR', '€', true) },
{ provide: DecimalSeparatorConfig, useValue: new DecimalSeparatorConfig('.') },
{ provide: DragService, useFactory: DragServiceFactory },
{ provide: TitlesConfig, useValue: new TitlesConfig({}, null, 'Squidex Headless CMS') }
{ provide: TitlesConfig, useValue: new TitlesConfig({}, undefined, 'Squidex Headless CMS') }
],
bootstrap: [AppComponent]
})

6
src/Squidex/app/app.routes.ts

@ -18,7 +18,8 @@ import {
NotFoundPageComponent
} from './components';
import {
import {
AppMustExistGuard,
MustBeAuthenticatedGuard,
MustBeNotAuthenticatedGuard
} from './shared';
@ -27,7 +28,7 @@ export const routes: Ng2Router.Routes = [
{
path: '',
component: HomePageComponent,
canActivate: [MustBeNotAuthenticatedGuard],
canActivate: [MustBeNotAuthenticatedGuard]
},
{
path: 'app',
@ -41,6 +42,7 @@ export const routes: Ng2Router.Routes = [
{
path: ':appName',
component: AppAreaComponent,
canActivate: [AppMustExistGuard],
children: [
{
path: '',

18
src/Squidex/app/components/internal/app/dashboard/dashboard-page.component.ts

@ -6,9 +6,25 @@
*/
import * as Ng2 from '@angular/core';
import * as Ng2Router from '@angular/router';
import { TitleService } from 'shared';
@Ng2.Component({
selector: 'sqx-dashboard-page',
template
})
export class DashboardComponent { }
export class DashboardComponent implements Ng2.OnInit {
constructor(
private readonly titles: TitleService,
private readonly route: Ng2Router.ActivatedRoute
) {
}
public ngOnInit() {
const appName = this.route.snapshot.params['appName'];
this.titles.setTitle('{appName} | Dashboard', { appName: appName });
}
}

12
src/Squidex/app/components/internal/apps/apps-page.component.ts

@ -7,10 +7,11 @@
import * as Ng2 from '@angular/core';
import {
fadeAnimation,
import {
AppsStoreService,
fadeAnimation,
ModalView,
TitleService
TitleService
} from 'shared';
@Ng2.Component({
@ -25,11 +26,14 @@ export class AppsPageComponent implements Ng2.OnInit {
public modalDialog = new ModalView();
constructor(
private readonly title: TitleService
private readonly title: TitleService,
private readonly appsStore: AppsStoreService,
) {
}
public ngOnInit() {
this.appsStore.selectApp(null);
this.title.setTitle('Apps');
}
}

24
src/Squidex/app/components/internal/internal-area.component.ts

@ -7,29 +7,9 @@
import * as Ng2 from '@angular/core';
import {
fadeAnimation,
ModalView,
TitleService
} from 'shared';
@Ng2.Component({
selector: 'sqx-internal-area',
styles,
template,
animations: [
fadeAnimation()
]
template
})
export class InternalAreaComponent implements Ng2.OnInit {
public modalDialog = new ModalView();
constructor(
private readonly title: TitleService
) {
}
public ngOnInit() {
this.title.setTitle('Apps');
}
}
export class InternalAreaComponent { }

6
src/Squidex/app/components/layout/app-form.component.html

@ -1,7 +1,7 @@
<form [formGroup]="createForm" (ngSubmit)="submit()">
<div *ngIf="creationError | async" [@fade]>
<div *ngIf="creationError" [@fade]>
<div class="form-error">
{{creationError | async}}
{{creationError}}
</div>
</div>
@ -25,7 +25,7 @@
<input type="text" class="form-control" id="app-name" formControlName="name" />
<span class="form-hint">
The app name becomes part of the api url, e.g, https://<b>{{appName | async}}</b>.squidex.io/.<br />
The app name becomes part of the api url, e.g, https://<b>{{appName}}</b>.squidex.io/.<br />
It must contain lower case letters (a-z), numbers and dashes only, and cannot be longer than 40 characters. The name cannot be changed later.
</span>
</div>

47
src/Squidex/app/components/layout/app-form.component.ts

@ -8,12 +8,11 @@
import * as Ng2 from '@angular/core';
import * as Ng2Forms from '@angular/forms';
import { BehaviorSubject } from 'rxjs';
import {
AppCreateDto,
import {
AppDto,
AppCreateDto,
AppsStoreService,
fadeAnimation
fadeAnimation
} from 'shared';
const FALLBACK_NAME = 'my-app';
@ -25,34 +24,30 @@ const FALLBACK_NAME = 'my-app';
fadeAnimation()
]
})
export class AppFormComponent {
export class AppFormComponent implements Ng2.OnInit {
@Ng2.Input()
public showClose = false;
@Ng2.Output()
public created = new Ng2.EventEmitter();
public created = new Ng2.EventEmitter<AppDto>();
@Ng2.Output()
public cancelled = new Ng2.EventEmitter();
public createForm =
this.formBuilder.group({
name: ['',
name: ['',
[
Ng2Forms.Validators.required,
Ng2Forms.Validators.maxLength(40),
Ng2Forms.Validators.pattern('[a-z0-9]+(\-[a-z0-9]+)*'),
Ng2Forms.Validators.pattern('[a-z0-9]+(\-[a-z0-9]+)*')
]]
});
public appName =
this.createForm.controls['name'].valueChanges.map(name => name || FALLBACK_NAME).publishBehavior(FALLBACK_NAME).refCount();
public creating =
new BehaviorSubject<boolean>(false);
public appName = FALLBACK_NAME;
public creationError =
new BehaviorSubject<string>('');
public creating = false;
public creationError = '';
constructor(
private readonly appsStore: AppsStoreService,
@ -60,30 +55,36 @@ export class AppFormComponent {
) {
}
public ngOnInit() {
this.createForm.controls['name'].valueChanges.subscribe(value => {
this.appName = value;
});
}
public submit() {
this.createForm.markAsDirty();
if (this.createForm.valid) {
this.createForm.disable();
this.creating.next(true);
this.creating = true;
const dto = new AppCreateDto(this.createForm.controls['name'].value);
this.appsStore.createApp(dto)
.subscribe(() => {
.subscribe(app => {
this.createForm.reset();
this.created.emit();
this.created.emit(app);
}, error => {
this.reset();
this.creationError.next(error);
this.creationError = error;
});
}
}
private reset() {
this.createForm.enable();
this.creating.next(false);
this.creationError.next(null);
this.creating = false;
this.creationError = '';
}
public cancel() {

12
src/Squidex/app/components/layout/apps-menu-list.component.html

@ -1,12 +0,0 @@
<a class="dropdown-item all-apps" [routerLink]="['/app']">
<span class="all-apps-text">All Apps</span>
<span class="all-apps-pill tag tag-pill tag-default">{{apps.length || 0}}</span>
</a>
<div class="dropdown-divider"></div>
<div *ngIf="apps && apps.length > 0">
<a class="dropdown-item" *ngFor="let app of apps" [routerLink]="['/app', app.name]">{{app.name}}</a>
<div class="dropdown-divider"></div>
</div>

19
src/Squidex/app/components/layout/apps-menu-list.component.scss

@ -1,19 +0,0 @@
@import '_vars';
@import '_mixins';
.all-apps {
& {
position: relative;
}
&-text {
font-weight: bold;
}
&-pill {
@include absolute(6px, 10px, auto, auto);
background: $theme-blue-lighter;
border: 0;
color: $theme-blue;
}
}

21
src/Squidex/app/components/layout/apps-menu-list.component.ts

@ -1,21 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import * as Ng2 from '@angular/core';
import { AppDto } from 'shared';
@Ng2.Component({
selector: 'sqx-apps-menu-list',
styles,
template
})
export class AppsMenuListComponent {
@Ng2.Input()
public apps: AppDto[];
}

21
src/Squidex/app/components/layout/apps-menu.component.html

@ -1,9 +1,20 @@
<ul class="nav navbar-nav" *ngIf="apps">
<li class="nav-item dropdown">
<span class="nav-link dropdown-toggle" id="app-name" (click)="modalMenu.toggle()">{{app | async}}</span>
<span class="nav-link dropdown-toggle" id="app-name" (click)="modalMenu.toggle()">{{appName}}</span>
<div class="dropdown-menu" *sqxModalView="modalMenu" [@fade]>
<sqx-apps-menu-list [apps]="apps | async"></sqx-apps-menu-list>
<div class="dropdown-menu" *sqxModalView="modalMenu" closeAlways="true" [@fade]>
<a class="dropdown-item all-apps" [routerLink]="['/app']">
<span class="all-apps-text">All Apps</span>
<span class="all-apps-pill tag tag-pill tag-default">{{apps.length || 0}}</span>
</a>
<div class="dropdown-divider"></div>
<div *ngIf="apps && apps.length > 0">
<a class="dropdown-item" *ngFor="let app of apps" [routerLink]="['/app', app.name]">{{app.name}}</a>
<div class="dropdown-divider"></div>
</div>
<div class="drodown-button">
<button class="btn btn-block btn-success" id="app-create" (click)="createApp()"><i class="icon-plus"></i> Create New App</button>
@ -25,8 +36,8 @@
<div class="modal-body">
<sqx-app-form
(created)="modalDialog.hide()"
(cancelled)="modalDialog.hide()"></sqx-app-form>
(created)="onAppCreationCompleted($event)"
(cancelled)="onAppCreationCancelled()"></sqx-app-form>
</div>
</div>
</div>

17
src/Squidex/app/components/layout/apps-menu.component.scss

@ -33,6 +33,23 @@
}
}
.all-apps {
& {
position: relative;
}
&-text {
font-weight: bold;
}
&-pill {
@include absolute(6px, 10px, auto, auto);
background: $theme-blue-lighter;
border: 0;
color: $theme-blue;
}
}
#app-name {
& {
@include transition(opacity .4 ease);

49
src/Squidex/app/components/layout/apps-menu.component.ts

@ -8,10 +8,11 @@
import * as Ng2 from '@angular/core';
import * as Ng2Router from '@angular/router';
import {
import {
AppDto,
AppsStoreService,
fadeAnimation,
ModalView
fadeAnimation,
ModalView
} from 'shared';
const FALLBACK_NAME = 'Apps Overview';
@ -24,19 +25,16 @@ const FALLBACK_NAME = 'Apps Overview';
fadeAnimation()
]
})
export class AppsMenuComponent {
public modalMenu = new ModalView();
export class AppsMenuComponent implements Ng2.OnInit, Ng2.OnDestroy {
private appsSubscription: any | null;
private appNameSubscription: any | null;
public modalMenu = new ModalView(false, true);
public modalDialog = new ModalView();
public apps =
this.appsStore.apps.map(a => a || []);
public apps: AppDto[] = [];
public app =
this.router.events.switchMap(() => {
return this.router.routerState.root.params.map(p => {
return p['my_named'] || FALLBACK_NAME;
} );
});
public appName = FALLBACK_NAME;
constructor(
private readonly appsStore: AppsStoreService,
@ -45,6 +43,31 @@ export class AppsMenuComponent {
) {
}
public ngOnInit() {
this.appsSubscription =
this.appsStore.apps.subscribe(apps => {
this.apps = apps || [];
});
this.appNameSubscription =
this.appsStore.selectedApp.subscribe(selectedApp => this.appName = selectedApp ? selectedApp.name : FALLBACK_NAME);
}
public ngOnDestroy() {
this.appsSubscription.unsubscribe();
this.appNameSubscription.unsubscribe();
}
public onAppCreationCancelled() {
this.modalDialog.hide();
}
public onAppCreationCompleted(app: AppDto) {
this.router.navigate(['/app', app.name]);
this.modalDialog.hide();
}
public createApp() {
this.modalMenu.hide();
this.modalDialog.show();

1
src/Squidex/app/components/layout/declarations.ts

@ -7,6 +7,5 @@
export * from './app-form.component';
export * from './apps-menu.component';
export * from './apps-menu-list.component';
export * from './profile-menu.component';
export * from './search-form.component';

3
src/Squidex/app/components/layout/module.ts

@ -12,7 +12,6 @@ import { SqxFrameworkModule } from 'shared';
import {
AppFormComponent,
AppsMenuComponent,
AppsMenuListComponent,
ProfileMenuComponent,
SearchFormComponent
} from './declarations';
@ -24,14 +23,12 @@ import {
declarations: [
AppFormComponent,
AppsMenuComponent,
AppsMenuListComponent,
ProfileMenuComponent,
SearchFormComponent,
],
exports: [
AppFormComponent,
AppsMenuComponent,
AppsMenuListComponent,
ProfileMenuComponent,
SearchFormComponent,
]

6
src/Squidex/app/components/layout/profile-menu.component.html

@ -1,12 +1,12 @@
<ul class="nav navbar-nav">
<li class="nav-item dropdown">
<span class="nav-link dropdown-toggle" (click)="modalMenu.toggle()">
<img [attr.src]="(pictureUrl | async)" />
<img [attr.src]="profilePictureUrl" />
<span>{{displayName | async}}</span>
<span>{{profileDisplayName}}</span>
</span>
<div class="dropdown-menu" *sqxModalView="modalMenu" [@fade]>
<div class="dropdown-menu" *sqxModalView="modalMenu" closeAlways="true" [@fade]>
<a class="dropdown-item" (click)="logout()">Logout</a>
</div>
</li>

27
src/Squidex/app/components/layout/profile-menu.component.ts

@ -21,20 +21,35 @@ import {
fadeAnimation()
]
})
export class ProfileMenuComponent {
public modalMenu = new ModalView();
export class ProfileMenuComponent implements Ng2.OnInit, Ng2.OnDestroy {
private authenticationSubscription: any | null;
public displayName
= this.auth.isAuthenticatedChanges.map(t => t ? this.auth.user.displayName : null);
public modalMenu = new ModalView(false, true);
public pictureUrl
= this.auth.isAuthenticatedChanges.map(t => t ? this.auth.user.pictureUrl : null);
public profileDisplayName = '';
public profilePictureUrl = '';
constructor(
private readonly auth: AuthService
) {
}
public ngOnInit() {
this.authenticationSubscription =
this.auth.isAuthenticatedChanges.subscribe(() => {
const user = this.auth.user;
if (user) {
this.profilePictureUrl = user.pictureUrl;
this.profileDisplayName = user.displayName;
}
});
}
public ngOnDestroy() {
this.authenticationSubscription.unsubscribe();
}
public logout() {
this.auth.logout();
}

4
src/Squidex/app/components/layout/search-form.component.ts

@ -12,6 +12,4 @@ import * as Ng2 from '@angular/core';
styles,
template
})
export class SearchFormComponent {
}
export class SearchFormComponent { }

16
src/Squidex/app/framework/angular/action.ts

@ -7,14 +7,14 @@
const EMPTY_FUNC = () => { };
export function Action() {
return function (target: any, key: string) {
export const Action = () => {
return (target: any, key: string) => {
let observable: any;
let instance: any;
let subscriptions: any;
let subscription: any;
function subscribe() {
const subscribe = () => {
const store = instance.store;
if (store && observable && observable.subscribe && typeof observable.subscribe === 'function') {
@ -24,7 +24,7 @@ export function Action() {
}
};
function unsubscribe() {
const unsubscribe = () => {
if (subscription) {
subscription.unsubscribe();
@ -34,16 +34,16 @@ export function Action() {
if (delete target[key]) {
Object.defineProperty(target, key, {
get: function () {
get: () => {
return observable;
},
set: function (v) {
set: (v) => {
instance = this;
if (!instance.___subscriptions) {
instance.___subscriptions = [];
let destroy = instance.ngOnDestroy ? instance.ngOnDestroy.bind(instance) : EMPTY_FUNC;
const destroy = instance.ngOnDestroy ? instance.ngOnDestroy.bind(instance) : EMPTY_FUNC;
instance.ngOnDestroy = () => {
for (let s of subscriptions) {
@ -66,4 +66,4 @@ export function Action() {
});
}
};
}
};

4
src/Squidex/app/framework/angular/cloak.directive.spec.ts

@ -20,8 +20,8 @@ describe('CloakDirective', () => {
}
}
};
new CloakDirective(element).ngOnInit();
new CloakDirective(element).ngOnInit();
expect(called).toBeTruthy();
});

9
src/Squidex/app/framework/angular/color-picker.component.ts

@ -7,13 +7,8 @@
import * as Ng2 from '@angular/core';
import {
Color }
from './../utils/color';
import {
ColorPalette
} from './../utils/color-palette';
import { Color } from './../utils/color';
import { ColorPalette } from './../utils/color-palette';
@Ng2.Component({
selector: 'sqx-color-picker',

2
src/Squidex/app/framework/angular/date-time.pipes.spec.ts

@ -78,7 +78,7 @@ describe('ShortDatePipe', () => {
describe('ShortTimePipe', () => {
it('should format to short time string', () => {
const pipe = new ShortTimePipe();
const actual = pipe.transform(dateTime, []);
const expected = '12:13';

2
src/Squidex/app/framework/angular/image-drop.directive.ts

@ -110,7 +110,7 @@ export class ImageDropDirective {
private getRelativeCoordinates(e: any, container: any): Vec2 {
const rect = container.getBoundingClientRect();
let pos = { x: 0, y: 0 };
const pos = { x: 0, y: 0 };
pos.x = !!e.touches ? e.touches[0].pageX : e.pageX;
pos.y = !!e.touches ? e.touches[0].pageY : e.pageY;

34
src/Squidex/app/framework/angular/modal-view.directive.ts

@ -16,11 +16,11 @@ export class ModalViewDirective implements Ng2.OnChanges, Ng2.OnInit, Ng2.OnDest
private subscription: any | null;
private isEnabled = true;
private clickHandler: Function | null;
private view: Ng2.EmbeddedViewRef<any> | null;
private renderedView: Ng2.EmbeddedViewRef<any> | null;
@Ng2.Input('sqxModalView')
public modalView: ModalView;
constructor(
private readonly templateRef: Ng2.TemplateRef<any>,
private readonly renderer: Ng2.Renderer,
@ -29,20 +29,26 @@ export class ModalViewDirective implements Ng2.OnChanges, Ng2.OnInit, Ng2.OnDest
}
public ngOnInit() {
this.clickHandler =
this.clickHandler =
this.renderer.listenGlobal('document', 'click', (event: MouseEvent) => {
if (!event.target || this.view === null) {
if (!event.target || this.renderedView === null) {
return;
}
if (this.view.rootNodes.length === 0) {
if (this.renderedView.rootNodes.length === 0) {
return;
}
const clickedInside = this.view.rootNodes[0].contains(event.target);
if (!clickedInside && this.modalView && this.isEnabled) {
this.modalView.hide();
if (this.isEnabled) {
if (this.modalView.closeAlways) {
this.modalView.hide();
} else {
const clickedInside = this.renderedView.rootNodes[0].contains(event.target);
if (!clickedInside && this.modalView) {
this.modalView.hide();
}
}
}
});
}
@ -63,17 +69,15 @@ export class ModalViewDirective implements Ng2.OnChanges, Ng2.OnInit, Ng2.OnDest
if (this.modalView) {
this.subscription = this.modalView.isOpen.subscribe(isOpen => {
if (this.isEnabled) {
if (isOpen === (this.view !== null)) {
if (isOpen === (this.renderedView !== null)) {
return;
}
if (isOpen) {
this.view = this.viewContainer.createEmbeddedView(this.templateRef);
this.renderer.setElementStyle(this.view.rootNodes[0], 'display', 'block');
this.renderedView = this.viewContainer.createEmbeddedView(this.templateRef);
this.renderer.setElementStyle(this.renderedView.rootNodes[0], 'display', 'block');
} else {
this.view = null;
this.renderedView = null;
this.viewContainer.clear();
}

4
src/Squidex/app/framework/angular/money.pipe.spec.ts

@ -18,7 +18,7 @@ describe('MoneyPipe', () => {
expect(actual).toBe(expected);
});
it('should format money values with symbol after number and one decimal', () => {
const pipe = new MoneyPipe(new CurrencyConfig('EUR', '€'), new DecimalSeparatorConfig(','));
@ -36,7 +36,7 @@ describe('MoneyPipe', () => {
expect(actual).toBe(expected);
});
it('should format money values with symbol before number and one decimal', () => {
const pipe = new MoneyPipe(new CurrencyConfig('EUR', '€', false), new DecimalSeparatorConfig(','));

6
src/Squidex/app/framework/angular/shortcut.component.ts

@ -10,7 +10,7 @@ import * as Ng2 from '@angular/core';
import { ShortcutService } from './../services/shortcut.service';
@Ng2.Component({
selector: 'sqx-shortcut',
selector: 'sqx-shortcut',
template: ''
})
export class ShortcutComponent implements Ng2.OnInit, Ng2.OnDestroy {
@ -22,11 +22,11 @@ export class ShortcutComponent implements Ng2.OnInit, Ng2.OnDestroy {
@Ng2.Output()
public trigger = new Ng2.EventEmitter();
private lastKeys: string;
constructor(
private readonly shortcutService: ShortcutService,
private readonly shortcutService: ShortcutService,
private readonly zone: Ng2.NgZone
) {
}

4
src/Squidex/app/framework/angular/slider.component.ts

@ -25,10 +25,10 @@ export class SliderComponent implements Ng2.OnChanges {
public thumb: Ng2.ElementRef;
@Ng2.Input()
public min: number = 0;
public min = 0;
@Ng2.Input()
public max: number = 100;
public max = 100;
@Ng2.Input()
public value: number;

8
src/Squidex/app/framework/angular/user-report.component.ts

@ -10,11 +10,11 @@ import * as Ng2 from '@angular/core';
import { UserReportConfig } from './../configurations';
@Ng2.Component({
selector: 'sqx-user-report',
selector: 'sqx-user-report',
template: ''
})
export class UserReportComponent implements Ng2.OnInit {
constructor(config: UserReportConfig,
constructor(config: UserReportConfig,
private readonly renderer: Ng2.Renderer
) {
window['_urq'] = window['_urq'] || [];
@ -24,13 +24,13 @@ export class UserReportComponent implements Ng2.OnInit {
public ngOnInit() {
setTimeout(() => {
const url = 'https://cdn.userreport.com/userreport.js';
const script = document.createElement('script');
script.src = url;
script.async = true;
const node = document.getElementsByTagName('script')[0];
node.parentNode.insertBefore(script, node);
}, 4000);
}

2
src/Squidex/app/framework/angular/validators.ts

@ -10,7 +10,7 @@ import * as Ng2Forms from '@angular/forms';
export class Validators {
public static between(minValue: number, maxValue: number) {
return (control: Ng2Forms.AbstractControl): { [key: string]: any } => {
let n: number = control.value;
const n: number = control.value;
if (typeof n !== 'number') {
return { 'validNumber': false };

4
src/Squidex/app/framework/configurations.ts

@ -11,7 +11,7 @@ import * as Ng2 from '@angular/core';
export class ApiUrlConfig {
public readonly value: string;
constructor(value: string) {
constructor(value: string) {
if (value.indexOf('/', value.length - 1) < 0) {
value = value + '/';
}
@ -23,7 +23,7 @@ export class ApiUrlConfig {
if (path.indexOf('/') === 0) {
path = path.substr(1);
}
return this.value + path;
}
}

6
src/Squidex/app/framework/services/drag.service.spec.ts

@ -5,10 +5,10 @@
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import {
DragService,
import {
DragService,
DragServiceFactory,
DropEvent
DropEvent
} from './../';
describe('DragService', () => {

2
src/Squidex/app/framework/services/local-store.service.spec.ts

@ -51,7 +51,7 @@ describe('LocalStore', () => {
}
});
let returnedVal = localStoreService.get('mykey');
const returnedVal = localStoreService.get('mykey');
expect(passedKey).toBe('mykey');
expect(returnedVal).toBe('myval');

2
src/Squidex/app/framework/services/title.service.ts

@ -10,7 +10,7 @@ import * as Ng2 from '@angular/core';
@Ng2.Injectable()
export class TitlesConfig {
constructor(
public readonly value: { [key: string]: string },
public readonly value: { [key: string]: string },
public readonly prefix?: string,
public readonly suffix?: string) { }
}

30
src/Squidex/app/framework/utils/color.ts

@ -16,30 +16,24 @@ interface IColorDefinition {
const ColorDefinitions: IColorDefinition[] = [
{
regex: /^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/,
process: function (bits) {
return new Color(
parseInt(bits[1], 10) / 255,
parseInt(bits[2], 10) / 255,
parseInt(bits[3], 10) / 255);
}
process: bits => new Color(
parseInt(bits[1], 10) / 255,
parseInt(bits[2], 10) / 255,
parseInt(bits[3], 10) / 255)
},
{
regex: /^(\w{2})(\w{2})(\w{2})$/,
process: function (bits) {
return new Color(
parseInt(bits[1], 16) / 255,
parseInt(bits[2], 16) / 255,
parseInt(bits[3], 16) / 255);
}
process: bits => new Color(
parseInt(bits[1], 16) / 255,
parseInt(bits[2], 16) / 255,
parseInt(bits[3], 16) / 255)
},
{
regex: /^(\w{1})(\w{1})(\w{1})$/,
process: function (bits) {
return new Color(
parseInt(bits[1] + bits[1], 16) / 255,
parseInt(bits[2] + bits[2], 16) / 255,
parseInt(bits[3] + bits[3], 16) / 255);
}
process: bits => new Color(
parseInt(bits[1] + bits[1], 16) / 255,
parseInt(bits[2] + bits[2], 16) / 255,
parseInt(bits[3] + bits[3], 16) / 255)
}
];

6
src/Squidex/app/framework/utils/date-time.spec.ts

@ -73,7 +73,7 @@ describe('DateTime', () => {
it('should create yesterday instance correctly', () => {
const actual = DateTime.yesterday();
const expected = DateTime.today().addDays(-1);
expect(actual).toEqual(expected);
});
@ -81,7 +81,7 @@ describe('DateTime', () => {
const value = DateTime.parseISO_UTC('2013-10-16T12:13:14T');
const actual = value.toStringFormat('hh:mm');
const expected = '12:13';
expect(actual).toEqual(expected);
});
@ -89,7 +89,7 @@ describe('DateTime', () => {
const value = DateTime.parseISO_UTC('2013-10-16T12:13:14');
const actual = value.toString().substr(0, 19);
const expected = '2013-10-16T12:13:14';
expect(actual).toEqual(expected);
});

2
src/Squidex/app/framework/utils/duration.spec.ts

@ -43,7 +43,7 @@ describe('Duration', () => {
const d = s.addHours(1).addMinutes(1).addSeconds(60);
const duration = Duration.create(s, d);
const actual = duration.toString();
const expected = '1:02h';

2
src/Squidex/app/framework/utils/immutable-id-map.spec.ts

@ -266,7 +266,7 @@ describe('ImmutableIdMap', () => {
const items: MockupData[] = [];
for (let i = 0; i < size; i++) {
items.push(new MockupData('id' + i));
items.push(new MockupData(`id${i}`));
}
const list_1 = new ImmutableIdMap<MockupData>();

2
src/Squidex/app/framework/utils/immutable-object.ts

@ -11,7 +11,7 @@ export abstract class ImmutableObject {
protected afterClone() { }
protected cloned<T extends ImmutableObject>(updater: (instance: ImmutableObject) => void) {
let cloned = <T>this.clone();
const cloned = <T>this.clone();
updater(cloned);

4
src/Squidex/app/framework/utils/modal-view.ts

@ -14,7 +14,9 @@ export class ModalView {
return this.isOpen$.distinctUntilChanged();
}
constructor(isOpen = false) {
constructor(isOpen = false,
public readonly closeAlways = false
) {
this.isOpen$ = new BehaviorSubject(isOpen);
}

6
src/Squidex/app/framework/utils/rect2.spec.ts

@ -90,7 +90,7 @@ describe('Rect2', () => {
it('should return empty for no intersection', () => {
const rect = new Rect2(new Vec2(10, 20), new Vec2(50, 30));
const outer = new Rect2(new Vec2(100, 20), new Vec2(50, 30));
const actual = rect.intersect(outer);
const expected = Rect2.EMPTY;
@ -100,7 +100,7 @@ describe('Rect2', () => {
it('should return result for intersection', () => {
const rect = new Rect2(new Vec2(10, 20), new Vec2(50, 30));
const inner = new Rect2(new Vec2(35, 35), new Vec2(100, 30));
const actual = rect.intersect(inner);
const expected = new Rect2(new Vec2(35, 35), new Vec2(25, 15));
@ -171,7 +171,7 @@ describe('Rect2', () => {
it('should provide valid zero instance', () => {
const actual = Rect2.ZERO;
const expected = new Rect2(new Vec2(0, 0), new Vec2(0, 0));
expect(actual).toEqual(expected);
});

4
src/Squidex/app/framework/utils/vec2.ts

@ -95,7 +95,7 @@ export class Vec2 {
return new Vec2(MathHelper.roundToMultipleOf(this.x, 2), MathHelper.roundToMultipleOf(this.y, 2));
}
public static createRotated(vec: Vec2, center: Vec2, rotation: Rotation): Vec2 {
public static createRotated(vec: Vec2, center: Vec2, rotation: Rotation): Vec2 {
const x = vec.x - center.x;
const y = vec.y - center.y;
@ -106,7 +106,7 @@ export class Vec2 {
return result;
}
public static createMedian(...vecs: Vec2[]) {
public static createMedian(...vecs: Vec2[]) {
let medianX = 0;
let medianY = 0;

38
src/Squidex/app/shared/guards/app-must-exist.guard.ts

@ -0,0 +1,38 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import * as Ng2 from '@angular/core';
import * as Ng2Router from '@angular/router';
import { Observable } from 'rxjs';
import { AppsStoreService } from './../services/apps-store.service';
@Ng2.Injectable()
export class AppMustExistGuard implements Ng2Router.CanActivate {
constructor(
private readonly appsStore: AppsStoreService,
private readonly router: Ng2Router.Router
) {
}
public canActivate(route: Ng2Router.ActivatedRouteSnapshot, state: Ng2Router.RouterStateSnapshot): Observable<boolean> {
const appName = route.params['appName'];
const result =
this.appsStore.selectApp(appName)
.take(1)
.map(app => app !== null)
.do(hasApp => {
if (!hasApp) {
this.router.navigate(['/404']);
}
});
return result;
}
}

2
src/Squidex/app/shared/guards/must-be-authenticated.guard.ts

@ -9,7 +9,7 @@ import * as Ng2 from '@angular/core';
import * as Ng2Router from '@angular/router';
import { AuthService } from './../services/auth.service';
@Ng2.Injectable()
export class MustBeAuthenticatedGuard implements Ng2Router.CanActivate {
constructor(

2
src/Squidex/app/shared/guards/must-be-not-authenticated.guard.ts

@ -9,7 +9,7 @@ import * as Ng2 from '@angular/core';
import * as Ng2Router from '@angular/router';
import { AuthService } from './../services/auth.service';
@Ng2.Injectable()
export class MustBeNotAuthenticatedGuard implements Ng2Router.CanActivate {
constructor(

1
src/Squidex/app/shared/index.ts

@ -5,6 +5,7 @@
* Copyright (c) Sebastian Stehle. All rights reserved
*/
export * from './guards/app-must-exist.guard';
export * from './guards/must-be-authenticated.guard';
export * from './guards/must-be-not-authenticated.guard';
export * from './services/apps-store.service';

44
src/Squidex/app/shared/services/apps-store.service.spec.ts

@ -2,10 +2,10 @@ import * as TypeMoq from 'typemoq';
import { Observable } from 'rxjs';
import {
import {
AppCreateDto,
AppDto,
AppsStoreService,
AppsStoreService,
AppsService,
AuthService
} from './../';
@ -33,13 +33,13 @@ describe('AppsStoreService', () => {
const store = new AppsStoreService(authService.object, appsService.object);
let result1: AppDto[];
let result2: AppDto[];
let result1: AppDto[];
let result2: AppDto[];
store.apps.subscribe(x => {
result1 = x;
}).unsubscribe();
store.apps.subscribe(x => {
result2 = x;
}).unsubscribe();
@ -65,8 +65,8 @@ describe('AppsStoreService', () => {
const store = new AppsStoreService(authService.object, appsService.object);
let result1: AppDto[];
let result2: AppDto[];
let result1: AppDto[];
let result2: AppDto[];
store.apps.subscribe(x => {
result1 = x;
@ -92,15 +92,15 @@ describe('AppsStoreService', () => {
appsService.setup(x => x.getApps())
.returns(() => Observable.of(oldApps))
.verifiable(TypeMoq.Times.once());
appsService.setup(x => x.postApp(TypeMoq.It.isAny()))
.returns(() => Observable.of(newApp))
.verifiable(TypeMoq.Times.once());
const store = new AppsStoreService(authService.object, appsService.object);
let result1: AppDto[];
let result2: AppDto[];
let result1: AppDto[];
let result2: AppDto[];
store.apps.subscribe(x => {
result1 = x;
@ -129,7 +129,7 @@ describe('AppsStoreService', () => {
const store = new AppsStoreService(authService.object, appsService.object);
let result: AppDto[];
let result: AppDto[] = null;
store.createApp(new AppCreateDto('new-name')).subscribe(x => { });
@ -141,4 +141,26 @@ describe('AppsStoreService', () => {
appsService.verifyAll();
});
it('should select app', () => {
authService.setup(x => x.isAuthenticatedChanges)
.returns(() => Observable.of(true))
.verifiable(TypeMoq.Times.once());
appsService.setup(x => x.getApps())
.returns(() => Observable.of(oldApps))
.verifiable(TypeMoq.Times.once());
const store = new AppsStoreService(authService.object, appsService.object);
let result: AppDto = null;
store.selectApp('name').subscribe(app => {
result = app;
});
expect(result).toEqual(oldApps[0]);
appsService.verifyAll();
});
});

47
src/Squidex/app/shared/services/apps-store.service.ts

@ -7,11 +7,15 @@
import * as Ng2 from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import {
BehaviorSubject,
Observable,
Subject
} from 'rxjs';
import {
AppCreateDto,
AppDto,
import {
AppCreateDto,
AppDto,
AppsService
} from './apps.service';
@ -19,11 +23,28 @@ import { AuthService } from './auth.service';
@Ng2.Injectable()
export class AppsStoreService {
private lastApps: AppDto[] = null;
private readonly apps$ = new BehaviorSubject<AppDto[]>(null);
private lastApps: AppDto[] | null = null;
private readonly apps$ = new Subject<AppDto[]>();
private readonly appName$ = new BehaviorSubject<string | null>(null);
private readonly appsPublished$ =
this.apps$
.distinctUntilChanged()
.publishReplay(1)
.refCount();
private readonly selectedApp$ =
this.appsPublished$.combineLatest(this.appName$, (apps, name) => apps && name ? apps.find(x => x.name === name) || null : null)
.distinctUntilChanged()
.publishReplay(1)
.refCount();
public get apps(): Observable<AppDto[]> {
return this.apps$;
return this.appsPublished$;
}
public get selectedApp(): Observable<AppDto | null> {
return this.selectedApp$;
}
constructor(
@ -34,7 +55,7 @@ export class AppsStoreService {
return;
}
this.apps$.subscribe(apps => {
this.appsPublished$.subscribe(apps => {
this.lastApps = apps;
});
@ -57,11 +78,17 @@ export class AppsStoreService {
});
}
public createApp(appToCreate: AppCreateDto): Observable<any> {
public selectApp(name: string | null): Observable<AppDto | null> {
this.appName$.next(name);
return this.selectedApp;
}
public createApp(appToCreate: AppCreateDto): Observable<AppDto> {
return this.appService.postApp(appToCreate).do(app => {
if (this.lastApps && app) {
this.apps$.next(this.lastApps.concat([app]));
}
});
});
}
}

4
src/Squidex/app/shared/services/apps.service.ts

@ -33,7 +33,7 @@ export class AppCreateDto {
@Ng2.Injectable()
export class AppsService {
constructor(
private readonly authService: AuthService,
private readonly authService: AuthService,
private readonly apiUrl: ApiUrlConfig
) {
}
@ -41,7 +41,7 @@ export class AppsService {
public getApps(): Observable<AppDto[]> {
return this.authService.authGet(this.apiUrl.buildUrl('/api/apps'))
.map(response => {
let body: any[] = response.json() || [];
const body: any[] = response.json() || [];
return body.map(item => {
return new AppDto(

28
src/Squidex/app/shared/services/auth.service.ts

@ -15,7 +15,7 @@ import {
UserManager
} from 'oidc-client';
import { BehaviorSubject, Observable } from 'rxjs';
import { Observable, Subject } from 'rxjs';
import { ApiUrlConfig } from 'framework';
@ -37,9 +37,15 @@ export class Profile {
@Ng2.Injectable()
export class AuthService {
private readonly userManager: UserManager;
private readonly isAuthenticatedChanged$ = new BehaviorSubject<boolean>(false);
private readonly isAuthenticatedChanged$ = new Subject<boolean>();
private currentUser: Profile | null = null;
private readonly isAuthenticatedChangedPublished$ =
this.isAuthenticatedChanged$
.distinctUntilChanged()
.publishReplay(1)
.refCount();
public get user(): Profile | null {
return this.currentUser;
}
@ -49,7 +55,7 @@ export class AuthService {
}
public get isAuthenticatedChanges(): Observable<boolean> {
return this.isAuthenticatedChanged$;
return this.isAuthenticatedChangedPublished$;
}
constructor(apiUrl: ApiUrlConfig,
@ -87,7 +93,7 @@ export class AuthService {
if (this.currentUser) {
return Promise.resolve(true);
} else {
const promise =
const promise =
this.checkState(this.userManager.signinSilent())
.then(result => {
return result || this.checkState(this.userManager.signinSilent());
@ -114,7 +120,7 @@ export class AuthService {
}
public loginPopup(): Observable<boolean> {
const promise =
const promise =
this.checkState(this.userManager.signinPopup())
.then(result => {
return result || this.checkState(this.userManager.signinSilent());
@ -124,15 +130,16 @@ export class AuthService {
}
private onAuthenticated(user: User) {
this.currentUser = new Profile(user);
this.isAuthenticatedChanged$.next(true);
this.currentUser = new Profile(user);
}
private onDeauthenticated() {
this.isAuthenticatedChanged$.next(false);
this.currentUser = null;
this.isAuthenticatedChanged$.next(false);
}
private checkState(promise: Promise<User>): Promise<boolean> {
@ -194,7 +201,10 @@ export class AuthService {
options.headers = new Ng2Http.Headers();
options.headers.append('Content-Type', 'application/json');
}
options.headers.append('Authorization', `${this.currentUser.user.token_type} ${this.currentUser.user.access_token}`);
if (this.currentUser && this.currentUser.user) {
options.headers.append('Authorization', `${this.currentUser.user.token_type} ${this.currentUser.user.access_token}`);
}
return options;
}

1
src/Squidex/tsconfig.json

@ -8,6 +8,7 @@
"experimentalDecorators": true,
"removeComments": false,
"noImplicitAny": true,
"strictNullChecks": false,
"suppressImplicitAnyIndexErrors": true,
"baseUrl": ".",
"paths": {

10
src/Squidex/tslint.json

@ -18,7 +18,10 @@
true,
240
],
"member-access": false,
"member-access": [
true,
"check-accessor"
],
"member-ordering": [
true,
"variables-before-functions"
@ -43,7 +46,7 @@
"no-shadowed-variable": true,
"no-string-literal": false,
"no-switch-case-fall-through": true,
"no-trailing-whitespace": false,
"no-trailing-whitespace": true,
"no-unreachable": true,
"no-unused-expression": true,
"no-unused-variable": true,
@ -57,6 +60,9 @@
"check-else",
"check-whitespace"
],
"only-arrow-functions": [
true,
"allow-declarations"],
"quotemark": [
true,
"single"

Loading…
Cancel
Save