Browse Source

app selection fixed.

pull/164/head
Sebastian Stehle 8 years ago
parent
commit
8dc89b5dc9
  1. 10
      src/Squidex/app/app.routes.ts
  2. 6
      src/Squidex/app/features/apps/pages/apps-page.component.ts
  3. 2
      src/Squidex/app/features/content/module.ts
  4. 9
      src/Squidex/app/features/content/pages/content/content-page.component.ts
  5. 17
      src/Squidex/app/features/content/pages/contents/contents-page.component.ts
  6. 19
      src/Squidex/app/shared/components/app-context.ts
  7. 6
      src/Squidex/app/shared/components/app-form.component.ts
  8. 4
      src/Squidex/app/shared/declarations-base.ts
  9. 73
      src/Squidex/app/shared/guards/app-must-exist.guard.spec.ts
  10. 40
      src/Squidex/app/shared/guards/app-must-exist.guard.ts
  11. 45
      src/Squidex/app/shared/guards/resolve-app.guard.ts
  12. 25
      src/Squidex/app/shared/guards/unset-app.guard.ts
  13. 8
      src/Squidex/app/shared/module.ts
  14. 107
      src/Squidex/app/shared/services/apps-store.service.spec.ts
  15. 70
      src/Squidex/app/shared/services/apps-store.service.ts
  16. 21
      src/Squidex/app/shared/services/apps.service.spec.ts
  17. 66
      src/Squidex/app/shared/services/apps.service.ts
  18. 11
      src/Squidex/app/shell/pages/internal/apps-menu.component.html
  19. 21
      src/Squidex/app/shell/pages/internal/apps-menu.component.scss
  20. 8
      src/Squidex/app/shell/pages/internal/apps-menu.component.ts
  21. 7
      src/Squidex/app/theme/_mixins.scss

10
src/Squidex/app/app.routes.ts

@ -18,9 +18,10 @@ import {
} from './shell';
import {
AppMustExistGuard,
MustBeAuthenticatedGuard,
MustBeNotAuthenticatedGuard,
ResolveAppGuard
UnsetAppGuard
} from './shared';
export const routes: Routes = [
@ -36,7 +37,8 @@ export const routes: Routes = [
children: [
{
path: '',
loadChildren: './features/apps/module#SqxFeatureAppsModule'
loadChildren: './features/apps/module#SqxFeatureAppsModule',
canActivate: [UnsetAppGuard]
},
{
path: 'administration',
@ -45,9 +47,7 @@ export const routes: Routes = [
{
path: ':appName',
component: AppAreaComponent,
resolve: {
app: ResolveAppGuard
},
canActivate: [AppMustExistGuard],
children: [
{
path: '',

6
src/Squidex/app/features/apps/pages/apps-page.component.ts

@ -10,7 +10,7 @@ import { Subscription } from 'rxjs';
import {
AppDto,
AppsService,
AppsStoreService,
fadeAnimation,
ModalView,
OnboardingService
@ -33,7 +33,7 @@ export class AppsPageComponent implements OnDestroy, OnInit {
public onboardingModal = new ModalView();
constructor(
private readonly appsService: AppsService,
private readonly appsStore: AppsStoreService,
private readonly onboardingService: OnboardingService
) {
}
@ -44,7 +44,7 @@ export class AppsPageComponent implements OnDestroy, OnInit {
public ngOnInit() {
this.appsSubscription =
this.appsService.getApps()
this.appsStore.apps
.subscribe(apps => {
if (apps.length === 0 && this.onboardingService.shouldShow('dialog')) {
this.onboardingService.disable('dialog');

2
src/Squidex/app/features/content/module.ts

@ -61,7 +61,7 @@ const routes: Routes = [
isReadOnly: true
},
resolve: {
schemaOverride: ResolvePublishedSchemaGuard
schema: ResolvePublishedSchemaGuard
}
}
]

9
src/Squidex/app/features/content/pages/content/content-page.component.ts

@ -68,9 +68,6 @@ export class ContentPageComponent implements CanComponentDeactivate, OnDestroy,
}
public ngOnInit() {
const routeData = allData(this.ctx.route);
this.languages = routeData['appLanguages'];
this.contentVersionSelectedSubscription =
this.ctx.bus.of(ContentVersionSelected)
@ -102,7 +99,11 @@ export class ContentPageComponent implements CanComponentDeactivate, OnDestroy,
}
});
this.setupContentForm(routeData['schema']);
const routeData = allData(this.ctx.route);
this.languages = routeData.appLanguages;
this.setupContentForm(routeData.schema);
this.ctx.route.data.map(d => d.content)
.subscribe((content: ContentDto) => {

17
src/Squidex/app/features/content/pages/contents/contents-page.component.ts

@ -72,10 +72,6 @@ export class ContentsPageComponent implements OnDestroy, OnInit {
}
public ngOnInit() {
const routeData = allData(this.ctx.route);
this.languages = routeData['appLanguages'];
this.contentCreatedSubscription =
this.ctx.bus.of(ContentCreated)
.subscribe(message => {
@ -89,20 +85,27 @@ export class ContentsPageComponent implements OnDestroy, OnInit {
this.contentItems = this.contentItems.replaceBy('id', message.content, (o, n) => o.update(n.data, n.lastModifiedBy, n.version, n.lastModified));
});
const routeData = allData(this.ctx.route);
this.languages = routeData.appLanguages;
this.ctx.route.data.map(p => p.isReadOnly)
.subscribe(isReadOnly => {
this.isReadOnly = isReadOnly;
});
this.ctx.route.params.map(p => p.language)
.subscribe(language => {
this.languageSelected = this.languages.find(l => l.iso2Code === language) || this.languages.find(l => l.isMaster) || this.languages[0];
});
this.ctx.route.data.map(d => d.schemaOverride || d.schema)
this.ctx.route.data.map(d => d.schema)
.subscribe(schema => {
this.schema = schema;
this.resetContents();
this.load();
});
this.isReadOnly = routeData['isReadOnly'];
}
public dropData(content: ContentDto) {

19
src/Squidex/app/shared/components/app-context.ts

@ -5,14 +5,15 @@
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { Injectable, OnDestroy } from '@angular/core';
import { Observable, Subscription } from 'rxjs';
import { ActivatedRoute } from '@angular/router';
import { MessageBus } from 'framework';
import {
AppDto,
AppsStoreService,
AuthService,
DialogService,
ErrorDto,
@ -21,7 +22,8 @@ import {
} from './../declarations-base';
@Injectable()
export class AppContext {
export class AppContext implements OnDestroy {
private readonly appSubscription: Subscription;
private appField: AppDto;
public get app(): AppDto {
@ -29,7 +31,7 @@ export class AppContext {
}
public get appName(): string {
return this.appField.name;
return this.appField ? this.appField.name : '';
}
public get userToken(): string {
@ -47,15 +49,20 @@ export class AppContext {
constructor(
public readonly dialogs: DialogService,
public readonly authService: AuthService,
public readonly appsStore: AppsStoreService,
public readonly route: ActivatedRoute,
public readonly bus: MessageBus
) {
Observable.merge(...this.route.pathFromRoot.map(r => r.data)).map(d => d.app).filter(a => !!a)
.subscribe((app: AppDto) => {
this.appSubscription =
this.appsStore.selectedApp.subscribe(app => {
this.appField = app;
});
}
public ngOnDestroy() {
this.appSubscription.unsubscribe();
}
public confirmUnsavedChanges(): Observable<boolean> {
return this.dialogs.confirmUnsavedChanges();
}

6
src/Squidex/app/shared/components/app-form.component.ts

@ -12,7 +12,7 @@ import { ApiUrlConfig, ValidatorsEx } from 'framework';
import {
AppDto,
AppsService,
AppsStoreService,
CreateAppDto
} from './../declarations-base';
@ -48,7 +48,7 @@ export class AppFormComponent {
constructor(
public readonly apiUrl: ApiUrlConfig,
private readonly appsService: AppsService,
private readonly appsStore: AppsStoreService,
private readonly formBuilder: FormBuilder
) {
}
@ -66,7 +66,7 @@ export class AppFormComponent {
const request = new CreateAppDto(this.createForm.controls['name'].value);
this.appsService.postApp(request)
this.appsStore.createApp(request)
.subscribe(dto => {
this.resetCreateForm();
this.emitCreated(dto);

4
src/Squidex/app/shared/declarations-base.ts

@ -5,20 +5,22 @@
* Copyright (c) Sebastian Stehle. All rights reserved
*/
export * from './guards/app-must-exist.guard';
export * from './guards/must-be-authenticated.guard';
export * from './guards/must-be-not-authenticated.guard';
export * from './guards/resolve-app-languages.guard';
export * from './guards/resolve-app.guard';
export * from './guards/resolve-content.guard';
export * from './guards/resolve-published-schema.guard';
export * from './guards/resolve-schema.guard';
export * from './guards/resolve-user.guard';
export * from './guards/unset-app.guard';
export * from './interceptors/auth.interceptor';
export * from './services/app-contributors.service';
export * from './services/app-clients.service';
export * from './services/app-languages.service';
export * from './services/apps-store.service';
export * from './services/apps.service';
export * from './services/assets.service';
export * from './services/auth.service';

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

@ -0,0 +1,73 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { IMock, Mock } from 'typemoq';
import { Observable } from 'rxjs';
import { AppsStoreService } from 'shared';
import { AppMustExistGuard } from './app-must-exist.guard';
import { RouterMockup } from './router-mockup';
describe('AppMustExistGuard', () => {
let appsStore: IMock<AppsStoreService>;
beforeEach(() => {
appsStore = Mock.ofType(AppsStoreService);
});
it('should navigate to 404 page if app is not found', (done) => {
appsStore.setup(x => x.selectApp('my-app'))
.returns(() => Observable.of(false));
const router = new RouterMockup();
const route = <any> { params: { appName: 'my-app' } };
const guard = new AppMustExistGuard(appsStore.object, <any>router);
guard.canActivate(route, <any>{})
.subscribe(result => {
expect(result).toBeFalsy();
expect(router.lastNavigation).toEqual(['/404']);
done();
});
});
it('should navigate to 404 page if app loading fails', (done) => {
appsStore.setup(x => x.selectApp('my-app'))
.returns(() => Observable.throw('error'));
const router = new RouterMockup();
const route = <any> { params: { appName: 'my-app' } };
const guard = new AppMustExistGuard(appsStore.object, <any>router);
guard.canActivate(route, <any>{})
.subscribe(result => {
expect(result).toBeFalsy();
expect(router.lastNavigation).toEqual(['/404']);
done();
});
});
it('should return true if app is found', (done) => {
appsStore.setup(x => x.selectApp('my-app'))
.returns(() => Observable.of(true));
const router = new RouterMockup();
const route = <any> { params: { appName: 'my-app' } };
const guard = new AppMustExistGuard(appsStore.object, <any>router);
guard.canActivate(route, <any>{})
.subscribe(result => {
expect(result).toBeTruthy();
expect(router.lastNavigation).toBeUndefined();
done();
});
});
});

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

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

45
src/Squidex/app/shared/guards/resolve-app.guard.ts

@ -1,45 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { allParams } from 'framework';
import { AppDto, AppsService } from './../services/apps.service';
@Injectable()
export class ResolveAppGuard implements Resolve<AppDto> {
constructor(
private readonly appsService: AppsService,
private readonly router: Router
) {
}
public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<AppDto | null> {
const params = allParams(route);
const appName = params['appName'];
if (!appName) {
throw 'Route must contain app name.';
}
return this.appsService.getApps().first().map(x => x.find(a => a.name === appName))
.do(dto => {
if (!dto) {
this.router.navigate(['/404']);
}
})
.catch(error => {
this.router.navigate(['/404']);
return Observable.of(null);
});
}
}

25
src/Squidex/app/shared/guards/unset-app.guard.ts

@ -0,0 +1,25 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router';
import { AppsStoreService } from './../services/apps-store.service';
@Injectable()
export class UnsetAppGuard implements CanActivate {
constructor(
private readonly appsStore: AppsStoreService
) {
}
public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
this.appsStore.selectApp(null);
return true;
}
}

8
src/Squidex/app/shared/module.ts

@ -16,6 +16,8 @@ import {
AppClientsService,
AppContributorsService,
AppLanguagesService,
AppMustExistGuard,
AppsStoreService,
AppsService,
AssetComponent,
AssetPreviewUrlPipe,
@ -36,7 +38,6 @@ import {
MustBeAuthenticatedGuard,
MustBeNotAuthenticatedGuard,
PlansService,
ResolveAppGuard,
ResolveAppLanguagesGuard,
ResolveContentGuard,
ResolvePublishedSchemaGuard,
@ -45,6 +46,7 @@ import {
ResolveUserGuard,
RulesService,
UIService,
UnsetAppGuard,
UsagesService,
UserDtoPicture,
UserEmailPipe,
@ -109,7 +111,9 @@ export class SqxSharedModule {
AppClientsService,
AppContributorsService,
AppLanguagesService,
AppMustExistGuard,
AppsService,
AppsStoreService,
AssetsService,
AuthService,
ContentsService,
@ -121,7 +125,6 @@ export class SqxSharedModule {
MustBeAuthenticatedGuard,
MustBeNotAuthenticatedGuard,
PlansService,
ResolveAppGuard,
ResolveAppLanguagesGuard,
ResolveContentGuard,
ResolvePublishedSchemaGuard,
@ -130,6 +133,7 @@ export class SqxSharedModule {
RulesService,
SchemasService,
UIService,
UnsetAppGuard,
UsagesService,
UserManagementService,
UsersProviderService,

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

@ -0,0 +1,107 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Observable } from 'rxjs';
import { It, IMock, Mock, Times } from 'typemoq';
import {
AppDto,
AppsService,
AppsStoreService,
CreateAppDto,
DateTime
} from './../';
describe('AppsStoreService', () => {
const now = DateTime.now();
const oldApps = [
new AppDto('id', 'old-name', 'Owner', now, now, 'Free', 'Plan'),
new AppDto('id', 'old-name', 'Owner', now, now, 'Free', 'Plan')
];
const newApp = new AppDto('id', 'new-name', 'Owner', now, now, 'Free', 'Plan');
let appsService: IMock<AppsService>;
beforeEach(() => {
appsService = Mock.ofType(AppsService);
});
it('should load automatically', () => {
appsService.setup(x => x.getApps())
.returns(() => Observable.of(oldApps))
.verifiable(Times.once());
const store = new AppsStoreService(appsService.object);
let result1: AppDto[] | null = null;
let result2: AppDto[] | null = null;
store.apps.subscribe(x => {
result1 = x;
}).unsubscribe();
store.apps.subscribe(x => {
result2 = x;
}).unsubscribe();
expect(result1).toEqual(oldApps);
expect(result2).toEqual(oldApps);
appsService.verifyAll();
});
it('should add app to cache when created', () => {
appsService.setup(x => x.getApps())
.returns(() => Observable.of(oldApps))
.verifiable(Times.once());
appsService.setup(x => x.postApp(It.isAny()))
.returns(() => Observable.of(newApp))
.verifiable(Times.once());
const store = new AppsStoreService(appsService.object);
let result1: AppDto[] | null = null;
let result2: AppDto[] | null = null;
store.apps.subscribe(x => {
result1 = x;
}).unsubscribe();
store.createApp(new CreateAppDto('new-name'), now).subscribe();
store.apps.subscribe(x => {
result2 = x;
}).unsubscribe();
expect(result1).toEqual(oldApps);
expect(JSON.stringify(result2)).toEqual(JSON.stringify(oldApps.concat([newApp])));
appsService.verifyAll();
});
it('should select app', (done) => {
appsService.setup(x => x.getApps())
.returns(() => Observable.of(oldApps))
.verifiable(Times.once());
const store = new AppsStoreService(appsService.object);
store.selectApp('old-name').subscribe(isSelected => {
expect(isSelected).toBeTruthy();
appsService.verifyAll();
done();
}, err => {
expect(err).toBeNull();
done();
});
});
});

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

@ -0,0 +1,70 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable, Observer, ReplaySubject } from 'rxjs';
import { DateTime } from 'framework';
import {
AppDto,
AppsService,
CreateAppDto
} from './apps.service';
@Injectable()
export class AppsStoreService {
private readonly apps$ = new ReplaySubject<AppDto[]>(1);
private readonly app$ = new BehaviorSubject<AppDto | null>(null);
public get apps(): Observable<AppDto[]> {
return this.apps$;
}
public get selectedApp(): Observable<AppDto | null> {
return this.app$;
}
constructor(
private readonly appsService: AppsService
) {
if (!appsService) {
return;
}
this.appsService.getApps()
.subscribe(apps => {
this.apps$.next(apps);
}, error => {
this.apps$.next([]);
});
}
public selectApp(name: string | null): Observable<boolean> {
return Observable.create((observer: Observer<boolean>) => {
this.apps$.subscribe(apps => {
const app = apps.find(x => x.name === name) || null;
this.app$.next(app);
observer.next(app !== null);
observer.complete();
}, error => {
observer.error(error);
});
});
}
public createApp(dto: CreateAppDto, now?: DateTime): Observable<AppDto> {
return this.appsService.postApp(dto)
.do(app => {
this.apps$.first().subscribe(apps => {
this.apps$.next(apps.concat([app]));
});
});
}
}

21
src/Squidex/app/shared/services/apps.service.spec.ts

@ -57,20 +57,24 @@ describe('AppsService', () => {
name: 'name1',
permission: 'Owner',
created: '2016-01-01',
lastModified: '2016-02-02'
lastModified: '2016-02-02',
planName: 'Free',
planUpgrade: 'Basic'
},
{
id: '456',
name: 'name2',
permission: 'Owner',
created: '2017-01-01',
lastModified: '2017-02-02'
lastModified: '2017-02-02',
planName: 'Basic',
planUpgrade: 'Enterprise'
}
]);
expect(apps).toEqual([
new AppDto('123', 'name1', 'Owner', DateTime.parseISO('2016-01-01'), DateTime.parseISO('2016-02-02')),
new AppDto('456', 'name2', 'Owner', DateTime.parseISO('2017-01-01'), DateTime.parseISO('2017-02-02'))
new AppDto('123', 'name1', 'Owner', DateTime.parseISO('2016-01-01'), DateTime.parseISO('2016-02-02'), 'Free', 'Basic'),
new AppDto('456', 'name2', 'Owner', DateTime.parseISO('2017-01-01'), DateTime.parseISO('2017-02-02'), 'Basic', 'Enterprise')
]);
}));
@ -90,8 +94,13 @@ describe('AppsService', () => {
expect(req.request.method).toEqual('POST');
expect(req.request.headers.get('If-Match')).toBeNull();
req.flush({ id: '123' });
req.flush({
id: '123',
permission: 'Reader',
planName: 'Basic',
planUpgrade: 'Enterprise'
});
expect(app).toEqual(new AppDto('123', dto.name, 'Owner', now, now));
expect(app).toEqual(new AppDto('123', dto.name, 'Reader', now, now, 'Basic', 'Enterprise'));
}));
});

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

@ -7,7 +7,7 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable, ReplaySubject } from 'rxjs';
import { Observable } from 'rxjs';
import 'framework/angular/http-extensions';
@ -24,7 +24,9 @@ export class AppDto {
public readonly name: string,
public readonly permission: string,
public readonly created: DateTime,
public readonly lastModified: DateTime
public readonly lastModified: DateTime,
public readonly planName: string,
public readonly planUpgrade: string
) {
}
}
@ -38,8 +40,6 @@ export class CreateAppDto {
@Injectable()
export class AppsService {
private apps$: ReplaySubject<AppDto[]> | null = null;
constructor(
private readonly http: HttpClient,
private readonly apiUrl: ApiUrlConfig,
@ -48,38 +48,26 @@ export class AppsService {
}
public getApps(): Observable<AppDto[]> {
if (this.apps$ === null) {
const url = this.apiUrl.buildUrl('/api/apps');
const loadedApps =
HTTP.getVersioned<any>(this.http, url)
.map(response => {
const body = response.payload.body;
const items: any[] = body;
return items.map(item => {
return new AppDto(
item.id,
item.name,
item.permission,
DateTime.parseISO(item.created),
DateTime.parseISO(item.lastModified));
});
})
.pretifyError('Failed to load apps. Please reload.');
this.apps$ = new ReplaySubject<AppDto[]>(1);
const url = this.apiUrl.buildUrl('/api/apps');
loadedApps
.subscribe(apps => {
this.apps$.next(apps);
}, error => {
this.apps$.error(loadedApps);
});
}
return HTTP.getVersioned<any>(this.http, url)
.map(response => {
const body = response.payload.body;
return this.apps$;
const items: any[] = body;
return items.map(item => {
return new AppDto(
item.id,
item.name,
item.permission,
DateTime.parseISO(item.created),
DateTime.parseISO(item.lastModified),
item.planName,
item.planUpgrade);
});
})
.pretifyError('Failed to load apps. Please reload.');
}
public postApp(dto: CreateAppDto, now?: DateTime): Observable<AppDto> {
@ -91,16 +79,10 @@ export class AppsService {
now = now || DateTime.now();
return new AppDto(body.id, dto.name, 'Owner', now, now);
return new AppDto(body.id, dto.name, body.permission, now, now, body.planName, body.planUpgrade);
})
.do(app => {
.do(() => {
this.analytics.trackEvent('App', 'Created', dto.name);
if (this.apps$) {
this.apps$.first().subscribe(apps => {
this.apps$.next(apps.concat([app]));
});
}
})
.pretifyError('Failed to create app. Please reload.');
}

11
src/Squidex/app/shell/pages/internal/apps-menu.component.html

@ -21,6 +21,17 @@
</div>
</div>
</li>
<li class="nav-item" *ngIf="ctx.app && ctx.app.planUpgrade && ctx.app.planName && ctx.app.permission === 'Owner'">
<div class="btn-group app-upgrade">
<button type="button" class="btn btn-primary">
You are on the <strong>{{ctx.app.planName}}</strong> plan.
</button>
<button type="button" class="btn btn-warning" [routerLink]="['/app', ctx.app.name, 'settings', 'plans']">
Upgrade!
</button>
</div>
</li>
</ul>
<div class="modal" *sqxModalView="modalDialog;onRoot:true" @fade>

21
src/Squidex/app/shell/pages/internal/apps-menu.component.scss

@ -4,6 +4,10 @@
$color-apps-border: #65a6ff;
.nav {
& {
@include flex-direction(row);
}
.nav-item {
.nav-link {
& {
@ -16,12 +20,10 @@ $color-apps-border: #65a6ff;
color: $color-dark-foreground;
border: 1px solid $color-theme-blue-lighter;
background: $color-theme-blue;
padding-top: .1rem;
line-height: 1.25rem;
padding-left: 1rem;
padding-right: 2rem;
padding-bottom: .1rem;
min-width: 150px;
margin-top: -.1rem;
}
&:hover {
@ -59,4 +61,17 @@ $color-apps-border: #65a6ff;
&-pill {
@include absolute(.5rem, .625rem, auto, auto);
}
}
.app-upgrade {
& {
margin-left: 2rem;
}
.btn-primary {
@include box-shadow-inner;
background: $color-theme-blue-dark;
border: 0;
pointer-events: none;
}
}

8
src/Squidex/app/shell/pages/internal/apps-menu.component.ts

@ -11,7 +11,7 @@ import { Subscription } from 'rxjs';
import {
AppContext,
AppDto,
AppsService,
AppsStoreService,
fadeAnimation,
ModalView
} from 'shared';
@ -29,7 +29,6 @@ import {
})
export class AppsMenuComponent implements OnDestroy, OnInit {
private appsSubscription: Subscription;
private appSubscription: Subscription;
public modalMenu = new ModalView(false, true);
public modalDialog = new ModalView();
@ -37,18 +36,17 @@ export class AppsMenuComponent implements OnDestroy, OnInit {
public apps: AppDto[] = [];
constructor(public readonly ctx: AppContext,
private readonly appsService: AppsService
private readonly appsStore: AppsStoreService
) {
}
public ngOnDestroy() {
this.appsSubscription.unsubscribe();
this.appSubscription.unsubscribe();
}
public ngOnInit() {
this.appsSubscription =
this.appsService.getApps().subscribe(apps => {
this.appsStore.apps.subscribe(apps => {
this.apps = apps;
});
}

7
src/Squidex/app/theme/_mixins.scss

@ -80,6 +80,13 @@
display: inline-flex;
}
@mixin flex-direction($direction) {
-webkit-flex-direction: $direction;
-moz-flex-direction: $direction;
-ms-flex-direction: $direction;
flex-direction: $direction;
}
@mixin flex-flow($values: (row nowrap)) {
-webkit-flex-flow: $values;
-moz-flex-flow: $values;

Loading…
Cancel
Save