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.Collections.Generic;
using System.Linq;
using Squidex.Core.Schemas;
using Squidex.Infrastructure.Reflection;
using Squidex.Read.Schemas.Repositories;
@ -20,11 +21,11 @@ namespace Squidex.Controllers.Api.Schemas.Models.Converters
{
{
typeof(NumberFieldProperties),
p => SimpleMapper.Map((NumberFieldProperties)p, new NumberFieldPropertiesDto())
p => Convert((NumberFieldProperties)p)
},
{
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>();
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
{
Name = field.Name,
IsHidden = field.IsHidden,
IsDisabled = field.IsDisabled,
Properties = fieldPropertiesDto
};
var fieldDto = SimpleMapper.Map(kvp.Value, new FieldDto { FieldId = kvp.Key, Properties = fieldPropertiesDto });
dto.Fields.Add(fieldDto);
}
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
{
/// <summary>
/// The id of the field.
/// </summary>
public long FieldId { get; set; }
/// <summary>
/// The name of the field. Must be unique within the schema.
/// </summary>

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

@ -6,6 +6,9 @@
// All rights reserved.
// ==========================================================================
using System.Collections.Immutable;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using NJsonSchema.Annotations;
using Squidex.Core.Schemas;
using Squidex.Infrastructure.Reflection;
@ -38,11 +41,19 @@ namespace Squidex.Controllers.Api.Schemas.Models
/// <summary>
/// The editor that is used to manage this field.
/// </summary>
[JsonConverter(typeof(StringEnumConverter))]
public NumberFieldEditor Editor { get; set; }
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.
// ==========================================================================
using System.Collections.Immutable;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using NJsonSchema.Annotations;
using Squidex.Core.Schemas;
using Squidex.Infrastructure.Reflection;
@ -48,11 +51,19 @@ namespace Squidex.Controllers.Api.Schemas.Models
/// <summary>
/// The editor that is used to manage this field.
/// </summary>
[JsonConverter(typeof(StringEnumConverter))]
public StringFieldEditor Editor { get; set; }
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="row">
<div class="col-xs-8">
<i class="field-icon icon-type-{{field.properties.fieldType}}"></i> {{field.name}}
<div class="col-xs-6">
<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 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">
<button type="button" class="btn btn-secondary field-edit-button" [class.active]="isEditing" (click)="toggleEditing()">
<i class="icon-settings"></i>
</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>
</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>
@ -38,7 +69,7 @@
</div>
</div>
<div class="field-details-tab" *ngIf="selectedTab == 0">
<div class="field-details-tab" [class.hidden]="selectedTab !== 0">
<div class="form-group row">
<label for="field-label" class="col-xs-3 col-form-label">Label</label>
@ -80,24 +111,24 @@
</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 *ngSwitchCase="'number'">
<sqx-number-validation [editForm]="editForm"></sqx-number-validation>
<sqx-number-validation [editForm]="editForm" [properties]="field.properties"></sqx-number-validation>
</div>
<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 class="field-details-tab" *ngIf="selectedTab == 2">
<div class="field-details-tab" [class.hidden]="selectedTab !== 2">
<div [ngSwitch]="field.properties.fieldType">
<div *ngSwitchCase="'number'">
<sqx-number-ui [editForm]="editForm"></sqx-number-ui>
<sqx-number-ui [editForm]="editForm" [properties]="field.properties"></sqx-number-ui>
</div>
<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>

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

@ -7,6 +7,31 @@ $field-header: #e7ebef;
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 {
&-summary {
padding: 15px 20px;
@ -14,11 +39,11 @@ $field-header: #e7ebef;
}
&-icon {
color: darken($color-border, 15%);
margin-right: 1rem;
color: $color-border-dark;
font-size: 1.2rem;
font-weight: normal;
vertical-align: middle;
margin-right: 1rem;
}
&-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 {
& {
position: relative;
@ -59,6 +99,12 @@ $field-header: #e7ebef;
padding: 15px 20px;
}
}
.tag {
@include opacity(.9);
min-width: 60px;
max-width: 90px;
}
}
.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 {
createProperties,
fadeAnimation,
FieldDto
FieldDto,
ModalView
} from 'shared';
@Component({
@ -23,17 +24,36 @@ import {
]
})
export class FieldComponent implements OnInit {
private oldValue: any;
public dropdown = new ModalView(false, true);
@Input()
public field: FieldDto;
@Output()
public hidden = new EventEmitter<FieldDto>();
@Output()
public shown = new EventEmitter<FieldDto>();
@Output()
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 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 =
this.formBuilder.group({
label: ['',
@ -53,7 +73,7 @@ export class FieldComponent implements OnInit {
}
public ngOnInit() {
this.resetForm(this.field.properties);
this.resetForm();
}
public save() {
@ -64,6 +84,7 @@ export class FieldComponent implements OnInit {
const field =
new FieldDto(
this.field.fieldId,
this.field.name,
this.field.isHidden,
this.field.isHidden,
@ -74,7 +95,7 @@ export class FieldComponent implements OnInit {
}
public cancel() {
this.resetForm(this.oldValue);
this.resetForm();
}
public toggleEditing() {
@ -85,20 +106,8 @@ export class FieldComponent implements OnInit {
this.selectedTab = tab;
}
private resetForm(properties: any) {
this.editForm.reset();
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);
private resetForm() {
this.editForm.reset(this.field.properties);
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-header">
<div>
<h3 class="panel-title">{{schemaName | async}}</h3>
<h3 class="panel-title">{{schemaName}}</h3>
<a class="panel-close" routerLink="../">
<i class="icon-close"></i>
@ -13,7 +13,13 @@
<div class="panel-content">
<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 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
*/
import { Component, OnInit } from '@angular/core';
import { Component, OnDestroy, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ActivatedRoute } from '@angular/router';
import { Observable } from 'rxjs';
import { Subscription } from 'rxjs';
import {
AddFieldDto,
AppComponentBase,
AppsStoreService,
createProperties,
FieldDto,
FieldPropertiesDto,
HistoryChannelUpdated,
ImmutableArray,
MessageBus,
NotificationService,
NumberFieldPropertiesDto,
SchemasService,
StringFieldPropertiesDto,
UpdateFieldDto,
UsersProviderService
} from 'shared';
@ -31,12 +30,15 @@ import {
styleUrls: ['./schema-page.component.scss'],
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[] = [
'string',
'number'
];
public schemaName: string;
public schemaFields = ImmutableArray.empty<FieldDto>();
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,
private readonly schemasService: SchemasService,
private readonly messageBus: MessageBus,
@ -66,14 +64,23 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
super(apps, notifications, users);
}
public ngOnDestroy() {
this.routerSubscription.unsubscribe();
}
public ngOnInit() {
this.routerSubscription =
this.route.params.map(p => p['schemaName']).subscribe(name => {
this.schemaName = name;
this.reset();
this.load();
});
}
public load() {
this.schemaName.combineLatest(this.appName(), (schemaName, appName) => { return { schemaName, appName }; })
.do(() => this.reset())
.switchMap(p => this.schemasService.getSchema(p.appName, p.schemaName)).retry(2)
this.appName()
.switchMap(app => this.schemasService.getSchema(app, this.schemaName)).retry(2)
.subscribe(dto => {
this.schemaFields = ImmutableArray.of(dto.fields);
}, error => {
@ -81,8 +88,66 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
});
}
public updateField(field: FieldDto, newField: FieldDto) {
this.updateFields(this.schemaFields.replace(field, newField));
public enableField(field: FieldDto) {
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() {
@ -91,15 +156,7 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
if (this.addFieldForm.valid) {
this.addFieldForm.disable();
let properties: FieldPropertiesDto;
switch (this.addFieldForm.get('type').value) {
case 'string':
properties = new StringFieldPropertiesDto();
break;
case 'number':
properties = new NumberFieldPropertiesDto();
}
const properties = createProperties(this.addFieldForm.get('type').value);
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.schemaName.combineLatest(this.appName(), (schemaName, appName) => { return { schemaName, appName }; })
.switchMap(p => this.schemasService.postField(p.appName, p.schemaName, dto))
this.appName()
.switchMap(app => this.schemasService.postField(app, this.schemaName, 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();
}, error => {
this.notifyError(error);
@ -124,6 +188,10 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
this.schemaFields = ImmutableArray.empty<FieldDto>();
}
public updateField(field: FieldDto, newField: FieldDto) {
this.updateFields(this.schemaFields.replace(field, newField));
}
private updateFields(fields: ImmutableArray<FieldDto>) {
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 { Observable, Subscription } from 'rxjs';
import { fadeAnimation, FloatConverter } from 'shared';
import {
fadeAnimation,
FloatConverter,
NumberFieldPropertiesDto
} from 'shared';
@Component({
selector: 'sqx-number-ui',
@ -25,24 +29,27 @@ export class NumberUIComponent implements OnDestroy, OnInit {
@Input()
public editForm: FormGroup;
@Input()
public properties: NumberFieldPropertiesDto;
public converter = new FloatConverter();
public hideAllowedValues: Observable<boolean>;
public ngOnInit() {
this.editForm.addControl('editor',
new FormControl('Input', [
new FormControl(this.properties.editor, [
Validators.required
]));
this.editForm.addControl('placeholder',
new FormControl('', [
new FormControl(this.properties.placeholder, [
Validators.maxLength(100)
]));
this.editForm.addControl('allowedValues',
new FormControl(undefined, []));
new FormControl(this.properties.allowedValues, []));
this.hideAllowedValues =
Observable.of(false)
Observable.of(this.properties.editor)
.merge(this.editForm.get('editor').valueChanges)
.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 { Observable } from 'rxjs';
import { NumberFieldPropertiesDto } from 'shared';
@Component({
selector: 'sqx-number-validation',
styleUrls: ['number-validation.component.scss'],
@ -18,22 +20,23 @@ export class NumberValidationComponent implements OnInit {
@Input()
public editForm: FormGroup;
@Input()
public properties: NumberFieldPropertiesDto;
public hideDefaultValue: Observable<boolean>;
public ngOnInit() {
this.editForm.addControl('maxValue',
new FormControl());
new FormControl(this.properties.maxValue));
this.editForm.addControl('minValue',
new FormControl());
this.editForm.addControl('pattern',
new FormControl());
this.editForm.addControl('patternMessage',
new FormControl());
new FormControl(this.properties.minValue));
this.editForm.addControl('defaultValue',
new FormControl());
new FormControl(this.properties.defaultValue));
this.hideDefaultValue =
Observable.of(false)
Observable.of(this.properties.isRequired)
.merge(this.editForm.get('isRequired').valueChanges)
.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>
</label>
<label class="btn btn-radio" [class.active]="editForm.controls.editor.value === 'Textarea'">
<input type="radio" class="radio-input" formControlName="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" />
<i class="icon-control-textarea"></i>
<span class="radio-label">Textarea</span>
<span class="radio-label">TextArea</span>
</label>
<label class="btn btn-radio" [class.active]="editForm.controls.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 { Observable, Subscription } from 'rxjs';
import { fadeAnimation } from 'shared';
import { fadeAnimation, StringFieldPropertiesDto } from 'shared';
@Component({
selector: 'sqx-string-ui',
@ -25,24 +25,29 @@ export class StringUIComponent implements OnDestroy, OnInit {
@Input()
public editForm: FormGroup;
@Input()
public properties: StringFieldPropertiesDto;
public hideAllowedValues: Observable<boolean>;
public ngOnInit() {
this.editForm.addControl('editor',
new FormControl('Input', [
this.editForm.setControl('editor',
new FormControl(this.properties.editor, [
Validators.required
]));
this.editForm.addControl('placeholder',
new FormControl('', [
this.editForm.setControl('placeholder',
new FormControl(this.properties.placeholder, [
Validators.maxLength(100)
]));
this.editForm.addControl('allowedValues',
new FormControl(10, []));
this.editForm.setControl('allowedValues',
new FormControl(this.properties.allowedValues));
this.hideAllowedValues =
Observable.of(false)
Observable.of(this.properties.editor)
.merge(this.editForm.get('editor').valueChanges)
.map(x => !x || x === 'Input' || x === 'Textarea');
.map(x => !x || x === 'Input' || x === 'TextArea');
this.editorSubscription =
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 { Observable, Subscription } from 'rxjs';
import { StringFieldPropertiesDto } from 'shared';
@Component({
selector: 'sqx-string-validation',
styleUrls: ['string-validation.component.scss'],
@ -20,20 +22,27 @@ export class StringValidationComponent implements OnDestroy, OnInit {
@Input()
public editForm: FormGroup;
@Input()
public properties: StringFieldPropertiesDto;
public hidePatternMessage: Observable<boolean>;
public hideDefaultValue: Observable<boolean>;
public ngOnInit() {
this.editForm.addControl('maxLength',
new FormControl());
this.editForm.addControl('minLength',
new FormControl());
this.editForm.addControl('pattern',
new FormControl());
this.editForm.addControl('patternMessage',
new FormControl());
this.editForm.addControl('defaultValue',
new FormControl());
this.editForm.setControl('maxLength',
new FormControl(this.properties.maxLength));
this.editForm.setControl('minLength',
new FormControl(this.properties.minLength));
this.editForm.setControl('pattern',
new FormControl(this.properties.pattern));
this.editForm.setControl('patternMessage',
new FormControl(this.properties.patternMessage));
this.editForm.setControl('defaultValue',
new FormControl(this.properties.defaultValue));
this.hideDefaultValue =
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() {
this.createForm.markAsTouched();
if (this.createForm.valid) {
if (this.createForm.valid && this.authService.user) {
this.createForm.disable();
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 { FormBuilder, FormControl } from '@angular/forms';
import { FormControl } from '@angular/forms';
import { BehaviorSubject, Observable } from 'rxjs';
import {
@ -57,7 +57,6 @@ export class SchemasPageComponent extends AppComponentBase {
});
constructor(apps: AppsStoreService, notifications: NotificationService, users: UsersProviderService,
private readonly formBuilder: FormBuilder,
private readonly schemasService: SchemasService
) {
super(apps, notifications, users);

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

@ -110,7 +110,7 @@ export class ClientComponent {
.subscribe(token => {
this.appClientToken = token;
this.modalDialog.show();
}, error => {
}, _ => {
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 messageBus: MessageBus,
private readonly usersService: UsersService,
private readonly authService: AuthService,
private readonly formBuilder: FormBuilder
) {
@ -94,7 +93,7 @@ export class ContributorsPageComponent extends AppComponentBase implements OnIni
}
public ngOnInit() {
this.currentUserId = this.authService.user.id;
this.currentUserId = this.authService.user!.id;
this.load();
}

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

@ -13,15 +13,19 @@ class MockupObject {
public isDestroyCalled = false;
@Action()
public event1 = new Subject<string>().map(x => { return { type: 'MOCK_ACTION' }; });
public event1 = new Subject<string>().map(_ => { return { type: 'MOCK_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) { }
public log() {
this.store.log();
}
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() {

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]
})
export class AutocompleteComponent implements ControlValueAccessor, OnDestroy, OnInit {
private subscription: Subscription | null = null;
private subscription: Subscription;
private changeCallback: (value: any) => void = NOOP;
private touchedCallback: () => void = NOOP;
@ -109,7 +109,7 @@ export class AutocompleteComponent implements ControlValueAccessor, OnDestroy, O
}
})
.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 => {
this.reset();
this.items = r || [];
@ -158,7 +158,7 @@ export class AutocompleteComponent implements ControlValueAccessor, OnDestroy, O
this.selectIndex(this.itemSelection + 1);
}
public chooseItem(selection: AutocompleteItem = null) {
public chooseItem(selection: AutocompleteItem | null = null) {
if (!selection) {
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">
<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="color-palette">

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

@ -42,8 +42,11 @@ export class ColorPickerComponent implements ControlValueAccessor {
@Input()
public isOpen = false;
@Input()
public isDisabled = false;
constructor(private readonly element: ElementRef) {
this.updateColor();
this.updateColor(Color.BLACK);
}
public writeValue(value: any) {
@ -51,6 +54,7 @@ export class ColorPickerComponent implements ControlValueAccessor {
}
public setDisabledState(isDisabled: boolean): void {
this.isDisabled = isDisabled;
}
public registerOnChange(fn: any) {
@ -100,7 +104,7 @@ export class ColorPickerComponent implements ControlValueAccessor {
}
}
private updateColor(color?: Color) {
private updateColor(color: Color) {
let hasColor = false;
try {
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'
})
export class ShortDatePipe {
public transform(value: DateTime, args: string[]): any {
public transform(value: DateTime): any {
return value.toStringFormat('DD.MMM');
}
}
@ -23,7 +23,7 @@ export class ShortDatePipe {
name: 'month'
})
export class MonthPipe {
public transform(value: DateTime, args: string[]): any {
public transform(value: DateTime): any {
return value.toStringFormat('MMMM');
}
}
@ -32,7 +32,7 @@ export class MonthPipe {
name: 'fromNow'
})
export class FromNowPipe {
public transform(value: DateTime, args: string[]): any {
public transform(value: DateTime): any {
return value.toFromNow();
}
}
@ -41,7 +41,7 @@ export class FromNowPipe {
name: 'dayOfWeek'
})
export class DayOfWeekPipe {
public transform(value: DateTime, args: string[]): any {
public transform(value: DateTime): any {
return value.toStringFormat('dd');
}
}
@ -50,7 +50,7 @@ export class DayOfWeekPipe {
name: 'day'
})
export class DayPipe {
public transform(value: DateTime, args: string[]): any {
public transform(value: DateTime): any {
return value.toStringFormat('DD');
}
}
@ -59,7 +59,7 @@ export class DayPipe {
name: 'shortTime'
})
export class ShortTimePipe {
public transform(value: DateTime, args: string[]): any {
public transform(value: DateTime): any {
return value.toStringFormat('HH:mm');
}
}
@ -68,7 +68,7 @@ export class ShortTimePipe {
name: 'duration'
})
export class DurationPipe {
public transform(value: Duration, args: string[]): any {
public transform(value: Duration): any {
return value.toString();
}
}

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

@ -5,7 +5,7 @@
* 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 { Vec2 } from './../utils/vec2';
@ -16,7 +16,6 @@ import { Vec2 } from './../utils/vec2';
export class ImageDropDirective {
constructor(
private readonly element: ElementRef,
private readonly renderer: Renderer,
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();
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.renderer.listenGlobal('window', 'mouseup', (e: MouseEvent) => {
this.onMouseUp(e);
this.onMouseUp();
});
this.renderer.setElementClass(this.thumb.nativeElement, 'focused', true);
@ -125,7 +125,7 @@ export class SliderComponent implements ControlValueAccessor {
return false;
}
private onMouseUp(event: MouseEvent) {
private onMouseUp() {
this.updateValue();
setTimeout(() => {

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

@ -5,7 +5,7 @@
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Component, OnInit, Renderer } from '@angular/core';
import { Component, OnInit } from '@angular/core';
import { UserReportConfig } from './../configurations';
@ -14,9 +14,7 @@ import { UserReportConfig } from './../configurations';
template: ''
})
export class UserReportComponent implements OnInit {
constructor(config: UserReportConfig,
private readonly renderer: Renderer
) {
constructor(config: UserReportConfig) {
window['_urq'] = window['_urq'] || [];
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> {
return this.appsStore.selectedApp.map(a => a.name);
return this.appsStore.selectedApp.map(a => a!.name);
}
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() {
this.appSubscription =
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);
});

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

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

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

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

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

@ -17,19 +17,29 @@ import {
import { AuthService } from './auth.service';
export function createProperties(fieldType: string, values: {}): FieldPropertiesDto {
export function createProperties(fieldType: string, values: {} | null = null): FieldPropertiesDto {
let properties: FieldPropertiesDto;
switch (fieldType) {
case 'number':
properties = new NumberFieldPropertiesDto();
properties =
new NumberFieldPropertiesDto(
undefined, undefined, undefined, false,
undefined, undefined, undefined, undefined, undefined);
break;
case 'string':
properties = new StringFieldPropertiesDto();
properties =
new StringFieldPropertiesDto(
undefined, undefined, undefined, false,
undefined, undefined, undefined, undefined, undefined, undefined, undefined);
break;
default:
throw 'Invalid properties type';
}
if (values) {
Object.assign(properties, values);
}
return properties;
}
@ -61,6 +71,7 @@ export class SchemaDetailsDto {
export class FieldDto {
constructor(
public readonly fieldId: number,
public readonly name: string,
public readonly isHidden: boolean,
public readonly isDisabled: boolean,
@ -71,20 +82,21 @@ export class FieldDto {
export abstract class FieldPropertiesDto {
constructor(
public readonly label: string,
public readonly hints: string,
public readonly placeholder: string,
public readonly isRequired: boolean
public readonly label?: string,
public readonly hints?: string,
public readonly placeholder?: string,
public readonly isRequired: boolean = false
) {
}
}
export class NumberFieldPropertiesDto extends FieldPropertiesDto {
constructor(label?: string, hints?: string, placeholder?: string, isRequired: boolean = false,
public readonly defaultValue?: number | null,
public readonly maxValue?: number | null,
public readonly minValue?: number | null,
public readonly allowedValues?: number[]
constructor(label: string, hints: string, placeholder: string, isRequired: boolean,
public readonly editor: string,
public readonly defaultValue: number | null,
public readonly maxValue: number | null,
public readonly minValue: number | null,
public readonly allowedValues: number[] | undefined
) {
super(label, hints, placeholder, isRequired);
@ -93,13 +105,14 @@ export class NumberFieldPropertiesDto extends FieldPropertiesDto {
}
export class StringFieldPropertiesDto extends FieldPropertiesDto {
constructor(label?: string, hints?: string, placeholder?: string, isRequired: boolean = false,
public readonly defaultValue?: string,
public readonly pattern?: string,
public readonly patternMessage?: string,
public readonly minLength?: number | null,
public readonly maxLength?: number | null,
public readonly allowedValues?: string[]
constructor(label: string, hints: string, placeholder: string, isRequired: boolean,
public readonly editor: string,
public readonly defaultValue: string,
public readonly pattern: string,
public readonly patternMessage: string,
public readonly minLength: number | null,
public readonly maxLength: number | null,
public readonly allowedValues: string[]
) {
super(label, hints, placeholder, isRequired);
@ -179,6 +192,7 @@ export class SchemasService {
item.properties);
return new FieldDto(
item.fieldId,
item.name,
item.isHidden,
item.isDisabled,
@ -218,4 +232,46 @@ export class SchemasService {
})
.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 { ActivatedRoute, Router } from '@angular/router';
import {
AppDto,
@ -37,9 +36,7 @@ export class AppsMenuComponent implements OnInit, OnDestroy {
public appName = FALLBACK_NAME;
constructor(
private readonly appsStore: AppsStoreService,
private readonly router: Router,
private readonly route: ActivatedRoute
private readonly appsStore: AppsStoreService
) {
}

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

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

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

@ -1,7 +1,7 @@
$color-background: #f4f8f9;
$color-border: #dbe4eb;
$color-border-dark: darken($color-border);
$color-border-dark: darken($color-border, 20%);
$color-text: #373a3c;
$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",
"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:clean": "rimraf wwwroot/build"
"build:clean": "rimraf wwwroot/build",
"build:detailed": "webpack --config app-config/webpack.run.prod.js --display-error-details"
},
"dependencies": {
"@angular/common": "2.2.3",

2
src/Squidex/tsconfig.json

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

1
src/Squidex/tslint.json

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

Loading…
Cancel
Save