Browse Source

Schema Editing

pull/1/head
Sebastian 9 years ago
parent
commit
e5fa3585d3
  1. 42
      src/Squidex/Controllers/Api/Schemas/Models/Converters/SchemaConverter.cs
  2. 5
      src/Squidex/Controllers/Api/Schemas/Models/FieldDto.cs
  3. 13
      src/Squidex/Controllers/Api/Schemas/Models/NumberFieldPropertiesDto.cs
  4. 13
      src/Squidex/Controllers/Api/Schemas/Models/StringFieldPropertiesDto.cs
  5. 55
      src/Squidex/app/features/schemas/pages/schema/field.component.html
  6. 50
      src/Squidex/app/features/schemas/pages/schema/field.component.scss
  7. 45
      src/Squidex/app/features/schemas/pages/schema/field.component.ts
  8. 12
      src/Squidex/app/features/schemas/pages/schema/schema-page.component.html
  9. 122
      src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts
  10. 17
      src/Squidex/app/features/schemas/pages/schema/types/number-ui.component.ts
  11. 19
      src/Squidex/app/features/schemas/pages/schema/types/number-validation.component.ts
  12. 6
      src/Squidex/app/features/schemas/pages/schema/types/string-ui.component.html
  13. 23
      src/Squidex/app/features/schemas/pages/schema/types/string-ui.component.ts
  14. 29
      src/Squidex/app/features/schemas/pages/schema/types/string-validation.component.ts
  15. 2
      src/Squidex/app/features/schemas/pages/schemas/schema-form.component.ts
  16. 3
      src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.ts
  17. 2
      src/Squidex/app/features/settings/pages/clients/client.component.ts
  18. 5
      src/Squidex/app/features/settings/pages/contributors/contributors-page.component.ts
  19. 10
      src/Squidex/app/framework/angular/action.spec.ts
  20. 6
      src/Squidex/app/framework/angular/autocomplete.component.ts
  21. 2
      src/Squidex/app/framework/angular/color-picker.component.html
  22. 8
      src/Squidex/app/framework/angular/color-picker.component.ts
  23. 14
      src/Squidex/app/framework/angular/date-time.pipes.ts
  24. 3
      src/Squidex/app/framework/angular/image-drop.directive.ts
  25. 2
      src/Squidex/app/framework/angular/money.pipe.ts
  26. 4
      src/Squidex/app/framework/angular/slider.component.ts
  27. 6
      src/Squidex/app/framework/angular/user-report.component.ts
  28. 2
      src/Squidex/app/shared/app-component-base.ts
  29. 2
      src/Squidex/app/shared/components/dashboard-link.directive.ts
  30. 4
      src/Squidex/app/shared/components/history.component.ts
  31. 4
      src/Squidex/app/shared/services/auth.service.ts
  32. 94
      src/Squidex/app/shared/services/schemas.service.ts
  33. 5
      src/Squidex/app/shell/pages/internal/apps-menu.component.ts
  34. 2
      src/Squidex/app/theme/_bootstrap.scss
  35. 2
      src/Squidex/app/theme/_vars.scss
  36. 3
      src/Squidex/package.json
  37. 2
      src/Squidex/tsconfig.json
  38. 1
      src/Squidex/tslint.json

42
src/Squidex/Controllers/Api/Schemas/Models/Converters/SchemaConverter.cs

@ -8,6 +8,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Squidex.Core.Schemas; using Squidex.Core.Schemas;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
using Squidex.Read.Schemas.Repositories; using Squidex.Read.Schemas.Repositories;
@ -20,11 +21,11 @@ namespace Squidex.Controllers.Api.Schemas.Models.Converters
{ {
{ {
typeof(NumberFieldProperties), typeof(NumberFieldProperties),
p => SimpleMapper.Map((NumberFieldProperties)p, new NumberFieldPropertiesDto()) p => Convert((NumberFieldProperties)p)
}, },
{ {
typeof(StringFieldProperties), typeof(StringFieldProperties),
p => SimpleMapper.Map((StringFieldProperties)p, new StringFieldPropertiesDto()) p => Convert((StringFieldProperties)p)
} }
}; };
@ -38,22 +39,41 @@ namespace Squidex.Controllers.Api.Schemas.Models.Converters
dto.Fields = new List<FieldDto>(); dto.Fields = new List<FieldDto>();
foreach (var field in entity.Schema.Fields.Values) foreach (var kvp in entity.Schema.Fields)
{ {
var fieldPropertiesDto = Factories[field.RawProperties.GetType()](field.RawProperties); var fieldPropertiesDto =
Factories[kvp.Value.RawProperties.GetType()](kvp.Value.RawProperties);
var fieldDto = new FieldDto var fieldDto = SimpleMapper.Map(kvp.Value, new FieldDto { FieldId = kvp.Key, Properties = fieldPropertiesDto });
{
Name = field.Name,
IsHidden = field.IsHidden,
IsDisabled = field.IsDisabled,
Properties = fieldPropertiesDto
};
dto.Fields.Add(fieldDto); dto.Fields.Add(fieldDto);
} }
return dto; return dto;
} }
private static FieldPropertiesDto Convert(StringFieldProperties source)
{
var result = SimpleMapper.Map(source, new StringFieldPropertiesDto());
if (source.AllowedValues != null)
{
result.AllowedValues = source.AllowedValues.ToArray();
}
return result;
}
private static FieldPropertiesDto Convert(NumberFieldProperties source)
{
var result = SimpleMapper.Map(source, new NumberFieldPropertiesDto());
if (source.AllowedValues != null)
{
result.AllowedValues = source.AllowedValues.ToArray();
}
return result;
}
} }
} }

5
src/Squidex/Controllers/Api/Schemas/Models/FieldDto.cs

@ -12,6 +12,11 @@ namespace Squidex.Controllers.Api.Schemas.Models
{ {
public sealed class FieldDto public sealed class FieldDto
{ {
/// <summary>
/// The id of the field.
/// </summary>
public long FieldId { get; set; }
/// <summary> /// <summary>
/// The name of the field. Must be unique within the schema. /// The name of the field. Must be unique within the schema.
/// </summary> /// </summary>

13
src/Squidex/Controllers/Api/Schemas/Models/NumberFieldPropertiesDto.cs

@ -6,6 +6,9 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using System.Collections.Immutable;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using NJsonSchema.Annotations; using NJsonSchema.Annotations;
using Squidex.Core.Schemas; using Squidex.Core.Schemas;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
@ -38,11 +41,19 @@ namespace Squidex.Controllers.Api.Schemas.Models
/// <summary> /// <summary>
/// The editor that is used to manage this field. /// The editor that is used to manage this field.
/// </summary> /// </summary>
[JsonConverter(typeof(StringEnumConverter))]
public NumberFieldEditor Editor { get; set; } public NumberFieldEditor Editor { get; set; }
public override FieldProperties ToProperties() public override FieldProperties ToProperties()
{ {
return SimpleMapper.Map(this, new NumberFieldProperties()); var result = SimpleMapper.Map(this, new NumberFieldProperties());
if (AllowedValues != null)
{
result.AllowedValues = ImmutableList.Create(AllowedValues);
}
return result;
} }
} }
} }

13
src/Squidex/Controllers/Api/Schemas/Models/StringFieldPropertiesDto.cs

@ -6,6 +6,9 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using System.Collections.Immutable;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using NJsonSchema.Annotations; using NJsonSchema.Annotations;
using Squidex.Core.Schemas; using Squidex.Core.Schemas;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
@ -48,11 +51,19 @@ namespace Squidex.Controllers.Api.Schemas.Models
/// <summary> /// <summary>
/// The editor that is used to manage this field. /// The editor that is used to manage this field.
/// </summary> /// </summary>
[JsonConverter(typeof(StringEnumConverter))]
public StringFieldEditor Editor { get; set; } public StringFieldEditor Editor { get; set; }
public override FieldProperties ToProperties() public override FieldProperties ToProperties()
{ {
return SimpleMapper.Map(this, new StringFieldProperties()); var result = SimpleMapper.Map(this, new StringFieldProperties());
if (AllowedValues != null)
{
result.AllowedValues = ImmutableList.Create(AllowedValues);
}
return result;
} }
} }
} }

55
src/Squidex/app/features/schemas/pages/schema/field.component.html

@ -1,17 +1,48 @@
<div class="table-items-row"> <div class="table-items-row field">
<div class="field-summary"> <div class="field-summary">
<div class="row"> <div class="row">
<div class="col-xs-8"> <div class="col-xs-6">
<i class="field-icon icon-type-{{field.properties.fieldType}}"></i> {{field.name}} <span class="field-name">
<i class="field-icon icon-type-{{field.properties.fieldType}}"></i>
<span [class.field-hidden]="field.isHidden" [attr.title]="field.isHidden ? 'Hidden Field' : 'Visible Field'">{{displayName}}</span>
<span class="field-hints">{{field.properties.hints}}</span>
</span>
</div> </div>
<div class="col-xs-4"> <div class="col-xs-3">
<div class="float-xs-right">
<span class="tag tag-success" *ngIf="!field.isDisabled">Enabled</span>
<span class="tag tag-danger" *ngIf="field.isDisabled">Disabled</span>
</div>
</div>
<div class="col-xs-3">
<div class="float-xs-right"> <div class="float-xs-right">
<button type="button" class="btn btn-secondary field-edit-button" [class.active]="isEditing" (click)="toggleEditing()"> <button type="button" class="btn btn-secondary field-edit-button" [class.active]="isEditing" (click)="toggleEditing()">
<i class="icon-settings"></i> <i class="icon-settings"></i>
</button> </button>
<button type="button" class="btn btn-simple">
<div class="dropdown">
<button type="button" class="btn btn-simple" (click)="dropdown.toggle()" [class.active]="dropdown.isOpen | async">
<i class="icon-dots"></i> <i class="icon-dots"></i>
</button> </button>
<div class="dropdown-menu" *sqxModalView="dropdown" closeAlways="true" [@fade]>
<a class="dropdown-item" (click)="enabled.emit()" *ngIf="field.isDisabled">
Enable
</a>
<a class="dropdown-item" (click)="disabled.emit()" *ngIf="!field.isDisabled">
Disable
</a>
<a class="dropdown-item" (click)="hidden.emit()" *ngIf="!field.isHidden">
Hide
</a>
<a class="dropdown-item" (click)="shown.emit()" *ngIf="field.isHidden">
Show
</a>
<a class="dropdown-item" (click)="deleted.emit()">
Delete
</a>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -38,7 +69,7 @@
</div> </div>
</div> </div>
<div class="field-details-tab" *ngIf="selectedTab == 0"> <div class="field-details-tab" [class.hidden]="selectedTab !== 0">
<div class="form-group row"> <div class="form-group row">
<label for="field-label" class="col-xs-3 col-form-label">Label</label> <label for="field-label" class="col-xs-3 col-form-label">Label</label>
@ -80,24 +111,24 @@
</div> </div>
</div> </div>
<div class="field-details-tab" *ngIf="selectedTab == 1"> <div class="field-details-tab" [class.hidden]="selectedTab !== 1">
<div [ngSwitch]="field.properties.fieldType"> <div [ngSwitch]="field.properties.fieldType">
<div *ngSwitchCase="'number'"> <div *ngSwitchCase="'number'">
<sqx-number-validation [editForm]="editForm"></sqx-number-validation> <sqx-number-validation [editForm]="editForm" [properties]="field.properties"></sqx-number-validation>
</div> </div>
<div *ngSwitchCase="'string'"> <div *ngSwitchCase="'string'">
<sqx-string-validation [editForm]="editForm"></sqx-string-validation> <sqx-string-validation [editForm]="editForm" [properties]="field.properties"></sqx-string-validation>
</div> </div>
</div> </div>
</div> </div>
<div class="field-details-tab" *ngIf="selectedTab == 2"> <div class="field-details-tab" [class.hidden]="selectedTab !== 2">
<div [ngSwitch]="field.properties.fieldType"> <div [ngSwitch]="field.properties.fieldType">
<div *ngSwitchCase="'number'"> <div *ngSwitchCase="'number'">
<sqx-number-ui [editForm]="editForm"></sqx-number-ui> <sqx-number-ui [editForm]="editForm" [properties]="field.properties"></sqx-number-ui>
</div> </div>
<div *ngSwitchCase="'string'"> <div *ngSwitchCase="'string'">
<sqx-string-ui [editForm]="editForm"></sqx-string-ui> <sqx-string-ui [editForm]="editForm" [properties]="field.properties"></sqx-string-ui>
</div> </div>
</div> </div>
</div> </div>

50
src/Squidex/app/features/schemas/pages/schema/field.component.scss

@ -7,6 +7,31 @@ $field-header: #e7ebef;
padding: 0; padding: 0;
} }
.dropdown {
& {
display: inline-block;
}
&-menu {
@include absolute(100%, 0, auto, auto);
}
&-item {
cursor: pointer;
}
}
.btn-simple {
& {
color: $color-border-dark;
}
&:hover,
&.active {
color: $color-text;
}
}
.field { .field {
&-summary { &-summary {
padding: 15px 20px; padding: 15px 20px;
@ -14,11 +39,11 @@ $field-header: #e7ebef;
} }
&-icon { &-icon {
color: darken($color-border, 15%); margin-right: 1rem;
color: $color-border-dark;
font-size: 1.2rem; font-size: 1.2rem;
font-weight: normal; font-weight: normal;
vertical-align: middle; vertical-align: middle;
margin-right: 1rem;
} }
&-edit-button { &-edit-button {
@ -37,6 +62,21 @@ $field-header: #e7ebef;
} }
} }
&-name {
@include truncate;
}
&-hidden {
color: $color-border-dark;
}
&-hints {
color: lighten($color-text, 50%);
font-weight: normal;
font-size: .95rem;
margin-left: .3rem;
}
&-details { &-details {
& { & {
position: relative; position: relative;
@ -59,6 +99,12 @@ $field-header: #e7ebef;
padding: 15px 20px; padding: 15px 20px;
} }
} }
.tag {
@include opacity(.9);
min-width: 60px;
max-width: 90px;
}
} }
.nav-field-tabs { .nav-field-tabs {

45
src/Squidex/app/features/schemas/pages/schema/field.component.ts

@ -11,7 +11,8 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { import {
createProperties, createProperties,
fadeAnimation, fadeAnimation,
FieldDto FieldDto,
ModalView
} from 'shared'; } from 'shared';
@Component({ @Component({
@ -23,17 +24,36 @@ import {
] ]
}) })
export class FieldComponent implements OnInit { export class FieldComponent implements OnInit {
private oldValue: any; public dropdown = new ModalView(false, true);
@Input() @Input()
public field: FieldDto; public field: FieldDto;
@Output()
public hidden = new EventEmitter<FieldDto>();
@Output()
public shown = new EventEmitter<FieldDto>();
@Output() @Output()
public saved = new EventEmitter<FieldDto>(); public saved = new EventEmitter<FieldDto>();
@Output()
public enabled = new EventEmitter<FieldDto>();
@Output()
public disabled = new EventEmitter<FieldDto>();
@Output()
public deleted = new EventEmitter<FieldDto>();
public isEditing: boolean = false; public isEditing: boolean = false;
public selectedTab = 0; public selectedTab = 0;
public get displayName() {
return this.field.properties.label && this.field.properties.label.length > 0 ? this.field.properties.label : this.field.name;
}
public editForm: FormGroup = public editForm: FormGroup =
this.formBuilder.group({ this.formBuilder.group({
label: ['', label: ['',
@ -53,7 +73,7 @@ export class FieldComponent implements OnInit {
} }
public ngOnInit() { public ngOnInit() {
this.resetForm(this.field.properties); this.resetForm();
} }
public save() { public save() {
@ -64,6 +84,7 @@ export class FieldComponent implements OnInit {
const field = const field =
new FieldDto( new FieldDto(
this.field.fieldId,
this.field.name, this.field.name,
this.field.isHidden, this.field.isHidden,
this.field.isHidden, this.field.isHidden,
@ -74,7 +95,7 @@ export class FieldComponent implements OnInit {
} }
public cancel() { public cancel() {
this.resetForm(this.oldValue); this.resetForm();
} }
public toggleEditing() { public toggleEditing() {
@ -85,20 +106,8 @@ export class FieldComponent implements OnInit {
this.selectedTab = tab; this.selectedTab = tab;
} }
private resetForm(properties: any) { private resetForm() {
this.editForm.reset(); this.editForm.reset(this.field.properties);
for (let property in properties) {
if (properties.hasOwnProperty(property)) {
const controlName = property + '';
if (this.editForm.contains(controlName)) {
this.editForm.get(controlName).setValue(properties[property]);
}
}
}
this.oldValue = Object.assign({}, this.editForm.value);
this.isEditing = false; this.isEditing = false;
} }

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

@ -1,9 +1,9 @@
<sqx-title message="{app} | {schema}" parameter1="app" value1="{{appName() | async}}" parameter2="schema" value2="{{schemaName | async}}"></sqx-title> <sqx-title message="{app} | {schema}" parameter1="app" value1="{{appName() | async}}" parameter2="schema" value2="{{schemaName}}"></sqx-title>
<div class="panel panel-light"> <div class="panel panel-light">
<div class="panel-header"> <div class="panel-header">
<div> <div>
<h3 class="panel-title">{{schemaName | async}}</h3> <h3 class="panel-title">{{schemaName}}</h3>
<a class="panel-close" routerLink="../"> <a class="panel-close" routerLink="../">
<i class="icon-close"></i> <i class="icon-close"></i>
@ -13,7 +13,13 @@
<div class="panel-content"> <div class="panel-content">
<div *ngFor="let field of schemaFields"> <div *ngFor="let field of schemaFields">
<sqx-field [field]="field" (saved)="updateField(field, $event)"></sqx-field> <sqx-field [field]="field"
(disabled)="disableField(field)"
(deleted)="deleteField(field)"
(enabled)="enableField(field)"
(hidden)="hideField(field)"
(saved)="saveField(field, $event)"
(shown)="showField(field)"></sqx-field>
</div> </div>
<div class="table-items-footer"> <div class="table-items-footer">

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

@ -5,24 +5,23 @@
* Copyright (c) Sebastian Stehle. All rights reserved * Copyright (c) Sebastian Stehle. All rights reserved
*/ */
import { Component, OnInit } from '@angular/core'; import { Component, OnDestroy, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { Observable } from 'rxjs'; import { Subscription } from 'rxjs';
import { import {
AddFieldDto, AddFieldDto,
AppComponentBase, AppComponentBase,
AppsStoreService, AppsStoreService,
createProperties,
FieldDto, FieldDto,
FieldPropertiesDto,
HistoryChannelUpdated, HistoryChannelUpdated,
ImmutableArray, ImmutableArray,
MessageBus, MessageBus,
NotificationService, NotificationService,
NumberFieldPropertiesDto,
SchemasService, SchemasService,
StringFieldPropertiesDto, UpdateFieldDto,
UsersProviderService UsersProviderService
} from 'shared'; } from 'shared';
@ -31,12 +30,15 @@ import {
styleUrls: ['./schema-page.component.scss'], styleUrls: ['./schema-page.component.scss'],
templateUrl: './schema-page.component.html' templateUrl: './schema-page.component.html'
}) })
export class SchemaPageComponent extends AppComponentBase implements OnInit { export class SchemaPageComponent extends AppComponentBase implements OnDestroy, OnInit {
private routerSubscription: Subscription;
public fieldTypes: string[] = [ public fieldTypes: string[] = [
'string', 'string',
'number' 'number'
]; ];
public schemaName: string;
public schemaFields = ImmutableArray.empty<FieldDto>(); public schemaFields = ImmutableArray.empty<FieldDto>();
public addFieldForm: FormGroup = public addFieldForm: FormGroup =
@ -53,10 +55,6 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
]] ]]
}); });
public get schemaName(): Observable<string> {
return this.route.params.map(p => p['schemaName']);
}
constructor(apps: AppsStoreService, notifications: NotificationService, users: UsersProviderService, constructor(apps: AppsStoreService, notifications: NotificationService, users: UsersProviderService,
private readonly schemasService: SchemasService, private readonly schemasService: SchemasService,
private readonly messageBus: MessageBus, private readonly messageBus: MessageBus,
@ -66,14 +64,23 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
super(apps, notifications, users); super(apps, notifications, users);
} }
public ngOnDestroy() {
this.routerSubscription.unsubscribe();
}
public ngOnInit() { public ngOnInit() {
this.routerSubscription =
this.route.params.map(p => p['schemaName']).subscribe(name => {
this.schemaName = name;
this.reset();
this.load(); this.load();
});
} }
public load() { public load() {
this.schemaName.combineLatest(this.appName(), (schemaName, appName) => { return { schemaName, appName }; }) this.appName()
.do(() => this.reset()) .switchMap(app => this.schemasService.getSchema(app, this.schemaName)).retry(2)
.switchMap(p => this.schemasService.getSchema(p.appName, p.schemaName)).retry(2)
.subscribe(dto => { .subscribe(dto => {
this.schemaFields = ImmutableArray.of(dto.fields); this.schemaFields = ImmutableArray.of(dto.fields);
}, error => { }, error => {
@ -81,8 +88,66 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
}); });
} }
public updateField(field: FieldDto, newField: FieldDto) { public enableField(field: FieldDto) {
this.updateFields(this.schemaFields.replace(field, newField)); this.appName()
.switchMap(app => this.schemasService.enableField(app, this.schemaName, field.fieldId)).retry(2)
.subscribe(() => {
this.updateField(field, new FieldDto(field.fieldId, field.name, field.isHidden, false, field.properties));
}, error => {
this.notifyError(error);
});
}
public disableField(field: FieldDto) {
this.appName()
.switchMap(app => this.schemasService.disableField(app, this.schemaName, field.fieldId)).retry(2)
.subscribe(() => {
this.updateField(field, new FieldDto(field.fieldId, field.name, field.isHidden, true, field.properties));
}, error => {
this.notifyError(error);
});
}
public showField(field: FieldDto) {
this.appName()
.switchMap(app => this.schemasService.showField(app, this.schemaName, field.fieldId)).retry(2)
.subscribe(() => {
this.updateField(field, new FieldDto(field.fieldId, field.name, false, field.isDisabled, field.properties));
}, error => {
this.notifyError(error);
});
}
public hideField(field: FieldDto) {
this.appName()
.switchMap(app => this.schemasService.hideField(app, this.schemaName, field.fieldId)).retry(2)
.subscribe(() => {
this.updateField(field, new FieldDto(field.fieldId, field.name, true, field.isDisabled, field.properties));
}, error => {
this.notifyError(error);
});
}
public deleteField(field: FieldDto) {
this.appName()
.switchMap(app => this.schemasService.deleteField(app, this.schemaName, field.fieldId)).retry(2)
.subscribe(() => {
this.updateFields(this.schemaFields.remove(field));
}, error => {
this.notifyError(error);
});
}
public saveField(field: FieldDto, newField: FieldDto) {
const request = new UpdateFieldDto(newField.properties);
this.appName()
.switchMap(app => this.schemasService.putField(app, this.schemaName, field.fieldId, request)).retry(2)
.subscribe(() => {
this.updateField(field, new FieldDto(field.fieldId, field.name, newField.isHidden, field.isDisabled, newField.properties));
}, error => {
this.notifyError(error);
});
} }
public addField() { public addField() {
@ -91,15 +156,7 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
if (this.addFieldForm.valid) { if (this.addFieldForm.valid) {
this.addFieldForm.disable(); this.addFieldForm.disable();
let properties: FieldPropertiesDto; const properties = createProperties(this.addFieldForm.get('type').value);
switch (this.addFieldForm.get('type').value) {
case 'string':
properties = new StringFieldPropertiesDto();
break;
case 'number':
properties = new NumberFieldPropertiesDto();
}
const dto = new AddFieldDto(this.addFieldForm.get('name').value, properties); const dto = new AddFieldDto(this.addFieldForm.get('name').value, properties);
@ -108,10 +165,17 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
this.addFieldForm.enable(); this.addFieldForm.enable();
}; };
this.schemaName.combineLatest(this.appName(), (schemaName, appName) => { return { schemaName, appName }; }) this.appName()
.switchMap(p => this.schemasService.postField(p.appName, p.schemaName, dto)) .switchMap(app => this.schemasService.postField(app, this.schemaName, dto))
.subscribe(dto => { .subscribe(dto => {
this.updateFields(this.schemaFields.push(new FieldDto(this.addFieldForm.get('name').value, false, false, properties))); const newField =
new FieldDto(parseInt(dto.id, 10),
this.addFieldForm.get('name').value,
false,
false,
properties);
this.updateFields(this.schemaFields.push(newField));
reset(); reset();
}, error => { }, error => {
this.notifyError(error); this.notifyError(error);
@ -124,6 +188,10 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
this.schemaFields = ImmutableArray.empty<FieldDto>(); this.schemaFields = ImmutableArray.empty<FieldDto>();
} }
public updateField(field: FieldDto, newField: FieldDto) {
this.updateFields(this.schemaFields.replace(field, newField));
}
private updateFields(fields: ImmutableArray<FieldDto>) { private updateFields(fields: ImmutableArray<FieldDto>) {
this.schemaFields = fields; this.schemaFields = fields;

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

@ -9,7 +9,11 @@ import { Component, Input, OnDestroy, OnInit } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms'; import { FormControl, FormGroup, Validators } from '@angular/forms';
import { Observable, Subscription } from 'rxjs'; import { Observable, Subscription } from 'rxjs';
import { fadeAnimation, FloatConverter } from 'shared'; import {
fadeAnimation,
FloatConverter,
NumberFieldPropertiesDto
} from 'shared';
@Component({ @Component({
selector: 'sqx-number-ui', selector: 'sqx-number-ui',
@ -25,24 +29,27 @@ export class NumberUIComponent implements OnDestroy, OnInit {
@Input() @Input()
public editForm: FormGroup; public editForm: FormGroup;
@Input()
public properties: NumberFieldPropertiesDto;
public converter = new FloatConverter(); public converter = new FloatConverter();
public hideAllowedValues: Observable<boolean>; public hideAllowedValues: Observable<boolean>;
public ngOnInit() { public ngOnInit() {
this.editForm.addControl('editor', this.editForm.addControl('editor',
new FormControl('Input', [ new FormControl(this.properties.editor, [
Validators.required Validators.required
])); ]));
this.editForm.addControl('placeholder', this.editForm.addControl('placeholder',
new FormControl('', [ new FormControl(this.properties.placeholder, [
Validators.maxLength(100) Validators.maxLength(100)
])); ]));
this.editForm.addControl('allowedValues', this.editForm.addControl('allowedValues',
new FormControl(undefined, [])); new FormControl(this.properties.allowedValues, []));
this.hideAllowedValues = this.hideAllowedValues =
Observable.of(false) Observable.of(this.properties.editor)
.merge(this.editForm.get('editor').valueChanges) .merge(this.editForm.get('editor').valueChanges)
.map(x => !x || x === 'Input' || x === 'Textarea'); .map(x => !x || x === 'Input' || x === 'Textarea');

19
src/Squidex/app/features/schemas/pages/schema/types/number-validation.component.ts

@ -9,6 +9,8 @@ import { Component, Input, OnInit } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms'; import { FormControl, FormGroup } from '@angular/forms';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { NumberFieldPropertiesDto } from 'shared';
@Component({ @Component({
selector: 'sqx-number-validation', selector: 'sqx-number-validation',
styleUrls: ['number-validation.component.scss'], styleUrls: ['number-validation.component.scss'],
@ -18,22 +20,23 @@ export class NumberValidationComponent implements OnInit {
@Input() @Input()
public editForm: FormGroup; public editForm: FormGroup;
@Input()
public properties: NumberFieldPropertiesDto;
public hideDefaultValue: Observable<boolean>; public hideDefaultValue: Observable<boolean>;
public ngOnInit() { public ngOnInit() {
this.editForm.addControl('maxValue', this.editForm.addControl('maxValue',
new FormControl()); new FormControl(this.properties.maxValue));
this.editForm.addControl('minValue', this.editForm.addControl('minValue',
new FormControl()); new FormControl(this.properties.minValue));
this.editForm.addControl('pattern',
new FormControl());
this.editForm.addControl('patternMessage',
new FormControl());
this.editForm.addControl('defaultValue', this.editForm.addControl('defaultValue',
new FormControl()); new FormControl(this.properties.defaultValue));
this.hideDefaultValue = this.hideDefaultValue =
Observable.of(false) Observable.of(this.properties.isRequired)
.merge(this.editForm.get('isRequired').valueChanges) .merge(this.editForm.get('isRequired').valueChanges)
.map(x => !!x); .map(x => !!x);
} }

6
src/Squidex/app/features/schemas/pages/schema/types/string-ui.component.html

@ -29,12 +29,12 @@
<span class="radio-label">Input</span> <span class="radio-label">Input</span>
</label> </label>
<label class="btn btn-radio" [class.active]="editForm.controls.editor.value === 'Textarea'"> <label class="btn btn-radio" [class.active]="editForm.controls.editor.value === 'TextArea'">
<input type="radio" class="radio-input" formControlName="editor" value="Textarea" /> <input type="radio" class="radio-input" formControlName="editor" value="TextArea" />
<i class="icon-control-textarea"></i> <i class="icon-control-textarea"></i>
<span class="radio-label">Textarea</span> <span class="radio-label">TextArea</span>
</label> </label>
<label class="btn btn-radio" [class.active]="editForm.controls.editor.value === 'Dropdown'"> <label class="btn btn-radio" [class.active]="editForm.controls.editor.value === 'Dropdown'">
<input type="radio" class="radio-input" formControlName="editor" value="Dropdown" /> <input type="radio" class="radio-input" formControlName="editor" value="Dropdown" />

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

@ -9,7 +9,7 @@ import { Component, Input, OnDestroy, OnInit } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms'; import { FormControl, FormGroup, Validators } from '@angular/forms';
import { Observable, Subscription } from 'rxjs'; import { Observable, Subscription } from 'rxjs';
import { fadeAnimation } from 'shared'; import { fadeAnimation, StringFieldPropertiesDto } from 'shared';
@Component({ @Component({
selector: 'sqx-string-ui', selector: 'sqx-string-ui',
@ -25,24 +25,29 @@ export class StringUIComponent implements OnDestroy, OnInit {
@Input() @Input()
public editForm: FormGroup; public editForm: FormGroup;
@Input()
public properties: StringFieldPropertiesDto;
public hideAllowedValues: Observable<boolean>; public hideAllowedValues: Observable<boolean>;
public ngOnInit() { public ngOnInit() {
this.editForm.addControl('editor', this.editForm.setControl('editor',
new FormControl('Input', [ new FormControl(this.properties.editor, [
Validators.required Validators.required
])); ]));
this.editForm.addControl('placeholder',
new FormControl('', [ this.editForm.setControl('placeholder',
new FormControl(this.properties.placeholder, [
Validators.maxLength(100) Validators.maxLength(100)
])); ]));
this.editForm.addControl('allowedValues',
new FormControl(10, [])); this.editForm.setControl('allowedValues',
new FormControl(this.properties.allowedValues));
this.hideAllowedValues = this.hideAllowedValues =
Observable.of(false) Observable.of(this.properties.editor)
.merge(this.editForm.get('editor').valueChanges) .merge(this.editForm.get('editor').valueChanges)
.map(x => !x || x === 'Input' || x === 'Textarea'); .map(x => !x || x === 'Input' || x === 'TextArea');
this.editorSubscription = this.editorSubscription =
this.hideAllowedValues.subscribe(isSelection => { this.hideAllowedValues.subscribe(isSelection => {

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

@ -9,6 +9,8 @@ import { Component, Input, OnDestroy, OnInit } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms'; import { FormControl, FormGroup } from '@angular/forms';
import { Observable, Subscription } from 'rxjs'; import { Observable, Subscription } from 'rxjs';
import { StringFieldPropertiesDto } from 'shared';
@Component({ @Component({
selector: 'sqx-string-validation', selector: 'sqx-string-validation',
styleUrls: ['string-validation.component.scss'], styleUrls: ['string-validation.component.scss'],
@ -20,20 +22,27 @@ export class StringValidationComponent implements OnDestroy, OnInit {
@Input() @Input()
public editForm: FormGroup; public editForm: FormGroup;
@Input()
public properties: StringFieldPropertiesDto;
public hidePatternMessage: Observable<boolean>; public hidePatternMessage: Observable<boolean>;
public hideDefaultValue: Observable<boolean>; public hideDefaultValue: Observable<boolean>;
public ngOnInit() { public ngOnInit() {
this.editForm.addControl('maxLength', this.editForm.setControl('maxLength',
new FormControl()); new FormControl(this.properties.maxLength));
this.editForm.addControl('minLength',
new FormControl()); this.editForm.setControl('minLength',
this.editForm.addControl('pattern', new FormControl(this.properties.minLength));
new FormControl());
this.editForm.addControl('patternMessage', this.editForm.setControl('pattern',
new FormControl()); new FormControl(this.properties.pattern));
this.editForm.addControl('defaultValue',
new FormControl()); this.editForm.setControl('patternMessage',
new FormControl(this.properties.patternMessage));
this.editForm.setControl('defaultValue',
new FormControl(this.properties.defaultValue));
this.hideDefaultValue = this.hideDefaultValue =
Observable.of(false) Observable.of(false)

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

@ -69,7 +69,7 @@ export class SchemaFormComponent implements OnInit {
public createSchema() { public createSchema() {
this.createForm.markAsTouched(); this.createForm.markAsTouched();
if (this.createForm.valid) { if (this.createForm.valid && this.authService.user) {
this.createForm.disable(); this.createForm.disable();
const name = this.createForm.get('name').value; const name = this.createForm.get('name').value;

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

@ -6,7 +6,7 @@
*/ */
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { FormBuilder, FormControl } from '@angular/forms'; import { FormControl } from '@angular/forms';
import { BehaviorSubject, Observable } from 'rxjs'; import { BehaviorSubject, Observable } from 'rxjs';
import { import {
@ -57,7 +57,6 @@ export class SchemasPageComponent extends AppComponentBase {
}); });
constructor(apps: AppsStoreService, notifications: NotificationService, users: UsersProviderService, constructor(apps: AppsStoreService, notifications: NotificationService, users: UsersProviderService,
private readonly formBuilder: FormBuilder,
private readonly schemasService: SchemasService private readonly schemasService: SchemasService
) { ) {
super(apps, notifications, users); super(apps, notifications, users);

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

@ -110,7 +110,7 @@ export class ClientComponent {
.subscribe(token => { .subscribe(token => {
this.appClientToken = token; this.appClientToken = token;
this.modalDialog.show(); this.modalDialog.show();
}, error => { }, _ => {
this.notifications.notify(Notification.error('Failed to retrieve access token. Please retry.')); this.notifications.notify(Notification.error('Failed to retrieve access token. Please retry.'));
}); });
} }

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

@ -81,10 +81,9 @@ export class ContributorsPageComponent extends AppComponentBase implements OnIni
]] ]]
}); });
constructor(apps: AppsStoreService, notifications: NotificationService, users: UsersProviderService, constructor(apps: AppsStoreService, notifications: NotificationService, users: UsersProviderService, usersService: UsersService,
private readonly appContributorsService: AppContributorsService, private readonly appContributorsService: AppContributorsService,
private readonly messageBus: MessageBus, private readonly messageBus: MessageBus,
private readonly usersService: UsersService,
private readonly authService: AuthService, private readonly authService: AuthService,
private readonly formBuilder: FormBuilder private readonly formBuilder: FormBuilder
) { ) {
@ -94,7 +93,7 @@ export class ContributorsPageComponent extends AppComponentBase implements OnIni
} }
public ngOnInit() { public ngOnInit() {
this.currentUserId = this.authService.user.id; this.currentUserId = this.authService.user!.id;
this.load(); this.load();
} }

10
src/Squidex/app/framework/angular/action.spec.ts

@ -13,15 +13,19 @@ class MockupObject {
public isDestroyCalled = false; public isDestroyCalled = false;
@Action() @Action()
public event1 = new Subject<string>().map(x => { return { type: 'MOCK_ACTION' }; }); public event1 = new Subject<string>().map(_ => { return { type: 'MOCK_ACTION' }; });
@Action() @Action()
public event2 = new Subject<string>().map(x => { return { type: 'MOCK_ACTION' }; }); public event2 = new Subject<string>().map(_ => { return { type: 'MOCK_ACTION' }; });
constructor(private readonly store: any) { } constructor(private readonly store: any) { }
public log() {
this.store.log();
}
public init() { public init() {
this.event2 = new Subject<string>().map(x => { return { type: 'MOCK_ACTION' }; }); this.event2 = new Subject<string>().map(_ => { return { type: 'MOCK_ACTION' }; });
} }
public ngOnDestroy() { public ngOnDestroy() {

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

@ -44,7 +44,7 @@ export const SQX_AUTOCOMPLETE_CONTROL_VALUE_ACCESSOR: any = {
providers: [SQX_AUTOCOMPLETE_CONTROL_VALUE_ACCESSOR] providers: [SQX_AUTOCOMPLETE_CONTROL_VALUE_ACCESSOR]
}) })
export class AutocompleteComponent implements ControlValueAccessor, OnDestroy, OnInit { export class AutocompleteComponent implements ControlValueAccessor, OnDestroy, OnInit {
private subscription: Subscription | null = null; private subscription: Subscription;
private changeCallback: (value: any) => void = NOOP; private changeCallback: (value: any) => void = NOOP;
private touchedCallback: () => void = NOOP; private touchedCallback: () => void = NOOP;
@ -109,7 +109,7 @@ export class AutocompleteComponent implements ControlValueAccessor, OnDestroy, O
} }
}) })
.filter(q => !!q && !!this.source) .filter(q => !!q && !!this.source)
.switchMap(q => this.source.find(q)).catch(error => Observable.of([])) .switchMap(q => this.source.find(q)).catch(_ => Observable.of([]))
.subscribe(r => { .subscribe(r => {
this.reset(); this.reset();
this.items = r || []; this.items = r || [];
@ -158,7 +158,7 @@ export class AutocompleteComponent implements ControlValueAccessor, OnDestroy, O
this.selectIndex(this.itemSelection + 1); this.selectIndex(this.itemSelection + 1);
} }
public chooseItem(selection: AutocompleteItem = null) { public chooseItem(selection: AutocompleteItem | null = null) {
if (!selection) { if (!selection) {
selection = this.items[this.itemSelection]; selection = this.items[this.itemSelection];
} }

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

@ -1,5 +1,5 @@
<div class="btn-group" [class.open]="isOpen"> <div class="btn-group" [class.open]="isOpen">
<button type="button" class="btn btn-secondary btn-sm" (click)="toggleOpen()" [style.background]="selectedColor.toString()"></button> <button type="button" class="btn btn-secondary btn-sm" [disabled]="isDisabled" (click)="toggleOpen()" [style.background]="selectedColor.toString()"></button>
<div class="dropdown-menu dropdown-menu-{{dropdownSide}}"> <div class="dropdown-menu dropdown-menu-{{dropdownSide}}">
<div class="color-palette"> <div class="color-palette">

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

@ -42,8 +42,11 @@ export class ColorPickerComponent implements ControlValueAccessor {
@Input() @Input()
public isOpen = false; public isOpen = false;
@Input()
public isDisabled = false;
constructor(private readonly element: ElementRef) { constructor(private readonly element: ElementRef) {
this.updateColor(); this.updateColor(Color.BLACK);
} }
public writeValue(value: any) { public writeValue(value: any) {
@ -51,6 +54,7 @@ export class ColorPickerComponent implements ControlValueAccessor {
} }
public setDisabledState(isDisabled: boolean): void { public setDisabledState(isDisabled: boolean): void {
this.isDisabled = isDisabled;
} }
public registerOnChange(fn: any) { public registerOnChange(fn: any) {
@ -100,7 +104,7 @@ export class ColorPickerComponent implements ControlValueAccessor {
} }
} }
private updateColor(color?: Color) { private updateColor(color: Color) {
let hasColor = false; let hasColor = false;
try { try {
this.selectedColor = Color.fromValue(color); this.selectedColor = Color.fromValue(color);

14
src/Squidex/app/framework/angular/date-time.pipes.ts

@ -14,7 +14,7 @@ import { Duration } from './../utils/duration';
name: 'shortDate' name: 'shortDate'
}) })
export class ShortDatePipe { export class ShortDatePipe {
public transform(value: DateTime, args: string[]): any { public transform(value: DateTime): any {
return value.toStringFormat('DD.MMM'); return value.toStringFormat('DD.MMM');
} }
} }
@ -23,7 +23,7 @@ export class ShortDatePipe {
name: 'month' name: 'month'
}) })
export class MonthPipe { export class MonthPipe {
public transform(value: DateTime, args: string[]): any { public transform(value: DateTime): any {
return value.toStringFormat('MMMM'); return value.toStringFormat('MMMM');
} }
} }
@ -32,7 +32,7 @@ export class MonthPipe {
name: 'fromNow' name: 'fromNow'
}) })
export class FromNowPipe { export class FromNowPipe {
public transform(value: DateTime, args: string[]): any { public transform(value: DateTime): any {
return value.toFromNow(); return value.toFromNow();
} }
} }
@ -41,7 +41,7 @@ export class FromNowPipe {
name: 'dayOfWeek' name: 'dayOfWeek'
}) })
export class DayOfWeekPipe { export class DayOfWeekPipe {
public transform(value: DateTime, args: string[]): any { public transform(value: DateTime): any {
return value.toStringFormat('dd'); return value.toStringFormat('dd');
} }
} }
@ -50,7 +50,7 @@ export class DayOfWeekPipe {
name: 'day' name: 'day'
}) })
export class DayPipe { export class DayPipe {
public transform(value: DateTime, args: string[]): any { public transform(value: DateTime): any {
return value.toStringFormat('DD'); return value.toStringFormat('DD');
} }
} }
@ -59,7 +59,7 @@ export class DayPipe {
name: 'shortTime' name: 'shortTime'
}) })
export class ShortTimePipe { export class ShortTimePipe {
public transform(value: DateTime, args: string[]): any { public transform(value: DateTime): any {
return value.toStringFormat('HH:mm'); return value.toStringFormat('HH:mm');
} }
} }
@ -68,7 +68,7 @@ export class ShortTimePipe {
name: 'duration' name: 'duration'
}) })
export class DurationPipe { export class DurationPipe {
public transform(value: Duration, args: string[]): any { public transform(value: Duration): any {
return value.toString(); return value.toString();
} }
} }

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

@ -5,7 +5,7 @@
* Copyright (c) Sebastian Stehle. All rights reserved * Copyright (c) Sebastian Stehle. All rights reserved
*/ */
import { Directive, ElementRef, HostListener, Renderer } from '@angular/core'; import { Directive, ElementRef, HostListener } from '@angular/core';
import { DragService } from './../services/drag.service'; import { DragService } from './../services/drag.service';
import { Vec2 } from './../utils/vec2'; import { Vec2 } from './../utils/vec2';
@ -16,7 +16,6 @@ import { Vec2 } from './../utils/vec2';
export class ImageDropDirective { export class ImageDropDirective {
constructor( constructor(
private readonly element: ElementRef, private readonly element: ElementRef,
private readonly renderer: Renderer,
private readonly dragService: DragService private readonly dragService: DragService
) { ) {
} }

2
src/Squidex/app/framework/angular/money.pipe.ts

@ -19,7 +19,7 @@ export class MoneyPipe {
) { ) {
} }
public transform(value: number, args: string[]): any { public transform(value: number): any {
const money = value.toFixed(2).toString(); const money = value.toFixed(2).toString();
let result = money.substr(0, money.length - 3) + this.separator.value + '<span class="decimal">' + money.substr(money.length - 2, 2) + '</span>'; let result = money.substr(0, money.length - 3) + this.separator.value + '<span class="decimal">' + money.substr(money.length - 2, 2) + '</span>';

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

@ -100,7 +100,7 @@ export class SliderComponent implements ControlValueAccessor {
this.mouseUpSubscription = this.mouseUpSubscription =
this.renderer.listenGlobal('window', 'mouseup', (e: MouseEvent) => { this.renderer.listenGlobal('window', 'mouseup', (e: MouseEvent) => {
this.onMouseUp(e); this.onMouseUp();
}); });
this.renderer.setElementClass(this.thumb.nativeElement, 'focused', true); this.renderer.setElementClass(this.thumb.nativeElement, 'focused', true);
@ -125,7 +125,7 @@ export class SliderComponent implements ControlValueAccessor {
return false; return false;
} }
private onMouseUp(event: MouseEvent) { private onMouseUp() {
this.updateValue(); this.updateValue();
setTimeout(() => { setTimeout(() => {

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

@ -5,7 +5,7 @@
* Copyright (c) Sebastian Stehle. All rights reserved * Copyright (c) Sebastian Stehle. All rights reserved
*/ */
import { Component, OnInit, Renderer } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { UserReportConfig } from './../configurations'; import { UserReportConfig } from './../configurations';
@ -14,9 +14,7 @@ import { UserReportConfig } from './../configurations';
template: '' template: ''
}) })
export class UserReportComponent implements OnInit { export class UserReportComponent implements OnInit {
constructor(config: UserReportConfig, constructor(config: UserReportConfig) {
private readonly renderer: Renderer
) {
window['_urq'] = window['_urq'] || []; window['_urq'] = window['_urq'] || [];
window['_urq'].push(['initSite', config.siteId]); window['_urq'].push(['initSite', config.siteId]);
} }

2
src/Squidex/app/shared/app-component-base.ts

@ -24,7 +24,7 @@ export abstract class AppComponentBase {
} }
public appName(): Observable<string> { public appName(): Observable<string> {
return this.appsStore.selectedApp.map(a => a.name); return this.appsStore.selectedApp.map(a => a!.name);
} }
public userEmail(userId: string): Observable<string> { public userEmail(userId: string): Observable<string> {

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

@ -28,7 +28,7 @@ export class DashboardLinkDirective implements OnInit, OnDestroy {
public ngOnInit() { public ngOnInit() {
this.appSubscription = this.appSubscription =
this.appsStore.selectedApp.subscribe(app => { this.appsStore.selectedApp.subscribe(app => {
this.url = this.router.createUrlTree(['app', app.name]).toString(); this.url = this.router.createUrlTree(['app', app!.name]).toString();
this.renderer.setElementAttribute(this.element.nativeElement, 'href', this.url); this.renderer.setElementAttribute(this.element.nativeElement, 'href', this.url);
}); });

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

@ -60,11 +60,11 @@ export class HistoryComponent extends AppComponentBase {
return this.userPicture(parts[1]); return this.userPicture(parts[1]);
} }
return Observable.of(null); return Observable.of('');
} }
public format(message: string): Observable<string> { public format(message: string): Observable<string> {
let foundUserId: string; let foundUserId: string | null = null;
message = message.replace(/{([^\s:]*):([^}]*)}/, (match: string, type: string, id: string) => { message = message.replace(/{([^\s:]*):([^}]*)}/, (match: string, type: string, id: string) => {
if (type === 'user') { if (type === 'user') {

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

@ -41,7 +41,7 @@ export class Profile {
export class AuthService { export class AuthService {
private readonly userManager: UserManager; private readonly userManager: UserManager;
private readonly isAuthenticatedChanged$ = new Subject<boolean>(); private readonly isAuthenticatedChanged$ = new Subject<boolean>();
private loginCompleted = false; private loginCompleted: boolean | null = false;
private loginCache: Promise<boolean> | null = null; private loginCache: Promise<boolean> | null = null;
private currentUser: Profile | null = null; private currentUser: Profile | null = null;
@ -153,7 +153,7 @@ export class AuthService {
this.loginCache = null; this.loginCache = null;
this.loginCompleted = null; this.loginCompleted = null;
this.onAuthenticated(null); this.onDeauthenticated();
return false; return false;
}); });

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

@ -17,19 +17,29 @@ import {
import { AuthService } from './auth.service'; import { AuthService } from './auth.service';
export function createProperties(fieldType: string, values: {}): FieldPropertiesDto { export function createProperties(fieldType: string, values: {} | null = null): FieldPropertiesDto {
let properties: FieldPropertiesDto; let properties: FieldPropertiesDto;
switch (fieldType) { switch (fieldType) {
case 'number': case 'number':
properties = new NumberFieldPropertiesDto(); properties =
new NumberFieldPropertiesDto(
undefined, undefined, undefined, false,
undefined, undefined, undefined, undefined, undefined);
break; break;
case 'string': case 'string':
properties = new StringFieldPropertiesDto(); properties =
new StringFieldPropertiesDto(
undefined, undefined, undefined, false,
undefined, undefined, undefined, undefined, undefined, undefined, undefined);
break; break;
default:
throw 'Invalid properties type';
} }
if (values) {
Object.assign(properties, values); Object.assign(properties, values);
}
return properties; return properties;
} }
@ -61,6 +71,7 @@ export class SchemaDetailsDto {
export class FieldDto { export class FieldDto {
constructor( constructor(
public readonly fieldId: number,
public readonly name: string, public readonly name: string,
public readonly isHidden: boolean, public readonly isHidden: boolean,
public readonly isDisabled: boolean, public readonly isDisabled: boolean,
@ -71,20 +82,21 @@ export class FieldDto {
export abstract class FieldPropertiesDto { export abstract class FieldPropertiesDto {
constructor( constructor(
public readonly label: string, public readonly label?: string,
public readonly hints: string, public readonly hints?: string,
public readonly placeholder: string, public readonly placeholder?: string,
public readonly isRequired: boolean public readonly isRequired: boolean = false
) { ) {
} }
} }
export class NumberFieldPropertiesDto extends FieldPropertiesDto { export class NumberFieldPropertiesDto extends FieldPropertiesDto {
constructor(label?: string, hints?: string, placeholder?: string, isRequired: boolean = false, constructor(label: string, hints: string, placeholder: string, isRequired: boolean,
public readonly defaultValue?: number | null, public readonly editor: string,
public readonly maxValue?: number | null, public readonly defaultValue: number | null,
public readonly minValue?: number | null, public readonly maxValue: number | null,
public readonly allowedValues?: number[] public readonly minValue: number | null,
public readonly allowedValues: number[] | undefined
) { ) {
super(label, hints, placeholder, isRequired); super(label, hints, placeholder, isRequired);
@ -93,13 +105,14 @@ export class NumberFieldPropertiesDto extends FieldPropertiesDto {
} }
export class StringFieldPropertiesDto extends FieldPropertiesDto { export class StringFieldPropertiesDto extends FieldPropertiesDto {
constructor(label?: string, hints?: string, placeholder?: string, isRequired: boolean = false, constructor(label: string, hints: string, placeholder: string, isRequired: boolean,
public readonly defaultValue?: string, public readonly editor: string,
public readonly pattern?: string, public readonly defaultValue: string,
public readonly patternMessage?: string, public readonly pattern: string,
public readonly minLength?: number | null, public readonly patternMessage: string,
public readonly maxLength?: number | null, public readonly minLength: number | null,
public readonly allowedValues?: string[] public readonly maxLength: number | null,
public readonly allowedValues: string[]
) { ) {
super(label, hints, placeholder, isRequired); super(label, hints, placeholder, isRequired);
@ -179,6 +192,7 @@ export class SchemasService {
item.properties); item.properties);
return new FieldDto( return new FieldDto(
item.fieldId,
item.name, item.name,
item.isHidden, item.isHidden,
item.isDisabled, item.isDisabled,
@ -218,4 +232,46 @@ export class SchemasService {
}) })
.catch(response => handleError('Failed to add field. Please reload.', response)); .catch(response => handleError('Failed to add field. Please reload.', response));
} }
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));
}
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));
}
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));
}
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));
}
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));
}
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));
}
} }

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

@ -6,7 +6,6 @@
*/ */
import { Component, OnDestroy, OnInit } from '@angular/core'; import { Component, OnDestroy, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { import {
AppDto, AppDto,
@ -37,9 +36,7 @@ export class AppsMenuComponent implements OnInit, OnDestroy {
public appName = FALLBACK_NAME; public appName = FALLBACK_NAME;
constructor( constructor(
private readonly appsStore: AppsStoreService, private readonly appsStore: AppsStoreService
private readonly router: Router,
private readonly route: ActivatedRoute
) { ) {
} }

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

@ -246,7 +246,7 @@
&-radio { &-radio {
& { & {
color: darken($color-border, 20%); color: $color-border-dark;
cursor: pointer; cursor: pointer;
border: 1px solid $color-border; border: 1px solid $color-border;
background: transparent; background: transparent;

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

@ -1,7 +1,7 @@
$color-background: #f4f8f9; $color-background: #f4f8f9;
$color-border: #dbe4eb; $color-border: #dbe4eb;
$color-border-dark: darken($color-border); $color-border-dark: darken($color-border, 20%);
$color-text: #373a3c; $color-text: #373a3c;
$color-empty: #777; $color-empty: #777;

3
src/Squidex/package.json

@ -11,7 +11,8 @@
"dev": "cpx node_modules/oidc-client/dist/oidc-client.min.js wwwroot/scripts/ & webpack-dev-server --config app-config/webpack.run.dev.js --inline --hot --port 3000", "dev": "cpx node_modules/oidc-client/dist/oidc-client.min.js wwwroot/scripts/ & webpack-dev-server --config app-config/webpack.run.dev.js --inline --hot --port 3000",
"build": "webpack --config app-config/webpack.run.prod.js --bail", "build": "webpack --config app-config/webpack.run.prod.js --bail",
"build:copy": "cpx node_modules/oidc-client/dist/oidc-client.min.js wwwroot/scripts/", "build:copy": "cpx node_modules/oidc-client/dist/oidc-client.min.js wwwroot/scripts/",
"build:clean": "rimraf wwwroot/build" "build:clean": "rimraf wwwroot/build",
"build:detailed": "webpack --config app-config/webpack.run.prod.js --display-error-details"
}, },
"dependencies": { "dependencies": {
"@angular/common": "2.2.3", "@angular/common": "2.2.3",

2
src/Squidex/tsconfig.json

@ -8,7 +8,7 @@
"experimentalDecorators": true, "experimentalDecorators": true,
"removeComments": false, "removeComments": false,
"noImplicitAny": true, "noImplicitAny": true,
"noUnusedLocals": false, "noUnusedLocals": true,
"noUnusedParameters": false, "noUnusedParameters": false,
"strictNullChecks": false, "strictNullChecks": false,
"suppressImplicitAnyIndexErrors": true, "suppressImplicitAnyIndexErrors": true,

1
src/Squidex/tslint.json

@ -50,7 +50,6 @@
"no-trailing-whitespace": true, "no-trailing-whitespace": true,
"no-unused-expression": true, "no-unused-expression": true,
"no-unused-variable": true,
"no-use-before-declare": true, "no-use-before-declare": true,
"no-var-keyword": true, "no-var-keyword": true,
"object-literal-sort-keys": false, "object-literal-sort-keys": false,

Loading…
Cancel
Save