Browse Source

Cleanup

pull/1/head
Sebastian 9 years ago
parent
commit
6896ff93cb
  1. 6
      src/Squidex/Controllers/Api/Schemas/Models/UpdateFieldDto.cs
  2. 5
      src/Squidex/Controllers/Api/Schemas/SchemasController.cs
  3. 2
      src/Squidex/Pipeline/WebpackMiddleware.cs
  4. 14
      src/Squidex/app/features/apps/pages/apps-page.component.html
  5. 22
      src/Squidex/app/features/apps/pages/apps-page.component.ts
  6. 1
      src/Squidex/app/features/schemas/declarations.ts
  7. 2
      src/Squidex/app/features/schemas/module.ts
  8. 1
      src/Squidex/app/features/schemas/pages/messages.ts
  9. 40
      src/Squidex/app/features/schemas/pages/schema/schema-edit-form.component.html
  10. 6
      src/Squidex/app/features/schemas/pages/schema/schema-edit-form.component.scss
  11. 92
      src/Squidex/app/features/schemas/pages/schema/schema-edit-form.component.ts
  12. 29
      src/Squidex/app/features/schemas/pages/schema/schema-page.component.html
  13. 12
      src/Squidex/app/features/schemas/pages/schema/schema-page.component.scss
  14. 38
      src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts
  15. 15
      src/Squidex/app/features/schemas/pages/schema/schema-properties.ts
  16. 11
      src/Squidex/app/features/schemas/pages/schema/types/boolean-ui.component.ts
  17. 8
      src/Squidex/app/features/schemas/pages/schema/types/number-ui.component.ts
  18. 8
      src/Squidex/app/features/schemas/pages/schema/types/string-ui.component.ts
  19. 10
      src/Squidex/app/features/schemas/pages/schema/types/string-validation.component.ts
  20. 4
      src/Squidex/app/features/schemas/pages/schemas/schema-form.component.html
  21. 40
      src/Squidex/app/features/schemas/pages/schemas/schema-form.component.ts
  22. 12
      src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.html
  23. 62
      src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.ts
  24. 4
      src/Squidex/app/features/settings/pages/clients/client.component.html
  25. 4
      src/Squidex/app/features/settings/pages/clients/client.component.ts
  26. 2
      src/Squidex/app/features/settings/pages/clients/clients-page.component.ts
  27. 2
      src/Squidex/app/features/settings/pages/contributors/contributors-page.component.html
  28. 8
      src/Squidex/app/framework/angular/autocomplete.component.ts
  29. 17
      src/Squidex/app/framework/angular/http-extensions.ts
  30. 16
      src/Squidex/app/framework/angular/http-utils.ts
  31. 53
      src/Squidex/app/framework/angular/name.pipe.spec.ts
  32. 23
      src/Squidex/app/framework/angular/name.pipe.ts
  33. 8
      src/Squidex/app/framework/angular/panel-container.directive.ts
  34. 8
      src/Squidex/app/framework/angular/panel.directive.ts
  35. 11
      src/Squidex/app/framework/angular/shortcut.component.ts
  36. 12
      src/Squidex/app/framework/angular/slider.component.ts
  37. 4
      src/Squidex/app/framework/declarations.ts
  38. 3
      src/Squidex/app/framework/module.ts
  39. 4
      src/Squidex/app/framework/services/notification.service.ts
  40. 15
      src/Squidex/app/framework/utils/immutable-array.spec.ts
  41. 41
      src/Squidex/app/framework/utils/string-helper.spec.ts
  42. 22
      src/Squidex/app/framework/utils/string-helper.ts
  43. 4
      src/Squidex/app/shared/components/app-form.component.html
  44. 22
      src/Squidex/app/shared/components/app-form.component.ts
  45. 10
      src/Squidex/app/shared/components/dashboard-link.directive.ts
  46. 6
      src/Squidex/app/shared/services/app-clients.service.spec.ts
  47. 22
      src/Squidex/app/shared/services/app-clients.service.ts
  48. 10
      src/Squidex/app/shared/services/app-contributors.service.ts
  49. 12
      src/Squidex/app/shared/services/app-languages.service.ts
  50. 4
      src/Squidex/app/shared/services/apps-store.service.spec.ts
  51. 2
      src/Squidex/app/shared/services/apps-store.service.ts
  52. 12
      src/Squidex/app/shared/services/apps.service.spec.ts
  53. 17
      src/Squidex/app/shared/services/apps.service.ts
  54. 4
      src/Squidex/app/shared/services/auth.service.ts
  55. 10
      src/Squidex/app/shared/services/history.service.ts
  56. 6
      src/Squidex/app/shared/services/languages.service.ts
  57. 69
      src/Squidex/app/shared/services/schemas.service.ts
  58. 8
      src/Squidex/app/shell/pages/app/left-menu.component.ts
  59. 10
      src/Squidex/app/shell/pages/internal/apps-menu.component.ts
  60. 8
      src/Squidex/app/shell/pages/internal/internal-area.component.ts
  61. 8
      src/Squidex/app/shell/pages/internal/profile-menu.component.ts
  62. 11
      src/Squidex/app/theme/_bootstrap.scss
  63. 2
      src/Squidex/app/theme/icomoon/fonts/icomoon.svg

6
src/Squidex/Controllers/Api/Schemas/Models/UpdateFieldDto.cs

@ -6,10 +6,16 @@
// All rights reserved.
// ==========================================================================
using System.ComponentModel.DataAnnotations;
namespace Squidex.Controllers.Api.Schemas.Models
{
public class UpdateFieldDto
{
/// <summary>
/// The field properties.
/// </summary>
[Required]
public FieldPropertiesDto Properties { get; set; }
}
}

5
src/Squidex/Controllers/Api/Schemas/SchemasController.cs

@ -16,6 +16,7 @@ using Squidex.Infrastructure.CQRS.Commands;
using Squidex.Infrastructure.Reflection;
using Squidex.Controllers.Api.Schemas.Models;
using Squidex.Controllers.Api.Schemas.Models.Converters;
using Squidex.Core.Schemas;
using Squidex.Pipeline;
using Squidex.Read.Schemas.Repositories;
using Squidex.Write.Schemas.Commands;
@ -119,9 +120,9 @@ namespace Squidex.Controllers.Api.Schemas
[Route("apps/{app}/schemas/{name}/")]
public async Task<IActionResult> PutSchema(string app, string name, [FromBody] UpdateSchemaDto request)
{
var command = SimpleMapper.Map(request, new UpdateSchema());
var properties = SimpleMapper.Map(request, new SchemaProperties());
await CommandBus.PublishAsync(command);
await CommandBus.PublishAsync(new UpdateSchema { Properties = properties });
return NoContent();
}

2
src/Squidex/Pipeline/WebpackMiddleware.cs

@ -89,7 +89,7 @@ namespace Squidex.Pipeline
foreach (var file in Styles)
{
stylesTag += $"<link href=\"http://{Host}:{Port}/{file}\" rel=\"stylesheet\" >";
stylesTag += $"<link href=\"http://{Host}:{Port}/{file}\" rel=\"stylesheet\">";
}
response = response.Replace("</head>", $"{stylesTag}</head>");

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

@ -1,14 +1,14 @@
<sqx-title message="Apps"></sqx-title>
<content>
<div class="empty" *ngIf="apps && apps.length === 0">
<div class="empty" *ngIf="(apps | async)?.length === 0">
<h3 class="empty-headline">You are not collaborating to any app yet</h3>
<button class="apps-empty-button btn btn-success" (click)="modalDialog.show()"><i class="icon-plus"></i> Create New App</button>
<button class="apps-empty-button btn btn-success" (click)="addAppDialog.show()"><i class="icon-plus"></i> Create New App</button>
</div>
<div class="apps">
<div class="app card" *ngFor="let app of apps">
<div class="app card" *ngFor="let app of (apps | async)">
<div class="card-block">
<h4 class="card-title">{{app.name}}</h4>
@ -18,12 +18,12 @@
</div>
</content>
<div class="modal" *sqxModalView="modalDialog" [@fade]>
<div class="modal" *sqxModalView="addAppDialog" [@fade]>
<div class="modal-backdrop"></div>
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close" (click)="modalDialog.hide()">
<button type="button" class="close" data-dismiss="modal" aria-label="Close" (click)="addAppDialog.hide()">
<span aria-hidden="true">&times;</span>
</button>
@ -32,8 +32,8 @@
<div class="modal-body">
<sqx-app-form
(created)="modalDialog.hide()"
(cancelled)="modalDialog.hide()"></sqx-app-form>
(created)="addAppDialog.hide()"
(cancelled)="addAppDialog.hide()"></sqx-app-form>
</div>
</div>
</div>

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

@ -5,11 +5,9 @@
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Subscription } from 'rxjs';
import { Component, OnInit } from '@angular/core';
import {
AppDto,
AppsStoreService,
fadeAnimation,
ModalView
@ -23,12 +21,11 @@ import {
fadeAnimation
]
})
export class AppsPageComponent implements OnInit, OnDestroy {
private appsSubscription: Subscription;
export class AppsPageComponent implements OnInit {
public addAppDialog = new ModalView();
public modalDialog = new ModalView();
public apps: AppDto[];
public apps =
this.appsStore.apps.map(a => a || []);
constructor(
private readonly appsStore: AppsStoreService
@ -37,14 +34,5 @@ export class AppsPageComponent implements OnInit, OnDestroy {
public ngOnInit() {
this.appsStore.selectApp(null);
this.appsSubscription =
this.appsStore.apps.subscribe(apps => {
this.apps = apps || [];
});
}
public ngOnDestroy() {
this.appsSubscription.unsubscribe();
}
}

1
src/Squidex/app/features/schemas/declarations.ts

@ -12,6 +12,7 @@ export * from './pages/schema/types/number-validation.component';
export * from './pages/schema/types/string-ui.component';
export * from './pages/schema/types/string-validation.component';
export * from './pages/schema/field.component';
export * from './pages/schema/schema-edit-form.component';
export * from './pages/schema/schema-page.component';
export * from './pages/schemas/schema-form.component';
export * from './pages/schemas/schemas-page.component';

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

@ -20,6 +20,7 @@ import {
BooleanValidationComponent,
NumberUIComponent,
NumberValidationComponent,
SchemaEditFormComponent,
SchemaFormComponent,
SchemaPageComponent,
SchemasPageComponent,
@ -63,6 +64,7 @@ const routes: Routes = [
BooleanValidationComponent,
NumberUIComponent,
NumberValidationComponent,
SchemaEditFormComponent,
SchemaFormComponent,
SchemaPageComponent,
SchemasPageComponent,

1
src/Squidex/app/features/schemas/pages/messages.ts

@ -8,6 +8,7 @@
export class SchemaUpdated {
constructor(
public readonly name: string,
public readonly label: string,
public readonly isPublished: boolean
) {
}

40
src/Squidex/app/features/schemas/pages/schema/schema-edit-form.component.html

@ -0,0 +1,40 @@
<form [formGroup]="editForm" (ngSubmit)="saveSchema()">
<div class="form-group">
<label for="schema-name">Name</label>
<input type="text" class="form-control" id="schema-name" formControlName="name" readonly />
</div>
<div class="form-group">
<label for="schema-label">Label</label>
<div class="errors-box" *ngIf="editForm.controls.label.invalid && editForm.controls.label.touched" [@fade]>
<div class="errors">
<span *ngIf="editForm.controls.label.hasError('maxlength')">
Label can not have more than 100 characters.
</span>
</div>
</div>
<input type="text" class="form-control" id="schema-label" formControlName="label" />
</div>
<div class="form-group">
<label for="schema-hints">Hints</label>
<div class="errors-box" *ngIf="editForm.controls.hints.invalid && editForm.controls.hints.touched" [@fade]>
<div class="errors">
<span *ngIf="editForm.controls.hints.hasError('maxlength')">
Hints can not have more than 1000 characters.
</span>
</div>
</div>
<textarea type="text" class="form-control" id="schema-hints" formControlName="hints" rows="4"></textarea>
</div>
<div class="form-group clearfix">
<button type="reset" class="float-xs-left btn btn-secondary" (click)="cancel()">Cancel</button>
<button type="submit" class="float-xs-right btn btn-primary">Save</button>
</div>
</form>

6
src/Squidex/app/features/schemas/pages/schema/schema-edit-form.component.scss

@ -0,0 +1,6 @@
@import '_vars';
@import '_mixins';
texarea {
resize: none;
}

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

@ -0,0 +1,92 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import {
fadeAnimation,
Notification,
NotificationService,
SchemasService
} from 'shared';
import { SchemaPropertiesDto } from './schema-properties';
@Component({
selector: 'sqx-schema-edit-form',
styleUrls: ['./schema-edit-form.component.scss'],
templateUrl: './schema-edit-form.component.html',
animations: [
fadeAnimation
]
})
export class SchemaEditFormComponent implements OnInit {
@Output()
public saved = new EventEmitter<SchemaPropertiesDto>();
@Output()
public cancelled = new EventEmitter();
@Input()
public schema: SchemaPropertiesDto;
@Input()
public appName: string;
public editForm: FormGroup =
this.formBuilder.group({
name: '',
label: ['',
[
Validators.maxLength(100)
]],
hints: ['',
[
Validators.maxLength(1000)
]]
});
constructor(
private readonly schemas: SchemasService,
private readonly formBuilder: FormBuilder,
private readonly notifications: NotificationService
) {
}
public ngOnInit() {
this.editForm.patchValue(this.schema);
}
public saveSchema() {
this.editForm.markAsTouched();
if (this.editForm.valid) {
this.editForm.disable();
const requestDto = this.editForm.value;
this.schemas.putSchema(this.appName, this.schema.name, requestDto)
.subscribe(dto => {
this.reset();
this.saved.emit(new SchemaPropertiesDto(this.schema.name, requestDto.label, requestDto.hints));
}, error => {
this.editForm.enable();
this.notifications.notify(Notification.error(error.displayMessage));
});
}
}
public reset() {
this.editForm.reset();
}
public cancel() {
this.reset();
this.cancelled.emit();
}
}

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

@ -14,8 +14,10 @@
</div>
</div>
<h3 class="panel-title">{{schemaName}}</h3>
<h3 class="panel-title">
{{schemaProperties|displayName}} <i class="schema-edit icon-pencil" (click)="editSchemaDialog.show()"></i>
</h3>
<a class="panel-close" routerLink="../">
<i class="icon-close"></i>
</a>
@ -75,4 +77,27 @@
</div>
</div>
<div class="modal" *sqxModalView="editSchemaDialog" [@fade]>
<div class="modal-backdrop"></div>
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close" (click)="editSchemaDialog.hide()">
<span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title">Edit Schema</h4>
</div>
<div class="modal-body">
<sqx-schema-edit-form
[appName]="appName() | async"
[schema]="schemaProperties"
(saved)="onSchemaSaved($event)"
(cancelled)="editSchemaDialog.hide()"></sqx-schema-edit-form>
</div>
</div>
</div>
</div>
<router-outlet></router-outlet>

12
src/Squidex/app/features/schemas/pages/schema/schema-page.component.scss

@ -15,4 +15,16 @@
&:disabled {
@include opacity(1);
}
}
.schema {
&-edit {
color: $color-border-dark;
font-size: .9rem;
font-weight: normal;
padding: .6rem .3rem;
border: 0;
background: transparent;
vertical-align: baseline;
}
}

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

@ -20,12 +20,14 @@ import {
HistoryChannelUpdated,
ImmutableArray,
MessageBus,
ModalView,
NotificationService,
SchemasService,
UpdateFieldDto,
UsersProviderService
} from 'shared';
import { SchemaPropertiesDto } from './schema-properties';
import { SchemaUpdated } from './../messages';
@Component({
@ -47,6 +49,9 @@ export class SchemaPageComponent extends AppComponentBase implements OnDestroy,
public schemaName: string;
public schemaFields = ImmutableArray.empty<FieldDto>();
public schemaProperties: SchemaPropertiesDto;
public editSchemaDialog = new ModalView();
public isPublished: boolean;
@ -92,6 +97,7 @@ export class SchemaPageComponent extends AppComponentBase implements OnDestroy,
.switchMap(app => this.schemasService.getSchema(app, this.schemaName)).retry(2)
.subscribe(dto => {
this.schemaFields = ImmutableArray.of(dto.fields);
this.schemaProperties = new SchemaPropertiesDto(dto.name, dto.label, dto.hints);
this.isPublished = dto.isPublished;
}, error => {
this.notifyError(error);
@ -103,7 +109,7 @@ export class SchemaPageComponent extends AppComponentBase implements OnDestroy,
.switchMap(app => this.schemasService.publishSchema(app, this.schemaName)).retry(2)
.subscribe(() => {
this.isPublished = true;
this.updateAll(this.schemaFields);
this.notify();
}, error => {
this.notifyError(error);
});
@ -114,7 +120,7 @@ export class SchemaPageComponent extends AppComponentBase implements OnDestroy,
.switchMap(app => this.schemasService.unpublishSchema(app, this.schemaName)).retry(2)
.subscribe(() => {
this.isPublished = false;
this.updateAll(this.schemaFields);
this.notify();
}, error => {
this.notifyError(error);
});
@ -164,7 +170,7 @@ export class SchemaPageComponent extends AppComponentBase implements OnDestroy,
this.appName()
.switchMap(app => this.schemasService.deleteField(app, this.schemaName, field.fieldId)).retry(2)
.subscribe(() => {
this.updateAll(this.schemaFields.remove(field));
this.updateFields(this.schemaFields.remove(field));
}, error => {
this.notifyError(error);
});
@ -207,7 +213,7 @@ export class SchemaPageComponent extends AppComponentBase implements OnDestroy,
false,
properties);
this.updateAll(this.schemaFields.push(newField));
this.updateFields(this.schemaFields.push(newField));
reset();
}, error => {
this.notifyError(error);
@ -220,15 +226,33 @@ export class SchemaPageComponent extends AppComponentBase implements OnDestroy,
this.schemaFields = ImmutableArray.empty<FieldDto>();
}
public onSchemaSaved(properties: SchemaPropertiesDto) {
this.updateProperties(properties);
this.editSchemaDialog.hide();
}
public updateProperties(properties: SchemaPropertiesDto) {
this.schemaProperties = properties;
this.notify();
}
public updateField(field: FieldDto, newField: FieldDto) {
this.updateAll(this.schemaFields.replace(field, newField));
this.schemaFields = this.schemaFields.replace(field, newField);
this.notify();
}
private updateAll(fields: ImmutableArray<FieldDto>) {
private updateFields(fields: ImmutableArray<FieldDto>) {
this.schemaFields = fields;
this.notify();
}
private notify() {
this.messageBus.publish(new HistoryChannelUpdated());
this.messageBus.publish(new SchemaUpdated(this.schemaName, this.isPublished));
this.messageBus.publish(new SchemaUpdated(this.schemaName, this.schemaProperties.label, this.isPublished));
}
}

15
src/Squidex/app/features/schemas/pages/schema/schema-properties.ts

@ -0,0 +1,15 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
export class SchemaPropertiesDto {
constructor(
public readonly name: string,
public readonly label: string,
public readonly hints: string
) {
}
}

11
src/Squidex/app/features/schemas/pages/schema/types/boolean-ui.component.ts

@ -7,13 +7,8 @@
import { Component, Input, OnInit } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { Observable } from 'rxjs';
import {
fadeAnimation,
FloatConverter,
BooleanFieldPropertiesDto
} from 'shared';
import { fadeAnimation, BooleanFieldPropertiesDto } from 'shared';
@Component({
selector: 'sqx-boolean-ui',
@ -30,10 +25,6 @@ export class BooleanUIComponent implements OnInit {
@Input()
public properties: BooleanFieldPropertiesDto;
public converter = new FloatConverter();
public hideAllowedValues: Observable<boolean>;
public ngOnInit() {
this.editForm.addControl('editor',
new FormControl(this.properties.editor, [

8
src/Squidex/app/features/schemas/pages/schema/types/number-ui.component.ts

@ -36,6 +36,10 @@ export class NumberUIComponent implements OnDestroy, OnInit {
public hideAllowedValues: Observable<boolean>;
public ngOnDestroy() {
this.editorSubscription.unsubscribe();
}
public ngOnInit() {
this.editForm.addControl('editor',
new FormControl(this.properties.editor, [
@ -60,8 +64,4 @@ export class NumberUIComponent implements OnDestroy, OnInit {
}
});
}
public ngOnDestroy() {
this.editorSubscription.unsubscribe();
}
}

8
src/Squidex/app/features/schemas/pages/schema/types/string-ui.component.ts

@ -30,6 +30,10 @@ export class StringUIComponent implements OnDestroy, OnInit {
public hideAllowedValues: Observable<boolean>;
public ngOnDestroy() {
this.editorSubscription.unsubscribe();
}
public ngOnInit() {
this.editForm.setControl('editor',
new FormControl(this.properties.editor, [
@ -56,8 +60,4 @@ export class StringUIComponent implements OnDestroy, OnInit {
}
});
}
public ngOnDestroy() {
this.editorSubscription.unsubscribe();
}
}

10
src/Squidex/app/features/schemas/pages/schema/types/string-validation.component.ts

@ -25,8 +25,12 @@ export class StringValidationComponent implements OnDestroy, OnInit {
@Input()
public properties: StringFieldPropertiesDto;
public hidePatternMessage: Observable<boolean>;
public hideDefaultValue: Observable<boolean>;
public hidePatternMessage: Observable<boolean>;
public ngOnDestroy() {
this.patternSubscription.unsubscribe();
}
public ngOnInit() {
this.editForm.setControl('maxLength',
@ -61,8 +65,4 @@ export class StringValidationComponent implements OnDestroy, OnInit {
}
});
}
public ngOnDestroy() {
this.patternSubscription.unsubscribe();
}
}

4
src/Squidex/app/features/schemas/pages/schemas/schema-form.component.html

@ -26,7 +26,7 @@
<span class="form-hint">
<p>
The schema name becomes part of the api url,<br /> e.g https://{{appName}}.squidex.io/<b>{{schemaName}}</b>/.
The schema name becomes part of the api url,<br /> e.g https://{{appName}}.squidex.io/<b>{{schemaName | async}}</b>/.
</p>
<p>
It must contain lower case letters (a-z), numbers and dashes only, and cannot be longer than 40 characters. The name cannot be changed later.
@ -36,6 +36,6 @@
<div class="form-group clearfix">
<button type="reset" class="float-xs-left btn btn-secondary" (click)="cancel()">Cancel</button>
<button type="submit" class="float-xs-right btn btn-primary" >Create</button>
<button type="submit" class="float-xs-right btn btn-success">Create</button>
</div>
</form>

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

@ -5,8 +5,9 @@
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Observable } from 'rxjs';
import {
AuthService,
@ -27,10 +28,7 @@ const FALLBACK_NAME = 'my-schema';
fadeAnimation
]
})
export class SchemaFormComponent implements OnInit {
@Input()
public showClose = false;
export class SchemaFormComponent {
@Input()
public appName: string;
@ -51,7 +49,9 @@ export class SchemaFormComponent implements OnInit {
]]
});
public schemaName = FALLBACK_NAME;
public schemaName =
Observable.of(FALLBACK_NAME)
.merge(this.createForm.get('name').valueChanges.map(n => n || FALLBACK_NAME));
constructor(
private readonly schemas: SchemasService,
@ -60,43 +60,41 @@ export class SchemaFormComponent implements OnInit {
) {
}
public ngOnInit() {
this.createForm.get('name').valueChanges.subscribe(value => {
this.schemaName = value || FALLBACK_NAME;
});
}
public createSchema() {
this.createForm.markAsTouched();
if (this.createForm.valid && this.authService.user) {
if (this.createForm.valid) {
this.createForm.disable();
const name = this.createForm.get('name').value;
const now = DateTime.now();
const requestDto = new CreateSchemaDto(name);
const me = `subject:${this.authService.user.id}`;
this.schemas.postSchema(this.appName, requestDto)
.subscribe(dto => {
this.createForm.reset();
this.created.emit(new SchemaDto(dto.id, name, now, now, me, me, false));
}, error => {
this.reset();
this.created.emit(this.createSchemaDto(dto.id, name));
}, error => {
this.createForm.enable();
this.creationError = error.displayMessage;
});
}
}
private reset() {
this.createForm.enable();
public reset() {
this.creationError = '';
this.createForm.reset();
}
public cancel() {
this.reset();
this.cancelled.emit();
}
private createSchemaDto(id: string, name: string) {
const user = this.authService.user!.token;
const now = DateTime.now();
return new SchemaDto(id, name, undefined, false, user, user, now, now);
}
}

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

@ -10,7 +10,7 @@
</a>
</div>
<div class="subheader">
<button class="btn btn-success subheader-button" (click)="modalDialog.show()">
<button class="btn btn-success subheader-button" (click)="addSchemaDialog.show()">
<i class="icon-plus"></i>
</button>
@ -29,7 +29,7 @@
<div class="schema-inner">
<div class="row">
<div class="col-xs-4">
<span class="schema-name">{{schema.name}}</span>
<span class="schema-name">{{schema|displayName}}</span>
</div>
<div class="col-xs-4">
<span class="schema-user">
@ -49,12 +49,12 @@
</div>
</div>
<div class="modal" *sqxModalView="modalDialog" [@fade]>
<div class="modal" *sqxModalView="addSchemaDialog" [@fade]>
<div class="modal-backdrop"></div>
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close" (click)="modalDialog.hide()">
<button type="button" class="close" data-dismiss="modal" aria-label="Close" (click)="addSchemaDialog.hide()">
<span aria-hidden="true">&times;</span>
</button>
@ -64,8 +64,8 @@
<div class="modal-body">
<sqx-schema-form
[appName]="appName() | async"
(created)="onSchemaCreationCompleted($event)"
(cancelled)="modalDialog.hide()"></sqx-schema-form>
(created)="onSchemaCreated($event)"
(cancelled)="addSchemaDialog.hide()"></sqx-schema-form>
</div>
</div>
</div>

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

@ -37,31 +37,33 @@ import { SchemaUpdated } from './../messages';
export class SchemasPageComponent extends AppComponentBase implements OnDestroy, OnInit {
private messageSubscription: Subscription;
public modalDialog = new ModalView();
public addSchemaDialog = new ModalView();
public schemas = new BehaviorSubject(ImmutableArray.empty<SchemaDto>());
public schemasFilter = new FormControl();
public schemasFiltered =
Observable.of(null).merge(this.schemasFilter.valueChanges.debounceTime(100)).combineLatest(this.schemas,
(query, schemas) => {
if (query && query.length > 0) {
schemas = schemas.filter(t => t.name.indexOf(query) >= 0);
}
schemas =
schemas.sort((a, b) => {
if (a.name < b.name) {
return -1;
}
if (a.name > b.name) {
return 1;
}
return 0;
});
return schemas;
});
Observable.of(null)
.merge(this.schemasFilter.valueChanges.debounceTime(100))
.combineLatest(this.schemas,
(query, schemas) => {
if (query && query.length > 0) {
schemas = schemas.filter(t => t.name.indexOf(query) >= 0);
}
schemas =
schemas.sort((a, b) => {
if (a.name < b.name) {
return -1;
}
if (a.name > b.name) {
return 1;
}
return 0;
});
return schemas;
});
constructor(apps: AppsStoreService, notifications: NotificationService, users: UsersProviderService,
private readonly schemasService: SchemasService,
@ -71,6 +73,10 @@ export class SchemasPageComponent extends AppComponentBase implements OnDestroy,
super(apps, notifications, users);
}
public ngOnDestroy() {
this.messageSubscription.unsubscribe();
}
public ngOnInit() {
this.load();
@ -86,19 +92,15 @@ export class SchemasPageComponent extends AppComponentBase implements OnDestroy,
new SchemaDto(
oldSchema.id,
oldSchema.name,
oldSchema.created,
DateTime.now(),
message.label,
message.isPublished,
oldSchema.createdBy, me,
message.isPublished);
oldSchema.created, DateTime.now());
this.schemas.next(schemas.replace(oldSchema, newSchema));
}
});
}
public ngOnDestroy() {
this.messageSubscription.unsubscribe();
}
public load() {
this.appName()
.switchMap(app => this.schemasService.getSchemas(app).retry(2))
@ -109,10 +111,10 @@ export class SchemasPageComponent extends AppComponentBase implements OnDestroy,
});
}
public onSchemaCreationCompleted(dto: SchemaDto) {
public onSchemaCreated(dto: SchemaDto) {
this.schemas.next(this.schemas.getValue().push(dto));
this.modalDialog.hide();
this.addSchemaDialog.hide();
}
}

4
src/Squidex/app/features/settings/pages/clients/client.component.html

@ -75,12 +75,12 @@
</table>
</div>
<div class="modal" *sqxModalView="modalDialog" [@fade]>
<div class="modal" *sqxModalView="tokenDialog" [@fade]>
<div class="modal-backdrop"></div>
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close" (click)="modalDialog.hide()">
<button type="button" class="close" data-dismiss="modal" aria-label="Close" (click)="tokenDialog.hide()">
<span aria-hidden="true">&times;</span>
</button>

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

@ -45,7 +45,7 @@ export class ClientComponent {
@Input()
public appName: string;
public modalDialog = new ModalView();
public tokenDialog = new ModalView();
public get clientName(): string {
return this.client.name || this.client.id;
@ -109,7 +109,7 @@ export class ClientComponent {
this.appClientsService.createToken(this.appName, client)
.subscribe(token => {
this.appClientToken = token;
this.modalDialog.show();
this.tokenDialog.show();
}, _ => {
this.notifications.notify(Notification.error('Failed to retrieve access token. Please retry.'));
});

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

@ -22,7 +22,7 @@ import {
UsersProviderService
} from 'shared';
function rename(client: AppClientDto, name: string) {
function rename(client: AppClientDto, name: string): AppClientDto {
return new AppClientDto(client.id, client.secret, name, client.expiresUtc);
};

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

@ -66,7 +66,7 @@
</table>
<div class="table-items-footer">
<form class="form-inline" [formGroup]="addContributorForm" (ngSubmit)="assignContributor()" >
<form class="form-inline" [formGroup]="addContributorForm" (ngSubmit)="assignContributor()">
<div class="form-group">
<sqx-autocomplete [source]="usersDataSource" formControlName="user" [inputName]="contributor"></sqx-autocomplete>
</div>

8
src/Squidex/app/framework/angular/autocomplete.component.ts

@ -96,6 +96,10 @@ export class AutocompleteComponent implements ControlValueAccessor, OnDestroy, O
this.touchedCallback = fn;
}
public ngOnDestroy() {
this.subscription.unsubscribe();
}
public ngOnInit() {
this.subscription =
this.queryInput.valueChanges
@ -116,10 +120,6 @@ export class AutocompleteComponent implements ControlValueAccessor, OnDestroy, O
});
}
public ngOnDestroy() {
this.subscription.unsubscribe();
}
public onBlur() {
this.touchedCallback();
}

17
src/Squidex/app/framework/angular/http-extensions.ts

@ -0,0 +1,17 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Observable } from 'rxjs/Observable';
import { catchError } from './http-utils';
declare module 'rxjs/Observable' {
interface Observable<T> {
catchError(message: string): Observable<any>;
}
}
Observable.prototype['catchError'] = catchError;

16
src/Squidex/app/framework/angular/http-utils.ts

@ -49,14 +49,16 @@ export class ErrorDto {
}
}
export function handleError(message: string, error: Response | any): Observable<any> {
let result = new ErrorDto(500, message);
export function catchError(message: string): Observable<any> {
return this.catch((error: any | Response) => {
let result = new ErrorDto(500, message);
if (error instanceof Response && error.status !== 500) {
const body = error.json();
if (error instanceof Response && error.status !== 500) {
const body = error.json();
result = new ErrorDto(error.status, body.message, body.details);
}
result = new ErrorDto(error.status, body.message, body.details);
}
return Observable.throw(result);
return Observable.throw(result);
});
}

53
src/Squidex/app/framework/angular/name.pipe.spec.ts

@ -0,0 +1,53 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { DisplayNamePipe } from './name.pipe';
describe('DisplayNamePipe', () => {
it('should return empty text if value is null or undefined', () => {
const pipe = new DisplayNamePipe();
expect(pipe.transform(null)).toBe('');
expect(pipe.transform(undefined)).toBe('');
});
it('should return label if value is valid', () => {
const pipe = new DisplayNamePipe();
expect(pipe.transform({ label: 'name' })).toBe('name');
});
it('should return trimmed label if value is valid', () => {
const pipe = new DisplayNamePipe();
expect(pipe.transform({ label: ' name ' })).toBe('name');
});
it('should return fallback name if label is empty', () => {
const pipe = new DisplayNamePipe();
expect(pipe.transform({ label: ' ', name: 'fallback' })).toBe('fallback');
});
it('should return fallback name if label is undefined', () => {
const pipe = new DisplayNamePipe();
expect(pipe.transform({ name: 'fallback' })).toBe('fallback');
});
it('should return trimmed fallback name if label is undefined', () => {
const pipe = new DisplayNamePipe();
expect(pipe.transform({ name: ' fallback ' })).toBe('fallback');
});
it('should return empty string if also fallback not found', () => {
const pipe = new DisplayNamePipe();
expect(pipe.transform({ })).toBe('');
});
});

23
src/Squidex/app/framework/angular/name.pipe.ts

@ -0,0 +1,23 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Pipe } from '@angular/core';
import { StringHelper } from './../utils/string-helper';
@Pipe({
name: 'displayName'
})
export class DisplayNamePipe {
public transform(value: any, field1 = 'label', field2 = 'name'): any {
if (!value) {
return '';
}
return StringHelper.firstNonEmpty(value[field1], value[field2]);
}
}

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

@ -28,6 +28,10 @@ export class PanelContainerDirective implements OnInit, OnDestroy {
this.resize();
}
public ngOnDestroy() {
this.subscription.unsubscribe();
}
public ngOnInit() {
this.subscription =
this.panels.changed.subscribe(width => {
@ -37,10 +41,6 @@ export class PanelContainerDirective implements OnInit, OnDestroy {
});
}
public ngOnDestroy() {
this.subscription.unsubscribe();
}
private resize() {
if (!this.panelsSize) {
return;

8
src/Squidex/app/framework/angular/panel.directive.ts

@ -20,11 +20,11 @@ export class PanelDirective implements OnInit, OnDestroy {
) {
}
public ngOnInit() {
this.panels.push(this.element.nativeElement, this.renderer);
}
public ngOnDestroy() {
this.panels.pop(this.element.nativeElement, this.renderer);
}
public ngOnInit() {
this.panels.push(this.element.nativeElement, this.renderer);
}
}

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

@ -31,6 +31,12 @@ export class ShortcutComponent implements OnInit, OnDestroy {
) {
}
public ngOnDestroy() {
if (this.lastKeys) {
this.shortcutService.off(this.lastKeys);
}
}
public ngOnInit() {
this.lastKeys = this.keys;
@ -46,9 +52,4 @@ export class ShortcutComponent implements OnInit, OnDestroy {
});
}
}
public ngOnDestroy() {
if (this.lastKeys) {
this.shortcutService.off(this.lastKeys);
}
}
}

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

@ -72,9 +72,9 @@ export class SliderComponent implements ControlValueAccessor {
this.touchedCallback = fn;
}
public onBarMouseClick(event: MouseEvent) {
public onBarMouseClick(event: MouseEvent): boolean {
if (this.mouseMoveSubscription) {
return;
return true;
}
const relativeValue = this.getRelativeX(event);
@ -88,7 +88,7 @@ export class SliderComponent implements ControlValueAccessor {
return false;
}
public onThumbMouseDown(event: MouseEvent) {
public onThumbMouseDown(event: MouseEvent): boolean {
this.centerStartOffset = event.offsetX - this.thumb.nativeElement.clientWidth * 0.5;
this.startValue = this.value;
@ -110,9 +110,9 @@ export class SliderComponent implements ControlValueAccessor {
return false;
}
private onMouseMove(event: MouseEvent) {
private onMouseMove(event: MouseEvent): boolean {
if (!this.isDragging) {
return;
return true;
}
const relativeValue = this.getRelativeX(event);
@ -125,7 +125,7 @@ export class SliderComponent implements ControlValueAccessor {
return false;
}
private onMouseUp() {
private onMouseUp(): boolean {
this.updateValue();
setTimeout(() => {

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

@ -16,6 +16,7 @@ export * from './angular/focus-on-init.directive';
export * from './angular/http-utils';
export * from './angular/modal-view.directive';
export * from './angular/money.pipe';
export * from './angular/name.pipe';
export * from './angular/panel.directive';
export * from './angular/panel-container.directive';
export * from './angular/scroll-active.directive';
@ -41,4 +42,5 @@ export * from './utils/date-time';
export * from './utils/duration';
export * from './utils/immutable-array';
export * from './utils/math-helper';
export * from './utils/modal-view';
export * from './utils/modal-view';
export * from './utils/string-helper';

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

@ -17,6 +17,7 @@ import {
CopyDirective,
DayOfWeekPipe,
DayPipe,
DisplayNamePipe,
DurationPipe,
FocusOnChangeDirective,
FocusOnInitDirective,
@ -50,6 +51,7 @@ import {
CopyDirective,
DayOfWeekPipe,
DayPipe,
DisplayNamePipe,
DurationPipe,
FocusOnChangeDirective,
FocusOnInitDirective,
@ -74,6 +76,7 @@ import {
CopyDirective,
DayOfWeekPipe,
DayPipe,
DisplayNamePipe,
DurationPipe,
FocusOnChangeDirective,
FocusOnInitDirective,

4
src/Squidex/app/framework/services/notification.service.ts

@ -20,11 +20,11 @@ export class Notification {
) {
}
public static error(message: string) {
public static error(message: string): Notification {
return new Notification(message, 'error');
}
public static info(message: string) {
public static info(message: string): Notification {
return new Notification(message, 'info');
}
}

15
src/Squidex/app/framework/utils/immutable-array.spec.ts

@ -138,4 +138,17 @@ describe('ImmutableArray', () => {
expect(array_2.values).toEqual([1, 2, 3, 4]);
});
});
it('should iterate over array items', () => {
const array_1 = ImmutableArray.of([3, 1, 4, 2]);
const values: number[] = [];
for (let iter = array_1[Symbol.iterator](), _step: any; !(_step = iter.next()).done; ) {
values.push(_step.value);
}
expect(values).toEqual([3, 1, 4, 2]);
});
});

41
src/Squidex/app/framework/utils/string-helper.spec.ts

@ -0,0 +1,41 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { StringHelper } from './../';
describe('StringHelper', () => {
it('should return empty text if value is null or undefined', () => {
expect(StringHelper.firstNonEmpty(null)).toBe('');
expect(StringHelper.firstNonEmpty(undefined)).toBe('');
});
it('should return fallback name if label is undefined or null', () => {
expect(StringHelper.firstNonEmpty(null, 'fallback')).toBe('fallback');
expect(StringHelper.firstNonEmpty(undefined, 'fallback')).toBe('fallback');
});
it('should return label if value is valid', () => {
expect(StringHelper.firstNonEmpty('name')).toBe('name');
});
it('should return trimmed label if value is valid', () => {
expect(StringHelper.firstNonEmpty(' name ')).toBe('name');
});
it('should return fallback name if label is empty', () => {
expect(StringHelper.firstNonEmpty('', 'fallback')).toBe('fallback');
});
it('should return trimmed fallback name if label is undefined', () => {
expect(StringHelper.firstNonEmpty('', ' fallback ')).toBe('fallback');
});
it('should return empty string if also fallback not found', () => {
expect(StringHelper.firstNonEmpty(null, undefined, '')).toBe('');
});
});

22
src/Squidex/app/framework/utils/string-helper.ts

@ -0,0 +1,22 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
export module StringHelper {
export function firstNonEmpty(...values: string[]) {
for (let value of values) {
if (value) {
value = value.trim();
if (value.length > 0) {
return value;
}
}
}
return '';
}
}

4
src/Squidex/app/shared/components/app-form.component.html

@ -26,7 +26,7 @@
<span class="form-hint">
<p>
The app name becomes part of the api url,<br /> e.g https://<b>{{appName}}</b>.squidex.io/.
The app name becomes part of the api url,<br /> e.g https://<b>{{appName | async}}</b>.squidex.io/.
</p>
<p>
It must contain lower case letters (a-z), numbers and dashes only, and cannot be longer than 40 characters. The name cannot be changed later.
@ -36,6 +36,6 @@
<div class="form-group clearfix">
<button type="reset" class="float-xs-left btn btn-secondary" (click)="cancel()">Cancel</button>
<button type="submit" class="float-xs-right btn btn-primary" >Create</button>
<button type="submit" class="float-xs-right btn btn-success">Create</button>
</div>
</form>

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

@ -5,8 +5,9 @@
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { Component, EventEmitter, Output } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Observable } from 'rxjs';
import { fadeAnimation } from 'framework';
@ -23,10 +24,7 @@ const FALLBACK_NAME = 'my-app';
fadeAnimation
]
})
export class AppFormComponent implements OnInit {
@Input()
public showClose = false;
export class AppFormComponent {
@Output()
public created = new EventEmitter<AppDto>();
@ -44,7 +42,9 @@ export class AppFormComponent implements OnInit {
]]
});
public appName = FALLBACK_NAME;
public appName =
Observable.of(FALLBACK_NAME)
.merge(this.createForm.get('name').valueChanges.map(n => n || FALLBACK_NAME));
constructor(
private readonly appsStore: AppsStoreService,
@ -52,12 +52,6 @@ export class AppFormComponent implements OnInit {
) {
}
public ngOnInit() {
this.createForm.get('name').valueChanges.subscribe(value => {
this.appName = value || FALLBACK_NAME;
});
}
public createApp() {
this.createForm.markAsTouched();
@ -68,10 +62,10 @@ export class AppFormComponent implements OnInit {
this.appsStore.createApp(request)
.subscribe(dto => {
this.createForm.reset();
this.reset();
this.created.emit(dto);
}, error => {
this.reset();
this.createForm.enable();
this.creationError = error.displayMessage;
});
}

10
src/Squidex/app/shared/components/dashboard-link.directive.ts

@ -26,6 +26,10 @@ export class DashboardLinkDirective implements OnInit, OnDestroy {
) {
}
public ngOnDestroy() {
this.appSubscription.unsubscribe();
}
public ngOnInit() {
this.appSubscription =
this.appsStore.selectedApp.subscribe(app => {
@ -35,12 +39,8 @@ export class DashboardLinkDirective implements OnInit, OnDestroy {
});
}
public ngOnDestroy() {
this.appSubscription.unsubscribe();
}
@HostListener('click')
public onClick() {
public onClick(): boolean {
this.router.navigateByUrl(this.url);
return false;

6
src/Squidex/app/shared/services/app-clients.service.spec.ts

@ -57,8 +57,8 @@ describe('AppClientsService', () => {
expect(clients).toEqual(
[
new AppClientDto('client1', 'secret1', 'Client 1', DateTime.parseISO_UTC('2016-12-12T10:10')),
new AppClientDto('client2', 'secret2', 'Client 2', DateTime.parseISO_UTC('2016-11-11T10:10'))
new AppClientDto('client1', 'Client 1', 'secret1', DateTime.parseISO_UTC('2016-12-12T10:10')),
new AppClientDto('client2', 'Client 2', 'secret2', DateTime.parseISO_UTC('2016-11-11T10:10'))
]);
authService.verifyAll();
@ -89,7 +89,7 @@ describe('AppClientsService', () => {
});
expect(client).toEqual(
new AppClientDto('client1', 'secret1', 'Client 1', DateTime.parseISO_UTC('2016-12-12T10:10')));
new AppClientDto('client1', 'Client 1', 'secret1', DateTime.parseISO_UTC('2016-12-12T10:10')));
authService.verifyAll();
});

22
src/Squidex/app/shared/services/app-clients.service.ts

@ -9,19 +9,17 @@ import { Injectable } from '@angular/core';
import { Headers, Http, RequestOptions } from '@angular/http';
import { Observable } from 'rxjs';
import {
ApiUrlConfig,
DateTime,
handleError
} from 'framework';
import 'framework/angular/http-extensions';
import { ApiUrlConfig, DateTime } from 'framework';
import { AuthService } from './auth.service';
export class AppClientDto {
constructor(
public readonly id: string,
public readonly secret: string,
public readonly name: string,
public readonly secret: string,
public readonly expiresUtc: DateTime
) {
}
@ -69,12 +67,12 @@ export class AppClientsService {
return items.map(item => {
return new AppClientDto(
item.id,
item.secret,
item.name,
item.secret,
DateTime.parseISO_UTC(item.expiresUtc));
});
})
.catch(response => handleError('Failed to load clients. Please reload.', response));
.catchError('Failed to load clients. Please reload.');
}
public postClient(appName: string, dto: CreateAppClientDto): Observable<AppClientDto> {
@ -85,25 +83,25 @@ export class AppClientsService {
.map(response => {
return new AppClientDto(
response.id,
response.secret,
response.name,
response.secret,
DateTime.parseISO_UTC(response.expiresUtc));
})
.catch(response => handleError('Failed to add client. Please reload.', response));
.catchError('Failed to add client. Please reload.');
}
public updateClient(appName: string, id: string, dto: UpdateAppClientDto): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/clients/${id}`);
return this.authService.authPut(url, dto)
.catch(response => handleError('Failed to revoke client. Please reload.', response));
.catchError('Failed to revoke client. Please reload.');
}
public deleteClient(appName: string, id: string): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/clients/${id}`);
return this.authService.authDelete(url)
.catch(response => handleError('Failed to revoke client. Please reload.', response));
.catchError('Failed to revoke client. Please reload.');
}
public createToken(appName: string, client: AppClientDto): Observable<AccessTokenDto> {

10
src/Squidex/app/shared/services/app-contributors.service.ts

@ -8,7 +8,9 @@
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { ApiUrlConfig, handleError } from 'framework';
import 'framework/angular/http-extensions';
import { ApiUrlConfig } from 'framework';
import { AuthService } from './auth.service';
@ -42,20 +44,20 @@ export class AppContributorsService {
item.permission);
});
})
.catch(response => handleError('Failed to load contributors. Please reload.', response));
.catchError('Failed to load contributors. Please reload.');
}
public postContributor(appName: string, dto: AppContributorDto): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/contributors`);
return this.authService.authPost(url, dto)
.catch(response => handleError('Failed to add contributors. Please reload.', response));
.catchError('Failed to add contributors. Please reload.');
}
public deleteContributor(appName: string, contributorId: string): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/contributors/${contributorId}`);
return this.authService.authDelete(url)
.catch(response => handleError('Failed to delete contributors. Please reload.', response));
.catchError('Failed to delete contributors. Please reload.');
}
}

12
src/Squidex/app/shared/services/app-languages.service.ts

@ -8,7 +8,9 @@
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { ApiUrlConfig, handleError } from 'framework';
import 'framework/angular/http-extensions';
import { ApiUrlConfig } from 'framework';
import { AuthService } from './auth.service';
@ -58,7 +60,7 @@ export class AppLanguagesService {
item.isMasterLanguage === true);
});
})
.catch(response => handleError('Failed to load languages. Please reload', response));
.catchError('Failed to load languages. Please reload');
}
public postLanguages(appName: string, dto: AddAppLanguageDto): Observable<AppLanguageDto> {
@ -72,20 +74,20 @@ export class AppLanguagesService {
response.englishName,
response.isMasterLanguage === true);
})
.catch(response => handleError('Failed to add language. Please reload.', response));
.catchError('Failed to add language. Please reload.');
}
public updateLanguage(appName: string, languageCode: string, dto: UpdateAppLanguageDto): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/languages/${languageCode}`);
return this.authService.authPut(url, dto)
.catch(response => handleError('Failed to change language. Please reload.', response));
.catchError('Failed to change language. Please reload.');
}
public deleteLanguage(appName: string, languageCode: string): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/languages/${languageCode}`);
return this.authService.authDelete(url)
.catch(response => handleError('Failed to add language. Please reload.', response));
.catchError('Failed to add language. Please reload.');
}
}

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

@ -20,8 +20,8 @@ import {
describe('AppsStoreService', () => {
const now = DateTime.now();
const oldApps = [new AppDto('id', 'old-name', now, now, 'Owner')];
const newApp = new AppDto('id', 'new-name', now, now, 'Owner');
const oldApps = [new AppDto('id', 'old-name', 'Owner', now, now)];
const newApp = new AppDto('id', 'new-name', 'Owner', now, now);
let appsService: IMock<AppsService>;
let authService: IMock<AuthService>;

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

@ -90,7 +90,7 @@ export class AppsStoreService {
.map(created => {
now = now || DateTime.now();
const app = new AppDto(created.id, dto.name, now, now, 'Owner');
const app = new AppDto(created.id, dto.name, 'Owner', now, now);
return app;
})

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

@ -36,15 +36,15 @@ describe('AppsService', () => {
body: [{
id: '123',
name: 'name1',
permission: 'Owner',
created: '2016-01-01',
lastModified: '2016-02-02',
permission: 'Owner'
lastModified: '2016-02-02'
}, {
id: '456',
name: 'name2',
permission: 'Editor',
created: '2017-01-01',
lastModified: '2017-02-02',
permission: 'Editor'
lastModified: '2017-02-02'
}]
})
)
@ -58,8 +58,8 @@ describe('AppsService', () => {
}).unsubscribe();
expect(apps).toEqual([
new AppDto('123', 'name1', DateTime.parseISO('2016-01-01'), DateTime.parseISO('2016-02-02'), 'Owner'),
new AppDto('456', 'name2', DateTime.parseISO('2017-01-01'), DateTime.parseISO('2017-02-02'), 'Editor')
new AppDto('123', 'name1', 'Owner', DateTime.parseISO('2016-01-01'), DateTime.parseISO('2016-02-02')),
new AppDto('456', 'name2', 'Editor', DateTime.parseISO('2017-01-01'), DateTime.parseISO('2017-02-02'))
]);
authService.verifyAll();

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

@ -8,11 +8,12 @@
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import 'framework/angular/http-extensions';
import {
ApiUrlConfig,
DateTime,
EntityCreatedDto,
handleError
EntityCreatedDto
} from 'framework';
import { AuthService } from './auth.service';
@ -21,9 +22,9 @@ export class AppDto {
constructor(
public readonly id: string,
public readonly name: string,
public readonly permission: string,
public readonly created: DateTime,
public readonly lastModified: DateTime,
public readonly permission: string
public readonly lastModified: DateTime
) {
}
}
@ -55,12 +56,12 @@ export class AppsService {
return new AppDto(
item.id,
item.name,
item.permission,
DateTime.parseISO(item.created),
DateTime.parseISO(item.lastModified),
item.permission);
DateTime.parseISO(item.lastModified));
});
})
.catch(response => handleError('Failed to load apps. Please reload.', response));
.catchError('Failed to load apps. Please reload.');
}
public postApp(dto: CreateAppDto): Observable<EntityCreatedDto> {
@ -71,6 +72,6 @@ export class AppsService {
.map(response => {
return new EntityCreatedDto(response.id);
})
.catch(response => handleError('Failed to create app. Please reload.', response));
.catchError('Failed to create app. Please reload.');
}
}

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

@ -31,6 +31,10 @@ export class Profile {
return this.user.profile['urn:squidex:picture'];
}
public get token(): string {
return `subject:${this.id}`;
}
constructor(
public readonly user: User
) {

10
src/Squidex/app/shared/services/history.service.ts

@ -8,11 +8,9 @@
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import {
ApiUrlConfig,
DateTime,
handleError
} from 'framework';
import 'framework/angular/http-extensions';
import { ApiUrlConfig, DateTime } from 'framework';
import { AuthService } from './auth.service';
@ -50,6 +48,6 @@ export class HistoryService {
DateTime.parseISO_UTC(item.created));
});
})
.catch(response => handleError('Failed to load history. Please reload', response));
.catchError('Failed to load history. Please reload');
}
}

6
src/Squidex/app/shared/services/languages.service.ts

@ -8,7 +8,9 @@
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { ApiUrlConfig, handleError } from 'framework';
import 'framework/angular/http-extensions';
import { ApiUrlConfig } from 'framework';
import { AuthService } from './auth.service';
@ -42,6 +44,6 @@ export class LanguageService {
item.englishName);
});
})
.catch(response => handleError('Failed to load languages. Please reload', response));
.catchError('Failed to load languages. Please reload');
}
}

69
src/Squidex/app/shared/services/schemas.service.ts

@ -8,11 +8,12 @@
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import 'framework/angular/http-extensions';
import {
ApiUrlConfig,
DateTime,
EntityCreatedDto,
handleError
EntityCreatedDto
} from 'framework';
import { AuthService } from './auth.service';
@ -54,11 +55,12 @@ export class SchemaDto {
constructor(
public readonly id: string,
public readonly name: string,
public readonly created: DateTime,
public readonly lastModified: DateTime,
public readonly label: string,
public readonly isPublished: boolean,
public readonly createdBy: string,
public readonly lastModifiedBy: string,
public readonly isPublished: boolean
public readonly created: DateTime,
public readonly lastModified: DateTime
) {
}
}
@ -67,14 +69,14 @@ export class SchemaDetailsDto {
constructor(
public readonly id: string,
public readonly name: string,
public readonly created: DateTime,
public readonly lastModified: DateTime,
public readonly createdBy: string,
public readonly lastModifiedBy: string,
public readonly fields: FieldDto[],
public readonly label: string,
public readonly hints: string,
public readonly isPublished: boolean
public readonly fields: FieldDto[],
public readonly isPublished: boolean,
public readonly createdBy: string,
public readonly lastModifiedBy: string,
public readonly created: DateTime,
public readonly lastModified: DateTime
) {
}
}
@ -191,14 +193,15 @@ export class SchemasService {
return new SchemaDto(
item.id,
item.name,
DateTime.parseISO_UTC(item.created),
DateTime.parseISO_UTC(item.lastModified),
item.label,
item.isPublished,
item.createdBy,
item.lastModifiedBy,
item.isPublished);
DateTime.parseISO_UTC(item.created),
DateTime.parseISO_UTC(item.lastModified));
});
})
.catch(response => handleError('Failed to load schemas. Please reload.', response));
.catchError('Failed to load schemas. Please reload.');
}
public getSchema(appName: string, id: string): Observable<SchemaDetailsDto> {
@ -224,16 +227,16 @@ export class SchemasService {
return new SchemaDetailsDto(
response.id,
response.name,
DateTime.parseISO_UTC(response.created),
DateTime.parseISO_UTC(response.lastModified),
response.createdBy,
response.lastModifiedBy,
fields,
response.label,
response.hints,
response.isPublished);
fields,
response.isPublished,
response.createdBy,
response.lastModifiedBy,
DateTime.parseISO_UTC(response.created),
DateTime.parseISO_UTC(response.lastModified));
})
.catch(response => handleError('Failed to load schema. Please reload.', response));
.catchError('Failed to load schema. Please reload.');
}
public postSchema(appName: string, dto: CreateSchemaDto): Observable<EntityCreatedDto> {
@ -244,7 +247,7 @@ export class SchemasService {
.map(response => {
return new EntityCreatedDto(response.id);
})
.catch(response => handleError('Failed to create schema. Please reload.', response));
.catchError('Failed to create schema. Please reload.');
}
public postField(appName: string, schemaName: string, dto: AddFieldDto): Observable<EntityCreatedDto> {
@ -255,69 +258,69 @@ export class SchemasService {
.map(response => {
return new EntityCreatedDto(response.id);
})
.catch(response => handleError('Failed to add field. Please reload.', response));
.catchError('Failed to add field. Please reload.');
}
public putSchema(appName: string, schemaName: string, dto: UpdateSchemaDto): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/`);
return this.authService.authPut(url, dto)
.catch(response => handleError('Failed to update schema. Please reload.', response));
.catchError('Failed to update schema. Please reload.');
}
public publishSchema(appName: string, schemaName: string): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/publish/`);
return this.authService.authPut(url, {})
.catch(response => handleError('Failed to publish schema. Please reload.', response));
.catchError('Failed to publish schema. Please reload.');
}
public unpublishSchema(appName: string, schemaName: string): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/unpublish/`);
return this.authService.authPut(url, {})
.catch(response => handleError('Failed to unpublish schema. Please reload.', response));
.catchError('Failed to unpublish schema. Please reload.');
}
public putField(appName: string, schemaName: string, fieldId: number, dto: UpdateFieldDto): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/${fieldId}/`);
return this.authService.authPut(url, dto)
.catch(response => handleError('Failed to update field. Please reload.', response));
.catchError('Failed to update field. Please reload.');
}
public enableField(appName: string, schemaName: string, fieldId: number): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/${fieldId}/enable/`);
return this.authService.authPut(url, {})
.catch(response => handleError('Failed to enable field. Please reload.', response));
.catchError('Failed to enable field. Please reload.');
}
public disableField(appName: string, schemaName: string, fieldId: number): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/${fieldId}/disable/`);
return this.authService.authPut(url, {})
.catch(response => handleError('Failed to disable field. Please reload.', response));
.catchError('Failed to disable field. Please reload.');
}
public showField(appName: string, schemaName: string, fieldId: number): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/${fieldId}/show/`);
return this.authService.authPut(url, {})
.catch(response => handleError('Failed to show field. Please reload.', response));
.catchError('Failed to show field. Please reload.');
}
public hideField(appName: string, schemaName: string, fieldId: number): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/${fieldId}/hide/`);
return this.authService.authPut(url, {})
.catch(response => handleError('Failed to hide field. Please reload.', response));
.catchError('Failed to hide field. Please reload.');
}
public deleteField(appName: string, schemaName: string, fieldId: number): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/${fieldId}/`);
return this.authService.authDelete(url)
.catch(response => handleError('Failed to delete field. Please reload.', response));
.catchError('Failed to delete field. Please reload.');
}
}

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

@ -25,6 +25,10 @@ export class LeftMenuComponent implements OnInit, OnDestroy {
) {
}
public ngOnDestroy() {
this.appSubscription.unsubscribe();
}
public ngOnInit() {
this.appSubscription =
this.appsStore.selectedApp.subscribe(app => {
@ -33,8 +37,4 @@ export class LeftMenuComponent implements OnInit, OnDestroy {
}
});
}
public ngOnDestroy() {
this.appSubscription.unsubscribe();
}
}

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

@ -41,6 +41,11 @@ export class AppsMenuComponent implements OnInit, OnDestroy {
) {
}
public ngOnDestroy() {
this.appsSubscription.unsubscribe();
this.appSubscription.unsubscribe();
}
public ngOnInit() {
this.appsSubscription =
this.appsStore.apps.subscribe(apps => {
@ -51,11 +56,6 @@ export class AppsMenuComponent implements OnInit, OnDestroy {
this.appsStore.selectedApp.subscribe(selectedApp => this.appName = selectedApp ? selectedApp.name : FALLBACK_NAME);
}
public ngOnDestroy() {
this.appsSubscription.unsubscribe();
this.appSubscription.unsubscribe();
}
public onAppCreationCancelled() {
this.modalDialog.hide();
}

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

@ -28,6 +28,10 @@ export class InternalAreaComponent implements OnInit, OnDestroy {
) {
}
public ngOnDestroy() {
this.notificationsSubscription.unsubscribe();
}
public ngOnInit() {
this.notificationsSubscription =
this.notificationService.notifications.subscribe(notification => {
@ -41,10 +45,6 @@ export class InternalAreaComponent implements OnInit, OnDestroy {
});
}
public ngOnDestroy() {
this.notificationsSubscription.unsubscribe();
}
public close(notification: Notification) {
this.notifications.splice(this.notifications.indexOf(notification), 1);
}

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

@ -35,6 +35,10 @@ export class ProfileMenuComponent implements OnInit, OnDestroy {
) {
}
public ngOnDestroy() {
this.authenticationSubscription.unsubscribe();
}
public ngOnInit() {
this.authenticationSubscription =
this.auth.isAuthenticated.subscribe(() => {
@ -47,10 +51,6 @@ export class ProfileMenuComponent implements OnInit, OnDestroy {
});
}
public ngOnDestroy() {
this.authenticationSubscription.unsubscribe();
}
public logout() {
this.auth.logoutRedirect();
}

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

@ -149,10 +149,13 @@
text-align: right;
}
.form-control {
padding-top: 0;
padding-bottom: 0;
height: calc(2.5rem - 2px);
input,
select {
&.form-control {
padding-top: 0;
padding-bottom: 0;
height: calc(2.5rem - 2px);
}
}
.form-hint {

2
src/Squidex/app/theme/icomoon/fonts/icomoon.svg

@ -1,5 +1,5 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>Generated by IcoMoon</metadata>
<defs>

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Loading…
Cancel
Save