Browse Source

Asset field.

pull/65/head
Sebastian Stehle 10 years ago
parent
commit
795b0a7116
  1. 4
      src/Squidex.Read.MongoDb/Assets/MongoAssetEntity.cs
  2. 5
      src/Squidex.Read.MongoDb/Assets/MongoAssetRepository.cs
  3. 5
      src/Squidex.Read.MongoDb/Assets/MongoAssetRepository_EventHandling.cs
  4. 2
      src/Squidex.Read/Assets/IAssetEntity.cs
  5. 4
      src/Squidex.Write/Assets/AssetCommandHandler.cs
  6. 3
      src/Squidex/Controllers/Api/Assets/AssetController.cs
  7. 3
      src/Squidex/app/features/assets/pages/assets-page.component.html
  8. 28
      src/Squidex/app/features/assets/pages/assets-page.component.ts
  9. 2
      src/Squidex/app/features/schemas/pages/schema/field.component.html
  10. 13
      src/Squidex/app/features/schemas/pages/schema/field.component.scss
  11. 20
      src/Squidex/app/shared/components/asset.component.html
  12. 15
      src/Squidex/app/shared/components/asset.component.ts
  13. 3
      src/Squidex/app/shared/components/assets-editor.component.html
  14. 1
      src/Squidex/app/shared/components/assets-editor.component.scss
  15. 30
      src/Squidex/app/shared/components/assets-editor.component.ts
  16. 12
      src/Squidex/app/shared/services/auth.service.ts
  17. 12
      src/Squidex/app/shared/utils/messages.ts
  18. 4
      src/Squidex/package.json

4
src/Squidex.Read.MongoDb/Assets/MongoAssetEntity.cs

@ -28,6 +28,10 @@ namespace Squidex.Read.MongoDb.Assets
[BsonElement] [BsonElement]
public bool IsImage { get; set; } public bool IsImage { get; set; }
[BsonRequired]
[BsonElement]
public bool IsDeleted { get; set; }
[BsonRequired] [BsonRequired]
[BsonElement] [BsonElement]
public long Version { get; set; } public long Version { get; set; }

5
src/Squidex.Read.MongoDb/Assets/MongoAssetRepository.cs

@ -34,7 +34,7 @@ namespace Squidex.Read.MongoDb.Assets
protected override Task SetupCollectionAsync(IMongoCollection<MongoAssetEntity> collection) protected override Task SetupCollectionAsync(IMongoCollection<MongoAssetEntity> collection)
{ {
return collection.Indexes.CreateOneAsync(IndexKeys.Descending(x => x.LastModified).Ascending(x => x.AppId).Ascending(x => x.FileName).Ascending(x => x.MimeType)); return collection.Indexes.CreateOneAsync(IndexKeys.Ascending(x => x.AppId).Ascending(x => x.IsDeleted).Descending(x => x.LastModified).Ascending(x => x.FileName).Ascending(x => x.MimeType));
} }
public async Task<bool> IsValidAsync(Guid assetId) public async Task<bool> IsValidAsync(Guid assetId)
@ -74,7 +74,8 @@ namespace Squidex.Read.MongoDb.Assets
{ {
var filters = new List<FilterDefinition<MongoAssetEntity>> var filters = new List<FilterDefinition<MongoAssetEntity>>
{ {
Filter.Eq(x => x.AppId, appId) Filter.Eq(x => x.AppId, appId),
Filter.Eq(x => x.IsDeleted, false)
}; };
if (ids != null && ids.Count > 0) if (ids != null && ids.Count > 0)

5
src/Squidex.Read.MongoDb/Assets/MongoAssetRepository_EventHandling.cs

@ -58,7 +58,10 @@ namespace Squidex.Read.MongoDb.Assets
protected Task On(AssetDeleted @event, EnvelopeHeaders headers) protected Task On(AssetDeleted @event, EnvelopeHeaders headers)
{ {
return Collection.DeleteOneAsync(Filter.Eq(x => x.Id, @event.AssetId)); return Collection.UpdateAsync(@event, headers, a =>
{
a.IsDeleted = true;
});
} }
} }
} }

2
src/Squidex.Read/Assets/IAssetEntity.cs

@ -20,6 +20,8 @@ namespace Squidex.Read.Assets
bool IsImage { get; } bool IsImage { get; }
bool IsDeleted { get; }
int? PixelWidth { get; } int? PixelWidth { get; }
int? PixelHeight { get; } int? PixelHeight { get; }

4
src/Squidex.Write/Assets/AssetCommandHandler.cs

@ -44,7 +44,7 @@ namespace Squidex.Write.Assets
c.Create(command); c.Create(command);
await assetStore.UploadAsync(c.Id, 1, null, command.File.OpenRead()); await assetStore.UploadAsync(c.Id, c.Version, null, command.File.OpenRead());
context.Succeed(EntityCreatedResult.Create(c.Id, c.Version)); context.Succeed(EntityCreatedResult.Create(c.Id, c.Version));
}); });
@ -58,7 +58,7 @@ namespace Squidex.Write.Assets
c.Update(command); c.Update(command);
await assetStore.UploadAsync(c.Id, 2, null, command.File.OpenRead()); await assetStore.UploadAsync(c.Id, c.Version, null, command.File.OpenRead());
}); });
} }

3
src/Squidex/Controllers/Api/Assets/AssetController.cs

@ -17,7 +17,6 @@ using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives; using Microsoft.Extensions.Primitives;
using NSwag.Annotations; using NSwag.Annotations;
using Squidex.Controllers.Api.Assets.Models; using Squidex.Controllers.Api.Assets.Models;
using Squidex.Controllers.Api.Schemas.Models;
using Squidex.Core.Identity; using Squidex.Core.Identity;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Commands; using Squidex.Infrastructure.CQRS.Commands;
@ -122,7 +121,7 @@ namespace Squidex.Controllers.Api.Assets
{ {
var entity = await assetRepository.FindAssetAsync(id); var entity = await assetRepository.FindAssetAsync(id);
if (entity == null) if (entity == null || entity.IsDeleted)
{ {
return NotFound(); return NotFound();
} }

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

@ -41,7 +41,8 @@
(loaded)="onAssetLoaded(file, $event)"> (loaded)="onAssetLoaded(file, $event)">
</sqx-asset> </sqx-asset>
<sqx-asset class="col-3" *ngFor="let asset of assetsItems" [asset]="asset" <sqx-asset class="col-3" *ngFor="let asset of assetsItems" [asset]="asset"
(deleting)="onAssetDeleting($event)"> (deleting)="onAssetDeleting($event)"
(updated)="onAssetUpdated($event)">
</sqx-asset> </sqx-asset>
</div> </div>

28
src/Squidex/app/features/assets/pages/assets-page.component.ts

@ -7,16 +7,19 @@
// tslint:disable:prefer-for-of // tslint:disable:prefer-for-of
import { Component, OnInit } from '@angular/core'; import { Component, OnDestroy, OnInit } from '@angular/core';
import { FormControl } from '@angular/forms'; import { FormControl } from '@angular/forms';
import { Subscription } from 'rxjs';
import { import {
AppComponentBase, AppComponentBase,
AppsStoreService, AppsStoreService,
AssetDto, AssetDto,
AssetsService, AssetsService,
AssetUpdated,
fadeAnimation, fadeAnimation,
ImmutableArray, ImmutableArray,
MessageBus,
NotificationService, NotificationService,
Pager Pager
} from 'shared'; } from 'shared';
@ -29,7 +32,9 @@ import {
fadeAnimation fadeAnimation
] ]
}) })
export class AssetsPageComponent extends AppComponentBase implements OnInit { export class AssetsPageComponent extends AppComponentBase implements OnDestroy, OnInit {
private assetUpdatedSubscription: Subscription;
public newFiles = ImmutableArray.empty<File>(); public newFiles = ImmutableArray.empty<File>();
public assetsItems = ImmutableArray.empty<AssetDto>(); public assetsItems = ImmutableArray.empty<AssetDto>();
@ -38,15 +43,28 @@ export class AssetsPageComponent extends AppComponentBase implements OnInit {
public assertQuery = ''; public assertQuery = '';
constructor(apps: AppsStoreService, notifications: NotificationService, constructor(apps: AppsStoreService, notifications: NotificationService,
private readonly assetsService: AssetsService private readonly assetsService: AssetsService,
private readonly messageBus: MessageBus
) { ) {
super(notifications, apps); super(notifications, apps);
} }
public ngOnInit() { public ngOnInit() {
this.assetUpdatedSubscription =
this.messageBus.of(AssetUpdated)
.subscribe(event => {
if (event.sender !== this) {
this.assetsItems = this.assetsItems.map(x => x.id === event.assetDto.id ? event.assetDto : x);
}
});
this.load(); this.load();
} }
public ngOnDestroy() {
this.assetUpdatedSubscription.unsubscribe();
}
public search() { public search() {
this.assetsPager = new Pager(0, 0, 12); this.assetsPager = new Pager(0, 0, 12);
this.assertQuery = this.assetsFilter.value; this.assertQuery = this.assetsFilter.value;
@ -83,6 +101,10 @@ export class AssetsPageComponent extends AppComponentBase implements OnInit {
this.assetsPager = this.assetsPager.incrementCount(); this.assetsPager = this.assetsPager.incrementCount();
} }
public onAssetUpdated(asset: AssetDto) {
this.messageBus.publish(new AssetUpdated(asset, this));
}
public onAssetFailed(file: File) { public onAssetFailed(file: File) {
this.newFiles = this.newFiles.remove(file); this.newFiles = this.newFiles.remove(file);
} }

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

@ -1,5 +1,7 @@
<div class="table-items-row field"> <div class="table-items-row field">
<div class="field-summary"> <div class="field-summary">
<i class="icon-drag field-drag"></i>
<div class="row"> <div class="row">
<div class="col col-6"> <div class="col col-6">
<span class="field-name"> <span class="field-name">

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

@ -9,10 +9,21 @@ $field-header: #e7ebef;
.field { .field {
&-summary { &-summary {
padding: 1rem 1.25rem; padding: 1rem 1.25rem 1rem 1.5rem;
position: relative;
line-height: 2.5rem; line-height: 2.5rem;
} }
&-drag {
@include absolute(0, auto, 0, 0);
z-index: 1000;
font-size: 1.4rem;
font-weight: normal;
line-height: 4.8rem;
cursor: move;
color: $color-border;
}
&-icon { &-icon {
margin-right: 1rem; margin-right: 1rem;
color: $color-border-dark; color: $color-border-dark;

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

@ -22,16 +22,12 @@
<i class="icon-download"></i> <i class="icon-download"></i>
</a> </a>
<span *ngIf="!closeMode"> <a class="file-delete" (click)="deleting.emit(asset)" *ngIf="!closeMode">
<a class="file-delete" (click)="deleting.emit(asset)"> <i class="icon-delete"></i>
<i class="icon-delete"></i> </a>
</a> <a class="file-delete" (click)="closing.emit(asset)" *ngIf="closeMode">
</span> <i class="icon-close"></i>
<span *ngIf="closeMode"> </a>
<a class="file-delete" (click)="closing.emit(asset)">
<i class="icon-close"></i>
</a>
</span>
<span class="file-overlay-type" *ngIf="fileType"> <span class="file-overlay-type" *ngIf="fileType">
{{fileType}} {{fileType}}
@ -45,7 +41,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="card-footer" dnd-draggable-handle> <div class="card-footer" dnd-draggable-handle *ngIf="asset && progress == 0">
<div class="file-name" [attr.title]="fileName"> <div class="file-name" [attr.title]="fileName">
{{fileName}} {{fileName}}
</div> </div>
@ -58,7 +54,7 @@
<sqx-progress-bar mode="Circle" [value]="progress"></sqx-progress-bar> <sqx-progress-bar mode="Circle" [value]="progress"></sqx-progress-bar>
</div> </div>
<div class="drop-overlay"> <div class="drop-overlay" *ngIf="asset && progress == 0">
<div class="drop-overlay-background"></div> <div class="drop-overlay-background"></div>
<span class="drop-overlay-text">Drop to update</span> <span class="drop-overlay-text">Drop to update</span>

15
src/Squidex/app/shared/components/asset.component.ts

@ -63,6 +63,9 @@ export class AssetComponent extends AppComponentBase implements OnInit {
@Output() @Output()
public closing = new EventEmitter<AssetDto>(); public closing = new EventEmitter<AssetDto>();
@Output()
public updated = new EventEmitter<AssetDto>();
@Output() @Output()
public deleting = new EventEmitter<AssetDto>(); public deleting = new EventEmitter<AssetDto>();
@ -119,7 +122,7 @@ export class AssetComponent extends AppComponentBase implements OnInit {
this.notifyError(error); this.notifyError(error);
}); });
} else { } else {
this.updateAsset(this.asset); this.updateAsset(this.asset, false);
} }
} }
@ -143,7 +146,7 @@ export class AssetComponent extends AppComponentBase implements OnInit {
result.pixelWidth, result.pixelWidth,
result.pixelHeight, result.pixelHeight,
result.version); result.version);
this.updateAsset(asset); this.updateAsset(asset, true);
} else { } else {
this.progress = result; this.progress = result;
} }
@ -181,7 +184,7 @@ export class AssetComponent extends AppComponentBase implements OnInit {
this.asset.pixelHeight, this.asset.pixelHeight,
this.asset.version); this.asset.version);
this.updateAsset(asset); this.updateAsset(asset, true);
this.resetRename(); this.resetRename();
}, error => { }, error => {
this.notifyError(error); this.notifyError(error);
@ -198,7 +201,7 @@ export class AssetComponent extends AppComponentBase implements OnInit {
this.renameDialog.hide(); this.renameDialog.hide();
} }
private updateAsset(asset: AssetDto) { private updateAsset(asset: AssetDto, emitEvent: boolean) {
this.asset = asset; this.asset = asset;
this.fileUrl = FileHelper.assetUrl(this.apiUrl, asset); this.fileUrl = FileHelper.assetUrl(this.apiUrl, asset);
this.fileInfo = FileHelper.assetInfo(asset); this.fileInfo = FileHelper.assetInfo(asset);
@ -209,6 +212,10 @@ export class AssetComponent extends AppComponentBase implements OnInit {
this.previewUrl = FileHelper.assetPreviewUrl(this.apiUrl, asset); this.previewUrl = FileHelper.assetPreviewUrl(this.apiUrl, asset);
this.version = asset.version; this.version = asset.version;
if (emitEvent) {
this.updated.emit(asset);
}
this.resetRename(); this.resetRename();
} }
} }

3
src/Squidex/app/shared/components/assets-editor.component.html

@ -11,7 +11,8 @@
(loaded)="onAssetLoaded(file, $event)"> (loaded)="onAssetLoaded(file, $event)">
</sqx-asset> </sqx-asset>
<sqx-asset class="col-4" *ngFor="let asset of oldAssets" [asset]="asset" [closeMode]="true" <sqx-asset class="col-4" *ngFor="let asset of oldAssets" [asset]="asset" [closeMode]="true"
(closing)="onAssetRemoving($event)"> (closing)="onAssetRemoving($event)"
(updated)="onAssetUpdated($event)">
</sqx-asset> </sqx-asset>
</div> </div>
</div> </div>

1
src/Squidex/app/shared/components/assets-editor.component.scss

@ -17,6 +17,7 @@
.drop-area { .drop-area {
& { & {
@include transition(border-color .4s ease); @include transition(border-color .4s ease);
@include border-radius;
border: 2px dashed $color-border; border: 2px dashed $color-border;
height: $asset-height; height: $asset-height;
font-size: 1.2rem; font-size: 1.2rem;

30
src/Squidex/app/shared/components/assets-editor.component.ts

@ -7,8 +7,9 @@
// tslint:disable:prefer-for-of // tslint:disable:prefer-for-of
import { Component, forwardRef } from '@angular/core'; import { Component, forwardRef, OnDestroy, OnInit } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { Subscription } from 'rxjs';
import { AppComponentBase } from './app.component-base'; import { AppComponentBase } from './app.component-base';
@ -16,7 +17,9 @@ import {
AppsStoreService, AppsStoreService,
AssetDto, AssetDto,
AssetsService, AssetsService,
AssetUpdated,
ImmutableArray, ImmutableArray,
MessageBus,
NotificationService NotificationService
} from './../declarations-base'; } from './../declarations-base';
@ -32,7 +35,9 @@ export const SQX_ASSETS_EDITOR_CONTROL_VALUE_ACCESSOR: any = {
templateUrl: './assets-editor.component.html', templateUrl: './assets-editor.component.html',
providers: [SQX_ASSETS_EDITOR_CONTROL_VALUE_ACCESSOR] providers: [SQX_ASSETS_EDITOR_CONTROL_VALUE_ACCESSOR]
}) })
export class AssetsEditorComponent extends AppComponentBase implements ControlValueAccessor { export class AssetsEditorComponent extends AppComponentBase implements ControlValueAccessor, OnDestroy, OnInit {
private assetUpdatedSubscription: Subscription;
public newAssets = ImmutableArray.empty<File>(); public newAssets = ImmutableArray.empty<File>();
public oldAssets = ImmutableArray.empty<AssetDto>(); public oldAssets = ImmutableArray.empty<AssetDto>();
@ -42,11 +47,26 @@ export class AssetsEditorComponent extends AppComponentBase implements ControlVa
public isDisabled = false; public isDisabled = false;
constructor(apps: AppsStoreService, notifications: NotificationService, constructor(apps: AppsStoreService, notifications: NotificationService,
private readonly assetsService: AssetsService private readonly assetsService: AssetsService,
private readonly messageBus: MessageBus
) { ) {
super(notifications, apps); super(notifications, apps);
} }
public ngOnInit() {
this.assetUpdatedSubscription =
this.messageBus.of(AssetUpdated)
.subscribe(event => {
if (event.sender !== this) {
this.oldAssets = this.oldAssets.map(x => x.id === event.assetDto.id ? event.assetDto : x);
}
});
}
public ngOnDestroy() {
this.assetUpdatedSubscription.unsubscribe();
}
public writeValue(value: any) { public writeValue(value: any) {
this.oldAssets = ImmutableArray.empty<AssetDto>(); this.oldAssets = ImmutableArray.empty<AssetDto>();
@ -90,6 +110,10 @@ export class AssetsEditorComponent extends AppComponentBase implements ControlVa
this.updateValue(); this.updateValue();
} }
public onAssetUpdated(asset: AssetDto) {
this.messageBus.publish(new AssetUpdated(asset, this));
}
public onAssetRemoving(asset: AssetDto) { public onAssetRemoving(asset: AssetDto) {
this.oldAssets = this.oldAssets.remove(asset); this.oldAssets = this.oldAssets.remove(asset);
} }

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

@ -7,7 +7,6 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Headers, Http, RequestOptions, Response } from '@angular/http'; import { Headers, Http, RequestOptions, Response } from '@angular/http';
import { Router } from '@angular/router';
import { Observable, Subject } from 'rxjs'; import { Observable, Subject } from 'rxjs';
import { import {
@ -67,8 +66,7 @@ export class AuthService {
} }
constructor(apiUrl: ApiUrlConfig, constructor(apiUrl: ApiUrlConfig,
private readonly http: Http, private readonly http: Http
private readonly router: Router
) { ) {
if (!apiUrl) { if (!apiUrl) {
return; return;
@ -205,14 +203,16 @@ export class AuthService {
} }
}) })
.catch((error: Response) => { .catch((error: Response) => {
if (error.status === 401) { if (error.status >= 401 && error.status <= 404 && (!this.user || this.user.user.expired)) {
this.logoutRedirect(); this.logoutRedirect();
return Observable.empty<Response>(); return Observable.empty<Response>();
} else if (error.status === 403) { } else if (error.status === 401) {
this.router.navigate(['/404']); this.logoutRedirect();
return Observable.empty<Response>(); return Observable.empty<Response>();
} else if (error.status === 403) {
error.status = 404;
} }
return Observable.throw(error); return Observable.throw(error);
}); });

12
src/Squidex/app/shared/utils/messages.ts

@ -5,4 +5,14 @@
* Copyright (c) Sebastian Stehle. All rights reserved * Copyright (c) Sebastian Stehle. All rights reserved
*/ */
export class HistoryChannelUpdated { } import { AssetDto } from './../services/assets.service';
export class HistoryChannelUpdated { }
export class AssetUpdated {
constructor(
public readonly assetDto: AssetDto,
public readonly sender: any
) {
}
}

4
src/Squidex/package.json

@ -48,7 +48,7 @@
"@types/node": "7.0.5", "@types/node": "7.0.5",
"angular2-router-loader": "0.3.5", "angular2-router-loader": "0.3.5",
"angular2-template-loader": "0.6.2", "angular2-template-loader": "0.6.2",
"awesome-typescript-loader": "3.1.2", "awesome-typescript-loader": "3.1.3",
"cpx": "1.5.0", "cpx": "1.5.0",
"css-loader": "0.28.0", "css-loader": "0.28.0",
"exports-loader": "0.6.4", "exports-loader": "0.6.4",
@ -79,7 +79,7 @@
"tslint": "4.5.1", "tslint": "4.5.1",
"tslint-loader": "3.5.3", "tslint-loader": "3.5.3",
"typemoq": "1.5.0", "typemoq": "1.5.0",
"typescript": "2.3.1", "typescript": "2.3.2",
"underscore": "1.8.3", "underscore": "1.8.3",
"webpack": "2.4.1", "webpack": "2.4.1",
"webpack-dev-server": "2.4.5", "webpack-dev-server": "2.4.5",

Loading…
Cancel
Save