From 7cd8c6a747b2201570700e3d76a70e5a233523a3 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Tue, 21 Feb 2017 19:14:35 +0100 Subject: [PATCH] Temp --- src/Squidex.Core/Schemas/DateTimeField.cs | 27 ++- .../Schemas/DateTimeFieldEditor.cs | 4 +- .../Schemas/DateTimeFieldProperties.cs | 16 +- .../pages/clients/clients-page.component.html | 5 + .../angular/date-time-editor.component.html | 15 ++ .../angular/date-time-editor.component.scss | 36 +++ .../angular/date-time-editor.component.ts | 215 ++++++++++++++++++ src/Squidex/app/framework/declarations.ts | 1 + src/Squidex/app/framework/module.ts | 3 + .../app/framework/utils/date-helper.spec.ts | 2 +- .../app/framework/utils/date-helper.ts | 2 +- src/Squidex/app/framework/utils/date-time.ts | 2 +- src/Squidex/app/framework/utils/duration.ts | 2 +- src/Squidex/app/theme/vendor.scss | 3 + src/Squidex/package.json | 1 + .../Schemas/DateTimeFieldPropertiesTests.cs | 5 +- .../Schemas/DateTimeFieldTests.cs | 9 +- .../Schemas/SchemaValidationTests.cs | 8 +- 18 files changed, 330 insertions(+), 26 deletions(-) create mode 100644 src/Squidex/app/framework/angular/date-time-editor.component.html create mode 100644 src/Squidex/app/framework/angular/date-time-editor.component.scss create mode 100644 src/Squidex/app/framework/angular/date-time-editor.component.ts diff --git a/src/Squidex.Core/Schemas/DateTimeField.cs b/src/Squidex.Core/Schemas/DateTimeField.cs index 56812c388..1ba98ff46 100644 --- a/src/Squidex.Core/Schemas/DateTimeField.cs +++ b/src/Squidex.Core/Schemas/DateTimeField.cs @@ -12,9 +12,13 @@ using Microsoft.OData.Edm; using Microsoft.OData.Edm.Library; using Newtonsoft.Json.Linq; using NJsonSchema; +using NodaTime; +using NodaTime.Text; using Squidex.Core.Schemas.Validators; using Squidex.Infrastructure; +// ReSharper disable ConvertIfStatementToSwitchStatement + namespace Squidex.Core.Schemas { [TypeName("DateTimeField")] @@ -34,13 +38,30 @@ namespace Squidex.Core.Schemas if (Properties.MinValue.HasValue || Properties.MaxValue.HasValue) { - yield return new RangeValidator(Properties.MinValue, Properties.MaxValue); + yield return new RangeValidator(Properties.MinValue, Properties.MaxValue); } } protected override object ConvertValue(JToken value) { - return (DateTimeOffset?)value; + if (value.Type == JTokenType.String) + { + var parseResult = InstantPattern.General.Parse(value.ToString()); + + if (!parseResult.Success) + { + throw parseResult.Exception; + } + + return parseResult.Value; + } + + if (value.Type == JTokenType.Null) + { + return null; + } + + throw new InvalidCastException("Invalid json type, expected string."); } protected override void PrepareJsonSchema(JsonProperty jsonProperty) @@ -51,7 +72,7 @@ namespace Squidex.Core.Schemas protected override IEdmTypeReference CreateEdmType() { - return EdmCoreModel.Instance.GetPrimitive(EdmPrimitiveTypeKind.DateTimeOffset, !Properties.IsRequired); + return EdmCoreModel.Instance.GetPrimitive(EdmPrimitiveTypeKind.Date, !Properties.IsRequired); } } } diff --git a/src/Squidex.Core/Schemas/DateTimeFieldEditor.cs b/src/Squidex.Core/Schemas/DateTimeFieldEditor.cs index b894d5c9b..4b352ff73 100644 --- a/src/Squidex.Core/Schemas/DateTimeFieldEditor.cs +++ b/src/Squidex.Core/Schemas/DateTimeFieldEditor.cs @@ -11,8 +11,6 @@ namespace Squidex.Core.Schemas public enum DateTimeFieldEditor { Date, - DateWithTimezone, - DateTime, - DateTimeWithTimezone + DateTime } } diff --git a/src/Squidex.Core/Schemas/DateTimeFieldProperties.cs b/src/Squidex.Core/Schemas/DateTimeFieldProperties.cs index 2867eb639..eefcca87d 100644 --- a/src/Squidex.Core/Schemas/DateTimeFieldProperties.cs +++ b/src/Squidex.Core/Schemas/DateTimeFieldProperties.cs @@ -6,9 +6,9 @@ // All rights reserved. // ========================================================================== -using System; using System.Collections.Generic; using Newtonsoft.Json.Linq; +using NodaTime; using Squidex.Infrastructure; namespace Squidex.Core.Schemas @@ -17,11 +17,11 @@ namespace Squidex.Core.Schemas public sealed class DateTimeFieldProperties : FieldProperties { private DateTimeFieldEditor editor; - private DateTimeOffset? maxValue; - private DateTimeOffset? minValue; - private DateTimeOffset? defaultValue; + private Instant? maxValue; + private Instant? minValue; + private Instant? defaultValue; - public DateTimeOffset? MaxValue + public Instant? MaxValue { get { return maxValue; } set @@ -32,7 +32,7 @@ namespace Squidex.Core.Schemas } } - public DateTimeOffset? MinValue + public Instant? MinValue { get { return minValue; } set @@ -43,7 +43,7 @@ namespace Squidex.Core.Schemas } } - public DateTimeOffset? DefaultValue + public Instant? DefaultValue { get { return defaultValue; } set @@ -67,7 +67,7 @@ namespace Squidex.Core.Schemas public override JToken GetDefaultValue() { - return DefaultValue; + return DefaultValue != null ? DefaultValue.ToString() : null; } protected override IEnumerable ValidateCore() diff --git a/src/Squidex/app/features/settings/pages/clients/clients-page.component.html b/src/Squidex/app/features/settings/pages/clients/clients-page.component.html index 16d74a829..5fed3b2a4 100644 --- a/src/Squidex/app/features/settings/pages/clients/clients-page.component.html +++ b/src/Squidex/app/features/settings/pages/clients/clients-page.component.html @@ -13,6 +13,11 @@
+
+
+
+
+
No client created yet.
diff --git a/src/Squidex/app/framework/angular/date-time-editor.component.html b/src/Squidex/app/framework/angular/date-time-editor.component.html new file mode 100644 index 000000000..67a9a55c1 --- /dev/null +++ b/src/Squidex/app/framework/angular/date-time-editor.component.html @@ -0,0 +1,15 @@ +
+
+
+ +
+
+ +
+
+ +
+
+
diff --git a/src/Squidex/app/framework/angular/date-time-editor.component.scss b/src/Squidex/app/framework/angular/date-time-editor.component.scss new file mode 100644 index 000000000..d379ab3e5 --- /dev/null +++ b/src/Squidex/app/framework/angular/date-time-editor.component.scss @@ -0,0 +1,36 @@ +@import '_mixins'; +@import '_vars'; + +$form-color: #fff; + +.form-control { + &[readonly] { + background: $form-color; + } +} + +.date-group { + & { + padding-right: .5rem; + } + + .form-control { + width: 8rem; + } +} + +.time-group { + & { + padding-right: .5rem; + } + + .form-control { + width: 5rem; + } +} + +.timezone-group { + .form-control { + width: 8.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 new file mode 100644 index 000000000..bef37912b --- /dev/null +++ b/src/Squidex/app/framework/angular/date-time-editor.component.ts @@ -0,0 +1,215 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Sebastian Stehle. All rights reserved + */ + +import { AfterViewInit, Component, forwardRef, ElementRef, Input, OnInit, ViewChild } from '@angular/core'; +import { ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR } from '@angular/forms'; +import * as moment from 'moment'; + +let Pikaday = require('pikaday/pikaday'); + + +/* tslint:disable:no-empty */ + +const NOOP = () => { }; + +export const SQX_DATE_TIME_EDITOR_CONTROL_VALUE_ACCESSOR: any = { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => DateTimeEditorComponent), + multi: true +}; + +const TIMEZONES: any[] = [ + { label: 'UTC-13:00', value: -780 }, + { label: 'UTC-12:00', value: -720 }, + { label: 'UTC-11:00', value: -660 }, + { label: 'UTC-10:00', value: -600 }, + { label: 'UTC-09:30', value: -570 }, + { label: 'UTC-09:00', value: -540 }, + { label: 'UTC-08:00', value: -480 }, + { label: 'UTC-07:00', value: -420 }, + { label: 'UTC-06:00', value: -360 }, + { label: 'UTC-05:00', value: -300 }, + { label: 'UTC-04:30', value: -270 }, + { label: 'UTC-04:00', value: -240 }, + { label: 'UTC-03:30', value: -210 }, + { label: 'UTC-03:00', value: -180 }, + { label: 'UTC-02:00', value: -120 }, + { label: 'UTC-01:00', value: -60 }, + { label: 'UTC+00:00', value: 0 }, + { label: 'UTC+01:00', value: 60 }, + { label: 'UTC+02:00', value: 120 }, + { label: 'UTC+03:00', value: 180 }, + { label: 'UTC+03:30', value: 210 }, + { label: 'UTC+04:00', value: 240 }, + { label: 'UTC+04:30', value: 270 }, + { label: 'UTC+05:00', value: 300 }, + { label: 'UTC+05:30', value: 330 }, + { label: 'UTC+05:45', value: 345 }, + { label: 'UTC+06:00', value: 360 }, + { label: 'UTC+06:30', value: 390 }, + { label: 'UTC+07:00', value: 420 }, + { label: 'UTC+08:00', value: 480 }, + { label: 'UTC+08:45', value: 425 }, + { label: 'UTC+09:00', value: 540 }, + { label: 'UTC+09:30', value: 570 }, + { label: 'UTC+10:00', value: 600 }, + { label: 'UTC+10:30', value: 630 }, + { label: 'UTC+11:00', value: 660 }, + { label: 'UTC+11:30', value: 690 }, + { label: 'UTC+12:00', value: 720 }, + { label: 'UTC+12:45', value: 765 }, + { label: 'UTC+13:00', value: 780 }, + { label: 'UTC+14:00', value: 840 } +]; + +@Component({ + selector: 'sqx-date-time-editor', + styleUrls: ['./date-time-editor.component.scss'], + templateUrl: './date-time-editor.component.html', + providers: [SQX_DATE_TIME_EDITOR_CONTROL_VALUE_ACCESSOR] +}) +export class DateTimeEditorComponent implements ControlValueAccessor, OnInit, AfterViewInit { + private picker: any; + private time: any; + private date: any; + private offset: number; + private suppressEvents = false; + private changeCallback: (value: any) => void = NOOP; + private touchedCallback: () => void = NOOP; + + public get showTime() { + return this.mode === 'DateTime' || this.mode === 'DateTimeWithTimezone'; + } + + public get showTimezone() { + return this.mode === 'DateWithTimezone' || this.mode === 'DateTimeWithTimezone'; + } + + public timezones = TIMEZONES; + + public timeControl = + new FormControl(); + + public timeZoneControl = + new FormControl(); + + public isDisabled = false; + + @Input() + public mode: string; + + @ViewChild('dateInput') + public dateInput: ElementRef; + + public ngOnInit() { + this.timeControl.valueChanges.subscribe(value => { + const time = moment(value, 'HH:mm:ss'); + + this.time = moment(); + this.time.hours(time.hours()).minutes(time.minutes()).seconds(time.seconds()); + + this.updateValue(); + }); + + this.timeZoneControl.valueChanges.subscribe(value => { + this.offset = value; + + this.updateValue(); + this.touched(); + }); + } + + public writeValue(value: any) { + const parsed = (moment.parseZone(value) || moment()); + + this.time = moment(parsed); + this.date = moment(parsed); + + this.offset = parsed.utcOffset(); + + this.updateControls(); + } + + public setDisabledState(isDisabled: boolean): void { + this.isDisabled = isDisabled; + + if (isDisabled) { + this.timeControl.disable(); + this.timeZoneControl.disable(); + } else { + this.timeControl.enable(); + this.timeZoneControl.enable(); + } + } + + public registerOnChange(fn: any) { + this.changeCallback = fn; + } + + public registerOnTouched(fn: any) { + this.touchedCallback = fn; + } + + public ngAfterViewInit() { + this.picker = new Pikaday({ + field: this.dateInput.nativeElement, + format: 'YYYY-MM-DD', + onSelect: () => { + if (this.suppressEvents) { + return; + } + + const date = this.picker.getMoment(); + + this.date.years(date.years()).months(date.months()).dates(date.dates()); + + this.updateValue(); + this.touched(); + } + }); + + this.updateControls(); + } + + public touched() { + this.touchedCallback(); + } + + private updateValue() { + let result = this.date.format('YYYY-MM-DD'); + + if (this.showTime) { + result += 'T'; + result += this.time.format('HH:mm:ss'); + } + + if (this.showTimezone) { + result += moment().utcOffset(this.offset).format('Z'); + } else if (this.showTime) { + result += 'Z'; + } + + this.changeCallback(result); + } + + private updateControls() { + if (!this.date) { + return; + } + + this.suppressEvents = true; + + this.timeControl.setValue(this.time.format('HH:mm'), { emitEvent: false }); + this.timeZoneControl.setValue(this.offset, { emitEvent: false }); + + if (this.picker) { + this.picker.setMoment(this.date); + } + + this.suppressEvents = false; + } +} \ No newline at end of file diff --git a/src/Squidex/app/framework/declarations.ts b/src/Squidex/app/framework/declarations.ts index 70c4dd554..f64b76af1 100644 --- a/src/Squidex/app/framework/declarations.ts +++ b/src/Squidex/app/framework/declarations.ts @@ -11,6 +11,7 @@ export * from './angular/validators'; export * from './angular/cloak.directive'; export * from './angular/control-errors.component'; export * from './angular/copy.directive'; +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'; diff --git a/src/Squidex/app/framework/module.ts b/src/Squidex/app/framework/module.ts index 179040f1a..cdff61d46 100644 --- a/src/Squidex/app/framework/module.ts +++ b/src/Squidex/app/framework/module.ts @@ -17,6 +17,7 @@ import { CloakDirective, ControlErrorsComponent, CopyDirective, + DateTimeEditorComponent, DayOfWeekPipe, DayPipe, DisplayNamePipe, @@ -58,6 +59,7 @@ import { CloakDirective, ControlErrorsComponent, CopyDirective, + DateTimeEditorComponent, DayOfWeekPipe, DayPipe, DisplayNamePipe, @@ -84,6 +86,7 @@ import { CloakDirective, ControlErrorsComponent, CopyDirective, + DateTimeEditorComponent, DayOfWeekPipe, DayPipe, DisplayNamePipe, diff --git a/src/Squidex/app/framework/utils/date-helper.spec.ts b/src/Squidex/app/framework/utils/date-helper.spec.ts index 3458cdac7..6532f5ee0 100644 --- a/src/Squidex/app/framework/utils/date-helper.spec.ts +++ b/src/Squidex/app/framework/utils/date-helper.spec.ts @@ -5,7 +5,7 @@ * Copyright (c) Sebastian Stehle. All rights reserved */ -import *as moment from 'moment'; +import * as moment from 'moment'; import { DateHelper } from './../'; diff --git a/src/Squidex/app/framework/utils/date-helper.ts b/src/Squidex/app/framework/utils/date-helper.ts index 87d62053c..5e18bd7d7 100644 --- a/src/Squidex/app/framework/utils/date-helper.ts +++ b/src/Squidex/app/framework/utils/date-helper.ts @@ -5,7 +5,7 @@ * Copyright (c) Sebastian Stehle. All rights reserved */ -import *as moment from 'moment'; +import * as moment from 'moment'; export module DateHelper { let momentInstance: any; diff --git a/src/Squidex/app/framework/utils/date-time.ts b/src/Squidex/app/framework/utils/date-time.ts index 45df8570d..8338bf11a 100644 --- a/src/Squidex/app/framework/utils/date-time.ts +++ b/src/Squidex/app/framework/utils/date-time.ts @@ -5,7 +5,7 @@ * Copyright (c) Sebastian Stehle. All rights reserved */ -import *as moment from 'moment'; +import * as moment from 'moment'; export class DateTime { public get raw(): Date { diff --git a/src/Squidex/app/framework/utils/duration.ts b/src/Squidex/app/framework/utils/duration.ts index 3474eed9e..53fcf6793 100644 --- a/src/Squidex/app/framework/utils/duration.ts +++ b/src/Squidex/app/framework/utils/duration.ts @@ -5,7 +5,7 @@ * Copyright (c) Sebastian Stehle. All rights reserved */ -import *as moment from 'moment'; +import * as moment from 'moment'; import { DateTime } from './date-time'; diff --git a/src/Squidex/app/theme/vendor.scss b/src/Squidex/app/theme/vendor.scss index 42e074015..8037bdf3d 100644 --- a/src/Squidex/app/theme/vendor.scss +++ b/src/Squidex/app/theme/vendor.scss @@ -3,6 +3,9 @@ // Bootstrap @import './../../node_modules/bootstrap/scss/bootstrap.scss'; +// Pikaday +@import './../../node_modules/pikaday/css/pikaday.css'; + // Bootstrap Overrides @import '_bootstrap.scss'; diff --git a/src/Squidex/package.json b/src/Squidex/package.json index 569388d60..315606576 100644 --- a/src/Squidex/package.json +++ b/src/Squidex/package.json @@ -28,6 +28,7 @@ "moment": "^2.17.1", "mousetrap": "^1.6.0", "oidc-client": "^1.2.2", + "pikaday": "^1.5.1", "rxjs": "5.0.3", "zone.js": "^0.7.6" }, diff --git a/tests/Squidex.Core.Tests/Schemas/DateTimeFieldPropertiesTests.cs b/tests/Squidex.Core.Tests/Schemas/DateTimeFieldPropertiesTests.cs index 31ff30be0..067b08a35 100644 --- a/tests/Squidex.Core.Tests/Schemas/DateTimeFieldPropertiesTests.cs +++ b/tests/Squidex.Core.Tests/Schemas/DateTimeFieldPropertiesTests.cs @@ -11,6 +11,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; using FluentAssertions; +using NodaTime; using Squidex.Infrastructure; using Xunit; @@ -133,9 +134,9 @@ namespace Squidex.Core.Schemas } } - private static DateTimeOffset FutureDays(int days) + private static Instant FutureDays(int days) { - return DateTimeOffset.UtcNow.AddDays(days); + return SystemClock.Instance.GetCurrentInstant().Plus(Duration.FromDays(days)); } } } \ No newline at end of file diff --git a/tests/Squidex.Core.Tests/Schemas/DateTimeFieldTests.cs b/tests/Squidex.Core.Tests/Schemas/DateTimeFieldTests.cs index 03a0aad2b..52105eab0 100644 --- a/tests/Squidex.Core.Tests/Schemas/DateTimeFieldTests.cs +++ b/tests/Squidex.Core.Tests/Schemas/DateTimeFieldTests.cs @@ -11,6 +11,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using FluentAssertions; using Newtonsoft.Json.Linq; +using NodaTime; using Xunit; namespace Squidex.Core.Schemas @@ -64,7 +65,7 @@ namespace Squidex.Core.Schemas await sut.ValidateAsync(CreateValue(FutureDays(0)), errors); errors.ShouldBeEquivalentTo( - new[] { $"My-DateTime must be greater than '{DateTimeOffset.UtcNow.AddDays(10)}'" }); + new[] { $"My-DateTime must be greater than '{FutureDays(10)}'" }); } [Fact] @@ -89,14 +90,14 @@ namespace Squidex.Core.Schemas new[] { "My-DateTime is not a valid value" }); } - private static DateTimeOffset FutureDays(int days) + private static Instant FutureDays(int days) { - return DateTimeOffset.UtcNow.AddDays(days); + return SystemClock.Instance.GetCurrentInstant().Plus(Duration.FromDays(days)); } private static JValue CreateValue(object v) { - return new JValue(v); + return v is Instant ? new JValue(v.ToString()) : new JValue(v); } } } diff --git a/tests/Squidex.Core.Tests/Schemas/SchemaValidationTests.cs b/tests/Squidex.Core.Tests/Schemas/SchemaValidationTests.cs index cf85bf50a..ebe848d18 100644 --- a/tests/Squidex.Core.Tests/Schemas/SchemaValidationTests.cs +++ b/tests/Squidex.Core.Tests/Schemas/SchemaValidationTests.cs @@ -10,6 +10,8 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; using FluentAssertions; +using NodaTime; +using NodaTime.Text; using Squidex.Core.Contents; using Squidex.Infrastructure; using Xunit; @@ -288,6 +290,8 @@ namespace Squidex.Core.Schemas [Fact] private void Should_enrich_with_default_values() { + var now = Instant.FromUnixTimeSeconds(SystemClock.Instance.GetCurrentInstant().ToUnixTimeSeconds()); + var schema = Schema.Create("my-schema", new SchemaProperties()) .AddOrUpdateField( @@ -297,7 +301,7 @@ namespace Squidex.Core.Schemas .AddOrUpdateField( new NumberField(3, "my-number", new NumberFieldProperties { DefaultValue = 123 })) .AddOrUpdateField( - new DateTimeField(4, "my-datetime", new DateTimeFieldProperties { DefaultValue = DateTime.Today })); + new DateTimeField(4, "my-datetime", new DateTimeFieldProperties { DefaultValue = now })); var data = new ContentData() @@ -315,7 +319,7 @@ namespace Squidex.Core.Schemas Assert.Equal("DE-String", (string)data["my-string"]["de"]); Assert.Equal("EN-String", (string)data["my-string"]["en"]); - Assert.Equal(DateTime.Today, (DateTime)data["my-datetime"]["iv"]); + Assert.Equal(now, InstantPattern.General.Parse((string)data["my-datetime"]["iv"]).Value); Assert.Equal(true, (bool)data["my-boolean"]["iv"]); }