Browse Source

Merge branch 'master' into feature-restore

pull/311/head
Sebastian 8 years ago
parent
commit
196ef2411f
  1. 4
      src/Squidex.Domain.Apps.Entities/Apps/AppHistoryEventsCreator.cs
  2. 2
      src/Squidex.Domain.Apps.Events/Apps/AppPatternDeleted.cs
  3. 4
      src/Squidex/app/features/administration/pages/event-consumers/event-consumers-page.component.html
  4. 9
      src/Squidex/app/framework/angular/forms/autocomplete.component.html
  5. 38
      src/Squidex/app/framework/angular/forms/autocomplete.component.ts
  6. 46
      src/Squidex/app/framework/angular/forms/tag-editor.component.html
  7. 7
      src/Squidex/app/framework/angular/forms/tag-editor.component.scss
  8. 132
      src/Squidex/app/framework/angular/forms/tag-editor.component.ts
  9. 6
      src/Squidex/app/framework/angular/http/http-extensions.ts
  10. 8
      src/Squidex/app/shared/components/asset.component.html
  11. 3
      src/Squidex/app/shared/components/asset.component.ts
  12. 3
      src/Squidex/app/shared/components/assets-list.component.html
  13. 4
      src/Squidex/app/shared/state/assets.state.ts
  14. 13
      src/Squidex/app/shared/state/contributors.state.ts
  15. 1
      src/Squidex/package.json

4
src/Squidex.Domain.Apps.Entities/Apps/AppHistoryEventsCreator.cs

@ -53,7 +53,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
"added pattern {[Name]}"); "added pattern {[Name]}");
AddEventMessage<AppPatternDeleted>( AddEventMessage<AppPatternDeleted>(
"deleted pattern {[Name]}"); "deleted pattern {[PatternId]}");
AddEventMessage<AppPatternUpdated>( AddEventMessage<AppPatternUpdated>(
"updated pattern {[Name]}"); "updated pattern {[Name]}");
@ -164,7 +164,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
return Task.FromResult( return Task.FromResult(
ForEvent(@event, channel) ForEvent(@event, channel)
.AddParameter("Name", @event.Name)); .AddParameter("PatternId", @event.PatternId));
} }
protected override Task<HistoryEventToStore> CreateEventCoreAsync(Envelope<IEvent> @event) protected override Task<HistoryEventToStore> CreateEventCoreAsync(Envelope<IEvent> @event)

2
src/Squidex.Domain.Apps.Events/Apps/AppPatternDeleted.cs

@ -14,7 +14,5 @@ namespace Squidex.Domain.Apps.Events.Apps
public sealed class AppPatternDeleted : AppEvent public sealed class AppPatternDeleted : AppEvent
{ {
public Guid PatternId { get; set; } public Guid PatternId { get; set; }
public string Name { get; set; }
} }
} }

4
src/Squidex/app/features/administration/pages/event-consumers/event-consumers-page.component.html

@ -60,11 +60,11 @@
</sqx-panel> </sqx-panel>
<sqx-modal-dialog *sqxModalView="eventConsumerErrorDialog;onRoot:true" (closed)="eventConsumerErrorDialog.hide()"> <sqx-modal-dialog *sqxModalView="eventConsumerErrorDialog;onRoot:true" (closed)="eventConsumerErrorDialog.hide()">
<ng-container #title> <ng-container title>
Error Error
</ng-container> </ng-container>
<ng-container #content> <ng-container content>
<textarea readonly class="form-control error-message">{{eventConsumerError}}</textarea> <textarea readonly class="form-control error-message">{{eventConsumerError}}</textarea>
</ng-container> </ng-container>
</sqx-modal-dialog> </sqx-modal-dialog>

9
src/Squidex/app/framework/angular/forms/autocomplete.component.html

@ -5,8 +5,13 @@
autocorrect="off" autocorrect="off"
autocapitalize="off"> autocapitalize="off">
<div *ngIf="items.length > 0" [sqxModalTarget]="input" class="control-dropdown" #container position="bottomLeft"> <div *ngIf="suggestedItems.length > 0" [sqxModalTarget]="input" class="control-dropdown" #container position="bottomLeft">
<div *ngFor="let item of items; let i = index;" class="control-dropdown-item control-dropdown-item-selectable" [class.active]="i === selectedIndex" (mousedown)="selectItem(item)" (mouseover)="selectIndex(i)" [sqxScrollActive]="i === selectedIndex" [container]="container"> <div *ngFor="let item of suggestedItems; let i = index;" class="control-dropdown-item control-dropdown-item-selectable"
[class.active]="i === suggestedIndex"
[container]="container"
(mousedown)="selectItem(item)"
(mouseover)="selectIndex(i)"
[sqxScrollActive]="i === suggestedIndex">
<ng-container *ngIf="!itemTemplate">{{item}}</ng-container> <ng-container *ngIf="!itemTemplate">{{item}}</ng-container>
<ng-template *ngIf="itemTemplate" [sqxTemplateWrapper]="itemTemplate" [item]="item" [index]="i"></ng-template> <ng-template *ngIf="itemTemplate" [sqxTemplateWrapper]="itemTemplate" [item]="item" [index]="i"></ng-template>

38
src/Squidex/app/framework/angular/forms/autocomplete.component.ts

@ -49,9 +49,8 @@ export class AutocompleteComponent implements ControlValueAccessor, OnDestroy, O
@ContentChild(TemplateRef) @ContentChild(TemplateRef)
public itemTemplate: TemplateRef<any>; public itemTemplate: TemplateRef<any>;
public items: any[] = []; public suggestedItems: any[] = [];
public suggestedIndex = -1;
public selectedIndex = -1;
public queryInput = new FormControl(); public queryInput = new FormControl();
@ -72,14 +71,13 @@ export class AutocompleteComponent implements ControlValueAccessor, OnDestroy, O
this.reset(); this.reset();
} }
}), }),
distinctUntilChanged(),
debounceTime(200), debounceTime(200),
distinctUntilChanged(),
filter(query => !!query && !!this.source), filter(query => !!query && !!this.source),
switchMap(query => this.source.find(query)), switchMap(query => this.source.find(query)), catchError(() => of([])))
catchError(error => of([])))
.subscribe(items => { .subscribe(items => {
this.reset(); this.suggestedIndex = -1;
this.items = items || []; this.suggestedItems = items || [];
}); });
} }
@ -96,7 +94,7 @@ export class AutocompleteComponent implements ControlValueAccessor, OnDestroy, O
this.reset(); this.reset();
return false; return false;
case KEY_ENTER: case KEY_ENTER:
if (this.items.length > 0) { if (this.suggestedItems.length > 0) {
this.selectItem(); this.selectItem();
return false; return false;
} }
@ -110,7 +108,7 @@ export class AutocompleteComponent implements ControlValueAccessor, OnDestroy, O
if (!obj) { if (!obj) {
this.resetForm(); this.resetForm();
} else { } else {
const item = this.items.find(i => i === obj); const item = this.suggestedItems.find(i => i === obj);
if (item) { if (item) {
this.queryInput.setValue(obj.title || ''); this.queryInput.setValue(obj.title || '');
@ -144,11 +142,11 @@ export class AutocompleteComponent implements ControlValueAccessor, OnDestroy, O
public selectItem(selection: any | null = null) { public selectItem(selection: any | null = null) {
if (!selection) { if (!selection) {
selection = this.items[this.selectedIndex]; selection = this.suggestedItems[this.suggestedIndex];
} }
if (!selection && this.items.length === 1) { if (!selection && this.suggestedItems.length === 1) {
selection = this.items[0]; selection = this.suggestedItems[0];
} }
if (selection) { if (selection) {
@ -170,19 +168,19 @@ export class AutocompleteComponent implements ControlValueAccessor, OnDestroy, O
selection = 0; selection = 0;
} }
if (selection >= this.items.length) { if (selection >= this.suggestedItems.length) {
selection = this.items.length - 1; selection = this.suggestedItems.length - 1;
} }
this.selectedIndex = selection; this.suggestedIndex = selection;
} }
private up() { private up() {
this.selectIndex(this.selectedIndex - 1); this.selectIndex(this.suggestedIndex - 1);
} }
private down() { private down() {
this.selectIndex(this.selectedIndex + 1); this.selectIndex(this.suggestedIndex + 1);
} }
private resetForm() { private resetForm() {
@ -190,7 +188,7 @@ export class AutocompleteComponent implements ControlValueAccessor, OnDestroy, O
} }
private reset() { private reset() {
this.items = []; this.suggestedItems = [];
this.selectedIndex = -1; this.suggestedIndex = -1;
} }
} }

46
src/Squidex/app/framework/angular/forms/tag-editor.component.html

@ -1,18 +1,30 @@
<div class="form-control {{class}}" (click)="input.focus()" [class.focus]="hasFocus" [class.disabled]="addInput.disabled"> <ng-container>
<span class="item" *ngFor="let item of items; let i = index" [class.disabled]="addInput.disabled"> <div class="form-control {{class}}" #form (click)="input.focus()" [class.focus]="hasFocus" [class.disabled]="addInput.disabled">
{{item}} <i class="icon-close" (click)="remove(i)"></i> <span class="item" *ngFor="let item of items; let i = index" [class.disabled]="addInput.disabled">
</span> {{item}} <i class="icon-close" (click)="remove(i)"></i>
</span>
<input type="text" class="blank" #input <input type="text" class="blank" #input
(blur)="markTouched()" (blur)="markTouched()"
(focus)="focus()" (focus)="focus()"
(input)="adjustSize()" (keydown)="onKeyDown($event)"
(keydown)="onKeyDown($event)" [formControl]="addInput"
[formControl]="addInput" [attr.name]="inputName"
[attr.name]="inputName" [attr.placeholder]="placeholder"
[attr.placeholder]="placeholder" [class.hidden]="addInput.disabled"
[class.hidden]="addInput.disabled" autocomplete="off"
autocomplete="off" autocorrect="off"
autocorrect="off" autocapitalize="off">
autocapitalize="off"> </div>
</div>
<div *ngIf="suggestedItems.length > 0" [sqxModalTarget]="form" class="control-dropdown" #container position="bottomLeft">
<div *ngFor="let item of suggestedItems; let i = index;" class="control-dropdown-item control-dropdown-item-selectable"
[class.active]="i === suggestedIndex"
[container]="container"
(mousedown)="selectValue(item)"
(mouseover)="selectIndex(i)"
[sqxScrollActive]="i === suggestedIndex">
<ng-container>{{item}}</ng-container>
</div>
</div>
</ng-container>

7
src/Squidex/app/framework/angular/forms/tag-editor.component.scss

@ -43,19 +43,14 @@
.item { .item {
& { & {
@include border-radius(10px); @include border-radius(10px);
@include truncate;
display: inline-block;
color: $color-dark-foreground; color: $color-dark-foreground;
cursor: default; cursor: default;
height: 20px;
padding: 0 .6rem; padding: 0 .6rem;
background: $color-theme-blue; background: $color-theme-blue;
border: 0; border: 0;
font-size: .8rem; font-size: .8rem;
font-weight: normal; font-weight: normal;
line-height: 20px; margin: 0px 2px 2px 0;
margin: 2px 2px 2px 0;
vertical-align: middle;
} }
&, &,

132
src/Squidex/app/framework/angular/forms/tag-editor.component.ts

@ -5,14 +5,18 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/ */
import { Component, ElementRef, forwardRef, Input, ViewChild } from '@angular/core'; import { Component, ElementRef, forwardRef, Input, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR } from '@angular/forms'; import { ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR } from '@angular/forms';
import { Subscription } from 'rxjs';
import { distinctUntilChanged, map, tap } from 'rxjs/operators';
import { Types } from '@app/framework/internal'; import { Types } from '@app/framework/internal';
const KEY_COMMA = 188; const KEY_COMMA = 188;
const KEY_DELETE = 8; const KEY_DELETE = 8;
const KEY_ENTER = 13; const KEY_ENTER = 13;
const KEY_UP = 38;
const KEY_DOWN = 40;
export interface Converter { export interface Converter {
convert(input: string): any; convert(input: string): any;
@ -73,7 +77,8 @@ export const SQX_TAG_EDITOR_CONTROL_VALUE_ACCESSOR: any = {
templateUrl: './tag-editor.component.html', templateUrl: './tag-editor.component.html',
providers: [SQX_TAG_EDITOR_CONTROL_VALUE_ACCESSOR] providers: [SQX_TAG_EDITOR_CONTROL_VALUE_ACCESSOR]
}) })
export class TagEditorComponent implements ControlValueAccessor { export class TagEditorComponent implements ControlValueAccessor, OnDestroy, OnInit {
private subscription: Subscription;
private callChange = (v: any) => { /* NOOP */ }; private callChange = (v: any) => { /* NOOP */ };
private callTouched = () => { /* NOOP */ }; private callTouched = () => { /* NOOP */ };
@ -81,11 +86,17 @@ export class TagEditorComponent implements ControlValueAccessor {
public converter: Converter = new StringConverter(); public converter: Converter = new StringConverter();
@Input() @Input()
public useDefaultValue = true; public undefinedWhenEmpty = true;
@Input() @Input()
public acceptEnter = false; public acceptEnter = false;
@Input()
public allowDuplicates = true;
@Input()
public suggestions: string[] = [];
@Input() @Input()
public class: string; public class: string;
@ -100,10 +111,44 @@ export class TagEditorComponent implements ControlValueAccessor {
public hasFocus = false; public hasFocus = false;
public suggestedItems: string[] = [];
public suggestedIndex = 0;
public items: any[] = []; public items: any[] = [];
public addInput = new FormControl(); public addInput = new FormControl();
public ngOnDestroy() {
this.subscription.unsubscribe();
}
public ngOnInit() {
this.subscription =
this.addInput.valueChanges.pipe(
tap(() => {
this.adjustSize();
}),
map(query => <string>query),
map(query => query ? query.trim() : query),
tap(query => {
if (!query) {
this.resetAutocompletion();
}
}),
distinctUntilChanged(),
map(query => {
if (Types.isArray(this.suggestions) && query && query.length > 0) {
return this.suggestions.filter(s => s.indexOf(query) >= 0 && this.items.indexOf(s) < 0);
} else {
return [];
}
}))
.subscribe(items => {
this.suggestedIndex = -1;
this.suggestedItems = items || [];
});
}
public writeValue(obj: any) { public writeValue(obj: any) {
this.resetForm(); this.resetForm();
@ -136,12 +181,6 @@ export class TagEditorComponent implements ControlValueAccessor {
} }
} }
private resetForm() {
this.addInput.reset();
this.adjustSize();
}
public markTouched() { public markTouched() {
this.callTouched(); this.callTouched();
@ -171,17 +210,13 @@ export class TagEditorComponent implements ControlValueAccessor {
} }
public onKeyDown(event: KeyboardEvent) { public onKeyDown(event: KeyboardEvent) {
if (event.keyCode === KEY_COMMA || (event.keyCode === KEY_ENTER && this.acceptEnter)) { const key = event.keyCode;
const value = <string>this.addInput.value;
if (value && this.converter.isValidInput(value)) {
const converted = this.converter.convert(value);
this.updateItems([...this.items, converted]); if (key === KEY_COMMA) {
this.resetForm(); if (this.selectValue(this.addInput.value)) {
return false; return false;
} }
} else if (event.keyCode === KEY_DELETE) { } else if (key === KEY_DELETE) {
const value = <string>this.addInput.value; const value = <string>this.addInput.value;
if (!value || value.length === 0) { if (!value || value.length === 0) {
@ -189,15 +224,74 @@ export class TagEditorComponent implements ControlValueAccessor {
return false; return false;
} }
} else if (key === KEY_UP) {
this.up();
return false;
} else if (key === KEY_DOWN) {
this.down();
return false;
} else if (key === KEY_ENTER) {
if (this.suggestedIndex >= 0) {
if (this.selectValue(this.suggestedItems[this.suggestedIndex])) {
return false;
}
} else if (this.acceptEnter) {
if (this.selectValue(this.addInput.value)) {
return false;
}
}
} }
return true; return true;
} }
private updateItems(items: string[]) { public selectValue(value: string) {
if (value && this.converter.isValidInput(value)) {
const converted = this.converter.convert(value);
if (this.allowDuplicates || this.items.indexOf(converted) < 0) {
this.updateItems([...this.items, converted]);
}
this.resetForm();
this.resetAutocompletion();
return true;
}
}
private resetAutocompletion() {
this.suggestedItems = [];
this.suggestedIndex = -1;
}
public selectIndex(selection: number) {
if (selection < 0) {
selection = 0;
}
if (selection >= this.items.length) {
selection = this.items.length - 1;
}
this.suggestedIndex = selection;
}
private resetForm() {
this.addInput.reset();
}
private up() {
this.selectIndex(this.suggestedIndex - 1);
}
private down() {
this.selectIndex(this.suggestedIndex + 1);
}
private updateItems(items: any[]) {
this.items = items; this.items = items;
if (items.length === 0 && this.useDefaultValue) { if (items.length === 0 && this.undefinedWhenEmpty) {
this.callChange(undefined); this.callChange(undefined);
} else { } else {
this.callChange(this.items); this.callChange(this.items);

6
src/Squidex/app/framework/angular/http/http-extensions.ts

@ -67,7 +67,11 @@ export const pretifyError = (message: string) => <T>(source: Observable<T>) =>
if (!Types.is(response.error, Error)) { if (!Types.is(response.error, Error)) {
try { try {
const errorDto = Types.isObject(response.error) ? response.error : JSON.parse(response.error); let errorDto = Types.isObject(response.error) ? response.error : JSON.parse(response.error);
if (!errorDto) {
errorDto = { message: 'Failed to make the request.', details: [] };
}
if (response.status === 412) { if (response.status === 412) {
result = new ErrorDto(response.status, 'Failed to make the update. Another user has made a change. Please reload.'); result = new ErrorDto(response.status, 'Failed to make the update. Another user has made a change. Please reload.');

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

@ -68,7 +68,13 @@
</div> </div>
</div> </div>
<div class="file-tags tags"> <div class="file-tags tags">
<sqx-tag-editor [acceptEnter]="true" [useDefaultValue]="false" [formControl]="tagInput" class="blank"></sqx-tag-editor> <sqx-tag-editor
[suggestions]="allTags"
[acceptEnter]="true"
[allowDuplicates]="false"
[undefinedWhenEmpty]="false"
[formControl]="tagInput" class="blank">
</sqx-tag-editor>
</div> </div>
<div class="file-info"> <div class="file-info">
<ng-container *ngIf="asset.pixelWidth">{{asset.pixelWidth}}x{{asset.pixelHeight}}px, </ng-container> {{asset.fileSize | sqxFileSize}} <ng-container *ngIf="asset.pixelWidth">{{asset.pixelWidth}}x{{asset.pixelHeight}}px, </ng-container> {{asset.fileSize | sqxFileSize}}

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

@ -54,6 +54,9 @@ export class AssetComponent implements OnDestroy, OnInit {
@Input() @Input()
public isSelectable = false; public isSelectable = false;
@Input()
public allTags: string[];
@Output() @Output()
public loaded = new EventEmitter<AssetDto>(); public loaded = new EventEmitter<AssetDto>();

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

@ -14,7 +14,7 @@
<div class="file-drop-info">Drop file on existing item to replace the asset with a newer version.</div> <div class="file-drop-info">Drop file on existing item to replace the asset with a newer version.</div>
</div> </div>
<div class="row assets"> <div class="row assets" *ngIf="state.tagsNames | async; let tags">
<sqx-asset *ngFor="let file of newFiles" [initFile]="file" <sqx-asset *ngFor="let file of newFiles" [initFile]="file"
(failed)="remove(file)" (failed)="remove(file)"
(loaded)="add(file, $event)"> (loaded)="add(file, $event)">
@ -25,6 +25,7 @@
[isDisabled]="isDisabled" [isDisabled]="isDisabled"
[isSelectable]="selectedIds" [isSelectable]="selectedIds"
[isSelected]="isSelected(asset)" [isSelected]="isSelected(asset)"
[allTags]="tags"
(updated)="update($event)" (updated)="update($event)"
(selected)="select($event)" (selected)="select($event)"
(deleting)="delete($event)"> (deleting)="delete($event)">

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

@ -37,6 +37,10 @@ export class AssetsState extends State<Snapshot> {
this.changes.pipe(map(x => x.tags), this.changes.pipe(map(x => x.tags),
distinctUntilChanged(), map(x => sort(x))); distinctUntilChanged(), map(x => sort(x)));
public tagsNames =
this.tags.pipe(
distinctUntilChanged(), map(x => x.map(t => t.name)));
public assets = public assets =
this.changes.pipe(map(x => x.assets), this.changes.pipe(map(x => x.assets),
distinctUntilChanged()); distinctUntilChanged());

13
src/Squidex/app/shared/state/contributors.state.ts

@ -6,14 +6,16 @@
*/ */
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable } from 'rxjs'; import { Observable, throwError } from 'rxjs';
import { distinctUntilChanged, map, tap } from 'rxjs/operators'; import { catchError, distinctUntilChanged, map, tap } from 'rxjs/operators';
import { import {
DialogService, DialogService,
ErrorDto,
ImmutableArray, ImmutableArray,
notify, notify,
State, State,
Types,
Version Version
} from '@app/framework'; } from '@app/framework';
@ -100,6 +102,13 @@ export class ContributorsState extends State<Snapshot> {
this.replaceContributors(contributors, dto.version); this.replaceContributors(contributors, dto.version);
}), }),
catchError(error => {
if (Types.is(error, ErrorDto) && error.statusCode === 404) {
return throwError(new ErrorDto(404, 'The user does not exist.'));
} else {
return throwError(error);
}
}),
notify(this.dialogs)); notify(this.dialogs));
} }

1
src/Squidex/package.json

@ -34,6 +34,7 @@
"moment": "2.22.2", "moment": "2.22.2",
"mousetrap": "1.6.2", "mousetrap": "1.6.2",
"ng2-dnd": "5.0.2", "ng2-dnd": "5.0.2",
"npm": "^6.2.0",
"oidc-client": "1.4.1", "oidc-client": "1.4.1",
"pikaday": "1.7.0", "pikaday": "1.7.0",
"progressbar.js": "1.0.1", "progressbar.js": "1.0.1",

Loading…
Cancel
Save