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-thumb" #thumb (mousedown)="onThumbMouseDown($event)"></div> |
|||
<div class="slider-bar" #bar (click)="onBarMouseClick($event)" [class.disabled]="isDisabled"> |
|||
<div class="slider-thumb" #thumb (mousedown)="onThumbMouseDown($event)" [class.disabled]="isDisabled"></div> |
|||
</div> |
|||
@ -1,39 +1,51 @@ |
|||
@import '_mixins'; |
|||
@import '_vars'; |
|||
|
|||
$bar-height: 12px; |
|||
|
|||
$thumb-size: 20px; |
|||
$thumb-margin: ($thumb-size - $bar-height) * .5; |
|||
|
|||
$color-border: #ccc; |
|||
$color-focus: #66afe9; |
|||
|
|||
.slider { |
|||
&-bar { |
|||
& { |
|||
@include border-radius($bar-height * .5); |
|||
position: relative; |
|||
border: 1px solid $color-border; |
|||
border: 1px solid $color-control; |
|||
margin-bottom: 20px; |
|||
margin-top: 5px; |
|||
margin-right: $thumb-size * .5; |
|||
background: $color-border; |
|||
background: $color-accent-dark; |
|||
height: $bar-height; |
|||
} |
|||
|
|||
&.disabled { |
|||
background: lighten($color-border, 5%); |
|||
} |
|||
} |
|||
|
|||
&-thumb { |
|||
& { |
|||
@include border-radius($thumb-size * .5); |
|||
position: absolute; |
|||
width: $thumb-size; |
|||
height: $thumb-size; |
|||
border: 1px solid $color-border; |
|||
background: $color-border; |
|||
border: 1px solid $color-control; |
|||
background: $color-accent-dark; |
|||
margin-top: -$thumb-margin; |
|||
margin-left: -$thumb-size * .5; |
|||
} |
|||
|
|||
&.disabled { |
|||
background: lighten($color-border, 5%); |
|||
} |
|||
|
|||
&.focused { |
|||
border-color: $color-focus; |
|||
border-color: $color-theme-blue; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.disabled { |
|||
pointer-events: none; |
|||
} |
|||
@ -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