diff --git a/src/Squidex/app/features/content/pages/content/content-field.component.html b/src/Squidex/app/features/content/pages/content/content-field.component.html index 605ad47eb..c875a2753 100644 --- a/src/Squidex/app/features/content/pages/content/content-field.component.html +++ b/src/Squidex/app/features/content/pages/content/content-field.component.html @@ -83,6 +83,9 @@
+
+ +
diff --git a/src/Squidex/app/features/schemas/declarations.ts b/src/Squidex/app/features/schemas/declarations.ts index bc742a5cd..3b194cfdd 100644 --- a/src/Squidex/app/features/schemas/declarations.ts +++ b/src/Squidex/app/features/schemas/declarations.ts @@ -9,6 +9,8 @@ export * from './pages/schema/types/boolean-ui.component'; export * from './pages/schema/types/boolean-validation.component'; export * from './pages/schema/types/date-time-ui.component'; export * from './pages/schema/types/date-time-validation.component'; +export * from './pages/schema/types/geolocation-ui.component'; +export * from './pages/schema/types/geolocation-validation.component'; export * from './pages/schema/types/json-ui.component'; export * from './pages/schema/types/json-validation.component'; export * from './pages/schema/types/number-ui.component'; diff --git a/src/Squidex/app/features/schemas/module.ts b/src/Squidex/app/features/schemas/module.ts index 89de29c50..5d71b6d99 100644 --- a/src/Squidex/app/features/schemas/module.ts +++ b/src/Squidex/app/features/schemas/module.ts @@ -22,6 +22,8 @@ import { BooleanValidationComponent, DateTimeUIComponent, DateTimeValidationComponent, + GeolocationUIComponent, + GeolocationValidationComponent, JsonUIComponent, JsonValidationComponent, NumberUIComponent, @@ -80,6 +82,8 @@ const routes: Routes = [ BooleanValidationComponent, DateTimeUIComponent, DateTimeValidationComponent, + GeolocationUIComponent, + GeolocationValidationComponent, JsonUIComponent, JsonValidationComponent, NumberUIComponent, diff --git a/src/Squidex/app/features/schemas/pages/schema/field.component.html b/src/Squidex/app/features/schemas/pages/schema/field.component.html index 3a414d8be..20b8b8ee8 100644 --- a/src/Squidex/app/features/schemas/pages/schema/field.component.html +++ b/src/Squidex/app/features/schemas/pages/schema/field.component.html @@ -141,6 +141,9 @@
+
+ +
@@ -161,6 +164,9 @@
+
+ +
diff --git a/src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts b/src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts index e013a565e..391f96d11 100644 --- a/src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts +++ b/src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts @@ -44,6 +44,7 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit { public fieldTypes: string[] = [ 'Boolean', 'DateTime', + 'Geolocation', 'Json', 'Number', 'String' diff --git a/src/Squidex/app/features/schemas/pages/schema/types/geolocation-ui.component.html b/src/Squidex/app/features/schemas/pages/schema/types/geolocation-ui.component.html new file mode 100644 index 000000000..22a20a7ae --- /dev/null +++ b/src/Squidex/app/features/schemas/pages/schema/types/geolocation-ui.component.html @@ -0,0 +1,15 @@ +
+
+ + +
+ +
+
+
\ No newline at end of file diff --git a/src/Squidex/app/features/schemas/pages/schema/types/geolocation-ui.component.scss b/src/Squidex/app/features/schemas/pages/schema/types/geolocation-ui.component.scss new file mode 100644 index 000000000..fbb752506 --- /dev/null +++ b/src/Squidex/app/features/schemas/pages/schema/types/geolocation-ui.component.scss @@ -0,0 +1,2 @@ +@import '_vars'; +@import '_mixins'; \ No newline at end of file diff --git a/src/Squidex/app/features/schemas/pages/schema/types/geolocation-ui.component.ts b/src/Squidex/app/features/schemas/pages/schema/types/geolocation-ui.component.ts new file mode 100644 index 000000000..a0fc725a5 --- /dev/null +++ b/src/Squidex/app/features/schemas/pages/schema/types/geolocation-ui.component.ts @@ -0,0 +1,31 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Sebastian Stehle. All rights reserved + */ + +import { Component, Input, OnInit } from '@angular/core'; +import { FormGroup, FormControl, Validators } from '@angular/forms'; + +import { GeolocationFieldPropertiesDto } from 'shared'; + +@Component({ + selector: 'sqx-geolocation-ui', + styleUrls: ['geolocation-ui.component.scss'], + templateUrl: 'geolocation-ui.component.html' +}) +export class GeolocationUIComponent implements OnInit { + @Input() + public editForm: FormGroup; + + @Input() + public properties: GeolocationFieldPropertiesDto; + + public ngOnInit() { + this.editForm.addControl('editor', + new FormControl(this.properties.editor, [ + Validators.required + ])); + } +} \ No newline at end of file diff --git a/src/Squidex/app/features/schemas/pages/schema/types/geolocation-validation.component.html b/src/Squidex/app/features/schemas/pages/schema/types/geolocation-validation.component.html new file mode 100644 index 000000000..54887deda --- /dev/null +++ b/src/Squidex/app/features/schemas/pages/schema/types/geolocation-validation.component.html @@ -0,0 +1,9 @@ +
+
+ + +
+ +
+
+
\ No newline at end of file diff --git a/src/Squidex/app/features/schemas/pages/schema/types/geolocation-validation.component.scss b/src/Squidex/app/features/schemas/pages/schema/types/geolocation-validation.component.scss new file mode 100644 index 000000000..f9405a205 --- /dev/null +++ b/src/Squidex/app/features/schemas/pages/schema/types/geolocation-validation.component.scss @@ -0,0 +1,10 @@ +@import '_vars'; +@import '_mixins'; + +.form-check-input { + margin: 0; +} + +.form-group { + margin-top: .5rem; +} \ No newline at end of file diff --git a/src/Squidex/app/features/schemas/pages/schema/types/geolocation-validation.component.ts b/src/Squidex/app/features/schemas/pages/schema/types/geolocation-validation.component.ts new file mode 100644 index 000000000..098fcacdc --- /dev/null +++ b/src/Squidex/app/features/schemas/pages/schema/types/geolocation-validation.component.ts @@ -0,0 +1,24 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Sebastian Stehle. All rights reserved + */ + +import { Component, Input } from '@angular/core'; +import { FormGroup } from '@angular/forms'; + +import { GeolocationFieldPropertiesDto } from 'shared'; + +@Component({ + selector: 'sqx-geolocation-validation', + styleUrls: ['geolocation-validation.component.scss'], + templateUrl: 'geolocation-validation.component.html' +}) +export class GeolocationValidationComponent { + @Input() + public editForm: FormGroup; + + @Input() + public properties: GeolocationFieldPropertiesDto; +} \ No newline at end of file diff --git a/src/Squidex/app/features/settings/pages/clients/client.component.html b/src/Squidex/app/features/settings/pages/clients/client.component.html index d4ab14dbf..cb98e2425 100644 --- a/src/Squidex/app/features/settings/pages/clients/client.component.html +++ b/src/Squidex/app/features/settings/pages/clients/client.component.html @@ -13,7 +13,7 @@
-
+
diff --git a/src/Squidex/app/framework/angular/date-time-editor.component.html b/src/Squidex/app/framework/angular/date-time-editor.component.html index 607c25cac..9b29ddf05 100644 --- a/src/Squidex/app/framework/angular/date-time-editor.component.html +++ b/src/Squidex/app/framework/angular/date-time-editor.component.html @@ -6,5 +6,8 @@
+
+ +
diff --git a/src/Squidex/app/framework/angular/date-time-editor.component.scss b/src/Squidex/app/framework/angular/date-time-editor.component.scss index 15ae9dda6..9c595c4d6 100644 --- a/src/Squidex/app/framework/angular/date-time-editor.component.scss +++ b/src/Squidex/app/framework/angular/date-time-editor.component.scss @@ -38,4 +38,8 @@ $form-color: #fff; .form-control { width: 7.5rem; } +} + +.clear { + margin-left: .5rem; } \ No newline at end of file diff --git a/src/Squidex/app/framework/angular/date-time-editor.component.ts b/src/Squidex/app/framework/angular/date-time-editor.component.ts index 8a104e206..232fa1b37 100644 --- a/src/Squidex/app/framework/angular/date-time-editor.component.ts +++ b/src/Squidex/app/framework/angular/date-time-editor.component.ts @@ -45,6 +45,10 @@ export class DateTimeEditorComponent implements ControlValueAccessor, OnInit, Af @Input() public enforceTime: boolean; + public get hasValue() { + return this.dateValue !== null; + } + @ViewChild('dateInput') public dateInput: ElementRef; @@ -125,6 +129,18 @@ export class DateTimeEditorComponent implements ControlValueAccessor, OnInit, Af this.touchedCallback(); } + public reset() { + this.timeControl.setValue(null, { emitEvent: false }); + this.dateControl.setValue(null, { emitEvent: false }); + + this.dateValue = null; + + this.changeCallback(null); + this.touchedCallback(); + + return false; + } + private updateValue() { let result: string = null; diff --git a/src/Squidex/app/framework/angular/geolocation-editor.component.html b/src/Squidex/app/framework/angular/geolocation-editor.component.html new file mode 100644 index 000000000..720f0c22c --- /dev/null +++ b/src/Squidex/app/framework/angular/geolocation-editor.component.html @@ -0,0 +1,19 @@ +
+
+ +
+ +
+ +
+
+ +
+
+ +
+ + + +
+
\ No newline at end of file diff --git a/src/Squidex/app/framework/angular/geolocation-editor.component.scss b/src/Squidex/app/framework/angular/geolocation-editor.component.scss new file mode 100644 index 000000000..ffa89e6ba --- /dev/null +++ b/src/Squidex/app/framework/angular/geolocation-editor.component.scss @@ -0,0 +1,14 @@ +@import '_mixins'; +@import '_vars'; + +.editor { + height: 30rem; +} + +.form-inline { + margin-top: .5rem; +} + +.latitude-group { + margin-right: .25rem; +} \ No newline at end of file diff --git a/src/Squidex/app/framework/angular/geolocation-editor.component.ts b/src/Squidex/app/framework/angular/geolocation-editor.component.ts new file mode 100644 index 000000000..c5db722aa --- /dev/null +++ b/src/Squidex/app/framework/angular/geolocation-editor.component.ts @@ -0,0 +1,160 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Sebastian Stehle. All rights reserved + */ + +import { AfterViewInit, Component, ElementRef, forwardRef, ViewChild } from '@angular/core'; +import { ControlValueAccessor, FormBuilder, NG_VALUE_ACCESSOR } from '@angular/forms'; + +import { ResourceLoaderService } from './../services/resource-loader.service'; +import { ValidatorsEx } from './validators'; + +const NOOP = () => { /* NOOP */ }; + +declare var L: any; + +export const SQX_GEOLOCATION_EDITOR_CONTROL_VALUE_ACCESSOR: any = { + provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => GeolocationEditorComponent), multi: true +}; + +@Component({ + selector: 'sqx-geolocation-editor', + styleUrls: ['./geolocation-editor.component.scss'], + templateUrl: './geolocation-editor.component.html', + providers: [SQX_GEOLOCATION_EDITOR_CONTROL_VALUE_ACCESSOR] +}) +export class GeolocationEditorComponent implements ControlValueAccessor, AfterViewInit { + private changeCallback: (value: any) => void = NOOP; + private touchedCallback: () => void = NOOP; + private marker: any; + private map: any; + private value: any; + + public get hasValue() { + return !!this.value; + } + + public geolocationForm = + this.formBuilder.group({ + latitude: ['', + [ + ValidatorsEx.between(-90, 90) + ]], + longitude: ['', + [ + ValidatorsEx.between(-180, 180) + ]] + }); + + public isDisabled = false; + + @ViewChild('editor') + public editor: ElementRef; + + constructor( + private readonly resourceLoader: ResourceLoaderService, + private readonly formBuilder: FormBuilder + ) { + } + + public writeValue(value: any) { + this.value = value; + + if (this.marker) { + this.updateMarker(true, false); + } + } + + public setDisabledState(isDisabled: boolean): void { + this.isDisabled = isDisabled; + } + + public registerOnChange(fn: any) { + this.changeCallback = fn; + } + + public registerOnTouched(fn: any) { + this.touchedCallback = fn; + } + + public updateValueByInput() { + if (this.geolocationForm.controls['latitude'].value !== null && + this.geolocationForm.controls['longitude'].value !== null && + this.geolocationForm.valid) { + this.value = this.geolocationForm.value; + } else { + this.value = null; + } + + this.updateMarker(true, true); + } + + public ngAfterViewInit() { + this.resourceLoader.loadStyle('https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.0.3/leaflet.css'); + this.resourceLoader.loadScript('https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.0.3/leaflet.js').then(() => { + this.map = L.map(this.editor.nativeElement).fitWorld(); + + L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', { + attribution: '© OpenStreetMap contributors' + }).addTo(this.map); + + this.map.on('click', (event: any) => { + this.value = { latitude: event.latlng.lat, longitude: event.latlng.lng }; + + this.updateMarker(false, true); + }); + + this.updateMarker(true, false); + }); + } + + public reset() { + this.value = null; + + this.updateMarker(true, true); + } + + private updateMarker(zoom: boolean, fireEvent: boolean) { + if (this.value) { + if (!this.marker) { + this.marker = L.marker([0, 90], { draggable: true }).addTo(this.map); + + this.marker.on('drag', (event: any) => { + this.value = { latitude: event.latlng.lat, longitude: event.latlng.lng }; + }); + + this.marker.on('dragend', (event: any) => { + this.updateMarker(false, true); + }); + } + + const latLng = L.latLng(this.value.latitude, this.value.longitude); + + if (zoom) { + this.map.setView(latLng, 8); + } else { + this.map.panTo(latLng); + } + + this.marker.setLatLng(latLng); + + this.geolocationForm.setValue(this.value, { emitEvent: false, onlySelf: false }); + } else { + if (this.marker) { + this.marker.removeFrom(this.map); + this.marker = null; + } + + this.map.fitWorld(); + + this.geolocationForm.reset(undefined, { emitEvent: false, onlySelf: false }); + } + + if (fireEvent) { + this.changeCallback(this.value); + this.touchedCallback(); + } + } +} \ No newline at end of file diff --git a/src/Squidex/app/framework/angular/stars.component.html b/src/Squidex/app/framework/angular/stars.component.html index b3054eac5..3f6f87aec 100644 --- a/src/Squidex/app/framework/angular/stars.component.html +++ b/src/Squidex/app/framework/angular/stars.component.html @@ -6,5 +6,7 @@ + +
\ No newline at end of file diff --git a/src/Squidex/app/framework/angular/stars.component.scss b/src/Squidex/app/framework/angular/stars.component.scss index cf00f7508..07aca0371 100644 --- a/src/Squidex/app/framework/angular/stars.component.scss +++ b/src/Squidex/app/framework/angular/stars.component.scss @@ -7,25 +7,28 @@ $color-gold: #ffd700; color: $color-empty; } -.stars-container { - height: 1.8rem; -} - .stars { & { display: inline-block; border: 0; + height: 2.4rem; + line-height: 2.4rem; } &.disabled { cursor: not-allowed; } + + &-container { + height: 2.4rem; + } } .star { & { background: transparent; border: 0; + line-height: 1px; } &::before { @@ -33,7 +36,7 @@ $color-gold: #ffd700; color: $color-gold; content: '☆'; font-size: 1.8rem; - line-height: 2rem; + line-height: 1px; } &.selected { diff --git a/src/Squidex/app/framework/angular/stars.component.ts b/src/Squidex/app/framework/angular/stars.component.ts index 3d2d167ec..a3ca5b000 100644 --- a/src/Squidex/app/framework/angular/stars.component.ts +++ b/src/Squidex/app/framework/angular/stars.component.ts @@ -23,7 +23,6 @@ export const SQX_STARS_CONTROL_VALUE_ACCESSOR: any = { export class StarsComponent implements ControlValueAccessor { private changeCallback: (value: any) => void = NOOP; private touchedCallback: () => void = NOOP; - private value = 1; private maximumStarsValue = 5; public isDisabled = false; @@ -31,6 +30,8 @@ export class StarsComponent implements ControlValueAccessor { public stars: number; public starsArray: number[] = [1, 2, 3, 4, 5]; + public value: number | null = 1; + public get maximumStars() { return this.maximumStarsValue; } @@ -71,13 +72,35 @@ export class StarsComponent implements ControlValueAccessor { } public setPreview(value: number) { + if (this.isDisabled) { + return; + } + this.stars = value; } public stopPreview() { + if (this.isDisabled) { + return; + } + this.stars = this.value; } + public reset() { + if (this.isDisabled) { + return; + } + + this.value = null; + this.stars = 0; + + this.changeCallback(this.value); + this.touchedCallback(); + + return false; + } + public setValue(value: number) { if (this.isDisabled) { return; @@ -87,5 +110,7 @@ export class StarsComponent implements ControlValueAccessor { this.changeCallback(this.value); this.touchedCallback(); + + return false; } } \ No newline at end of file diff --git a/src/Squidex/app/framework/angular/toggle.component.scss b/src/Squidex/app/framework/angular/toggle.component.scss index 0520d0784..ba591b7c5 100644 --- a/src/Squidex/app/framework/angular/toggle.component.scss +++ b/src/Squidex/app/framework/angular/toggle.component.scss @@ -50,11 +50,9 @@ $toggle-button-size: $toggle-height - .3rem; } &.disabled { - & { - background: $color-disabled; - border: 0; - cursor: not-allowed; - } + background: $color-disabled; + border: 0; + cursor: not-allowed; } } } \ No newline at end of file diff --git a/src/Squidex/app/framework/declarations.ts b/src/Squidex/app/framework/declarations.ts index a3f16a970..23a95f22f 100644 --- a/src/Squidex/app/framework/declarations.ts +++ b/src/Squidex/app/framework/declarations.ts @@ -15,6 +15,7 @@ export * from './angular/date-time-editor.component'; export * from './angular/date-time.pipes'; export * from './angular/focus-on-change.directive'; export * from './angular/focus-on-init.directive'; +export * from './angular/geolocation-editor.component'; export * from './angular/http-utils'; export * from './angular/indeterminate.directive'; export * from './angular/json-editor.component'; diff --git a/src/Squidex/app/framework/module.ts b/src/Squidex/app/framework/module.ts index 7568e41ca..8003f3995 100644 --- a/src/Squidex/app/framework/module.ts +++ b/src/Squidex/app/framework/module.ts @@ -25,6 +25,7 @@ import { FocusOnChangeDirective, FocusOnInitDirective, FromNowPipe, + GeolocationEditorComponent, JsonEditorComponent, LocalStoreService, MarkdownEditorComponent, @@ -73,6 +74,7 @@ import { FocusOnChangeDirective, FocusOnInitDirective, FromNowPipe, + GeolocationEditorComponent, JsonEditorComponent, MarkdownEditorComponent, ModalViewDirective, @@ -105,6 +107,7 @@ import { FocusOnChangeDirective, FocusOnInitDirective, FromNowPipe, + GeolocationEditorComponent, JsonEditorComponent, MarkdownEditorComponent, ModalViewDirective, diff --git a/src/Squidex/app/shared/services/schemas.service.spec.ts b/src/Squidex/app/shared/services/schemas.service.spec.ts index 4cee13d7a..e7f9c0952 100644 --- a/src/Squidex/app/shared/services/schemas.service.spec.ts +++ b/src/Squidex/app/shared/services/schemas.service.spec.ts @@ -149,6 +149,14 @@ describe('SchemasService', () => { properties: { fieldType: 'Json' } + }, { + fieldId: 6, + name: 'field6', + isHidden: true, + isDisabled: true, + properties: { + fieldType: 'Geolocation' + } }] } }) @@ -172,7 +180,8 @@ describe('SchemasService', () => { new FieldDto(2, 'field2', true, true, createProperties('String')), new FieldDto(3, 'field3', true, true, createProperties('Boolean')), new FieldDto(4, 'field4', true, true, createProperties('DateTime')), - new FieldDto(5, 'field5', true, true, createProperties('Json')) + new FieldDto(5, 'field5', true, true, createProperties('Json')), + new FieldDto(6, 'field6', true, true, createProperties('Geolocation')) ])); authService.verifyAll(); diff --git a/src/Squidex/app/shared/services/schemas.service.ts b/src/Squidex/app/shared/services/schemas.service.ts index 03f64df54..131e19757 100644 --- a/src/Squidex/app/shared/services/schemas.service.ts +++ b/src/Squidex/app/shared/services/schemas.service.ts @@ -50,6 +50,9 @@ export function createProperties(fieldType: string, values: Object | null = null case 'Json': properties = new JsonFieldPropertiesDto(undefined, undefined, undefined, false, false, false); break; + case 'Geolocation': + properties = new GeolocationFieldPropertiesDto(undefined, undefined, undefined, false, false, false, 'Map'); + break; default: throw 'Invalid properties type'; } @@ -182,6 +185,19 @@ export class BooleanFieldPropertiesDto extends FieldPropertiesDto { } } +export class GeolocationFieldPropertiesDto extends FieldPropertiesDto { + constructor(label: string | undefined, hints: string | undefined, placeholder: string | undefined, + isRequired: boolean, + isListField: boolean, + isLocalizable: boolean, + public readonly editor: string + ) { + super(label, hints, placeholder, isRequired, isListField, isLocalizable); + + this['fieldType'] = 'Geolocation'; + } +} + export class JsonFieldPropertiesDto extends FieldPropertiesDto { constructor(label: string | undefined, hints: string | undefined, placeholder: string | undefined, isRequired: boolean, diff --git a/src/Squidex/app/theme/icomoon/demo.html b/src/Squidex/app/theme/icomoon/demo.html index 027eedbc0..441bf2912 100644 --- a/src/Squidex/app/theme/icomoon/demo.html +++ b/src/Squidex/app/theme/icomoon/demo.html @@ -64,22 +64,6 @@ -
-
- - - - icon-control-Date -
-
- - -
-
- liga: - -
-

Grid Size: 14

@@ -857,6 +841,38 @@
+
+
+ + + + icon-control-Map +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-type-Geolocation +
+
+ + +
+
+ liga: + +
+