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 @@
+
+
\ 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.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 @@
diff --git a/src/Squidex/app/theme/icomoon/fonts/icomoon.eot b/src/Squidex/app/theme/icomoon/fonts/icomoon.eot
index 41b69d144..e435a2e1c 100644
Binary files a/src/Squidex/app/theme/icomoon/fonts/icomoon.eot and b/src/Squidex/app/theme/icomoon/fonts/icomoon.eot differ
diff --git a/src/Squidex/app/theme/icomoon/fonts/icomoon.svg b/src/Squidex/app/theme/icomoon/fonts/icomoon.svg
index 101381bb3..254067692 100644
--- a/src/Squidex/app/theme/icomoon/fonts/icomoon.svg
+++ b/src/Squidex/app/theme/icomoon/fonts/icomoon.svg
@@ -34,7 +34,7 @@
-
+
diff --git a/src/Squidex/app/theme/icomoon/fonts/icomoon.ttf b/src/Squidex/app/theme/icomoon/fonts/icomoon.ttf
index 7683b900f..ee9573fbc 100644
Binary files a/src/Squidex/app/theme/icomoon/fonts/icomoon.ttf and b/src/Squidex/app/theme/icomoon/fonts/icomoon.ttf differ
diff --git a/src/Squidex/app/theme/icomoon/fonts/icomoon.woff b/src/Squidex/app/theme/icomoon/fonts/icomoon.woff
index 9d811003e..b5b241e29 100644
Binary files a/src/Squidex/app/theme/icomoon/fonts/icomoon.woff and b/src/Squidex/app/theme/icomoon/fonts/icomoon.woff differ
diff --git a/src/Squidex/app/theme/icomoon/selection.json b/src/Squidex/app/theme/icomoon/selection.json
index b85cf938f..cba2e7013 100644
--- a/src/Squidex/app/theme/icomoon/selection.json
+++ b/src/Squidex/app/theme/icomoon/selection.json
@@ -1370,7 +1370,7 @@
"properties": {
"order": 25,
"id": 13,
- "name": "location",
+ "name": "location, control-Map, type-Geolocation",
"prevSize": 32,
"code": 59675
},
diff --git a/src/Squidex/app/theme/icomoon/style.css b/src/Squidex/app/theme/icomoon/style.css
index 58fc10e50..00a5149ed 100644
--- a/src/Squidex/app/theme/icomoon/style.css
+++ b/src/Squidex/app/theme/icomoon/style.css
@@ -1,10 +1,10 @@
@font-face {
font-family: 'icomoon';
- src: url('fonts/icomoon.eot?y7zcsi');
- src: url('fonts/icomoon.eot?y7zcsi#iefix') format('embedded-opentype'),
- url('fonts/icomoon.ttf?y7zcsi') format('truetype'),
- url('fonts/icomoon.woff?y7zcsi') format('woff'),
- url('fonts/icomoon.svg?y7zcsi#icomoon') format('svg');
+ src: url('fonts/icomoon.eot?gbczp5');
+ src: url('fonts/icomoon.eot?gbczp5#iefix') format('embedded-opentype'),
+ url('fonts/icomoon.ttf?gbczp5') format('truetype'),
+ url('fonts/icomoon.woff?gbczp5') format('woff'),
+ url('fonts/icomoon.svg?gbczp5#icomoon') format('svg');
font-weight: normal;
font-style: normal;
}
@@ -33,9 +33,6 @@
.icon-control-RichText:before {
content: "\e939";
}
-.icon-control-Date:before {
- content: "\e936";
-}
.icon-control-Markdown:before {
content: "\e938";
}
@@ -180,6 +177,12 @@
.icon-location:before {
content: "\e91b";
}
+.icon-control-Map:before {
+ content: "\e91b";
+}
+.icon-type-Geolocation:before {
+ content: "\e91b";
+}
.icon-logo:before {
content: "\e91c";
}