mirror of https://github.com/Squidex/squidex.git
18 changed files with 381 additions and 45 deletions
@ -0,0 +1,40 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Threading.Tasks; |
|||
using Squidex.Domain.Apps.Entities.History; |
|||
using Squidex.Domain.Apps.Events.Assets; |
|||
using Squidex.Infrastructure.EventSourcing; |
|||
using Squidex.Infrastructure.Reflection; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Assets |
|||
{ |
|||
public sealed class AssetHistoryEventsCreator : HistoryEventsCreatorBase |
|||
{ |
|||
public AssetHistoryEventsCreator(TypeNameRegistry typeNameRegistry) |
|||
: base(typeNameRegistry) |
|||
{ |
|||
AddEventMessage<AssetCreated>( |
|||
"uploaded asset."); |
|||
|
|||
AddEventMessage<AssetUpdated>( |
|||
"replaced asset."); |
|||
|
|||
AddEventMessage<AssetAnnotated>( |
|||
"updated asset."); |
|||
} |
|||
|
|||
protected override Task<HistoryEvent?> CreateEventCoreAsync(Envelope<IEvent> @event) |
|||
{ |
|||
var channel = $"assets.{@event.Headers.AggregateId()}"; |
|||
|
|||
var result = ForEvent(@event.Payload, channel); |
|||
|
|||
return Task.FromResult<HistoryEvent?>(result); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,49 @@ |
|||
<!DOCTYPE html> |
|||
<html> |
|||
|
|||
<head> |
|||
<meta charset="utf-8"> |
|||
|
|||
<!-- Load the editor sdk from the local folder or https://cloud.squidex.io/scripts/editor-sdk.js --> |
|||
<script src="editor-sdk.js"></script> |
|||
</head> |
|||
|
|||
<body> |
|||
<button id="button">New Value</button> |
|||
|
|||
<script> |
|||
var numberGenerator = 1; |
|||
|
|||
var button = document.getElementById('button'); |
|||
|
|||
var field = new SquidexFormField(); |
|||
|
|||
function logState(message) { |
|||
console.log(`${message}. Value: <${JSON.stringify(field.getValue(), 2)}>, Form Value: <${JSON.stringify(field.getFormValue())}>`); |
|||
} |
|||
|
|||
logState('Init'); |
|||
|
|||
if (button) { |
|||
button.addEventListener('click', function () { |
|||
numberGenerator++; |
|||
|
|||
field.valueChanged(numberGenerator); |
|||
|
|||
logState('Click'); |
|||
}); |
|||
} |
|||
|
|||
// Handle the value change event and set the text to the editor. |
|||
field.onValueChanged(function (value) { |
|||
logState(`Value changed: <${JSON.stringify(value, 2)}>`); |
|||
}); |
|||
|
|||
// Disable the editor when it should be disabled. |
|||
field.onDisabled(function (disabled) { |
|||
logState(`Disabled: <${JSON.stringify(disabled, 2)}>`); |
|||
}); |
|||
</script> |
|||
</body> |
|||
|
|||
</html> |
|||
@ -0,0 +1,76 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
|||
*/ |
|||
|
|||
import { parseError } from './http-extensions'; |
|||
|
|||
import { ErrorDto } from './../../utils/error'; |
|||
|
|||
describe('ErrorParsing', () => { |
|||
it('should return default when error is javascript exception', () => { |
|||
const response: any = new Error(); |
|||
const result = parseError(response, 'Fallback'); |
|||
|
|||
expect(result).toEqual(new ErrorDto(500, 'Fallback', [], response)); |
|||
}); |
|||
|
|||
it('should just forward error dto', () => { |
|||
const response: any = new ErrorDto(500, 'error', []); |
|||
const result = parseError(response, 'Fallback'); |
|||
|
|||
expect(result).toBe(response); |
|||
}); |
|||
|
|||
it('should return default 412 error', () => { |
|||
const response: any = { status: 412 }; |
|||
const result = parseError(response, 'Fallback'); |
|||
|
|||
expect(result).toEqual(new ErrorDto(412, 'Failed to make the update. Another user has made a change. Please reload.', [], response)); |
|||
}); |
|||
|
|||
it('should return default 429 error', () => { |
|||
const response: any = { status: 429 }; |
|||
const result = parseError(response, 'Fallback'); |
|||
|
|||
expect(result).toEqual(new ErrorDto(429, 'You have exceeded the maximum limit of API calls.', [], response)); |
|||
}); |
|||
|
|||
it('should return error from error object', () => { |
|||
const error = { message: 'My-Message', details: ['My-Detail'] }; |
|||
|
|||
const response: any = { status: 400, error }; |
|||
const result = parseError(response, 'Fallback'); |
|||
|
|||
expect(result).toEqual(new ErrorDto(400, 'My-Message', ['My-Detail'], response)); |
|||
}); |
|||
|
|||
it('should return error from error json', () => { |
|||
const error = { message: 'My-Message', details: ['My-Detail'] }; |
|||
|
|||
const response: any = { status: 400, error: JSON.stringify(error) }; |
|||
const result = parseError(response, 'Fallback'); |
|||
|
|||
expect(result).toEqual(new ErrorDto(400, 'My-Message', ['My-Detail'], response)); |
|||
}); |
|||
|
|||
it('should return default when object is invalid', () => { |
|||
const error = { text: 'invalid' }; |
|||
|
|||
const response: any = { status: 400, error }; |
|||
const result = parseError(response, 'Fallback'); |
|||
|
|||
expect(result).toEqual(new ErrorDto(500, 'Fallback', [], response)); |
|||
}); |
|||
|
|||
it('should return default when json is invalid', () => { |
|||
const error = '{{'; |
|||
|
|||
const response: any = { status: 400, error }; |
|||
const result = parseError(response, 'Fallback'); |
|||
|
|||
expect(result).toEqual(new ErrorDto(500, 'Fallback', [], response)); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,19 @@ |
|||
<div class="events"> |
|||
<div class="event row no-gutters" *ngFor="let assetEvent of assetEvents | async; trackBy: trackByEvent"> |
|||
<div class="col-auto"> |
|||
<img class="user-picture" title="{{assetEvent.event.actor | sqxUserNameRef}}" [src]="assetEvent.event.actor | sqxUserPictureRef" /> |
|||
</div> |
|||
<div class="col pl-2"> |
|||
<div class="event-message"> |
|||
<span class="event-actor user-ref mr-1">{{assetEvent.event.actor | sqxUserNameRef:null}}</span> |
|||
<span [innerHTML]="assetEvent.event | sqxHistoryMessage"></span> |
|||
</div> |
|||
|
|||
<div class="event-created">{{assetEvent.event.created | sqxFromNow}}</div> |
|||
|
|||
<ng-container *ngIf="assetEvent.canDownload"> |
|||
<a class="event-load force" [href]="asset | sqxAssetUrl:assetEvent.fileVersion" sqxExternalLink="noicon">Download this Version</a> |
|||
</ng-container> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
@ -0,0 +1,33 @@ |
|||
:host ::ng-deep { |
|||
.user-ref { |
|||
font-weight: 500; |
|||
} |
|||
|
|||
.marker-ref { |
|||
font-weight: 500; |
|||
} |
|||
} |
|||
|
|||
.events { |
|||
margin-left: auto; |
|||
margin-right: auto; |
|||
max-width: 400px; |
|||
} |
|||
|
|||
.event { |
|||
& { |
|||
font-size: .9rem; |
|||
font-weight: normal; |
|||
margin-bottom: 1.5rem; |
|||
} |
|||
|
|||
&-created { |
|||
font-size: .75rem; |
|||
font-weight: normal; |
|||
margin: .25rem 0; |
|||
} |
|||
} |
|||
|
|||
.user-picture { |
|||
margin-top: .25rem; |
|||
} |
|||
@ -0,0 +1,63 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
|||
*/ |
|||
|
|||
import { Component, Input, OnChanges } from '@angular/core'; |
|||
import { Observable } from 'rxjs'; |
|||
import { map } from 'rxjs/operators'; |
|||
|
|||
import { |
|||
AppsState, |
|||
AssetDto, |
|||
HistoryEventDto, |
|||
HistoryService |
|||
} from '@app/shared/internal'; |
|||
|
|||
interface AssetEvent { event: HistoryEventDto; fileVersion: number; canDownload: boolean; } |
|||
|
|||
@Component({ |
|||
selector: 'sqx-asset-history', |
|||
styleUrls: ['./asset-history.component.scss'], |
|||
templateUrl: './asset-history.component.html' |
|||
}) |
|||
export class AssetHistoryComponent implements OnChanges { |
|||
@Input() |
|||
public asset: AssetDto; |
|||
|
|||
public assetEvents: Observable<ReadonlyArray<AssetEvent>>; |
|||
|
|||
constructor( |
|||
private readonly appsState: AppsState, |
|||
private readonly historyService: HistoryService |
|||
) { |
|||
} |
|||
|
|||
public ngOnChanges() { |
|||
const channel = `assets.${this.asset.id}`; |
|||
|
|||
this.assetEvents = |
|||
this.historyService.getHistory(this.appsState.appName, channel).pipe( |
|||
map(events => { |
|||
let fileVersion = -1; |
|||
|
|||
return events.map(event => { |
|||
const canDownload = |
|||
event.eventType === 'AssetUpdatedEventV2' || |
|||
event.eventType === 'AssetCreatedEventV2'; |
|||
|
|||
if (canDownload) { |
|||
fileVersion++; |
|||
} |
|||
|
|||
return { event, fileVersion, canDownload }; |
|||
}); |
|||
})); |
|||
} |
|||
|
|||
public trackByEvent(index: number, assetEvent: AssetEvent) { |
|||
return assetEvent.event.eventId; |
|||
} |
|||
} |
|||
Loading…
Reference in new issue