Browse Source

Feature/references improvements (#803)

* Added input reference editor.

* Load references on demand.

* Fix images.

* Just some code cleanup.
pull/804/head
Sebastian Stehle 4 years ago
committed by GitHub
parent
commit
d3da990380
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/ReferencesFieldEditor.cs
  2. 11
      frontend/app/features/content/shared/forms/field-editor.component.html
  3. 73
      frontend/app/features/schemas/pages/schema/fields/types/string-ui.component.html
  4. 2
      frontend/app/framework/angular/forms/editors/dropdown.component.html
  5. 28
      frontend/app/framework/angular/forms/editors/dropdown.component.ts
  6. 4
      frontend/app/framework/angular/forms/editors/tag-editor.component.html
  7. 27
      frontend/app/framework/angular/forms/editors/tag-editor.component.ts
  8. 8
      frontend/app/framework/utils/array-extensions.spec.ts
  9. 10
      frontend/app/framework/utils/array-extensions.ts
  10. 2
      frontend/app/shared/components/references/reference-dropdown.component.html
  11. 121
      frontend/app/shared/components/references/reference-dropdown.component.ts
  12. 46
      frontend/app/shared/components/references/reference-input.component.ts
  13. 12
      frontend/app/shared/components/references/references-checkboxes.component.ts
  14. 9
      frontend/app/shared/components/references/references-tags.component.html
  15. 110
      frontend/app/shared/components/references/references-tags.component.ts
  16. 3
      frontend/app/shared/services/schemas.types.ts
  17. 14
      frontend/app/theme/icomoon/demo.html
  18. BIN
      frontend/app/theme/icomoon/fonts/icomoon.eot
  19. 2
      frontend/app/theme/icomoon/fonts/icomoon.svg
  20. BIN
      frontend/app/theme/icomoon/fonts/icomoon.ttf
  21. BIN
      frontend/app/theme/icomoon/fonts/icomoon.woff
  22. 2
      frontend/app/theme/icomoon/selection.json
  23. 13
      frontend/app/theme/icomoon/style.css

3
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/ReferencesFieldEditor.cs

@ -12,6 +12,7 @@ namespace Squidex.Domain.Apps.Core.Schemas
List, List,
Dropdown, Dropdown,
Tags, Tags,
Checkboxes Checkboxes,
Input
} }
} }

11
frontend/app/features/content/shared/forms/field-editor.component.html

@ -116,13 +116,24 @@
mode="Array" mode="Array"
[formControl]="$any(fieldForm)" [formControl]="$any(fieldForm)"
[language]="language" [language]="language"
[languages]="languages"
[schemaId]="field.rawProperties.singleId"> [schemaId]="field.rawProperties.singleId">
</sqx-reference-dropdown> </sqx-reference-dropdown>
</ng-container> </ng-container>
<ng-container *ngSwitchCase="'Input'">
<sqx-reference-input
mode="Array"
[formControl]="$any(fieldForm)"
[language]="language"
[languages]="languages"
[schemaIds]="field.rawProperties.schemaIds">
</sqx-reference-input>
</ng-container>
<ng-container *ngSwitchCase="'Tags'"> <ng-container *ngSwitchCase="'Tags'">
<sqx-references-tags <sqx-references-tags
[formControl]="$any(fieldForm)" [formControl]="$any(fieldForm)"
[language]="language" [language]="language"
[languages]="languages"
[schemaId]="field.rawProperties.singleId"> [schemaId]="field.rawProperties.singleId">
</sqx-references-tags> </sqx-references-tags>
</ng-container> </ng-container>

73
frontend/app/features/schemas/pages/schema/fields/types/string-ui.component.html

@ -13,77 +13,14 @@
<div class="form-group row"> <div class="form-group row">
<label class="col-3 col-form-label">{{ 'schemas.field.editor' | sqxTranslate }}</label> <label class="col-3 col-form-label">{{ 'schemas.field.editor' | sqxTranslate }}</label>
<div class="col-9"> <div class="col-9">
<label class="btn btn-radio" [class.active]="fieldForm.controls['editor'].value === 'Input'"> <label class="btn btn-radio" *ngFor="let editor of editors" [class.active]="fieldForm.controls['editor'].value === editor">
<input type="radio" class="radio-input" name="editor" formControlName="editor" value="Input"> <input type="radio" class="radio-input" name="editor" formControlName="editor" [value]="editor">
<i class="icon-control-Input"></i>
<span class="radio-label">Input</span>
</label>
<label class="btn btn-radio" [class.active]="fieldForm.controls['editor'].value === 'Slug'">
<input type="radio" class="radio-input" name="editor" formControlName="editor" value="Slug">
<i class="icon-control-Slug"></i>
<span class="radio-label">Slug</span>
</label>
<label class="btn btn-radio" [class.active]="fieldForm.controls['editor'].value === 'TextArea'">
<input type="radio" class="radio-input" name="editor" formControlName="editor" value="TextArea">
<i class="icon-control-TextArea"></i>
<span class="radio-label">TextArea</span>
</label>
<label class="btn btn-radio" [class.active]="fieldForm.controls['editor'].value === 'RichText'">
<input type="radio" class="radio-input" name="editor" formControlName="editor" value="RichText">
<i class="icon-control-RichText"></i>
<span class="radio-label">RichText</span>
</label>
<label class="btn btn-radio" [class.active]="fieldForm.controls['editor'].value === 'Markdown'">
<input type="radio" class="radio-input" name="editor" formControlName="editor" value="Markdown">
<i class="icon-control-Markdown"></i>
<span class="radio-label">Markdown</span>
</label>
<label class="btn btn-radio" [class.active]="fieldForm.controls['editor'].value === 'Dropdown'">
<input type="radio" class="radio-input" name="editor" formControlName="editor" value="Dropdown">
<i class="icon-control-Dropdown"></i>
<span class="radio-label" clas>Dropdown</span>
</label>
<label class="btn btn-radio" [class.active]="fieldForm.controls['editor'].value === 'Radio'">
<input type="radio" class="radio-input" name="editor" formControlName="editor" value="Radio">
<i class="icon-control-Radio"></i>
<span class="radio-label">Radio</span>
</label>
<label class="btn btn-radio" [class.active]="fieldForm.controls['editor'].value === 'Color'">
<input type="radio" class="radio-input" name="editor" formControlName="editor" value="Color">
<i class="icon-control-Color"></i>
<span class="radio-label">Color</span>
</label>
<label class="btn btn-radio" [class.active]="fieldForm.controls['editor'].value === 'Html'">
<input type="radio" class="radio-input" name="editor" formControlName="editor" value="Html">
<i class="icon-control-Html"></i>
<span class="radio-label">HTML</span>
</label>
<label class="btn btn-radio" [class.active]="fieldForm.controls['editor'].value === 'StockPhoto'">
<input type="radio" class="radio-input" name="editor" formControlName="editor" value="StockPhoto">
<i class="icon-media"></i> <i class="icon-control-{{editor}}"></i>
<span class="radio-label">StockPhoto</span> <span class="radio-label">{{editor}}</span>
</label> </label>
</div> </div>
</div> </div>

2
frontend/app/framework/angular/forms/editors/dropdown.component.html

@ -1,5 +1,5 @@
<div class="selection"> <div class="selection">
<input type="text" class="form-select" [disabled]="snapshot.isDisabled" (click)="open()" readonly (keydown)="onKeyDown($event)" #input <input type="text" class="form-select" [disabled]="snapshot.isDisabled" (click)="openModal()" readonly (keydown)="onKeyDown($event)" #input
autocomplete="off" autocomplete="off"
autocorrect="off" autocorrect="off"
autocapitalize="off"> autocapitalize="off">

28
frontend/app/framework/angular/forms/editors/dropdown.component.ts

@ -5,7 +5,7 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/ */
import { AfterContentInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChildren, forwardRef, Input, OnChanges, OnInit, QueryList, SimpleChanges, TemplateRef } from '@angular/core'; import { AfterContentInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChildren, EventEmitter, forwardRef, Input, OnChanges, OnInit, Output, QueryList, SimpleChanges, TemplateRef } from '@angular/core';
import { FormControl, NG_VALUE_ACCESSOR } from '@angular/forms'; import { FormControl, NG_VALUE_ACCESSOR } from '@angular/forms';
import { Keys, ModalModel, StatefulControlComponent, Types } from '@app/framework/internal'; import { Keys, ModalModel, StatefulControlComponent, Types } from '@app/framework/internal';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
@ -40,6 +40,12 @@ interface State {
export class DropdownComponent extends StatefulControlComponent<State, ReadonlyArray<any>> implements AfterContentInit, OnChanges, OnInit { export class DropdownComponent extends StatefulControlComponent<State, ReadonlyArray<any>> implements AfterContentInit, OnChanges, OnInit {
private value: any; private value: any;
@Output()
public open = new EventEmitter();
@Output()
public close = new EventEmitter();
@Input() @Input()
public items: ReadonlyArray<any> | undefined | null = []; public items: ReadonlyArray<any> | undefined | null = [];
@ -147,7 +153,7 @@ export class DropdownComponent extends StatefulControlComponent<State, ReadonlyA
public onKeyDown(event: KeyboardEvent) { public onKeyDown(event: KeyboardEvent) {
if (Keys.isEscape(event) && this.dropdown.isOpen) { if (Keys.isEscape(event) && this.dropdown.isOpen) {
this.close(); this.closeModal();
return false; return false;
} else if (Keys.isUp(event)) { } else if (Keys.isUp(event)) {
this.selectPrevIndex(); this.selectPrevIndex();
@ -163,12 +169,16 @@ export class DropdownComponent extends StatefulControlComponent<State, ReadonlyA
return true; return true;
} }
public open() { public openModal() {
if (!this.dropdown.isOpen) { if (!this.dropdown.isOpen) {
this.selectSearch(''); this.selectSearch('');
} }
this.dropdown.show(); if (!this.dropdown.isOpen) {
this.open.emit();
this.dropdown.show();
}
this.callTouched(); this.callTouched();
} }
@ -176,11 +186,15 @@ export class DropdownComponent extends StatefulControlComponent<State, ReadonlyA
public selectIndexAndClose(selectedIndex: number) { public selectIndexAndClose(selectedIndex: number) {
this.selectIndex(selectedIndex, true); this.selectIndex(selectedIndex, true);
this.close(); this.closeModal();
} }
private close() { public closeModal() {
this.dropdown.hide(); if (this.dropdown.isOpen) {
this.close.emit();
this.dropdown.hide();
}
} }
private selectSearch(value: string) { private selectSearch(value: string) {

4
frontend/app/framework/angular/forms/editors/tag-editor.component.html

@ -24,7 +24,7 @@
[formControl]="addInput"> [formControl]="addInput">
</div> </div>
<div class="btn btn-sm" (click)="suggestionsModal.show()" sqxStopClick *ngIf="suggestionsSorted.length > 0"> <div class="btn btn-sm" (click)="openModal()" sqxStopClick *ngIf="allowOpen || suggestionsSorted.length > 0">
<i class="icon-caret-down"></i> <i class="icon-caret-down"></i>
</div> </div>
@ -42,7 +42,7 @@
</div> </div>
</ng-container> </ng-container>
<ng-container *ngIf="snapshot.suggestedItems.length === 0 && suggestionsSorted.length > 0"> <ng-container *ngIf="allowOpen || suggestionsSorted.length > 0">
<ng-container *sqxModal="suggestionsModal"> <ng-container *sqxModal="suggestionsModal">
<div class="control-dropdown suggestions-dropdown" [sqxAnchoredTo]="form" position="bottom-left" @fade> <div class="control-dropdown suggestions-dropdown" [sqxAnchoredTo]="form" position="bottom-left" @fade>
<div class="row"> <div class="row">

27
frontend/app/framework/angular/forms/editors/tag-editor.component.ts

@ -51,6 +51,12 @@ export class TagEditorComponent extends StatefulControlComponent<State, Readonly
@ViewChild('input', { static: false }) @ViewChild('input', { static: false })
public inputElement: ElementRef<HTMLInputElement>; public inputElement: ElementRef<HTMLInputElement>;
@Output()
public open = new EventEmitter();
@Output()
public close = new EventEmitter();
@Output() @Output()
public blur = new EventEmitter(); public blur = new EventEmitter();
@ -63,6 +69,9 @@ export class TagEditorComponent extends StatefulControlComponent<State, Readonly
@Input() @Input()
public acceptEnter?: boolean | null; public acceptEnter?: boolean | null;
@Input()
public allowOpen?: boolean | null = true;
@Input() @Input()
public allowDuplicates?: boolean | null = true; public allowDuplicates?: boolean | null = true;
@ -277,7 +286,7 @@ export class TagEditorComponent extends StatefulControlComponent<State, Readonly
return false; return false;
} }
} else if (Keys.isEscape(event) && this.suggestionsModal.isOpen) { } else if (Keys.isEscape(event) && this.suggestionsModal.isOpen) {
this.suggestionsModal.hide(); this.closeModal();
return false; return false;
} else if (Keys.isUp(event)) { } else if (Keys.isUp(event)) {
this.selectPrevIndex(); this.selectPrevIndex();
@ -370,6 +379,22 @@ export class TagEditorComponent extends StatefulControlComponent<State, Readonly
return this.snapshot.items.find(x => x.id === tagValue.id); return this.snapshot.items.find(x => x.id === tagValue.id);
} }
public closeModal() {
if (this.suggestionsModal.isOpen) {
this.close.emit();
this.suggestionsModal.hide();
}
}
public openModal() {
if (!this.suggestionsModal.isOpen) {
this.open.emit();
this.suggestionsModal.show();
}
}
public callTouched() { public callTouched() {
this.blur.next(true); this.blur.next(true);

8
frontend/app/framework/utils/array-extensions.spec.ts

@ -121,6 +121,14 @@ describe('ArrayExtensions', () => {
expect(array_1).toEqual([{ id: 'A' }, { id: 'b' }, { id: 'C' }]); expect(array_1).toEqual([{ id: 'A' }, { id: 'b' }, { id: 'C' }]);
}); });
it('should clear array', () => {
const array_0 = [1, 2, 3];
const array_1 = array_0.clear();
expect(array_0).toEqual([]);
expect(array_1).toEqual([]);
});
it('should convert to map', () => { it('should convert to map', () => {
const array_0 = [{ id: 'A', value: 1 }, { id: 'B', value: 2 }, { id: 'B', value: 3 }]; const array_0 = [{ id: 'A', value: 1 }, { id: 'B', value: 2 }, { id: 'B', value: 3 }];
const map = array_0.toMap(x => x.id); const map = array_0.toMap(x => x.id);

10
frontend/app/framework/utils/array-extensions.ts

@ -22,6 +22,8 @@ interface ReadonlyArray<T> {
} }
interface Array<T> { interface Array<T> {
clear(): Array<T>;
replacedBy(field: keyof T, value: T): ReadonlyArray<T>; replacedBy(field: keyof T, value: T): ReadonlyArray<T>;
replaceBy(field: keyof T, value: T): Array<T>; replaceBy(field: keyof T, value: T): Array<T>;
@ -83,6 +85,14 @@ Array.prototype.replacedBy = function<T>(field: keyof T, value: T) {
return copy; return copy;
}; };
Array.prototype.clear = function<T>() {
const self: T[] = this;
self.splice(0, self.length);
return self;
};
Array.prototype.removeBy = function<T>(field: keyof T, value: T) { Array.prototype.removeBy = function<T>(field: keyof T, value: T) {
const self: T[] = this; const self: T[] = this;

2
frontend/app/shared/components/references/reference-dropdown.component.html

@ -1,4 +1,4 @@
<sqx-dropdown [formControl]="control" [items]="snapshot.contentNames" valueProperty="id"> <sqx-dropdown [formControl]="control" [items]="snapshot.contentNames" valueProperty="id" (open)="onOpened()">
<ng-template let-content="$implicit" let-context="context"> <ng-template let-content="$implicit" let-context="context">
<span class="truncate" [innerHTML]="content.name | sqxHighlight:context"></span> <span class="truncate" [innerHTML]="content.name | sqxHighlight:context"></span>
</ng-template> </ng-template>

121
frontend/app/shared/components/references/reference-dropdown.component.ts

@ -7,16 +7,15 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, forwardRef, Input, OnChanges, SimpleChanges } from '@angular/core'; import { ChangeDetectionStrategy, ChangeDetectorRef, Component, forwardRef, Input, OnChanges, SimpleChanges } from '@angular/core';
import { FormControl, NG_VALUE_ACCESSOR } from '@angular/forms'; import { FormControl, NG_VALUE_ACCESSOR } from '@angular/forms';
import { ContentsDto } from '@app/shared';
import { AppsState, ContentDto, ContentsService, getContentValue, LanguageDto, LocalizerService, StatefulControlComponent, Types, UIOptions, value$ } from '@app/shared/internal'; import { AppsState, ContentDto, ContentsService, getContentValue, LanguageDto, LocalizerService, StatefulControlComponent, Types, UIOptions, value$ } from '@app/shared/internal';
import { Observable } from 'rxjs';
export const SQX_REFERENCE_DROPDOWN_CONTROL_VALUE_ACCESSOR: any = { export const SQX_REFERENCE_DROPDOWN_CONTROL_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => ReferenceDropdownComponent), multi: true, provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => ReferenceDropdownComponent), multi: true,
}; };
interface State { interface State {
// The referenced content items.
contents: ReadonlyArray<ContentDto>;
// The names of the selected content items for search. // The names of the selected content items for search.
contentNames: ReadonlyArray<ContentName>; contentNames: ReadonlyArray<ContentName>;
@ -29,7 +28,7 @@ type ContentName = { name: string; id?: string };
const NO_EMIT = { emitEvent: false }; const NO_EMIT = { emitEvent: false };
@Component({ @Component({
selector: 'sqx-reference-dropdown[mode][schemaId]', selector: 'sqx-reference-dropdown[mode][language][languages][schemaId]',
styleUrls: ['./reference-dropdown.component.scss'], styleUrls: ['./reference-dropdown.component.scss'],
templateUrl: './reference-dropdown.component.html', templateUrl: './reference-dropdown.component.html',
providers: [ providers: [
@ -39,10 +38,16 @@ const NO_EMIT = { emitEvent: false };
}) })
export class ReferenceDropdownComponent extends StatefulControlComponent<State, ReadonlyArray<string> | string> implements OnChanges { export class ReferenceDropdownComponent extends StatefulControlComponent<State, ReadonlyArray<string> | string> implements OnChanges {
private readonly itemCount: number; private readonly itemCount: number;
private readonly contents: ContentDto[] = [];
private isOpenedBefore = false;
private isLoadingFailed = false;
@Input() @Input()
public language: LanguageDto; public language: LanguageDto;
@Input()
public languages: ReadonlyArray<LanguageDto>;
@Input() @Input()
public schemaId: string; public schemaId: string;
@ -66,7 +71,6 @@ export class ReferenceDropdownComponent extends StatefulControlComponent<State,
private readonly localizer: LocalizerService, private readonly localizer: LocalizerService,
) { ) {
super(changeDetector, { super(changeDetector, {
contents: [],
contentNames: [], contentNames: [],
}); });
@ -95,30 +99,13 @@ export class ReferenceDropdownComponent extends StatefulControlComponent<State,
public ngOnChanges(changes: SimpleChanges) { public ngOnChanges(changes: SimpleChanges) {
if (changes['schemaId']) { if (changes['schemaId']) {
this.resetState(); this.contents.clear();
if (this.isValid) { this.resetState();
this.contentsService.getContents(this.appsState.appName, this.schemaId, { take: this.itemCount })
.subscribe({
next: ({ items: contents }) => {
const contentNames = this.createContentNames(contents);
this.next({ contents, contentNames });
},
error: () => {
this.control.disable(NO_EMIT);
},
});
} else {
this.control.disable(NO_EMIT);
}
} }
if (changes['language']) { if (changes['language'] || changes['schemaId']) {
this.next(s => ({ this.resetContentNames(true);
...s,
contentNames: this.createContentNames(this.snapshot.contents),
}));
} }
} }
@ -140,27 +127,77 @@ export class ReferenceDropdownComponent extends StatefulControlComponent<State,
} }
} }
private selectContent(value: any) { public onOpened() {
this.control.setValue(value, NO_EMIT); if (this.isOpenedBefore) {
return;
}
this.isOpenedBefore = true;
this.loadMore(this.contentsService.getContents(this.appsState.appName, this.schemaId, { take: this.itemCount }));
} }
private createContentNames(contents: ReadonlyArray<ContentDto>): ReadonlyArray<ContentName> { private selectContent(id: string | undefined) {
if (contents.length === 0) { const isNewId = !this.contents.find(x => x.id === id);
return [];
if (id && isNewId) {
this.loadMore(this.contentsService.getAllContents(this.appsState.appName, { ids: [id] }));
} }
const names = contents.map(content => { this.control.setValue(id, NO_EMIT);
const name = }
content.referenceFields
.map(f => getContentValue(content, this.language, f, false))
.map(v => v.formatted)
.filter(v => !!v)
.join(', ')
|| this.localizer.getOrKey('common.noValue');
return { name, id: content.id }; private loadMore(observable: Observable<ContentsDto>) {
}); observable
.subscribe({
next: ({ items: newContents }) => {
if (newContents.length === 0) {
return;
}
for (const content of newContents) {
const index = this.contents.findIndex(x => x.id === content.id);
if (index >= 0) {
this.contents[index] = content;
} else {
this.contents.push(content);
}
}
this.isLoadingFailed = false;
this.resetContentNames(true);
},
error: () => {
this.isLoadingFailed = true;
this.resetContentNames(false);
},
});
}
return [{ name: this.localizer.getOrKey('contents.noReference') }, ...names]; private resetContentNames(rebuild: boolean) {
const success = this.isValid && !this.isLoadingFailed;
this.onDisabled(!success);
if (success && rebuild) {
const contentNames: ContentName[] = [
{ name: this.localizer.getOrKey('contents.noReference') },
];
for (const content of this.contents) {
const name =
content.referenceFields
.map(f => getContentValue(content, this.language, f, false))
.map(v => v.formatted).filter(v => !!v)
.join(', ')
|| this.localizer.getOrKey('common.noValue');
contentNames.push({ name, id: content.id });
}
this.next({ contentNames });
}
} }
} }

46
frontend/app/shared/components/references/reference-input.component.ts

@ -22,7 +22,7 @@ interface State {
} }
@Component({ @Component({
selector: 'sqx-reference-input[language][languages]', selector: 'sqx-reference-input[mode][[language][languages]',
styleUrls: ['./reference-input.component.scss'], styleUrls: ['./reference-input.component.scss'],
templateUrl: './reference-input.component.html', templateUrl: './reference-input.component.html',
providers: [ providers: [
@ -30,7 +30,7 @@ interface State {
], ],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class ReferenceInputComponent extends StatefulControlComponent<State, string> implements OnChanges { export class ReferenceInputComponent extends StatefulControlComponent<State, ReadonlyArray<string> | string> implements OnChanges {
@Input() @Input()
public schemaIds?: ReadonlyArray<string>; public schemaIds?: ReadonlyArray<string>;
@ -40,6 +40,9 @@ export class ReferenceInputComponent extends StatefulControlComponent<State, str
@Input() @Input()
public languages: ReadonlyArray<LanguageDto>; public languages: ReadonlyArray<LanguageDto>;
@Input()
public mode: 'Array' | 'Single' = 'Single';
@Input() @Input()
public set disabled(value: boolean | undefined | null) { public set disabled(value: boolean | undefined | null) {
this.setDisabledState(value === true); this.setDisabledState(value === true);
@ -63,20 +66,26 @@ export class ReferenceInputComponent extends StatefulControlComponent<State, str
public writeValue(obj: any) { public writeValue(obj: any) {
if (Types.isString(obj)) { if (Types.isString(obj)) {
this.contentsService.getAllContents(this.appsState.appName, { ids: [obj] }) this.loadContent(obj);
.subscribe({ } else if (Types.isArrayOfString(obj)) {
next: contents => { this.loadContent(obj[0]);
this.updateContent(contents.items[0]);
},
error: () => {
this.updateContent(undefined);
},
});
} else { } else {
this.updateContent(); this.updateContent();
} }
} }
private loadContent(id: string) {
this.contentsService.getAllContents(this.appsState.appName, { ids: [id] })
.subscribe({
next: contents => {
this.updateContent(contents.items[0]);
},
error: () => {
this.updateContent(undefined);
},
});
}
public select(contents: ReadonlyArray<ContentDto>) { public select(contents: ReadonlyArray<ContentDto>) {
if (contents.length > 0) { if (contents.length > 0) {
this.selectContent(contents[0]); this.selectContent(contents[0]);
@ -86,7 +95,20 @@ export class ReferenceInputComponent extends StatefulControlComponent<State, str
} }
public selectContent(selectedContent?: ContentDto) { public selectContent(selectedContent?: ContentDto) {
this.callChange(selectedContent?.id); const id = selectedContent?.id;
if (id) {
if (this.mode === 'Single') {
this.callChange(id);
} else {
this.callChange([id]);
}
} else if (this.mode === 'Single') {
this.callChange(null);
} else {
this.callChange([]);
}
this.callTouched(); this.callTouched();
this.updateContent(selectedContent); this.updateContent(selectedContent);

12
frontend/app/shared/components/references/references-checkboxes.component.ts

@ -116,16 +116,16 @@ export class ReferencesCheckboxesComponent extends StatefulControlComponent<Stat
} }
private resetConverterState() { private resetConverterState() {
let converter: ReferencesTagsConverter; const success = this.isValid && this.contentItems && this.contentItems.length > 0;
if (this.isValid && this.contentItems && this.contentItems.length > 0) { this.onDisabled(!success);
converter = new ReferencesTagsConverter(this.language, this.contentItems, this.localizer);
this.control.enable(NO_EMIT); let converter: ReferencesTagsConverter;
if (success) {
converter = new ReferencesTagsConverter(this.language, this.contentItems!, this.localizer);
} else { } else {
converter = new ReferencesTagsConverter(null!, [], this.localizer); converter = new ReferencesTagsConverter(null!, [], this.localizer);
this.control.disable(NO_EMIT);
} }
this.next({ converter }); this.next({ converter });

9
frontend/app/shared/components/references/references-tags.component.html

@ -1,3 +1,8 @@
<sqx-tag-editor placeholder="{{ 'common.tagAddReference' | sqxTranslate }}" <sqx-tag-editor placeholder="{{ 'common.tagAddReference' | sqxTranslate }}"
[converter]="snapshot.converter" [formControl]="control" [suggestions]="snapshot.converter.suggestions"> (open)="onOpened()"
[formControl]="control"
[allowDuplicates]="false"
[allowOpen]="true"
[converter]="snapshot.converter"
[suggestions]="snapshot.converter.suggestions">
</sqx-tag-editor> </sqx-tag-editor>

110
frontend/app/shared/components/references/references-tags.component.ts

@ -7,7 +7,9 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, forwardRef, Input, OnChanges, SimpleChanges } from '@angular/core'; import { ChangeDetectionStrategy, ChangeDetectorRef, Component, forwardRef, Input, OnChanges, SimpleChanges } from '@angular/core';
import { FormControl, NG_VALUE_ACCESSOR } from '@angular/forms'; import { FormControl, NG_VALUE_ACCESSOR } from '@angular/forms';
import { AppsState, ContentDto, ContentsService, LanguageDto, LocalizerService, StatefulControlComponent, UIOptions } from '@app/shared/internal'; import { Types } from '@app/framework';
import { AppsState, ContentDto, ContentsDto, ContentsService, LanguageDto, LocalizerService, StatefulControlComponent, UIOptions } from '@app/shared/internal';
import { Observable } from 'rxjs';
import { ReferencesTagsConverter } from './references-tag-converter'; import { ReferencesTagsConverter } from './references-tag-converter';
export const SQX_REFERENCES_TAGS_CONTROL_VALUE_ACCESSOR: any = { export const SQX_REFERENCES_TAGS_CONTROL_VALUE_ACCESSOR: any = {
@ -22,7 +24,7 @@ interface State {
const NO_EMIT = { emitEvent: false }; const NO_EMIT = { emitEvent: false };
@Component({ @Component({
selector: 'sqx-references-tags[language][schemaId]', selector: 'sqx-references-tags[language][languages][schemaId]',
styleUrls: ['./references-tags.component.scss'], styleUrls: ['./references-tags.component.scss'],
templateUrl: './references-tags.component.html', templateUrl: './references-tags.component.html',
providers: [ providers: [
@ -32,7 +34,9 @@ const NO_EMIT = { emitEvent: false };
}) })
export class ReferencesTagsComponent extends StatefulControlComponent<State, ReadonlyArray<string>> implements OnChanges { export class ReferencesTagsComponent extends StatefulControlComponent<State, ReadonlyArray<string>> implements OnChanges {
private readonly itemCount: number; private readonly itemCount: number;
private contentItems: ReadonlyArray<ContentDto> | null = null; private readonly contents: ContentDto[] = [];
private isOpenedBefore = false;
private isLoadingFailed = false;
@Input() @Input()
public schemaId: string; public schemaId: string;
@ -40,6 +44,9 @@ export class ReferencesTagsComponent extends StatefulControlComponent<State, Rea
@Input() @Input()
public language: LanguageDto; public language: LanguageDto;
@Input()
public languages: ReadonlyArray<LanguageDto>;
@Input() @Input()
public set disabled(value: boolean | undefined | null) { public set disabled(value: boolean | undefined | null) {
this.setDisabledState(value === true); this.setDisabledState(value === true);
@ -77,29 +84,13 @@ export class ReferencesTagsComponent extends StatefulControlComponent<State, Rea
public ngOnChanges(changes: SimpleChanges) { public ngOnChanges(changes: SimpleChanges) {
if (changes['schemaId']) { if (changes['schemaId']) {
this.contents.clear();
this.resetState(); this.resetState();
}
if (this.isValid) { if (changes['language'] || changes['schemaId']) {
this.contentsService.getContents(this.appsState.appName, this.schemaId, { take: this.itemCount }) this.resetConverterState(true);
.subscribe({
next: contents => {
this.contentItems = contents.items;
this.resetConverterState();
},
error: () => {
this.contentItems = null;
this.resetConverterState();
},
});
} else {
this.contentItems = null;
this.resetConverterState();
}
} else {
this.resetConverterState();
} }
} }
@ -111,23 +102,72 @@ export class ReferencesTagsComponent extends StatefulControlComponent<State, Rea
} }
} }
public onOpened() {
if (this.isOpenedBefore) {
return;
}
this.isOpenedBefore = true;
this.loadMore(this.contentsService.getContents(this.appsState.appName, this.schemaId, { take: this.itemCount }));
}
public writeValue(obj: ReadonlyArray<string>) { public writeValue(obj: ReadonlyArray<string>) {
this.control.setValue(obj, NO_EMIT); if (Types.isArrayOfString(obj)) {
this.selectContent(obj);
} else {
this.selectContent(undefined);
}
} }
private resetConverterState() { private selectContent(ids?: ReadonlyArray<string>) {
let converter: ReferencesTagsConverter; const newIds = ids?.filter(x => !this.contents?.find(y => y.id === x));
if (this.isValid && this.contentItems && this.contentItems.length > 0) { if (newIds && newIds.length > 0) {
converter = new ReferencesTagsConverter(this.language, this.contentItems, this.localizer); this.loadMore(this.contentsService.getAllContents(this.appsState.appName, { ids: newIds }));
}
this.control.enable(NO_EMIT); this.control.setValue(ids, NO_EMIT);
} else { }
converter = new ReferencesTagsConverter(null!, [], this.localizer);
this.control.disable(NO_EMIT); private loadMore(observable: Observable<ContentsDto>) {
} observable
.subscribe({
next: ({ items: newContents }) => {
if (newContents.length === 0) {
return;
}
for (const content of newContents) {
const index = this.contents.findIndex(x => x.id === content.id);
if (index >= 0) {
this.contents[index] = content;
} else {
this.contents.push(content);
}
}
this.isLoadingFailed = false;
this.next({ converter }); this.resetConverterState(true);
},
error: () => {
this.isLoadingFailed = true;
this.resetConverterState(false);
},
});
}
private resetConverterState(rebuild: boolean) {
const success = this.isValid && !this.isLoadingFailed;
this.onDisabled(!success);
if (rebuild) {
const converter = new ReferencesTagsConverter(this.language, this.contents, this.localizer);
this.next({ converter });
}
} }
} }

3
frontend/app/shared/services/schemas.types.ts

@ -367,13 +367,14 @@ export class NumberFieldPropertiesDto extends FieldPropertiesDto {
} }
} }
export type ReferencesFieldEditor = 'List' | 'Dropdown' | 'Checkboxes' | 'Tags'; export type ReferencesFieldEditor = 'List' | 'Dropdown' | 'Checkboxes' | 'Tags' | 'Input';
export const REFERENCES_FIELD_EDITORS: ReadonlyArray<ReferencesFieldEditor> = [ export const REFERENCES_FIELD_EDITORS: ReadonlyArray<ReferencesFieldEditor> = [
'List', 'List',
'Dropdown', 'Dropdown',
'Checkboxes', 'Checkboxes',
'Tags', 'Tags',
'Input',
]; ];
export class ReferencesFieldPropertiesDto extends FieldPropertiesDto { export class ReferencesFieldPropertiesDto extends FieldPropertiesDto {

14
frontend/app/theme/icomoon/demo.html

@ -1629,6 +1629,20 @@
<input type="text" readonly value="" class="liga unitRight" /> <input type="text" readonly value="" class="liga unitRight" />
</div> </div>
</div> </div>
<div class="glyph fs3">
<div class="clearfix bshadow0 pbs">
<span class="icon-control-StockPhoto"></span>
<span class="mls"> icon-control-StockPhoto</span>
</div>
<fieldset class="fs0 size1of1 clearfix hidden-false">
<input type="text" readonly value="e91d" class="unit size1of2" />
<input type="text" maxlength="1" readonly value="&#xe91d;" class="unitRight size1of2 talign-right" />
</fieldset>
<div class="fs0 bshadow0 clearfix hidden-true">
<span class="unit pvs fgc1">liga: </span>
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs3"> <div class="glyph fs3">
<div class="clearfix bshadow0 pbs"> <div class="clearfix bshadow0 pbs">
<span class="icon-more"></span> <span class="icon-more"></span>

BIN
frontend/app/theme/icomoon/fonts/icomoon.eot

Binary file not shown.

2
frontend/app/theme/icomoon/fonts/icomoon.svg

@ -36,7 +36,7 @@
<glyph unicode="&#xe91a;" glyph-name="type-Json, json" d="M179.2 682.667c0 28.262 22.938 51.2 51.2 51.2h25.6c14.157 0 25.6 11.443 25.6 25.6 0 14.131-11.443 25.6-25.6 25.6h-25.6c-56.55 0-102.4-45.85-102.4-102.4v-179.2c0-28.262-22.938-51.2-51.2-51.2h-25.6c-14.157 0-25.6-11.469-25.6-25.6 0-14.157 11.443-25.6 25.6-25.6h25.6c28.262 0 51.2-22.938 51.2-51.2v-179.2c0-56.55 45.85-102.4 102.4-102.4h25.6c14.157 0 25.6 11.443 25.6 25.6s-11.443 25.6-25.6 25.6h-25.6c-28.262 0-51.2 22.938-51.2 51.2v179.2c0 30.746-13.85 58.061-35.328 76.8 21.478 18.765 35.328 46.029 35.328 76.8v179.2zM972.8 452.267h-25.6c-28.262 0-51.2 22.938-51.2 51.2v179.2c0 56.55-45.85 102.4-102.4 102.4h-25.6c-14.157 0-25.6-11.469-25.6-25.6 0-14.157 11.443-25.6 25.6-25.6h25.6c28.262 0 51.2-22.938 51.2-51.2v-179.2c0-30.771 13.85-58.035 35.328-76.8-21.478-18.739-35.328-46.054-35.328-76.8v-179.2c0-28.262-22.938-51.2-51.2-51.2h-25.6c-14.157 0-25.6-11.443-25.6-25.6s11.443-25.6 25.6-25.6h25.6c56.55 0 102.4 45.85 102.4 102.4v179.2c0 28.262 22.938 51.2 51.2 51.2h25.6c14.157 0 25.6 11.443 25.6 25.6 0 14.131-11.443 25.6-25.6 25.6zM512 605.867c-14.157 0-25.6-11.469-25.6-25.6 0-14.157 11.443-25.6 25.6-25.6s25.6 11.443 25.6 25.6c0 14.131-11.443 25.6-25.6 25.6zM512 503.467c-14.157 0-25.6-11.469-25.6-25.6v-204.8c0-14.157 11.443-25.6 25.6-25.6s25.6 11.443 25.6 25.6v204.8c0 14.131-11.443 25.6-25.6 25.6z" /> <glyph unicode="&#xe91a;" glyph-name="type-Json, json" d="M179.2 682.667c0 28.262 22.938 51.2 51.2 51.2h25.6c14.157 0 25.6 11.443 25.6 25.6 0 14.131-11.443 25.6-25.6 25.6h-25.6c-56.55 0-102.4-45.85-102.4-102.4v-179.2c0-28.262-22.938-51.2-51.2-51.2h-25.6c-14.157 0-25.6-11.469-25.6-25.6 0-14.157 11.443-25.6 25.6-25.6h25.6c28.262 0 51.2-22.938 51.2-51.2v-179.2c0-56.55 45.85-102.4 102.4-102.4h25.6c14.157 0 25.6 11.443 25.6 25.6s-11.443 25.6-25.6 25.6h-25.6c-28.262 0-51.2 22.938-51.2 51.2v179.2c0 30.746-13.85 58.061-35.328 76.8 21.478 18.765 35.328 46.029 35.328 76.8v179.2zM972.8 452.267h-25.6c-28.262 0-51.2 22.938-51.2 51.2v179.2c0 56.55-45.85 102.4-102.4 102.4h-25.6c-14.157 0-25.6-11.469-25.6-25.6 0-14.157 11.443-25.6 25.6-25.6h25.6c28.262 0 51.2-22.938 51.2-51.2v-179.2c0-30.771 13.85-58.035 35.328-76.8-21.478-18.739-35.328-46.054-35.328-76.8v-179.2c0-28.262-22.938-51.2-51.2-51.2h-25.6c-14.157 0-25.6-11.443-25.6-25.6s11.443-25.6 25.6-25.6h25.6c56.55 0 102.4 45.85 102.4 102.4v179.2c0 28.262 22.938 51.2 51.2 51.2h25.6c14.157 0 25.6 11.443 25.6 25.6 0 14.131-11.443 25.6-25.6 25.6zM512 605.867c-14.157 0-25.6-11.469-25.6-25.6 0-14.157 11.443-25.6 25.6-25.6s25.6 11.443 25.6 25.6c0 14.131-11.443 25.6-25.6 25.6zM512 503.467c-14.157 0-25.6-11.469-25.6-25.6v-204.8c0-14.157 11.443-25.6 25.6-25.6s25.6 11.443 25.6 25.6v204.8c0 14.131-11.443 25.6-25.6 25.6z" />
<glyph unicode="&#xe91b;" glyph-name="location, control-Map, type-Geolocation" d="M512 938.667c-169.421 0-307.2-137.779-307.2-307.2 0-78.643 15.258-164.915 45.261-256.41 23.859-72.55 56.986-148.582 98.56-226.099 70.707-131.635 140.339-220.774 143.309-224.512 4.813-6.195 12.288-9.779 20.070-9.779 7.834 0 15.258 3.584 20.122 9.779 2.97 3.686 72.602 92.826 143.309 224.512 41.574 77.517 74.701 153.549 98.56 226.099 29.952 91.494 45.21 177.766 45.21 256.41 0 169.421-137.83 307.2-307.2 307.2zM630.682 173.995c-46.234-86.374-92.979-154.982-118.682-190.822-25.6 35.635-72.038 103.885-118.221 189.952-62.874 117.146-137.779 291.738-137.779 458.342 0 141.158 114.842 256 256 256s256-114.842 256-256c0-166.298-74.65-340.582-137.318-457.472zM512 785.067c-84.685 0-153.6-68.915-153.6-153.6s68.915-153.6 153.6-153.6 153.6 68.915 153.6 153.6-68.915 153.6-153.6 153.6zM512 529.067c-56.525 0-102.4 45.875-102.4 102.4 0 56.474 45.875 102.4 102.4 102.4 56.474 0 102.4-45.926 102.4-102.4 0-56.525-45.926-102.4-102.4-102.4z" /> <glyph unicode="&#xe91b;" glyph-name="location, control-Map, type-Geolocation" d="M512 938.667c-169.421 0-307.2-137.779-307.2-307.2 0-78.643 15.258-164.915 45.261-256.41 23.859-72.55 56.986-148.582 98.56-226.099 70.707-131.635 140.339-220.774 143.309-224.512 4.813-6.195 12.288-9.779 20.070-9.779 7.834 0 15.258 3.584 20.122 9.779 2.97 3.686 72.602 92.826 143.309 224.512 41.574 77.517 74.701 153.549 98.56 226.099 29.952 91.494 45.21 177.766 45.21 256.41 0 169.421-137.83 307.2-307.2 307.2zM630.682 173.995c-46.234-86.374-92.979-154.982-118.682-190.822-25.6 35.635-72.038 103.885-118.221 189.952-62.874 117.146-137.779 291.738-137.779 458.342 0 141.158 114.842 256 256 256s256-114.842 256-256c0-166.298-74.65-340.582-137.318-457.472zM512 785.067c-84.685 0-153.6-68.915-153.6-153.6s68.915-153.6 153.6-153.6 153.6 68.915 153.6 153.6-68.915 153.6-153.6 153.6zM512 529.067c-56.525 0-102.4 45.875-102.4 102.4 0 56.474 45.875 102.4 102.4 102.4 56.474 0 102.4-45.926 102.4-102.4 0-56.525-45.926-102.4-102.4-102.4z" />
<glyph unicode="&#xe91c;" glyph-name="logo" d="M512.273 854.885c-0.141-0.056-182.959-84.073-229.418-256.782-4.481-16.584 32.696-9.296 31.036-27.527-2.034-22.136-44.668-31.201-39.109-94.764 5.659-64.734 60.321-130.141 68.527-169.673v-27.655c-0.497-8.54-4.566-31.715-18.018-43.036-7.378-6.19-17.322-8.421-30.436-6.782-18.205 2.275-25.449 14.468-28.345 24.309-4.753 16.218-0.322 35.123 10.345 44 10.724 8.924 12.17 24.842 3.236 35.564-8.934 10.712-24.858 12.161-35.582 3.218-25.995-21.64-36.887-61.52-26.491-97 9.815-33.392 36.197-55.884 70.6-60.182 4.903-0.609 9.566-0.909 14-0.909 26.623 0 44.661 10.175 55.582 19.455 32.866 27.97 35.449 74.593 35.636 79.818 0.009 0.309 0.018 0.618 0.018 0.927v21.218h0.109v1.418c0 12.351 10.008 22.364 22.382 22.364 11.944 0 21.609-9.346 22.273-21.109v-202.491c-0.206-2.912-2.536-29.892-17.891-42.945-7.368-6.274-17.384-8.53-30.545-6.873-18.214 2.275-25.476 14.468-28.364 24.291-4.762 16.228-0.322 35.151 10.345 44.018 10.724 8.933 12.188 24.833 3.255 35.564-8.924 10.694-24.876 12.161-35.6 3.218-26.013-21.631-36.887-61.52-26.491-97 9.796-33.392 36.197-55.893 70.6-60.2 4.903-0.609 9.566-0.891 14-0.891 26.623 0 44.671 10.156 55.564 19.436 32.875 27.97 35.458 74.611 35.636 79.836 0.019 0.328 0.018 0.609 0.018 0.909v225.636l0.127 0.055v1c0 12.595 10.219 22.8 22.836 22.8 12.349 0 22.333-9.824 22.727-22.073v-227.418c0-0.309 0-0.591 0.018-0.909 0.187-5.216 2.779-51.866 35.655-79.836 10.912-9.28 28.959-19.436 55.582-19.436 4.443 0 9.088 0.282 13.982 0.891 34.394 4.307 60.804 26.818 70.6 60.2 10.405 35.48-0.487 75.36-26.491 97-10.743 8.943-26.676 7.466-35.6-3.218-8.934-10.74-7.488-26.63 3.236-35.564 10.668-8.868 15.135-27.79 10.364-44.018-2.878-9.823-10.159-22.015-28.364-24.291-13.105-1.648-23.050 0.592-30.418 6.782-13.508 11.358-17.558 34.657-18.036 43v201.818c0.297 12.093 10.14 21.818 22.327 21.818 12.374 0 22.4-10.003 22.4-22.364v-1.418h0.073v-21.218c0-0.318 0-0.628 0.018-0.927 0.178-5.216 2.779-51.848 35.655-79.818 10.912-9.28 28.941-19.455 55.564-19.455 4.434 0 9.107 0.292 14 0.891 34.394 4.298 60.786 26.818 70.582 60.2 10.405 35.48-0.487 75.351-26.491 97-10.743 8.933-26.667 7.476-35.582-3.236-8.943-10.722-7.488-26.622 3.236-35.545 10.668-8.877 15.117-27.8 10.345-44.018-2.878-9.842-10.159-22.025-28.364-24.291-13.086-1.648-23.050 0.583-30.418 6.764-13.508 11.368-17.549 34.675-18.018 43v21.018c5.305 54.103 63.095 107.777 69.091 176.364 5.531 63.563-37.121 72.627-39.145 94.764-1.669 18.232 35.498 10.944 31.036 27.527-46.468 172.709-229.269 256.726-229.4 256.782z" /> <glyph unicode="&#xe91c;" glyph-name="logo" d="M512.273 854.885c-0.141-0.056-182.959-84.073-229.418-256.782-4.481-16.584 32.696-9.296 31.036-27.527-2.034-22.136-44.668-31.201-39.109-94.764 5.659-64.734 60.321-130.141 68.527-169.673v-27.655c-0.497-8.54-4.566-31.715-18.018-43.036-7.378-6.19-17.322-8.421-30.436-6.782-18.205 2.275-25.449 14.468-28.345 24.309-4.753 16.218-0.322 35.123 10.345 44 10.724 8.924 12.17 24.842 3.236 35.564-8.934 10.712-24.858 12.161-35.582 3.218-25.995-21.64-36.887-61.52-26.491-97 9.815-33.392 36.197-55.884 70.6-60.182 4.903-0.609 9.566-0.909 14-0.909 26.623 0 44.661 10.175 55.582 19.455 32.866 27.97 35.449 74.593 35.636 79.818 0.009 0.309 0.018 0.618 0.018 0.927v21.218h0.109v1.418c0 12.351 10.008 22.364 22.382 22.364 11.944 0 21.609-9.346 22.273-21.109v-202.491c-0.206-2.912-2.536-29.892-17.891-42.945-7.368-6.274-17.384-8.53-30.545-6.873-18.214 2.275-25.476 14.468-28.364 24.291-4.762 16.228-0.322 35.151 10.345 44.018 10.724 8.933 12.188 24.833 3.255 35.564-8.924 10.694-24.876 12.161-35.6 3.218-26.013-21.631-36.887-61.52-26.491-97 9.796-33.392 36.197-55.893 70.6-60.2 4.903-0.609 9.566-0.891 14-0.891 26.623 0 44.671 10.156 55.564 19.436 32.875 27.97 35.458 74.611 35.636 79.836 0.019 0.328 0.018 0.609 0.018 0.909v225.636l0.127 0.055v1c0 12.595 10.219 22.8 22.836 22.8 12.349 0 22.333-9.824 22.727-22.073v-227.418c0-0.309 0-0.591 0.018-0.909 0.187-5.216 2.779-51.866 35.655-79.836 10.912-9.28 28.959-19.436 55.582-19.436 4.443 0 9.088 0.282 13.982 0.891 34.394 4.307 60.804 26.818 70.6 60.2 10.405 35.48-0.487 75.36-26.491 97-10.743 8.943-26.676 7.466-35.6-3.218-8.934-10.74-7.488-26.63 3.236-35.564 10.668-8.868 15.135-27.79 10.364-44.018-2.878-9.823-10.159-22.015-28.364-24.291-13.105-1.648-23.050 0.592-30.418 6.782-13.508 11.358-17.558 34.657-18.036 43v201.818c0.297 12.093 10.14 21.818 22.327 21.818 12.374 0 22.4-10.003 22.4-22.364v-1.418h0.073v-21.218c0-0.318 0-0.628 0.018-0.927 0.178-5.216 2.779-51.848 35.655-79.818 10.912-9.28 28.941-19.455 55.564-19.455 4.434 0 9.107 0.292 14 0.891 34.394 4.298 60.786 26.818 70.582 60.2 10.405 35.48-0.487 75.351-26.491 97-10.743 8.933-26.667 7.476-35.582-3.236-8.943-10.722-7.488-26.622 3.236-35.545 10.668-8.877 15.117-27.8 10.345-44.018-2.878-9.842-10.159-22.025-28.364-24.291-13.086-1.648-23.050 0.583-30.418 6.764-13.508 11.368-17.549 34.675-18.018 43v21.018c5.305 54.103 63.095 107.777 69.091 176.364 5.531 63.563-37.121 72.627-39.145 94.764-1.669 18.232 35.498 10.944 31.036 27.527-46.468 172.709-229.269 256.726-229.4 256.782z" />
<glyph unicode="&#xe91d;" glyph-name="media, type-Assets, trigger-AssetChanged" d="M947.2 938.667h-870.4c-42.342 0-76.8-34.458-76.8-76.8v-870.4c0-42.342 34.458-76.8 76.8-76.8h870.4c42.342 0 76.8 34.458 76.8 76.8v870.4c0 42.342-34.458 76.8-76.8 76.8zM972.8-8.533c0-14.157-11.443-25.6-25.6-25.6h-870.4c-14.131 0-25.6 11.443-25.6 25.6v870.4c0 14.131 11.469 25.6 25.6 25.6h870.4c14.157 0 25.6-11.469 25.6-25.6v-870.4zM665.6 477.867c56.448 0 102.4 45.926 102.4 102.4s-45.952 102.4-102.4 102.4c-56.448 0-102.4-45.926-102.4-102.4s45.952-102.4 102.4-102.4zM665.6 631.467c28.211 0 51.2-22.989 51.2-51.2s-22.989-51.2-51.2-51.2c-28.211 0-51.2 22.989-51.2 51.2s22.989 51.2 51.2 51.2zM896 836.267h-768c-14.131 0-25.6-11.469-25.6-25.6v-614.4c0-14.157 11.469-25.6 25.6-25.6h768c14.157 0 25.6 11.443 25.6 25.6v614.4c0 14.131-11.443 25.6-25.6 25.6zM153.6 221.867v118.246l164.301 184.858c4.198 4.787 9.728 7.373 15.462 7.475 5.734 0.051 11.29-2.458 15.642-7.040l283.238-303.539h-478.643zM870.4 221.867h-168.090l-315.853 338.432c-14.285 15.334-33.331 23.603-53.709 23.347-20.326-0.256-39.219-9.011-53.094-24.627l-126.054-141.798v367.846h716.8v-563.2z" /> <glyph unicode="&#xe91d;" glyph-name="media, type-Assets, trigger-AssetChanged, control-StockPhoto" d="M947.2 938.667h-870.4c-42.342 0-76.8-34.458-76.8-76.8v-870.4c0-42.342 34.458-76.8 76.8-76.8h870.4c42.342 0 76.8 34.458 76.8 76.8v870.4c0 42.342-34.458 76.8-76.8 76.8zM972.8-8.533c0-14.157-11.443-25.6-25.6-25.6h-870.4c-14.131 0-25.6 11.443-25.6 25.6v870.4c0 14.131 11.469 25.6 25.6 25.6h870.4c14.157 0 25.6-11.469 25.6-25.6v-870.4zM665.6 477.867c56.448 0 102.4 45.926 102.4 102.4s-45.952 102.4-102.4 102.4c-56.448 0-102.4-45.926-102.4-102.4s45.952-102.4 102.4-102.4zM665.6 631.467c28.211 0 51.2-22.989 51.2-51.2s-22.989-51.2-51.2-51.2c-28.211 0-51.2 22.989-51.2 51.2s22.989 51.2 51.2 51.2zM896 836.267h-768c-14.131 0-25.6-11.469-25.6-25.6v-614.4c0-14.157 11.469-25.6 25.6-25.6h768c14.157 0 25.6 11.443 25.6 25.6v614.4c0 14.131-11.443 25.6-25.6 25.6zM153.6 221.867v118.246l164.301 184.858c4.198 4.787 9.728 7.373 15.462 7.475 5.734 0.051 11.29-2.458 15.642-7.040l283.238-303.539h-478.643zM870.4 221.867h-168.090l-315.853 338.432c-14.285 15.334-33.331 23.603-53.709 23.347-20.326-0.256-39.219-9.011-53.094-24.627l-126.054-141.798v367.846h716.8v-563.2z" />
<glyph unicode="&#xe91e;" glyph-name="more, dots" d="M128 554.667c-70.656 0-128-57.344-128-128s57.344-128 128-128c70.656 0 128 57.344 128 128s-57.344 128-128 128zM512 554.667c-70.656 0-128-57.344-128-128s57.344-128 128-128c70.656 0 128 57.344 128 128s-57.344 128-128 128zM896 554.667c-70.656 0-128-57.344-128-128s57.344-128 128-128c70.656 0 128 57.344 128 128s-57.344 128-128 128z" /> <glyph unicode="&#xe91e;" glyph-name="more, dots" d="M128 554.667c-70.656 0-128-57.344-128-128s57.344-128 128-128c70.656 0 128 57.344 128 128s-57.344 128-128 128zM512 554.667c-70.656 0-128-57.344-128-128s57.344-128 128-128c70.656 0 128 57.344 128 128s-57.344 128-128 128zM896 554.667c-70.656 0-128-57.344-128-128s57.344-128 128-128c70.656 0 128 57.344 128 128s-57.344 128-128 128z" />
<glyph unicode="&#xe91f;" glyph-name="pencil" d="M877.12 627.563l-66.304-66.368-228.224 228.224 66.368 66.368c25.216 25.152 66.048 25.152 91.264 0l136.896-137.024c25.216-25.216 25.216-65.984 0-91.2zM760.896 511.275l-386.176-386.112c-25.216-25.28-66.048-25.28-91.264 0l-136.96 136.896c-25.216 25.28-25.216 66.112 0 91.264l386.24 386.24 228.16-228.288zM64 42.667v191.872l191.936-191.872h-191.936z" /> <glyph unicode="&#xe91f;" glyph-name="pencil" d="M877.12 627.563l-66.304-66.368-228.224 228.224 66.368 66.368c25.216 25.152 66.048 25.152 91.264 0l136.896-137.024c25.216-25.216 25.216-65.984 0-91.2zM760.896 511.275l-386.176-386.112c-25.216-25.28-66.048-25.28-91.264 0l-136.96 136.896c-25.216 25.28-25.216 66.112 0 91.264l386.24 386.24 228.16-228.288zM64 42.667v191.872l191.936-191.872h-191.936z" />
<glyph unicode="&#xe920;" glyph-name="reference" d="M892.083 806.75c-73.523 73.498-193.152 73.498-266.65 0l-157.184-157.107c-9.958-10.035-9.958-26.214 0-36.275 10.061-9.984 26.24-9.984 36.25 0l157.133 157.107c53.504 53.555 140.672 53.555 194.176 0 53.581-53.504 53.581-140.672 0-194.176l-186.138-186.163c-53.53-53.581-140.672-53.581-194.176 0-10.086 10.010-26.24 10.010-36.275 0-10.035-10.086-10.035-26.189 0-36.25 36.787-36.736 84.992-55.117 133.325-55.117s96.589 18.432 133.376 55.117l186.163 186.214c73.498 73.472 73.498 193.152 0 266.65zM519.45 239.941l-157.082-157.082c-53.504-53.555-140.672-53.555-194.176 0-53.581 53.504-53.581 140.672 0 194.176l186.138 186.163c53.53 53.581 140.672 53.581 194.176 0 10.086-9.984 26.189-9.984 36.275 0 10.035 10.086 10.035 26.214 0 36.25-73.549 73.498-193.203 73.498-266.701 0l-186.163-186.163c-73.498-73.574-73.498-193.203 0-266.701 36.787-36.71 85.043-55.117 133.325-55.117 48.333 0 96.538 18.406 133.325 55.117l157.133 157.133c10.010 10.010 10.010 26.189 0 36.224-10.010 9.984-26.189 9.984-36.25 0z" /> <glyph unicode="&#xe920;" glyph-name="reference" d="M892.083 806.75c-73.523 73.498-193.152 73.498-266.65 0l-157.184-157.107c-9.958-10.035-9.958-26.214 0-36.275 10.061-9.984 26.24-9.984 36.25 0l157.133 157.107c53.504 53.555 140.672 53.555 194.176 0 53.581-53.504 53.581-140.672 0-194.176l-186.138-186.163c-53.53-53.581-140.672-53.581-194.176 0-10.086 10.010-26.24 10.010-36.275 0-10.035-10.086-10.035-26.189 0-36.25 36.787-36.736 84.992-55.117 133.325-55.117s96.589 18.432 133.376 55.117l186.163 186.214c73.498 73.472 73.498 193.152 0 266.65zM519.45 239.941l-157.082-157.082c-53.504-53.555-140.672-53.555-194.176 0-53.581 53.504-53.581 140.672 0 194.176l186.138 186.163c53.53 53.581 140.672 53.581 194.176 0 10.086-9.984 26.189-9.984 36.275 0 10.035 10.086 10.035 26.214 0 36.25-73.549 73.498-193.203 73.498-266.701 0l-186.163-186.163c-73.498-73.574-73.498-193.203 0-266.701 36.787-36.71 85.043-55.117 133.325-55.117 48.333 0 96.538 18.406 133.325 55.117l157.133 157.133c10.010 10.010 10.010 26.189 0 36.224-10.010 9.984-26.189 9.984-36.25 0z" />

Before

Width:  |  Height:  |  Size: 118 KiB

After

Width:  |  Height:  |  Size: 118 KiB

BIN
frontend/app/theme/icomoon/fonts/icomoon.ttf

Binary file not shown.

BIN
frontend/app/theme/icomoon/fonts/icomoon.woff

Binary file not shown.

2
frontend/app/theme/icomoon/selection.json

File diff suppressed because one or more lines are too long

13
frontend/app/theme/icomoon/style.css

@ -1,10 +1,10 @@
@font-face { @font-face {
font-family: 'icomoon'; font-family: 'icomoon';
src: url('fonts/icomoon.eot?7gtqq7'); src: url('fonts/icomoon.eot?yp975g');
src: url('fonts/icomoon.eot?7gtqq7#iefix') format('embedded-opentype'), src: url('fonts/icomoon.eot?yp975g#iefix') format('embedded-opentype'),
url('fonts/icomoon.ttf?7gtqq7') format('truetype'), url('fonts/icomoon.ttf?yp975g') format('truetype'),
url('fonts/icomoon.woff?7gtqq7') format('woff'), url('fonts/icomoon.woff?yp975g') format('woff'),
url('fonts/icomoon.svg?7gtqq7#icomoon') format('svg'); url('fonts/icomoon.svg?yp975g#icomoon') format('svg');
font-weight: normal; font-weight: normal;
font-style: normal; font-style: normal;
font-display: block; font-display: block;
@ -370,6 +370,9 @@
.icon-trigger-AssetChanged:before { .icon-trigger-AssetChanged:before {
content: "\e91d"; content: "\e91d";
} }
.icon-control-StockPhoto:before {
content: "\e91d";
}
.icon-more:before { .icon-more:before {
content: "\e91e"; content: "\e91e";
} }

Loading…
Cancel
Save