Browse Source

Cleanup

pull/164/head
Sebastian Stehle 9 years ago
parent
commit
2e74821408
  1. 6
      src/Squidex.Domain.Apps.Read/Apps/Services/IAppPlansProvider.cs
  2. 40
      src/Squidex.Domain.Apps.Read/Apps/Services/Implementations/ConfigAppPlansProvider.cs
  3. 26
      src/Squidex/Controllers/Api/Apps/AppsController.cs
  4. 45
      src/Squidex/Controllers/Api/Apps/Models/AppCreatedDto.cs
  5. 10
      src/Squidex/Controllers/Api/Apps/Models/AppDto.cs
  6. 8
      src/Squidex/app/app.routes.ts
  7. 2
      src/Squidex/app/features/administration/pages/event-consumers/event-consumers-page.component.html
  8. 21
      src/Squidex/app/features/administration/pages/event-consumers/event-consumers-page.component.ts
  9. 4
      src/Squidex/app/features/administration/pages/users/user-page.component.html
  10. 39
      src/Squidex/app/features/administration/pages/users/user-page.component.ts
  11. 4
      src/Squidex/app/features/administration/pages/users/users-page.component.html
  12. 35
      src/Squidex/app/features/administration/pages/users/users-page.component.ts
  13. 6
      src/Squidex/app/features/api/api-area.component.html
  14. 18
      src/Squidex/app/features/api/api-area.component.ts
  15. 4
      src/Squidex/app/features/api/pages/graphql/graphql-page.component.html
  16. 17
      src/Squidex/app/features/api/pages/graphql/graphql-page.component.ts
  17. 4
      src/Squidex/app/features/apps/pages/apps-page.component.html
  18. 19
      src/Squidex/app/features/apps/pages/apps-page.component.ts
  19. 4
      src/Squidex/app/features/assets/pages/assets-page.component.html
  20. 35
      src/Squidex/app/features/assets/pages/assets-page.component.ts
  21. 2
      src/Squidex/app/features/content/pages/content/content-history.component.html
  22. 33
      src/Squidex/app/features/content/pages/content/content-history.component.ts
  23. 4
      src/Squidex/app/features/content/pages/content/content-page.component.html
  24. 74
      src/Squidex/app/features/content/pages/content/content-page.component.ts
  25. 4
      src/Squidex/app/features/content/pages/contents/contents-page.component.html
  26. 77
      src/Squidex/app/features/content/pages/contents/contents-page.component.ts
  27. 4
      src/Squidex/app/features/content/pages/schemas/schemas-page.component.html
  28. 20
      src/Squidex/app/features/content/pages/schemas/schemas-page.component.ts
  29. 26
      src/Squidex/app/features/content/shared/assets-editor.component.ts
  30. 14
      src/Squidex/app/features/content/shared/content-item.component.ts
  31. 26
      src/Squidex/app/features/content/shared/references-editor.component.ts
  32. 6
      src/Squidex/app/features/dashboard/pages/dashboard-page.component.html
  33. 27
      src/Squidex/app/features/dashboard/pages/dashboard-page.component.ts
  34. 4
      src/Squidex/app/features/rules/pages/events/rule-events-page.component.html
  35. 29
      src/Squidex/app/features/rules/pages/events/rule-events-page.component.ts
  36. 32
      src/Squidex/app/features/rules/pages/rules/rule-wizard.component.ts
  37. 4
      src/Squidex/app/features/rules/pages/rules/rules-page.component.html
  38. 43
      src/Squidex/app/features/rules/pages/rules/rules-page.component.ts
  39. 20
      src/Squidex/app/features/schemas/pages/schema/schema-edit-form.component.ts
  40. 8
      src/Squidex/app/features/schemas/pages/schema/schema-page.component.html
  41. 125
      src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts
  42. 20
      src/Squidex/app/features/schemas/pages/schema/schema-scripts-form.component.ts
  43. 6
      src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.html
  44. 32
      src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.ts
  45. 18
      src/Squidex/app/features/settings/pages/clients/client.component.ts
  46. 6
      src/Squidex/app/features/settings/pages/clients/clients-page.component.html
  47. 44
      src/Squidex/app/features/settings/pages/clients/clients-page.component.ts
  48. 8
      src/Squidex/app/features/settings/pages/contributors/contributors-page.component.html
  49. 45
      src/Squidex/app/features/settings/pages/contributors/contributors-page.component.ts
  50. 4
      src/Squidex/app/features/settings/pages/languages/languages-page.component.html
  51. 43
      src/Squidex/app/features/settings/pages/languages/languages-page.component.ts
  52. 4
      src/Squidex/app/features/settings/pages/plans/plans-page.component.html
  53. 33
      src/Squidex/app/features/settings/pages/plans/plans-page.component.ts
  54. 4
      src/Squidex/app/features/settings/settings-area.component.html
  55. 17
      src/Squidex/app/features/settings/settings-area.component.ts
  56. 10
      src/Squidex/app/framework/angular/panel-container.directive.ts
  57. 12
      src/Squidex/app/framework/angular/panel.component.ts
  58. 2
      src/Squidex/app/framework/utils/math-helper.ts
  59. 74
      src/Squidex/app/shared/components/app-context.ts
  60. 6
      src/Squidex/app/shared/components/app-form.component.ts
  61. 42
      src/Squidex/app/shared/components/app.component-base.ts
  62. 35
      src/Squidex/app/shared/components/asset.component.ts
  63. 31
      src/Squidex/app/shared/components/component-base.ts
  64. 2
      src/Squidex/app/shared/components/help.component.html
  65. 2
      src/Squidex/app/shared/components/history.component.html
  66. 31
      src/Squidex/app/shared/components/history.component.ts
  67. 3
      src/Squidex/app/shared/declarations-base.ts
  68. 3
      src/Squidex/app/shared/declarations.ts
  69. 73
      src/Squidex/app/shared/guards/app-must-exist.guard.spec.ts
  70. 40
      src/Squidex/app/shared/guards/app-must-exist.guard.ts
  71. 45
      src/Squidex/app/shared/guards/resolve-app.guard.ts
  72. 6
      src/Squidex/app/shared/module.ts
  73. 104
      src/Squidex/app/shared/services/apps-store.service.spec.ts
  74. 70
      src/Squidex/app/shared/services/apps-store.service.ts
  75. 56
      src/Squidex/app/shared/services/apps.service.ts
  76. 2
      src/Squidex/app/shared/services/users-provider.service.ts
  77. 12
      src/Squidex/app/shell/pages/app/left-menu.component.html
  78. 32
      src/Squidex/app/shell/pages/app/left-menu.component.ts
  79. 2
      src/Squidex/app/shell/pages/internal/apps-menu.component.html
  80. 2
      src/Squidex/app/shell/pages/internal/apps-menu.component.scss
  81. 19
      src/Squidex/app/shell/pages/internal/apps-menu.component.ts
  82. 56
      src/Squidex/package.json
  83. 130
      tests/Squidex.Domain.Apps.Read.Tests/Apps/ConfigAppLimitsProviderTests.cs

6
src/Squidex.Domain.Apps.Read/Apps/Services/IAppPlansProvider.cs

@ -16,7 +16,11 @@ namespace Squidex.Domain.Apps.Read.Apps.Services
bool IsConfiguredPlan(string planId);
IAppLimitsPlan GetPlanForApp(IAppEntity entity);
IAppLimitsPlan GetPlanUpgradeForApp(IAppEntity app);
IAppLimitsPlan GetPlanUpgrade(string planId);
IAppLimitsPlan GetPlanForApp(IAppEntity app);
IAppLimitsPlan GetPlan(string planId);
}

40
src/Squidex.Domain.Apps.Read/Apps/Services/Implementations/ConfigAppPlansProvider.cs

@ -24,18 +24,25 @@ namespace Squidex.Domain.Apps.Read.Apps.Services.Implementations
MaxContributors = -1
};
private readonly Dictionary<string, ConfigAppLimitsPlan> config;
private readonly Dictionary<string, ConfigAppLimitsPlan> plansById;
private readonly List<ConfigAppLimitsPlan> plansList;
public ConfigAppPlansProvider(IEnumerable<ConfigAppLimitsPlan> config)
{
Guard.NotNull(config, nameof(config));
this.config = config.Select(c => c.Clone()).OrderBy(x => x.MaxApiCalls).ToDictionary(c => c.Id, StringComparer.OrdinalIgnoreCase);
plansList = config.Select(c => c.Clone()).OrderBy(x => x.MaxApiCalls).ToList();
plansById = plansList.ToDictionary(c => c.Id, StringComparer.OrdinalIgnoreCase);
}
public IEnumerable<IAppLimitsPlan> GetAvailablePlans()
{
return config.Values;
return plansList;
}
public bool IsConfiguredPlan(string planId)
{
return planId != null && plansById.ContainsKey(planId);
}
public IAppLimitsPlan GetPlanForApp(IAppEntity app)
@ -47,12 +54,33 @@ namespace Squidex.Domain.Apps.Read.Apps.Services.Implementations
public IAppLimitsPlan GetPlan(string planId)
{
return config.GetOrDefault(planId ?? string.Empty) ?? config.Values.FirstOrDefault() ?? Infinite;
return GetPlanCore(planId);
}
public bool IsConfiguredPlan(string planId)
public IAppLimitsPlan GetPlanUpgradeForApp(IAppEntity app)
{
Guard.NotNull(app, nameof(app));
return GetPlanUpgrade(app.PlanId);
}
public IAppLimitsPlan GetPlanUpgrade(string planId)
{
var plan = GetPlanCore(planId);
var nextPlanIndex = plansList.IndexOf(plan);
if (nextPlanIndex >= 0 && nextPlanIndex < plansList.Count - 1)
{
return plansList[nextPlanIndex + 1];
}
return null;
}
private ConfigAppLimitsPlan GetPlanCore(string planId)
{
return planId != null && config.ContainsKey(planId);
return plansById.GetOrDefault(planId ?? string.Empty) ?? plansById.Values.FirstOrDefault() ?? Infinite;
}
}
}

26
src/Squidex/Controllers/Api/Apps/AppsController.cs

@ -12,7 +12,9 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using NSwag.Annotations;
using Squidex.Controllers.Api.Apps.Models;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Read.Apps.Repositories;
using Squidex.Domain.Apps.Read.Apps.Services;
using Squidex.Domain.Apps.Write.Apps.Commands;
using Squidex.Infrastructure.CQRS.Commands;
using Squidex.Infrastructure.Reflection;
@ -30,11 +32,15 @@ namespace Squidex.Controllers.Api.Apps
public sealed class AppsController : ControllerBase
{
private readonly IAppRepository appRepository;
private readonly IAppPlansProvider appPlansProvider;
public AppsController(ICommandBus commandBus, IAppRepository appRepository)
public AppsController(ICommandBus commandBus,
IAppRepository appRepository,
IAppPlansProvider appPlansProvider)
: base(commandBus)
{
this.appRepository = appRepository;
this.appPlansProvider = appPlansProvider;
}
/// <summary>
@ -57,11 +63,14 @@ namespace Squidex.Controllers.Api.Apps
var apps = await appRepository.QueryAllAsync(subject);
var response = apps.Select(s =>
var response = apps.Select(a =>
{
var dto = SimpleMapper.Map(s, new AppDto());
var dto = SimpleMapper.Map(a, new AppDto());
dto.Permission = s.Contributors[subject];
dto.Permission = a.Contributors[subject];
dto.PlanName = appPlansProvider.GetPlanForApp(a)?.Name;
dto.PlanUpgrade = appPlansProvider.GetPlanUpgradeForApp(a)?.Name;
return dto;
}).ToList();
@ -84,7 +93,7 @@ namespace Squidex.Controllers.Api.Apps
/// </remarks>
[HttpPost]
[Route("apps/")]
[ProducesResponseType(typeof(EntityCreatedDto), 201)]
[ProducesResponseType(typeof(AppCreatedDto), 201)]
[ProducesResponseType(typeof(ErrorDto), 400)]
[ProducesResponseType(typeof(ErrorDto), 409)]
[ApiCosts(1)]
@ -95,7 +104,12 @@ namespace Squidex.Controllers.Api.Apps
var context = await CommandBus.PublishAsync(command);
var result = context.Result<EntityCreatedResult<Guid>>();
var response = new EntityCreatedDto { Id = result.IdOrValue.ToString(), Version = result.Version };
var response = new AppCreatedDto { Id = result.IdOrValue.ToString(), Version = result.Version };
response.Permission = AppContributorPermission.Owner;
response.PlanName = appPlansProvider.GetPlan(null)?.Name;
response.PlanUpgrade = appPlansProvider.GetPlanUpgrade(null)?.Name;
return CreatedAtAction(nameof(GetApps), response);
}

45
src/Squidex/Controllers/Api/Apps/Models/AppCreatedDto.cs

@ -0,0 +1,45 @@
// ==========================================================================
// AppCreatedDto.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.ComponentModel.DataAnnotations;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Squidex.Domain.Apps.Core.Apps;
namespace Squidex.Controllers.Api.Apps.Models
{
public sealed class AppCreatedDto
{
/// <summary>
/// Id of the created entity.
/// </summary>
[Required]
public string Id { get; set; }
/// <summary>
/// The new version of the entity.
/// </summary>
public long Version { get; set; }
/// <summary>
/// The permission level of the user.
/// </summary>
[JsonConverter(typeof(StringEnumConverter))]
public AppContributorPermission Permission { get; set; }
/// <summary>
/// Gets the current plan name.
/// </summary>
public string PlanName { get; set; }
/// <summary>
/// Gets the next plan name.
/// </summary>
public string PlanUpgrade { get; set; }
}
}

10
src/Squidex/Controllers/Api/Apps/Models/AppDto.cs

@ -49,5 +49,15 @@ namespace Squidex.Controllers.Api.Apps.Models
/// </summary>
[JsonConverter(typeof(StringEnumConverter))]
public AppContributorPermission Permission { get; set; }
/// <summary>
/// Gets the current plan name.
/// </summary>
public string PlanName { get; set; }
/// <summary>
/// Gets the next plan name.
/// </summary>
public string PlanUpgrade { get; set; }
}
}

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

@ -18,9 +18,9 @@ import {
} from './shell';
import {
AppMustExistGuard,
MustBeAuthenticatedGuard,
MustBeNotAuthenticatedGuard
MustBeNotAuthenticatedGuard,
ResolveAppGuard
} from './shared';
export const routes: Routes = [
@ -45,7 +45,9 @@ export const routes: Routes = [
{
path: ':appName',
component: AppAreaComponent,
canActivate: [AppMustExistGuard],
resolve: {
app: ResolveAppGuard
},
children: [
{
path: '',

2
src/Squidex/app/features/administration/pages/event-consumers/event-consumers-page.component.html

@ -1,6 +1,6 @@
<sqx-title message="Event Consumers"></sqx-title>
<sqx-panel theme="light" panelWidth="50rem">
<sqx-panel theme="light" desiredWidth="50rem">
<div class="panel-header">
<div class="panel-title-row">
<div class="float-right">

21
src/Squidex/app/features/administration/pages/event-consumers/event-consumers-page.component.ts

@ -9,8 +9,7 @@ import { Component, OnDestroy, OnInit } from '@angular/core';
import { Observable, Subscription } from 'rxjs';
import {
ComponentBase,
DialogService,
AppContext,
EventConsumerDto,
EventConsumersService,
fadeAnimation,
@ -22,21 +21,23 @@ import {
selector: 'sqx-event-consumers-page',
styleUrls: ['./event-consumers-page.component.scss'],
templateUrl: './event-consumers-page.component.html',
providers: [
AppContext
],
animations: [
fadeAnimation
]
})
export class EventConsumersPageComponent extends ComponentBase implements OnDestroy, OnInit {
export class EventConsumersPageComponent implements OnDestroy, OnInit {
private subscription: Subscription;
public eventConsumerErrorDialog = new ModalView();
public eventConsumerError = '';
public eventConsumers = ImmutableArray.empty<EventConsumerDto>();
constructor(dialogs: DialogService,
constructor(public readonly ctx: AppContext,
private readonly eventConsumersService: EventConsumersService
) {
super(dialogs);
}
public ngOnDestroy() {
@ -58,11 +59,11 @@ export class EventConsumersPageComponent extends ComponentBase implements OnDest
this.eventConsumers = ImmutableArray.of(dtos);
if (showInfo) {
this.notifyInfo('Event Consumers reloaded.');
this.ctx.notifyInfo('Event Consumers reloaded.');
}
}, error => {
if (showError) {
this.notifyError(error);
this.ctx.notifyError(error);
}
});
}
@ -72,7 +73,7 @@ export class EventConsumersPageComponent extends ComponentBase implements OnDest
.subscribe(() => {
this.eventConsumers = this.eventConsumers.replaceBy('name', consumer.start());
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
});
}
@ -81,7 +82,7 @@ export class EventConsumersPageComponent extends ComponentBase implements OnDest
.subscribe(() => {
this.eventConsumers = this.eventConsumers.replaceBy('name', consumer.stop());
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
});
}
@ -90,7 +91,7 @@ export class EventConsumersPageComponent extends ComponentBase implements OnDest
.subscribe(() => {
this.eventConsumers = this.eventConsumers.replaceBy('name', consumer.reset());
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
});
}

4
src/Squidex/app/features/administration/pages/users/user-page.component.html

@ -3,7 +3,7 @@
<form [formGroup]="userForm" (ngSubmit)="save()">
<input style="display:none" type="password" name="foilautofill"/>
<sqx-panel panelWidth="26rem">
<sqx-panel desiredWidth="26rem">
<div class="panel-header">
<div class="panel-title-row">
<div class="float-right">
@ -52,7 +52,7 @@
<input type="text" class="form-control" id="displayName" maxlength="100" formControlName="displayName" autocomplete="false" />
</div>
<div class="form-group form-group-password" [class.hidden]="isCurrentUser">
<div class="form-group form-group-password" [class.hidden]="user.id === ctx.userId">
<div class="form-group">
<label for="password">Password</label>

39
src/Squidex/app/features/administration/pages/users/user-page.component.ts

@ -7,13 +7,10 @@
import { Component, OnInit } from '@angular/core';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { Router } from '@angular/router';
import {
AuthService,
ComponentBase,
DialogService,
MessageBus,
AppContext,
UserDto,
UserManagementService,
ValidatorsEx
@ -24,12 +21,14 @@ import { UserCreated, UserUpdated } from './../messages';
@Component({
selector: 'sqx-user-page',
styleUrls: ['./user-page.component.scss'],
templateUrl: './user-page.component.html'
templateUrl: './user-page.component.html',
providers: [
AppContext
]
})
export class UserPageComponent extends ComponentBase implements OnInit {
export class UserPageComponent implements OnInit {
private user: UserDto;
public currentUserId: string;
public userFormSubmitted = false;
public userForm: FormGroup;
public userFormError? = '';
@ -37,21 +36,15 @@ export class UserPageComponent extends ComponentBase implements OnInit {
public isCurrentUser = false;
public isNewMode = false;
constructor(dialogs: DialogService,
private readonly authService: AuthService,
constructor(public readonly ctx: AppContext,
private readonly formBuilder: FormBuilder,
private readonly messageBus: MessageBus,
private readonly route: ActivatedRoute,
private readonly router: Router,
private readonly userManagementService: UserManagementService
) {
super(dialogs);
}
public ngOnInit() {
this.currentUserId = this.authService.user!.id;
this.route.data.map(p => p['user'])
this.ctx.route.data.map(d => d.user)
.subscribe((user: UserDto) => {
this.user = user;
@ -78,8 +71,9 @@ export class UserPageComponent extends ComponentBase implements OnInit {
created.pictureUrl!,
false);
this.ctx.notifyInfo('User created successfully.');
this.emitUserCreated(this.user);
this.notifyInfo('User created successfully.');
this.back();
}, error => {
this.resetUserForm(error.displayMessage);
@ -92,8 +86,9 @@ export class UserPageComponent extends ComponentBase implements OnInit {
requestDto.email,
requestDto.displayMessage);
this.ctx.notifyInfo('User saved successfully.');
this.emitUserUpdated(this.user);
this.notifyInfo('User saved successfully.');
this.resetUserForm();
}, error => {
this.resetUserForm(error.displayMessage);
@ -103,15 +98,15 @@ export class UserPageComponent extends ComponentBase implements OnInit {
}
private back() {
this.router.navigate(['../'], { relativeTo: this.route, replaceUrl: true });
this.router.navigate(['../'], { relativeTo: this.ctx.route, replaceUrl: true });
}
private emitUserCreated(user: UserDto) {
this.messageBus.emit(new UserCreated(user));
this.ctx.bus.emit(new UserCreated(user));
}
private emitUserUpdated(user: UserDto) {
this.messageBus.emit(new UserUpdated(user));
this.ctx.bus.emit(new UserUpdated(user));
}
private setupAndPopulateForm() {
@ -141,7 +136,7 @@ export class UserPageComponent extends ComponentBase implements OnInit {
]]
});
this.isCurrentUser = this.user && this.user.id === this.currentUserId;
this.isCurrentUser = this.user && this.user.id === this.ctx.userId;
this.resetUserForm();
}

4
src/Squidex/app/features/administration/pages/users/users-page.component.html

@ -1,6 +1,6 @@
<sqx-title message="User Management"></sqx-title>
<sqx-panel panelWidth="50rem">
<sqx-panel desiredWidth="50rem">
<div class="panel-header">
<div class="panel-title-row">
<div class="float-right">
@ -69,7 +69,7 @@
<span class="user-email table-cell">{{user.email}}</span>
</td>
<td class="text-right">
<span *ngIf="user.id !== currentUserId">
<span *ngIf="user.id !== ctx.userId">
<button class="btn btn-link" (click)="lock(user); $event.stopPropagation();" *ngIf="!user.isLocked" title="Lock User">
<i class="icon icon-unlocked"></i>
</button>

35
src/Squidex/app/features/administration/pages/users/users-page.component.ts

@ -10,11 +10,8 @@ import { FormControl } from '@angular/forms';
import { Subscription } from 'rxjs';
import {
AuthService,
ComponentBase,
DialogService,
AppContext,
ImmutableArray,
MessageBus,
Pager,
UserDto,
UserManagementService
@ -25,25 +22,23 @@ import { UserCreated, UserUpdated } from './../messages';
@Component({
selector: 'sqx-users-page',
styleUrls: ['./users-page.component.scss'],
templateUrl: './users-page.component.html'
templateUrl: './users-page.component.html',
providers: [
AppContext
]
})
export class UsersPageComponent extends ComponentBase implements OnDestroy, OnInit {
export class UsersPageComponent implements OnDestroy, OnInit {
private userCreatedSubscription: Subscription;
private userUpdatedSubscription: Subscription;
public currentUserId: string;
public usersItems = ImmutableArray.empty<UserDto>();
public usersPager = new Pager(0);
public usersFilter = new FormControl();
public usersQuery = '';
constructor(dialogs: DialogService,
private readonly userManagementService: UserManagementService,
private readonly authService: AuthService,
private readonly messageBus: MessageBus
constructor(public readonly ctx: AppContext,
private readonly userManagementService: UserManagementService
) {
super(dialogs);
}
public ngOnDestroy() {
@ -53,20 +48,18 @@ export class UsersPageComponent extends ComponentBase implements OnDestroy, OnIn
public ngOnInit() {
this.userCreatedSubscription =
this.messageBus.of(UserCreated)
this.ctx.bus.of(UserCreated)
.subscribe(message => {
this.usersItems = this.usersItems.pushFront(message.user);
this.usersPager = this.usersPager.incrementCount();
});
this.userUpdatedSubscription =
this.messageBus.of(UserUpdated)
this.ctx.bus.of(UserUpdated)
.subscribe(message => {
this.usersItems = this.usersItems.replaceBy('id', message.user);
});
this.currentUserId = this.authService.user!.id;
this.load();
}
@ -84,10 +77,10 @@ export class UsersPageComponent extends ComponentBase implements OnDestroy, OnIn
this.usersPager = this.usersPager.setCount(dtos.total);
if (showInfo) {
this.notifyInfo('Users reloaded.');
this.ctx.notifyInfo('Users reloaded.');
}
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
});
}
@ -96,7 +89,7 @@ export class UsersPageComponent extends ComponentBase implements OnDestroy, OnIn
.subscribe(() => {
this.usersItems = this.usersItems.replaceBy('id', user.lock());
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
});
}
@ -105,7 +98,7 @@ export class UsersPageComponent extends ComponentBase implements OnDestroy, OnIn
.subscribe(() => {
this.usersItems = this.usersItems.replaceBy('id', user.unlock());
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
});
}

6
src/Squidex/app/features/api/api-area.component.html

@ -1,6 +1,6 @@
<sqx-title message="{app} | Settings" parameter1="app" value1="{{appName() | async}}"></sqx-title>
<sqx-title message="{app} | Settings" parameter1="app" [value1]="ctx.appName"></sqx-title>
<sqx-panel theme="dark" panelWidth="12rem">
<sqx-panel theme="dark" desiredWidth="12rem">
<div class="panel-header">
<div class="panel-title-row">
<h3 class="panel-title">API</h3>
@ -21,7 +21,7 @@
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/api/content/{{appName() | async}}/docs" target="_blank">
<a class="nav-link" href="/api/content/{{ctx.appName}}/docs" target="_blank">
Swagger
</a>
</li>

18
src/Squidex/app/features/api/api-area.component.ts

@ -7,21 +7,19 @@
import { Component } from '@angular/core';
import {
AppComponentBase,
AppsStoreService,
AuthService,
DialogService
} from 'shared';
import { AppContext } from 'shared';
@Component({
selector: 'sqx-api-area',
styleUrls: ['./api-area.component.scss'],
templateUrl: './api-area.component.html'
templateUrl: './api-area.component.html',
providers: [
AppContext
]
})
export class ApiAreaComponent extends AppComponentBase {
constructor(apps: AppsStoreService, dialogs: DialogService, authService: AuthService
export class ApiAreaComponent {
constructor(
public readonly ctx: AppContext
) {
super(dialogs, apps, authService);
}
}

4
src/Squidex/app/features/api/pages/graphql/graphql-page.component.html

@ -1,5 +1,5 @@
<sqx-title message="{app} | API | GraphQL" parameter1="app" value1="{{appName() | async}}"></sqx-title>
<sqx-title message="{app} | API | GraphQL" parameter1="app" [value1]="ctx.appName"></sqx-title>
<sqx-panel panelWidth="60rem" expand="true">
<sqx-panel desiredWidth="*">
<div #graphiQLContainer></div>
</sqx-panel>

17
src/Squidex/app/features/api/pages/graphql/graphql-page.component.ts

@ -16,10 +16,7 @@ const GraphiQL = require('graphiql');
/* tslint:disable:use-view-encapsulation */
import {
AppComponentBase,
AppsStoreService,
AuthService,
DialogService,
AppContext,
GraphQlService,
LocalStoreService
} from 'shared';
@ -28,17 +25,19 @@ import {
selector: 'sqx-graphql-page',
styleUrls: ['./graphql-page.component.scss'],
templateUrl: './graphql-page.component.html',
providers: [
AppContext
],
encapsulation: ViewEncapsulation.None
})
export class GraphQLPageComponent extends AppComponentBase implements OnInit {
export class GraphQLPageComponent implements OnInit {
@ViewChild('graphiQLContainer')
public graphiQLContainer: ElementRef;
constructor(apps: AppsStoreService, dialogs: DialogService, authService: AuthService,
constructor(public readonly ctx: AppContext,
private readonly graphQlService: GraphQlService,
private readonly localStoreService: LocalStoreService
) {
super(dialogs, apps, authService);
}
public ngOnInit() {
@ -57,9 +56,7 @@ export class GraphQLPageComponent extends AppComponentBase implements OnInit {
}
private request(params: any) {
return this.appNameOnce()
.switchMap(app => this.graphQlService.query(app, params).catch(response => Observable.of(response.error)))
.toPromise();
return this.graphQlService.query(this.ctx.appName, params).catch(response => Observable.of(response.error)).toPromise();
}
}

4
src/Squidex/app/features/apps/pages/apps-page.component.html

@ -1,13 +1,13 @@
<sqx-title message="Apps"></sqx-title>
<div class="apps">
<div class="empty text-center" *ngIf="(apps | async)?.length === 0">
<div class="empty text-center" *ngIf="apps?.length === 0">
<h3 class="empty-headline">You are not collaborating to any app yet</h3>
<button class="apps-empty-button btn btn-success" (click)="addAppDialog.show()"><i class="icon-plus"></i> Create New App</button>
</div>
<div class="app card float-left" *ngFor="let app of (apps | async)" title="{{app.name}}">
<div class="app card float-left" *ngFor="let app of apps" title="{{app.name}}">
<div class="card-block app-content">
<h4 class="card-title app-title">{{app.name}}</h4>

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

@ -9,7 +9,8 @@ import { Component, OnDestroy, OnInit } from '@angular/core';
import { Subscription } from 'rxjs';
import {
AppsStoreService,
AppDto,
AppsService,
fadeAnimation,
ModalView,
OnboardingService
@ -24,33 +25,33 @@ import {
]
})
export class AppsPageComponent implements OnDestroy, OnInit {
private onboardingAppsSubscription: Subscription;
private appsSubscription: Subscription;
public addAppDialog = new ModalView();
public apps = this.appsStore.apps;
public apps: AppDto[];
public onboardingModal = new ModalView();
constructor(
private readonly appsStore: AppsStoreService,
private readonly appsService: AppsService,
private readonly onboardingService: OnboardingService
) {
}
public ngOnDestroy() {
this.onboardingAppsSubscription.unsubscribe();
this.appsSubscription.unsubscribe();
}
public ngOnInit() {
this.appsStore.selectApp(null);
this.onboardingAppsSubscription =
this.appsStore.apps
this.appsSubscription =
this.appsService.getApps()
.subscribe(apps => {
if (apps.length === 0 && this.onboardingService.shouldShow('dialog')) {
this.onboardingService.disable('dialog');
this.onboardingModal.show();
}
this.apps = apps;
});
}
}

4
src/Squidex/app/features/assets/pages/assets-page.component.html

@ -1,6 +1,6 @@
<sqx-title message="{app} | Assets" parameter1="app" value1="{{appName() | async}}"></sqx-title>
<sqx-title message="{app} | Assets" parameter1="app" [value1]="ctx.appName"></sqx-title>
<sqx-panel panelWidth="60rem">
<sqx-panel desiredWidth="60rem">
<div class="panel-header">
<div class="panel-title-row">
<div class="float-right">

35
src/Squidex/app/features/assets/pages/assets-page.component.ts

@ -12,24 +12,23 @@ import { FormControl } from '@angular/forms';
import { Subscription } from 'rxjs';
import {
AppComponentBase,
AppsStoreService,
AppContext,
AssetDto,
AssetsService,
AssetUpdated,
AuthService,
DialogService,
ImmutableArray,
MessageBus,
Pager
} from 'shared';
@Component({
selector: 'sqx-assets-page',
styleUrls: ['./assets-page.component.scss'],
templateUrl: './assets-page.component.html'
templateUrl: './assets-page.component.html',
providers: [
AppContext
]
})
export class AssetsPageComponent extends AppComponentBase implements OnDestroy, OnInit {
export class AssetsPageComponent implements OnDestroy, OnInit {
private assetUpdatedSubscription: Subscription;
public newFiles = ImmutableArray.empty<File>();
@ -39,11 +38,9 @@ export class AssetsPageComponent extends AppComponentBase implements OnDestroy,
public assetsFilter = new FormControl();
public assertQuery = '';
constructor(apps: AppsStoreService, dialogs: DialogService, authService: AuthService,
private readonly assetsService: AssetsService,
private readonly messageBus: MessageBus
constructor(public readonly ctx: AppContext,
private readonly assetsService: AssetsService
) {
super(dialogs, apps, authService);
}
public ngOnDestroy() {
@ -52,7 +49,7 @@ export class AssetsPageComponent extends AppComponentBase implements OnDestroy,
public ngOnInit() {
this.assetUpdatedSubscription =
this.messageBus.of(AssetUpdated)
this.ctx.bus.of(AssetUpdated)
.subscribe(event => {
if (event.sender !== this) {
this.assetsItems = this.assetsItems.replaceBy('id', event.assetDto);
@ -70,28 +67,26 @@ export class AssetsPageComponent extends AppComponentBase implements OnDestroy,
}
public load(showInfo = false) {
this.appNameOnce()
.switchMap(app => this.assetsService.getAssets(app, this.assetsPager.pageSize, this.assetsPager.skip, this.assertQuery))
this.assetsService.getAssets(this.ctx.appName, this.assetsPager.pageSize, this.assetsPager.skip, this.assertQuery)
.subscribe(dtos => {
this.assetsItems = ImmutableArray.of(dtos.items);
this.assetsPager = this.assetsPager.setCount(dtos.total);
if (showInfo) {
this.notifyInfo('Assets reloaded.');
this.ctx.notifyInfo('Assets reloaded.');
}
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
});
}
public onAssetDeleting(asset: AssetDto) {
this.appNameOnce()
.switchMap(app => this.assetsService.deleteAsset(app, asset.id, asset.version))
this.assetsService.deleteAsset(this.ctx.appName, asset.id, asset.version)
.subscribe(dto => {
this.assetsItems = this.assetsItems.filter(x => x.id !== asset.id);
this.assetsPager = this.assetsPager.decrementCount();
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
});
}
@ -103,7 +98,7 @@ export class AssetsPageComponent extends AppComponentBase implements OnDestroy,
}
public onAssetUpdated(asset: AssetDto) {
this.messageBus.emit(new AssetUpdated(asset, this));
this.ctx.bus.emit(new AssetUpdated(asset, this));
}
public onAssetFailed(file: File) {

2
src/Squidex/app/features/content/pages/content/content-history.component.html

@ -1,4 +1,4 @@
<sqx-panel panelWidth="16rem">
<sqx-panel desiredWidth="16rem">
<div class="panel-header">
<div class="panel-title-row">
<h3 class="panel-title">Activity</h3>

33
src/Squidex/app/features/content/pages/content/content-history.component.ts

@ -6,19 +6,14 @@
*/
import { Component } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Observable } from 'rxjs';
import {
allParams,
AppComponentBase,
AppsStoreService,
AuthService,
DialogService,
AppContext,
HistoryChannelUpdated,
HistoryEventDto,
HistoryService,
MessageBus,
UsersProviderService
} from 'shared';
@ -29,14 +24,17 @@ const REPLACEMENT_TEMP = '$TEMP$';
@Component({
selector: 'sqx-history',
styleUrls: ['./content-history.component.scss'],
templateUrl: './content-history.component.html'
templateUrl: './content-history.component.html',
providers: [
AppContext
]
})
export class ContentHistoryComponent extends AppComponentBase {
export class ContentHistoryComponent {
public get channel(): string {
let channelPath = this.route.snapshot.data['channel'];
let channelPath = this.ctx.route.snapshot.data['channel'];
if (channelPath) {
const params = allParams(this.route);
const params = allParams(this.ctx.route);
for (let key in params) {
if (params.hasOwnProperty(key)) {
@ -51,18 +49,13 @@ export class ContentHistoryComponent extends AppComponentBase {
}
public events: Observable<HistoryEventDto[]> =
Observable.timer(0, 10000)
.merge(this.messageBus.of(HistoryChannelUpdated).delay(1000))
.switchMap(() => this.appNameOnce())
.switchMap(app => this.historyService.getHistory(app, this.channel).retry(2));
Observable.timer(0, 10000).merge(this.ctx.bus.of(HistoryChannelUpdated).delay(1000))
.switchMap(app => this.historyService.getHistory(this.ctx.appName, this.channel));
constructor(appsStore: AppsStoreService, dialogs: DialogService, authService: AuthService,
constructor(public readonly ctx: AppContext,
private readonly users: UsersProviderService,
private readonly historyService: HistoryService,
private readonly messageBus: MessageBus,
private readonly route: ActivatedRoute
private readonly historyService: HistoryService
) {
super(dialogs, appsStore, authService);
}
private userName(userId: string): Observable<string> {
@ -80,7 +73,7 @@ export class ContentHistoryComponent extends AppComponentBase {
}
public loadVersion(version: number) {
this.messageBus.emit(new ContentVersionSelected(version));
this.ctx.bus.emit(new ContentVersionSelected(version));
}
public format(message: string): Observable<string> {

4
src/Squidex/app/features/content/pages/content/content-page.component.html

@ -1,7 +1,7 @@
<sqx-title message="{app} | {schema} | Content" parameter1="app" parameter2="schema" value1="{{appName() | async}}" value2="{{schema.name}}"></sqx-title>
<sqx-title message="{app} | {schema} | Content" parameter1="app" parameter2="schema" [value1]="ctx.appName" [value2]="schema?.name"></sqx-title>
<form [formGroup]="contentForm" (ngSubmit)="saveAndPublish()">
<sqx-panel panelWidth="53rem">
<sqx-panel desiredWidth="53rem">
<div class="panel-header">
<div class="panel-title-row">
<div class="float-right" *ngIf="!content || content.status !== 'Archived'">

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

@ -7,7 +7,7 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { AbstractControl, FormControl, FormGroup } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { Router } from '@angular/router';
import { Observable, Subscription } from 'rxjs';
import {
@ -20,16 +20,12 @@ import {
} from './../messages';
import {
AppComponentBase,
AppContext,
AppLanguageDto,
AppsStoreService,
allData,
AuthService,
CanComponentDeactivate,
ContentDto,
ContentsService,
DialogService,
MessageBus,
SchemaDetailsDto,
Version
} from 'shared';
@ -37,9 +33,12 @@ import {
@Component({
selector: 'sqx-content-page',
styleUrls: ['./content-page.component.scss'],
templateUrl: './content-page.component.html'
templateUrl: './content-page.component.html',
providers: [
AppContext
]
})
export class ContentPageComponent extends AppComponentBase implements CanComponentDeactivate, OnDestroy, OnInit {
export class ContentPageComponent implements CanComponentDeactivate, OnDestroy, OnInit {
private contentPublishedSubscription: Subscription;
private contentUnpublishedSubscription: Subscription;
private contentDeletedSubscription: Subscription;
@ -55,13 +54,10 @@ export class ContentPageComponent extends AppComponentBase implements CanCompone
public languages: AppLanguageDto[] = [];
constructor(apps: AppsStoreService, dialogs: DialogService, authService: AuthService,
constructor(public readonly ctx: AppContext,
private readonly contentsService: ContentsService,
private readonly route: ActivatedRoute,
private readonly router: Router,
private readonly messageBus: MessageBus
private readonly router: Router
) {
super(dialogs, apps, authService);
}
public ngOnDestroy() {
@ -72,18 +68,18 @@ export class ContentPageComponent extends AppComponentBase implements CanCompone
}
public ngOnInit() {
const routeData = allData(this.route);
const routeData = allData(this.ctx.route);
this.languages = routeData['appLanguages'];
this.contentVersionSelectedSubscription =
this.messageBus.of(ContentVersionSelected)
this.ctx.bus.of(ContentVersionSelected)
.subscribe(message => {
this.loadVersion(message.version);
});
this.contentPublishedSubscription =
this.messageBus.of(ContentPublished)
this.ctx.bus.of(ContentPublished)
.subscribe(message => {
if (this.content && message.content.id === this.content.id) {
this.content = this.content.publish(message.content.lastModifiedBy, message.content.version, message.content.lastModified);
@ -91,7 +87,7 @@ export class ContentPageComponent extends AppComponentBase implements CanCompone
});
this.contentUnpublishedSubscription =
this.messageBus.of(ContentUnpublished)
this.ctx.bus.of(ContentUnpublished)
.subscribe(message => {
if (this.content && message.content.id === this.content.id) {
this.content = this.content.unpublish(message.content.lastModifiedBy, message.content.version, message.content.lastModified);
@ -99,16 +95,16 @@ export class ContentPageComponent extends AppComponentBase implements CanCompone
});
this.contentDeletedSubscription =
this.messageBus.of(ContentRemoved)
this.ctx.bus.of(ContentRemoved)
.subscribe(message => {
if (this.content && message.content.id === this.content.id) {
this.router.navigate(['../'], { relativeTo: this.route });
this.router.navigate(['../'], { relativeTo: this.ctx.route });
}
});
this.setupContentForm(routeData['schema']);
this.route.data.map(p => p['content'])
this.ctx.route.data.map(d => d.content)
.subscribe((content: ContentDto) => {
this.content = content;
@ -120,7 +116,7 @@ export class ContentPageComponent extends AppComponentBase implements CanCompone
if (!this.contentForm.dirty || this.isNewMode) {
return Observable.of(true);
} else {
return this.dialogs.confirmUnsavedChanges();
return this.ctx.confirmUnsavedChanges();
}
}
@ -141,64 +137,66 @@ export class ContentPageComponent extends AppComponentBase implements CanCompone
const requestDto = this.contentForm.value;
if (this.isNewMode) {
this.appNameOnce()
.switchMap(app => this.contentsService.postContent(app, this.schema.name, requestDto, publish))
this.contentsService.postContent(this.ctx.appName, this.schema.name, requestDto, publish)
.subscribe(dto => {
this.content = dto;
this.ctx.notifyInfo('Content created successfully.');
this.emitContentCreated(this.content);
this.notifyInfo('Content created successfully.');
this.back();
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
this.enableContentForm();
});
} else {
this.appNameOnce()
.switchMap(app => this.contentsService.putContent(app, this.schema.name, this.content.id, requestDto, this.content.version))
this.contentsService.putContent(this.ctx.appName, this.schema.name, this.content.id, requestDto, this.content.version)
.subscribe(dto => {
this.content = this.content.update(dto.payload, this.userToken, dto.version);
this.content = this.content.update(dto.payload, this.ctx.userToken, dto.version);
this.ctx.notifyInfo('Content saved successfully.');
this.emitContentUpdated(this.content);
this.notifyInfo('Content saved successfully.');
this.enableContentForm();
this.populateContentForm();
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
this.enableContentForm();
});
}
} else {
this.notifyError('Content element not valid, please check the field with the red bar on the left in all languages (if localizable).');
this.ctx.notifyError('Content element not valid, please check the field with the red bar on the left in all languages (if localizable).');
}
}
private loadVersion(version: number) {
if (!this.isNewMode && this.content) {
this.appNameOnce()
.switchMap(app => this.contentsService.getVersionData(app, this.schema.name, this.content.id, new Version(version.toString())))
this.contentsService.getVersionData(this.ctx.appName, this.schema.name, this.content.id, new Version(version.toString()))
.subscribe(dto => {
this.content = this.content.setData(dto);
this.ctx.notifyInfo('Content version loaded successfully.');
this.emitContentUpdated(this.content);
this.notifyInfo('Content version loaded successfully.');
this.populateContentForm();
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
});
}
}
private back() {
this.router.navigate(['../'], { relativeTo: this.route, replaceUrl: true });
this.router.navigate(['../'], { relativeTo: this.ctx.route, replaceUrl: true });
}
private emitContentCreated(content: ContentDto) {
this.messageBus.emit(new ContentCreated(content));
this.ctx.bus.emit(new ContentCreated(content));
}
private emitContentUpdated(content: ContentDto) {
this.messageBus.emit(new ContentUpdated(content));
this.ctx.bus.emit(new ContentUpdated(content));
}
private disableContentForm() {

4
src/Squidex/app/features/content/pages/contents/contents-page.component.html

@ -1,6 +1,6 @@
<sqx-title message="{app} | {schema} | Contents" parameter1="app" parameter2="schema" value1="{{appName() | async}}" value2="{{schema.name}}"></sqx-title>
<sqx-title message="{app} | {schema} | Contents" parameter1="app" parameter2="schema" [value1]="ctx.appName" [value2]="schema?.name"></sqx-title>
<sqx-panel [panelWidth]="isReadOnly ? '40rem' : '60rem'">
<sqx-panel [desiredWidth]="isReadOnly ? '40rem' : '60rem'">
<div class="panel-header">
<div class="panel-title-row">
<div class="float-right">

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

@ -7,7 +7,6 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { FormControl } from '@angular/forms';
import { ActivatedRoute } from '@angular/router';
import { Subscription } from 'rxjs';
import {
@ -20,16 +19,12 @@ import {
import {
allData,
AppComponentBase,
AppContext,
AppLanguageDto,
AppsStoreService,
AuthService,
ContentDto,
ContentsService,
DialogService,
FieldDto,
ImmutableArray,
MessageBus,
ModalView,
Pager,
SchemaDetailsDto
@ -38,9 +33,12 @@ import {
@Component({
selector: 'sqx-contents-page',
styleUrls: ['./contents-page.component.scss'],
templateUrl: './contents-page.component.html'
templateUrl: './contents-page.component.html',
providers: [
AppContext
]
})
export class ContentsPageComponent extends AppComponentBase implements OnDestroy, OnInit {
export class ContentsPageComponent implements OnDestroy, OnInit {
private contentCreatedSubscription: Subscription;
private contentUpdatedSubscription: Subscription;
@ -63,12 +61,9 @@ export class ContentsPageComponent extends AppComponentBase implements OnDestroy
public columnWidth: number;
constructor(apps: AppsStoreService, dialogs: DialogService, authService: AuthService,
private readonly contentsService: ContentsService,
private readonly route: ActivatedRoute,
private readonly messageBus: MessageBus
constructor(public readonly ctx: AppContext,
private readonly contentsService: ContentsService
) {
super(dialogs, apps, authService);
}
public ngOnDestroy() {
@ -77,29 +72,29 @@ export class ContentsPageComponent extends AppComponentBase implements OnDestroy
}
public ngOnInit() {
const routeData = allData(this.route);
const routeData = allData(this.ctx.route);
this.languages = routeData['appLanguages'];
this.contentCreatedSubscription =
this.messageBus.of(ContentCreated)
this.ctx.bus.of(ContentCreated)
.subscribe(message => {
this.contentItems = this.contentItems.pushFront(message.content);
this.contentsPager = this.contentsPager.incrementCount();
});
this.contentUpdatedSubscription =
this.messageBus.of(ContentUpdated)
this.ctx.bus.of(ContentUpdated)
.subscribe(message => {
this.contentItems = this.contentItems.replaceBy('id', message.content, (o, n) => o.update(n.data, n.lastModifiedBy, n.version, n.lastModified));
});
this.route.params.map(p => <string> p['language'])
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.route.data.map(p => p['schemaOverride'] || p['schema'])
this.ctx.route.data.map(d => d.schemaOverride || d.schema)
.subscribe(schema => {
this.schema = schema;
@ -115,79 +110,73 @@ export class ContentsPageComponent extends AppComponentBase implements OnDestroy
}
public publishContent(content: ContentDto) {
this.appNameOnce()
.switchMap(app => this.contentsService.publishContent(app, this.schema.name, content.id, content.version))
this.contentsService.publishContent(this.ctx.appName, this.schema.name, content.id, content.version)
.subscribe(dto => {
content = content.publish(this.userToken, dto.version);
content = content.publish(this.ctx.userToken, dto.version);
this.contentItems = this.contentItems.replaceBy('id', content);
this.emitContentPublished(content);
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
});
}
public unpublishContent(content: ContentDto) {
this.appNameOnce()
.switchMap(app => this.contentsService.unpublishContent(app, this.schema.name, content.id, content.version))
this.contentsService.unpublishContent(this.ctx.appName, this.schema.name, content.id, content.version)
.subscribe(dto => {
content = content.unpublish(this.userToken, dto.version);
content = content.unpublish(this.ctx.userToken, dto.version);
this.contentItems = this.contentItems.replaceBy('id', content);
this.emitContentUnpublished(content);
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
});
}
public archiveContent(content: ContentDto) {
this.appNameOnce()
.switchMap(app => this.contentsService.archiveContent(app, this.schema.name, content.id, content.version))
this.contentsService.archiveContent(this.ctx.appName, this.schema.name, content.id, content.version)
.subscribe(dto => {
content = content.archive(this.userToken, dto.version);
content = content.archive(this.ctx.userToken, dto.version);
this.removeContent(content);
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
});
}
public restoreContent(content: ContentDto) {
this.appNameOnce()
.switchMap(app => this.contentsService.restoreContent(app, this.schema.name, content.id, content.version))
this.contentsService.restoreContent(this.ctx.appName, this.schema.name, content.id, content.version)
.subscribe(dto => {
content = content.restore(this.userToken, dto.version);
content = content.restore(this.ctx.userToken, dto.version);
this.removeContent(content);
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
});
}
public deleteContent(content: ContentDto) {
this.appNameOnce()
.switchMap(app => this.contentsService.deleteContent(app, this.schema.name, content.id, content.version))
this.contentsService.deleteContent(this.ctx.appName, this.schema.name, content.id, content.version)
.subscribe(() => {
this.removeContent(content);
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
});
}
public load(showInfo = false) {
this.appNameOnce()
.switchMap(app => this.contentsService.getContents(app, this.schema.name, this.contentsPager.pageSize, this.contentsPager.skip, this.contentsQuery, null, this.isArchive))
this.contentsService.getContents(this.ctx.appName, this.schema.name, this.contentsPager.pageSize, this.contentsPager.skip, this.contentsQuery, null, this.isArchive)
.subscribe(dtos => {
this.contentItems = ImmutableArray.of(dtos.items);
this.contentsPager = this.contentsPager.setCount(dtos.total);
if (showInfo) {
this.notifyInfo('Contents reloaded.');
this.ctx.notifyInfo('Contents reloaded.');
}
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
});
}
@ -226,15 +215,15 @@ export class ContentsPageComponent extends AppComponentBase implements OnDestroy
}
private emitContentPublished(content: ContentDto) {
this.messageBus.emit(new ContentPublished(content));
this.ctx.bus.emit(new ContentPublished(content));
}
private emitContentUnpublished(content: ContentDto) {
this.messageBus.emit(new ContentUnpublished(content));
this.ctx.bus.emit(new ContentUnpublished(content));
}
private emitContentRemoved(content: ContentDto) {
this.messageBus.emit(new ContentRemoved(content));
this.ctx.bus.emit(new ContentRemoved(content));
}
private resetContents() {

4
src/Squidex/app/features/content/pages/schemas/schemas-page.component.html

@ -1,6 +1,6 @@
<sqx-title message="{app} | Schemas" parameter1="app" value1="{{appName() | async}}"></sqx-title>
<sqx-title message="{app} | Schemas" parameter1="app" [value1]="ctx.appName"></sqx-title>
<sqx-panel theme="dark" panelWidth="16rem">
<sqx-panel theme="dark" desiredWidth="16rem">
<div class="panel-header">
<div class="panel-title-row">
<h3 class="panel-title">Schemas</h3>

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

@ -10,10 +10,7 @@ import { FormControl } from '@angular/forms';
import { Observable } from 'rxjs';
import {
AppComponentBase,
AppsStoreService,
AuthService,
DialogService,
AppContext,
SchemaDto,
SchemasService
} from 'shared';
@ -21,9 +18,12 @@ import {
@Component({
selector: 'sqx-schemas-page',
styleUrls: ['./schemas-page.component.scss'],
templateUrl: './schemas-page.component.html'
templateUrl: './schemas-page.component.html',
providers: [
AppContext
]
})
export class SchemasPageComponent extends AppComponentBase {
export class SchemasPageComponent {
public schemasFilter = new FormControl();
public schemasFiltered =
this.schemasFilter.valueChanges
@ -53,17 +53,15 @@ export class SchemasPageComponent extends AppComponentBase {
});
});
constructor(apps: AppsStoreService, dialogs: DialogService, authService: AuthService,
constructor(public readonly ctx: AppContext,
private readonly schemasService: SchemasService
) {
super(dialogs, apps, authService);
}
private loadSchemas(): Observable<SchemaDto[]> {
return this.appNameOnce()
.switchMap(app => this.schemasService.getSchemas(app).retry(2))
return this.schemasService.getSchemas(this.ctx.appName)
.catch(error => {
this.notifyError(error);
this.ctx.notifyError(error);
return [];
});
}

26
src/Squidex/app/features/content/shared/assets-editor.component.ts

@ -12,15 +12,11 @@ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { Subscription } from 'rxjs';
import {
AppComponentBase,
AppsStoreService,
AppContext,
AssetDto,
AssetsService,
AssetUpdated,
AuthService,
DialogService,
ImmutableArray,
MessageBus,
Types
} from 'shared';
@ -32,9 +28,12 @@ export const SQX_ASSETS_EDITOR_CONTROL_VALUE_ACCESSOR: any = {
selector: 'sqx-assets-editor',
styleUrls: ['./assets-editor.component.scss'],
templateUrl: './assets-editor.component.html',
providers: [SQX_ASSETS_EDITOR_CONTROL_VALUE_ACCESSOR]
providers: [
AppContext,
SQX_ASSETS_EDITOR_CONTROL_VALUE_ACCESSOR
]
})
export class AssetsEditorComponent extends AppComponentBase implements ControlValueAccessor, OnDestroy, OnInit {
export class AssetsEditorComponent implements ControlValueAccessor, OnDestroy, OnInit {
private assetUpdatedSubscription: Subscription;
private callChange = (v: any) => { /* NOOP */ };
private callTouched = () => { /* NOOP */ };
@ -44,11 +43,9 @@ export class AssetsEditorComponent extends AppComponentBase implements ControlVa
public isDisabled = false;
constructor(apps: AppsStoreService, dialogs: DialogService, authService: AuthService,
private readonly assetsService: AssetsService,
private readonly messageBus: MessageBus
constructor(public readonly ctx: AppContext,
private readonly assetsService: AssetsService
) {
super(dialogs, apps, authService);
}
public ngOnDestroy() {
@ -57,7 +54,7 @@ export class AssetsEditorComponent extends AppComponentBase implements ControlVa
public ngOnInit() {
this.assetUpdatedSubscription =
this.messageBus.of(AssetUpdated)
this.ctx.bus.of(AssetUpdated)
.subscribe(event => {
if (event.sender !== this) {
this.oldAssets = this.oldAssets.replaceBy('id', event.assetDto);
@ -71,8 +68,7 @@ export class AssetsEditorComponent extends AppComponentBase implements ControlVa
if (Types.isArrayOfString(value) && value.length > 0) {
const assetIds: string[] = value;
this.appNameOnce()
.switchMap(app => this.assetsService.getAssets(app, 10000, 0, undefined, undefined, value))
this.assetsService.getAssets(this.ctx.appName, 10000, 0, undefined, undefined, value)
.subscribe(dtos => {
this.oldAssets = ImmutableArray.of(assetIds.map(id => dtos.items.find(x => x.id === id)));
});
@ -129,7 +125,7 @@ export class AssetsEditorComponent extends AppComponentBase implements ControlVa
}
public onAssetUpdated(asset: AssetDto) {
this.messageBus.emit(new AssetUpdated(asset, this));
this.ctx.bus.emit(new AssetUpdated(asset, this));
}
public onAssetFailed(file: File) {

14
src/Squidex/app/features/content/shared/content-item.component.ts

@ -8,11 +8,8 @@
import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core';
import {
AppComponentBase,
AppsStoreService,
AuthService,
AppContext,
ContentDto,
DialogService,
fadeAnimation,
FieldDto,
ModalView,
@ -25,11 +22,14 @@ import {
selector: '[sqxContent]',
styleUrls: ['./content-item.component.scss'],
templateUrl: './content-item.component.html',
providers: [
AppContext
],
animations: [
fadeAnimation
]
})
export class ContentItemComponent extends AppComponentBase implements OnInit, OnChanges {
export class ContentItemComponent implements OnInit, OnChanges {
@Output()
public publishing = new EventEmitter();
@ -67,8 +67,8 @@ export class ContentItemComponent extends AppComponentBase implements OnInit, On
public values: any[] = [];
constructor(apps: AppsStoreService, dialogs: DialogService, authService: AuthService) {
super(dialogs, apps, authService);
constructor(public readonly ctx: AppContext
) {
}
public ngOnChanges() {

26
src/Squidex/app/features/content/shared/references-editor.component.ts

@ -11,14 +11,12 @@ import { Component, forwardRef, Input, OnInit } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import {
AppComponentBase,
AppsStoreService,
AuthService,
AppContext,
ContentDto,
ContentsService,
DialogService,
FieldDto,
ImmutableArray,
MathHelper,
SchemaDetailsDto,
SchemasService,
Types
@ -32,9 +30,12 @@ export const SQX_REFERENCES_EDITOR_CONTROL_VALUE_ACCESSOR: any = {
selector: 'sqx-references-editor',
styleUrls: ['./references-editor.component.scss'],
templateUrl: './references-editor.component.html',
providers: [SQX_REFERENCES_EDITOR_CONTROL_VALUE_ACCESSOR]
providers: [
AppContext,
SQX_REFERENCES_EDITOR_CONTROL_VALUE_ACCESSOR
]
})
export class ReferencesEditorComponent extends AppComponentBase implements ControlValueAccessor, OnInit {
export class ReferencesEditorComponent implements ControlValueAccessor, OnInit {
private callChange = (v: any) => { /* NOOP */ };
private callTouched = () => { /* NOOP */ };
@ -54,16 +55,18 @@ export class ReferencesEditorComponent extends AppComponentBase implements Contr
public isDisabled = false;
public isInvalidSchema = false;
constructor(apps: AppsStoreService, dialogs: DialogService, authService: AuthService,
constructor(public readonly ctx: AppContext,
private readonly contentsService: ContentsService,
private readonly schemasService: SchemasService
) {
super(dialogs, apps, authService);
}
public ngOnInit() {
this.appNameOnce()
.switchMap(app => this.schemasService.getSchema(app, this.schemaId))
if (this.schemaId === MathHelper.EMPTY_GUID) {
return;
}
this.schemasService.getSchema(this.ctx.appName, this.schemaId)
.subscribe(dto => {
this.schema = dto;
@ -79,8 +82,7 @@ export class ReferencesEditorComponent extends AppComponentBase implements Contr
if (Types.isArrayOfString(value) && value.length > 0) {
const contentIds: string[] = value;
this.appNameOnce()
.switchMap(app => this.contentsService.getContents(app, this.schemaId, 10000, 0, undefined, contentIds))
this.contentsService.getContents(this.ctx.appName, this.schemaId, 10000, 0, undefined, contentIds)
.subscribe(dtos => {
this.contentItems = ImmutableArray.of(contentIds.map(id => dtos.items.find(c => c.id === id)).filter(c => !!c));
});

6
src/Squidex/app/features/dashboard/pages/dashboard-page.component.html

@ -1,13 +1,13 @@
<sqx-title message="{app} | Dashboard" parameter1="app" value1="{{appName() | async}}"></sqx-title>
<sqx-title message="{app} | Dashboard" parameter1="app" [value1]="ctx.appName"></sqx-title>
<div class="dashboard" @fade>
<div class="dashboard-inner">
<div>
<h1 class="dashboard-title">Hi {{profileDisplayName}}</h1>
<h1 class="dashboard-title">Hi {{ctx.user.displayName}}</h1>
<div class="subtext">
Welcome to <span class="app-name">{{appName() | async}}</span> dashboard.
Welcome to <span class="app-name">{{ctx.appName}}</span> dashboard.
</div>
</div>

27
src/Squidex/app/features/dashboard/pages/dashboard-page.component.ts

@ -8,11 +8,8 @@
import { Component, OnInit } from '@angular/core';
import {
AppComponentBase,
AppsStoreService,
AuthService,
AppContext,
DateTime,
DialogService,
fadeAnimation,
UsagesService
} from 'shared';
@ -23,11 +20,14 @@ declare var _urq: any;
selector: 'sqx-dashboard-page',
styleUrls: ['./dashboard-page.component.scss'],
templateUrl: './dashboard-page.component.html',
providers: [
AppContext
],
animations: [
fadeAnimation
]
})
export class DashboardPageComponent extends AppComponentBase implements OnInit {
export class DashboardPageComponent implements OnInit {
public profileDisplayName = '';
public chartStorageCount: any;
@ -60,29 +60,25 @@ export class DashboardPageComponent extends AppComponentBase implements OnInit {
public callsCurrent = 0;
public callsMax = 0;
constructor(apps: AppsStoreService, dialogs: DialogService, authService: AuthService,
constructor(public readonly ctx: AppContext,
private readonly usagesService: UsagesService
) {
super(dialogs, apps, authService);
}
public ngOnInit() {
this.appName()
.switchMap(app => this.usagesService.getTodayStorage(app))
this.usagesService.getTodayStorage(this.ctx.appName)
.subscribe(dto => {
this.assetsCurrent = dto.size;
this.assetsMax = dto.maxAllowed;
});
this.appName()
.switchMap(app => this.usagesService.getMonthCalls(app))
this.usagesService.getMonthCalls(this.ctx.appName)
.subscribe(dto => {
this.callsCurrent = dto.count;
this.callsMax = dto.maxAllowed;
});
this.appName()
.switchMap(app => this.usagesService.getStorageUsages(app, DateTime.today().addDays(-20), DateTime.today()))
this.usagesService.getStorageUsages(this.ctx.appName, DateTime.today().addDays(-20), DateTime.today())
.subscribe(dtos => {
this.chartStorageCount = {
labels: createLabels(dtos),
@ -115,8 +111,7 @@ export class DashboardPageComponent extends AppComponentBase implements OnInit {
};
});
this.appName()
.switchMap(app => this.usagesService.getCallsUsages(app, DateTime.today().addDays(-20), DateTime.today()))
this.usagesService.getCallsUsages(this.ctx.appName, DateTime.today().addDays(-20), DateTime.today())
.subscribe(dtos => {
this.chartCallsCount = {
labels: createLabels(dtos),
@ -144,8 +139,6 @@ export class DashboardPageComponent extends AppComponentBase implements OnInit {
]
};
});
this.profileDisplayName = this.authService.user!.displayName;
}
public showForum() {

4
src/Squidex/app/features/rules/pages/events/rule-events-page.component.html

@ -1,6 +1,6 @@
<sqx-title message="{app} | Rules Events" parameter1="app" value1="{{appName() | async}}"></sqx-title>
<sqx-title message="{app} | Rules Events" parameter1="app" [value1]="ctx.appName"></sqx-title>
<sqx-panel panelWidth="63rem">
<sqx-panel desiredWidth="63rem">
<div class="panel-header">
<div class="panel-title-row">
<div class="float-right">

29
src/Squidex/app/features/rules/pages/events/rule-events-page.component.ts

@ -8,10 +8,7 @@
import { Component, OnInit } from '@angular/core';
import {
AppComponentBase,
AppsStoreService,
AuthService,
DialogService,
AppContext,
ImmutableArray,
Pager,
RuleEventDto,
@ -21,18 +18,20 @@ import {
@Component({
selector: 'sqx-rule-events-page',
styleUrls: ['./rule-events-page.component.scss'],
templateUrl: './rule-events-page.component.html'
templateUrl: './rule-events-page.component.html',
providers: [
AppContext
]
})
export class RuleEventsPageComponent extends AppComponentBase implements OnInit {
export class RuleEventsPageComponent implements OnInit {
public eventsItems = ImmutableArray.empty<RuleEventDto>();
public eventsPager = new Pager(0);
public selectedEventId: string | null = null;
constructor(dialogs: DialogService, appsStore: AppsStoreService, authService: AuthService,
constructor(public readonly ctx: AppContext,
private readonly rulesService: RulesService
) {
super(dialogs, appsStore, authService);
}
public ngOnInit() {
@ -40,27 +39,25 @@ export class RuleEventsPageComponent extends AppComponentBase implements OnInit
}
public load(showInfo = false) {
this.appNameOnce()
.switchMap(app => this.rulesService.getEvents(app, this.eventsPager.pageSize, this.eventsPager.skip))
this.rulesService.getEvents(this.ctx.appName, this.eventsPager.pageSize, this.eventsPager.skip)
.subscribe(dtos => {
this.eventsItems = ImmutableArray.of(dtos.items);
this.eventsPager = this.eventsPager.setCount(dtos.total);
if (showInfo) {
this.notifyInfo('Events reloaded.');
this.ctx.notifyInfo('Events reloaded.');
}
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
});
}
public enqueueEvent(event: RuleEventDto) {
this.appNameOnce()
.switchMap(app => this.rulesService.enqueueEvent(app, event.id))
this.rulesService.enqueueEvent(this.ctx.appName, event.id)
.subscribe(() => {
this.notifyInfo('Events enqueued. Will be resend in a few seconds.');
this.ctx.notifyInfo('Events enqueued. Will be resend in a few seconds.');
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
});
}

32
src/Squidex/app/features/rules/pages/rules/rule-wizard.component.ts

@ -8,12 +8,9 @@
import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core';
import {
AppComponentBase,
AppsStoreService,
AuthService,
AppContext,
CreateRuleDto,
DateTime,
DialogService,
fadeAnimation,
ruleActions,
ruleTriggers,
@ -31,11 +28,14 @@ export const MODE_EDIT_ACTION = 'EditAction';
selector: 'sqx-rule-wizard',
styleUrls: ['./rule-wizard.component.scss'],
templateUrl: './rule-wizard.component.html',
providers: [
AppContext
],
animations: [
fadeAnimation
]
})
export class RuleWizardComponent extends AppComponentBase implements OnInit {
export class RuleWizardComponent implements OnInit {
public ruleActions = ruleActions;
public ruleTriggers = ruleTriggers;
@ -69,10 +69,9 @@ export class RuleWizardComponent extends AppComponentBase implements OnInit {
@Input()
public mode = MODE_WIZARD;
constructor(apps: AppsStoreService, dialogs: DialogService, authService: AuthService,
constructor(public readonly ctx: AppContext,
private readonly rulesService: RulesService
) {
super(dialogs, apps, authService);
}
public ngOnInit() {
@ -126,39 +125,36 @@ export class RuleWizardComponent extends AppComponentBase implements OnInit {
private createRule() {
const requestDto = new CreateRuleDto(this.trigger, this.action);
this.appNameOnce()
.switchMap(app => this.rulesService.postRule(app, requestDto, this.authService.user!.id, DateTime.now()))
this.rulesService.postRule(this.ctx.appName, requestDto, this.ctx.userToken, DateTime.now())
.subscribe(dto => {
this.created.emit(dto);
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
});
}
private updateTrigger() {
const requestDto = new UpdateRuleDto(this.trigger, null);
this.appNameOnce()
.switchMap(app => this.rulesService.putRule(app, this.rule.id, requestDto, this.rule.version))
this.rulesService.putRule(this.ctx.appName, this.rule.id, requestDto, this.rule.version)
.subscribe(dto => {
const rule = this.rule.updateTrigger(this.trigger, this.authService.user.id, dto.version, DateTime.now());
const rule = this.rule.updateTrigger(this.trigger, this.ctx.userToken, dto.version, DateTime.now());
this.updated.emit(rule);
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
});
}
private updateAction() {
const requestDto = new UpdateRuleDto(null, this.action);
this.appNameOnce()
.switchMap(app => this.rulesService.putRule(app, this.rule.id, requestDto, this.rule.version))
this.rulesService.putRule(this.ctx.appName, this.rule.id, requestDto, this.rule.version)
.subscribe(dto => {
const rule = this.rule.updateAction(this.action, this.authService.user.id, dto.version, DateTime.now());
const rule = this.rule.updateAction(this.action, this.ctx.userToken, dto.version, DateTime.now());
this.updated.emit(rule);
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
});
}

4
src/Squidex/app/features/rules/pages/rules/rules-page.component.html

@ -1,6 +1,6 @@
<sqx-title message="{app} | Rules" parameter1="app" value1="{{appName() | async}}"></sqx-title>
<sqx-title message="{app} | Rules" parameter1="app" [value1]="ctx.appName"></sqx-title>
<sqx-panel panelWidth="54rem">
<sqx-panel desiredWidth="54rem">
<div class="panel-header">
<div class="panel-title-row">
<div class="float-right">

43
src/Squidex/app/features/rules/pages/rules/rules-page.component.ts

@ -8,11 +8,8 @@
import { Component, OnInit } from '@angular/core';
import {
AppComponentBase,
AppsStoreService,
AuthService,
AppContext,
DateTime,
DialogService,
fadeAnimation,
ImmutableArray,
ModalView,
@ -28,11 +25,14 @@ import {
selector: 'sqx-rules-page',
styleUrls: ['./rules-page.component.scss'],
templateUrl: './rules-page.component.html',
providers: [
AppContext
],
animations: [
fadeAnimation
]
})
export class RulesPageComponent extends AppComponentBase implements OnInit {
export class RulesPageComponent implements OnInit {
public ruleActions = ruleActions;
public ruleTriggers = ruleTriggers;
@ -44,11 +44,10 @@ export class RulesPageComponent extends AppComponentBase implements OnInit {
public wizardMode = 'Wizard';
public wizardRule: RuleDto;
constructor(apps: AppsStoreService, dialogs: DialogService, authService: AuthService,
constructor(public readonly ctx: AppContext,
private readonly schemasService: SchemasService,
private readonly rulesService: RulesService
) {
super(dialogs, apps, authService);
}
public ngOnInit() {
@ -56,20 +55,17 @@ export class RulesPageComponent extends AppComponentBase implements OnInit {
}
public load(showInfo = false) {
this.appNameOnce()
.switchMap(app =>
this.schemasService.getSchemas(app)
.combineLatest(this.rulesService.getRules(app),
(s, w) => { return { rules: w, schemas: s }; }))
this.schemasService.getSchemas(this.ctx.appName)
.combineLatest(this.rulesService.getRules(this.ctx.appName), (s, w) => { return { rules: w, schemas: s }; })
.subscribe(dtos => {
this.schemas = dtos.schemas;
this.rules = ImmutableArray.of(dtos.rules);
if (showInfo) {
this.notifyInfo('Rules reloaded.');
this.ctx.notifyInfo('Rules reloaded.');
}
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
});
}
@ -108,31 +104,28 @@ export class RulesPageComponent extends AppComponentBase implements OnInit {
public toggleRule(rule: RuleDto) {
if (rule.isEnabled) {
this.appNameOnce()
.switchMap(app => this.rulesService.disableRule(app, rule.id, rule.version))
this.rulesService.disableRule(this.ctx.appName, rule.id, rule.version)
.subscribe(dto => {
this.rules = this.rules.replace(rule, rule.disable(this.authService.user.id, dto.version, DateTime.now()));
this.rules = this.rules.replace(rule, rule.disable(this.ctx.userToken, dto.version, DateTime.now()));
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
});
} else {
this.appNameOnce()
.switchMap(app => this.rulesService.enableRule(app, rule.id, rule.version))
this.rulesService.enableRule(this.ctx.appName, rule.id, rule.version)
.subscribe(dto => {
this.rules = this.rules.replace(rule, rule.enable(this.authService.user.id, dto.version, DateTime.now()));
this.rules = this.rules.replace(rule, rule.enable(this.ctx.userToken, dto.version, DateTime.now()));
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
});
}
}
public deleteRule(rule: RuleDto) {
this.appNameOnce()
.switchMap(app => this.rulesService.deleteRule(app, rule.id, rule.version))
this.rulesService.deleteRule(this.ctx.appName, rule.id, rule.version)
.subscribe(dto => {
this.rules = this.rules.remove(rule);
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
});
}
}

20
src/Squidex/app/features/schemas/pages/schema/schema-edit-form.component.ts

@ -9,8 +9,7 @@ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { FormBuilder, Validators } from '@angular/forms';
import {
ComponentBase,
DialogService,
AppContext,
SchemaPropertiesDto,
SchemasService,
Version
@ -19,9 +18,12 @@ import {
@Component({
selector: 'sqx-schema-edit-form',
styleUrls: ['./schema-edit-form.component.scss'],
templateUrl: './schema-edit-form.component.html'
templateUrl: './schema-edit-form.component.html',
providers: [
AppContext
]
})
export class SchemaEditFormComponent extends ComponentBase implements OnInit {
export class SchemaEditFormComponent implements OnInit {
@Output()
public saved = new EventEmitter<SchemaPropertiesDto>();
@ -37,9 +39,6 @@ export class SchemaEditFormComponent extends ComponentBase implements OnInit {
@Input()
public version: Version;
@Input()
public appName: string;
public editFormSubmitted = false;
public editForm =
this.formBuilder.group({
@ -53,11 +52,10 @@ export class SchemaEditFormComponent extends ComponentBase implements OnInit {
]]
});
constructor(dialogs: DialogService,
constructor(public readonly ctx: AppContext,
private readonly schemas: SchemasService,
private readonly formBuilder: FormBuilder
) {
super(dialogs);
}
public ngOnInit() {
@ -77,12 +75,12 @@ export class SchemaEditFormComponent extends ComponentBase implements OnInit {
const requestDto = this.editForm.value;
this.schemas.putSchema(this.appName, this.name, requestDto, this.version)
this.schemas.putSchema(this.ctx.appName, this.name, requestDto, this.version)
.subscribe(dto => {
this.emitSaved(requestDto);
this.resetEditForm();
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
this.enableEditForm();
});
}

8
src/Squidex/app/features/schemas/pages/schema/schema-page.component.html

@ -1,6 +1,6 @@
<sqx-title message="{app} | {schema}" parameter1="app" value1="{{appName() | async}}" parameter2="schema" value2="{{schema.name}}"></sqx-title>
<sqx-title message="{app} | {schema}" parameter1="app" [value1]="ctx.appName" parameter2="schema" [value2]="schema?.name"></sqx-title>
<sqx-panel panelWidth="48rem">
<sqx-panel desiredWidth="48rem">
<div class="panel-header">
<div class="panel-title-row">
<div class="float-right">
@ -138,7 +138,7 @@
</div>
<div class="modal-body">
<sqx-schema-edit-form [appName]="appName() | async" [name]="schema.name" [properties]="schema.properties" [version]="schema.version" (saved)="onSchemaSaved($event)" (cancelled)="editSchemaDialog.hide()"></sqx-schema-edit-form>
<sqx-schema-edit-form [name]="schema.name" [properties]="schema.properties" [version]="schema.version" (saved)="onSchemaSaved($event)" (cancelled)="editSchemaDialog.hide()"></sqx-schema-edit-form>
</div>
</div>
</div>
@ -167,7 +167,7 @@
<div class="modal-backdrop"></div>
<div class="modal-dialog modal-lg">
<div class="modal-content">
<sqx-schema-scripts-form [appName]="appName() | async" [schema]="schema" (saved)="onSchemaScriptsSaved($event)" (cancelled)="configureScriptsDialog.hide()"></sqx-schema-scripts-form>
<sqx-schema-scripts-form [schema]="schema" (saved)="onSchemaScriptsSaved($event)" (cancelled)="configureScriptsDialog.hide()"></sqx-schema-scripts-form>
</div>
</div>
</div>

125
src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts

@ -7,22 +7,18 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { FormBuilder, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { Router } from '@angular/router';
import { Subscription } from 'rxjs';
import {
AddFieldDto,
AppComponentBase,
AppsStoreService,
AuthService,
AppContext,
createProperties,
DialogService,
fadeAnimation,
FieldDto,
fieldTypes,
HistoryChannelUpdated,
ImmutableArray,
MessageBus,
ModalView,
SchemaDetailsDto,
SchemaDto,
@ -44,11 +40,14 @@ import {
selector: 'sqx-schema-page',
styleUrls: ['./schema-page.component.scss'],
templateUrl: './schema-page.component.html',
providers: [
AppContext
],
animations: [
fadeAnimation
]
})
export class SchemaPageComponent extends AppComponentBase implements OnDestroy, OnInit {
export class SchemaPageComponent implements OnDestroy, OnInit {
private schemaCreatedSubscription: Subscription;
public fieldTypes = fieldTypes;
@ -83,14 +82,11 @@ export class SchemaPageComponent extends AppComponentBase implements OnDestroy,
return this.addFieldForm.controls['name'].value && this.addFieldForm.controls['name'].value.length > 0;
}
constructor(apps: AppsStoreService, dialogs: DialogService, authService: AuthService,
constructor(public readonly ctx: AppContext,
private readonly formBuilder: FormBuilder,
private readonly messageBus: MessageBus,
private readonly route: ActivatedRoute,
private readonly router: Router,
private readonly schemasService: SchemasService
) {
super(dialogs, apps, authService);
}
public ngOnDestroy() {
@ -99,14 +95,14 @@ export class SchemaPageComponent extends AppComponentBase implements OnDestroy,
public ngOnInit() {
this.schemaCreatedSubscription =
this.messageBus.of(SchemaCreated)
this.ctx.bus.of(SchemaCreated)
.subscribe(message => {
if (this.schemas) {
this.schemas = this.schemas.push(message.schema);
}
});
this.route.data.map(p => p['schema'])
this.ctx.route.data.map(d => d.schema)
.subscribe((schema: SchemaDetailsDto) => {
this.schema = schema;
@ -117,125 +113,113 @@ export class SchemaPageComponent extends AppComponentBase implements OnDestroy,
}
private load() {
this.appNameOnce()
.switchMap(app => this.schemasService.getSchemas(app))
this.schemasService.getSchemas(this.ctx.appName)
.subscribe(dtos => {
this.schemas = ImmutableArray.of(dtos);
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
});
}
public publish() {
this.appNameOnce()
.switchMap(app => this.schemasService.publishSchema(app, this.schema.name, this.schema.version)).retry(2)
this.schemasService.publishSchema(this.ctx.appName, this.schema.name, this.schema.version)
.subscribe(dto => {
this.updateSchema(this.schema.publish(this.userToken, dto.version));
this.updateSchema(this.schema.publish(this.ctx.userToken, dto.version));
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
});
}
public unpublish() {
this.appNameOnce()
.switchMap(app => this.schemasService.unpublishSchema(app, this.schema.name, this.schema.version)).retry(2)
this.schemasService.unpublishSchema(this.ctx.appName, this.schema.name, this.schema.version)
.subscribe(dto => {
this.updateSchema(this.schema.unpublish(this.userToken, dto.version));
this.updateSchema(this.schema.unpublish(this.ctx.userToken, dto.version));
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
});
}
public enableField(field: FieldDto) {
this.appNameOnce()
.switchMap(app => this.schemasService.enableField(app, this.schema.name, field.fieldId, this.schema.version)).retry(2)
this.schemasService.enableField(this.ctx.appName, this.schema.name, field.fieldId, this.schema.version)
.subscribe(dto => {
this.updateSchema(this.schema.updateField(field.enable(), this.userToken, dto.version));
this.updateSchema(this.schema.updateField(field.enable(), this.ctx.userToken, dto.version));
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
});
}
public disableField(field: FieldDto) {
this.appNameOnce()
.switchMap(app => this.schemasService.disableField(app, this.schema.name, field.fieldId, this.schema.version)).retry(2)
this.schemasService.disableField(this.ctx.appName, this.schema.name, field.fieldId, this.schema.version)
.subscribe(dto => {
this.updateSchema(this.schema.updateField(field.disable(), this.userToken, dto.version));
this.updateSchema(this.schema.updateField(field.disable(), this.ctx.userToken, dto.version));
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
});
}
public lockField(field: FieldDto) {
this.appNameOnce()
.switchMap(app => this.schemasService.lockField(app, this.schema.name, field.fieldId, this.schema.version)).retry(2)
this.schemasService.lockField(this.ctx.appName, this.schema.name, field.fieldId, this.schema.version)
.subscribe(dto => {
this.updateSchema(this.schema.updateField(field.lock(), this.userToken, dto.version));
this.updateSchema(this.schema.updateField(field.lock(), this.ctx.userToken, dto.version));
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
});
}
public showField(field: FieldDto) {
this.appNameOnce()
.switchMap(app => this.schemasService.showField(app, this.schema.name, field.fieldId, this.schema.version)).retry(2)
this.schemasService.showField(this.ctx.appName, this.schema.name, field.fieldId, this.schema.version)
.subscribe(dto => {
this.updateSchema(this.schema.updateField(field.show(), this.userToken, dto.version));
this.updateSchema(this.schema.updateField(field.show(), this.ctx.userToken, dto.version));
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
});
}
public hideField(field: FieldDto) {
this.appNameOnce()
.switchMap(app => this.schemasService.hideField(app, this.schema.name, field.fieldId, this.schema.version)).retry(2)
this.schemasService.hideField(this.ctx.appName, this.schema.name, field.fieldId, this.schema.version)
.subscribe(dto => {
this.updateSchema(this.schema.updateField(field.hide(), this.userToken, dto.version));
this.updateSchema(this.schema.updateField(field.hide(), this.ctx.userToken, dto.version));
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
});
}
public deleteField(field: FieldDto) {
this.appNameOnce()
.switchMap(app => this.schemasService.deleteField(app, this.schema.name, field.fieldId, this.schema.version)).retry(2)
this.schemasService.deleteField(this.ctx.appName, this.schema.name, field.fieldId, this.schema.version)
.subscribe(dto => {
this.updateSchema(this.schema.removeField(field, this.userToken, dto.version));
this.updateSchema(this.schema.removeField(field, this.ctx.userToken, dto.version));
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
});
}
public sortFields(fields: FieldDto[]) {
this.appNameOnce()
.switchMap(app => this.schemasService.putFieldOrdering(app, this.schema.name, fields.map(t => t.fieldId), this.schema.version)).retry(2)
this.schemasService.putFieldOrdering(this.ctx.appName, this.schema.name, fields.map(t => t.fieldId), this.schema.version)
.subscribe(dto => {
this.updateSchema(this.schema.replaceFields(fields, this.userToken, dto.version));
this.updateSchema(this.schema.replaceFields(fields, this.ctx.userToken, dto.version));
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
});
}
public saveField(field: FieldDto) {
const requestDto = new UpdateFieldDto(field.properties);
this.appNameOnce()
.switchMap(app => this.schemasService.putField(app, this.schema.name, field.fieldId, requestDto, this.schema.version)).retry(2)
this.schemasService.putField(this.ctx.appName, this.schema.name, field.fieldId, requestDto, this.schema.version)
.subscribe(dto => {
this.updateSchema(this.schema.updateField(field, this.userToken, dto.version));
this.updateSchema(this.schema.updateField(field, this.ctx.userToken, dto.version));
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
});
}
public deleteSchema() {
this.appNameOnce()
.switchMap(app => this.schemasService.deleteSchema(app, this.schema.name, this.schema.version)).retry(2)
this.schemasService.deleteSchema(this.ctx.appName, this.schema.name, this.schema.version)
.subscribe(() => {
this.onSchemaRemoved(this.schema);
this.back();
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
});
}
@ -250,13 +234,12 @@ export class SchemaPageComponent extends AppComponentBase implements OnDestroy,
const requestDto = new AddFieldDto(this.addFieldForm.controls['name'].value, partitioning, properties);
this.appNameOnce()
.switchMap(app => this.schemasService.postField(app, this.schema.name, requestDto, this.schema.version))
this.schemasService.postField(this.ctx.appName, this.schema.name, requestDto, this.schema.version)
.subscribe(dto => {
this.updateSchema(this.schema.addField(dto.payload, this.userToken, dto.version));
this.updateSchema(this.schema.addField(dto.payload, this.ctx.userToken, dto.version));
this.resetFieldForm();
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
this.resetFieldForm();
});
}
@ -267,13 +250,13 @@ export class SchemaPageComponent extends AppComponentBase implements OnDestroy,
}
public onSchemaSaved(properties: SchemaPropertiesDto, version: Version) {
this.updateSchema(this.schema.update(properties, this.userToken, version));
this.updateSchema(this.schema.update(properties, this.ctx.userToken, version));
this.editSchemaDialog.hide();
}
public onSchemaScriptsSaved(scripts: UpdateSchemaScriptsDto, version: Version) {
this.updateSchema(this.schema.configureScripts(scripts, this.userToken, version));
this.updateSchema(this.schema.configureScripts(scripts, this.ctx.userToken, version));
this.configureScriptsDialog.hide();
}
@ -295,7 +278,7 @@ export class SchemaPageComponent extends AppComponentBase implements OnDestroy,
this.schemas = this.schemas.replaceBy('id', schema);
this.emitSchemaUpdated(schema);
this.notify();
this.emitHistoryUpdate();
this.export();
}
@ -331,19 +314,19 @@ export class SchemaPageComponent extends AppComponentBase implements OnDestroy,
}
private back() {
this.router.navigate(['../'], { relativeTo: this.route });
this.router.navigate(['../'], { relativeTo: this.ctx.route });
}
private emitSchemaDeleted(schema: SchemaDto) {
this.messageBus.emit(new SchemaDeleted(schema));
this.ctx.bus.emit(new SchemaDeleted(schema));
}
private emitSchemaUpdated(schema: SchemaDto) {
this.messageBus.emit(new SchemaUpdated(schema));
this.ctx.bus.emit(new SchemaUpdated(schema));
}
private notify() {
this.messageBus.emit(new HistoryChannelUpdated());
private emitHistoryUpdate() {
this.ctx.bus.emit(new HistoryChannelUpdated());
}
}

20
src/Squidex/app/features/schemas/pages/schema/schema-scripts-form.component.ts

@ -9,8 +9,7 @@ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import {
ComponentBase,
DialogService,
AppContext,
SchemaDetailsDto,
SchemasService,
UpdateSchemaScriptsDto
@ -19,9 +18,12 @@ import {
@Component({
selector: 'sqx-schema-scripts-form',
styleUrls: ['./schema-scripts-form.component.scss'],
templateUrl: './schema-scripts-form.component.html'
templateUrl: './schema-scripts-form.component.html',
providers: [
AppContext
]
})
export class SchemaScriptsFormComponent extends ComponentBase implements OnInit {
export class SchemaScriptsFormComponent implements OnInit {
@Output()
public saved = new EventEmitter<UpdateSchemaScriptsDto>();
@ -31,9 +33,6 @@ export class SchemaScriptsFormComponent extends ComponentBase implements OnInit
@Input()
public schema: SchemaDetailsDto;
@Input()
public appName: string;
public selectedField = 'scriptQuery';
public scripts = [
@ -54,11 +53,10 @@ export class SchemaScriptsFormComponent extends ComponentBase implements OnInit
scriptChange: ''
});
constructor(dialogs: DialogService,
constructor(public readonly ctx: AppContext,
private readonly schemas: SchemasService,
private readonly formBuilder: FormBuilder
) {
super(dialogs);
}
public ngOnInit() {
@ -84,12 +82,12 @@ export class SchemaScriptsFormComponent extends ComponentBase implements OnInit
this.editForm.controls['scriptDelete'].value,
this.editForm.controls['scriptChange'].value);
this.schemas.putSchemaScripts(this.appName, this.schema.name, requestDto, this.schema.version)
this.schemas.putSchemaScripts(this.ctx.appName, this.schema.name, requestDto, this.schema.version)
.subscribe(dto => {
this.emitSaved(requestDto);
this.resetEditForm();
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
this.enableEditForm();
});
}

6
src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.html

@ -1,6 +1,6 @@
<sqx-title message="{app} | Schemas" parameter1="app" value1="{{appName() | async}}"></sqx-title>
<sqx-title message="{app} | Schemas" parameter1="app" [value1]="ctx.appName"></sqx-title>
<sqx-panel theme="dark" panelWidth="30rem">
<sqx-panel theme="dark" desiredWidth="30rem">
<div class="panel-header">
<div class="panel-title-row">
<h3 class="panel-title">Schemas</h3>
@ -66,7 +66,7 @@
</div>
<div class="modal-body">
<sqx-schema-form [appName]="appName() | async"
<sqx-schema-form
(created)="onSchemaCreated($event)"
(cancelled)="addSchemaDialog.hide()">
</sqx-schema-form>

32
src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.ts

@ -6,18 +6,13 @@
*/
import { Component, OnDestroy, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { FormControl } from '@angular/forms';
import { Subscription } from 'rxjs';
import {
AppComponentBase,
AppsStoreService,
AuthService,
DialogService,
AppContext,
fadeAnimation,
ImmutableArray,
MessageBus,
ModalView,
SchemaDto,
SchemasService
@ -33,11 +28,14 @@ import {
selector: 'sqx-schemas-page',
styleUrls: ['./schemas-page.component.scss'],
templateUrl: './schemas-page.component.html',
providers: [
AppContext
],
animations: [
fadeAnimation
]
})
export class SchemasPageComponent extends AppComponentBase implements OnDestroy, OnInit {
export class SchemasPageComponent implements OnDestroy, OnInit {
private schemaUpdatedSubscription: Subscription;
private schemaDeletedSubscription: Subscription;
@ -48,12 +46,9 @@ export class SchemasPageComponent extends AppComponentBase implements OnDestroy,
public schemasFilter = new FormControl();
public schemasFiltered = ImmutableArray.empty<SchemaDto>();
constructor(apps: AppsStoreService, dialogs: DialogService, authService: AuthService,
private readonly schemasService: SchemasService,
private readonly messageBus: MessageBus,
private readonly route: ActivatedRoute
constructor(public readonly ctx: AppContext,
private readonly schemasService: SchemasService
) {
super(dialogs, apps, authService);
}
public ngOnDestroy() {
@ -69,7 +64,7 @@ export class SchemasPageComponent extends AppComponentBase implements OnDestroy,
this.updateSchemas(this.schemas, this.schemaQuery = q);
});
this.route.params.map(q => q['showDialog'])
this.ctx.route.params.map(q => q['showDialog'])
.subscribe(showDialog => {
if (showDialog) {
this.addSchemaDialog.show();
@ -77,13 +72,13 @@ export class SchemasPageComponent extends AppComponentBase implements OnDestroy,
});
this.schemaUpdatedSubscription =
this.messageBus.of(SchemaUpdated)
this.ctx.bus.of(SchemaUpdated)
.subscribe(m => {
this.updateSchemas(this.schemas.replaceBy('id', m.schema));
});
this.schemaDeletedSubscription =
this.messageBus.of(SchemaDeleted)
this.ctx.bus.of(SchemaDeleted)
.subscribe(m => {
this.updateSchemas(this.schemas.filter(s => s.id !== m.schema.id));
});
@ -92,12 +87,11 @@ export class SchemasPageComponent extends AppComponentBase implements OnDestroy,
}
private load() {
this.appNameOnce()
.switchMap(app => this.schemasService.getSchemas(app).retry(2))
this.schemasService.getSchemas(this.ctx.appName)
.subscribe(dtos => {
this.updateSchemas(ImmutableArray.of(dtos));
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
});
}
@ -109,7 +103,7 @@ export class SchemasPageComponent extends AppComponentBase implements OnDestroy,
}
private emitSchemaCreated(schema: SchemaDto) {
this.messageBus.emit(new SchemaCreated(schema));
this.ctx.bus.emit(new SchemaCreated(schema));
}
private updateSchemas(schemas: ImmutableArray<SchemaDto>, query?: string) {

18
src/Squidex/app/features/settings/pages/clients/client.component.ts

@ -10,10 +10,9 @@ import { FormBuilder, Validators } from '@angular/forms';
import {
AccessTokenDto,
AppContext,
AppClientDto,
AppClientsService,
ComponentBase,
DialogService,
fadeAnimation,
ModalView
} from 'shared';
@ -24,11 +23,14 @@ const ESCAPE_KEY = 27;
selector: 'sqx-client',
styleUrls: ['./client.component.scss'],
templateUrl: './client.component.html',
providers: [
AppContext
],
animations: [
fadeAnimation
]
})
export class ClientComponent extends ComponentBase {
export class ClientComponent {
@Output()
public renaming = new EventEmitter<string>();
@ -38,9 +40,6 @@ export class ClientComponent extends ComponentBase {
@Output()
public updating = new EventEmitter<boolean>();
@Input()
public appName: string;
@Input()
public client: AppClientDto;
@ -62,11 +61,10 @@ export class ClientComponent extends ComponentBase {
return this.renameForm.controls['name'].value !== this.client.name;
}
constructor(dialogs: DialogService,
constructor(public readonly ctx: AppContext,
private readonly appClientsService: AppClientsService,
private readonly formBuilder: FormBuilder
) {
super(dialogs);
}
public cancelRename() {
@ -86,12 +84,12 @@ export class ClientComponent extends ComponentBase {
}
public createToken(client: AppClientDto) {
this.appClientsService.createToken(this.appName, client)
this.appClientsService.createToken(this.ctx.appName, client)
.subscribe(dto => {
this.token = dto;
this.tokenDialog.show();
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
});
}
}

6
src/Squidex/app/features/settings/pages/clients/clients-page.component.html

@ -1,6 +1,6 @@
<sqx-title message="{app} | Clients | Settings" parameter1="app" value1="{{appName() | async}}"></sqx-title>
<sqx-title message="{app} | Clients | Settings" parameter1="app" [value1]="ctx.appName"></sqx-title>
<sqx-panel panelWidth="44rem">
<sqx-panel desiredWidth="44rem">
<div class="panel-header">
<div class="panel-title-row">
<h3 class="panel-title">Clients</h3>
@ -18,7 +18,7 @@
</div>
<div *ngFor="let client of appClients?.clients">
<sqx-client [client]="client" [appName]="appName() | async"
<sqx-client [client]="client"
(updating)="updateClient(client, $event)"
(renaming)="renameClient(client, $event)"
(revoking)="revokeClient(client)"></sqx-client>

44
src/Squidex/app/features/settings/pages/clients/clients-page.component.ts

@ -12,13 +12,9 @@ import {
AppClientDto,
AppClientsDto,
AppClientsService,
AppComponentBase,
AppsStoreService,
AuthService,
AppContext,
CreateAppClientDto,
DialogService,
HistoryChannelUpdated,
MessageBus,
UpdateAppClientDto,
ValidatorsEx
} from 'shared';
@ -26,9 +22,12 @@ import {
@Component({
selector: 'sqx-clients-page',
styleUrls: ['./clients-page.component.scss'],
templateUrl: './clients-page.component.html'
templateUrl: './clients-page.component.html',
providers: [
AppContext
]
})
export class ClientsPageComponent extends AppComponentBase implements OnInit {
export class ClientsPageComponent implements OnInit {
public appClients: AppClientsDto;
public addClientFormSubmitted = false;
@ -45,12 +44,10 @@ export class ClientsPageComponent extends AppComponentBase implements OnInit {
return this.addClientForm.controls['name'].value && this.addClientForm.controls['name'].value.length > 0;
}
constructor(apps: AppsStoreService, dialogs: DialogService, authService: AuthService,
constructor(public readonly ctx: AppContext,
private readonly appClientsService: AppClientsService,
private readonly messageBus: MessageBus,
private readonly formBuilder: FormBuilder
) {
super(dialogs, apps, authService);
}
public ngOnInit() {
@ -58,46 +55,42 @@ export class ClientsPageComponent extends AppComponentBase implements OnInit {
}
public load() {
this.appNameOnce()
.switchMap(app => this.appClientsService.getClients(app).retry(2))
this.appClientsService.getClients(this.ctx.appName)
.subscribe(dtos => {
this.updateClients(dtos);
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
});
}
public revokeClient(client: AppClientDto) {
this.appNameOnce()
.switchMap(app => this.appClientsService.deleteClient(app, client.id, this.appClients.version))
this.appClientsService.deleteClient(this.ctx.appName, client.id, this.appClients.version)
.subscribe(dto => {
this.updateClients(this.appClients.removeClient(client, dto.version));
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
});
}
public renameClient(client: AppClientDto, name: string) {
const requestDto = new UpdateAppClientDto(name);
this.appNameOnce()
.switchMap(app => this.appClientsService.updateClient(app, client.id, requestDto, this.appClients.version))
this.appClientsService.updateClient(this.ctx.appName, client.id, requestDto, this.appClients.version)
.subscribe(dto => {
this.updateClients(this.appClients.updateClient(client.rename(name), dto.version));
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
});
}
public updateClient(client: AppClientDto, permission: string) {
const requestDto = new UpdateAppClientDto(undefined, permission);
this.appNameOnce()
.switchMap(app => this.appClientsService.updateClient(app, client.id, requestDto, this.appClients.version))
this.appClientsService.updateClient(this.ctx.appName, client.id, requestDto, this.appClients.version)
.subscribe(dto => {
this.updateClients(this.appClients.updateClient(client.update(permission), dto.version));
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
});
}
@ -109,13 +102,12 @@ export class ClientsPageComponent extends AppComponentBase implements OnInit {
const requestDto = new CreateAppClientDto(this.addClientForm.controls['name'].value);
this.appNameOnce()
.switchMap(app => this.appClientsService.postClient(app, requestDto, this.appClients.version))
this.appClientsService.postClient(this.ctx.appName, requestDto, this.appClients.version)
.subscribe(dto => {
this.updateClients(this.appClients.addClient(dto.payload, dto.version));
this.resetClientForm();
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
this.resetClientForm();
});
}
@ -134,6 +126,6 @@ export class ClientsPageComponent extends AppComponentBase implements OnInit {
private updateClients(appClients: AppClientsDto) {
this.appClients = appClients;
this.messageBus.emit(new HistoryChannelUpdated());
this.ctx.bus.emit(new HistoryChannelUpdated());
}
}

8
src/Squidex/app/features/settings/pages/contributors/contributors-page.component.html

@ -1,6 +1,6 @@
<sqx-title message="{app} | Contributors | Settings" parameter1="app" value1="{{appName() | async}}"></sqx-title>
<sqx-title message="{app} | Contributors | Settings" parameter1="app" [value1]="ctx.appName"></sqx-title>
<sqx-panel panelWidth="50rem">
<sqx-panel desiredWidth="50rem">
<div class="panel-header">
<div class="panel-title-row">
<h3 class="panel-title">Contributors</h3>
@ -39,12 +39,12 @@
<span class="user-email table-cell">{{contributor.contributorId | sqxUserEmail}}</span>
</td>
<td>
<select class="form-control" [ngModel]="contributor.permission" (ngModelChange)="changePermission(contributor, $event)" [disabled]="currentUserId === contributor.contributorId">
<select class="form-control" [ngModel]="contributor.permission" (ngModelChange)="changePermission(contributor, $event)" [disabled]="userId === contributor.contributorId">
<option *ngFor="let permission of usersPermissions" [ngValue]="permission">{{permission}}</option>
</select>
</td>
<td>
<button type="button" class="btn btn-link btn-danger" [disabled]="currentUserId === contributor.contributorId" (click)="removeContributor(contributor)">
<button type="button" class="btn btn-link btn-danger" [disabled]="userId === contributor.contributorId" (click)="removeContributor(contributor)">
<i class="icon-bin2"></i>
</button>
</td>

45
src/Squidex/app/features/settings/pages/contributors/contributors-page.component.ts

@ -10,16 +10,12 @@ import { FormBuilder, Validators } from '@angular/forms';
import { Observable } from 'rxjs';
import {
AppComponentBase,
AppContext,
AppContributorDto,
AppContributorsDto,
AppContributorsService,
AppsStoreService,
AuthService,
AutocompleteSource,
DialogService,
HistoryChannelUpdated,
MessageBus,
UsersService
} from 'shared';
@ -48,13 +44,14 @@ export class UsersDataSource implements AutocompleteSource {
@Component({
selector: 'sqx-contributors-page',
styleUrls: ['./contributors-page.component.scss'],
templateUrl: './contributors-page.component.html'
templateUrl: './contributors-page.component.html',
providers: [
AppContext
]
})
export class ContributorsPageComponent extends AppComponentBase implements OnInit {
export class ContributorsPageComponent implements OnInit {
public appContributors: AppContributorsDto;
public currentUserId: string;
public maxContributors = -1;
public usersDataSource: UsersDataSource;
@ -72,64 +69,56 @@ export class ContributorsPageComponent extends AppComponentBase implements OnIni
]]
});
constructor(apps: AppsStoreService, dialogs: DialogService, usersService: UsersService, authService: AuthService,
constructor(public readonly ctx: AppContext, usersService: UsersService,
private readonly appContributorsService: AppContributorsService,
private readonly messageBus: MessageBus,
private readonly formBuilder: FormBuilder
) {
super(dialogs, apps, authService);
this.usersDataSource = new UsersDataSource(usersService, this);
}
public ngOnInit() {
this.currentUserId = this.authService.user!.id;
this.load();
}
public load() {
this.appNameOnce()
.switchMap(app => this.appContributorsService.getContributors(app).retry(2))
this.appContributorsService.getContributors(this.ctx.appName)
.subscribe(dto => {
this.updateContributorsFromDto(dto);
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
});
}
public removeContributor(contributor: AppContributorDto) {
this.appNameOnce()
.switchMap(app => this.appContributorsService.deleteContributor(app, contributor.contributorId, this.appContributors.version))
this.appContributorsService.deleteContributor(this.ctx.appName, contributor.contributorId, this.appContributors.version)
.subscribe(dto => {
this.updateContributors(this.appContributors.removeContributor(contributor, dto.version));
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
});
}
public changePermission(contributor: AppContributorDto, permission: string) {
const requestDto = contributor.changePermission(permission);
this.appNameOnce()
.switchMap(app => this.appContributorsService.postContributor(app, requestDto, this.appContributors.version))
this.appContributorsService.postContributor(this.ctx.appName, requestDto, this.appContributors.version)
.subscribe(dto => {
this.updateContributors(this.appContributors.updateContributor(contributor, dto.version));
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
});
}
public assignContributor() {
const requestDto = new AppContributorDto(this.addContributorForm.controls['user'].value.id, 'Editor');
this.appNameOnce()
.switchMap(app => this.appContributorsService.postContributor(app, requestDto, this.appContributors.version))
this.appContributorsService.postContributor(this.ctx.appName, requestDto, this.appContributors.version)
.subscribe(dto => {
this.updateContributors(this.appContributors.addContributor(requestDto, dto.version));
this.resetContributorForm();
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
this.resetContributorForm();
});
}
@ -147,6 +136,6 @@ export class ContributorsPageComponent extends AppComponentBase implements OnIni
private updateContributors(appContributors: AppContributorsDto) {
this.appContributors = appContributors;
this.messageBus.emit(new HistoryChannelUpdated());
this.ctx.bus.emit(new HistoryChannelUpdated());
}
}

4
src/Squidex/app/features/settings/pages/languages/languages-page.component.html

@ -1,6 +1,6 @@
<sqx-title message="{app} | Languages | Settings" parameter1="app" value1="{{appName() | async}}"></sqx-title>
<sqx-title message="{app} | Languages | Settings" parameter1="app" [value1]="ctx.appName"></sqx-title>
<sqx-panel panelWidth="42rem">
<sqx-panel desiredWidth="42rem">
<div class="panel-header">
<div class="panel-title-row">
<h3 class="panel-title">Languages</h3>

43
src/Squidex/app/features/settings/pages/languages/languages-page.component.ts

@ -10,16 +10,12 @@ import { FormBuilder, Validators } from '@angular/forms';
import {
AddAppLanguageDto,
AppComponentBase,
AppContext,
AppLanguageDto,
AppLanguagesDto,
AppLanguagesService,
AppsStoreService,
AuthService,
DialogService,
HistoryChannelUpdated,
ImmutableArray,
MessageBus,
LanguageDto,
LanguagesService
} from 'shared';
@ -27,9 +23,12 @@ import {
@Component({
selector: 'sqx-languages-page',
styleUrls: ['./languages-page.component.scss'],
templateUrl: './languages-page.component.html'
templateUrl: './languages-page.component.html',
providers: [
AppContext
]
})
export class LanguagesPageComponent extends AppComponentBase implements OnInit {
export class LanguagesPageComponent implements OnInit {
public allLanguages: LanguageDto[] = [];
public newLanguages: LanguageDto[] = [];
public appLanguages: AppLanguagesDto;
@ -41,13 +40,11 @@ export class LanguagesPageComponent extends AppComponentBase implements OnInit {
]
});
constructor(apps: AppsStoreService, dialogs: DialogService, authService: AuthService,
constructor(public readonly ctx: AppContext,
private readonly appLanguagesService: AppLanguagesService,
private readonly languagesService: LanguagesService,
private readonly messageBus: MessageBus,
private readonly formBuilder: FormBuilder
) {
super(dialogs, apps, authService);
}
public ngOnInit() {
@ -56,55 +53,51 @@ export class LanguagesPageComponent extends AppComponentBase implements OnInit {
}
public load() {
this.appNameOnce()
.switchMap(app => this.appLanguagesService.getLanguages(app).retry(2))
this.appLanguagesService.getLanguages(this.ctx.appName)
.subscribe(dto => {
this.updateLanguages(dto);
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
});
}
public removeLanguage(language: AppLanguageDto) {
this.appNameOnce()
.switchMap(app => this.appLanguagesService.deleteLanguage(app, language.iso2Code, this.appLanguages.version))
this.appLanguagesService.deleteLanguage(this.ctx.appName, language.iso2Code, this.appLanguages.version)
.subscribe(dto => {
this.updateLanguages(this.appLanguages.removeLanguage(language, dto.version));
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
});
}
public addLanguage() {
const requestDto = new AddAppLanguageDto(this.addLanguageForm.controls['language'].value.iso2Code);
this.appNameOnce()
.switchMap(app => this.appLanguagesService.postLanguages(app, requestDto, this.appLanguages.version))
this.appLanguagesService.postLanguages(this.ctx.appName, requestDto, this.appLanguages.version)
.subscribe(dto => {
this.updateLanguages(this.appLanguages.addLanguage(dto.payload, dto.version));
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
});
}
public updateLanguage(language: AppLanguageDto) {
this.appNameOnce()
.switchMap(app => this.appLanguagesService.updateLanguage(app, language.iso2Code, language, this.appLanguages.version))
this.appLanguagesService.updateLanguage(this.ctx.appName, language.iso2Code, language, this.appLanguages.version)
.subscribe(dto => {
this.updateLanguages(this.appLanguages.updateLanguage(language, dto.version));
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
});
}
private loadAllLanguages() {
this.languagesService.getLanguages().retry(2)
this.languagesService.getLanguages()
.subscribe(languages => {
this.allLanguages = ImmutableArray.of(languages).sortByStringAsc(l => l.englishName).values;
this.updateNewLanguages();
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
});
}
@ -132,7 +125,7 @@ export class LanguagesPageComponent extends AppComponentBase implements OnInit {
this.updateNewLanguages();
this.messageBus.emit(new HistoryChannelUpdated());
this.ctx.bus.emit(new HistoryChannelUpdated());
}
private updateNewLanguages() {

4
src/Squidex/app/features/settings/pages/plans/plans-page.component.html

@ -1,6 +1,6 @@
<sqx-title message="{app} | Plans | Settings" parameter1="app" value1="{{appName() | async}}"></sqx-title>
<sqx-title message="{app} | Plans | Settings" parameter1="app" [value1]="ctx.appName"></sqx-title>
<sqx-panel panelWidth="61rem">
<sqx-panel desiredWidth="61rem">
<div class="panel-header">
<div class="panel-title-row">
<div class="float-right">

33
src/Squidex/app/features/settings/pages/plans/plans-page.component.ts

@ -6,26 +6,25 @@
*/
import { Component, OnDestroy, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Subscription } from 'rxjs';
import {
ApiUrlConfig,
AppComponentBase,
AppContext,
AppPlansDto,
AppsStoreService,
AuthService,
ChangePlanDto,
DialogService,
PlansService
} from 'shared';
@Component({
selector: 'sqx-plans-page',
styleUrls: ['./plans-page.component.scss'],
templateUrl: './plans-page.component.html'
templateUrl: './plans-page.component.html',
providers: [
AppContext
]
})
export class PlansPageComponent extends AppComponentBase implements OnDestroy, OnInit {
export class PlansPageComponent implements OnDestroy, OnInit {
private queryParamsSubscription: Subscription;
private overridePlanId: string;
@ -36,12 +35,10 @@ export class PlansPageComponent extends AppComponentBase implements OnDestroy, O
public isDisabled = false;
constructor(apps: AppsStoreService, dialogs: DialogService, authService: AuthService,
constructor(public readonly ctx: AppContext,
private readonly plansService: PlansService,
private readonly route: ActivatedRoute,
private readonly apiUrl: ApiUrlConfig
) {
super(dialogs, apps, authService);
}
public ngOnDestroy() {
@ -50,7 +47,7 @@ export class PlansPageComponent extends AppComponentBase implements OnDestroy, O
public ngOnInit() {
this.queryParamsSubscription =
this.route.queryParams.subscribe(params => {
this.ctx.route.queryParams.subscribe(params => {
this.overridePlanId = params['planId'];
});
@ -58,8 +55,7 @@ export class PlansPageComponent extends AppComponentBase implements OnDestroy, O
}
public load(showInfo = false) {
this.appNameOnce()
.switchMap(app => this.plansService.getPlans(app).retry(2))
this.plansService.getPlans(this.ctx.appName)
.subscribe(dto => {
if (this.overridePlanId) {
this.plans = dto.changePlanId(this.overridePlanId);
@ -67,21 +63,20 @@ export class PlansPageComponent extends AppComponentBase implements OnDestroy, O
this.plans = dto;
}
this.planOwned = !dto.planOwner || (dto.planOwner === this.authService.user!.id);
this.planOwned = !dto.planOwner || (dto.planOwner === this.ctx.userId);
if (showInfo) {
this.notifyInfo('Plans reloaded.');
this.ctx.notifyInfo('Plans reloaded.');
}
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
});
}
public changePlan(planId: string) {
this.isDisabled = true;
this.appNameOnce()
.switchMap(app => this.plansService.putPlan(app, new ChangePlanDto(planId), this.plans.version))
this.plansService.putPlan(this.ctx.appName, new ChangePlanDto(planId), this.plans.version)
.subscribe(dto => {
if (dto.payload.redirectUri && dto.payload.redirectUri.length > 0) {
window.location.href = dto.payload.redirectUri;
@ -90,7 +85,7 @@ export class PlansPageComponent extends AppComponentBase implements OnDestroy, O
this.isDisabled = false;
}
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
this.isDisabled = false;
});
}

4
src/Squidex/app/features/settings/settings-area.component.html

@ -1,6 +1,6 @@
<sqx-title message="{app} | Settings" parameter1="app" value1="{{appName() | async}}"></sqx-title>
<sqx-title message="{app} | Settings" parameter1="app" [value1]="ctx.appName"></sqx-title>
<sqx-panel theme="dark" panelWidth="16rem">
<sqx-panel theme="dark" desiredWidth="16rem">
<div class="panel-header">
<div class="panel-title-row">
<h3 class="panel-title">Settings</h3>

17
src/Squidex/app/features/settings/settings-area.component.ts

@ -7,21 +7,18 @@
import { Component } from '@angular/core';
import {
AppComponentBase,
AppsStoreService,
AuthService,
DialogService
} from 'shared';
import { AppContext } from 'shared';
@Component({
selector: 'sqx-settings-area',
styleUrls: ['./settings-area.component.scss'],
templateUrl: './settings-area.component.html'
templateUrl: './settings-area.component.html',
providers: [
AppContext
]
})
export class SettingsAreaComponent extends AppComponentBase {
constructor(apps: AppsStoreService, dialogs: DialogService, authService: AuthService
export class SettingsAreaComponent {
constructor(public readonly ctx: AppContext
) {
super(dialogs, apps, authService);
}
}

10
src/Squidex/app/framework/angular/panel-container.directive.ts

@ -67,12 +67,12 @@ export class PanelContainerDirective implements AfterViewInit, OnDestroy {
for (let panel of this.panels) {
const panelRoot = panel.panel.nativeElement;
let width = panel.clientWidth;
let width = panel.desiredWidth;
if (panel.expand && panel === last) {
width = this.containerWidth - currentPosition;
if (panel.desiredWidth === '*' && panel === last) {
panel.actualWidth = this.containerWidth - currentPosition;
panel.panelWidth = width + 'px';
panel.desiredWidth = width + 'px';
}
this.renderer.setElementStyle(panelRoot, 'top', '0px');
@ -81,7 +81,7 @@ export class PanelContainerDirective implements AfterViewInit, OnDestroy {
this.renderer.setElementStyle(panelRoot, 'position', 'absolute');
this.renderer.setElementStyle(panelRoot, 'z-index', currentLayer.toString());
currentPosition += width;
currentPosition += panel.actualWidth;
currentLayer -= 10;
}

12
src/Squidex/app/framework/angular/panel.component.ts

@ -14,7 +14,7 @@ import { PanelContainerDirective } from './panel-container.directive';
@Component({
selector: 'sqx-panel',
template: `
<div [style.width]="panelWidth" [attr.expand]="expand" #panel>
<div [style.width]="desiredWidth" [attr.expand]="expand" #panel>
<div class="panel panel-{{theme}}" [@slideRight]>
<ng-content></ng-content>
</div>
@ -24,13 +24,13 @@ import { PanelContainerDirective } from './panel-container.directive';
]
})
export class PanelComponent implements AfterViewInit, OnDestroy, OnInit {
private clientWidthValue = 0;
public actualWidth = 0;
@Input()
public theme = 'light';
@Input()
public panelWidth = '10rem';
public desiredWidth = '10rem';
@Input()
public expand = false;
@ -38,10 +38,6 @@ export class PanelComponent implements AfterViewInit, OnDestroy, OnInit {
@ViewChild('panel')
public panel: ElementRef;
public get clientWidth() {
return this.clientWidthValue;
}
constructor(
private readonly container: PanelContainerDirective
) {
@ -56,7 +52,7 @@ export class PanelComponent implements AfterViewInit, OnDestroy, OnInit {
}
public ngAfterViewInit() {
this.clientWidthValue = this.panel.nativeElement.getBoundingClientRect().width;
this.actualWidth = this.panel.nativeElement.getBoundingClientRect().width;
this.container.invalidate();
}

2
src/Squidex/app/framework/utils/math-helper.ts

@ -8,6 +8,8 @@
/* tslint:disable: no-bitwise */
export module MathHelper {
export const EMPTY_GUID = '00000000-0000-0000-0000-000000000000';
const CRC32_TABLE: number[] = [];
function ensureCrc32Table() {

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

@ -0,0 +1,74 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { ActivatedRoute } from '@angular/router';
import { MessageBus } from 'framework';
import {
AppDto,
AuthService,
DialogService,
ErrorDto,
Notification,
Profile
} from './../declarations-base';
@Injectable()
export class AppContext {
private appField: AppDto;
public get app(): AppDto {
return this.appField;
}
public get appName(): string {
return this.appField.name;
}
public get userToken(): string {
return this.authService.user!.token;
}
public get userId(): string {
return this.authService.user!.id;
}
public get user(): Profile {
return this.authService.user!;
}
constructor(
public readonly dialogs: DialogService,
public readonly authService: AuthService,
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.appField = app;
});
}
public confirmUnsavedChanges(): Observable<boolean> {
return this.dialogs.confirmUnsavedChanges();
}
public notifyInfo(error: string) {
this.dialogs.notify(Notification.info(error));
}
public notifyError(error: string | ErrorDto) {
if (error instanceof ErrorDto) {
this.dialogs.notify(Notification.error(error.displayMessage));
} else {
this.dialogs.notify(Notification.error(error));
}
}
}

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

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

42
src/Squidex/app/shared/components/app.component-base.ts

@ -1,42 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Observable } from 'rxjs';
import {
AppsStoreService,
AuthService,
DialogService
} from './../declarations-base';
import { ComponentBase } from './component-base';
export abstract class AppComponentBase extends ComponentBase {
private appName$: Observable<string>;
public get userToken(): string {
return this.authService.user!.token;
}
constructor(dialogs: DialogService,
protected readonly appsStore: AppsStoreService,
protected readonly authService: AuthService
) {
super(dialogs);
this.appName$ = this.appsStore.selectedApp.filter(a => !!a).map(a => a!.name);
}
public appName(): Observable<string> {
return this.appName$;
}
public appNameOnce(): Observable<string> {
return this.appName$.first();
}
}

35
src/Squidex/app/shared/components/asset.component.ts

@ -8,15 +8,12 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { FormBuilder, Validators } from '@angular/forms';
import { AppComponentBase } from './app.component-base';
import { AppContext } from './app-context';
import {
AppsStoreService,
AssetDto,
AssetsService,
AuthService,
DateTime,
DialogService,
fadeAnimation,
ModalView,
UpdateAssetDto,
@ -28,11 +25,14 @@ import {
selector: 'sqx-asset',
styleUrls: ['./asset.component.scss'],
templateUrl: './asset.component.html',
providers: [
AppContext
],
animations: [
fadeAnimation
]
})
export class AssetComponent extends AppComponentBase implements OnInit {
export class AssetComponent implements OnInit {
private assetVersion: Version;
@Input()
@ -71,19 +71,17 @@ export class AssetComponent extends AppComponentBase implements OnInit {
public progress = 0;
constructor(apps: AppsStoreService, dialogs: DialogService, authService: AuthService,
constructor(public readonly ctx: AppContext,
private readonly formBuilder: FormBuilder,
private readonly assetsService: AssetsService
) {
super(dialogs, apps, authService);
}
public ngOnInit() {
const initFile = this.initFile;
if (initFile) {
this.appNameOnce()
.switchMap(app => this.assetsService.uploadFile(app, initFile, this.userToken, DateTime.now()))
this.assetsService.uploadFile(this.ctx.appName, initFile, this.ctx.userToken, DateTime.now())
.subscribe(dto => {
if (dto instanceof AssetDto) {
this.emitLoaded(dto);
@ -91,7 +89,8 @@ export class AssetComponent extends AppComponentBase implements OnInit {
this.progress = dto;
}
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
this.emitFailed(error);
});
} else {
@ -101,16 +100,16 @@ export class AssetComponent extends AppComponentBase implements OnInit {
public updateFile(files: FileList) {
if (files.length === 1) {
this.appNameOnce()
.switchMap(app => this.assetsService.replaceFile(app, this.asset.id, files[0], this.assetVersion))
this.assetsService.replaceFile(this.ctx.appName, this.asset.id, files[0], this.assetVersion)
.subscribe(dto => {
if (dto instanceof Versioned) {
this.updateAsset(this.asset.update(dto.payload, this.userToken, dto.version), true);
this.updateAsset(this.asset.update(dto.payload, this.ctx.userToken, dto.version), true);
} else {
this.setProgress(dto);
}
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
this.setProgress();
});
}
@ -124,13 +123,13 @@ export class AssetComponent extends AppComponentBase implements OnInit {
const requestDto = new UpdateAssetDto(this.renameForm.controls['name'].value);
this.appNameOnce()
.switchMap(app => this.assetsService.putAsset(app, this.asset.id, requestDto, this.assetVersion))
this.assetsService.putAsset(this.ctx.appName, this.asset.id, requestDto, this.assetVersion)
.subscribe(dto => {
this.updateAsset(this.asset.rename(requestDto.fileName, this.userToken, dto.version), true);
this.updateAsset(this.asset.rename(requestDto.fileName, this.ctx.userToken, dto.version), true);
this.resetRenameForm();
}, error => {
this.notifyError(error);
this.ctx.notifyError(error);
this.enableRenameForm();
});
}

31
src/Squidex/app/shared/components/component-base.ts

@ -1,31 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import {
DialogService,
ErrorDto,
Notification
} from './../declarations-base';
export abstract class ComponentBase {
constructor(
public readonly dialogs: DialogService
) {
}
protected notifyError(error: string | ErrorDto) {
if (error instanceof ErrorDto) {
this.dialogs.notify(Notification.error(error.displayMessage));
} else {
this.dialogs.notify(Notification.error(error));
}
}
protected notifyInfo(error: string) {
this.dialogs.notify(Notification.info(error));
}
}

2
src/Squidex/app/shared/components/help.component.html

@ -1,4 +1,4 @@
<sqx-panel panelWidth="16rem">
<sqx-panel desiredWidth="16rem">
<div class="panel-header">
<div class="panel-title-row">
<h3 class="panel-title">Help</h3>

2
src/Squidex/app/shared/components/history.component.html

@ -1,4 +1,4 @@
<sqx-panel panelWidth="16rem">
<sqx-panel desiredWidth="16rem">
<div class="panel-header">
<div class="panel-title-row">
<h3 class="panel-title">Activity</h3>

31
src/Squidex/app/shared/components/history.component.ts

@ -6,20 +6,15 @@
*/
import { Component } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Observable } from 'rxjs';
import { AppComponentBase } from './app.component-base';
import { AppContext } from './app-context';
import {
allParams,
AppsStoreService,
AuthService,
DialogService,
HistoryChannelUpdated,
HistoryEventDto,
HistoryService,
MessageBus,
UsersProviderService
} from './../declarations-base';
@ -28,14 +23,17 @@ const REPLACEMENT_TEMP = '$TEMP$';
@Component({
selector: 'sqx-history',
styleUrls: ['./history.component.scss'],
templateUrl: './history.component.html'
templateUrl: './history.component.html',
providers: [
AppContext
]
})
export class HistoryComponent extends AppComponentBase {
export class HistoryComponent {
public get channel(): string {
let channelPath = this.route.snapshot.data['channel'];
let channelPath = this.ctx.route.snapshot.data['channel'];
if (channelPath) {
const params = allParams(this.route);
const params = allParams(this.ctx.route);
for (let key in params) {
if (params.hasOwnProperty(key)) {
@ -50,18 +48,13 @@ export class HistoryComponent extends AppComponentBase {
}
public events: Observable<HistoryEventDto[]> =
Observable.timer(0, 10000)
.merge(this.messageBus.of(HistoryChannelUpdated).delay(1000))
.switchMap(() => this.appNameOnce())
.switchMap(app => this.historyService.getHistory(app, this.channel).retry(2));
Observable.timer(0, 10000).merge(this.ctx.bus.of(HistoryChannelUpdated).delay(1000))
.switchMap(app => this.historyService.getHistory(this.ctx.appName, this.channel));
constructor(appsStore: AppsStoreService, dialogs: DialogService, authService: AuthService,
constructor(public readonly ctx: AppContext,
private readonly users: UsersProviderService,
private readonly historyService: HistoryService,
private readonly messageBus: MessageBus,
private readonly route: ActivatedRoute
private readonly historyService: HistoryService
) {
super(dialogs, appsStore, authService);
}
private userName(userId: string): Observable<string> {

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

@ -5,10 +5,10 @@
* 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';
@ -19,7 +19,6 @@ 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';

3
src/Squidex/app/shared/declarations.ts

@ -5,10 +5,9 @@
* Copyright (c) Sebastian Stehle. All rights reserved
*/
export * from './components/app.component-base';
export * from './components/app-context';
export * from './components/app-form.component';
export * from './components/asset.component';
export * from './components/component-base';
export * from './components/help.component';
export * from './components/history.component';
export * from './components/language-selector.component';

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

@ -1,73 +0,0 @@
/*
* 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

@ -1,40 +0,0 @@
/*
* 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

@ -0,0 +1,45 @@
/*
* 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);
});
}
}

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

@ -16,9 +16,7 @@ import {
AppClientsService,
AppContributorsService,
AppLanguagesService,
AppsStoreService,
AppsService,
AppMustExistGuard,
AssetComponent,
AssetPreviewUrlPipe,
AssetsService,
@ -38,6 +36,7 @@ import {
MustBeAuthenticatedGuard,
MustBeNotAuthenticatedGuard,
PlansService,
ResolveAppGuard,
ResolveAppLanguagesGuard,
ResolveContentGuard,
ResolvePublishedSchemaGuard,
@ -110,9 +109,7 @@ export class SqxSharedModule {
AppClientsService,
AppContributorsService,
AppLanguagesService,
AppsStoreService,
AppsService,
AppMustExistGuard,
AssetsService,
AuthService,
ContentsService,
@ -124,6 +121,7 @@ export class SqxSharedModule {
MustBeAuthenticatedGuard,
MustBeNotAuthenticatedGuard,
PlansService,
ResolveAppGuard,
ResolveAppLanguagesGuard,
ResolveContentGuard,
ResolvePublishedSchemaGuard,

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

@ -1,104 +0,0 @@
/*
* 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)];
const newApp = new AppDto('id', 'new-name', 'Owner', now, now);
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

@ -1,70 +0,0 @@
/*
* 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]));
});
});
}
}

56
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 } from 'rxjs';
import { Observable, ReplaySubject } from 'rxjs';
import 'framework/angular/http-extensions';
@ -38,6 +38,8 @@ export class CreateAppDto {
@Injectable()
export class AppsService {
private apps$: ReplaySubject<AppDto[]> | null = null;
constructor(
private readonly http: HttpClient,
private readonly apiUrl: ApiUrlConfig,
@ -46,24 +48,38 @@ export class AppsService {
}
public getApps(): Observable<AppDto[]> {
const url = this.apiUrl.buildUrl('/api/apps');
if (this.apps$ === null) {
const url = this.apiUrl.buildUrl('/api/apps');
return HTTP.getVersioned<any>(this.http, url)
.map(response => {
const body = response.payload.body;
const loadedApps =
HTTP.getVersioned<any>(this.http, url)
.map(response => {
const body = response.payload.body;
const items: any[] = 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.');
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);
loadedApps
.subscribe(apps => {
this.apps$.next(apps);
}, error => {
this.apps$.error(loadedApps);
});
}
return this.apps$;
}
public postApp(dto: CreateAppDto, now?: DateTime): Observable<AppDto> {
@ -77,8 +93,14 @@ export class AppsService {
return new AppDto(body.id, dto.name, 'Owner', now, now);
})
.do(() => {
.do(app => {
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.');
}

2
src/Squidex/app/shared/services/users-provider.service.ts

@ -27,7 +27,7 @@ export class UsersProviderService {
if (!result) {
const request =
this.usersService.getUser(id).retry(2)
this.usersService.getUser(id)
.catch(error => {
return Observable.of(new UserDto('NOT FOUND', 'NOT FOUND', 'NOT FOUND', null, false));
})

12
src/Squidex/app/shell/pages/app/left-menu.component.html

@ -1,31 +1,31 @@
<div class="sidebar">
<ul class="nav flex-column">
<li class="nav-item" *ngIf="permission !== 'Editor'">
<li class="nav-item" *ngIf="ctx.app.permission !== 'Editor'">
<a class="nav-link" routerLink="schemas" routerLinkActive="active">
<i class="nav-icon icon-schemas"></i> <div class="nav-text">Schemas</div>
</a>
</li>
<li class="nav-item" *ngIf="permission">
<li class="nav-item" *ngIf="ctx.app">
<a class="nav-link" routerLink="content" routerLinkActive="active">
<i class="nav-icon icon-contents"></i> <div class="nav-text">Content</div>
</a>
</li>
<li class="nav-item" *ngIf="permission">
<li class="nav-item" *ngIf="ctx.app">
<a class="nav-link" routerLink="assets" routerLinkActive="active">
<i class="nav-icon icon-assets"></i> <div class="nav-text">Assets</div>
</a>
</li>
<li class="nav-item" *ngIf="permission !== 'Editor'">
<li class="nav-item" *ngIf="ctx.app.permission !== 'Editor'">
<a class="nav-link" routerLink="rules" routerLinkActive="active">
<i class="nav-icon icon-rules"></i> <div class="nav-text">Rules</div>
</a>
</li>
<li class="nav-item" *ngIf="permission === 'Owner'">
<li class="nav-item" *ngIf="ctx.app.permission === 'Owner'">
<a class="nav-link" routerLink="settings" routerLinkActive="active">
<i class="nav-icon icon-settings"></i> <div class="nav-text">Settings</div>
</a>
</li>
<li class="nav-item" *ngIf="permission !== 'Editor'">
<li class="nav-item" *ngIf="ctx.app.permission !== 'Editor'">
<a class="nav-link" routerLink="api" routerLinkActive="active">
<i class="nav-icon icon-api"></i> <div class="nav-text">API</div>
</a>

32
src/Squidex/app/shell/pages/app/left-menu.component.ts

@ -5,36 +5,20 @@
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Subscription } from 'rxjs';
import { Component } from '@angular/core';
import { AppsStoreService } from 'shared';
import { AppContext } from 'shared';
@Component({
selector: 'sqx-left-menu',
styleUrls: ['./left-menu.component.scss'],
templateUrl: './left-menu.component.html'
templateUrl: './left-menu.component.html',
providers: [
AppContext
]
})
export class LeftMenuComponent implements OnDestroy, OnInit {
private appSubscription: Subscription;
public permission: string | null = null;
constructor(
private readonly appsStore: AppsStoreService
export class LeftMenuComponent {
constructor(public readonly ctx: AppContext
) {
}
public ngOnDestroy() {
this.appSubscription.unsubscribe();
}
public ngOnInit() {
this.appSubscription =
this.appsStore.selectedApp.subscribe(app => {
if (app) {
this.permission = app.permission;
}
});
}
}

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

@ -1,6 +1,6 @@
<ul class="nav navbar-nav" *ngIf="apps">
<li class="nav-item dropdown">
<span class="nav-link dropdown-toggle" id="app-name" (click)="modalMenu.toggle()">{{appName}}</span>
<span class="nav-link dropdown-toggle" id="app-name" (click)="modalMenu.toggle()">{{ctx.app ? ctx.app.name : 'Apps Overview'}}</span>
<div class="dropdown-menu" *sqxModalView="modalMenu" closeAlways="true" @fade>
<a class="dropdown-item all-apps" routerLink="/app">

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

@ -57,6 +57,6 @@ $color-apps-border: #65a6ff;
}
&-pill {
@include absolute(.8rem, .625rem, auto, auto);
@include absolute(.5rem, .625rem, auto, auto);
}
}

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

@ -9,18 +9,20 @@ import { Component, OnDestroy, OnInit } from '@angular/core';
import { Subscription } from 'rxjs';
import {
AppContext,
AppDto,
AppsStoreService,
AppsService,
fadeAnimation,
ModalView
} from 'shared';
const FALLBACK_NAME = 'Apps Overview';
@Component({
selector: 'sqx-apps-menu',
styleUrls: ['./apps-menu.component.scss'],
templateUrl: './apps-menu.component.html',
providers: [
AppContext
],
animations: [
fadeAnimation
]
@ -34,10 +36,8 @@ export class AppsMenuComponent implements OnDestroy, OnInit {
public apps: AppDto[] = [];
public appName = FALLBACK_NAME;
constructor(
private readonly appsStore: AppsStoreService
constructor(public readonly ctx: AppContext,
private readonly appsService: AppsService
) {
}
@ -48,12 +48,9 @@ export class AppsMenuComponent implements OnDestroy, OnInit {
public ngOnInit() {
this.appsSubscription =
this.appsStore.apps.subscribe(apps => {
this.appsService.getApps().subscribe(apps => {
this.apps = apps;
});
this.appSubscription =
this.appsStore.selectedApp.subscribe(selectedApp => this.appName = selectedApp ? selectedApp.name : FALLBACK_NAME);
}
public createApp() {

56
src/Squidex/package.json

@ -15,36 +15,36 @@
"build:clean": "rimraf wwwroot/build"
},
"dependencies": {
"@angular/animations": "4.4.4",
"@angular/common": "4.4.4",
"@angular/compiler": "4.4.4",
"@angular/core": "4.4.4",
"@angular/forms": "4.4.4",
"@angular/http": "4.4.4",
"@angular/platform-browser": "4.4.4",
"@angular/platform-browser-dynamic": "4.4.4",
"@angular/platform-server": "4.4.4",
"@angular/router": "4.4.4",
"@angular/animations": "4.4.6",
"@angular/common": "4.4.6",
"@angular/compiler": "4.4.6",
"@angular/core": "4.4.6",
"@angular/forms": "4.4.6",
"@angular/http": "4.4.6",
"@angular/platform-browser": "4.4.6",
"@angular/platform-browser-dynamic": "4.4.6",
"@angular/platform-server": "4.4.6",
"@angular/router": "4.4.6",
"angular2-chartjs": "0.3.0",
"babel-polyfill": "6.26.0",
"bootstrap": "4.0.0-alpha.6",
"core-js": "2.5.1",
"graphiql": "0.11.5",
"moment": "2.18.1",
"graphiql": "0.11.10",
"moment": "2.19.1",
"mousetrap": "1.6.1",
"ng2-dnd": "4.2.0",
"oidc-client": "1.4.1",
"pikaday": "1.6.1",
"progressbar.js": "1.0.1",
"react": "15.6.2",
"react-dom": "15.6.2",
"react": "16.0.0",
"react-dom": "16.0.0",
"redoc": "1.19.1",
"rxjs": "5.4.3",
"rxjs": "5.5.2",
"zone.js": "0.8.18"
},
"devDependencies": {
"@angular/compiler-cli": "4.4.4",
"@angular/tsc-wrapped": "4.4.4",
"@angular/compiler-cli": "4.4.6",
"@angular/tsc-wrapped": "4.4.6",
"@ngtools/webpack": "1.7.2",
"@types/core-js": "0.9.35",
"@types/jasmine": "2.5.45",
@ -54,12 +54,12 @@
"@types/react-dom": "15.5.6",
"angular2-router-loader": "0.3.5",
"angular2-template-loader": "0.6.2",
"awesome-typescript-loader": "3.2.3",
"codelyzer": "^3.1.2",
"awesome-typescript-loader": "3.3.0",
"codelyzer": "4.0.1",
"cpx": "1.5.0",
"css-loader": "0.28.7",
"exports-loader": "0.6.4",
"extract-text-webpack-plugin": "3.0.1",
"extract-text-webpack-plugin": "3.0.2",
"file-loader": "1.1.5",
"html-loader": "0.5.1",
"html-webpack-plugin": "2.30.1",
@ -73,26 +73,26 @@
"karma-htmlfile-reporter": "0.3.5",
"karma-jasmine": "1.1.0",
"karma-jasmine-html-reporter": "^0.2.2",
"karma-mocha-reporter": "2.2.4",
"karma-mocha-reporter": "2.2.5",
"karma-phantomjs-launcher": "1.0.4",
"karma-sourcemap-loader": "0.3.7",
"karma-webpack": "2.0.5",
"node-sass": "4.5.3",
"noop-loader": "^1.0.0",
"null-loader": "0.1.1",
"phantomjs-prebuilt": "2.1.15",
"phantomjs-prebuilt": "2.1.16",
"raw-loader": "0.5.1",
"rimraf": "2.6.2",
"sass-lint": "1.12.0",
"sass-lint": "1.12.1",
"sass-loader": "6.0.6",
"style-loader": "0.19.0",
"tslint": "5.7.0",
"tslint": "5.8.0",
"tslint-loader": "3.5.3",
"typemoq": "2.0.1",
"typemoq": "2.1.0",
"typescript": "2.5.3",
"underscore": "1.8.3",
"webpack": "3.6.0",
"webpack-dev-server": "2.9.1",
"webpack-merge": "4.1.0"
"webpack": "3.8.1",
"webpack-dev-server": "2.9.4",
"webpack-merge": "4.1.1"
}
}

130
tests/Squidex.Domain.Apps.Read.Tests/Apps/ConfigAppLimitsProviderTests.cs

@ -16,26 +16,35 @@ namespace Squidex.Domain.Apps.Read.Apps
{
public class ConfigAppLimitsProviderTests
{
private static readonly ConfigAppLimitsPlan[] Plans =
private static readonly ConfigAppLimitsPlan InfinitePlan = new ConfigAppLimitsPlan
{
new ConfigAppLimitsPlan
{
Id = "basic",
Name = "Basic",
MaxApiCalls = 150000,
MaxAssetSize = 1024 * 1024 * 2,
MaxContributors = 5
},
new ConfigAppLimitsPlan
{
Id = "free",
Name = "Free",
MaxApiCalls = 50000,
MaxAssetSize = 1024 * 1024 * 10,
MaxContributors = 2
}
Id = "infinite",
Name = "Infinite",
MaxApiCalls = -1,
MaxAssetSize = -1,
MaxContributors = -1
};
private static readonly ConfigAppLimitsPlan FreePlan = new ConfigAppLimitsPlan
{
Id = "free",
Name = "Free",
MaxApiCalls = 50000,
MaxAssetSize = 1024 * 1024 * 10,
MaxContributors = 2
};
private static readonly ConfigAppLimitsPlan BasicPlan = new ConfigAppLimitsPlan
{
Id = "basic",
Name = "Basic",
MaxApiCalls = 150000,
MaxAssetSize = 1024 * 1024 * 2,
MaxContributors = 5
};
private static readonly ConfigAppLimitsPlan[] Plans = { BasicPlan, FreePlan };
[Fact]
public void Should_return_plans()
{
@ -53,61 +62,80 @@ namespace Squidex.Domain.Apps.Read.Apps
var plan = sut.GetPlanForApp(CreateApp(planId));
plan.ShouldBeEquivalentTo(new ConfigAppLimitsPlan
{
Id = "infinite",
Name = "Infinite",
MaxApiCalls = -1,
MaxAssetSize = -1,
MaxContributors = -1
});
plan.ShouldBeEquivalentTo(InfinitePlan);
}
[Fact]
public void Should_check_plan_exists()
public void Should_return_fitting_app_plan()
{
var sut = new ConfigAppPlansProvider(Plans);
Assert.True(sut.IsConfiguredPlan("basic"));
Assert.True(sut.IsConfiguredPlan("free"));
var plan = sut.GetPlanForApp(CreateApp("basic"));
Assert.False(sut.IsConfiguredPlan("infinite"));
Assert.False(sut.IsConfiguredPlan("invalid"));
Assert.False(sut.IsConfiguredPlan(null));
plan.ShouldBeEquivalentTo(BasicPlan);
}
[Fact]
public void Should_return_fitting_app_plan()
public void Should_smallest_plan_if_none_fits()
{
var sut = new ConfigAppPlansProvider(Plans);
var plan = sut.GetPlanForApp(CreateApp("basic"));
var plan = sut.GetPlanForApp(CreateApp("enterprise"));
plan.ShouldBeEquivalentTo(new ConfigAppLimitsPlan
{
Id = "basic",
Name = "Basic",
MaxApiCalls = 150000,
MaxAssetSize = 1024 * 1024 * 2,
MaxContributors = 5
});
plan.ShouldBeEquivalentTo(FreePlan);
}
[Fact]
public void Should_smallest_plan_if_none_fits()
public void Should_return_second_plan_for_upgrade_if_plan_is_null()
{
var sut = new ConfigAppPlansProvider(Plans);
var plan = sut.GetPlanForApp(CreateApp("Enterprise"));
var upgradePlan = sut.GetPlanUpgrade(null);
upgradePlan.ShouldBeEquivalentTo(BasicPlan);
}
plan.ShouldBeEquivalentTo(new ConfigAppLimitsPlan
{
Id = "free",
Name = "Free",
MaxApiCalls = 50000,
MaxAssetSize = 1024 * 1024 * 10,
MaxContributors = 2
});
[Fact]
public void Should_return_second_plan_for_upgrade_if_plan_not_found()
{
var sut = new ConfigAppPlansProvider(Plans);
var upgradePlan = sut.GetPlanUpgradeForApp(CreateApp("enterprise"));
upgradePlan.ShouldBeEquivalentTo(BasicPlan);
}
[Fact]
public void Should_not_return_plan_for_upgrade_if_plan_is_highest_plan()
{
var sut = new ConfigAppPlansProvider(Plans);
var upgradePlan = sut.GetPlanUpgradeForApp(CreateApp("basic"));
Assert.Null(upgradePlan);
}
[Fact]
public void Should_return_next_plan_if_plan_is_upgradeable()
{
var sut = new ConfigAppPlansProvider(Plans);
var upgradePlan = sut.GetPlanUpgradeForApp(CreateApp("free"));
upgradePlan.ShouldBeEquivalentTo(BasicPlan);
}
[Fact]
public void Should_check_plan_exists()
{
var sut = new ConfigAppPlansProvider(Plans);
Assert.True(sut.IsConfiguredPlan("basic"));
Assert.True(sut.IsConfiguredPlan("free"));
Assert.False(sut.IsConfiguredPlan("infinite"));
Assert.False(sut.IsConfiguredPlan("invalid"));
Assert.False(sut.IsConfiguredPlan(null));
}
private static IAppEntity CreateApp(string plan)

Loading…
Cancel
Save