Browse Source

Merge pull request #95 from SebastianStehle/feature-lock-fields

Locking fields
pull/100/head
Sebastian Stehle 8 years ago
committed by GitHub
parent
commit
6ca82f9426
  1. 38
      src/Squidex.Domain.Apps.Core/Schemas/Field.cs
  2. 2
      src/Squidex.Domain.Apps.Core/Schemas/Field_Generic.cs
  3. 3
      src/Squidex.Domain.Apps.Core/Schemas/Json/JsonFieldModel.cs
  4. 6
      src/Squidex.Domain.Apps.Core/Schemas/Json/SchemaJsonSerializer.cs
  5. 10
      src/Squidex.Domain.Apps.Core/Schemas/Schema.cs
  6. 17
      src/Squidex.Domain.Apps.Events/Schemas/FieldLocked.cs
  7. 2
      src/Squidex.Domain.Apps.Events/Schemas/SchemaCreatedField.cs
  8. 10
      src/Squidex.Domain.Apps.Events/Schemas/Utils/SchemaEventDispatcher.cs
  9. 13
      src/Squidex.Domain.Apps.Read.MongoDb/Schemas/MongoSchemaRepository_EventHandling.cs
  10. 13
      src/Squidex.Domain.Apps.Read/Schemas/SchemaHistoryEventsCreator.cs
  11. 2
      src/Squidex.Domain.Apps.Write/Schemas/Commands/CreateSchemaField.cs
  12. 14
      src/Squidex.Domain.Apps.Write/Schemas/Commands/LockField.cs
  13. 19
      src/Squidex.Domain.Apps.Write/Schemas/SchemaCommandMiddleware.cs
  14. 16
      src/Squidex.Domain.Apps.Write/Schemas/SchemaDomainObject.cs
  15. 5
      src/Squidex/Controllers/Api/Schemas/Models/CreateSchemaFieldDto.cs
  16. 5
      src/Squidex/Controllers/Api/Schemas/Models/FieldDto.cs
  17. 34
      src/Squidex/Controllers/Api/Schemas/SchemaFieldsController.cs
  18. 48
      src/Squidex/app/features/schemas/pages/schema/field.component.html
  19. 11
      src/Squidex/app/features/schemas/pages/schema/field.component.scss
  20. 26
      src/Squidex/app/features/schemas/pages/schema/field.component.ts
  21. 1
      src/Squidex/app/features/schemas/pages/schema/schema-page.component.html
  22. 6
      src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts
  23. 4
      src/Squidex/app/features/webhooks/pages/webhook-events-page.component.html
  24. 30
      src/Squidex/app/features/webhooks/pages/webhook-events-page.component.scss
  25. 12
      src/Squidex/app/features/webhooks/pages/webhook-events-page.component.ts
  26. 9
      src/Squidex/app/shared/services/schemas.fields.spec.ts
  27. 61
      src/Squidex/app/shared/services/schemas.service.spec.ts
  28. 24
      src/Squidex/app/shared/services/schemas.service.ts
  29. 2
      src/Squidex/app/shell/pages/internal/apps-menu.component.html
  30. 8
      src/Squidex/app/shell/pages/internal/apps-menu.component.scss
  31. 9
      src/Squidex/app/theme/_bootstrap-vars.scss
  32. 48
      src/Squidex/app/theme/_bootstrap.scss
  33. 22
      src/Squidex/app/theme/_vars.scss
  34. 3
      tests/Squidex.Domain.Apps.Core.Tests/Schemas/Json/JsonSerializerTests.cs
  35. 46
      tests/Squidex.Domain.Apps.Core.Tests/Schemas/SchemaTests.cs
  36. 14
      tests/Squidex.Domain.Apps.Write.Tests/Schemas/SchemaCommandHandlerTests.cs
  37. 48
      tests/Squidex.Domain.Apps.Write.Tests/Schemas/SchemaDomainObjectTests.cs

38
src/Squidex.Domain.Apps.Core/Schemas/Field.cs

@ -28,6 +28,7 @@ namespace Squidex.Domain.Apps.Core.Schemas
private string fieldName; private string fieldName;
private bool isDisabled; private bool isDisabled;
private bool isHidden; private bool isHidden;
private bool isLocked;
public long Id public long Id
{ {
@ -39,6 +40,11 @@ namespace Squidex.Domain.Apps.Core.Schemas
get { return fieldName; } get { return fieldName; }
} }
public bool IsLocked
{
get { return isLocked; }
}
public bool IsHidden public bool IsHidden
{ {
get { return isHidden; } get { return isHidden; }
@ -75,10 +81,15 @@ namespace Squidex.Domain.Apps.Core.Schemas
validators = new Lazy<List<IValidator>>(() => new List<IValidator>(CreateValidators())); validators = new Lazy<List<IValidator>>(() => new List<IValidator>(CreateValidators()));
} }
public abstract Field Update(FieldProperties newProperties); protected abstract Field UpdateInternal(FieldProperties newProperties);
public abstract object ConvertValue(JToken value); public abstract object ConvertValue(JToken value);
public Field Lock()
{
return Clone<Field>(clone => clone.isLocked = true);
}
public Field Hide() public Field Hide()
{ {
return Clone<Field>(clone => clone.isHidden = true); return Clone<Field>(clone => clone.isHidden = true);
@ -99,7 +110,30 @@ namespace Squidex.Domain.Apps.Core.Schemas
return Clone<Field>(clone => clone.isDisabled = false); return Clone<Field>(clone => clone.isDisabled = false);
} }
public Field Update(FieldProperties newProperties)
{
ThrowIfLocked();
return UpdateInternal(newProperties);
}
public Field Rename(string newName) public Field Rename(string newName)
{
ThrowIfLocked();
ThrowIfSameName(newName);
return Clone<Field>(clone => clone.fieldName = newName);
}
private void ThrowIfLocked()
{
if (isLocked)
{
throw new DomainException($"Field {fieldId} is locked.");
}
}
private void ThrowIfSameName(string newName)
{ {
if (!newName.IsSlug()) if (!newName.IsSlug())
{ {
@ -107,8 +141,6 @@ namespace Squidex.Domain.Apps.Core.Schemas
throw new ValidationException($"Cannot rename the field '{fieldName}' ({fieldId})", error); throw new ValidationException($"Cannot rename the field '{fieldName}' ({fieldId})", error);
} }
return Clone<Field>(clone => clone.fieldName = newName);
} }
public void AddToEdmType(EdmStructuredType edmType, PartitionResolver partitionResolver, string schemaName, Func<EdmComplexType, EdmComplexType> typeResolver) public void AddToEdmType(EdmStructuredType edmType, PartitionResolver partitionResolver, string schemaName, Func<EdmComplexType, EdmComplexType> typeResolver)

2
src/Squidex.Domain.Apps.Core/Schemas/Field_Generic.cs

@ -33,7 +33,7 @@ namespace Squidex.Domain.Apps.Core.Schemas
this.properties = ValidateProperties(properties); this.properties = ValidateProperties(properties);
} }
public override Field Update(FieldProperties newProperties) protected override Field UpdateInternal(FieldProperties newProperties)
{ {
var typedProperties = ValidateProperties(newProperties); var typedProperties = ValidateProperties(newProperties);

3
src/Squidex.Domain.Apps.Core/Schemas/Json/JsonFieldModel.cs

@ -18,6 +18,9 @@ namespace Squidex.Domain.Apps.Core.Schemas.Json
[JsonProperty] [JsonProperty]
public bool IsHidden { get; set; } public bool IsHidden { get; set; }
[JsonProperty]
public bool IsLocked { get; set; }
[JsonProperty] [JsonProperty]
public bool IsDisabled { get; set; } public bool IsDisabled { get; set; }

6
src/Squidex.Domain.Apps.Core/Schemas/Json/SchemaJsonSerializer.cs

@ -42,6 +42,7 @@ namespace Squidex.Domain.Apps.Core.Schemas.Json
Id = x.Id, Id = x.Id,
Name = x.Name, Name = x.Name,
IsHidden = x.IsHidden, IsHidden = x.IsHidden,
IsLocked = x.IsLocked,
IsDisabled = x.IsDisabled, IsDisabled = x.IsDisabled,
Partitioning = x.Paritioning.Key, Partitioning = x.Paritioning.Key,
Properties = x.RawProperties Properties = x.RawProperties
@ -66,6 +67,11 @@ namespace Squidex.Domain.Apps.Core.Schemas.Json
field = field.Disable(); field = field.Disable();
} }
if (fieldModel.IsLocked)
{
field = field.Lock();
}
if (fieldModel.IsHidden) if (fieldModel.IsHidden)
{ {
field = field.Hide(); field = field.Hide();

10
src/Squidex.Domain.Apps.Core/Schemas/Schema.cs

@ -101,6 +101,11 @@ namespace Squidex.Domain.Apps.Core.Schemas
return UpdateField(fieldId, field => field.Update(newProperties)); return UpdateField(fieldId, field => field.Update(newProperties));
} }
public Schema LockField(long fieldId)
{
return UpdateField(fieldId, field => field.Lock());
}
public Schema DisableField(long fieldId) public Schema DisableField(long fieldId)
{ {
return UpdateField(fieldId, field => field.Disable()); return UpdateField(fieldId, field => field.Disable());
@ -128,6 +133,11 @@ namespace Squidex.Domain.Apps.Core.Schemas
public Schema DeleteField(long fieldId) public Schema DeleteField(long fieldId)
{ {
if (fieldsById.TryGetValue(fieldId, out var field) && field.IsLocked)
{
throw new DomainException($"Field {fieldId} is locked.");
}
return new Schema(name, isPublished, properties, fields.Where(x => x.Id != fieldId).ToImmutableList()); return new Schema(name, isPublished, properties, fields.Where(x => x.Id != fieldId).ToImmutableList());
} }

17
src/Squidex.Domain.Apps.Events/Schemas/FieldLocked.cs

@ -0,0 +1,17 @@
// ==========================================================================
// FieldLocked.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Events.Schemas
{
[TypeName("FieldLockedEvent")]
public class FieldLocked : FieldEvent
{
}
}

2
src/Squidex.Domain.Apps.Events/Schemas/SchemaCreatedField.cs

@ -18,6 +18,8 @@ namespace Squidex.Domain.Apps.Events.Schemas
public bool IsHidden { get; set; } public bool IsHidden { get; set; }
public bool IsLocked { get; set; }
public bool IsDisabled { get; set; } public bool IsDisabled { get; set; }
public FieldProperties Properties { get; set; } public FieldProperties Properties { get; set; }

10
src/Squidex.Domain.Apps.Events/Schemas/Utils/SchemaEventDispatcher.cs

@ -44,6 +44,11 @@ namespace Squidex.Domain.Apps.Events.Schemas.Utils
field = field.Disable(); field = field.Disable();
} }
if (eventField.IsLocked)
{
field = field.Lock();
}
schema = schema.AddOrUpdateField(field); schema = schema.AddOrUpdateField(field);
fieldId++; fieldId++;
@ -68,6 +73,11 @@ namespace Squidex.Domain.Apps.Events.Schemas.Utils
return schema.UpdateField(@event.FieldId.Id, @event.Properties); return schema.UpdateField(@event.FieldId.Id, @event.Properties);
} }
public static Schema Dispatch(FieldLocked @event, Schema schema)
{
return schema.LockField(@event.FieldId.Id);
}
public static Schema Dispatch(FieldHidden @event, Schema schema) public static Schema Dispatch(FieldHidden @event, Schema schema)
{ {
return schema.HideField(@event.FieldId.Id); return schema.HideField(@event.FieldId.Id);

13
src/Squidex.Domain.Apps.Read.MongoDb/Schemas/MongoSchemaRepository_EventHandling.cs

@ -48,22 +48,27 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Schemas
return UpdateSchema(@event, headers, s => SchemaEventDispatcher.Dispatch(@event, s)); return UpdateSchema(@event, headers, s => SchemaEventDispatcher.Dispatch(@event, s));
} }
protected Task On(FieldDisabled @event, EnvelopeHeaders headers) protected Task On(FieldLocked @event, EnvelopeHeaders headers)
{ {
return UpdateSchema(@event, headers, s => SchemaEventDispatcher.Dispatch(@event, s)); return UpdateSchema(@event, headers, s => SchemaEventDispatcher.Dispatch(@event, s));
} }
protected Task On(FieldEnabled @event, EnvelopeHeaders headers) protected Task On(FieldHidden @event, EnvelopeHeaders headers)
{ {
return UpdateSchema(@event, headers, s => SchemaEventDispatcher.Dispatch(@event, s)); return UpdateSchema(@event, headers, s => SchemaEventDispatcher.Dispatch(@event, s));
} }
protected Task On(FieldHidden @event, EnvelopeHeaders headers) protected Task On(FieldShown @event, EnvelopeHeaders headers)
{ {
return UpdateSchema(@event, headers, s => SchemaEventDispatcher.Dispatch(@event, s)); return UpdateSchema(@event, headers, s => SchemaEventDispatcher.Dispatch(@event, s));
} }
protected Task On(FieldShown @event, EnvelopeHeaders headers) protected Task On(FieldDisabled @event, EnvelopeHeaders headers)
{
return UpdateSchema(@event, headers, s => SchemaEventDispatcher.Dispatch(@event, s));
}
protected Task On(FieldEnabled @event, EnvelopeHeaders headers)
{ {
return UpdateSchema(@event, headers, s => SchemaEventDispatcher.Dispatch(@event, s)); return UpdateSchema(@event, headers, s => SchemaEventDispatcher.Dispatch(@event, s));
} }

13
src/Squidex.Domain.Apps.Read/Schemas/SchemaHistoryEventsCreator.cs

@ -46,11 +46,8 @@ namespace Squidex.Domain.Apps.Read.Schemas
AddEventMessage<FieldDeleted>( AddEventMessage<FieldDeleted>(
"deleted field {[Field]} from schema {[Name]}"); "deleted field {[Field]} from schema {[Name]}");
AddEventMessage<FieldDisabled>( AddEventMessage<FieldLocked>(
"disabled field {[Field]} of schema {[Name]}"); "has locked field {[Field]} of schema {[Name]}");
AddEventMessage<FieldEnabled>(
"disabled field {[Field]} of schema {[Name]}");
AddEventMessage<FieldHidden>( AddEventMessage<FieldHidden>(
"has hidden field {[Field]} of schema {[Name]}"); "has hidden field {[Field]} of schema {[Name]}");
@ -58,6 +55,12 @@ namespace Squidex.Domain.Apps.Read.Schemas
AddEventMessage<FieldShown>( AddEventMessage<FieldShown>(
"has shown field {[Field]} of schema {[Name]}"); "has shown field {[Field]} of schema {[Name]}");
AddEventMessage<FieldDisabled>(
"disabled field {[Field]} of schema {[Name]}");
AddEventMessage<FieldEnabled>(
"disabled field {[Field]} of schema {[Name]}");
AddEventMessage<FieldUpdated>( AddEventMessage<FieldUpdated>(
"has updated field {[Field]} of schema {[Name]}"); "has updated field {[Field]} of schema {[Name]}");

2
src/Squidex.Domain.Apps.Write/Schemas/Commands/CreateSchemaField.cs

@ -21,6 +21,8 @@ namespace Squidex.Domain.Apps.Write.Schemas.Commands
public bool IsHidden { get; set; } public bool IsHidden { get; set; }
public bool IsLocked { get; set; }
public bool IsDisabled { get; set; } public bool IsDisabled { get; set; }
public FieldProperties Properties { get; set; } public FieldProperties Properties { get; set; }

14
src/Squidex.Domain.Apps.Write/Schemas/Commands/LockField.cs

@ -0,0 +1,14 @@
// ==========================================================================
// LockField.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
namespace Squidex.Domain.Apps.Write.Schemas.Commands
{
public class LockField : FieldCommand
{
}
}

19
src/Squidex.Domain.Apps.Write/Schemas/SchemaCommandMiddleware.cs

@ -80,14 +80,9 @@ namespace Squidex.Domain.Apps.Write.Schemas
return handler.UpdateAsync<SchemaDomainObject>(context, s => s.DeleteField(command)); return handler.UpdateAsync<SchemaDomainObject>(context, s => s.DeleteField(command));
} }
protected Task On(DisableField command, CommandContext context) protected Task On(LockField command, CommandContext context)
{ {
return handler.UpdateAsync<SchemaDomainObject>(context, s => s.DisableField(command)); return handler.UpdateAsync<SchemaDomainObject>(context, s => s.LockField(command));
}
protected Task On(EnableField command, CommandContext context)
{
return handler.UpdateAsync<SchemaDomainObject>(context, s => s.EnableField(command));
} }
protected Task On(HideField command, CommandContext context) protected Task On(HideField command, CommandContext context)
@ -100,6 +95,16 @@ namespace Squidex.Domain.Apps.Write.Schemas
return handler.UpdateAsync<SchemaDomainObject>(context, s => s.ShowField(command)); return handler.UpdateAsync<SchemaDomainObject>(context, s => s.ShowField(command));
} }
protected Task On(DisableField command, CommandContext context)
{
return handler.UpdateAsync<SchemaDomainObject>(context, s => s.DisableField(command));
}
protected Task On(EnableField command, CommandContext context)
{
return handler.UpdateAsync<SchemaDomainObject>(context, s => s.EnableField(command));
}
protected Task On(ReorderFields command, CommandContext context) protected Task On(ReorderFields command, CommandContext context)
{ {
return handler.UpdateAsync<SchemaDomainObject>(context, s => s.Reorder(command)); return handler.UpdateAsync<SchemaDomainObject>(context, s => s.Reorder(command));

16
src/Squidex.Domain.Apps.Write/Schemas/SchemaDomainObject.cs

@ -65,6 +65,11 @@ namespace Squidex.Domain.Apps.Write.Schemas
schema = SchemaEventDispatcher.Dispatch(@event, schema); schema = SchemaEventDispatcher.Dispatch(@event, schema);
} }
protected void On(FieldLocked @event)
{
schema = SchemaEventDispatcher.Dispatch(@event, schema);
}
protected void On(FieldHidden @event) protected void On(FieldHidden @event)
{ {
schema = SchemaEventDispatcher.Dispatch(@event, schema); schema = SchemaEventDispatcher.Dispatch(@event, schema);
@ -217,6 +222,17 @@ namespace Squidex.Domain.Apps.Write.Schemas
return this; return this;
} }
public SchemaDomainObject LockField(LockField command)
{
Guard.NotNull(command, nameof(command));
VerifyCreatedAndNotDeleted();
RaiseEvent(command, new FieldLocked());
return this;
}
public SchemaDomainObject HideField(HideField command) public SchemaDomainObject HideField(HideField command)
{ {
Guard.NotNull(command, nameof(command)); Guard.NotNull(command, nameof(command));

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

@ -24,6 +24,11 @@ namespace Squidex.Controllers.Api.Schemas.Models
/// </summary> /// </summary>
public bool IsHidden { get; set; } public bool IsHidden { get; set; }
/// <summary>
/// Defines if the field is locked.
/// </summary>
public bool IsLocked { get; set; }
/// <summary> /// <summary>
/// Defines if the field is disabled. /// Defines if the field is disabled.
/// </summary> /// </summary>

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

@ -29,6 +29,11 @@ namespace Squidex.Controllers.Api.Schemas.Models
/// </summary> /// </summary>
public bool IsHidden { get; set; } public bool IsHidden { get; set; }
/// <summary>
/// Defines if the field is locked.
/// </summary>
public bool IsLocked { get; set; }
/// <summary> /// <summary>
/// Defines if the field is disabled. /// Defines if the field is disabled.
/// </summary> /// </summary>

34
src/Squidex/Controllers/Api/Schemas/SchemaFieldsController.cs

@ -98,7 +98,7 @@ namespace Squidex.Controllers.Api.Schemas
/// <param name="request">The field object that needs to be added to the schema.</param> /// <param name="request">The field object that needs to be added to the schema.</param>
/// <returns> /// <returns>
/// 204 => Schema field updated. /// 204 => Schema field updated.
/// 400 => Schema field properties not valid. /// 400 => Schema field properties not valid or field is locked.
/// 404 => Schema, field or app not found. /// 404 => Schema, field or app not found.
/// </returns> /// </returns>
[HttpPut] [HttpPut]
@ -115,6 +115,33 @@ namespace Squidex.Controllers.Api.Schemas
return NoContent(); return NoContent();
} }
/// <summary>
/// Lock a schema field.
/// </summary>
/// <param name="app">The name of the app.</param>
/// <param name="name">The name of the schema.</param>
/// <param name="id">The id of the field to lock.</param>
/// <returns>
/// 204 => Schema field shown.
/// 400 => Schema field already locked.
/// 404 => Schema, field or app not found.
/// </returns>
/// <remarks>
/// A hidden field is not part of the API response, but can still be edited in the portal.
/// </remarks>
[HttpPut]
[Route("apps/{app}/schemas/{name}/fields/{id:long}/lock/")]
[ProducesResponseType(typeof(ErrorDto), 400)]
[ApiCosts(1)]
public async Task<IActionResult> LockField(string app, string name, long id)
{
var command = new LockField { FieldId = id };
await CommandBus.PublishAsync(command);
return NoContent();
}
/// <summary> /// <summary>
/// Hide a schema field. /// Hide a schema field.
/// </summary> /// </summary>
@ -127,7 +154,7 @@ namespace Squidex.Controllers.Api.Schemas
/// 404 => Schema, field or app not found. /// 404 => Schema, field or app not found.
/// </returns> /// </returns>
/// <remarks> /// <remarks>
/// A hidden field is not part of the API response, but can still be edited in the portal. /// A locked field cannot be edited or deleted.
/// </remarks> /// </remarks>
[HttpPut] [HttpPut]
[Route("apps/{app}/schemas/{name}/fields/{id:long}/hide/")] [Route("apps/{app}/schemas/{name}/fields/{id:long}/hide/")]
@ -147,7 +174,7 @@ namespace Squidex.Controllers.Api.Schemas
/// </summary> /// </summary>
/// <param name="app">The name of the app.</param> /// <param name="app">The name of the app.</param>
/// <param name="name">The name of the schema.</param> /// <param name="name">The name of the schema.</param>
/// <param name="id">The id of the field to shows.</param> /// <param name="id">The id of the field to show.</param>
/// <returns> /// <returns>
/// 204 => Schema field shown. /// 204 => Schema field shown.
/// 400 => Schema field already visible. /// 400 => Schema field already visible.
@ -233,6 +260,7 @@ namespace Squidex.Controllers.Api.Schemas
/// <param name="id">The id of the field to disable.</param> /// <param name="id">The id of the field to disable.</param>
/// <returns> /// <returns>
/// 204 => Schema field deleted. /// 204 => Schema field deleted.
/// 400 => Field is locked.
/// 404 => Schema, field or app not found. /// 404 => Schema, field or app not found.
/// </returns> /// </returns>
[HttpDelete] [HttpDelete]

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

@ -9,13 +9,14 @@
<span class="field-partitioning" *ngIf="field.partitioning === 'language'">localizable</span> <span class="field-partitioning" *ngIf="field.partitioning === 'language'">localizable</span>
</span> </span>
</div> </div>
<div class="col col-3"> <div class="col col-tags">
<div class="float-right"> <div class="float-right">
<span class="tag tag-success" *ngIf="!field.isDisabled">Enabled</span> <span class="badge badge-pill badge-danger" *ngIf="field.isLocked">Locked</span>
<span class="tag tag-danger" *ngIf="field.isDisabled">Disabled</span> <span class="badge badge-pill badge-success" *ngIf="!field.isDisabled">Enabled</span>
<span class="badge badge-pill badge-danger" *ngIf="field.isDisabled">Disabled</span>
</div> </div>
</div> </div>
<div class="col col-3"> <div class="col col-options">
<div class="float-right"> <div class="float-right">
<button type="button" class="btn btn-secondary table-items-edit-button" [class.active]="isEditing" (click)="toggleEditing()"> <button type="button" class="btn btn-secondary table-items-edit-button" [class.active]="isEditing" (click)="toggleEditing()">
<i class="icon-settings2"></i> <i class="icon-settings2"></i>
@ -26,19 +27,22 @@
<i class="icon-dots"></i> <i class="icon-dots"></i>
</button> </button>
<div class="dropdown-menu" *sqxModalView="dropdown" closeAlways="true" [sqxModalTarget]="optionsButton" position="right" [@fade]> <div class="dropdown-menu" *sqxModalView="dropdown" closeAlways="true" [sqxModalTarget]="optionsButton" position="right" [@fade]>
<a class="dropdown-item" (click)="enabling.emit()" *ngIf="field.isDisabled"> <a class="dropdown-item" (click)="askLock()" *ngIf="!field.isLocked">
Lock
</a>
<a class="dropdown-item" (click)="enabling.emit()" *ngIf="field.isDisabled" [class.disabled]="field.isLocked">
Enable Enable
</a> </a>
<a class="dropdown-item" (click)="disabling.emit()" *ngIf="!field.isDisabled"> <a class="dropdown-item" (click)="disabling.emit()" *ngIf="!field.isDisabled" [class.disabled]="field.isLocked">
Disable Disable
</a> </a>
<a class="dropdown-item" (click)="hiding.emit()" *ngIf="!field.isHidden"> <a class="dropdown-item" (click)="hiding.emit()" *ngIf="!field.isHidden" [class.disabled]="field.isLocked">
Hide Hide
</a> </a>
<a class="dropdown-item" (click)="showing.emit()" *ngIf="field.isHidden"> <a class="dropdown-item" (click)="showing.emit()" *ngIf="field.isHidden" [class.disabled]="field.isLocked">
Show Show
</a> </a>
<a class="dropdown-item dropdown-item-delete" (click)="deleting.emit()"> <a class="dropdown-item dropdown-item-delete" (click)="deleting.emit()" [class.disabled]="field.isLocked">
Delete Delete
</a> </a>
</div> </div>
@ -49,7 +53,7 @@
</div> </div>
<div class="table-items-row-details" *ngIf="isEditing" (dragstart)="alert('ff');"> <div class="table-items-row-details" *ngIf="isEditing" (dragstart)="alert('ff');">
<form [formGroup]="editForm" (ngSubmit)="save()"> <form [formGroup]="editForm" (ngSubmit)="save()" [attr.disabled]="field.isLocked">
<div class="table-items-row-details-tabs clearfix"> <div class="table-items-row-details-tabs clearfix">
<ul class="nav nav-inline nav-field-tabs"> <ul class="nav nav-inline nav-field-tabs">
<li class="nav-item"> <li class="nav-item">
@ -64,8 +68,8 @@
</ul> </ul>
<div class="float-right"> <div class="float-right">
<button type="reset" class="btn btn-link" (click)="cancel()" [disabled]="editFormSubmitted">Cancel</button> <button [disabled]="editFormSubmitted || field.isLocked" type="reset" class="btn btn-link" (click)="cancel()">Cancel</button>
<button type="submit" class="btn btn-primary">Save</button> <button [disabled]="editFormSubmitted || field.isLocked" type="submit" class="btn btn-primary">Save</button>
</div> </div>
</div> </div>
@ -183,3 +187,23 @@
</form> </form>
</div> </div>
</div> </div>
<div class="modal" *sqxModalView="lockDialog;onRoot:true" [@fade]>
<div class="modal-backdrop"></div>
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Lock Field</h4>
</div>
<div class="modal-body">
Do you really want to lock the field? Lock fields cannot be changed and deleted.
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger" (click)="confirmLock()">Yes</button>
<button type="button" class="btn btn-secondary" (click)="cancelLock()">No</button>
</div>
</div>
</div>
</div>

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

@ -7,6 +7,17 @@ $field-header: #e7ebef;
cursor: default; cursor: default;
} }
.col {
&-tags,
&-options {
white-space: nowrap;
}
&-options {
max-width: 140px;
}
}
.field { .field {
&-icon { &-icon {
margin-right: 1rem; margin-right: 1rem;

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

@ -31,6 +31,9 @@ export class FieldComponent implements OnInit {
@Input() @Input()
public schemas: SchemaDto[]; public schemas: SchemaDto[];
@Output()
public locking = new EventEmitter<FieldDto>();
@Output() @Output()
public hiding = new EventEmitter<FieldDto>(); public hiding = new EventEmitter<FieldDto>();
@ -50,6 +53,7 @@ export class FieldComponent implements OnInit {
public deleting = new EventEmitter<FieldDto>(); public deleting = new EventEmitter<FieldDto>();
public dropdown = new ModalView(false, true); public dropdown = new ModalView(false, true);
public lockDialog = new ModalView(false, true);
public isEditing = false; public isEditing = false;
public selectedTab = 0; public selectedTab = 0;
@ -104,6 +108,7 @@ export class FieldComponent implements OnInit {
new FieldDto( new FieldDto(
this.field.fieldId, this.field.fieldId,
this.field.name, this.field.name,
this.field.isLocked,
this.field.isHidden, this.field.isHidden,
this.field.isHidden, this.field.isHidden,
this.field.partitioning, this.field.partitioning,
@ -113,6 +118,23 @@ export class FieldComponent implements OnInit {
} }
} }
public confirmLock() {
this.lockDialog.hide();
this.emitLocking(this.field);
}
public cancelLock() {
this.lockDialog.hide();
}
public askLock() {
this.lockDialog.show();
}
private emitLocking(field: FieldDto) {
this.locking.emit(field);
}
private emitSaving(field: FieldDto) { private emitSaving(field: FieldDto) {
this.saving.emit(field); this.saving.emit(field);
} }
@ -121,6 +143,10 @@ export class FieldComponent implements OnInit {
this.editFormSubmitted = false; this.editFormSubmitted = false;
this.editForm.reset(this.field.properties); this.editForm.reset(this.field.properties);
if (this.field.isLocked) {
this.editForm.disable();
}
this.isEditing = false; this.isEditing = false;
} }
} }

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

@ -46,6 +46,7 @@
(disabling)="disableField(field)" (disabling)="disableField(field)"
(deleting)="deleteField(field)" (deleting)="deleteField(field)"
(enabling)="enableField(field)" (enabling)="enableField(field)"
(locking)="lockField(field)"
(showing)="showField(field)" (showing)="showField(field)"
(hiding)="hideField(field)" (hiding)="hideField(field)"
(saving)="saveField($event)"></sqx-field> (saving)="saveField($event)"></sqx-field>

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

@ -145,11 +145,11 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
}); });
} }
public showField(field: FieldDto) { public lockField(field: FieldDto) {
this.appNameOnce() this.appNameOnce()
.switchMap(app => this.schemasService.showField(app, this.schema.name, field.fieldId, this.schema.version)).retry(2) .switchMap(app => this.schemasService.lockField(app, this.schema.name, field.fieldId, this.schema.version)).retry(2)
.subscribe(() => { .subscribe(() => {
this.updateSchema(this.schema.updateField(field.show(), this.authService.user.token)); this.updateSchema(this.schema.updateField(field.lock(), this.authService.user.token));
}, error => { }, error => {
this.notifyError(error); this.notifyError(error);
}); });

4
src/Squidex/app/features/webhooks/pages/webhook-events-page.component.html

@ -51,7 +51,7 @@
<ng-template ngFor let-event [ngForOf]="eventsItems"> <ng-template ngFor let-event [ngForOf]="eventsItems">
<tr [class.expanded]="selectedEventId === event.id"> <tr [class.expanded]="selectedEventId === event.id">
<td> <td>
<span class="badge badge-{{event.jobResult | lowercase}}">{{event.jobResult}}</span> <span class="badge badge-pill badge-{{getBadgeClass(event.jobResult)}}">{{event.jobResult}}</span>
</td> </td>
<td> <td>
<span class="truncate">{{event.eventName}}</span> <span class="truncate">{{event.eventName}}</span>
@ -76,7 +76,7 @@
<div class="row event-stats"> <div class="row event-stats">
<div class="col-3"> <div class="col-3">
<span class="badge badge-small badge-{{event.result | lowercase}}">{{event.result}}</span> <span class="badge badge-pill badge-{{getBadgeClass(event.result)}}">{{event.result}}</span>
</div> </div>
<div class="col-3"> <div class="col-3">
Attempts: {{event.numCalls}} Attempts: {{event.numCalls}}

30
src/Squidex/app/features/webhooks/pages/webhook-events-page.component.scss

@ -5,36 +5,6 @@ h3 {
margin-bottom: 1rem; margin-bottom: 1rem;
} }
.badge {
& {
@include border-radius(12px);
font-size: .9rem;
font-weight: normal;
line-height: 1.5rem;
padding: 0 .5rem;
}
&-small {
font-size: .8rem;
}
&-pending {
background: $color-border;
}
&-success {
background: $color-badge-success;
}
&-retry {
background: $color-badge-warning;
}
&-failed {
background: $color-badge-error;
}
}
.event { .event {
&-stats { &-stats {
font-size: .8rem; font-size: .8rem;

12
src/Squidex/app/features/webhooks/pages/webhook-events-page.component.ts

@ -78,5 +78,17 @@ export class WebhookEventsPageComponent extends AppComponentBase implements OnIn
this.load(); this.load();
} }
public getBadgeClass(status: string) {
if (status === 'Retry') {
return 'warning';
} else if (status === 'Failed') {
return 'danger';
} else if (status === 'Pending') {
return 'default';
} else {
return status.toLowerCase();
}
}
} }

9
src/Squidex/app/shared/services/schemas.fields.spec.ts

@ -20,6 +20,13 @@ import {
} from './../'; } from './../';
describe('FieldDto', () => { describe('FieldDto', () => {
it('should update isLocked property when locking', () => {
const field_1 = createField(createProperties('String'));
const field_2 = field_1.lock();
expect(field_2.isLocked).toBeTruthy();
});
it('should update isHidden property when hiding', () => { it('should update isHidden property when hiding', () => {
const field_1 = createField(createProperties('String')); const field_1 = createField(createProperties('String'));
const field_2 = field_1.hide(); const field_2 = field_1.hide();
@ -211,5 +218,5 @@ describe('NumberField', () => {
}); });
function createField(properties: FieldPropertiesDto) { function createField(properties: FieldPropertiesDto) {
return new FieldDto(1, 'field1', false, false, 'languages', properties); return new FieldDto(1, 'field1', false, false, false, 'languages', properties);
} }

61
src/Squidex/app/shared/services/schemas.service.spec.ts

@ -103,8 +103,8 @@ describe('SchemaDetailsDto', () => {
}); });
it('should update fields property and user info when adding field', () => { it('should update fields property and user info when adding field', () => {
const field1 = new FieldDto(1, '1', false, false, 'l', createProperties('String')); const field1 = new FieldDto(1, '1', false, false, false, 'l', createProperties('String'));
const field2 = new FieldDto(2, '2', false, false, 'l', createProperties('Number')); const field2 = new FieldDto(2, '2', false, false, false, 'l', createProperties('Number'));
const now = DateTime.now(); const now = DateTime.now();
@ -117,8 +117,8 @@ describe('SchemaDetailsDto', () => {
}); });
it('should update fields property and user info when removing field', () => { it('should update fields property and user info when removing field', () => {
const field1 = new FieldDto(1, '1', false, false, 'l', createProperties('String')); const field1 = new FieldDto(1, '1', false, false, false, 'l', createProperties('String'));
const field2 = new FieldDto(2, '2', false, false, 'l', createProperties('Number')); const field2 = new FieldDto(2, '2',false, false, false, 'l', createProperties('Number'));
const now = DateTime.now(); const now = DateTime.now();
@ -131,8 +131,8 @@ describe('SchemaDetailsDto', () => {
}); });
it('should update fields property and user info when replacing fields', () => { it('should update fields property and user info when replacing fields', () => {
const field1 = new FieldDto(1, '1', false, false, 'l', createProperties('String')); const field1 = new FieldDto(1, '1', false, false, false, 'l', createProperties('String'));
const field2 = new FieldDto(2, '2', false, false, 'l', createProperties('Number')); const field2 = new FieldDto(2, '2', false, false, false, 'l', createProperties('Number'));
const now = DateTime.now(); const now = DateTime.now();
@ -145,16 +145,16 @@ describe('SchemaDetailsDto', () => {
}); });
it('should update fields property and user info when updating field', () => { it('should update fields property and user info when updating field', () => {
const field1 = new FieldDto(1, '1', false, false, 'l', createProperties('String')); const field1_0 = new FieldDto(1, '1', false, false, false, 'l', createProperties('String'));
const field2_1 = new FieldDto(2, '2', false, false, 'l', createProperties('Number')); const field2_1 = new FieldDto(2, '2', false, false, false, 'l', createProperties('Number'));
const field2_2 = new FieldDto(2, '2', false, false, 'l', createProperties('Boolean')); const field2_2 = new FieldDto(2, '2', false, false, false, 'l', createProperties('Boolean'));
const now = DateTime.now(); const now = DateTime.now();
const schema_1 = new SchemaDetailsDto('1', 'name', properties, false, 'other', 'other', DateTime.now(), DateTime.now(), null, [field1, field2_1]); const schema_1 = new SchemaDetailsDto('1', 'name', properties, false, 'other', 'other', DateTime.now(), DateTime.now(), null, [field1_0, field2_1]);
const schema_2 = schema_1.updateField(field2_2, 'me', now); const schema_2 = schema_1.updateField(field2_2, 'me', now);
expect(schema_2.fields).toEqual([field1, field2_2]); expect(schema_2.fields).toEqual([field1_0, field2_2]);
expect(schema_2.lastModified).toEqual(now); expect(schema_2.lastModified).toEqual(now);
expect(schema_2.lastModifiedBy).toEqual('me'); expect(schema_2.lastModifiedBy).toEqual('me');
}); });
@ -276,6 +276,7 @@ describe('SchemasService', () => {
{ {
fieldId: 1, fieldId: 1,
name: 'field1', name: 'field1',
isLocked: true,
isHidden: true, isHidden: true,
isDisabled: true, isDisabled: true,
partitioning: 'language', partitioning: 'language',
@ -286,6 +287,7 @@ describe('SchemasService', () => {
{ {
fieldId: 2, fieldId: 2,
name: 'field2', name: 'field2',
isLocked: true,
isHidden: true, isHidden: true,
isDisabled: true, isDisabled: true,
partitioning: 'language', partitioning: 'language',
@ -296,6 +298,7 @@ describe('SchemasService', () => {
{ {
fieldId: 3, fieldId: 3,
name: 'field3', name: 'field3',
isLocked: true,
isHidden: true, isHidden: true,
isDisabled: true, isDisabled: true,
partitioning: 'language', partitioning: 'language',
@ -306,6 +309,7 @@ describe('SchemasService', () => {
{ {
fieldId: 4, fieldId: 4,
name: 'field4', name: 'field4',
isLocked: true,
isHidden: true, isHidden: true,
isDisabled: true, isDisabled: true,
partitioning: 'language', partitioning: 'language',
@ -316,6 +320,7 @@ describe('SchemasService', () => {
{ {
fieldId: 5, fieldId: 5,
name: 'field5', name: 'field5',
isLocked: true,
isHidden: true, isHidden: true,
isDisabled: true, isDisabled: true,
partitioning: 'language', partitioning: 'language',
@ -326,6 +331,7 @@ describe('SchemasService', () => {
{ {
fieldId: 6, fieldId: 6,
name: 'field6', name: 'field6',
isLocked: true,
isHidden: true, isHidden: true,
isDisabled: true, isDisabled: true,
partitioning: 'language', partitioning: 'language',
@ -336,6 +342,7 @@ describe('SchemasService', () => {
{ {
fieldId: 7, fieldId: 7,
name: 'field7', name: 'field7',
isLocked: true,
isHidden: true, isHidden: true,
isDisabled: true, isDisabled: true,
partitioning: 'language', partitioning: 'language',
@ -346,6 +353,7 @@ describe('SchemasService', () => {
{ {
fieldId: 8, fieldId: 8,
name: 'field8', name: 'field8',
isLocked: true,
isHidden: true, isHidden: true,
isDisabled: true, isDisabled: true,
partitioning: 'language', partitioning: 'language',
@ -362,14 +370,14 @@ describe('SchemasService', () => {
DateTime.parseISO_UTC('2017-12-12T10:10'), DateTime.parseISO_UTC('2017-12-12T10:10'),
new Version('11'), new Version('11'),
[ [
new FieldDto(1, 'field1', true, true, 'language', createProperties('Number')), new FieldDto(1, 'field1', true, true, true, 'language', createProperties('Number')),
new FieldDto(2, 'field2', true, true, 'language', createProperties('String')), new FieldDto(2, 'field2', true, true, true, 'language', createProperties('String')),
new FieldDto(3, 'field3', true, true, 'language', createProperties('Boolean')), new FieldDto(3, 'field3', true, true, true, 'language', createProperties('Boolean')),
new FieldDto(4, 'field4', true, true, 'language', createProperties('DateTime')), new FieldDto(4, 'field4', true, true, true, 'language', createProperties('DateTime')),
new FieldDto(5, 'field5', true, true, 'language', createProperties('Json')), new FieldDto(5, 'field5', true, true, true, 'language', createProperties('Json')),
new FieldDto(6, 'field6', true, true, 'language', createProperties('Geolocation')), new FieldDto(6, 'field6', true, true, true, 'language', createProperties('Geolocation')),
new FieldDto(7, 'field7', true, true, 'language', createProperties('Assets')), new FieldDto(7, 'field7', true, true, true, 'language', createProperties('Assets')),
new FieldDto(8, 'field8', true, true, 'language', createProperties('References')) new FieldDto(8, 'field8', true, true, true, 'language', createProperties('References'))
])); ]));
})); }));
@ -437,7 +445,7 @@ describe('SchemasService', () => {
req.flush({ id: 123 }); req.flush({ id: 123 });
expect(field).toEqual( expect(field).toEqual(
new FieldDto(123, dto.name, false, false, dto.partitioning, dto.properties)); new FieldDto(123, dto.name, false, false, false, dto.partitioning, dto.properties));
})); }));
it('should make put request to update schema', it('should make put request to update schema',
@ -537,6 +545,19 @@ describe('SchemasService', () => {
req.flush({}); req.flush({});
})); }));
it('should make put request to lock field',
inject([SchemasService, HttpTestingController], (schemasService: SchemasService, httpMock: HttpTestingController) => {
schemasService.lockField('my-app', 'my-schema', 1, version).subscribe();
const req = httpMock.expectOne('http://service/p/api/apps/my-app/schemas/my-schema/fields/1/lock');
expect(req.request.method).toEqual('PUT');
expect(req.request.headers.get('If-Match')).toBe(version.value);
req.flush({});
}));
it('should make put request to show field', it('should make put request to show field',
inject([SchemasService, HttpTestingController], (schemasService: SchemasService, httpMock: HttpTestingController) => { inject([SchemasService, HttpTestingController], (schemasService: SchemasService, httpMock: HttpTestingController) => {

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

@ -215,6 +215,7 @@ export class FieldDto {
constructor( constructor(
public readonly fieldId: number, public readonly fieldId: number,
public readonly name: string, public readonly name: string,
public readonly isLocked: boolean,
public readonly isHidden: boolean, public readonly isHidden: boolean,
public readonly isDisabled: boolean, public readonly isDisabled: boolean,
public readonly partitioning: string, public readonly partitioning: string,
@ -222,24 +223,28 @@ export class FieldDto {
) { ) {
} }
public lock(): FieldDto {
return new FieldDto(this.fieldId, this.name, true, this.isHidden, this.isDisabled, this.partitioning, this.properties);
}
public show(): FieldDto { public show(): FieldDto {
return new FieldDto(this.fieldId, this.name, false, this.isDisabled, this.partitioning, this.properties); return new FieldDto(this.fieldId, this.name, this.isLocked, false, this.isDisabled, this.partitioning, this.properties);
} }
public hide(): FieldDto { public hide(): FieldDto {
return new FieldDto(this.fieldId, this.name, true, this.isDisabled, this.partitioning, this.properties); return new FieldDto(this.fieldId, this.name, this.isLocked, true, this.isDisabled, this.partitioning, this.properties);
} }
public enable(): FieldDto { public enable(): FieldDto {
return new FieldDto(this.fieldId, this.name, this.isHidden, false, this.partitioning, this.properties); return new FieldDto(this.fieldId, this.name, this.isLocked, this.isHidden, false, this.partitioning, this.properties);
} }
public disable(): FieldDto { public disable(): FieldDto {
return new FieldDto(this.fieldId, this.name, this.isHidden, true, this.partitioning, this.properties); return new FieldDto(this.fieldId, this.name, this.isLocked, this.isHidden, true, this.partitioning, this.properties);
} }
public update(properties: FieldPropertiesDto): FieldDto { public update(properties: FieldPropertiesDto): FieldDto {
return new FieldDto(this.fieldId, this.name, this.isHidden, this.isDisabled, this.partitioning, properties); return new FieldDto(this.fieldId, this.name, this.isLocked, this.isHidden, this.isDisabled, this.partitioning, properties);
} }
public formatValue(value: any): string { public formatValue(value: any): string {
@ -658,6 +663,7 @@ export class SchemasService {
return new FieldDto( return new FieldDto(
item.fieldId, item.fieldId,
item.name, item.name,
item.isLocked,
item.isHidden, item.isHidden,
item.isDisabled, item.isDisabled,
item.partitioning, item.partitioning,
@ -727,6 +733,7 @@ export class SchemasService {
dto.name, dto.name,
false, false,
false, false,
false,
dto.partitioning, dto.partitioning,
dto.properties); dto.properties);
}) })
@ -792,6 +799,13 @@ export class SchemasService {
.pretifyError('Failed to disable field. Please reload.'); .pretifyError('Failed to disable field. Please reload.');
} }
public lockField(appName: string, schemaName: string, fieldId: number, version?: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/${fieldId}/lock`);
return HTTP.putVersioned(this.http, url, {}, version)
.pretifyError('Failed to lock field. Please reload.');
}
public showField(appName: string, schemaName: string, fieldId: number, version?: Version): Observable<any> { public showField(appName: string, schemaName: string, fieldId: number, version?: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/${fieldId}/show`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/${fieldId}/show`);

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

@ -5,7 +5,7 @@
<div class="dropdown-menu" *sqxModalView="modalMenu" closeAlways="true" [@fade]> <div class="dropdown-menu" *sqxModalView="modalMenu" closeAlways="true" [@fade]>
<a class="dropdown-item all-apps" routerLink="/app"> <a class="dropdown-item all-apps" routerLink="/app">
<span class="all-apps-text">All Apps</span> <span class="all-apps-text">All Apps</span>
<span class="all-apps-pill tag tag-pill tag-default">{{apps.length}}</span> <span class="all-apps-pill badge badge-pill badge-primary">{{apps.length}}</span>
</a> </a>
<div class="dropdown-divider"></div> <div class="dropdown-divider"></div>

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

@ -51,13 +51,5 @@
&-pill { &-pill {
@include absolute(.8rem, .625rem, auto, auto); @include absolute(.8rem, .625rem, auto, auto);
@include border-radius(8px);
padding: 0 .4rem;
background: $color-theme-blue-lightest;
border: 0;
line-height: 1.2rem;
font-size: .8rem;
font-weight: normal;
color: $color-theme-blue;
} }
} }

9
src/Squidex/app/theme/_bootstrap-vars.scss

@ -12,7 +12,14 @@ $input-bg-disabled: $color-disabled;
$input-border-color: $color-input; $input-border-color: $color-input;
$input-color-placeholder: $color-empty; $input-color-placeholder: $color-empty;
$badge-default-bg: $color-badge-default-background;
$badge-primary-bg: $color-badge-primary-background;
$badge-success-bg: $color-badge-success-background;
$badge-info-bg: $color-badge-info-background;
$badge-warning-bg: $color-badge-warning-background;
$badge-danger-bg: $color-badge-danger-background;
$dropdown-border-color: $color-border; $dropdown-border-color: $color-border;
$card-border-color: $color-border; $card-border-color: $color-border;
$card-cap-bg: #fff; $card-cap-bg: $panel-light-background;

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

@ -18,6 +18,49 @@ h3 {
font-size: 1.05rem; font-size: 1.05rem;
} }
//
// Bade colors
//
.badge {
& {
font-size: .9rem;
font-weight: normal;
padding: .3rem .6rem;
}
&-default {
color: $color-badge-default-foreground;
}
&-primary {
color: $color-badge-primary-foreground;
}
&-success {
color: $color-badge-success-foreground;
}
&-info {
color: $color-badge-info-foreground;
}
&-warning {
color: $color-badge-warning-foreground;
}
&-danger {
color: $color-badge-danger-foreground;
}
}
a {
&:disabled,
&.disabled {
@include opacity(.8);
pointer-events: none;
}
}
// //
// Fix navbar. // Fix navbar.
// //
@ -68,6 +111,11 @@ h3 {
&:active { &:active {
background: $color-theme-error-dark; background: $color-theme-error-dark;
} }
&:disabled,
&.disabled {
color: lighten($color-theme-error, 20%);
}
} }
&.dropdown-item { &.dropdown-item {

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

@ -33,10 +33,6 @@ $color-theme-green-dark: #47b353;
$color-theme-error: #f00; $color-theme-error: #f00;
$color-theme-error-dark: darken($color-theme-error, 5%); $color-theme-error-dark: darken($color-theme-error, 5%);
$color-badge-success: $color-theme-green-dark;
$color-badge-error: $color-theme-error;
$color-badge-warning: #ff7b00;
$color-dark1-background: #2e3842; $color-dark1-background: #2e3842;
$color-dark1-foreground: #6a7681; $color-dark1-foreground: #6a7681;
$color-dark1-border1: #37424c; $color-dark1-border1: #37424c;
@ -55,6 +51,24 @@ $color-dark2-separator: #2e3842;
$color-panel-icon: #a2b0b6; $color-panel-icon: #a2b0b6;
$color-badge-success-background: #d6ffdb;
$color-badge-success-foreground: #3bab48;
$color-badge-warning-background: #ffe8cc;
$color-badge-warning-foreground: #efa243;
$color-badge-danger-background: #fcc;
$color-badge-danger-foreground: #ef4343;
$color-badge-info-background: #d0f2fb;
$color-badge-info-foreground: #43ccef;
$color-badge-primary-background: #cce1ff;
$color-badge-primary-foreground: #438cef;
$color-badge-default-background: #e6e6e6;
$color-badge-default-foreground: #999;
$color-table: #fff; $color-table: #fff;
$color-table-footer: #ecf2f6; $color-table-footer: #ecf2f6;
$color-table-border: $color-border; $color-table-border: $color-border;

3
tests/Squidex.Domain.Apps.Core.Tests/Schemas/Json/JsonSerializerTests.cs

@ -55,6 +55,9 @@ namespace Squidex.Domain.Apps.Core.Schemas.Json
new ReferencesFieldProperties { SchemaId = Guid.NewGuid() })) new ReferencesFieldProperties { SchemaId = Guid.NewGuid() }))
.AddOrUpdateField(new GeolocationField(10, "my-geolocation", Partitioning.Invariant, .AddOrUpdateField(new GeolocationField(10, "my-geolocation", Partitioning.Invariant,
new GeolocationFieldProperties())) new GeolocationFieldProperties()))
.HideField(1)
.LockField(2)
.DisableField(3)
.Publish(); .Publish();
var deserialized = sut.Deserialize(sut.Serialize(schema)); var deserialized = sut.Deserialize(sut.Serialize(schema));

46
tests/Squidex.Domain.Apps.Core.Tests/Schemas/SchemaTests.cs

@ -147,6 +147,52 @@ namespace Squidex.Domain.Apps.Core.Schemas
Assert.Throws<DomainObjectNotFoundException>(() => sut.EnableField(1)); Assert.Throws<DomainObjectNotFoundException>(() => sut.EnableField(1));
} }
[Fact]
public void Should_lock_field()
{
AddField();
sut = sut.LockField(1);
Assert.True(sut.FieldsById[1].IsLocked);
}
[Fact]
public void Should_throw_exception_if_field_to_lock_does_not_exist()
{
Assert.Throws<DomainObjectNotFoundException>(() => sut.LockField(1));
}
[Fact]
public void Should_throw_exception_if_updating_locked_field()
{
AddField();
sut = sut.LockField(1);
Assert.Throws<DomainException>(() => sut.UpdateField(1, new NumberFieldProperties { IsRequired = true }));
}
[Fact]
public void Should_throw_exception_if_renaming_locked_field()
{
AddField();
sut = sut.LockField(1);
Assert.Throws<DomainException>(() => sut.RenameField(1, "new-name"));
}
[Fact]
public void Should_throw_exception_if_deleting_locked_field()
{
AddField();
sut = sut.LockField(1);
Assert.Throws<DomainException>(() => sut.DeleteField(1));
}
[Fact] [Fact]
public void Should_rename_field() public void Should_rename_field()
{ {

14
tests/Squidex.Domain.Apps.Write.Tests/Schemas/SchemaCommandHandlerTests.cs

@ -167,6 +167,20 @@ namespace Squidex.Domain.Apps.Write.Schemas
}); });
} }
[Fact]
public async Task LockField_should_update_domain_object()
{
CreateSchema();
CreateField();
var context = CreateContextForCommand(new LockField { FieldId = 1 });
await TestUpdate(schema, async _ =>
{
await sut.HandleAsync(context);
});
}
[Fact] [Fact]
public async Task HideField_should_update_domain_object() public async Task HideField_should_update_domain_object()
{ {

48
tests/Squidex.Domain.Apps.Write.Tests/Schemas/SchemaDomainObjectTests.cs

@ -436,6 +436,54 @@ namespace Squidex.Domain.Apps.Write.Schemas
); );
} }
[Fact]
public void LockField_should_throw_exception_if_not_created()
{
Assert.Throws<DomainException>(() =>
{
sut.LockField(CreateCommand(new LockField { FieldId = 1 }));
});
}
[Fact]
public void LockField_should_throw_exception_if_field_is_not_found()
{
CreateSchema();
Assert.Throws<DomainObjectNotFoundException>(() =>
{
sut.LockField(CreateCommand(new LockField { FieldId = 2 }));
});
}
[Fact]
public void LockField_should_throw_exception_if_schema_is_deleted()
{
CreateSchema();
DeleteSchema();
Assert.Throws<DomainException>(() =>
{
sut.LockField(CreateCommand(new LockField { FieldId = 1 }));
});
}
[Fact]
public void LockField_should_update_schema_and_create_events()
{
CreateSchema();
CreateField();
sut.LockField(CreateCommand(new LockField { FieldId = 1 }));
Assert.False(sut.Schema.FieldsById[1].IsDisabled);
sut.GetUncomittedEvents()
.ShouldHaveSameEvents(
CreateEvent(new FieldLocked { FieldId = fieldId })
);
}
[Fact] [Fact]
public void HideField_should_throw_exception_if_not_created() public void HideField_should_throw_exception_if_not_created()
{ {

Loading…
Cancel
Save