mirror of https://github.com/Squidex/squidex.git
56 changed files with 1282 additions and 219 deletions
@ -0,0 +1,17 @@ |
|||||
|
// ==========================================================================
|
||||
|
// NumberFieldEditor.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
namespace Squidex.Core.Schemas |
||||
|
{ |
||||
|
public enum NumberFieldEditor |
||||
|
{ |
||||
|
Input, |
||||
|
Radio, |
||||
|
Dropdown |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
// ==========================================================================
|
||||
|
// StringFieldEditor.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
namespace Squidex.Core.Schemas |
||||
|
{ |
||||
|
public enum StringFieldEditor |
||||
|
{ |
||||
|
Input, |
||||
|
TextArea, |
||||
|
Radio, |
||||
|
Dropdown |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,55 @@ |
|||||
|
<div [formGroup]="editForm"> |
||||
|
<div class="form-group row"> |
||||
|
<label for="field-placeholder" class="col-xs-3 col-form-label">Placeholder</label> |
||||
|
|
||||
|
<div class="col-xs-6"> |
||||
|
<div class="errors-box" *ngIf="editForm.controls.placeholder.invalid && editForm.controls.placeholder.touched" [@fade]> |
||||
|
<div class="errors"> |
||||
|
<span *ngIf="editForm.controls.placeholder.hasError('maxlength')"> |
||||
|
Placeholder can not have more than 100 characters. |
||||
|
</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<input type="text" class="form-control" id="field-placeholder" maxlength="100" formControlName="placeholder" /> |
||||
|
|
||||
|
<span class="form-hint"> |
||||
|
Define the placeholder for the input control. |
||||
|
</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="form-group row"> |
||||
|
<label for="field-editor" class="col-xs-3 col-form-label">Editor</label> |
||||
|
|
||||
|
<div class="col-xs-9"> |
||||
|
<label class="btn btn-radio" [class.active]="editForm.controls.editor.value === 'Input'"> |
||||
|
<input type="radio" class="radio-input" formControlName="editor" value="Input" /> |
||||
|
|
||||
|
<i class="icon-control-input"></i> |
||||
|
|
||||
|
<span class="radio-label">Input</span> |
||||
|
</label> |
||||
|
<label class="btn btn-radio" [class.active]="editForm.controls.editor.value === 'Dropdown'"> |
||||
|
<input type="radio" class="radio-input" 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]="editForm.controls.editor.value === 'Radio'"> |
||||
|
<input type="radio" class="radio-input" formControlName="editor" value="Radio" /> |
||||
|
|
||||
|
<i class="icon-control-radio"></i> |
||||
|
|
||||
|
<span class="radio-label">Radio</span> |
||||
|
</label> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="form-group row"> |
||||
|
<label for="field-allowed-values" class="col-xs-3 col-form-label">Allowed Values</label> |
||||
|
|
||||
|
<div class="col-xs-6"> |
||||
|
<sqx-tag-editor formControlName="allowedValues" [converter]="converter"></sqx-tag-editor> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
@ -0,0 +1,2 @@ |
|||||
|
@import '_vars'; |
||||
|
@import '_mixins'; |
||||
@ -0,0 +1,47 @@ |
|||||
|
/* |
||||
|
* Squidex Headless CMS |
||||
|
* |
||||
|
* @license |
||||
|
* Copyright (c) Sebastian Stehle. All rights reserved |
||||
|
*/ |
||||
|
|
||||
|
import { Component, Input, OnInit } from '@angular/core'; |
||||
|
import { FormControl, FormGroup, Validators } from '@angular/forms'; |
||||
|
import { Observable } from 'rxjs'; |
||||
|
|
||||
|
import { fadeAnimation, FloatConverter } from 'shared'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'sqx-number-ui', |
||||
|
styleUrls: ['number-ui.component.scss'], |
||||
|
templateUrl: 'number-ui.component.html', |
||||
|
animations: [ |
||||
|
fadeAnimation |
||||
|
] |
||||
|
}) |
||||
|
export class NumberUIComponent implements OnInit { |
||||
|
@Input() |
||||
|
public editForm: FormGroup; |
||||
|
|
||||
|
public converter = new FloatConverter(); |
||||
|
|
||||
|
public hideAllowedValues: Observable<boolean>; |
||||
|
|
||||
|
public ngOnInit() { |
||||
|
this.editForm.addControl('editor', |
||||
|
new FormControl('Input', [ |
||||
|
Validators.required |
||||
|
])); |
||||
|
this.editForm.addControl('placeholder', |
||||
|
new FormControl('', [ |
||||
|
Validators.maxLength(100) |
||||
|
])); |
||||
|
this.editForm.addControl('allowedValues', |
||||
|
new FormControl(undefined, [])); |
||||
|
|
||||
|
this.hideAllowedValues = |
||||
|
Observable.of(false) |
||||
|
.merge(this.editForm.get('editor').valueChanges) |
||||
|
.map(x => !x || x === 'Input' || x === 'Textarea'); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,30 @@ |
|||||
|
<div [formGroup]="editForm"> |
||||
|
<div class="form-group row"> |
||||
|
<label class="col-xs-3 col-form-checkbox-label" for="field-required">Required</label> |
||||
|
|
||||
|
<div class="col-xs-6"> |
||||
|
<input type="checkbox" class="form-check-input" id="field-required" formControlName="isRequired" /> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="form-group row"> |
||||
|
<label class="col-xs-3 col-form-label">Range</label> |
||||
|
|
||||
|
<div class="col-xs-3 minlength-col"> |
||||
|
<input type="number" class="form-control" id="field-min-value" formControlName="minValue" placeholder="Min Value" /> |
||||
|
|
||||
|
<label class="col-form-label minlength-label">-</label> |
||||
|
</div> |
||||
|
<div class="col-xs-3"> |
||||
|
<input type="number" class="form-control" id="field-max-value" formControlName="maxValue" placeholder="Max Value" /> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="form-group row" [class.hide]="(hideDefaultValue | async)"> |
||||
|
<label class="col-xs-3 col-form-label" for="field-default-value">Default Value</label> |
||||
|
|
||||
|
<div class="col-xs-6"> |
||||
|
<input type="number" class="form-control" id="field-default-value" formControlName="defaultValue" /> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
@ -0,0 +1,16 @@ |
|||||
|
@import '_vars'; |
||||
|
@import '_mixins'; |
||||
|
|
||||
|
.minlength { |
||||
|
&-col { |
||||
|
position: relative; |
||||
|
} |
||||
|
|
||||
|
&-label { |
||||
|
@include absolute(0, -4px, auto, auto); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.form-check-input { |
||||
|
margin: 0; |
||||
|
} |
||||
@ -0,0 +1,40 @@ |
|||||
|
/* |
||||
|
* Squidex Headless CMS |
||||
|
* |
||||
|
* @license |
||||
|
* Copyright (c) Sebastian Stehle. All rights reserved |
||||
|
*/ |
||||
|
|
||||
|
import { Component, Input, OnInit } from '@angular/core'; |
||||
|
import { FormControl, FormGroup } from '@angular/forms'; |
||||
|
import { Observable } from 'rxjs'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'sqx-number-validation', |
||||
|
styleUrls: ['number-validation.component.scss'], |
||||
|
templateUrl: 'number-validation.component.html' |
||||
|
}) |
||||
|
export class NumberValidationComponent implements OnInit { |
||||
|
@Input() |
||||
|
public editForm: FormGroup; |
||||
|
|
||||
|
public hideDefaultValue: Observable<boolean>; |
||||
|
|
||||
|
public ngOnInit() { |
||||
|
this.editForm.addControl('maxValue', |
||||
|
new FormControl()); |
||||
|
this.editForm.addControl('minValue', |
||||
|
new FormControl()); |
||||
|
this.editForm.addControl('pattern', |
||||
|
new FormControl()); |
||||
|
this.editForm.addControl('patternMessage', |
||||
|
new FormControl()); |
||||
|
this.editForm.addControl('defaultValue', |
||||
|
new FormControl()); |
||||
|
|
||||
|
this.hideDefaultValue = |
||||
|
Observable.of(false) |
||||
|
.merge(this.editForm.get('isRequired').valueChanges) |
||||
|
.map(x => !!x); |
||||
|
} |
||||
|
} |
||||
@ -1,3 +1,3 @@ |
|||||
<div class="slider-bar" #bar (click)="onBarMouseClick($event)"> |
<div class="slider-bar" #bar (click)="onBarMouseClick($event)" [class.disabled]="isDisabled"> |
||||
<div class="slider-thumb" #thumb (mousedown)="onThumbMouseDown($event)"></div> |
<div class="slider-thumb" #thumb (mousedown)="onThumbMouseDown($event)" [class.disabled]="isDisabled"></div> |
||||
</div> |
</div> |
||||
@ -0,0 +1,11 @@ |
|||||
|
<input type="text" class="form-control" [attr.name]="inputName" (keydown)="onKeyDown($event)" (blur)="onBlur()" |
||||
|
[formControl]="addInput" |
||||
|
autocomplete="off" |
||||
|
autocorrect="off" |
||||
|
autocapitalize="off"> |
||||
|
|
||||
|
<div class="items"> |
||||
|
<span class="item" *ngFor="let item of items; let i = index" [class.disabled]="addInput.disabled"> |
||||
|
{{item}} <i class="icon-close" (click)="remove(i)"></i> |
||||
|
</span> |
||||
|
</div> |
||||
@ -0,0 +1,39 @@ |
|||||
|
@import '_mixins'; |
||||
|
@import '_vars'; |
||||
|
|
||||
|
.items { |
||||
|
margin-top: .4rem; |
||||
|
min-height: 1.6rem; |
||||
|
} |
||||
|
|
||||
|
.item { |
||||
|
& { |
||||
|
@include border-radius(.8rem); |
||||
|
display: inline-block; |
||||
|
color: $color-accent-dark; |
||||
|
margin-right: .4rem; |
||||
|
margin-bottom: .3rem; |
||||
|
min-height: 1.6rem; |
||||
|
padding: 0 .6rem; |
||||
|
background: $color-theme-blue; |
||||
|
border: 0; |
||||
|
font-size: .8rem; |
||||
|
font-weight: normal; |
||||
|
line-height: 1.6rem; |
||||
|
} |
||||
|
|
||||
|
&.disabled { |
||||
|
& { |
||||
|
pointer-events: none; |
||||
|
} |
||||
|
|
||||
|
&, |
||||
|
&:hover { |
||||
|
background: $color-theme-blue-light; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
&:hover { |
||||
|
background: $color-theme-blue-dark; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,138 @@ |
|||||
|
/* |
||||
|
* Squidex Headless CMS |
||||
|
* |
||||
|
* @license |
||||
|
* Copyright (c) Sebastian Stehle. All rights reserved |
||||
|
*/ |
||||
|
|
||||
|
import { Component, forwardRef, Input } from '@angular/core'; |
||||
|
import { ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR } from '@angular/forms'; |
||||
|
|
||||
|
/* tslint:disable:no-empty */ |
||||
|
|
||||
|
const KEY_ENTER = 13; |
||||
|
const NOOP = () => { }; |
||||
|
|
||||
|
export interface Converter { |
||||
|
convert(input: string): any; |
||||
|
|
||||
|
isValid(input: string): boolean; |
||||
|
} |
||||
|
|
||||
|
export class IntConverter implements Converter { |
||||
|
public isValid(input: string): boolean { |
||||
|
return !!parseInt(input, 10) || input === '0'; |
||||
|
} |
||||
|
|
||||
|
public convert(input: string): any { |
||||
|
return parseInt(input, 10) || 0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export class FloatConverter implements Converter { |
||||
|
public isValid(input: string): boolean { |
||||
|
return !!parseFloat(input) || input === '0'; |
||||
|
} |
||||
|
|
||||
|
public convert(input: string): any { |
||||
|
return parseFloat(input) || 0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export class NoopConverter implements Converter { |
||||
|
public isValid(input: string): boolean { |
||||
|
return input.trim().length > 0; |
||||
|
} |
||||
|
|
||||
|
public convert(input: string): any { |
||||
|
return input.trim(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export const SQX_TAG_EDITOR_CONTROL_VALUE_ACCESSOR: any = { |
||||
|
provide: NG_VALUE_ACCESSOR, |
||||
|
useExisting: forwardRef(() => TagEditorComponent), |
||||
|
multi: true |
||||
|
}; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'sqx-tag-editor', |
||||
|
styleUrls: ['./tag-editor.component.scss'], |
||||
|
templateUrl: './tag-editor.component.html', |
||||
|
providers: [SQX_TAG_EDITOR_CONTROL_VALUE_ACCESSOR] |
||||
|
}) |
||||
|
export class TagEditorComponent implements ControlValueAccessor { |
||||
|
private changeCallback: (value: any) => void = NOOP; |
||||
|
private touchedCallback: () => void = NOOP; |
||||
|
|
||||
|
public addInput = new FormControl(); |
||||
|
|
||||
|
@Input() |
||||
|
public converter: Converter = new NoopConverter(); |
||||
|
|
||||
|
@Input() |
||||
|
public useDefaultValue: boolean = true; |
||||
|
|
||||
|
@Input() |
||||
|
public inputName: string = 'tag-editor'; |
||||
|
|
||||
|
public items: any[] = []; |
||||
|
|
||||
|
public writeValue(value: any) { |
||||
|
this.addInput.setValue(''); |
||||
|
|
||||
|
if (Array.isArray(value)) { |
||||
|
this.items = value; |
||||
|
} else { |
||||
|
this.items = []; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public setDisabledState(isDisabled: boolean): void { |
||||
|
if (isDisabled) { |
||||
|
this.addInput.disable(); |
||||
|
} else { |
||||
|
this.addInput.enable(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public registerOnChange(fn: any) { |
||||
|
this.changeCallback = fn; |
||||
|
} |
||||
|
|
||||
|
public registerOnTouched(fn: any) { |
||||
|
this.touchedCallback = fn; |
||||
|
} |
||||
|
|
||||
|
public remove(index: number) { |
||||
|
this.updateItems([...this.items.slice(0, index), ...this.items.splice(index + 1)]); |
||||
|
} |
||||
|
|
||||
|
public onBlur() { |
||||
|
this.touchedCallback(); |
||||
|
} |
||||
|
|
||||
|
public onKeyDown(event: KeyboardEvent) { |
||||
|
if (event.keyCode === KEY_ENTER) { |
||||
|
const value = <string>this.addInput.value; |
||||
|
|
||||
|
if (this.converter.isValid(value)) { |
||||
|
const converted = this.converter.convert(value); |
||||
|
|
||||
|
this.updateItems([...this.items, converted]); |
||||
|
this.addInput.reset(); |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private updateItems(items: string[]) { |
||||
|
this.items = items; |
||||
|
|
||||
|
if (items.length === 0 && this.useDefaultValue) { |
||||
|
this.changeCallback(undefined); |
||||
|
} else { |
||||
|
this.changeCallback(this.items); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Binary file not shown.
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 25 KiB |
Binary file not shown.
Binary file not shown.
Loading…
Reference in new issue