Browse Source

Temp

pull/479/head
Sebastian 7 years ago
parent
commit
2cb6ef66e4
  1. 10
      backend/src/Squidex.Domain.Apps.Entities/Assets/AssetCommandMiddleware.cs
  2. 5
      frontend/app/framework/angular/forms/progress-bar.component.ts
  3. 4
      frontend/app/framework/angular/http/http-extensions.ts
  4. 18
      frontend/app/shared/components/assets/asset-dialog.component.html
  5. 12
      frontend/app/shared/components/assets/asset-dialog.component.scss
  6. 101
      frontend/app/shared/components/assets/asset-dialog.component.ts
  7. 1
      frontend/app/shared/components/assets/asset.component.html
  8. 11
      frontend/app/shared/components/assets/asset.component.ts
  9. 33
      frontend/app/shared/components/assets/image-editor.component.ts
  10. 4
      frontend/app/shared/services/assets.service.ts
  11. 12
      frontend/app/shared/state/asset-uploader.state.ts
  12. 6
      frontend/app/shared/state/assets.forms.ts

10
backend/src/Squidex.Domain.Apps.Entities/Assets/AssetCommandMiddleware.cs

@ -75,7 +75,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
} }
} }
await UploadAsync(context, tempFile, createAsset, true, next); await UploadAsync(context, tempFile, createAsset, createAsset.Tags, true, next);
} }
finally finally
{ {
@ -91,7 +91,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
try try
{ {
await UploadAsync(context, tempFile, updateAsset, false, next); await UploadAsync(context, tempFile, updateAsset, null, false, next);
} }
finally finally
{ {
@ -107,9 +107,9 @@ namespace Squidex.Domain.Apps.Entities.Assets
} }
} }
private async Task UploadAsync(CommandContext context, string tempFile, UploadAssetCommand command, bool created, NextDelegate next) private async Task UploadAsync(CommandContext context, string tempFile, UploadAssetCommand command, HashSet<string>? tags, bool created, NextDelegate next)
{ {
await EnrichWithMetadataAsync(command); await EnrichWithMetadataAsync(command, tags);
var asset = await HandleCoreAsync(context, created, next); var asset = await HandleCoreAsync(context, created, next);
@ -160,7 +160,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
} }
} }
private async Task EnrichWithMetadataAsync(UploadAssetCommand command, HashSet<string>? tags = null) private async Task EnrichWithMetadataAsync(UploadAssetCommand command, HashSet<string>? tags)
{ {
foreach (var metadataSource in assetMetadataSources) foreach (var metadataSource in assetMetadataSources)
{ {

5
frontend/app/framework/angular/forms/progress-bar.component.ts

@ -11,6 +11,11 @@ import * as ProgressBar from 'progressbar.js';
@Component({ @Component({
selector: 'sqx-progress-bar', selector: 'sqx-progress-bar',
styles: [`
:host /deep/ svg {
vertical-align: top
}`
],
template: '', template: '',
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
}) })

4
frontend/app/framework/angular/http/http-extensions.ts

@ -17,7 +17,7 @@ import {
} from '@app/framework/internal'; } from '@app/framework/internal';
export module HTTP { export module HTTP {
export function upload<T = any>(http: HttpClient, method: string, url: string, file: File, version?: Version): Observable<HttpEvent<T>> { export function upload<T = any>(http: HttpClient, method: string, url: string, file: Blob, version?: Version): Observable<HttpEvent<T>> {
const req = new HttpRequest(method, url, getFormData(file), { headers: createHeaders(version), reportProgress: true }); const req = new HttpRequest(method, url, getFormData(file), { headers: createHeaders(version), reportProgress: true });
return http.request<T>(req); return http.request<T>(req);
@ -59,7 +59,7 @@ export module HTTP {
return handleVersion(http.request<T>(method, url, { observe: 'response', headers, body })); return handleVersion(http.request<T>(method, url, { observe: 'response', headers, body }));
} }
function getFormData(file: File) { function getFormData(file: Blob) {
const formData = new FormData(); const formData = new FormData();
formData.append('file', file); formData.append('file', file);

18
frontend/app/shared/components/assets/asset-dialog.component.html

@ -11,7 +11,14 @@
</li> </li>
</ul> </ul>
<button type="submit" class="float-right btn btn-primary" [disabled]="!isEditable">Save</button> <ng-container [ngSwitch]="selectedTab">
<ng-container *ngSwitchCase="'Image'">
<button type="submit" class="float-right btn btn-primary" [class.invisible]="!isUploadable" [disabled]="progress > 0">Save</button>
</ng-container>
<ng-container *ngSwitchCase="'Metadata'">
<button type="submit" class="float-right btn btn-primary" [class.invisible]="!isEditable">Save</button>
</ng-container>
</ng-container>
</ng-container> </ng-container>
<ng-container content> <ng-container content>
@ -19,6 +26,15 @@
<ng-container *ngSwitchCase="'Image'"> <ng-container *ngSwitchCase="'Image'">
<div class="image"> <div class="image">
<sqx-image-editor [imageUrl]="asset.contentUrl"></sqx-image-editor> <sqx-image-editor [imageUrl]="asset.contentUrl"></sqx-image-editor>
<div class="image-progress" *ngIf="progress > 0">
<sqx-progress-bar
[strokeWidth]="2"
[trailColor]="'transparent'"
[trailWidth]="0"
[value]="progress">
</sqx-progress-bar>
</div>
</div> </div>
</ng-container> </ng-container>
<ng-container *ngSwitchCase="'Metadata'"> <ng-container *ngSwitchCase="'Metadata'">

12
frontend/app/shared/components/assets/asset-dialog.component.scss

@ -3,7 +3,17 @@
} }
.image { .image {
@include absolute(0, 0, 0, 0); & {
@include absolute(0, 0, 0, 0);
}
&-progress {
@include absolute(0, 0, null, 0);
}
}
.invisible {
visibility: hidden;
} }
.metadata { .metadata {

101
frontend/app/shared/components/assets/asset-dialog.component.ts

@ -5,15 +5,21 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/ */
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output, QueryList, ViewChildren } from '@angular/core';
import { FormBuilder } from '@angular/forms'; import { FormBuilder } from '@angular/forms';
import { import {
AnnotateAssetForm, AnnotateAssetForm,
AssetDto, AssetDto,
AssetsState AssetsState,
AssetUploaderState,
DialogService,
Types,
UploadCanceled
} from '@app/shared/internal'; } from '@app/shared/internal';
import { ImageEditorComponent } from './image-editor.component';
@Component({ @Component({
selector: 'sqx-asset-dialog', selector: 'sqx-asset-dialog',
styleUrls: ['./asset-dialog.component.scss'], styleUrls: ['./asset-dialog.component.scss'],
@ -24,30 +30,52 @@ export class AssetDialogComponent implements OnInit {
@Output() @Output()
public complete = new EventEmitter(); public complete = new EventEmitter();
@Output()
public changed = new EventEmitter<AssetDto>();
@Input() @Input()
public asset: AssetDto; public asset: AssetDto;
@Input() @Input()
public allTags: ReadonlyArray<string>; public allTags: ReadonlyArray<string>;
@ViewChildren(ImageEditorComponent)
public imageEditor: QueryList<ImageEditorComponent>;
public isEditable = false; public isEditable = false;
public isEditableAny = false;
public isUploadable = false;
public progress = 0;
public selectableTabs: ReadonlyArray<string> = ['Image', 'Metadata']; public selectableTabs: ReadonlyArray<string>;
public selectedTab = this.selectableTabs[0]; public selectedTab: string;
public annotateForm = new AnnotateAssetForm(this.formBuilder); public annotateForm = new AnnotateAssetForm(this.formBuilder);
constructor( constructor(
private readonly assetsState: AssetsState, private readonly assetsState: AssetsState,
private readonly assetUploader: AssetUploaderState,
private readonly changeDetector: ChangeDetectorRef,
private readonly dialogs: DialogService,
private readonly formBuilder: FormBuilder private readonly formBuilder: FormBuilder
) { ) {
} }
public ngOnInit() { public ngOnInit() {
this.isEditable = this.asset.canUpdate; this.isEditable = this.asset.canUpdate;
this.isUploadable = this.asset.canUpload;
this.annotateForm.load(this.asset); this.annotateForm.load(this.asset);
this.annotateForm.setEnabled(this.isEditable); this.annotateForm.setEnabled(this.isEditable);
if (this.asset.type === 'Image') {
this.selectableTabs = ['Image', 'Metadata'];
} else {
this.selectableTabs = ['Metadata'];
}
this.selectTab(this.selectableTabs[0]);
} }
public selectTab(tab: string) { public selectTab(tab: string) {
@ -63,21 +91,58 @@ export class AssetDialogComponent implements OnInit {
} }
public annotateAsset() { public annotateAsset() {
if (!this.isEditable) { if (this.selectedTab === 'Image') {
return; if (!this.isUploadable) {
return;
}
const file = this.imageEditor.first.toFile();
if (file) {
this.setProgress(0);
this.assetUploader.uploadAsset(this.asset, file)
.subscribe(dto => {
if (Types.isNumber(dto)) {
this.setProgress(dto);
} else {
this.changed.emit(dto);
}
this.dialogs.notifyInfo('Asset has been updated.');
}, error => {
if (!Types.is(error, UploadCanceled)) {
this.dialogs.notifyError(error);
}
}, () => {
this.setProgress(0);
});
} else {
this.dialogs.notifyInfo('Nothing has changed.');
}
} else {
if (!this.isEditable) {
return;
}
const value = this.annotateForm.submit(this.asset);
if (value) {
this.assetsState.updateAsset(this.asset, value)
.subscribe(() => {
this.annotateForm.submitCompleted({ noReset: true });
this.dialogs.notifyInfo('Asset has been updated.');
}, error => {
this.annotateForm.submitFailed(error);
});
}
} }
}
const value = this.annotateForm.submit(this.asset); public setProgress(progress: number) {
this.progress = progress;
if (value) {
this.assetsState.updateAsset(this.asset, value) this.changeDetector.markForCheck();
.subscribe(() => {
this.emitComplete();
}, error => {
this.annotateForm.submitFailed(error);
});
} else if (this.annotateForm.form.valid) {
this.emitComplete();
}
} }
} }

1
frontend/app/shared/components/assets/asset.component.html

@ -153,6 +153,7 @@
<ng-container *ngIf="asset"> <ng-container *ngIf="asset">
<ng-container *sqxModal="editDialog"> <ng-container *sqxModal="editDialog">
<sqx-asset-dialog [allTags]="allTags" [asset]="asset" <sqx-asset-dialog [allTags]="allTags" [asset]="asset"
(changed)="setAsset($event)"
(complete)="editDialog.hide()"> (complete)="editDialog.hide()">
</sqx-asset-dialog> </sqx-asset-dialog>
</ng-container> </ng-container>

11
frontend/app/shared/components/assets/asset.component.ts

@ -115,8 +115,7 @@ export class AssetComponent implements OnInit {
this.setProgress(asset); this.setProgress(asset);
} else { } else {
this.setProgress(0); this.setProgress(0);
this.setAsset(asset);
this.asset = asset;
} }
}, error => { }, error => {
this.dialogs.notifyError(error); this.dialogs.notifyError(error);
@ -142,7 +141,13 @@ export class AssetComponent implements OnInit {
this.loadError.emit(error); this.loadError.emit(error);
} }
private setProgress(progress: number) { public setAsset(asset: AssetDto) {
this.asset = asset;
this.changeDetector.markForCheck();
}
public setProgress(progress: number) {
this.progress = progress; this.progress = progress;
this.changeDetector.markForCheck(); this.changeDetector.markForCheck();

33
frontend/app/shared/components/assets/image-editor.component.ts

@ -102,6 +102,8 @@ const blackTheme = {
}) })
export class ImageEditorComponent implements AfterViewInit, OnChanges { export class ImageEditorComponent implements AfterViewInit, OnChanges {
private imageEditor: any; private imageEditor: any;
private isChanged = false;
private isChangedBefore = false;
@Input() @Input()
public imageUrl: string; public imageUrl: string;
@ -120,6 +122,29 @@ export class ImageEditorComponent implements AfterViewInit, OnChanges {
} }
} }
public toFile(): Blob | null {
if (!this.isChanged) {
return null;
}
this.isChanged = false;
const dataURI = this.imageEditor.toDataURL();
const byteString = atob(dataURI.split(',')[1]);
const byteBuffer = new ArrayBuffer(byteString.length);
const type = dataURI.split(',')[0].split(':')[1].split(';')[0];
const array = new Uint8Array(byteBuffer);
for (let i = 0; i < byteString.length; i++) {
array[i] = byteString.charCodeAt(i);
}
return new Blob([array], { type });
}
public ngAfterViewInit() { public ngAfterViewInit() {
const styles = [ const styles = [
'https://uicdn.toast.com/tui-color-picker/latest/tui-color-picker.css', 'https://uicdn.toast.com/tui-color-picker/latest/tui-color-picker.css',
@ -154,6 +179,14 @@ export class ImageEditorComponent implements AfterViewInit, OnChanges {
theme: blackTheme theme: blackTheme
} }
}); });
this.imageEditor.on('undoStackChanged', () => {
if (this.isChangedBefore) {
this.isChanged = true;
} else {
this.isChangedBefore = true;
}
});
}); });
} }
} }

4
frontend/app/shared/services/assets.service.ts

@ -233,7 +233,7 @@ export class AssetsService {
pretifyError('Failed to load assets. Please reload.')); pretifyError('Failed to load assets. Please reload.'));
} }
public postAssetFile(appName: string, file: File, parentId?: string): Observable<number | AssetDto> { public postAssetFile(appName: string, file: Blob, parentId?: string): Observable<number | AssetDto> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/assets?parentId=${parentId}`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/assets?parentId=${parentId}`);
return HTTP.upload(this.http, 'POST', url, file).pipe( return HTTP.upload(this.http, 'POST', url, file).pipe(
@ -266,7 +266,7 @@ export class AssetsService {
pretifyError('Failed to upload asset. Please reload.')); pretifyError('Failed to upload asset. Please reload.'));
} }
public putAssetFile(appName: string, resource: Resource, file: File, version: Version): Observable<number | AssetDto> { public putAssetFile(appName: string, resource: Resource, file: Blob, version: Version): Observable<number | AssetDto> {
const link = resource._links['upload']; const link = resource._links['upload'];
const url = this.apiUrl.buildUrl(link.href); const url = this.apiUrl.buildUrl(link.href);

12
frontend/app/shared/state/asset-uploader.state.ts

@ -75,9 +75,9 @@ export class AssetUploaderState extends State<Snapshot> {
const stream = this.assetsService.postAssetFile(this.appName, file, parentId); const stream = this.assetsService.postAssetFile(this.appName, file, parentId);
return this.upload(stream, MathHelper.guid(), file, asset => { return this.upload(stream, MathHelper.guid(), file.name, asset => {
if (asset.isDuplicate) { if (asset.isDuplicate) {
this.dialogs.notifyError('Asset has already been uploaded.'); this.dialogs.notifyInfo('Asset has already been uploaded.');
} else if (target) { } else if (target) {
target.addAsset(asset); target.addAsset(asset);
} }
@ -86,14 +86,14 @@ export class AssetUploaderState extends State<Snapshot> {
}); });
} }
public uploadAsset(asset: AssetDto, file: File): Observable<UploadResult> { public uploadAsset(asset: AssetDto, file: Blob): Observable<UploadResult> {
const stream = this.assetsService.putAssetFile(this.appName, asset, file, asset.version); const stream = this.assetsService.putAssetFile(this.appName, asset, file, asset.version);
return this.upload(stream, asset.id, file); return this.upload(stream, asset.id, file['name'] || asset.fileName);
} }
private upload(source: Observable<number | AssetDto>, id: string, file: File, complete?: ((completion: AssetDto) => AssetDto)) { private upload(source: Observable<number | AssetDto>, id: string, name: string, complete?: ((completion: AssetDto) => AssetDto)) {
let upload = { id, name: file.name, progress: 1, status: 'Running', cancel: new Subject() }; let upload = { id, name, progress: 1, status: 'Running', cancel: new Subject() };
this.addUpload(upload); this.addUpload(upload);

6
frontend/app/shared/state/assets.forms.ts

@ -42,11 +42,7 @@ export class AnnotateAssetForm extends Form<FormGroup, AnnotateAssetDto, AssetDt
Validators.required Validators.required
] ]
], ],
tags: [[], tags: [],
[
Validators.required
]
],
metadata: formBuilder.array([]) metadata: formBuilder.array([])
})); }));
} }

Loading…
Cancel
Save