Browse Source

Temp version

pull/1/head
Sebastian 9 years ago
parent
commit
f281ecb9d7
  1. 16
      src/Squidex/app/app.component.html
  2. 17
      src/Squidex/app/app.component.scss
  3. 10
      src/Squidex/app/app.component.spec.ts
  4. 6
      src/Squidex/app/app.component.ts
  5. 17
      src/Squidex/app/app.module.ts
  6. 8
      src/Squidex/app/app.routes.ts
  7. 27
      src/Squidex/app/components/apps/apps-page.component.html
  8. 17
      src/Squidex/app/components/apps/apps-page.component.scss
  9. 22
      src/Squidex/app/components/apps/apps-page.component.ts
  10. 3
      src/Squidex/app/components/apps/apps.component.html
  11. 12
      src/Squidex/app/components/apps/apps.module.ts
  12. 2
      src/Squidex/app/components/apps/declarations.ts
  13. 3
      src/Squidex/app/components/apps/index.ts
  14. 1
      src/Squidex/app/components/index.ts
  15. 31
      src/Squidex/app/components/layout/app-form.component.html
  16. 0
      src/Squidex/app/components/layout/app-form.component.scss
  17. 91
      src/Squidex/app/components/layout/app-form.component.ts
  18. 41
      src/Squidex/app/components/layout/apps-menu.component.html
  19. 33
      src/Squidex/app/components/layout/apps-menu.component.scss
  20. 54
      src/Squidex/app/components/layout/apps-menu.component.ts
  21. 10
      src/Squidex/app/components/layout/declarations.ts
  22. 10
      src/Squidex/app/components/layout/index.ts
  23. 33
      src/Squidex/app/components/layout/layout.module.ts
  24. 3
      src/Squidex/app/components/layout/search-form.component.html
  25. 16
      src/Squidex/app/components/layout/search-form.component.scss
  26. 7
      src/Squidex/app/components/layout/search-form.component.ts
  27. 2
      src/Squidex/app/components/login/declarations.ts
  28. 3
      src/Squidex/app/components/login/index.ts
  29. 0
      src/Squidex/app/components/login/login-page.component.html
  30. 2
      src/Squidex/app/components/login/login-page.component.ts
  31. 10
      src/Squidex/app/components/login/login.module.ts
  32. 26
      src/Squidex/app/framework/angular/animations.ts
  33. 4
      src/Squidex/app/framework/angular/cloak.directive.ts
  34. 2
      src/Squidex/app/framework/angular/color-picker.component.ts
  35. 2
      src/Squidex/app/framework/angular/drag-model.directive.ts
  36. 2
      src/Squidex/app/framework/angular/image-drop.directive.ts
  37. 69
      src/Squidex/app/framework/angular/modal-view.directive.ts
  38. 2
      src/Squidex/app/framework/angular/shortcut.component.ts
  39. 2
      src/Squidex/app/framework/angular/slider.component.ts
  40. 2
      src/Squidex/app/framework/angular/spinner.component.ts
  41. 2
      src/Squidex/app/framework/angular/user-report.component.ts
  42. 28
      src/Squidex/app/framework/declarations.ts
  43. 17
      src/Squidex/app/framework/framework.module.ts
  44. 26
      src/Squidex/app/framework/index.ts
  45. 2
      src/Squidex/app/framework/services/clipboard.service.spec.ts
  46. 10
      src/Squidex/app/framework/services/clipboard.service.ts
  47. 2
      src/Squidex/app/framework/services/drag.service.spec.ts
  48. 2
      src/Squidex/app/framework/services/drag.service.ts
  49. 65
      src/Squidex/app/framework/utils/modal-view.spec.ts
  50. 38
      src/Squidex/app/framework/utils/modal-view.ts
  51. 2
      src/Squidex/app/shared/index.ts
  52. 144
      src/Squidex/app/shared/services/apps-store.service.spec.ts
  53. 67
      src/Squidex/app/shared/services/apps-store.service.ts
  54. 20
      src/Squidex/app/shared/services/apps.service.ts
  55. 82
      src/Squidex/app/shared/services/auth.service.ts
  56. 83
      src/Squidex/app/theme/_bootstrap.scss
  57. 10
      src/Squidex/app/theme/_vars.scss
  58. 7
      src/Squidex/app/theme/_vendor-overrides.scss
  59. 2
      src/Squidex/app/theme/vendor.scss
  60. 5
      src/Squidex/app/vendor.ts
  61. 2
      src/Squidex/wwwroot/index.html

16
src/Squidex/app/app.component.html

@ -1 +1,15 @@
<router-outlet></router-outlet> <nav class="navbar navbar-fixed-top navbar-dark bg-primary bg-faded">
<span class="navbar-brand">Squidex</span>
<div class="float-xs-left apps-menu">
<sqx-apps-menu></sqx-apps-menu>
</div>
<div class="float-xs-left search-form">
<sqx-search-form></sqx-search-form>
</div>
</nav>
<main>
<router-outlet></router-outlet>
</main>

17
src/Squidex/app/app.component.scss

@ -1,8 +1,13 @@
@import 'theme/mixins.scss';
.navbar {
@include box-shadow(0, 4px, 4px, 0.2px);
}
.search-form {
margin-left: 15px;
}
main { main {
padding: 1em; margin-top: 54px
font-family: Arial, Helvetica, sans-serif;
font-size: 1.1rem;
text-align: center;
margin-top: 50px;
display: block;
} }

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

@ -3,6 +3,10 @@ import { TestBed } from '@angular/core/testing';
import { RouterModule, provideRoutes } from '@angular/router'; import { RouterModule, provideRoutes } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing'; import { RouterTestingModule } from '@angular/router/testing';
import { SqxLayoutModule } from './components/layout';
import { AppsStoreService } from './shared';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
describe('App', () => { describe('App', () => {
@ -13,10 +17,12 @@ describe('App', () => {
], ],
imports: [ imports: [
RouterModule, RouterModule,
RouterTestingModule RouterTestingModule,
SqxLayoutModule
], ],
providers: [ providers: [
provideRoutes([]) provideRoutes([]),
{ provide: AppsStoreService, useValue: new AppsStoreService(null, null) }
] ]
}); });
}); });

6
src/Squidex/app/app.component.ts

@ -8,8 +8,8 @@
import * as Ng2 from '@angular/core'; import * as Ng2 from '@angular/core';
@Ng2.Component({ @Ng2.Component({
selector: 'my-app', selector: 'sqx-app',
template, styles,
styles template
}) })
export class AppComponent { } export class AppComponent { }

17
src/Squidex/app/app.module.ts

@ -19,15 +19,20 @@ import {
} from './framework'; } from './framework';
import { import {
AppsStoreService,
AppsService,
AuthGuard, AuthGuard,
AuthService, AuthService,
} from './shared'; } from './shared';
import { import {
MyAppModule, SqxAppModule,
MyLoginModule SqxLayoutModule,
SqxLoginModule
} from './components'; } from './components';
import { SqxFrameworkModule } from './framework';
import { routing } from './app.routes'; import { routing } from './app.routes';
const baseUrl = window.location.protocol + '//' + window.location.host + '/'; const baseUrl = window.location.protocol + '//' + window.location.host + '/';
@ -35,14 +40,18 @@ const baseUrl = window.location.protocol + '//' + window.location.host + '/';
@Ng2.NgModule({ @Ng2.NgModule({
imports: [ imports: [
Ng2Browser.BrowserModule, Ng2Browser.BrowserModule,
MyAppModule, SqxAppModule,
MyLoginModule, SqxLayoutModule,
SqxLoginModule,
SqxFrameworkModule,
routing routing
], ],
declarations: [ declarations: [
AppComponent AppComponent
], ],
providers: [ providers: [
AppsStoreService,
AppsService,
AuthGuard, AuthGuard,
AuthService, AuthService,
{ provide: ApiUrlConfig, useValue: new ApiUrlConfig(baseUrl) }, { provide: ApiUrlConfig, useValue: new ApiUrlConfig(baseUrl) },

8
src/Squidex/app/app.routes.ts

@ -9,8 +9,8 @@ import * as Ng2 from '@angular/core';
import * as Ng2Router from '@angular/router'; import * as Ng2Router from '@angular/router';
import { import {
AppsComponent, AppsPageComponent,
LoginComponent LoginPageComponent
} from './components'; } from './components';
import { import {
@ -25,12 +25,12 @@ export const routes: Ng2Router.Routes = [
}, },
{ {
path: 'apps', path: 'apps',
component: AppsComponent, component: AppsPageComponent,
canActivate: [AuthGuard] canActivate: [AuthGuard]
}, },
{ {
path: 'login', path: 'login',
component: LoginComponent component: LoginPageComponent
} }
]; ];

27
src/Squidex/app/components/apps/apps-page.component.html

@ -0,0 +1,27 @@
<content>
<div class="apps-empty">
<h3 class="apps-empty-headline">You are not collaborating to any app yet</h3>
<button class="apps-empty-button btn btn-success" (click)="modalDialog.show()">Create App</button>
</div>
</content>
<div class="modal" [(sqxModalView)]="modalDialog" [@fade]="modalDialog.isOpenChanges | async">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close" (click)="modalDialog.hide()">
<span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title">Create App</h4>
</div>
<div class="modal-body">
<sqx-app-form
(onCreated)="modalDialog.hide()"
(onCancelled)="modalDialog.hide()"></sqx-app-form>
</div>
</div>
</div>
</div>

17
src/Squidex/app/components/apps/apps-page.component.scss

@ -0,0 +1,17 @@
@import '../../theme/_vars.scss';
@import '../../theme/_mixins.scss';
content {
padding: 20px;
}
.apps-empty {
& {
text-align: center;
}
&-headline {
margin-top: 100px;
margin-bottom: 20px;
}
}

22
src/Squidex/app/components/apps/apps-page.component.ts

@ -0,0 +1,22 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import * as Ng2 from '@angular/core';
import { fadeAnimation, ModalView } from './../../framework';
@Ng2.Component({
selector: 'sqx-apps-page',
styles,
template,
animations: [
fadeAnimation()
]
})
export class AppsPageComponent {
public modalDialog = new ModalView();
}

3
src/Squidex/app/components/apps/apps.component.html

@ -1,3 +0,0 @@
<main>
<h1>Hello from Angular 2 App with Webpack!</h1>
</main>

12
src/Squidex/app/components/apps/apps.module.ts

@ -7,18 +7,20 @@
import * as Ng2 from '@angular/core'; import * as Ng2 from '@angular/core';
import { FrameworkModule } from './../../framework'; import { SqxFrameworkModule } from './../../framework';
import { SqxLayoutModule } from './../layout';
import { import {
AppsComponent AppsPageComponent
} from './declarations'; } from './declarations';
@Ng2.NgModule({ @Ng2.NgModule({
imports: [ imports: [
FrameworkModule SqxFrameworkModule,
SqxLayoutModule
], ],
declarations: [ declarations: [
AppsComponent AppsPageComponent
] ]
}) })
export class MyAppModule { } export class SqxAppModule { }

2
src/Squidex/app/components/apps/declarations.ts

@ -5,4 +5,4 @@
* Copyright (c) Sebastian Stehle. All rights reserved * Copyright (c) Sebastian Stehle. All rights reserved
*/ */
export * from './apps.component'; export * from './apps-page.component';

3
src/Squidex/app/components/apps/index.ts

@ -5,5 +5,6 @@
* Copyright (c) Sebastian Stehle. All rights reserved * Copyright (c) Sebastian Stehle. All rights reserved
*/ */
export * from './apps.component'; export * from './declarations';
export * from './apps.module'; export * from './apps.module';

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

@ -6,4 +6,5 @@
*/ */
export * from './apps'; export * from './apps';
export * from './layout';
export * from './login'; export * from './login';

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

@ -0,0 +1,31 @@
<form [formGroup]="createForm" (ngSubmit)="submit()">
<div class="form-group">
<label for="app-name">Name</label>
<div class="errors-box" *ngIf="createForm.get('name').invalid && createForm.get('name').dirty" [@fade]>
<div class="errors">
<span *ngIf="createForm.get('name').hasError('required')">
Name is required.
</span>
<span *ngIf="createForm.get('name').hasError('maxlength')">
Name can not have more than 40 characters.
</span>
<span *ngIf="createForm.get('name').hasError('pattern')">
Name can contain lower case letters (a-z), numbers and dashes only.
</span>
</div>
</div>
<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 />
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>
<div class="form-group">
<button type="reset" class="btn btn-link" (click)="cancel()">Cancel</button>
<button type="submit" class="btn btn-success">Create</button>
</div>
</form>

0
src/Squidex/app/components/layout/app-form.component.scss

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

@ -0,0 +1,91 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import * as Ng2 from '@angular/core';
import * as Ng2Forms from '@angular/forms';
import { Observable } from 'rxjs';
import { fadeAnimation } from './../../framework';
import { AppCreateDto, AppsStoreService } from './../../shared';
const FALLBACK_NAME = 'my-app';
@Ng2.Component({
selector: 'sqx-app-form',
styles,
template,
animations: [
fadeAnimation()
]
})
export class AppFormComponent implements Ng2.OnInit {
public createForm: Ng2Forms.FormGroup;
public appName: Observable<string>;
@Ng2.Input()
public showClose = false;
@Ng2.Output()
public onCreated = new Ng2.EventEmitter();
@Ng2.Output()
public onCancelled = new Ng2.EventEmitter();
public creating = new Ng2.EventEmitter<boolean>();
public creationError = new Ng2.EventEmitter<any>();
constructor(
private readonly appsStore: AppsStoreService,
private readonly formBuilder: Ng2Forms.FormBuilder
) {
}
public ngOnInit() {
this.createForm = this.formBuilder.group({
name: ['',
[
Ng2Forms.Validators.required,
Ng2Forms.Validators.maxLength(40),
Ng2Forms.Validators.pattern('[a-z0-9]+(\-[a-z0-9]+)*'),
]]
});
this.appName = this.createForm.controls['name'].valueChanges.map(name => name || FALLBACK_NAME).publishBehavior(FALLBACK_NAME).refCount();
}
public submit() {
if (this.createForm.valid) {
this.createForm.disable();
this.creating.emit(true);
const dto = new AppCreateDto(this.createForm.controls['name'].value);
this.appsStore.createApp(dto)
.finally(() => {
this.reset();
})
.subscribe(() => {
this.onCreated.emit();
}, error => {
this.creationError.emit(error);
});
}
}
private reset() {
this.createForm.enable();
this.creating.emit(false);
}
public cancel() {
this.onCancelled.emit();
}
}

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

@ -0,0 +1,41 @@
<ul class="nav navbar-nav" *ngIf="apps">
<li class="nav-item dropdown">
<span class="nav-link dropdown-toggle" id="app-name" (click)="modalMenu.toggle()">My App with a really very long name</span>
<div class="dropdown-menu" [(sqxModalView)]="modalMenu">
<a class="dropdown-item" href="#">Action</a>
<a class="dropdown-item" href="#">Another action</a>
<a class="dropdown-item" href="#">Something else here</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" routerLink="/apps">All apps</a>
<div class="dropdown-divider"></div>
<div class="drodown-button">
<button class="btn btn-block btn-success" id="app-create" (click)="createApp()">Create App</button>
</div>
</div>
</li>
</ul>
<div class="modal ng-animate" [(sqxModalView)]="modalDialog" [@fade]="(modalDialog.isOpenChanges | async)">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close" (click)="modalDialog.hide()">
<span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title">Create App</h4>
</div>
<div class="modal-body">
<sqx-app-form
(onCreated)="modalDialog.hide()"
(onCancelled)="modalDialog.hide()"></sqx-app-form>
</div>
</div>
</div>
</div>

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

@ -0,0 +1,33 @@
@import '../../theme/_vars.scss';
@import '../../theme/_mixins.scss';
.navbar-dark .navbar-nav .nav-link {
color: white;
}
.nav-link {
@include truncate();
}
.drodown-button {
padding: 3px 1.5rem;
}
#app-name {
& {
padding-right: 15px;
@include transition(opacity 0.4 ease);
@include opacity(0.95);
color: white;
cursor: pointer;
width: 200px;
}
&:hover {
@include opacity(1);
}
&:after {
@include absolute(50%, 0px, auto, auto);
}
}

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

@ -0,0 +1,54 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import * as Ng2 from '@angular/core';
import {
AppDto,
AppsStoreService
} from './../../shared';
import { fadeAnimation, ModalView } from './../../framework';
@Ng2.Component({
selector: 'sqx-apps-menu',
styles,
template,
animations: [
fadeAnimation()
]
})
export class AppsMenuComponent implements Ng2.OnInit, Ng2.OnDestroy {
private subscription: any | null = null;
public modalMenu = new ModalView();
public modalDialog = new ModalView();
public apps: AppDto[] | null = null;
constructor(
private readonly appsStore: AppsStoreService
) {
}
public ngOnInit() {
this.subscription = this.appsStore.appsChanges.subscribe(apps => {
this.apps = apps;
});
}
public ngOnDestroy() {
if (this.subscription) {
this.subscription.unsubscribe();
}
}
public createApp() {
this.modalMenu.hide();
this.modalDialog.show();
}
}

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

@ -0,0 +1,10 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
export * from './app-form.component';
export * from './apps-menu.component';
export * from './search-form.component';

10
src/Squidex/app/components/layout/index.ts

@ -0,0 +1,10 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
export * from './declarations';
export * from './layout.module';

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

@ -0,0 +1,33 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import * as Ng2 from '@angular/core';
import { SqxFrameworkModule } from './../../framework';
import {
AppFormComponent,
AppsMenuComponent,
SearchFormComponent
} from './declarations';
@Ng2.NgModule({
imports: [
SqxFrameworkModule
],
declarations: [
AppFormComponent,
AppsMenuComponent,
SearchFormComponent,
],
exports: [
AppFormComponent,
AppsMenuComponent,
SearchFormComponent,
]
})
export class SqxLayoutModule { }

3
src/Squidex/app/components/layout/search-form.component.html

@ -0,0 +1,3 @@
<form class="form-inline">
<input class="form-control search" type="text" />
</form>

16
src/Squidex/app/components/layout/search-form.component.scss

@ -0,0 +1,16 @@
@import '../../theme/_vars.scss';
@import '../../theme/_mixins.scss';
.search {
& {
@include transition(background 0.4s ease);
color: white;
background: $accent-blue-dark;
border-color: $accent-blue-dark;
border-width: 1px;
}
&:focus {
background: darken($accent-blue-dark, 5%);
}
}

7
src/Squidex/app/components/apps/apps.component.ts → src/Squidex/app/components/layout/search-form.component.ts

@ -1,4 +1,4 @@
/* /*
* Squidex Headless CMS * Squidex Headless CMS
* *
* @license * @license
@ -8,9 +8,10 @@
import * as Ng2 from '@angular/core'; import * as Ng2 from '@angular/core';
@Ng2.Component({ @Ng2.Component({
selector: 'apps', selector: 'sqx-search-form',
styles,
template template
}) })
export class AppsComponent { export class SearchFormComponent {
} }

2
src/Squidex/app/components/login/declarations.ts

@ -5,4 +5,4 @@
* Copyright (c) Sebastian Stehle. All rights reserved * Copyright (c) Sebastian Stehle. All rights reserved
*/ */
export * from './login.component'; export * from './login-page.component';

3
src/Squidex/app/components/login/index.ts

@ -5,5 +5,6 @@
* Copyright (c) Sebastian Stehle. All rights reserved * Copyright (c) Sebastian Stehle. All rights reserved
*/ */
export * from './login.component'; export * from './declarations';
export * from './login.module'; export * from './login.module';

0
src/Squidex/app/components/login/login.component.html → src/Squidex/app/components/login/login-page.component.html

2
src/Squidex/app/components/login/login.component.ts → src/Squidex/app/components/login/login-page.component.ts

@ -14,7 +14,7 @@ import { AuthService } from './../../shared';
selector: 'login', selector: 'login',
template template
}) })
export class LoginComponent implements Ng2.OnInit { export class LoginPageComponent implements Ng2.OnInit {
public showFailedError = false; public showFailedError = false;
constructor( constructor(

10
src/Squidex/app/components/login/login.module.ts

@ -7,18 +7,18 @@
import * as Ng2 from '@angular/core'; import * as Ng2 from '@angular/core';
import { FrameworkModule } from './../../framework'; import { SqxFrameworkModule } from './../../framework';
import { import {
LoginComponent LoginPageComponent
} from './declarations'; } from './declarations';
@Ng2.NgModule({ @Ng2.NgModule({
imports: [ imports: [
FrameworkModule SqxFrameworkModule
], ],
declarations: [ declarations: [
LoginComponent LoginPageComponent
] ]
}) })
export class MyLoginModule { } export class SqxLoginModule { }

26
src/Squidex/app/framework/angular/animations.ts

@ -0,0 +1,26 @@
import * as Ng2 from '@angular/core';
export const fadeAnimation = (name = 'fade', timing = '200ms'): Ng2.AnimationEntryMetadata => {
return Ng2.trigger(
name, [
Ng2.transition(':enter', [
Ng2.style({ opacity: 0 }),
Ng2.animate(timing, Ng2.style({ opacity: 1 }))
]),
Ng2.transition(':leave', [
Ng2.style({ 'opacity': 1 }),
Ng2.animate(timing, Ng2.style({ opacity: 0 }))
]),
Ng2.state('true',
Ng2.style({ opacity: 1 })
),
Ng2.state('false',
Ng2.style({ opacity: 0 })
),
Ng2.transition('1 => 0', Ng2.animate(timing)),
Ng2.transition('0 => 1', Ng2.animate(timing))
]
);
}

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

@ -8,12 +8,12 @@
import * as Ng2 from '@angular/core'; import * as Ng2 from '@angular/core';
@Ng2.Directive({ @Ng2.Directive({
selector: '.gp-cloak' selector: '.sqx-cloak'
}) })
export class CloakDirective implements Ng2.OnInit { export class CloakDirective implements Ng2.OnInit {
constructor(private readonly element: Ng2.ElementRef) { } constructor(private readonly element: Ng2.ElementRef) { }
public ngOnInit() { public ngOnInit() {
this.element.nativeElement.classList.remove('gp-cloak'); this.element.nativeElement.classList.remove('sqx-cloak');
} }
} }

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

@ -16,7 +16,7 @@ import {
} from './../utils/color-palette'; } from './../utils/color-palette';
@Ng2.Component({ @Ng2.Component({
selector: 'gp-color-picker', selector: 'sqx-color-picker',
styles, styles,
template template
}) })

2
src/Squidex/app/framework/angular/drag-model.directive.ts

@ -77,7 +77,7 @@ export class DragModelDirective {
let dropCandidate: Element | null = document.elementFromPoint(event.clientX, event.clientY); let dropCandidate: Element | null = document.elementFromPoint(event.clientX, event.clientY);
while (dropCandidate && (dropCandidate.classList && !dropCandidate.classList.contains('gp-drop'))) { while (dropCandidate && (dropCandidate.classList && !dropCandidate.classList.contains('sqx-drop'))) {
dropCandidate = dropCandidate.parentNode as Element; dropCandidate = dropCandidate.parentNode as Element;
} }

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

@ -11,7 +11,7 @@ import { DragService } from './../services/drag.service';
import { Vec2 } from './../utils/vec2'; import { Vec2 } from './../utils/vec2';
@Ng2.Directive({ @Ng2.Directive({
selector: '.gp-image-drop' selector: '.sqx-image-drop'
}) })
export class ImageDropDirective { export class ImageDropDirective {
constructor( constructor(

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

@ -0,0 +1,69 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import * as Ng2 from '@angular/core';
import { ModalView } from './../utils/modal-view';
@Ng2.Directive({
selector: '[sqxModalView]'
})
export class ModalViewDirective implements Ng2.OnChanges {
private subscription: any | null;
private isEnabled = true;
@Ng2.Input('sqxModalView')
public modalView: ModalView;
constructor(
private readonly elementRef: Ng2.ElementRef,
private readonly renderer: Ng2.Renderer,
) {
}
@Ng2.HostListener('document:click', ['$event', '$event.target'])
public clickOutside(event: MouseEvent, targetElement: HTMLElement) {
if (!targetElement) {
return;
}
const clickedInside = this.elementRef.nativeElement.contains(targetElement);
if (!clickedInside && this.modalView && this.isEnabled) {
this.modalView.hide();
}
}
public ngOnChanges() {
if (this.subscription) {
this.subscription.unsubscribe();
this.subscription = null;
}
if (this.modalView) {
this.subscription = this.modalView.isOpenChanges.subscribe(isOpen => {
if (this.isEnabled) {
if (isOpen) {
this.renderer.setElementStyle(this.elementRef.nativeElement, 'display', 'block');
} else {
this.renderer.setElementStyle(this.elementRef.nativeElement, 'display', 'none');
}
this.updateEnabled();
}
});
}
}
private updateEnabled() {
this.isEnabled = false;
setTimeout(() => {
this.isEnabled = true;
}, 500);
}
}

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

@ -10,7 +10,7 @@ import * as Ng2 from '@angular/core';
import { ShortcutService } from './../services/shortcut.service'; import { ShortcutService } from './../services/shortcut.service';
@Ng2.Component({ @Ng2.Component({
selector: 'gp-shortcut', selector: 'sqx-shortcut',
template: '' template: ''
}) })
export class ShortcutComponent implements Ng2.OnInit, Ng2.OnDestroy { export class ShortcutComponent implements Ng2.OnInit, Ng2.OnDestroy {

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

@ -8,7 +8,7 @@
import * as Ng2 from '@angular/core'; import * as Ng2 from '@angular/core';
@Ng2.Component({ @Ng2.Component({
selector: 'gp-slider', selector: 'sqx-slider',
styles, styles,
template template
}) })

2
src/Squidex/app/framework/angular/spinner.component.ts

@ -10,7 +10,7 @@ import * as Ng2 from '@angular/core';
declare var Spinner: any; declare var Spinner: any;
@Ng2.Component({ @Ng2.Component({
selector: 'gp-spinner', selector: 'sqx-spinner',
template: '' template: ''
}) })
export class SpinnerComponent { export class SpinnerComponent {

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

@ -10,7 +10,7 @@ import * as Ng2 from '@angular/core';
import { UserReportConfig } from './../configurations'; import { UserReportConfig } from './../configurations';
@Ng2.Component({ @Ng2.Component({
selector: 'gp-user-report', selector: 'sqx-user-report',
template: '' template: ''
}) })
export class UserReportComponent implements Ng2.OnInit { export class UserReportComponent implements Ng2.OnInit {

28
src/Squidex/app/framework/declarations.ts

@ -5,16 +5,42 @@
* Copyright (c) Sebastian Stehle. All rights reserved * Copyright (c) Sebastian Stehle. All rights reserved
*/ */
export * from './angular/action';
export * from './angular/animations';
export * from './angular/validators';
export * from './angular/cloak.directive'; export * from './angular/cloak.directive';
export * from './angular/color-picker.component'; export * from './angular/color-picker.component';
export * from './angular/date-time.pipes'; export * from './angular/date-time.pipes';
export * from './angular/drag-model.directive'; export * from './angular/drag-model.directive';
export * from './angular/focus-on-change.directive'; export * from './angular/focus-on-change.directive';
export * from './angular/image-drop.directive'; export * from './angular/image-drop.directive';
export * from './angular/modal-view.directive';
export * from './angular/money.pipe'; export * from './angular/money.pipe';
export * from './angular/shortcut.component'; export * from './angular/shortcut.component';
export * from './angular/slider.component'; export * from './angular/slider.component';
export * from './angular/spinner.component'; export * from './angular/spinner.component';
export * from './angular/user-report.component'; export * from './angular/user-report.component';
export * from './configurations';
export * from './services/clipboard.service';
export * from './services/drag.service'; export * from './services/drag.service';
export * from './services/title.service'; export * from './services/local-store.service';
export * from './services/shortcut.service';
export * from './services/title.service';
export * from './plattform';
export * from './utils/color';
export * from './utils/color-palette';
export * from './utils/date-helper';
export * from './utils/date-time';
export * from './utils/duration';
export * from './utils/immutable-id-map';
export * from './utils/immutable-list';
export * from './utils/immutable-object';
export * from './utils/immutable-set';
export * from './utils/math-helper';
export * from './utils/modal-view';
export * from './utils/rotation';
export * from './utils/vec2';
export * from './utils/rect2';

17
src/Squidex/app/framework/framework.module.ts

@ -16,10 +16,9 @@ import {
ColorPickerComponent, ColorPickerComponent,
DayOfWeekPipe, DayOfWeekPipe,
DayPipe, DayPipe,
DragModelDirective,
DurationPipe, DurationPipe,
FocusOnChangeDirective, FocusOnChangeDirective,
ImageDropDirective, ModalViewDirective,
MoneyPipe, MoneyPipe,
MonthPipe, MonthPipe,
ShortcutComponent, ShortcutComponent,
@ -32,18 +31,20 @@ import {
@Ng2.NgModule({ @Ng2.NgModule({
imports: [ imports: [
Ng2Http.HttpModule,
Ng2Forms.FormsModule, Ng2Forms.FormsModule,
Ng2Common.CommonModule Ng2Forms.ReactiveFormsModule,
Ng2Common.CommonModule,
Ng2Router.RouterModule
], ],
declarations: [ declarations: [
CloakDirective, CloakDirective,
ColorPickerComponent, ColorPickerComponent,
DayOfWeekPipe, DayOfWeekPipe,
DayPipe, DayPipe,
DragModelDirective,
DurationPipe, DurationPipe,
FocusOnChangeDirective, FocusOnChangeDirective,
ImageDropDirective, ModalViewDirective,
MoneyPipe, MoneyPipe,
MonthPipe, MonthPipe,
ShortcutComponent, ShortcutComponent,
@ -51,7 +52,7 @@ import {
ShortTimePipe, ShortTimePipe,
SliderComponent, SliderComponent,
SpinnerComponent, SpinnerComponent,
UserReportComponent UserReportComponent,
], ],
exports: [ exports: [
CloakDirective, CloakDirective,
@ -60,6 +61,7 @@ import {
DayPipe, DayPipe,
DurationPipe, DurationPipe,
FocusOnChangeDirective, FocusOnChangeDirective,
ModalViewDirective,
MoneyPipe, MoneyPipe,
MonthPipe, MonthPipe,
ShortcutComponent, ShortcutComponent,
@ -70,8 +72,9 @@ import {
UserReportComponent, UserReportComponent,
Ng2Http.HttpModule, Ng2Http.HttpModule,
Ng2Forms.FormsModule, Ng2Forms.FormsModule,
Ng2Forms.ReactiveFormsModule,
Ng2Common.CommonModule, Ng2Common.CommonModule,
Ng2Router.RouterModule Ng2Router.RouterModule
] ]
}) })
export class FrameworkModule { } export class SqxFrameworkModule { }

26
src/Squidex/app/framework/index.ts

@ -5,26 +5,6 @@
* Copyright (c) Sebastian Stehle. All rights reserved * Copyright (c) Sebastian Stehle. All rights reserved
*/ */
export * from './angular/action'; export * from './declarations';
export * from './angular/validators';
export * from './configurations'; export * from './framework.module';
export * from './framework.module';
export * from './plattform';
export * from './services/clipboard.service';
export * from './services/drag.service';
export * from './services/local-store.service';
export * from './services/shortcut.service';
export * from './services/title.service';
export * from './utils/color';
export * from './utils/color-palette';
export * from './utils/date-helper';
export * from './utils/date-time';
export * from './utils/duration';
export * from './utils/immutable-id-map';
export * from './utils/immutable-list';
export * from './utils/immutable-object';
export * from './utils/immutable-set';
export * from './utils/math-helper';
export * from './utils/rotation';
export * from './utils/vec2';
export * from './utils/rect2';

2
src/Squidex/app/framework/services/clipboard.service.spec.ts

@ -40,7 +40,7 @@ describe('ShortcutService', () => {
let text = ''; let text = '';
clipboardService.text.subscribe(t => { clipboardService.textChanges.subscribe(t => {
text = t; text = t;
}); });

10
src/Squidex/app/framework/services/clipboard.service.ts

@ -15,16 +15,16 @@ export const ClipboardServiceFactory = () => {
@Ng2.Injectable() @Ng2.Injectable()
export class ClipboardService { export class ClipboardService {
private textInstance = new BehaviorSubject<string>(''); private readonly text$ = new BehaviorSubject<string>('');
public get text(): Observable<string> { public get textChanges(): Observable<string> {
return this.textInstance; return this.text$;
} }
public selectText(): string { public selectText(): string {
let result = ''; let result = '';
this.textInstance.subscribe(t => { this.text$.subscribe(t => {
result = t; result = t;
}).unsubscribe(); }).unsubscribe();
@ -32,6 +32,6 @@ export class ClipboardService {
} }
public setText(text: any) { public setText(text: any) {
this.textInstance.next(text); this.text$.next(text);
} }
} }

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

@ -29,7 +29,7 @@ describe('DragService', () => {
const dragService = new DragService(); const dragService = new DragService();
dragService.drop.subscribe(e => { dragService.onDrop.subscribe(e => {
emittedEvent = e; emittedEvent = e;
}); });

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

@ -21,7 +21,7 @@ export const DragServiceFactory = () => {
export class DragService { export class DragService {
private readonly dropEvent = new Subject<DropEvent>(); private readonly dropEvent = new Subject<DropEvent>();
public get drop(): Observable<DropEvent> { public get onDrop(): Observable<DropEvent> {
return this.dropEvent; return this.dropEvent;
} }

65
src/Squidex/app/framework/utils/modal-view.spec.ts

@ -0,0 +1,65 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { ModalView } from './../';
describe('ModalView', () => {
it('should have initial true value', () => {
const dialog = new ModalView(true);
checkValue(dialog, true);
});
it('should have initial false value', () => {
const dialog = new ModalView(false);
checkValue(dialog, false);
});
it('should become open after show', () => {
const dialog = new ModalView(false);
dialog.show();
checkValue(dialog, true);
});
it('should become open after toggle', () => {
const dialog = new ModalView(false);
dialog.toggle();
checkValue(dialog, true);
});
it('should become closed after hide', () => {
const dialog = new ModalView(true);
dialog.hide();
checkValue(dialog, false);
});
it('should become closed after toggle', () => {
const dialog = new ModalView(true);
dialog.toggle();
checkValue(dialog, false);
});
function checkValue(dialog: ModalView, expected: boolean) {
let result: boolean | null = null;
dialog.isOpenChanges.subscribe(value => {
result = value;
}).unsubscribe();
expect(result).toBe(expected);
}
});

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

@ -0,0 +1,38 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { BehaviorSubject, Observable } from 'rxjs';
export class ModalView {
private readonly isOpen$: BehaviorSubject<boolean>;
public get isOpenChanges(): Observable<boolean> {
return this.isOpen$.distinctUntilChanged();
}
constructor(isOpen = false) {
this.isOpen$ = new BehaviorSubject(isOpen);
}
public show() {
this.isOpen$.next(true);
}
public hide() {
this.isOpen$.next(false);
}
public toggle() {
let value = false;
this.isOpenChanges.subscribe(v => {
value = v;
}).unsubscribe();
this.isOpen$.next(!value);
}
}

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

@ -6,4 +6,6 @@
*/ */
export * from './guards/auth.guard'; export * from './guards/auth.guard';
export * from './services/apps-store.service';
export * from './services/apps.service';
export * from './services/auth.service'; export * from './services/auth.service';

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

@ -0,0 +1,144 @@
import * as TypeMoq from 'typemoq';
import { Observable } from 'rxjs';
import {
AppCreateDto,
AppDto,
AppsStoreService,
AppsService,
AuthService
} from './../';
describe('AppsStoreService', () => {
const oldApps = [new AppDto('id', 'name', null, null)];
const newApp = new AppDto('id', 'new-name', null, null);
let appsService: TypeMoq.Mock<AppsService>;
let authService: TypeMoq.Mock<AuthService>;
beforeEach(() => {
appsService = TypeMoq.Mock.ofType(AppsService);
authService = TypeMoq.Mock.ofType(AuthService);
});
it('should load when authenticated once', () => {
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 result1: AppDto[];
let result2: AppDto[];
store.appsChanges.subscribe(x => {
result1 = x;
}).unsubscribe();
store.appsChanges.subscribe(x => {
result2 = x;
}).unsubscribe();
expect(result1).toEqual(oldApps);
expect(result2).toEqual(oldApps);
appsService.verifyAll();
});
it('should reload value from apps-service when called', () => {
authService.setup(x => x.isAuthenticated)
.returns(() => true)
.verifiable(TypeMoq.Times.once());
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.exactly(2));
const store = new AppsStoreService(authService.object, appsService.object);
let result1: AppDto[];
let result2: AppDto[];
store.appsChanges.subscribe(x => {
result1 = x;
}).unsubscribe();
store.reload();
store.appsChanges.subscribe(x => {
result2 = x;
}).unsubscribe();
expect(result1).toEqual(oldApps);
expect(result2).toEqual(oldApps);
appsService.verifyAll();
});
it('should add app to cache when created', () => {
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());
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[];
store.appsChanges.subscribe(x => {
result1 = x;
}).unsubscribe();
store.createApp(new AppCreateDto('new-name')).subscribe(x => { });
store.appsChanges.subscribe(x => {
result2 = x;
}).unsubscribe();
expect(result1).toEqual(oldApps);
expect(JSON.stringify(result2)).toEqual(JSON.stringify(oldApps.concat([newApp])));
appsService.verifyAll();
});
it('should not add app to cache when cache is null', () => {
authService.setup(x => x.isAuthenticatedChanges)
.returns(() => Observable.of(false))
.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 result: AppDto[];
store.createApp(new AppCreateDto('new-name')).subscribe(x => { });
store.appsChanges.subscribe(x => {
result = x;
}).unsubscribe();
expect(result).toBeNull();
appsService.verifyAll();
});
});

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

@ -0,0 +1,67 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import * as Ng2 from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import {
AppCreateDto,
AppDto,
AppsService
} from './apps.service';
import { AuthService } from './auth.service';
@Ng2.Injectable()
export class AppsStoreService {
private lastApps: AppDto[] = null;
private readonly apps$ = new BehaviorSubject<AppDto[]>(null);
public get appsChanges(): Observable<AppDto[]> {
return this.apps$;
}
constructor(
private readonly authService: AuthService,
private readonly appService: AppsService
) {
if (!authService || !appService) {
return;
}
this.apps$.subscribe(apps => {
this.lastApps = apps;
});
this.authService.isAuthenticatedChanges.subscribe(isAuthenticated => {
if (isAuthenticated) {
this.load();
}
});
}
public reload() {
if (this.authService.isAuthenticated) {
this.load();
}
}
private load() {
this.appService.getApps().subscribe(apps => {
this.apps$.next(apps);
});
}
public createApp(appToCreate: AppCreateDto): Observable<any> {
return this.appService.postApp(appToCreate).do(app => {
if (this.lastApps && app) {
this.apps$.next(this.lastApps.concat([app]));
}
});
}
}

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

@ -14,17 +14,17 @@ import { AuthService } from './auth.service';
export class AppDto { export class AppDto {
constructor( constructor(
private readonly id: string, public readonly id: string,
private readonly name: string, public readonly name: string,
private readonly created: DateTime, public readonly created: DateTime,
private readonly lastModified: DateTime public readonly lastModified: DateTime
) { ) {
} }
} }
export class AppCreateDto { export class AppCreateDto {
constructor( constructor(
private readonly name: string public readonly name: string
) { ) {
} }
} }
@ -53,7 +53,13 @@ export class AppsService {
}); });
} }
public postApp(app: AppCreateDto): Observable<any> { public postApp(appToCreate: AppCreateDto): Observable<AppDto> {
return this.authService.authPost(this.apiUrl.buildUrl('api/apps'), app); const now = DateTime.now();
return this.authService.authPost(this.apiUrl.buildUrl('api/apps'), appToCreate)
.map(response => response.json())
.map(response => {
return new AppDto(response.id, appToCreate.name, now, now);
});
} }
} }

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

@ -14,13 +14,14 @@ import {
UserManager UserManager
} from 'oidc-client'; } from 'oidc-client';
import { Observable } from 'rxjs'; import { BehaviorSubject, Observable } from 'rxjs';
import { ApiUrlConfig } from './../../framework'; import { ApiUrlConfig } from './../../framework';
@Ng2.Injectable() @Ng2.Injectable()
export class AuthService { export class AuthService {
private readonly userManager: UserManager; private readonly userManager: UserManager;
private readonly isAuthenticatedChanged$ = new BehaviorSubject<boolean>(false);
private currentUser: User | null = null; private currentUser: User | null = null;
private checkLoginPromise: Promise<boolean>; private checkLoginPromise: Promise<boolean>;
@ -32,6 +33,38 @@ export class AuthService {
return !!this.currentUser; return !!this.currentUser;
} }
public get isAuthenticatedChanges(): Observable<boolean> {
return this.isAuthenticatedChanged$;
}
constructor(apiUrl: ApiUrlConfig,
private readonly http: Ng2Http.Http,
) {
Log.logger = console;
if (apiUrl) {
this.userManager = new UserManager({
client_id: 'squidex-frontend',
scope: 'squidex-api openid profile ',
response_type: 'id_token token',
silent_redirect_uri: apiUrl.buildUrl('identity-server/client-callback-silent/'),
popup_redirect_uri: apiUrl.buildUrl('identity-server/client-callback-popup/'),
authority: apiUrl.buildUrl('identity-server/'),
automaticSilentRenew: true
});
this.userManager.events.addUserLoaded(user => {
this.onAuthenticated(user);
});
this.userManager.events.addUserUnloaded(() => {
this.onDeauthenticated();
});
this.checkLogin();
}
}
public checkLogin(): Promise<boolean> { public checkLogin(): Promise<boolean> {
if (this.checkLoginPromise) { if (this.checkLoginPromise) {
return this.checkLoginPromise; return this.checkLoginPromise;
@ -39,7 +72,7 @@ export class AuthService {
return Promise.resolve(true); return Promise.resolve(true);
} else { } else {
this.checkLoginPromise = this.checkLoginPromise =
this.checkState(this.userManager.getUser()) this.checkState(this.userManager.signinSilent())
.then(result => { .then(result => {
return result || this.checkState(this.userManager.signinSilent()); return result || this.checkState(this.userManager.signinSilent());
}); });
@ -48,31 +81,6 @@ export class AuthService {
} }
} }
constructor(apiUrl: ApiUrlConfig,
private readonly http: Ng2Http.Http,
) {
Log.logger = console;
this.userManager = new UserManager({
client_id: 'squidex-frontend',
scope: 'squidex-api openid profile ',
response_type: 'id_token token',
silent_redirect_uri: apiUrl.buildUrl('identity-server/client-callback-silent/'),
popup_redirect_uri: apiUrl.buildUrl('identity-server/client-callback-popup/'),
authority: apiUrl.buildUrl('identity-server/')
});
this.userManager.events.addUserLoaded(user => {
this.currentUser = user;
});
this.userManager.events.addUserUnloaded(() => {
this.currentUser = null;
});
this.checkLogin();
}
public logout(): Observable<any> { public logout(): Observable<any> {
return Observable.fromPromise(this.userManager.signoutRedirectCallback()); return Observable.fromPromise(this.userManager.signoutRedirectCallback());
} }
@ -87,12 +95,24 @@ export class AuthService {
return Observable.fromPromise(userPromise); return Observable.fromPromise(userPromise);
} }
private onAuthenticated(user: User) {
this.currentUser = user;
this.isAuthenticatedChanged$.next(true);
}
private onDeauthenticated() {
this.currentUser = null;
this.isAuthenticatedChanged$.next(false);
}
private checkState(promise: Promise<User>): Promise<boolean> { private checkState(promise: Promise<User>): Promise<boolean> {
const resultPromise = const resultPromise =
promise promise
.then(user => { .then(user => {
if (user) { if (user) {
this.currentUser = user; this.onAuthenticated(user);
} }
return !!this.currentUser; return !!this.currentUser;
}).catch((err) => { }).catch((err) => {
@ -128,11 +148,13 @@ export class AuthService {
private setRequestOptions(options?: Ng2Http.RequestOptions) { private setRequestOptions(options?: Ng2Http.RequestOptions) {
if (!options) { if (!options) {
options = new Ng2Http.RequestOptions(); options = new Ng2Http.RequestOptions();
}
if (!options.headers) {
options.headers = new Ng2Http.Headers();
options.headers.append('Content-Type', 'application/json'); options.headers.append('Content-Type', 'application/json');
} }
options.headers.append('Authorization', `${this.currentUser.token_type} ${this.currentUser.access_token}`);
options.headers.append('Authorization', '${this.user.token_type} {this.user.access_token}');
return options; return options;
} }

83
src/Squidex/app/theme/_bootstrap.scss

@ -1,69 +1,50 @@
@import '_mixins.scss'; @import '_mixins.scss';
@import '_vars.scss'; @import '_vars.scss';
.nav-icon { .form-hint {
& { font-size: 0.8rem;
margin-top: -0.5rem;
text-align: center;
text-decoration: none;
cursor: default;
}
span {
display: block;
font-size: 0.8em;
font-weight: normal;
cursor: default;
}
} }
.navbar-light .navbar-nav .nav-link, .btn-icon { .ng-invalid.ng-dirty {
& { border-color: $accent-error;
color: $nav-text-color; }
}
&:hover, &:focus {
text-decoration: none;
}
&:hover {
color: black;
}
&.disabled { .ng-invalid.ng-dirty :focus, .ng-invalid.ng-dirty :hover {
color: lighten($nav-text-color, 55%); border-color: $accent-error-dark;
}
} }
.navbar-light .navbar-nav .nav-link.btn-blue, .btn-icon.btn-blue, .btn-blue { .errors {
&:hover { &-box {
color: $accent-normal; position: relative;
} }
&:focus { &:after {
color: $accent-dark @include absolute(2rem, auto, auto, 0.6rem);
content: '';
height: 0;
border-style: solid;
border-width: 0.4rem;
border-color: $accent-error transparent transparent transparent;
width: 0;
} }
&.disabled { & {
color: lighten($nav-text-color, 55%); @include absolute(-2.4rem, 0px, auto, 0px);
@include border-radius(2px);
color: white;
cursor: none;
font-size: 0.9rem;
font-weight: normal;
line-height: 2rem;
padding: 0 0.4rem;
background: $accent-error;
} }
} }
.btn-icon { .modal-content {
padding-left: 0.4rem; @include box-shadow(0px, 6px, 16px, 0.2px);
padding-right: 0.4rem;
cursor: default;
}
.navbar-nav .nav-item + .nav-nomargin {
margin-left: 0;
margin-right: -1rem;
} }
.nav-separator { .modal-content, .modal-header {
margin-top: .425rem; border: 0;
margin-bottom: .425rem;
margin-left: 1.2em;
margin-right: 1.2em;
display: block;
} }

10
src/Squidex/app/theme/_vars.scss

@ -1,4 +1,10 @@
$nav-text-color: #333; $nav-text-color: #333;
$accent-normal: blue; $accent-blue: #438CEF;
$accent-dark: darkblue; $accent-blue-dark: #3F83DF;
$accent-green: #4CC159;
$accent-green-dark: #47B353;
$accent-error: red;
$accent-error-dark: darken(red, 5%);

7
src/Squidex/app/theme/_vendor-overrides.scss

@ -0,0 +1,7 @@
@import '_mixins.scss';
@import '_vars.scss';
$fa-font-path: "~font-awesome/fonts";
$brand-primary: $accent-blue;
$brand-success: $accent-green;

2
src/Squidex/app/theme/vendor.scss

@ -1,4 +1,4 @@
$fa-font-path: "~font-awesome/fonts"; @import '_vendor-overrides.scss';
// Font Awesome // Font Awesome
@import './../../node_modules/font-awesome/scss/font-awesome.scss'; @import './../../node_modules/font-awesome/scss/font-awesome.scss';

5
src/Squidex/app/vendor.ts

@ -18,4 +18,7 @@ import '@angular/router';
import 'oidc-client'; import 'oidc-client';
// RxJS // RxJS
import 'rxjs'; import 'rxjs';
// Bootstrap
import 'theme/vendor.scss';

2
src/Squidex/wwwroot/index.html

@ -7,6 +7,6 @@
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
</head> </head>
<body> <body>
<my-app>Loading...</my-app> <sqx-app>Loading...</sqx-app>
</body> </body>
</html> </html>
Loading…
Cancel
Save