diff --git a/src/Squidex/app/app.component.html b/src/Squidex/app/app.component.html
index 16227ef88..157c6ce2d 100644
--- a/src/Squidex/app/app.component.html
+++ b/src/Squidex/app/app.component.html
@@ -1 +1,15 @@
-
\ No newline at end of file
+
+
+
+
+
diff --git a/src/Squidex/app/app.component.scss b/src/Squidex/app/app.component.scss
index 040e5e425..01319f4c0 100644
--- a/src/Squidex/app/app.component.scss
+++ b/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 {
- padding: 1em;
- font-family: Arial, Helvetica, sans-serif;
- font-size: 1.1rem;
- text-align: center;
- margin-top: 50px;
- display: block;
+ margin-top: 54px
}
\ No newline at end of file
diff --git a/src/Squidex/app/app.component.spec.ts b/src/Squidex/app/app.component.spec.ts
index d6fc9c607..0cc8bde5a 100644
--- a/src/Squidex/app/app.component.spec.ts
+++ b/src/Squidex/app/app.component.spec.ts
@@ -3,6 +3,10 @@ import { TestBed } from '@angular/core/testing';
import { RouterModule, provideRoutes } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
+import { SqxLayoutModule } from './components/layout';
+
+import { AppsStoreService } from './shared';
+
import { AppComponent } from './app.component';
describe('App', () => {
@@ -13,10 +17,12 @@ describe('App', () => {
],
imports: [
RouterModule,
- RouterTestingModule
+ RouterTestingModule,
+ SqxLayoutModule
],
providers: [
- provideRoutes([])
+ provideRoutes([]),
+ { provide: AppsStoreService, useValue: new AppsStoreService(null, null) }
]
});
});
diff --git a/src/Squidex/app/app.component.ts b/src/Squidex/app/app.component.ts
index 9ed0b28d8..92f567eaf 100644
--- a/src/Squidex/app/app.component.ts
+++ b/src/Squidex/app/app.component.ts
@@ -8,8 +8,8 @@
import * as Ng2 from '@angular/core';
@Ng2.Component({
- selector: 'my-app',
- template,
- styles
+ selector: 'sqx-app',
+ styles,
+ template
})
export class AppComponent { }
\ No newline at end of file
diff --git a/src/Squidex/app/app.module.ts b/src/Squidex/app/app.module.ts
index d5d3c3b23..01b21d2b1 100644
--- a/src/Squidex/app/app.module.ts
+++ b/src/Squidex/app/app.module.ts
@@ -19,15 +19,20 @@ import {
} from './framework';
import {
+ AppsStoreService,
+ AppsService,
AuthGuard,
AuthService,
} from './shared';
import {
- MyAppModule,
- MyLoginModule
+ SqxAppModule,
+ SqxLayoutModule,
+ SqxLoginModule
} from './components';
+import { SqxFrameworkModule } from './framework';
+
import { routing } from './app.routes';
const baseUrl = window.location.protocol + '//' + window.location.host + '/';
@@ -35,14 +40,18 @@ const baseUrl = window.location.protocol + '//' + window.location.host + '/';
@Ng2.NgModule({
imports: [
Ng2Browser.BrowserModule,
- MyAppModule,
- MyLoginModule,
+ SqxAppModule,
+ SqxLayoutModule,
+ SqxLoginModule,
+ SqxFrameworkModule,
routing
],
declarations: [
AppComponent
],
providers: [
+ AppsStoreService,
+ AppsService,
AuthGuard,
AuthService,
{ provide: ApiUrlConfig, useValue: new ApiUrlConfig(baseUrl) },
diff --git a/src/Squidex/app/app.routes.ts b/src/Squidex/app/app.routes.ts
index 513aeafa4..b74d4f669 100644
--- a/src/Squidex/app/app.routes.ts
+++ b/src/Squidex/app/app.routes.ts
@@ -9,8 +9,8 @@ import * as Ng2 from '@angular/core';
import * as Ng2Router from '@angular/router';
import {
- AppsComponent,
- LoginComponent
+ AppsPageComponent,
+ LoginPageComponent
} from './components';
import {
@@ -25,12 +25,12 @@ export const routes: Ng2Router.Routes = [
},
{
path: 'apps',
- component: AppsComponent,
+ component: AppsPageComponent,
canActivate: [AuthGuard]
},
{
path: 'login',
- component: LoginComponent
+ component: LoginPageComponent
}
];
diff --git a/src/Squidex/app/components/apps/apps-page.component.html b/src/Squidex/app/components/apps/apps-page.component.html
new file mode 100644
index 000000000..26449ea61
--- /dev/null
+++ b/src/Squidex/app/components/apps/apps-page.component.html
@@ -0,0 +1,27 @@
+
+
+
You are not collaborating to any app yet
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Squidex/app/components/apps/apps-page.component.scss b/src/Squidex/app/components/apps/apps-page.component.scss
new file mode 100644
index 000000000..3b5bb8840
--- /dev/null
+++ b/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;
+ }
+}
\ No newline at end of file
diff --git a/src/Squidex/app/components/apps/apps-page.component.ts b/src/Squidex/app/components/apps/apps-page.component.ts
new file mode 100644
index 000000000..3a4bb1aff
--- /dev/null
+++ b/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();
+}
\ No newline at end of file
diff --git a/src/Squidex/app/components/apps/apps.component.html b/src/Squidex/app/components/apps/apps.component.html
deleted file mode 100644
index aa8f55823..000000000
--- a/src/Squidex/app/components/apps/apps.component.html
+++ /dev/null
@@ -1,3 +0,0 @@
-
- Hello from Angular 2 App with Webpack!
-
\ No newline at end of file
diff --git a/src/Squidex/app/components/apps/apps.module.ts b/src/Squidex/app/components/apps/apps.module.ts
index f74f3f618..fbeeb6850 100644
--- a/src/Squidex/app/components/apps/apps.module.ts
+++ b/src/Squidex/app/components/apps/apps.module.ts
@@ -7,18 +7,20 @@
import * as Ng2 from '@angular/core';
-import { FrameworkModule } from './../../framework';
+import { SqxFrameworkModule } from './../../framework';
+import { SqxLayoutModule } from './../layout';
import {
- AppsComponent
+ AppsPageComponent
} from './declarations';
@Ng2.NgModule({
imports: [
- FrameworkModule
+ SqxFrameworkModule,
+ SqxLayoutModule
],
declarations: [
- AppsComponent
+ AppsPageComponent
]
})
-export class MyAppModule { }
\ No newline at end of file
+export class SqxAppModule { }
\ No newline at end of file
diff --git a/src/Squidex/app/components/apps/declarations.ts b/src/Squidex/app/components/apps/declarations.ts
index cb1bc8d45..b8ee4800a 100644
--- a/src/Squidex/app/components/apps/declarations.ts
+++ b/src/Squidex/app/components/apps/declarations.ts
@@ -5,4 +5,4 @@
* Copyright (c) Sebastian Stehle. All rights reserved
*/
-export * from './apps.component';
\ No newline at end of file
+export * from './apps-page.component';
\ No newline at end of file
diff --git a/src/Squidex/app/components/apps/index.ts b/src/Squidex/app/components/apps/index.ts
index 46ecc5b9e..8b961c614 100644
--- a/src/Squidex/app/components/apps/index.ts
+++ b/src/Squidex/app/components/apps/index.ts
@@ -5,5 +5,6 @@
* Copyright (c) Sebastian Stehle. All rights reserved
*/
-export * from './apps.component';
+export * from './declarations';
+
export * from './apps.module';
\ No newline at end of file
diff --git a/src/Squidex/app/components/index.ts b/src/Squidex/app/components/index.ts
index a856e83bb..61702b4e3 100644
--- a/src/Squidex/app/components/index.ts
+++ b/src/Squidex/app/components/index.ts
@@ -6,4 +6,5 @@
*/
export * from './apps';
+export * from './layout';
export * from './login';
\ No newline at end of file
diff --git a/src/Squidex/app/components/layout/app-form.component.html b/src/Squidex/app/components/layout/app-form.component.html
new file mode 100644
index 000000000..53c42f3cc
--- /dev/null
+++ b/src/Squidex/app/components/layout/app-form.component.html
@@ -0,0 +1,31 @@
+
\ No newline at end of file
diff --git a/src/Squidex/app/components/layout/app-form.component.scss b/src/Squidex/app/components/layout/app-form.component.scss
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/Squidex/app/components/layout/app-form.component.ts b/src/Squidex/app/components/layout/app-form.component.ts
new file mode 100644
index 000000000..41e90f31e
--- /dev/null
+++ b/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;
+
+ @Ng2.Input()
+ public showClose = false;
+
+ @Ng2.Output()
+ public onCreated = new Ng2.EventEmitter();
+
+ @Ng2.Output()
+ public onCancelled = new Ng2.EventEmitter();
+
+ public creating = new Ng2.EventEmitter();
+
+ public creationError = new Ng2.EventEmitter();
+
+ 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();
+ }
+}
\ No newline at end of file
diff --git a/src/Squidex/app/components/layout/apps-menu.component.html b/src/Squidex/app/components/layout/apps-menu.component.html
new file mode 100644
index 000000000..d61412a7f
--- /dev/null
+++ b/src/Squidex/app/components/layout/apps-menu.component.html
@@ -0,0 +1,41 @@
+
+ -
+ My App with a really very long name
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Squidex/app/components/layout/apps-menu.component.scss b/src/Squidex/app/components/layout/apps-menu.component.scss
new file mode 100644
index 000000000..d6e18b3eb
--- /dev/null
+++ b/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);
+ }
+}
\ No newline at end of file
diff --git a/src/Squidex/app/components/layout/apps-menu.component.ts b/src/Squidex/app/components/layout/apps-menu.component.ts
new file mode 100644
index 000000000..da8e8ff11
--- /dev/null
+++ b/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();
+ }
+}
\ No newline at end of file
diff --git a/src/Squidex/app/components/layout/declarations.ts b/src/Squidex/app/components/layout/declarations.ts
new file mode 100644
index 000000000..924233228
--- /dev/null
+++ b/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';
\ No newline at end of file
diff --git a/src/Squidex/app/components/layout/index.ts b/src/Squidex/app/components/layout/index.ts
new file mode 100644
index 000000000..3f66f638f
--- /dev/null
+++ b/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';
\ No newline at end of file
diff --git a/src/Squidex/app/components/layout/layout.module.ts b/src/Squidex/app/components/layout/layout.module.ts
new file mode 100644
index 000000000..5c0ecfdf6
--- /dev/null
+++ b/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 { }
\ No newline at end of file
diff --git a/src/Squidex/app/components/layout/search-form.component.html b/src/Squidex/app/components/layout/search-form.component.html
new file mode 100644
index 000000000..3559a01f1
--- /dev/null
+++ b/src/Squidex/app/components/layout/search-form.component.html
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/src/Squidex/app/components/layout/search-form.component.scss b/src/Squidex/app/components/layout/search-form.component.scss
new file mode 100644
index 000000000..b703e4d4b
--- /dev/null
+++ b/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%);
+ }
+}
\ No newline at end of file
diff --git a/src/Squidex/app/components/apps/apps.component.ts b/src/Squidex/app/components/layout/search-form.component.ts
similarity index 67%
rename from src/Squidex/app/components/apps/apps.component.ts
rename to src/Squidex/app/components/layout/search-form.component.ts
index 84f9fd40e..212e8d4b6 100644
--- a/src/Squidex/app/components/apps/apps.component.ts
+++ b/src/Squidex/app/components/layout/search-form.component.ts
@@ -1,4 +1,4 @@
-/*
+/*
* Squidex Headless CMS
*
* @license
@@ -8,9 +8,10 @@
import * as Ng2 from '@angular/core';
@Ng2.Component({
- selector: 'apps',
+ selector: 'sqx-search-form',
+ styles,
template
})
-export class AppsComponent {
+export class SearchFormComponent {
}
\ No newline at end of file
diff --git a/src/Squidex/app/components/login/declarations.ts b/src/Squidex/app/components/login/declarations.ts
index c3fdc28a2..830720b45 100644
--- a/src/Squidex/app/components/login/declarations.ts
+++ b/src/Squidex/app/components/login/declarations.ts
@@ -5,4 +5,4 @@
* Copyright (c) Sebastian Stehle. All rights reserved
*/
-export * from './login.component';
\ No newline at end of file
+export * from './login-page.component';
\ No newline at end of file
diff --git a/src/Squidex/app/components/login/index.ts b/src/Squidex/app/components/login/index.ts
index 9451cbeda..4ec27fcf1 100644
--- a/src/Squidex/app/components/login/index.ts
+++ b/src/Squidex/app/components/login/index.ts
@@ -5,5 +5,6 @@
* Copyright (c) Sebastian Stehle. All rights reserved
*/
-export * from './login.component';
+export * from './declarations';
+
export * from './login.module';
\ No newline at end of file
diff --git a/src/Squidex/app/components/login/login.component.html b/src/Squidex/app/components/login/login-page.component.html
similarity index 100%
rename from src/Squidex/app/components/login/login.component.html
rename to src/Squidex/app/components/login/login-page.component.html
diff --git a/src/Squidex/app/components/login/login.component.ts b/src/Squidex/app/components/login/login-page.component.ts
similarity index 92%
rename from src/Squidex/app/components/login/login.component.ts
rename to src/Squidex/app/components/login/login-page.component.ts
index a0bedcfd1..d569945bf 100644
--- a/src/Squidex/app/components/login/login.component.ts
+++ b/src/Squidex/app/components/login/login-page.component.ts
@@ -14,7 +14,7 @@ import { AuthService } from './../../shared';
selector: 'login',
template
})
-export class LoginComponent implements Ng2.OnInit {
+export class LoginPageComponent implements Ng2.OnInit {
public showFailedError = false;
constructor(
diff --git a/src/Squidex/app/components/login/login.module.ts b/src/Squidex/app/components/login/login.module.ts
index 4ebbe7d3f..add4240f6 100644
--- a/src/Squidex/app/components/login/login.module.ts
+++ b/src/Squidex/app/components/login/login.module.ts
@@ -7,18 +7,18 @@
import * as Ng2 from '@angular/core';
-import { FrameworkModule } from './../../framework';
+import { SqxFrameworkModule } from './../../framework';
import {
- LoginComponent
+ LoginPageComponent
} from './declarations';
@Ng2.NgModule({
imports: [
- FrameworkModule
+ SqxFrameworkModule
],
declarations: [
- LoginComponent
+ LoginPageComponent
]
})
-export class MyLoginModule { }
\ No newline at end of file
+export class SqxLoginModule { }
\ No newline at end of file
diff --git a/src/Squidex/app/framework/angular/animations.ts b/src/Squidex/app/framework/angular/animations.ts
new file mode 100644
index 000000000..9c08d3350
--- /dev/null
+++ b/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))
+ ]
+ );
+}
+
\ No newline at end of file
diff --git a/src/Squidex/app/framework/angular/cloak.directive.ts b/src/Squidex/app/framework/angular/cloak.directive.ts
index 359f6668c..6b6e6398b 100644
--- a/src/Squidex/app/framework/angular/cloak.directive.ts
+++ b/src/Squidex/app/framework/angular/cloak.directive.ts
@@ -8,12 +8,12 @@
import * as Ng2 from '@angular/core';
@Ng2.Directive({
- selector: '.gp-cloak'
+ selector: '.sqx-cloak'
})
export class CloakDirective implements Ng2.OnInit {
constructor(private readonly element: Ng2.ElementRef) { }
public ngOnInit() {
- this.element.nativeElement.classList.remove('gp-cloak');
+ this.element.nativeElement.classList.remove('sqx-cloak');
}
}
\ No newline at end of file
diff --git a/src/Squidex/app/framework/angular/color-picker.component.ts b/src/Squidex/app/framework/angular/color-picker.component.ts
index 0020b36a8..70b8d94b5 100644
--- a/src/Squidex/app/framework/angular/color-picker.component.ts
+++ b/src/Squidex/app/framework/angular/color-picker.component.ts
@@ -16,7 +16,7 @@ import {
} from './../utils/color-palette';
@Ng2.Component({
- selector: 'gp-color-picker',
+ selector: 'sqx-color-picker',
styles,
template
})
diff --git a/src/Squidex/app/framework/angular/drag-model.directive.ts b/src/Squidex/app/framework/angular/drag-model.directive.ts
index a918dcfc4..59fe9212a 100644
--- a/src/Squidex/app/framework/angular/drag-model.directive.ts
+++ b/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);
- while (dropCandidate && (dropCandidate.classList && !dropCandidate.classList.contains('gp-drop'))) {
+ while (dropCandidate && (dropCandidate.classList && !dropCandidate.classList.contains('sqx-drop'))) {
dropCandidate = dropCandidate.parentNode as Element;
}
diff --git a/src/Squidex/app/framework/angular/image-drop.directive.ts b/src/Squidex/app/framework/angular/image-drop.directive.ts
index 1caa7d347..8b057f77b 100644
--- a/src/Squidex/app/framework/angular/image-drop.directive.ts
+++ b/src/Squidex/app/framework/angular/image-drop.directive.ts
@@ -11,7 +11,7 @@ import { DragService } from './../services/drag.service';
import { Vec2 } from './../utils/vec2';
@Ng2.Directive({
- selector: '.gp-image-drop'
+ selector: '.sqx-image-drop'
})
export class ImageDropDirective {
constructor(
diff --git a/src/Squidex/app/framework/angular/modal-view.directive.ts b/src/Squidex/app/framework/angular/modal-view.directive.ts
new file mode 100644
index 000000000..4e76e2985
--- /dev/null
+++ b/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);
+ }
+}
\ No newline at end of file
diff --git a/src/Squidex/app/framework/angular/shortcut.component.ts b/src/Squidex/app/framework/angular/shortcut.component.ts
index e947b97c1..52e1dcef5 100644
--- a/src/Squidex/app/framework/angular/shortcut.component.ts
+++ b/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: 'gp-shortcut',
+ selector: 'sqx-shortcut',
template: ''
})
export class ShortcutComponent implements Ng2.OnInit, Ng2.OnDestroy {
diff --git a/src/Squidex/app/framework/angular/slider.component.ts b/src/Squidex/app/framework/angular/slider.component.ts
index 6ee092926..99cdf5f43 100644
--- a/src/Squidex/app/framework/angular/slider.component.ts
+++ b/src/Squidex/app/framework/angular/slider.component.ts
@@ -8,7 +8,7 @@
import * as Ng2 from '@angular/core';
@Ng2.Component({
- selector: 'gp-slider',
+ selector: 'sqx-slider',
styles,
template
})
diff --git a/src/Squidex/app/framework/angular/spinner.component.ts b/src/Squidex/app/framework/angular/spinner.component.ts
index 8517df0a7..431dd1c8d 100644
--- a/src/Squidex/app/framework/angular/spinner.component.ts
+++ b/src/Squidex/app/framework/angular/spinner.component.ts
@@ -10,7 +10,7 @@ import * as Ng2 from '@angular/core';
declare var Spinner: any;
@Ng2.Component({
- selector: 'gp-spinner',
+ selector: 'sqx-spinner',
template: ''
})
export class SpinnerComponent {
diff --git a/src/Squidex/app/framework/angular/user-report.component.ts b/src/Squidex/app/framework/angular/user-report.component.ts
index 27bf2f611..54e83a24f 100644
--- a/src/Squidex/app/framework/angular/user-report.component.ts
+++ b/src/Squidex/app/framework/angular/user-report.component.ts
@@ -10,7 +10,7 @@ import * as Ng2 from '@angular/core';
import { UserReportConfig } from './../configurations';
@Ng2.Component({
- selector: 'gp-user-report',
+ selector: 'sqx-user-report',
template: ''
})
export class UserReportComponent implements Ng2.OnInit {
diff --git a/src/Squidex/app/framework/declarations.ts b/src/Squidex/app/framework/declarations.ts
index 58bcedfaf..207d46e55 100644
--- a/src/Squidex/app/framework/declarations.ts
+++ b/src/Squidex/app/framework/declarations.ts
@@ -5,16 +5,42 @@
* 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/color-picker.component';
export * from './angular/date-time.pipes';
export * from './angular/drag-model.directive';
export * from './angular/focus-on-change.directive';
export * from './angular/image-drop.directive';
+export * from './angular/modal-view.directive';
export * from './angular/money.pipe';
export * from './angular/shortcut.component';
export * from './angular/slider.component';
export * from './angular/spinner.component';
export * from './angular/user-report.component';
+export * from './configurations';
+
+export * from './services/clipboard.service';
export * from './services/drag.service';
-export * from './services/title.service';
\ No newline at end of file
+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';
\ No newline at end of file
diff --git a/src/Squidex/app/framework/framework.module.ts b/src/Squidex/app/framework/framework.module.ts
index cd9909f30..3e2482e16 100644
--- a/src/Squidex/app/framework/framework.module.ts
+++ b/src/Squidex/app/framework/framework.module.ts
@@ -16,10 +16,9 @@ import {
ColorPickerComponent,
DayOfWeekPipe,
DayPipe,
- DragModelDirective,
DurationPipe,
FocusOnChangeDirective,
- ImageDropDirective,
+ ModalViewDirective,
MoneyPipe,
MonthPipe,
ShortcutComponent,
@@ -32,18 +31,20 @@ import {
@Ng2.NgModule({
imports: [
+ Ng2Http.HttpModule,
Ng2Forms.FormsModule,
- Ng2Common.CommonModule
+ Ng2Forms.ReactiveFormsModule,
+ Ng2Common.CommonModule,
+ Ng2Router.RouterModule
],
declarations: [
CloakDirective,
ColorPickerComponent,
DayOfWeekPipe,
DayPipe,
- DragModelDirective,
DurationPipe,
FocusOnChangeDirective,
- ImageDropDirective,
+ ModalViewDirective,
MoneyPipe,
MonthPipe,
ShortcutComponent,
@@ -51,7 +52,7 @@ import {
ShortTimePipe,
SliderComponent,
SpinnerComponent,
- UserReportComponent
+ UserReportComponent,
],
exports: [
CloakDirective,
@@ -60,6 +61,7 @@ import {
DayPipe,
DurationPipe,
FocusOnChangeDirective,
+ ModalViewDirective,
MoneyPipe,
MonthPipe,
ShortcutComponent,
@@ -70,8 +72,9 @@ import {
UserReportComponent,
Ng2Http.HttpModule,
Ng2Forms.FormsModule,
+ Ng2Forms.ReactiveFormsModule,
Ng2Common.CommonModule,
Ng2Router.RouterModule
]
})
-export class FrameworkModule { }
\ No newline at end of file
+export class SqxFrameworkModule { }
\ No newline at end of file
diff --git a/src/Squidex/app/framework/index.ts b/src/Squidex/app/framework/index.ts
index b5a8af614..7b39fb2ac 100644
--- a/src/Squidex/app/framework/index.ts
+++ b/src/Squidex/app/framework/index.ts
@@ -5,26 +5,6 @@
* Copyright (c) Sebastian Stehle. All rights reserved
*/
-export * from './angular/action';
-export * from './angular/validators';
-export * from './configurations';
-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';
\ No newline at end of file
+export * from './declarations';
+
+export * from './framework.module';
\ No newline at end of file
diff --git a/src/Squidex/app/framework/services/clipboard.service.spec.ts b/src/Squidex/app/framework/services/clipboard.service.spec.ts
index 7a3ba0fc4..3c80cfaf5 100644
--- a/src/Squidex/app/framework/services/clipboard.service.spec.ts
+++ b/src/Squidex/app/framework/services/clipboard.service.spec.ts
@@ -40,7 +40,7 @@ describe('ShortcutService', () => {
let text = '';
- clipboardService.text.subscribe(t => {
+ clipboardService.textChanges.subscribe(t => {
text = t;
});
diff --git a/src/Squidex/app/framework/services/clipboard.service.ts b/src/Squidex/app/framework/services/clipboard.service.ts
index 8d5ee5c92..ff3675ca2 100644
--- a/src/Squidex/app/framework/services/clipboard.service.ts
+++ b/src/Squidex/app/framework/services/clipboard.service.ts
@@ -15,16 +15,16 @@ export const ClipboardServiceFactory = () => {
@Ng2.Injectable()
export class ClipboardService {
- private textInstance = new BehaviorSubject('');
+ private readonly text$ = new BehaviorSubject('');
- public get text(): Observable {
- return this.textInstance;
+ public get textChanges(): Observable {
+ return this.text$;
}
public selectText(): string {
let result = '';
- this.textInstance.subscribe(t => {
+ this.text$.subscribe(t => {
result = t;
}).unsubscribe();
@@ -32,6 +32,6 @@ export class ClipboardService {
}
public setText(text: any) {
- this.textInstance.next(text);
+ this.text$.next(text);
}
}
\ No newline at end of file
diff --git a/src/Squidex/app/framework/services/drag.service.spec.ts b/src/Squidex/app/framework/services/drag.service.spec.ts
index 158bf554f..418a51dd7 100644
--- a/src/Squidex/app/framework/services/drag.service.spec.ts
+++ b/src/Squidex/app/framework/services/drag.service.spec.ts
@@ -29,7 +29,7 @@ describe('DragService', () => {
const dragService = new DragService();
- dragService.drop.subscribe(e => {
+ dragService.onDrop.subscribe(e => {
emittedEvent = e;
});
diff --git a/src/Squidex/app/framework/services/drag.service.ts b/src/Squidex/app/framework/services/drag.service.ts
index 7807ff1b5..80f7ce015 100644
--- a/src/Squidex/app/framework/services/drag.service.ts
+++ b/src/Squidex/app/framework/services/drag.service.ts
@@ -21,7 +21,7 @@ export const DragServiceFactory = () => {
export class DragService {
private readonly dropEvent = new Subject();
- public get drop(): Observable {
+ public get onDrop(): Observable {
return this.dropEvent;
}
diff --git a/src/Squidex/app/framework/utils/modal-view.spec.ts b/src/Squidex/app/framework/utils/modal-view.spec.ts
new file mode 100644
index 000000000..7214110f0
--- /dev/null
+++ b/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);
+ }
+});
+
diff --git a/src/Squidex/app/framework/utils/modal-view.ts b/src/Squidex/app/framework/utils/modal-view.ts
new file mode 100644
index 000000000..440d05a5c
--- /dev/null
+++ b/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;
+
+ public get isOpenChanges(): Observable {
+ 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);
+ }
+}
\ No newline at end of file
diff --git a/src/Squidex/app/shared/index.ts b/src/Squidex/app/shared/index.ts
index 118e5a6f8..e4c1a1998 100644
--- a/src/Squidex/app/shared/index.ts
+++ b/src/Squidex/app/shared/index.ts
@@ -6,4 +6,6 @@
*/
export * from './guards/auth.guard';
+export * from './services/apps-store.service';
+export * from './services/apps.service';
export * from './services/auth.service';
\ No newline at end of file
diff --git a/src/Squidex/app/shared/services/apps-store.service.spec.ts b/src/Squidex/app/shared/services/apps-store.service.spec.ts
new file mode 100644
index 000000000..1406f39e0
--- /dev/null
+++ b/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;
+ let authService: TypeMoq.Mock;
+
+ 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();
+ });
+});
\ No newline at end of file
diff --git a/src/Squidex/app/shared/services/apps-store.service.ts b/src/Squidex/app/shared/services/apps-store.service.ts
new file mode 100644
index 000000000..e5c43c074
--- /dev/null
+++ b/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(null);
+
+ public get appsChanges(): Observable {
+ 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 {
+ return this.appService.postApp(appToCreate).do(app => {
+ if (this.lastApps && app) {
+ this.apps$.next(this.lastApps.concat([app]));
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/Squidex/app/shared/services/apps.service.ts b/src/Squidex/app/shared/services/apps.service.ts
index ee2aa5581..63d1685cd 100644
--- a/src/Squidex/app/shared/services/apps.service.ts
+++ b/src/Squidex/app/shared/services/apps.service.ts
@@ -14,17 +14,17 @@ import { AuthService } from './auth.service';
export class AppDto {
constructor(
- private readonly id: string,
- private readonly name: string,
- private readonly created: DateTime,
- private readonly lastModified: DateTime
+ public readonly id: string,
+ public readonly name: string,
+ public readonly created: DateTime,
+ public readonly lastModified: DateTime
) {
}
}
export class AppCreateDto {
constructor(
- private readonly name: string
+ public readonly name: string
) {
}
}
@@ -53,7 +53,13 @@ export class AppsService {
});
}
- public postApp(app: AppCreateDto): Observable {
- return this.authService.authPost(this.apiUrl.buildUrl('api/apps'), app);
+ public postApp(appToCreate: AppCreateDto): Observable {
+ 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);
+ });
}
}
\ No newline at end of file
diff --git a/src/Squidex/app/shared/services/auth.service.ts b/src/Squidex/app/shared/services/auth.service.ts
index 7df98f2b6..6a03d0f3c 100644
--- a/src/Squidex/app/shared/services/auth.service.ts
+++ b/src/Squidex/app/shared/services/auth.service.ts
@@ -14,13 +14,14 @@ import {
UserManager
} from 'oidc-client';
-import { Observable } from 'rxjs';
+import { BehaviorSubject, Observable } from 'rxjs';
import { ApiUrlConfig } from './../../framework';
@Ng2.Injectable()
export class AuthService {
private readonly userManager: UserManager;
+ private readonly isAuthenticatedChanged$ = new BehaviorSubject(false);
private currentUser: User | null = null;
private checkLoginPromise: Promise;
@@ -32,6 +33,38 @@ export class AuthService {
return !!this.currentUser;
}
+ public get isAuthenticatedChanges(): Observable {
+ 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 {
if (this.checkLoginPromise) {
return this.checkLoginPromise;
@@ -39,7 +72,7 @@ export class AuthService {
return Promise.resolve(true);
} else {
this.checkLoginPromise =
- this.checkState(this.userManager.getUser())
+ this.checkState(this.userManager.signinSilent())
.then(result => {
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 {
return Observable.fromPromise(this.userManager.signoutRedirectCallback());
}
@@ -87,12 +95,24 @@ export class AuthService {
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): Promise {
const resultPromise =
promise
.then(user => {
if (user) {
- this.currentUser = user;
+ this.onAuthenticated(user);
}
return !!this.currentUser;
}).catch((err) => {
@@ -128,11 +148,13 @@ export class AuthService {
private setRequestOptions(options?: Ng2Http.RequestOptions) {
if (!options) {
options = new Ng2Http.RequestOptions();
+ }
+ if (!options.headers) {
+ options.headers = new Ng2Http.Headers();
options.headers.append('Content-Type', 'application/json');
}
-
- options.headers.append('Authorization', '${this.user.token_type} {this.user.access_token}');
+ options.headers.append('Authorization', `${this.currentUser.token_type} ${this.currentUser.access_token}`);
return options;
}
diff --git a/src/Squidex/app/theme/_bootstrap.scss b/src/Squidex/app/theme/_bootstrap.scss
index 64a90dcf0..274f0a945 100644
--- a/src/Squidex/app/theme/_bootstrap.scss
+++ b/src/Squidex/app/theme/_bootstrap.scss
@@ -1,69 +1,50 @@
@import '_mixins.scss';
@import '_vars.scss';
-.nav-icon {
- & {
- margin-top: -0.5rem;
- text-align: center;
- text-decoration: none;
- cursor: default;
- }
-
- span {
- display: block;
- font-size: 0.8em;
- font-weight: normal;
- cursor: default;
- }
+.form-hint {
+ font-size: 0.8rem;
}
-.navbar-light .navbar-nav .nav-link, .btn-icon {
- & {
- color: $nav-text-color;
- }
-
- &:hover, &:focus {
- text-decoration: none;
- }
-
- &:hover {
- color: black;
- }
+.ng-invalid.ng-dirty {
+ border-color: $accent-error;
+}
- &.disabled {
- color: lighten($nav-text-color, 55%);
- }
+.ng-invalid.ng-dirty :focus, .ng-invalid.ng-dirty :hover {
+ border-color: $accent-error-dark;
}
-.navbar-light .navbar-nav .nav-link.btn-blue, .btn-icon.btn-blue, .btn-blue {
- &:hover {
- color: $accent-normal;
+.errors {
+ &-box {
+ position: relative;
}
- &:focus {
- color: $accent-dark
+ &:after {
+ @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 {
- padding-left: 0.4rem;
- padding-right: 0.4rem;
- cursor: default;
-}
-
-.navbar-nav .nav-item + .nav-nomargin {
- margin-left: 0;
- margin-right: -1rem;
+.modal-content {
+ @include box-shadow(0px, 6px, 16px, 0.2px);
}
-.nav-separator {
- margin-top: .425rem;
- margin-bottom: .425rem;
- margin-left: 1.2em;
- margin-right: 1.2em;
- display: block;
+.modal-content, .modal-header {
+ border: 0;
}
\ No newline at end of file
diff --git a/src/Squidex/app/theme/_vars.scss b/src/Squidex/app/theme/_vars.scss
index 08896bc36..9f6b7004d 100644
--- a/src/Squidex/app/theme/_vars.scss
+++ b/src/Squidex/app/theme/_vars.scss
@@ -1,4 +1,10 @@
$nav-text-color: #333;
-$accent-normal: blue;
-$accent-dark: darkblue;
\ No newline at end of file
+$accent-blue: #438CEF;
+$accent-blue-dark: #3F83DF;
+
+$accent-green: #4CC159;
+$accent-green-dark: #47B353;
+
+$accent-error: red;
+$accent-error-dark: darken(red, 5%);
\ No newline at end of file
diff --git a/src/Squidex/app/theme/_vendor-overrides.scss b/src/Squidex/app/theme/_vendor-overrides.scss
new file mode 100644
index 000000000..1a97cb2dc
--- /dev/null
+++ b/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;
\ No newline at end of file
diff --git a/src/Squidex/app/theme/vendor.scss b/src/Squidex/app/theme/vendor.scss
index 07031f251..bbd65bbfb 100644
--- a/src/Squidex/app/theme/vendor.scss
+++ b/src/Squidex/app/theme/vendor.scss
@@ -1,4 +1,4 @@
-$fa-font-path: "~font-awesome/fonts";
+@import '_vendor-overrides.scss';
// Font Awesome
@import './../../node_modules/font-awesome/scss/font-awesome.scss';
diff --git a/src/Squidex/app/vendor.ts b/src/Squidex/app/vendor.ts
index 3b16330ab..41b7a3bb8 100644
--- a/src/Squidex/app/vendor.ts
+++ b/src/Squidex/app/vendor.ts
@@ -18,4 +18,7 @@ import '@angular/router';
import 'oidc-client';
// RxJS
-import 'rxjs';
\ No newline at end of file
+import 'rxjs';
+
+// Bootstrap
+import 'theme/vendor.scss';
\ No newline at end of file
diff --git a/src/Squidex/wwwroot/index.html b/src/Squidex/wwwroot/index.html
index bc47aa5fc..b83038cba 100644
--- a/src/Squidex/wwwroot/index.html
+++ b/src/Squidex/wwwroot/index.html
@@ -7,6 +7,6 @@
- Loading...
+ Loading...