Browse Source

Merge branch 'hateaos' into workflow-generalization

pull/373/head
Sebastian Stehle 7 years ago
parent
commit
a57e74953a
  1. 4
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs
  2. 2
      src/Squidex.Domain.Apps.Entities/Apps/RoleExtensions.cs
  3. 2
      src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchemaField.cs
  4. 5
      src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs
  5. 5
      src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs
  6. 2
      src/Squidex/Areas/Api/Controllers/Assets/Models/AssetsDto.cs
  7. 2
      src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs
  8. 2
      src/Squidex/Areas/Api/Controllers/Rules/Models/RuleDto.cs
  9. 8
      src/Squidex/Areas/Api/Controllers/Schemas/Models/FieldDto.cs
  10. 2
      src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDto.cs
  11. 2
      src/Squidex/app/features/assets/pages/assets-page.component.html
  12. 4
      src/Squidex/app/features/content/shared/assets-editor.component.html
  13. 2
      src/Squidex/app/features/content/shared/content-item.component.html
  14. 2
      src/Squidex/app/features/schemas/pages/schema/field.component.ts
  15. 2
      src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts
  16. 2
      src/Squidex/app/features/settings/pages/backups/backups-page.component.html
  17. 20
      src/Squidex/app/features/settings/pages/clients/client.component.ts
  18. 16
      src/Squidex/app/features/settings/pages/languages/language.component.ts
  19. 20
      src/Squidex/app/features/settings/pages/patterns/pattern.component.ts
  20. 13
      src/Squidex/app/framework/angular/forms/file-drop.directive.ts
  21. 2
      src/Squidex/app/shared/components/asset-dialog.component.ts
  22. 70
      src/Squidex/app/shared/components/asset-uploader.component.html
  23. 22
      src/Squidex/app/shared/components/asset.component.html
  24. 2
      src/Squidex/app/shared/components/assets-list.component.html
  25. 19
      src/Squidex/app/shared/components/assets-list.component.ts
  26. 2
      src/Squidex/app/shared/components/assets-selector.component.html
  27. 2
      src/Squidex/app/shared/components/language-selector.component.html
  28. 2
      src/Squidex/app/shared/components/markdown-editor.component.html
  29. 8
      src/Squidex/app/shared/components/markdown-editor.component.ts
  30. 2
      src/Squidex/app/shared/components/rich-editor.component.html
  31. 4
      src/Squidex/app/shared/components/rich-editor.component.ts
  32. 2
      src/Squidex/app/shared/services/app-languages.service.spec.ts
  33. 2
      src/Squidex/app/shared/services/app-languages.service.ts
  34. 2
      src/Squidex/app/shared/services/apps.service.ts
  35. 10
      src/Squidex/app/shared/services/assets.service.ts
  36. 2
      src/Squidex/app/shared/services/rules.service.ts
  37. 6
      src/Squidex/app/shared/services/schemas.service.spec.ts
  38. 8
      src/Squidex/app/shared/services/schemas.service.ts
  39. 2
      src/Squidex/app/shared/state/asset-uploader.state.ts
  40. 10
      src/Squidex/app/shared/state/assets.state.ts
  41. 12
      src/Squidex/app/shared/state/schemas.state.spec.ts
  42. 8
      src/Squidex/app/shared/state/schemas.state.ts
  43. 9
      tests/Squidex.Domain.Apps.Entities.Tests/Apps/RoleExtensionsTests.cs

4
src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs

@ -87,9 +87,8 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
public async Task<IResultList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, HashSet<Guid> ids, bool includeDraft = true)
{
Guard.NotNull(app, nameof(app));
Guard.NotNull(schema, nameof(schema));
Guard.NotNull(status, nameof(status));
Guard.NotNull(ids, nameof(ids));
Guard.NotNull(schema, nameof(schema));
using (Profiler.TraceMethod<MongoContentRepository>("QueryAsyncByIds"))
{
@ -100,7 +99,6 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
public async Task<List<(IContentEntity Content, ISchemaEntity Schema)>> QueryAsync(IAppEntity app, Status[] status, HashSet<Guid> ids, bool includeDraft = true)
{
Guard.NotNull(app, nameof(app));
Guard.NotNull(status, nameof(status));
Guard.NotNull(ids, nameof(ids));
using (Profiler.TraceMethod<MongoContentRepository>("QueryAsyncByIdsWithoutSchema"))

2
src/Squidex.Domain.Apps.Entities/Apps/RoleExtensions.cs

@ -55,7 +55,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
{
return id;
}
}));
}).Where(x => x != "common"));
}
}
}

2
src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchemaField.cs

@ -121,7 +121,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
throw new DomainException("Schema field is already disabled.");
}
if (!field.IsForApi())
if (!field.IsForApi(true))
{
throw new DomainException("UI field cannot be disabled.");
}

5
src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs

@ -182,6 +182,11 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models
AddPostLink("schemas/create", controller.Url<SchemasController>(x => nameof(x.PostSchema), values));
}
if (controller.HasPermission(AllPermissions.AppAssetsCreate, Name, permissions: permissions))
{
AddPostLink("assets/create", controller.Url<SchemasController>(x => nameof(x.PostSchema), values));
}
return this;
}
}

5
src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs

@ -179,7 +179,10 @@ namespace Squidex.Areas.Api.Controllers.Assets
var command = new CreateAsset { File = assetFile };
var response = await InvokeCommandAsync(app, command);
var context = await CommandBus.PublishAsync(command);
var result = context.Result<AssetCreatedResult>();
var response = AssetDto.FromAsset(result.Asset, this, app, result.IsDuplicate);
return CreatedAtAction(nameof(GetAsset), new { app, id = response.Id }, response);
}

2
src/Squidex/Areas/Api/Controllers/Assets/Models/AssetsDto.cs

@ -59,7 +59,7 @@ namespace Squidex.Areas.Api.Controllers.Assets.Models
response.AddPostLink("create", controller.Url<AssetsController>(x => nameof(x.PostAsset), values));
}
response.AddDeleteLink("tags", controller.Url<AssetsController>(x => nameof(x.GetTags), values));
response.AddGetLink("tags", controller.Url<AssetsController>(x => nameof(x.GetTags), values));
return response;
}

2
src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs

@ -154,7 +154,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models
if (controller.HasPermission(Permissions.AppContentsDelete, app, schema))
{
AddPutLink("delete", controller.Url<ContentsController>(x => nameof(x.DeleteContent), values));
AddDeleteLink("delete", controller.Url<ContentsController>(x => nameof(x.DeleteContent), values));
}
var nextStatuses = await contentWorkflow.GetNextsAsync(content);

2
src/Squidex/Areas/Api/Controllers/Rules/Models/RuleDto.cs

@ -109,7 +109,7 @@ namespace Squidex.Areas.Api.Controllers.Rules.Models
if (controller.HasPermission(Permissions.AppRulesDelete))
{
AddPutLink("delete", controller.Url<RulesController>(x => nameof(x.DeleteRule), values));
AddDeleteLink("delete", controller.Url<RulesController>(x => nameof(x.DeleteRule), values));
}
return this;

8
src/Squidex/Areas/Api/Controllers/Schemas/Models/FieldDto.cs

@ -83,14 +83,16 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models
}
else
{
AddPutLink("show", controller.Url<SchemaFieldsController>(x => nameof(x.DisableField), values));
AddPutLink("disable", controller.Url<SchemaFieldsController>(x => nameof(x.DisableField), values));
}
if (Properties is ArrayFieldPropertiesDto)
{
AddPostLink("fields/add", controller.Url<SchemaFieldsController>(x => nameof(x.PostNestedField), values));
var parentValues = new { app, name = schema, parentId = FieldId };
AddPutLink("order", controller.Url<SchemaFieldsController>(x => nameof(x.PutNestedFieldOrdering), values));
AddPostLink("fields/add", controller.Url<SchemaFieldsController>(x => nameof(x.PostNestedField), parentValues));
AddPutLink("fields/order", controller.Url<SchemaFieldsController>(x => nameof(x.PutNestedFieldOrdering), parentValues));
}
AddPutLink("lock", controller.Url<SchemaFieldsController>(x => nameof(x.LockField), values));

2
src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDto.cs

@ -119,7 +119,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models
if (allowUpdate)
{
AddPutLink("order", controller.Url<SchemaFieldsController>(x => nameof(x.PutSchemaFieldOrdering), values));
AddPutLink("fields/order", controller.Url<SchemaFieldsController>(x => nameof(x.PutSchemaFieldOrdering), values));
AddPutLink("update", controller.Url<SchemasController>(x => nameof(x.PutSchema), values));
AddPutLink("update/category", controller.Url<SchemasController>(x => nameof(x.PutCategory), values));

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

@ -25,7 +25,7 @@
</sqx-tag-editor>
</div>
<div class="col-6">
<sqx-search-form formClass="form" placeholder="Search by asset name" fieldExample="fileSize"
<sqx-search-form formClass="form" placeholder="Search by name" fieldExample="fileSize"
[filter]="filter"
(querySubmit)="search()"
[queries]="queries"

4
src/Squidex/app/features/content/shared/assets-editor.component.html

@ -1,9 +1,9 @@
<div class="assets-container" [class.disabled]="snapshot.isDisabled" (sqxFileDrop)="addFiles($event)" tabindex="1000">
<div class="assets-container" [class.disabled]="snapshot.isDisabled" (sqxDropFile)="addFiles($event)" tabindex="1000">
<div class="header list">
<div class="row no-gutters">
<div class="col">
<div class="drop-area align-items-center" (click)="assetsDialog.show()" (sqxFileDrop)="addFiles($event)">
<div class="drop-area align-items-center" (click)="assetsDialog.show()" (sqxDropFile)="addFiles($event)">
Drop files or click
</div>
</div>

2
src/Squidex/app/features/content/shared/content-item.component.html

@ -11,7 +11,7 @@
</ng-template>
</td>
<td class="cell-auto" *ngFor="let field of schema.listFields; let i = index; trackBy: trackByField.bind(this)" [sqxStopClick]="isDirty || field.isInlineEditable">
<td class="cell-auto" *ngFor="let field of schema.listFields; let i = index; trackBy: trackByField.bind(this)" [sqxStopClick]="isDirty || (field.isInlineEditable && patchAllowed)">
<ng-container *ngIf="field.isInlineEditable && patchAllowed; else displayTemplate">
<sqx-content-item-editor [form]="patchForm.form" [field]="field"></sqx-content-item-editor>
</ng-container>

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

@ -95,7 +95,7 @@ export class FieldComponent implements OnChanges {
}
public sortFields(fields: NestedFieldDto[]) {
this.schemasState.sortFields(this.schema, fields, <any>this.field).subscribe();
this.schemasState.orderFields(this.schema, fields, <any>this.field).subscribe();
}
public lockField() {

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

@ -87,7 +87,7 @@ export class SchemaPageComponent extends ResourceOwner implements OnInit {
}
public sortFields(fields: FieldDto[]) {
this.schemasState.sortFields(this.schema, fields).subscribe();
this.schemasState.orderFields(this.schema, fields).subscribe();
}
public trackByField(index: number, field: FieldDto) {

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

@ -52,7 +52,7 @@
Duration:
</div>
</div>
<div class="col-auto">
<div class="col-3">
<div>
{{backup.started | sqxFromNow}}
</div>

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

@ -5,7 +5,7 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Component, Input, OnChanges } from '@angular/core';
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import {
@ -58,16 +58,18 @@ export class ClientComponent implements OnChanges {
) {
}
public ngOnChanges() {
this.renameForm.load(this.client);
public ngOnChanges(changes: SimpleChanges) {
if (changes['client']) {
this.renameForm.load(this.client);
const app = this.appsState.appName;
const app = this.appsState.appName;
this.connectHttpText = connectHttpText(this.apiUrl, app, this.client);
this.connectCLINetText = connectCLINetText(app, this.client, this.apiUrl);
this.connectCLINixText = connectCLINixText(app, this.client, this.apiUrl);
this.connectCLIWinText = connectCLIWinText(app, this.client, this.apiUrl);
this.connectLibraryText = connectLibrary(this.apiUrl, app, this.client);
this.connectHttpText = connectHttpText(this.apiUrl, app, this.client);
this.connectCLINetText = connectCLINetText(app, this.client, this.apiUrl);
this.connectCLINixText = connectCLINixText(app, this.client, this.apiUrl);
this.connectCLIWinText = connectCLIWinText(app, this.client, this.apiUrl);
this.connectLibraryText = connectLibrary(this.apiUrl, app, this.client);
}
}
public revoke() {

16
src/Squidex/app/features/settings/pages/languages/language.component.ts

@ -48,7 +48,12 @@ export class LanguageComponent implements OnChanges {
}
public ngOnChanges() {
this.resetForm();
this.isEditable = this.language.canUpdate;
this.editForm.load(this.language);
this.editForm.setEnabled(this.isEditable);
this.otherLanguage = this.fallbackLanguagesNew.at(0);
}
public toggleEditing() {
@ -94,15 +99,6 @@ export class LanguageComponent implements OnChanges {
this.otherLanguage = this.fallbackLanguagesNew.at(0);
}
private resetForm() {
this.isEditable = this.language.canUpdate;
this.editForm.load(this.language);
this.editForm.setEnabled(this.isEditable);
this.otherLanguage = this.fallbackLanguagesNew.at(0);
}
public trackByLanguage(index: number, language: AppLanguageDto) {
return language.iso2Code;
}

20
src/Squidex/app/features/settings/pages/patterns/pattern.component.ts

@ -5,7 +5,7 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Component, Input, OnInit } from '@angular/core';
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import {
@ -19,13 +19,13 @@ import {
styleUrls: ['./pattern.component.scss'],
templateUrl: './pattern.component.html'
})
export class PatternComponent implements OnInit {
export class PatternComponent implements OnChanges {
@Input()
public pattern: PatternDto;
public editForm = new EditPatternForm(this.formBuilder);
public isEditable = false;
public isEditable = true;
public isDeletable = false;
constructor(
@ -34,12 +34,14 @@ export class PatternComponent implements OnInit {
) {
}
public ngOnInit() {
this.isEditable = !this.pattern || this.pattern.canUpdate;
this.isDeletable = this.pattern && this.pattern.canDelete;
public ngOnChanges(changes: SimpleChanges) {
if (changes['pattern']) {
this.isEditable = !this.pattern || this.pattern.canUpdate;
this.isDeletable = this.pattern && this.pattern.canDelete;
this.editForm.load(this.pattern);
this.editForm.setEnabled(this.isEditable);
this.editForm.load(this.pattern);
this.editForm.setEnabled(this.isEditable);
}
}
public cancel() {
@ -61,7 +63,7 @@ export class PatternComponent implements OnInit {
if (this.pattern) {
this.patternsState.update(this.pattern, value)
.subscribe(newPattern => {
this.editForm.submitCompleted({ newValue: newPattern });
this.editForm.submitCompleted(newPattern);
}, error => {
this.editForm.submitFailed(error);
});

13
src/Squidex/app/framework/angular/forms/file-drop.directive.ts

@ -19,7 +19,7 @@ const ImageTypes = [
];
@Directive({
selector: '[sqxFileDrop]'
selector: '[sqxDropFile]'
})
export class FileDropDirective {
private dragCounter = 0;
@ -33,7 +33,10 @@ export class FileDropDirective {
@Input()
public noDrop: boolean;
@Output('sqxFileDrop')
@Input('sqxDropDisabled')
public disabled = false;
@Output('sqxDropFile')
public drop = new EventEmitter<File[]>();
constructor(
@ -60,7 +63,7 @@ export class FileDropDirective {
}
}
if (result.length > 0) {
if (result.length > 0 && !this.disabled) {
this.drop.emit(result);
}
@ -127,7 +130,7 @@ export class FileDropDirective {
private dragStart() {
this.dragCounter++;
if (this.dragCounter === 1) {
if (this.dragCounter === 1 && !this.disabled) {
this.renderer.addClass(this.element.nativeElement, 'drag');
}
}
@ -135,7 +138,7 @@ export class FileDropDirective {
private dragEnd(number?: number ) {
this.dragCounter = number || this.dragCounter - 1;
if (this.dragCounter === 0) {
if (this.dragCounter === 0 && !this.disabled) {
this.renderer.removeClass(this.element.nativeElement, 'drag');
}
}

2
src/Squidex/app/shared/components/asset-dialog.component.ts

@ -55,7 +55,7 @@ export class AssetDialogComponent extends StatefulComponent implements OnInit {
this.isEditable = this.asset.canUpdate;
this.annotateForm.load(this.asset);
this.annotateForm.setEnabled(!this.isEditable);
this.annotateForm.setEnabled(this.isEditable);
}
public generateSlug() {

70
src/Squidex/app/shared/components/asset-uploader.component.html

@ -1,44 +1,46 @@
<ng-container *ngIf="appsState.selectedValidApp | async">
<ul class="nav navbar-nav" *ngIf="assetUploader.uploads | async; let uploads" (sqxFileDrop)="addFiles($event)">
<li class="nav-item dropdown">
<span class="nav-link dropdown-toggle" (click)="modalMenu.toggle()">
<i class="icon-upload-3"></i>
<ng-container *ngIf="appsState.selectedValidApp | async; let selectedApp">
<ng-container *ngIf="selectedApp.canUploadAssets">
<ul class="nav navbar-nav" *ngIf="assetUploader.uploads | async; let uploads" (sqxDropFile)="addFiles($event)">
<li class="nav-item dropdown">
<span class="nav-link dropdown-toggle" (click)="modalMenu.toggle()">
<i class="icon-upload-3"></i>
<span>{{uploads.length}}</span>
</span>
<span>{{uploads.length}}</span>
</span>
<div class="dropdown-menu container" *ngIf="modalMenu.isOpen | async" (sqxFileDrop)="addFiles($event)" @fade>
<div class="uploads">
<small class="uploads-empty text-muted" *ngIf="uploads.length === 0">
No upload in progress, drop files here.
</small>
<div class="dropdown-menu container" *ngIf="modalMenu.isOpen | async" (sqxDropFile)="addFiles($event)" @fade>
<div class="uploads">
<small class="uploads-empty text-muted" *ngIf="uploads.length === 0">
No upload in progress, drop files here.
</small>
<div class="upload row no-gutters" *ngFor="let upload of uploads; trackBy: trackByUpload">
<div class="col-auto" [ngSwitch]="upload.status">
<div *ngSwitchCase="'Failed'" class="upload-status upload-status-failed">
<i class="icon-exclamation"></i>
<div class="upload row no-gutters" *ngFor="let upload of uploads; trackBy: trackByUpload">
<div class="col-auto" [ngSwitch]="upload.status">
<div *ngSwitchCase="'Failed'" class="upload-status upload-status-failed">
<i class="icon-exclamation"></i>
</div>
<div *ngSwitchCase="'Completed'" class="upload-status upload-status-success">
<i class="icon-checkmark"></i>
</div>
<div *ngSwitchDefault class="upload-status upload-status-running">
<i class="icon-hour-glass"></i>
</div>
</div>
<div *ngSwitchCase="'Completed'" class="upload-status upload-status-success">
<i class="icon-checkmark"></i>
<div class="col-6">
<div class="upload-name">{{upload.name}}</div>
</div>
<div *ngSwitchDefault class="upload-status upload-status-running">
<i class="icon-hour-glass"></i>
<div class="col">
<sqx-progress-bar [value]="upload.progress" [trailWidth]="1.5" [strokeWidth]="1.5" [showText]="false" [animated]="false"></sqx-progress-bar>
</div>
<div class="col-auto">
<button type="button" class="btn btn-text-secondary" (click)="stopUpload(upload)">
<i class="icon-close"></i>
</button>
</div>
</div>
<div class="col-6">
<div class="upload-name">{{upload.name}}</div>
</div>
<div class="col">
<sqx-progress-bar [value]="upload.progress" [trailWidth]="1.5" [strokeWidth]="1.5" [showText]="false" [animated]="false"></sqx-progress-bar>
</div>
<div class="col-auto">
<button type="button" class="btn btn-text-secondary" (click)="stopUpload(upload)">
<i class="icon-close"></i>
</button>
</div>
</div>
</div>
</div>
</li>
</ul>
</li>
</ul>
</ng-container>
</ng-container>

22
src/Squidex/app/shared/components/asset.component.html

@ -1,5 +1,8 @@
<ng-container *ngIf="!isListView; else listTemplate">
<div class="card" [class.selectable]="isSelectable" [class.border-primary]="isSelected" (click)="emitSelect()" (sqxFileDrop)="updateFile($event)" [noDrop]="true">
<div class="card" [class.selectable]="isSelectable" [class.border-primary]="isSelected" (click)="emitSelect()"
(sqxDropFile)="updateFile($event)"
[sqxDropDisabled]="!asset || !asset.canUpload"
[noDrop]="true">
<div class="card-body">
<div class="file-preview" *ngIf="asset && snapshot.progress === 0" @fade>
<span class="file-type" *ngIf="asset.fileType">
@ -23,7 +26,10 @@
<a class="file-download ml-2" [href]="asset | sqxAssetUrl" sqxStopClick sqxExternalLink="noicon">
<i class="icon-download"></i>
</a>
<a class="file-delete ml-2" (click)="emitDelete()" *ngIf="!isDisabled && !removeMode && asset.canDelete">
<a class="file-delete ml-2" *ngIf="!isDisabled && !removeMode && asset.canDelete"
(sqxConfirmClick)="emitDelete()"
confirmTitle="Delete asset"
confirmText="Do you really want to delete the asset?">
<i class="icon-delete"></i>
</a>
<a class="file-delete ml-2" (click)="emitRemove()" *ngIf="removeMode">
@ -74,7 +80,10 @@
</ng-container>
<ng-template #listTemplate>
<div class="table-items-row" [class.selectable]="isSelectable" (click)="emitSelect()" (sqxFileDrop)="updateFile($event)" [noDrop]="true">
<div class="table-items-row" [class.selectable]="isSelectable" (click)="emitSelect()"
(sqxDropFile)="updateFile($event)"
[sqxDropDisabled]="!asset || !asset.canUpload"
[noDrop]="true">
<div class="left-border" [class.hidden]="!isSelectable" [class.selected]="isSelected" ></div>
<div *ngIf="asset && asset.canPreview && snapshot.progress === 0" class="image drag-handle" [class.image-left]="!isSelectable" @fade>
@ -103,7 +112,10 @@
</a>
</td>
<td class="col-actions text-right" *ngIf="!isDisabled || removeMode">
<button type="button" class="btn btn-text-danger" (click)="emitDelete()" *ngIf="!isDisabled && !removeMode && asset.canDelete">
<button type="button" class="btn btn-text-danger" *ngIf="!isDisabled && !removeMode && asset.canDelete"
(sqxConfirmClick)="emitDelete()"
confirmTitle="Delete asset"
confirmText="Do you really want to delete the asset?">
<i class="icon-bin2"></i>
</button>
<button type="button" class="btn btn-text-secondary" (click)="emitRemove()" *ngIf="removeMode">
@ -125,7 +137,7 @@
</ng-template>
<ng-container *ngIf="asset">
<sqx-asset-dialog *sqxModalView="editDialog;onRoot:true"
<sqx-asset-dialog *sqxModalView="editDialog;onRoot:true"
[asset]="asset" [allTags]="allTags"
(cancel)="cancelEdit()"
(complete)="updateAsset($event, true)">

2
src/Squidex/app/shared/components/assets-list.component.html

@ -1,4 +1,4 @@
<div class="file-drop" (sqxFileDrop)="addFiles($event)" *ngIf="!isDisabled">
<div class="file-drop" (sqxDropFile)="addFiles($event)" *ngIf="!isDisabled && (state.canCreate | async)">
<h3 class="file-drop-header">Drop files here to upload</h3>
<div class="file-drop-or">or</div>

19
src/Squidex/app/shared/components/assets-list.component.ts

@ -5,7 +5,7 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, Output } from '@angular/core';
import { onErrorResumeNext } from 'rxjs/operators';
import {
@ -38,10 +38,23 @@ export class AssetsListComponent {
@Output()
public select = new EventEmitter<AssetDto>();
constructor(
private readonly changeDetector: ChangeDetectorRef
) {
}
public add(file: File, asset: AssetDto) {
this.newFiles = this.newFiles.remove(file);
if (asset.isDuplicate) {
setTimeout(() => {
this.newFiles = this.newFiles.remove(file);
this.state.add(asset);
this.changeDetector.detectChanges();
}, 2000);
} else {
this.newFiles = this.newFiles.remove(file);
this.state.add(asset);
}
}
public search() {

2
src/Squidex/app/shared/components/assets-selector.component.html

@ -21,7 +21,7 @@
</sqx-tag-editor>
</div>
<div class="col-6">
<sqx-search-form formClass="form" placeholder="Search by asset name" fieldExample="fileSize" [filter]="filter" (querySubmit)="search()"
<sqx-search-form formClass="form" placeholder="Search by name" fieldExample="fileSize" [filter]="filter" (querySubmit)="search()"
enableShortcut="true">
</sqx-search-form>
</div>

2
src/Squidex/app/shared/components/language-selector.component.html

@ -1,6 +1,6 @@
<div class="btn-group btn-group-{{size}}" *ngIf="isSmallMode">
<button type="button" class="btn btn-secondary" *ngFor="let language of languages; trackBy: trackByLanguage" title="{{language.englishName}}" [class.active]="language == selectedLanguage" (click)="selectLanguage(language)" tabindex="-1">
<span class="iso-code">{{language.iso2Code}}</span>
{{language.iso2Code}}
</button>
</div>

2
src/Squidex/app/shared/components/markdown-editor.component.html

@ -1,4 +1,4 @@
<div #container class="drop-container" (sqxFileDrop)="insertFiles($event)" [onlyImages]="true">
<div #container class="drop-container" (sqxDropFile)="insertFiles($event)" [onlyImages]="true">
<div #inner [class.fullscreen]="snapshot.isFullscreen">
<textarea class="form-control" #editor></textarea>
</div>

8
src/Squidex/app/shared/components/markdown-editor.component.ts

@ -78,6 +78,10 @@ export class MarkdownEditorComponent extends StatefulControlComponent<State, str
}
private showSelector = () => {
if (this.isDisabled) {
return;
}
this.assetsDialog.show();
}
@ -220,6 +224,10 @@ export class MarkdownEditorComponent extends StatefulControlComponent<State, str
}
private uploadFile(doc: any, file: File) {
if (this.isDisabled) {
return;
}
const uploadCursor = doc.getCursor();
const uploadText = `![Uploading file...${new Date()}]()`;

2
src/Squidex/app/shared/components/rich-editor.component.html

@ -1,4 +1,4 @@
<div class="drop-container" (sqxFileDrop)="insertFiles($event)" [onlyImages]="true">
<div class="drop-container" (sqxDropFile)="insertFiles($event)" [onlyImages]="true">
<div class="editor" #editor></div>
</div>

4
src/Squidex/app/shared/components/rich-editor.component.ts

@ -78,6 +78,10 @@ export class RichEditorComponent extends StatefulControlComponent<any, string> i
}
private showSelector = () => {
if (this.isDisabled) {
return;
}
this.assetsDialog.show();
}

2
src/Squidex/app/shared/services/app-languages.service.spec.ts

@ -124,7 +124,7 @@ describe('AppLanguagesService', () => {
const resource: Resource = {
_links: {
update: { method: 'DELETE', href: 'api/apps/my-app/languages/de' }
delete: { method: 'DELETE', href: 'api/apps/my-app/languages/de' }
}
};

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

@ -109,7 +109,7 @@ export class AppLanguagesService {
}
public deleteLanguage(appName: string, resource: Resource, version: Version): Observable<AppLanguagesDto> {
const link = resource._links['update'];
const link = resource._links['delete'];
const url = this.apiUrl.buildUrl(link.href);

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

@ -35,6 +35,7 @@ export class AppDto {
public readonly canReadRoles: boolean;
public readonly canReadRules: boolean;
public readonly canReadSchemas: boolean;
public readonly canUploadAssets: boolean;
constructor(links: ResourceLinks,
public readonly id: string,
@ -61,6 +62,7 @@ export class AppDto {
this.canReadRoles = hasAnyLink(links, 'roles');
this.canReadRules = hasAnyLink(links, 'rules');
this.canReadSchemas = hasAnyLink(links, 'schemas');
this.canUploadAssets = hasAnyLink(links, 'assets/create');
}
}

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

@ -27,7 +27,11 @@ import {
Versioned
} from '@app/framework';
export class AssetsDto extends ResultSet<AssetDto> {}
export class AssetsDto extends ResultSet<AssetDto> {
public get canCreate() {
return hasAnyLink(this._links, 'create');
}
}
export class AssetDto {
public readonly _meta: Metadata = {};
@ -39,6 +43,10 @@ export class AssetDto {
public readonly canUpdate: boolean;
public readonly canUpload: boolean;
public get isDuplicate() {
return this._meta && this._meta['isDuplicate'] === 'true';
}
public get contentUrl() {
return this._links['content'].href;
}

2
src/Squidex/app/shared/services/rules.service.ts

@ -111,6 +111,8 @@ export class RuleDto {
public readonly action: any,
public readonly actionType: string
) {
this._links = links;
this.canDelete = hasAnyLink(links, 'delete');
this.canDisable = hasAnyLink(links, 'disable');
this.canEnable = hasAnyLink(links, 'enable');

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

@ -205,7 +205,7 @@ describe('SchemasService', () => {
const resource: Resource = {
_links: {
updateCategory: { method: 'PUT', href: '/api/apps/my-app/schemas/my-schema/category' }
['update/category']: { method: 'PUT', href: '/api/apps/my-app/schemas/my-schema/category' }
}
};
@ -236,7 +236,7 @@ describe('SchemasService', () => {
const resource: Resource = {
_links: {
updateUrls: { method: 'PUT', href: '/api/apps/my-app/schemas/my-schema/preview-urls' }
['update/urls']: { method: 'PUT', href: '/api/apps/my-app/schemas/my-schema/preview-urls' }
}
};
@ -387,7 +387,7 @@ describe('SchemasService', () => {
const resource: Resource = {
_links: {
order: { method: 'PUT', href: '/api/apps/my-app/schemas/my-schema/fields/ordering' }
['fields/order']: { method: 'PUT', href: '/api/apps/my-app/schemas/my-schema/fields/ordering' }
}
};

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

@ -310,7 +310,7 @@ export class SchemasService {
}
public putCategory(appName: string, resource: Resource, dto: UpdateSchemaCategoryDto, version: Version): Observable<SchemaDetailsDto> {
const link = resource._links['updateCategory'];
const link = resource._links['update/category'];
const url = this.apiUrl.buildUrl(link.href);
@ -325,7 +325,7 @@ export class SchemasService {
}
public putPreviewUrls(appName: string, resource: Resource, dto: {}, version: Version): Observable<SchemaDetailsDto> {
const link = resource._links['updateUrls'];
const link = resource._links['update/urls'];
const url = this.apiUrl.buildUrl(link.href);
@ -385,11 +385,11 @@ export class SchemasService {
}
public putFieldOrdering(appName: string, resource: Resource, dto: number[], version: Version): Observable<SchemaDetailsDto> {
const link = resource._links['order'];
const link = resource._links['fields/order'];
const url = this.apiUrl.buildUrl(link.href);
return HTTP.requestVersioned(this.http, link.method, url, version, dto).pipe(
return HTTP.requestVersioned(this.http, link.method, url, version, { fieldIds: dto }).pipe(
map(({ version: newVersion, payload }) => {
return parseSchemaWithDetails(payload.body, newVersion);
}),

2
src/Squidex/app/shared/state/asset-uploader.state.ts

@ -75,7 +75,7 @@ export class AssetUploaderState extends State<Snapshot> {
const stream = this.assetsService.uploadFile(this.appName, file);
return this.upload(stream, MathHelper.guid(), file, asset => {
if (asset._meta && asset._meta['isDuplicate'] === 'true') {
if (asset.isDuplicate) {
this.dialogs.notifyError('Asset has already been uploaded.');
} else if (target) {
target.add(asset);

10
src/Squidex/app/shared/state/assets.state.ts

@ -39,6 +39,9 @@ interface Snapshot {
// Indicates if the assets are loaded.
isLoaded?: boolean;
// Indicates if the user can create assets.
canCreate?: boolean;
}
@Injectable()
@ -64,6 +67,9 @@ export class AssetsState extends State<Snapshot> {
public isLoaded =
this.project(x => !!x.isLoaded);
public canCreate =
this.project(x => !!x.canCreate);
constructor(
private readonly appsState: AppsState,
private readonly assetsService: AssetsService,
@ -89,7 +95,7 @@ export class AssetsState extends State<Snapshot> {
Object.keys(this.snapshot.tagsSelected)),
this.assetsService.getTags(this.appName)
).pipe(
tap(([ { items, total }, tags ]) => {
tap(([ { items, total, canCreate }, tags ]) => {
if (isReload) {
this.dialogs.notifyInfo('Assets reloaded.');
}
@ -98,7 +104,7 @@ export class AssetsState extends State<Snapshot> {
const assets = ImmutableArray.of(items);
const assetsPager = s.assetsPager.setCount(total);
return { ...s, assets, assetsPager, isLoaded: true, tags };
return { ...s, assets, assetsPager, isLoaded: true, tags, canCreate };
});
}),
shareSubscribed(this.dialogs));

12
src/Squidex/app/shared/state/schemas.state.spec.ts

@ -69,7 +69,7 @@ describe('SchemasState', () => {
expect(schemasState.snapshot.schemas.values).toEqual(oldSchemas.items);
expect(schemasState.snapshot.isLoaded).toBeTruthy();
expect(schemasState.snapshot.categories).toEqual({ 'category1': false, 'category2': false, '': true });
expect(schemasState.snapshot.categories).toEqual({ 'category1': false, 'category2': false });
schemasService.verifyAll();
});
@ -83,7 +83,7 @@ describe('SchemasState', () => {
expect(schemasState.snapshot.schemas.values).toEqual(oldSchemas.items);
expect(schemasState.snapshot.isLoaded).toBeTruthy();
expect(schemasState.snapshot.categories).toEqual({ 'category1': false, 'category2': false, 'category3': true, '': true });
expect(schemasState.snapshot.categories).toEqual({ 'category1': false, 'category2': false, 'category3': true });
schemasService.verifyAll();
});
@ -111,13 +111,13 @@ describe('SchemasState', () => {
it('should add category', () => {
schemasState.addCategory('category3');
expect(schemasState.snapshot.categories).toEqual({ 'category1': false, 'category2': false, 'category3': true, '': true });
expect(schemasState.snapshot.categories).toEqual({ 'category1': false, 'category2': false, 'category3': true });
});
it('should remove category', () => {
schemasState.removeCategory('category1');
expect(schemasState.snapshot.categories).toEqual({ 'category2': false, '': true });
expect(schemasState.snapshot.categories).toEqual({ 'category2': false });
});
it('should return schema on select and reload when already loaded', () => {
@ -373,7 +373,7 @@ describe('SchemasState', () => {
schemasService.setup(x => x.putFieldOrdering(app, schema1, [schema.fields[1].fieldId, schema.fields[2].fieldId], version))
.returns(() => of(updated)).verifiable();
schemasState.sortFields(schema1, [schema.fields[1], schema.fields[2]]).subscribe();
schemasState.orderFields(schema1, [schema.fields[1], schema.fields[2]]).subscribe();
const schema1New = <SchemaDetailsDto>schemasState.snapshot.schemas.at(0);
@ -387,7 +387,7 @@ describe('SchemasState', () => {
schemasService.setup(x => x.putFieldOrdering(app, schema.fields[0], [schema.fields[1].fieldId, schema.fields[2].fieldId], version))
.returns(() => of(updated)).verifiable();
schemasState.sortFields(schema1, [schema.fields[1], schema.fields[2]], schema.fields[0]).subscribe();
schemasState.orderFields(schema1, [schema.fields[1], schema.fields[2]], schema.fields[0]).subscribe();
const schema1New = <SchemaDetailsDto>schemasState.snapshot.schemas.at(0);

8
src/Squidex/app/shared/state/schemas.state.ts

@ -140,7 +140,9 @@ export class SchemasState extends State<Snapshot> {
this.next(s => {
const schemas = s.schemas.push(created).sortByStringAsc(x => x.displayName);
return { ...s, schemas };
const categories = buildCategories(s.categories, schemas);
return { ...s, schemas, categories };
});
}),
shareSubscribed(this.dialogs, { silent: true }));
@ -231,7 +233,7 @@ export class SchemasState extends State<Snapshot> {
shareMapSubscribed(this.dialogs, x => getField(x, request, parent), { silent: true }));
}
public sortFields(schema: SchemaDto, fields: any[], parent?: RootFieldDto): Observable<SchemaDetailsDto> {
public orderFields(schema: SchemaDto, fields: any[], parent?: RootFieldDto): Observable<SchemaDetailsDto> {
return this.schemasService.putFieldOrdering(this.appName, parent || schema, fields.map(t => t.fieldId), schema.version).pipe(
tap(updated => {
this.replaceSchema(updated);
@ -343,8 +345,6 @@ function buildCategories(categories: { [name: string]: boolean }, schemas?: Sche
}
}
categories[''] = true;
return categories;
}

9
tests/Squidex.Domain.Apps.Entities.Tests/Apps/RoleExtensionsTests.cs

@ -67,5 +67,14 @@ namespace Squidex.Domain.Apps.Entities.Apps
Assert.Equal(Permission.Any, result.First().Id);
}
[Fact]
public void Should_remove_common_permission()
{
var source = new PermissionSet("squidex.apps.my-app.common");
var result = source.WithoutApp("my-app");
Assert.Empty(result);
}
}
}

Loading…
Cancel
Save